@ticatec/uniface-element 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/README.md +320 -1
  2. package/README_CN.md +392 -0
  3. package/dist/accordion/Accordion.svelte +1 -1
  4. package/dist/action-bar/ActionBar.svelte +7 -1
  5. package/dist/attachment-files/FileRender.svelte +1 -1
  6. package/dist/attachment-files/FileUploadBar.svelte +7 -6
  7. package/dist/attachment-files/FileUploadPanel.svelte +2 -2
  8. package/dist/base-calendar/DateContext.js +5 -4
  9. package/dist/base-calendar/YearPickupPanel.svelte +4 -4
  10. package/dist/card/Card.svelte +1 -1
  11. package/dist/card/Card.svelte.d.ts +1 -1
  12. package/dist/cascade-options-select/CascadeOptionSelect.svelte +2 -3
  13. package/dist/cascade-options-select/CascadeOptionSelect.svelte.d.ts +46 -1
  14. package/dist/cascade-options-select/CascadePanel.svelte +1 -1
  15. package/dist/checkbox/CheckBox.svelte +1 -1
  16. package/dist/color-picker/ColorPicker.svelte +9 -9
  17. package/dist/color-picker/ColorPicker.svelte.d.ts +0 -2
  18. package/dist/color-picker/ColorPickerPanel.svelte +11 -15
  19. package/dist/color-picker/ColorPickerPanel.svelte.d.ts +1 -1
  20. package/dist/common/CommonPicker.svelte +2 -2
  21. package/dist/common/CommonPicker.svelte.d.ts +1 -1
  22. package/dist/common/MouseClickHandler.d.ts +1 -1
  23. package/dist/common/Popover.svelte +28 -6
  24. package/dist/common/Popover.svelte.d.ts +9 -3
  25. package/dist/common/uniface-utils.js +6 -4
  26. package/dist/common-editor/CommonEditor.svelte +1 -1
  27. package/dist/common-editor/CommonEditor.svelte.d.ts +1 -1
  28. package/dist/common-editor/NumberInput.svelte +1 -1
  29. package/dist/common-editor/NumberInput.svelte.d.ts +1 -1
  30. package/dist/concise-data-table/ConciseListTable.svelte +7 -2
  31. package/dist/concise-data-table/ConciseListTable.svelte.d.ts +1 -0
  32. package/dist/concise-data-table/DataRow.svelte +1 -1
  33. package/dist/concise-data-table/TableOptions.d.ts +1 -1
  34. package/dist/data-table/DataTable.svelte +1 -1
  35. package/dist/data-table/parts/ActionsPanel.svelte +4 -3
  36. package/dist/data-table/parts/ActionsRow.svelte +3 -3
  37. package/dist/data-table/parts/ContentPanel.svelte +3 -2
  38. package/dist/data-table/parts/FixedColumnsPanel.svelte +0 -1
  39. package/dist/data-table/parts/FixedColumnsPanel.svelte.d.ts +1 -2
  40. package/dist/data-table/parts/FixedHeaderPanel.svelte +2 -2
  41. package/dist/data-table/parts/FixedRow.svelte +2 -1
  42. package/dist/data-table/parts/FixedRow.svelte.d.ts +1 -1
  43. package/dist/data-table/parts/HrefCell.svelte +23 -2
  44. package/dist/data-table/parts/PopupMenu.svelte +25 -2
  45. package/dist/data-table/parts/PopupMenu.svelte.d.ts +8 -1
  46. package/dist/date-picker/DatePicker.svelte +0 -1
  47. package/dist/date-picker/DateTimePicker.svelte +3 -3
  48. package/dist/date-picker/ScrollBar.svelte +4 -7
  49. package/dist/date-range/DateRangeEditor.svelte +1 -1
  50. package/dist/dialog/CommonDialog.svelte +8 -5
  51. package/dist/dialog/CommonDialog.svelte.d.ts +2 -1
  52. package/dist/dialog/Dialog.svelte +37 -12
  53. package/dist/dialog/Dialog.svelte.d.ts +4 -3
  54. package/dist/dialog/DialogBoard.svelte +11 -8
  55. package/dist/dialog/DialogWrapper.svelte +10 -0
  56. package/dist/{common-editor/DateInput.svelte.d.ts → dialog/DialogWrapper.svelte.d.ts} +6 -13
  57. package/dist/dialog/Dialogs.js +1 -1
  58. package/dist/dialog/ModalResult.d.ts +5 -0
  59. package/dist/dialog/ModalResult.js +5 -0
  60. package/dist/drawer/Drawer.svelte +1 -1
  61. package/dist/form-field/FormField.svelte +2 -1
  62. package/dist/form-field/FormField.svelte.d.ts +1 -0
  63. package/dist/form-panel/flex-row-form/FormContainer.svelte +1 -1
  64. package/dist/form-panel/grid-form/EmptyCell.svelte +8 -7
  65. package/dist/form-panel/grid-form/EmptyCell.svelte.d.ts +2 -2
  66. package/dist/form-panel/grid-form/GridField.svelte +1 -1
  67. package/dist/i18nRes/i18nRes.d.ts +2 -0
  68. package/dist/i18nRes/i18nRes.js +44 -0
  69. package/dist/i18nRes/index.d.ts +2 -0
  70. package/dist/i18nRes/index.js +2 -0
  71. package/dist/image-files/ImageFilesField.svelte +1 -1
  72. package/dist/indicator/IndicatorBoard.svelte +1 -1
  73. package/dist/inline-cell-editor/InvalidEditor.svelte +1 -0
  74. package/dist/input-options-select/InputOptionsSelect.svelte +6 -6
  75. package/dist/input-options-select/InputOptionsSelect.svelte.d.ts +1 -1
  76. package/dist/layout/flex/FlexBlock.svelte +1 -1
  77. package/dist/lookup-editor/LookupEditor.svelte +1 -1
  78. package/dist/memo-editor/MemoEditor.svelte +2 -2
  79. package/dist/message-box/IMessageBox.d.ts +2 -4
  80. package/dist/message-box/IMessageBox.js +4 -6
  81. package/dist/message-box/MessageBoxBoard.svelte +1 -3
  82. package/dist/options-multi-select/OptionsMultiSelect.svelte +1 -1
  83. package/dist/prompts-text-editor/PromptsTextEditor.svelte +2 -2
  84. package/dist/prompts-text-editor/PromptsTextEditor.svelte.d.ts +1 -1
  85. package/dist/property-editor/PropertyEditor.svelte +3 -3
  86. package/dist/text-editor/PasswordEditor.svelte +1 -1
  87. package/dist/transfer/Transfer.svelte +17 -12
  88. package/dist/transfer/Transfer.svelte.d.ts +0 -1
  89. package/dist/unit-number-editor/UnitNumberEditor.svelte +2 -2
  90. package/docs/buttons/cn/ActionBar.md +459 -0
  91. package/docs/buttons/cn/Button.md +228 -0
  92. package/docs/buttons/cn/IconButton.md +294 -0
  93. package/docs/buttons/cn/README.md +600 -0
  94. package/docs/buttons/cn/TextButton.md +163 -0
  95. package/docs/buttons/en/ActionBar.md +459 -0
  96. package/docs/buttons/en/Button.md +228 -0
  97. package/docs/buttons/en/IconButton.md +294 -0
  98. package/docs/buttons/en/README.md +600 -0
  99. package/docs/buttons/en/TextButton.md +163 -0
  100. package/docs/containers/cn/accordion.md +622 -0
  101. package/docs/containers/cn/box.md +445 -0
  102. package/docs/containers/cn/card.md +404 -0
  103. package/docs/containers/cn/page.md +576 -0
  104. package/docs/containers/cn/tabs.md +686 -0
  105. package/docs/containers/en/accordion.md +622 -0
  106. package/docs/containers/en/box.md +445 -0
  107. package/docs/containers/en/card.md +404 -0
  108. package/docs/containers/en/page.md +576 -0
  109. package/docs/containers/en/tabs.md +686 -0
  110. package/docs/data-display/cn/concise-data-table.md +731 -0
  111. package/docs/data-display/cn/listbox.md +786 -0
  112. package/docs/data-display/cn/treeview.md +863 -0
  113. package/docs/data-display/en/concise-data-table.md +732 -0
  114. package/docs/data-display/en/listbox.md +786 -0
  115. package/docs/data-display/en/treeview.md +863 -0
  116. package/docs/data-table/cn/README.md +596 -0
  117. package/docs/data-table/en/README.md +596 -0
  118. package/docs/dialog/cn/README.md +767 -0
  119. package/docs/dialog/en/README.md +767 -0
  120. package/docs/form/cn/README.md +847 -0
  121. package/docs/form/en/README.md +847 -0
  122. package/docs/form-components/cn/README.md +495 -0
  123. package/docs/form-components/cn/attachmentfilesfield.md +757 -0
  124. package/docs/form-components/cn/cascade-options-select.md +374 -0
  125. package/docs/form-components/cn/checkbox.md +58 -0
  126. package/docs/form-components/cn/date-picker.md +138 -0
  127. package/docs/form-components/cn/datetime-picker.md +228 -0
  128. package/docs/form-components/cn/group-checkbox.md +405 -0
  129. package/docs/form-components/cn/group-radiobox.md +525 -0
  130. package/docs/form-components/cn/imagefilesfield.md +651 -0
  131. package/docs/form-components/cn/input-options-select.md +250 -0
  132. package/docs/form-components/cn/lookup-editor.md +406 -0
  133. package/docs/form-components/cn/memoeditor.md +409 -0
  134. package/docs/form-components/cn/number-editor.md +261 -0
  135. package/docs/form-components/cn/options-multi-select.md +177 -0
  136. package/docs/form-components/cn/options-select.md +83 -0
  137. package/docs/form-components/cn/passwordeditor.md +538 -0
  138. package/docs/form-components/cn/prompts-text-editor.md +241 -0
  139. package/docs/form-components/cn/radiobutton.md +84 -0
  140. package/docs/form-components/cn/switch.md +178 -0
  141. package/docs/form-components/cn/text-editor.md +142 -0
  142. package/docs/form-components/cn/time-editor.md +159 -0
  143. package/docs/form-components/cn/unit-number-editor.md +164 -0
  144. package/docs/form-components/en/README.md +463 -0
  145. package/docs/form-components/en/attachmentfilesfield.md +757 -0
  146. package/docs/form-components/en/cascade-options-select.md +358 -0
  147. package/docs/form-components/en/checkbox.md +59 -0
  148. package/docs/form-components/en/date-picker.md +138 -0
  149. package/docs/form-components/en/datetime-picker.md +228 -0
  150. package/docs/form-components/en/group-checkbox.md +395 -0
  151. package/docs/form-components/en/group-radiobox.md +498 -0
  152. package/docs/form-components/en/imagefilesfield.md +651 -0
  153. package/docs/form-components/en/input-options-select.md +239 -0
  154. package/docs/form-components/en/lookup-editor.md +389 -0
  155. package/docs/form-components/en/memoeditor.md +409 -0
  156. package/docs/form-components/en/number-editor.md +249 -0
  157. package/docs/form-components/en/options-multi-select.md +177 -0
  158. package/docs/form-components/en/options-select.md +83 -0
  159. package/docs/form-components/en/passwordeditor.md +538 -0
  160. package/docs/form-components/en/prompts-text-editor.md +241 -0
  161. package/docs/form-components/en/radiobutton.md +84 -0
  162. package/docs/form-components/en/switch.md +180 -0
  163. package/docs/form-components/en/text-editor.md +142 -0
  164. package/docs/form-components/en/time-editor.md +159 -0
  165. package/docs/form-components/en/unit-number-editor.md +164 -0
  166. package/docs/form-layout/cn/criteria-field.md +652 -0
  167. package/docs/form-layout/cn/flex-form.md +454 -0
  168. package/docs/form-layout/cn/flex-row-form.md +542 -0
  169. package/docs/form-layout/cn/form-field.md +923 -0
  170. package/docs/form-layout/cn/grid-form.md +676 -0
  171. package/docs/form-layout/en/criteria-field.md +652 -0
  172. package/docs/form-layout/en/flex-form.md +454 -0
  173. package/docs/form-layout/en/flex-row-form.md +542 -0
  174. package/docs/form-layout/en/form-field.md +923 -0
  175. package/docs/form-layout/en/grid-form.md +676 -0
  176. package/docs/global/cn/README.md +534 -0
  177. package/docs/global/en/README.md +534 -0
  178. package/docs/layout/cn/classiclayout.md +460 -0
  179. package/docs/layout/cn/headerlayout.md +524 -0
  180. package/docs/layout/cn/sidebarlayout.md +263 -0
  181. package/docs/layout/en/classiclayout.md +951 -0
  182. package/docs/layout/en/headerlayout.md +524 -0
  183. package/docs/layout/en/sidebarlayout.md +262 -0
  184. package/docs/misc/cn/README.md +1217 -0
  185. package/docs/misc/en/README.md +1217 -0
  186. package/docs/navigation/cn/breadcrumb.md +291 -0
  187. package/docs/navigation/cn/navigator.md +322 -0
  188. package/docs/navigation/cn/navigatormenu.md +512 -0
  189. package/docs/navigation/cn/progressbar.md +199 -0
  190. package/docs/navigation/cn/progressstepbar.md +299 -0
  191. package/docs/navigation/en/breadcrumb.md +291 -0
  192. package/docs/navigation/en/navigator.md +322 -0
  193. package/docs/navigation/en/navigatormenu.md +512 -0
  194. package/docs/navigation/en/progressbar.md +199 -0
  195. package/docs/navigation/en/progressstepbar.md +299 -0
  196. package/docs/search-filters/cn/daterange.md +1156 -0
  197. package/docs/search-filters/cn/numberrange.md +688 -0
  198. package/docs/search-filters/cn/searchbox.md +1582 -0
  199. package/docs/search-filters/en/daterange.md +1156 -0
  200. package/docs/search-filters/en/numberrange.md +688 -0
  201. package/docs/search-filters/en/searchbox.md +1582 -0
  202. package/package.json +53 -13
  203. package/dist/common-editor/DateInput.svelte +0 -27
  204. package/dist/i18n_resources/index.d.ts +0 -3
  205. package/dist/i18n_resources/index.js +0 -3
  206. /package/dist/{i18n_resources → i18nRes}/uniface_cn_resource.d.ts +0 -0
  207. /package/dist/{i18n_resources → i18nRes}/uniface_cn_resource.js +0 -0
  208. /package/dist/{i18n_resources → i18nRes}/uniface_en_resource.d.ts +0 -0
  209. /package/dist/{i18n_resources → i18nRes}/uniface_en_resource.js +0 -0
@@ -0,0 +1,863 @@
1
+ # TreeView 树形视图组件
2
+
3
+ 一个分层数据显示组件,可渲染具有可展开/折叠节点、懒加载支持和交互式节点选择的树形结构。
4
+
5
+ ## 概述
6
+
7
+ TreeView 组件为显示分层数据结构提供了复杂的界面。它支持节点展开/折叠、大数据集的懒加载、自定义节点渲染、上下文菜单和灵活的节点选择。非常适合文件系统、组织结构图、菜单结构和任何分层数据可视化。
8
+
9
+ ## 安装
10
+
11
+ ```typescript
12
+ import TreeView from '@ticatec/uniface-element/TreeView';
13
+ import type { LazyLoader, NodeVisibleFun, OnNodeSelectionChange, LoadChildrenFun } from '@ticatec/uniface-element/TreeView';
14
+ import type { TreeNode, CheckIsDirectory, GetText } from '@ticatec/uniface-element/TreeNodes';
15
+ ```
16
+
17
+ ## 基本用法
18
+
19
+ ```svelte
20
+ <script lang="ts">
21
+ import TreeView from '@ticatec/uniface-element/TreeView';
22
+ import type { TreeNode, CheckIsDirectory } from '@ticatec/uniface-element/TreeNodes';
23
+
24
+ // 示例分层数据
25
+ let treeData: TreeNode<any>[] = [
26
+ {
27
+ item: { id: '1', name: '根文件夹', type: 'folder' },
28
+ expand: true,
29
+ children: [
30
+ {
31
+ item: { id: '2', name: '文档', type: 'folder' },
32
+ expand: false,
33
+ children: [
34
+ { item: { id: '3', name: 'file1.txt', type: 'file' } },
35
+ { item: { id: '4', name: 'file2.pdf', type: 'file' } }
36
+ ]
37
+ },
38
+ {
39
+ item: { id: '5', name: '图片', type: 'folder' },
40
+ expand: false,
41
+ children: [
42
+ { item: { id: '6', name: 'photo.jpg', type: 'file' } }
43
+ ]
44
+ }
45
+ ]
46
+ }
47
+ ];
48
+
49
+ const checkIsDirectory: CheckIsDirectory<any> = (node) => {
50
+ return node.item.type === 'folder';
51
+ };
52
+
53
+ let activeNode = null;
54
+ </script>
55
+
56
+ <TreeView
57
+ nodes={treeData}
58
+ textField="name"
59
+ {checkIsDirectory}
60
+ bind:activeNode
61
+ />
62
+ ```
63
+
64
+ ## 属性
65
+
66
+ | 属性 | 类型 | 默认值 | 描述 |
67
+ |------|------|--------|------|
68
+ | `nodes` | `Array<TreeNode<any>>` | - | 要显示的树节点数组 |
69
+ | `textField` | `string \| GetText<any>` | - | 获取节点文本的字段名或函数 |
70
+ | `style` | `string` | `""` | 树容器的自定义 CSS 样式 |
71
+ | `lazyLoader` | `LazyLoader \| null` | `null` | 懒加载配置 |
72
+ | `activeNode` | `any` | `null` | 当前活动/选中的节点 |
73
+ | `class` | `string` | `""` | 额外的 CSS 类名 |
74
+ | `isVisible` | `NodeVisibleFun` | `null` | 确定节点可见性的函数 |
75
+ | `onSelectionChange` | `OnNodeSelectionChange` | `null` | 节点选择变更回调 |
76
+ | `onContextMenu` | `OnContextMenu` | `null` | 上下文菜单回调 |
77
+ | `checkIsDirectory` | `CheckIsDirectory<any>` | - | 检查节点是否为目录的函数 |
78
+
79
+ ## 类型定义
80
+
81
+ ```typescript
82
+ /**
83
+ * 树节点结构
84
+ */
85
+ export type TreeNode<T> = {
86
+ item: T;
87
+ expand?: boolean;
88
+ children?: TreeNode<T>[];
89
+ };
90
+
91
+ /**
92
+ * 检查节点是否为目录/分支的函数
93
+ */
94
+ export type CheckIsDirectory<T> = (node: TreeNode<T>) => boolean;
95
+
96
+ /**
97
+ * 从节点项目获取文本的函数
98
+ */
99
+ export type GetText<T> = (data: T) => string;
100
+
101
+ /**
102
+ * 节点选择变更回调
103
+ */
104
+ export type OnNodeSelectionChange = (node: TreeNode<any>) => Promise<boolean>;
105
+
106
+ /**
107
+ * 节点可见性过滤函数
108
+ */
109
+ export type NodeVisibleFun = (node: TreeNode<any>) => boolean;
110
+
111
+ /**
112
+ * 懒加载的加载子节点函数
113
+ */
114
+ export type LoadChildrenFun = (item: TreeNode<any>) => Promise<Array<TreeNode<any>>>;
115
+
116
+ /**
117
+ * 懒加载器配置
118
+ */
119
+ export interface LazyLoader {
120
+ /**
121
+ * 检查节点是否是分支(有子节点)
122
+ */
123
+ isBranch: CheckIsDirectory<any>;
124
+
125
+ /**
126
+ * 数据加载器函数
127
+ */
128
+ load: LoadChildrenFun;
129
+ }
130
+ ```
131
+
132
+ ## 使用示例
133
+
134
+ ### 文件系统树
135
+
136
+ ```svelte
137
+ <script lang="ts">
138
+ import TreeView from '@ticatec/uniface-element/TreeView';
139
+ import type { TreeNode, CheckIsDirectory } from '@ticatec/uniface-element/TreeNodes';
140
+
141
+ interface FileSystemItem {
142
+ id: string;
143
+ name: string;
144
+ type: 'file' | 'folder';
145
+ size?: number;
146
+ modified?: string;
147
+ }
148
+
149
+ let fileSystem: TreeNode<FileSystemItem>[] = [
150
+ {
151
+ item: { id: 'root', name: '项目根目录', type: 'folder' },
152
+ expand: true,
153
+ children: [
154
+ {
155
+ item: { id: 'src', name: 'src', type: 'folder' },
156
+ expand: true,
157
+ children: [
158
+ {
159
+ item: { id: 'components', name: 'components', type: 'folder' },
160
+ expand: false,
161
+ children: [
162
+ { item: { id: 'header', name: 'Header.svelte', type: 'file', size: 2048 } },
163
+ { item: { id: 'footer', name: 'Footer.svelte', type: 'file', size: 1024 } }
164
+ ]
165
+ },
166
+ { item: { id: 'main', name: 'main.ts', type: 'file', size: 512 } },
167
+ { item: { id: 'app', name: 'App.svelte', type: 'file', size: 3072 } }
168
+ ]
169
+ },
170
+ {
171
+ item: { id: 'public', name: 'public', type: 'folder' },
172
+ expand: false,
173
+ children: [
174
+ { item: { id: 'index', name: 'index.html', type: 'file', size: 1536 } },
175
+ { item: { id: 'favicon', name: 'favicon.ico', type: 'file', size: 256 } }
176
+ ]
177
+ },
178
+ { item: { id: 'package', name: 'package.json', type: 'file', size: 1024 } },
179
+ { item: { id: 'readme', name: 'README.md', type: 'file', size: 2048 } }
180
+ ]
181
+ }
182
+ ];
183
+
184
+ const checkIsDirectory: CheckIsDirectory<FileSystemItem> = (node) => {
185
+ return node.item.type === 'folder';
186
+ };
187
+
188
+ let selectedNode: TreeNode<FileSystemItem> | null = null;
189
+
190
+ async function handleNodeSelection(node: TreeNode<FileSystemItem>): Promise<boolean> {
191
+ console.log('选中节点:', node.item.name);
192
+ selectedNode = node;
193
+ return true; // 允许选择
194
+ }
195
+ </script>
196
+
197
+ <div class="file-explorer">
198
+ <TreeView
199
+ nodes={fileSystem}
200
+ textField="name"
201
+ checkIsDirectory={checkIsDirectory}
202
+ onSelectionChange={handleNodeSelection}
203
+ style="border: 1px solid #ddd; height: 400px; overflow-y: auto;"
204
+ />
205
+
206
+ {#if selectedNode}
207
+ <div class="file-details">
208
+ <h3>选中: {selectedNode.item.name}</h3>
209
+ <p><strong>类型:</strong> {selectedNode.item.type === 'folder' ? '文件夹' : '文件'}</p>
210
+ {#if selectedNode.item.size}
211
+ <p><strong>大小:</strong> {selectedNode.item.size} 字节</p>
212
+ {/if}
213
+ </div>
214
+ {/if}
215
+ </div>
216
+
217
+ <style>
218
+ .file-explorer {
219
+ display: flex;
220
+ gap: 20px;
221
+ }
222
+
223
+ .file-details {
224
+ padding: 16px;
225
+ background: #f8f9fa;
226
+ border-radius: 4px;
227
+ min-width: 200px;
228
+ }
229
+ </style>
230
+ ```
231
+
232
+ ### 组织架构图
233
+
234
+ ```svelte
235
+ <script lang="ts">
236
+ import TreeView from '@ticatec/uniface-element/TreeView';
237
+ import type { TreeNode, CheckIsDirectory, GetText } from '@ticatec/uniface-element/TreeNodes';
238
+
239
+ interface Employee {
240
+ id: string;
241
+ name: string;
242
+ position: string;
243
+ department: string;
244
+ hasSubordinates: boolean;
245
+ }
246
+
247
+ let organizationChart: TreeNode<Employee>[] = [
248
+ {
249
+ item: {
250
+ id: 'ceo',
251
+ name: '张总',
252
+ position: 'CEO',
253
+ department: '总裁办',
254
+ hasSubordinates: true
255
+ },
256
+ expand: true,
257
+ children: [
258
+ {
259
+ item: {
260
+ id: 'cto',
261
+ name: '李技术总监',
262
+ position: 'CTO',
263
+ department: '技术部',
264
+ hasSubordinates: true
265
+ },
266
+ expand: true,
267
+ children: [
268
+ {
269
+ item: {
270
+ id: 'dev-manager',
271
+ name: '王开发经理',
272
+ position: '开发经理',
273
+ department: '工程部',
274
+ hasSubordinates: true
275
+ },
276
+ expand: false,
277
+ children: [
278
+ { item: { id: 'dev1', name: '刘高级开发', position: '高级开发工程师', department: '工程部', hasSubordinates: false } },
279
+ { item: { id: 'dev2', name: '陈开发', position: '开发工程师', department: '工程部', hasSubordinates: false } }
280
+ ]
281
+ },
282
+ { item: { id: 'qa', name: '赵测试负责人', position: 'QA负责人', department: '质量保证部', hasSubordinates: false } }
283
+ ]
284
+ },
285
+ {
286
+ item: {
287
+ id: 'cfo',
288
+ name: '周财务总监',
289
+ position: 'CFO',
290
+ department: '财务部',
291
+ hasSubordinates: true
292
+ },
293
+ expand: false,
294
+ children: [
295
+ { item: { id: 'accountant', name: '吴高级会计', position: '高级会计', department: '财务部', hasSubordinates: false } }
296
+ ]
297
+ }
298
+ ]
299
+ }
300
+ ];
301
+
302
+ const checkIsDirectory: CheckIsDirectory<Employee> = (node) => {
303
+ return node.item.hasSubordinates;
304
+ };
305
+
306
+ const getEmployeeText: GetText<Employee> = (employee) => {
307
+ return `${employee.name} - ${employee.position}`;
308
+ };
309
+
310
+ let selectedEmployee: TreeNode<Employee> | null = null;
311
+ </script>
312
+
313
+ <div class="org-chart">
314
+ <h2>组织架构图</h2>
315
+
316
+ <TreeView
317
+ nodes={organizationChart}
318
+ textField={getEmployeeText}
319
+ checkIsDirectory={checkIsDirectory}
320
+ bind:activeNode={selectedEmployee}
321
+ style="background: white; border: 1px solid #e0e0e0; padding: 16px; border-radius: 8px;"
322
+ />
323
+
324
+ {#if selectedEmployee}
325
+ <div class="employee-card">
326
+ <div class="employee-header">
327
+ <h3>{selectedEmployee.item.name}</h3>
328
+ <span class="position">{selectedEmployee.item.position}</span>
329
+ </div>
330
+ <div class="employee-details">
331
+ <p><strong>部门:</strong> {selectedEmployee.item.department}</p>
332
+ <p><strong>ID:</strong> {selectedEmployee.item.id}</p>
333
+ <p><strong>有下属:</strong> {selectedEmployee.item.hasSubordinates ? '是' : '否'}</p>
334
+ </div>
335
+ </div>
336
+ {/if}
337
+ </div>
338
+
339
+ <style>
340
+ .org-chart {
341
+ max-width: 800px;
342
+ margin: 0 auto;
343
+ }
344
+
345
+ .employee-card {
346
+ margin-top: 20px;
347
+ padding: 20px;
348
+ background: #f8f9fa;
349
+ border-radius: 8px;
350
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
351
+ }
352
+
353
+ .employee-header {
354
+ margin-bottom: 12px;
355
+ }
356
+
357
+ .employee-header h3 {
358
+ margin: 0 0 4px 0;
359
+ color: #333;
360
+ }
361
+
362
+ .position {
363
+ background: #007bff;
364
+ color: white;
365
+ padding: 4px 8px;
366
+ border-radius: 4px;
367
+ font-size: 0.875rem;
368
+ }
369
+
370
+ .employee-details p {
371
+ margin: 8px 0;
372
+ color: #555;
373
+ }
374
+ </style>
375
+ ```
376
+
377
+ ### 懒加载树
378
+
379
+ ```svelte
380
+ <script lang="ts">
381
+ import TreeView from '@ticatec/uniface-element/TreeView';
382
+ import type { TreeNode, CheckIsDirectory } from '@ticatec/uniface-element/TreeNodes';
383
+ import type { LazyLoader, LoadChildrenFun } from '@ticatec/uniface-element/TreeView';
384
+
385
+ interface ApiNode {
386
+ id: string;
387
+ name: string;
388
+ type: 'folder' | 'file';
389
+ hasChildren?: boolean;
390
+ }
391
+
392
+ // 初始根节点
393
+ let initialNodes: TreeNode<ApiNode>[] = [
394
+ {
395
+ item: { id: 'api', name: 'API 接口', type: 'folder', hasChildren: true },
396
+ expand: false
397
+ },
398
+ {
399
+ item: { id: 'components', name: '组件', type: 'folder', hasChildren: true },
400
+ expand: false
401
+ }
402
+ ];
403
+
404
+ const checkIsDirectory: CheckIsDirectory<ApiNode> = (node) => {
405
+ return node.item.type === 'folder' && node.item.hasChildren;
406
+ };
407
+
408
+ // 模拟 API 调用加载子节点
409
+ const loadChildren: LoadChildrenFun = async (parentNode) => {
410
+ console.log('正在加载子节点:', parentNode.item.name);
411
+
412
+ // 模拟网络延迟
413
+ await new Promise(resolve => setTimeout(resolve, 1000));
414
+
415
+ // 根据父节点的模拟数据
416
+ if (parentNode.item.id === 'api') {
417
+ return [
418
+ {
419
+ item: { id: 'users', name: '用户 API', type: 'folder', hasChildren: true }
420
+ },
421
+ {
422
+ item: { id: 'orders', name: '订单 API', type: 'folder', hasChildren: true }
423
+ },
424
+ {
425
+ item: { id: 'auth', name: '身份验证', type: 'file' }
426
+ }
427
+ ];
428
+ } else if (parentNode.item.id === 'components') {
429
+ return [
430
+ {
431
+ item: { id: 'ui', name: 'UI 组件', type: 'folder', hasChildren: true }
432
+ },
433
+ {
434
+ item: { id: 'forms', name: '表单组件', type: 'folder', hasChildren: true }
435
+ }
436
+ ];
437
+ } else if (parentNode.item.id === 'users') {
438
+ return [
439
+ { item: { id: 'get-users', name: 'GET /users', type: 'file' } },
440
+ { item: { id: 'post-user', name: 'POST /users', type: 'file' } },
441
+ { item: { id: 'put-user', name: 'PUT /users/:id', type: 'file' } }
442
+ ];
443
+ } else if (parentNode.item.id === 'orders') {
444
+ return [
445
+ { item: { id: 'get-orders', name: 'GET /orders', type: 'file' } },
446
+ { item: { id: 'post-order', name: 'POST /orders', type: 'file' } }
447
+ ];
448
+ }
449
+
450
+ return [];
451
+ };
452
+
453
+ const lazyLoader: LazyLoader = {
454
+ isBranch: checkIsDirectory,
455
+ load: loadChildren
456
+ };
457
+
458
+ let activeNode: TreeNode<ApiNode> | null = null;
459
+ let loadingNodes = new Set();
460
+
461
+ async function handleNodeSelection(node: TreeNode<ApiNode>): Promise<boolean> {
462
+ console.log('选中:', node.item.name);
463
+ return true;
464
+ }
465
+ </script>
466
+
467
+ <div class="api-explorer">
468
+ <h2>API 浏览器(懒加载)</h2>
469
+
470
+ <TreeView
471
+ nodes={initialNodes}
472
+ textField="name"
473
+ checkIsDirectory={checkIsDirectory}
474
+ lazyLoader={lazyLoader}
475
+ onSelectionChange={handleNodeSelection}
476
+ bind:activeNode
477
+ style="height: 500px; border: 1px solid #ddd; overflow-y: auto; padding: 8px;"
478
+ />
479
+
480
+ {#if activeNode}
481
+ <div class="node-info">
482
+ <h3>选中: {activeNode.item.name}</h3>
483
+ <p><strong>类型:</strong> {activeNode.item.type === 'folder' ? '文件夹' : '文件'}</p>
484
+ <p><strong>ID:</strong> {activeNode.item.id}</p>
485
+ {#if activeNode.item.hasChildren}
486
+ <p><strong>有子节点:</strong> 是</p>
487
+ {/if}
488
+ </div>
489
+ {/if}
490
+ </div>
491
+
492
+ <style>
493
+ .api-explorer {
494
+ max-width: 600px;
495
+ margin: 20px 0;
496
+ }
497
+
498
+ .node-info {
499
+ margin-top: 20px;
500
+ padding: 16px;
501
+ background: #f8f9fa;
502
+ border-radius: 4px;
503
+ }
504
+
505
+ .node-info h3 {
506
+ margin: 0 0 12px 0;
507
+ color: #333;
508
+ }
509
+
510
+ .node-info p {
511
+ margin: 6px 0;
512
+ color: #555;
513
+ }
514
+ </style>
515
+ ```
516
+
517
+ ### 带上下文菜单的过滤树
518
+
519
+ ```svelte
520
+ <script lang="ts">
521
+ import TreeView from '@ticatec/uniface-element/TreeView';
522
+ import type { TreeNode, CheckIsDirectory } from '@ticatec/uniface-element/TreeNodes';
523
+ import type { NodeVisibleFun, OnNodeSelectionChange } from '@ticatec/uniface-element/TreeView';
524
+ import type { OnContextMenu } from '@ticatec/uniface-element/ContextMenu';
525
+
526
+ interface MenuItem {
527
+ id: string;
528
+ label: string;
529
+ icon?: string;
530
+ children?: MenuItem[];
531
+ category: string;
532
+ }
533
+
534
+ let menuStructure: TreeNode<MenuItem>[] = [
535
+ {
536
+ item: { id: 'dashboard', label: '仪表板', icon: '📊', category: 'main' },
537
+ expand: true,
538
+ children: [
539
+ { item: { id: 'analytics', label: '分析', icon: '📈', category: 'dashboard' } },
540
+ { item: { id: 'reports', label: '报告', icon: '📋', category: 'dashboard' } }
541
+ ]
542
+ },
543
+ {
544
+ item: { id: 'users', label: '用户管理', icon: '👥', category: 'main' },
545
+ expand: false,
546
+ children: [
547
+ { item: { id: 'user-list', label: '用户列表', icon: '📝', category: 'users' } },
548
+ { item: { id: 'user-roles', label: '角色权限', icon: '🔒', category: 'users' } },
549
+ { item: { id: 'user-settings', label: '用户设置', icon: '⚙️', category: 'users' } }
550
+ ]
551
+ },
552
+ {
553
+ item: { id: 'content', label: '内容管理', icon: '📄', category: 'main' },
554
+ expand: false,
555
+ children: [
556
+ { item: { id: 'pages', label: '页面', icon: '📖', category: 'content' } },
557
+ { item: { id: 'media', label: '媒体库', icon: '🖼️', category: 'content' } }
558
+ ]
559
+ },
560
+ {
561
+ item: { id: 'settings', label: '系统设置', icon: '🔧', category: 'main' },
562
+ expand: false,
563
+ children: [
564
+ { item: { id: 'general', label: '常规', icon: '⚙️', category: 'settings' } },
565
+ { item: { id: 'security', label: '安全', icon: '🛡️', category: 'settings' } }
566
+ ]
567
+ }
568
+ ];
569
+
570
+ let searchQuery = '';
571
+ let selectedNode: TreeNode<MenuItem> | null = null;
572
+
573
+ const checkIsDirectory: CheckIsDirectory<MenuItem> = (node) => {
574
+ return node.children && node.children.length > 0;
575
+ };
576
+
577
+ // 搜索的过滤函数
578
+ const nodeVisibilityFilter: NodeVisibleFun = (node) => {
579
+ if (!searchQuery.trim()) return true;
580
+
581
+ const query = searchQuery.toLowerCase();
582
+ return (
583
+ node.item.label.toLowerCase().includes(query) ||
584
+ node.item.category.toLowerCase().includes(query)
585
+ );
586
+ };
587
+
588
+ // 节点选择处理器
589
+ const handleNodeSelection: OnNodeSelectionChange = async (node) => {
590
+ selectedNode = node;
591
+ console.log('选中菜单项:', node.item.label);
592
+ return true;
593
+ };
594
+
595
+ // 上下文菜单处理器
596
+ const handleContextMenu: OnContextMenu = (event, node) => {
597
+ event.preventDefault();
598
+ console.log('上下文菜单:', node.item.label);
599
+
600
+ // 这里可以显示自定义上下文菜单
601
+ const menuItems = [
602
+ { label: '编辑', action: () => console.log('编辑', node.item.label) },
603
+ { label: '删除', action: () => console.log('删除', node.item.label) },
604
+ { label: '复制', action: () => console.log('复制', node.item.label) }
605
+ ];
606
+
607
+ // 在光标位置显示上下文菜单
608
+ showContextMenu(event.clientX, event.clientY, menuItems);
609
+ };
610
+
611
+ function showContextMenu(x: number, y: number, items: any[]) {
612
+ // 实现会在指定位置显示上下文菜单
613
+ alert(`上下文菜单位置 (${x}, ${y})\n项目: ${items.map(i => i.label).join(', ')}`);
614
+ }
615
+
616
+ function clearSearch() {
617
+ searchQuery = '';
618
+ }
619
+ </script>
620
+
621
+ <div class="menu-manager">
622
+ <h2>导航菜单管理器</h2>
623
+
624
+ <div class="search-controls">
625
+ <input
626
+ type="text"
627
+ bind:value={searchQuery}
628
+ placeholder="搜索菜单项..."
629
+ class="search-input"
630
+ />
631
+ {#if searchQuery}
632
+ <button on:click={clearSearch} class="clear-btn">清除</button>
633
+ {/if}
634
+ </div>
635
+
636
+ <div class="tree-container">
637
+ <TreeView
638
+ nodes={menuStructure}
639
+ textField="label"
640
+ checkIsDirectory={checkIsDirectory}
641
+ isVisible={nodeVisibilityFilter}
642
+ onSelectionChange={handleNodeSelection}
643
+ onContextMenu={handleContextMenu}
644
+ style="height: 400px; border: 1px solid #ddd; padding: 12px; overflow-y: auto;"
645
+ />
646
+
647
+ {#if selectedNode}
648
+ <div class="menu-item-details">
649
+ <h3>
650
+ {#if selectedNode.item.icon}
651
+ <span class="item-icon">{selectedNode.item.icon}</span>
652
+ {/if}
653
+ {selectedNode.item.label}
654
+ </h3>
655
+ <div class="details-grid">
656
+ <div class="detail-item">
657
+ <strong>ID:</strong> {selectedNode.item.id}
658
+ </div>
659
+ <div class="detail-item">
660
+ <strong>分类:</strong> {selectedNode.item.category}
661
+ </div>
662
+ <div class="detail-item">
663
+ <strong>类型:</strong> {checkIsDirectory(selectedNode) ? '菜单组' : '菜单项'}
664
+ </div>
665
+ {#if selectedNode.children}
666
+ <div class="detail-item">
667
+ <strong>子项:</strong> {selectedNode.children.length}
668
+ </div>
669
+ {/if}
670
+ </div>
671
+
672
+ <div class="action-buttons">
673
+ <button class="btn btn-primary">编辑</button>
674
+ <button class="btn btn-secondary">复制</button>
675
+ <button class="btn btn-danger">删除</button>
676
+ </div>
677
+ </div>
678
+ {/if}
679
+ </div>
680
+ </div>
681
+
682
+ <style>
683
+ .menu-manager {
684
+ max-width: 800px;
685
+ margin: 0 auto;
686
+ padding: 20px;
687
+ }
688
+
689
+ .search-controls {
690
+ display: flex;
691
+ gap: 8px;
692
+ margin-bottom: 16px;
693
+ }
694
+
695
+ .search-input {
696
+ flex: 1;
697
+ padding: 8px 12px;
698
+ border: 1px solid #ddd;
699
+ border-radius: 4px;
700
+ font-size: 14px;
701
+ }
702
+
703
+ .clear-btn {
704
+ padding: 8px 16px;
705
+ background: #f8f9fa;
706
+ border: 1px solid #ddd;
707
+ border-radius: 4px;
708
+ cursor: pointer;
709
+ }
710
+
711
+ .clear-btn:hover {
712
+ background: #e9ecef;
713
+ }
714
+
715
+ .tree-container {
716
+ display: flex;
717
+ gap: 20px;
718
+ }
719
+
720
+ .menu-item-details {
721
+ flex: 0 0 300px;
722
+ padding: 20px;
723
+ background: #f8f9fa;
724
+ border-radius: 8px;
725
+ height: fit-content;
726
+ }
727
+
728
+ .menu-item-details h3 {
729
+ display: flex;
730
+ align-items: center;
731
+ gap: 8px;
732
+ margin: 0 0 16px 0;
733
+ color: #333;
734
+ }
735
+
736
+ .item-icon {
737
+ font-size: 1.2em;
738
+ }
739
+
740
+ .details-grid {
741
+ display: grid;
742
+ gap: 8px;
743
+ margin-bottom: 20px;
744
+ }
745
+
746
+ .detail-item {
747
+ padding: 8px 0;
748
+ border-bottom: 1px solid #e9ecef;
749
+ color: #555;
750
+ }
751
+
752
+ .action-buttons {
753
+ display: flex;
754
+ gap: 8px;
755
+ flex-wrap: wrap;
756
+ }
757
+
758
+ .btn {
759
+ padding: 6px 12px;
760
+ border: none;
761
+ border-radius: 4px;
762
+ cursor: pointer;
763
+ font-size: 0.875rem;
764
+ }
765
+
766
+ .btn-primary {
767
+ background: #007bff;
768
+ color: white;
769
+ }
770
+
771
+ .btn-secondary {
772
+ background: #6c757d;
773
+ color: white;
774
+ }
775
+
776
+ .btn-danger {
777
+ background: #dc3545;
778
+ color: white;
779
+ }
780
+
781
+ .btn:hover {
782
+ opacity: 0.8;
783
+ }
784
+ </style>
785
+ ```
786
+
787
+ ## 样式
788
+
789
+ TreeView 组件使用以下 CSS 类:
790
+
791
+ ```css
792
+ .uniface-tree-view {
793
+ /* 主要树容器 */
794
+ }
795
+
796
+ /* 各个树节点由 TreeNodeView 组件进行样式设置 */
797
+ ```
798
+
799
+ ## API 参考
800
+
801
+ ### TreeView 组件属性
802
+
803
+ ```typescript
804
+ interface TreeViewProps {
805
+ nodes: Array<TreeNode<any>>; // 要显示的树节点
806
+ textField: string | GetText<any>; // 节点文本的字段或函数
807
+ style?: string; // 自定义样式
808
+ lazyLoader?: LazyLoader | null; // 懒加载配置
809
+ activeNode?: any; // 当前活动节点
810
+ class?: string; // CSS 类
811
+ isVisible?: NodeVisibleFun; // 节点可见性过滤器
812
+ onSelectionChange?: OnNodeSelectionChange; // 选择回调
813
+ onContextMenu?: OnContextMenu; // 上下文菜单回调
814
+ checkIsDirectory: CheckIsDirectory<any>; // 目录检查函数
815
+ }
816
+ ```
817
+
818
+ ## 与 TreeNodes 工具的集成
819
+
820
+ TreeView 组件与 TreeNodes 工具类无缝协作,用于管理分层数据:
821
+
822
+ ```svelte
823
+ <script lang="ts">
824
+ import TreeView from '@ticatec/uniface-element/TreeView';
825
+ import TreeNodes from '@ticatec/uniface-element/TreeNodes';
826
+
827
+ // 创建 TreeNodes 实例进行数据管理
828
+ const treeManager = new TreeNodes({
829
+ keyField: 'id',
830
+ parentKeyField: 'parentId',
831
+ textField: 'name',
832
+ checkIsRoot: (item) => !item.parentId,
833
+ checkIsDirectory: (node) => node.item.type === 'folder'
834
+ });
835
+
836
+ // 设置扁平数据数组 - TreeNodes 将构建层次结构
837
+ let flatData = [
838
+ { id: '1', name: '根目录', type: 'folder', parentId: null },
839
+ { id: '2', name: '子项 1', type: 'folder', parentId: '1' },
840
+ { id: '3', name: '文件 1', type: 'file', parentId: '2' }
841
+ ];
842
+
843
+ treeManager.setData(flatData);
844
+ let treeNodes = treeManager.nodes;
845
+ </script>
846
+
847
+ <TreeView
848
+ nodes={treeNodes}
849
+ textField="name"
850
+ checkIsDirectory={treeManager.checkIsDirectory}
851
+ />
852
+ ```
853
+
854
+ ## 最佳实践
855
+
856
+ 1. **性能优化** - 对于大型树结构(>100个节点)使用懒加载
857
+ 2. **节点文本** - 为节点显示使用一致的文本字段或函数
858
+ 3. **状态管理** - 跟踪活动/选中节点以便用户交互
859
+ 4. **可访问性** - 确保键盘导航支持
860
+ 5. **错误处理** - 在懒加载器中优雅地处理加载错误
861
+ 6. **视觉反馈** - 在懒加载过程中提供加载指示器
862
+ 7. **上下文操作** - 为节点操作实现上下文菜单
863
+ 8. **过滤功能** - 使用可见性函数进行搜索/过滤功能