@ticatec/uniface-element 0.3.14 → 0.3.16

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.
@@ -11,6 +11,7 @@ export class CommonTreeNodes {
11
11
  compareFun;
12
12
  expendDepth;
13
13
  _nodes = [];
14
+ _version = 0;
14
15
  constructor(options) {
15
16
  this.checkIsRoot = options.checkIsRoot;
16
17
  this.compareFun = options.compareFun;
@@ -21,15 +22,150 @@ export class CommonTreeNodes {
21
22
  this.expendDepth = options.expendDepth ?? 1;
22
23
  this.checkIsDirectory = options.checkIsDirectory;
23
24
  }
25
+ touch() {
26
+ this._version++;
27
+ }
28
+ /**
29
+ * 创建带有方法的节点
30
+ */
31
+ createNodeWithMethods(item) {
32
+ const node = {
33
+ item,
34
+ expand: false,
35
+ append: (childItem) => this.addNodeToNode(node, childItem),
36
+ remove: () => this.removeNode(node),
37
+ replace: (newItem) => this.replaceNodeItem(node, newItem),
38
+ moveTo: (newParentId) => this.moveNodeTo(node, newParentId),
39
+ removeChild: (childId) => this.removeChildNode(node, childId),
40
+ removeChildren: () => this.removeChildrenNodes(node)
41
+ };
42
+ return node;
43
+ }
44
+ /**
45
+ * 添加子节点到指定节点
46
+ */
47
+ addNodeToNode(parentNode, childItem) {
48
+ // 如果父节点还没有 children(可能是 lazy 节点)
49
+ // 自动触发 lazyLoader 加载或初始化 children 数组
50
+ if (parentNode.children == null) {
51
+ parentNode.children = [];
52
+ }
53
+ const childNode = this.createNodeWithMethods(childItem);
54
+ parentNode.children = [...parentNode.children, childNode];
55
+ if (this.compareFun) {
56
+ parentNode.children = parentNode.children.sort((n1, n2) => {
57
+ return this.compareFun(n1.item, n2.item);
58
+ });
59
+ }
60
+ parentNode.expand = true;
61
+ this.nodeMap.set(childItem[this.keyField], childNode);
62
+ this.touch();
63
+ }
64
+ /**
65
+ * 删除指定节点
66
+ */
67
+ removeNode(node) {
68
+ const idKey = node.item[this.keyField];
69
+ const parentKey = node.item[this.parentKeyField];
70
+ const parentNode = this.nodeMap.get(parentKey);
71
+ this.nodeMap.delete(idKey);
72
+ if (parentNode) {
73
+ if (parentNode.children) {
74
+ parentNode.children = parentNode.children.filter(el => el.item[this.keyField] != idKey);
75
+ }
76
+ }
77
+ else {
78
+ // 是根节点
79
+ this._nodes = this._nodes.filter(el => el.item[this.keyField] != idKey);
80
+ }
81
+ this.touch();
82
+ }
83
+ /**
84
+ * 替换节点的数据
85
+ */
86
+ replaceNodeItem(node, newItem) {
87
+ node.item = newItem;
88
+ // 如果排序字段改变了,需要重新排序
89
+ const parentKey = node.item[this.parentKeyField];
90
+ const parentNode = this.nodeMap.get(parentKey);
91
+ if (parentNode && parentNode.children && parentNode.children.length > 0) {
92
+ parentNode.children = parentNode.children.sort((n1, n2) => {
93
+ return this.compareFun?.(n1.item, n2.item);
94
+ });
95
+ }
96
+ else {
97
+ this._nodes.sort((n1, n2) => {
98
+ return this.compareFun?.(n1.item, n2.item);
99
+ });
100
+ }
101
+ this.touch();
102
+ }
103
+ /**
104
+ * 将节点移动到新的父节点下
105
+ */
106
+ moveNodeTo(node, newParentId) {
107
+ const newParent = this.nodeMap.get(newParentId);
108
+ const currentParentKey = node.item[this.parentKeyField];
109
+ const currentParent = this.nodeMap.get(currentParentKey);
110
+ if (!newParent || !currentParent) {
111
+ console.warn('Cannot move node: parent not found');
112
+ return;
113
+ }
114
+ // 从当前父节点移除
115
+ if (currentParent.children) {
116
+ currentParent.children = currentParent.children.filter(el => el.item[this.keyField] != node.item[this.keyField]);
117
+ }
118
+ // 如果新父节点还没有 children,初始化数组
119
+ if (newParent.children == null) {
120
+ newParent.children = [];
121
+ }
122
+ // 添加到新父节点
123
+ newParent.children = [...newParent.children, node];
124
+ if (this.compareFun) {
125
+ newParent.children = newParent.children.sort((n1, n2) => {
126
+ return this.compareFun(n1.item, n2.item);
127
+ });
128
+ }
129
+ // 更新节点的 parentKeyField
130
+ node.item[this.parentKeyField] = newParentId;
131
+ this.touch();
132
+ }
133
+ /**
134
+ * 删除指定节点的某个子节点
135
+ */
136
+ removeChildNode(parentNode, childId) {
137
+ if (!parentNode.children)
138
+ return;
139
+ const childNode = parentNode.children.find(child => child.item[this.keyField] == childId);
140
+ if (!childNode)
141
+ return;
142
+ // 从 children 数组中移除
143
+ parentNode.children = parentNode.children.filter(child => child.item[this.keyField] != childId);
144
+ // 从 nodeMap 中删除
145
+ this.nodeMap.delete(childId);
146
+ this.touch();
147
+ }
148
+ /**
149
+ * 删除指定节点的所有子节点
150
+ */
151
+ removeChildrenNodes(parentNode) {
152
+ if (!parentNode.children)
153
+ return;
154
+ // 从 nodeMap 中删除所有子节点
155
+ for (const child of parentNode.children) {
156
+ const childId = child.item[this.keyField];
157
+ this.nodeMap.delete(childId);
158
+ }
159
+ // 清空 children 数组
160
+ parentNode.children = [];
161
+ this.touch();
162
+ }
24
163
  setData(list) {
25
164
  this.nodeMap = new Map();
26
165
  this._nodes = [];
27
166
  // 初始化每个节点
28
167
  for (const item of list) {
29
- const node = {
30
- item,
31
- expand: false
32
- };
168
+ const node = this.createNodeWithMethods(item);
33
169
  const key = item[this.keyField];
34
170
  this.nodeMap.set(key, node);
35
171
  }
@@ -42,13 +178,22 @@ export class CommonTreeNodes {
42
178
  if (this.expendDepth > 1) {
43
179
  this.setExpandForDepth(this.nodes, 0);
44
180
  }
181
+ this.touch();
45
182
  }
46
183
  /**
47
184
  * 获取节点
48
185
  */
49
186
  get nodes() {
187
+ // 通过访问 _version 让 Svelte 检测到变化
188
+ void this._version;
50
189
  return this._nodes;
51
190
  }
191
+ /**
192
+ * 获取版本号,用于触发 Svelte 响应式更新
193
+ */
194
+ get version() {
195
+ return this._version;
196
+ }
52
197
  /**
53
198
  * 设置展开层级
54
199
  * @param nodes
@@ -72,9 +217,11 @@ export class CommonTreeNodes {
72
217
  appendNode(node, doSort = false) {
73
218
  let item = node.item;
74
219
  const parentKey = item[this.parentKeyField];
75
- if (this.checkIsRoot(item)) { //|| !this.nodeMap.has(parentKey)
220
+ if (this.checkIsRoot(item)) {
76
221
  this._nodes.push(node);
77
- this._nodes.sort(this.compareFun);
222
+ this._nodes.sort((n1, n2) => {
223
+ return this.compareFun?.(n1.item, n2.item);
224
+ });
78
225
  }
79
226
  else {
80
227
  const parentNode = this.nodeMap.get(parentKey);
@@ -82,7 +229,9 @@ export class CommonTreeNodes {
82
229
  parentNode.expand = true;
83
230
  parentNode.children = [...(parentNode.children ?? []), node];
84
231
  if (doSort && this.compareFun) {
85
- parentNode.children = parentNode.children.sort(this.compareFun);
232
+ parentNode.children = parentNode.children.sort((n1, n2) => {
233
+ return this.compareFun(n1.item, n2.item);
234
+ });
86
235
  }
87
236
  }
88
237
  else {
@@ -99,79 +248,17 @@ export default class TreeNodes extends CommonTreeNodes {
99
248
  return this.collectExpandedNodes(this._nodes);
100
249
  }
101
250
  /**
102
- * 给treeview设定数据,根据数据构建树结构
103
- * @param list
104
- */
105
- /**
106
- * 增加一个新节点
107
- * @param item
251
+ * 增加一个新节点(便捷方法,会自动根据 parentKeyField 找到父节点)
252
+ * @param item 要添加的节点数据
108
253
  */
109
254
  append(item) {
110
- const node = {
111
- item,
112
- expand: true
113
- };
114
- this.appendNode(node, true);
115
- this.nodeMap.set(item[this.keyField], node);
116
- }
117
- /**
118
- * 替换一个节点的数据
119
- * @param item
120
- */
121
- replace(item) {
122
- let node = this.nodeMap.get(item[this.keyField]);
123
- if (node) {
124
- node.item = item;
125
- let parent = this.nodeMap.get(item[this.parentKeyField]);
126
- if (parent && parent.children && parent.children.length > 0) {
127
- parent.children = parent.children.sort(this.compareFun);
128
- }
129
- else {
130
- this._nodes.sort(this.compareFun);
131
- }
132
- }
133
- }
134
- remove(item) {
135
- let idKey = item[this.keyField];
136
- let node = this.nodeMap.get(idKey);
137
- if (node) {
138
- let parent = this.nodeMap.get(node.item[this.parentKeyField]);
139
- this.nodeMap.delete(idKey);
140
- if (parent) {
141
- if (parent.children) {
142
- let pos = parent.children.findIndex(el => el.item[this.keyField] == idKey);
143
- if (pos > -1) {
144
- parent.children.splice(pos, 1);
145
- }
146
- }
147
- }
148
- else {
149
- let pos = this._nodes.findIndex(el => el.item[this.keyField] == idKey);
150
- if (pos > -1) {
151
- this._nodes.splice(pos, 1);
152
- }
153
- }
154
- }
155
- }
156
- /**
157
- * 将一个节点移动到另外一个节点下
158
- * @param item
159
- * @param parentId
160
- */
161
- moveTo(item, parentId) {
162
- let newParent = this.nodeMap.get(parentId);
163
- let node = this.nodeMap.get(item[this.keyField]);
164
- let currentParent = this.nodeMap.get(item[this.parentKeyField]);
165
- if (newParent && node && currentParent && currentParent.children) {
166
- let pos = currentParent.children.findIndex(el => el.item[this.keyField] == item[this.keyField]);
167
- if (pos > -1) {
168
- currentParent.children.splice(pos, 1);
169
- }
170
- newParent.children = [...(newParent.children ?? []), node];
171
- if (this.compareFun) {
172
- newParent.children = newParent.children.sort(this.compareFun);
173
- }
255
+ const parentKey = item[this.parentKeyField];
256
+ const parentNode = this.nodeMap.get(parentKey);
257
+ if (!parentNode) {
258
+ console.warn(`Parent node ${parentKey} not found. Cannot add node.`);
259
+ return;
174
260
  }
261
+ parentNode.append(item);
175
262
  }
176
263
  /**
177
264
  * 获取除指定节点外的其他节点
@@ -133,7 +133,6 @@
133
133
  }
134
134
 
135
135
  const handleSelectionChanged = (item: any) => () => {
136
- console.log('选择项目', item);
137
136
  let idx = selectedList.indexOf(item);
138
137
  if (idx < 0) {
139
138
  selectedList = [...selectedList, item];
@@ -1,4 +1,4 @@
1
1
  import ListBox from "./ListBox.svelte";
2
- import type { LazyLoader, LoadResult, FunFilter } from "./types";
2
+ import type { ListLazyLoader, LazyLoader, LoadResult, FunFilter } from "./types";
3
3
  export default ListBox;
4
- export type { LazyLoader, FunFilter, LoadResult };
4
+ export type { ListLazyLoader, LazyLoader, FunFilter, LoadResult };
@@ -3,4 +3,14 @@ export interface LoadResult {
3
3
  list: Array<any>;
4
4
  }
5
5
  export type FunFilter = (item: any, text: string) => boolean;
6
- export type LazyLoader = (text: string, pageNo: number) => Promise<LoadResult>;
6
+ /**
7
+ * 分页加载列表数据
8
+ * @param text 搜索文本
9
+ * @param pageNo 页码(从0开始)
10
+ * @returns 加载结果,包含数据列表和是否有更多数据的标志
11
+ */
12
+ export type ListLazyLoader = (text: string, pageNo: number) => Promise<LoadResult>;
13
+ /**
14
+ * @deprecated 使用 ListLazyLoader 代替。LazyLoader 已弃用,将在未来版本中移除。
15
+ */
16
+ export type LazyLoader = ListLazyLoader;
@@ -27,7 +27,6 @@
27
27
  }
28
28
 
29
29
  let showMessageBox = (_message: string, _buttons: Array<any> | null, _title: string, _type: string | null, _escapeHTML: boolean = true): Promise<any> => {
30
- console.log('显示消息框')
31
30
  if (!visible) {
32
31
  top = 0;
33
32
  left = 0;
@@ -16,7 +16,6 @@
16
16
  export let placeholder: string | null = null;
17
17
 
18
18
  export const focus = () => {
19
- console.log('查询框获取焦点')
20
19
  editor.focus();
21
20
  }
22
21
 
@@ -43,7 +43,6 @@
43
43
 
44
44
  const checkOverflow = () => {
45
45
  if (container) {
46
- console.log(currentLeft, container.clientWidth, container.scrollWidth);
47
46
  if (currentLeft + container.clientWidth >= container.scrollWidth) {
48
47
  currentLeft = container.scrollWidth - container.clientWidth;
49
48
  scrollX.set(currentLeft);
@@ -75,6 +75,7 @@ import TreeNodes, { type TreeNode } from "@ticatec/uniface-element/TreeNodes";
75
75
  | `onblur` | `((event: FocusEvent) => void) \| null` | `null` | Callback when tree loses focus |
76
76
  | `onContextMenu` | `OnContextMenu` | `null` | Right-click context menu handler |
77
77
  | `checkIsDirectory` | `CheckIsDirectory<any>` | Required | Function to check if node is a directory |
78
+ | `version` | `number \| undefined` | `undefined` | Version number to trigger reactive updates (from TreeNodes object) |
78
79
  | `class` | `string` | `""` | CSS class name |
79
80
 
80
81
  ## Type Definitions
@@ -119,14 +120,14 @@ type NodeVisibleFun = (node: TreeNode<any>) => boolean;
119
120
  let activeNode = null;
120
121
 
121
122
  const fileSystemData = [
122
- { id: 1, name: "=� Documents", type: "folder", parent: null },
123
- { id: 2, name: "=� Pictures", type: "folder", parent: null },
124
- { id: 3, name: "=� Work", type: "folder", parent: 1 },
125
- { id: 4, name: "=� Personal", type: "folder", parent: 1 },
126
- { id: 5, name: "=� resume.pdf", type: "file", parent: 3 },
127
- { id: 6, name: "=� cover-letter.doc", type: "file", parent: 3 },
128
- { id: 7, name: "=� vacation.jpg", type: "file", parent: 2 },
129
- { id: 8, name: "=� family.png", type: "file", parent: 2 }
123
+ { id: 1, name: "=� Documents", type: "folder", parent: null },
124
+ { id: 2, name: "=� Pictures", type: "folder", parent: null },
125
+ { id: 3, name: "=� Work", type: "folder", parent: 1 },
126
+ { id: 4, name: "=� Personal", type: "folder", parent: 1 },
127
+ { id: 5, name: "=� resume.pdf", type: "file", parent: 3 },
128
+ { id: 6, name: "=� cover-letter.doc", type: "file", parent: 3 },
129
+ { id: 7, name: "=� vacation.jpg", type: "file", parent: 2 },
130
+ { id: 8, name: "=� family.png", type: "file", parent: 2 }
130
131
  ];
131
132
 
132
133
  const fileTree = new TreeNodes({
@@ -645,10 +646,10 @@ type NodeVisibleFun = (node: TreeNode<any>) => boolean;
645
646
  switch (type) {
646
647
  case 'country': return '<
647
648
  case 'province':
648
- case 'state': return '<�';
649
+ case 'state': return '<�';
649
650
  case 'district':
650
- case 'city': return '<�';
651
- default: return '=�';
651
+ case 'city': return '<�';
652
+ default: return '=�';
652
653
  }
653
654
  }
654
655
 
@@ -51,8 +51,9 @@ import TreeNodes, { type TreeNode } from "@ticatec/uniface-element/TreeNodes";
51
51
  }
52
52
  </script>
53
53
 
54
- <TreeView
54
+ <TreeView
55
55
  nodes={treeNodes.nodes}
56
+ version={treeNodes.version}
56
57
  textField="name"
57
58
  bind:activeNode
58
59
  onchange={handleSelectionChange}
@@ -75,6 +76,7 @@ import TreeNodes, { type TreeNode } from "@ticatec/uniface-element/TreeNodes";
75
76
  | `onblur` | `((event: FocusEvent) => void) \| null` | `null` | 树失去焦点时的回调函数 |
76
77
  | `onContextMenu` | `OnContextMenu` | `null` | 右键上下文菜单处理程序 |
77
78
  | `checkIsDirectory` | `CheckIsDirectory<any>` | 必需 | 判断节点是否为目录的函数 |
79
+ | `version` | `number \| undefined` | `undefined` | 用于触发响应式更新的版本号(从 TreeNodes 对象传入) |
78
80
  | `class` | `string` | `""` | CSS 类名 |
79
81
 
80
82
  ## 类型定义
@@ -727,8 +729,146 @@ const treeNodes = new TreeNodes({
727
729
  });
728
730
 
729
731
  treeNodes.setData(flatData); // 将扁平数据转换为树结构
732
+
733
+ // 动态添加节点
734
+ treeNodes.append(newItem);
735
+
736
+ // 移除节点
737
+ treeNodes.removeItem(oldItem);
738
+
739
+ // 移动节点
740
+ treeNodes.moveTo(itemToMove, newParentId);
741
+ ```
742
+
743
+ ## 动态更新数据
744
+
745
+ TreeView 的每个节点都提供了便捷的方法来操作树结构:
746
+
747
+ ### 节点方法
748
+
749
+ 所有从 TreeNodes 获取的节点都包含以下方法:
750
+
751
+ - `node.append(childItem)` - 添加子节点到当前节点
752
+ - `node.remove()` - 删除当前节点(从父节点中移除)
753
+ - `node.replace(newItem)` - 替换当前节点的数据
754
+ - `node.moveTo(newParentId)` - 将当前节点移动到另一个父节点下
755
+
756
+ ### 使用示例
757
+
758
+ ```svelte
759
+ <script>
760
+ import TreeView from "@ticatec/uniface-element/TreeView";
761
+ import TreeNodes from "@ticatec/uniface-element/TreeNodes";
762
+
763
+ let activeNode = null;
764
+ let zones;
765
+
766
+ const treeNodes = new TreeNodes({
767
+ keyField: 'id',
768
+ textField: 'name',
769
+ parentKeyField: 'parent',
770
+ checkIsRoot: (item) => item.parent === null,
771
+ checkIsDirectory: (node) => node.children != null
772
+ });
773
+
774
+ treeNodes.setData(initialData);
775
+ zones = treeNodes;
776
+
777
+ // 添加子节点到选中的节点
778
+ function addNode() {
779
+ if (!activeNode) return;
780
+
781
+ const newItem = {
782
+ id: Date.now(),
783
+ name: "新节点",
784
+ parent: activeNode.item.id
785
+ };
786
+
787
+ // 方式1:直接在节点上调用 append 方法
788
+ activeNode.append(newItem);
789
+
790
+ // 方式2:使用 TreeNodes 的 append 方法(会自动找到父节点)
791
+ // treeNodes.append(newItem);
792
+
793
+ // version 会自动更新,TreeView 会重新渲染
794
+ }
795
+
796
+ // 删除选中的节点
797
+ function deleteNode() {
798
+ if (activeNode) {
799
+ // 直接在节点上调用 remove 方法
800
+ activeNode.remove();
801
+ activeNode = null;
802
+ }
803
+ }
804
+
805
+ // 替换节点的数据
806
+ function renameNode() {
807
+ if (activeNode) {
808
+ const newItem = {
809
+ ...activeNode.item,
810
+ name: "重命名后的节点"
811
+ };
812
+ activeNode.replace(newItem);
813
+ }
814
+ }
815
+
816
+ // 移动节点到另一个父节点
817
+ function moveNode() {
818
+ if (activeNode) {
819
+ const newParentId = prompt("输入新的父节点 ID:");
820
+ if (newParentId) {
821
+ activeNode.moveTo(newParentId);
822
+ }
823
+ }
824
+ }
825
+ </script>
826
+
827
+ <div>
828
+ <button on:click={addNode} disabled={!activeNode}>添加子节点</button>
829
+ <button on:click={deleteNode} disabled={!activeNode}>删除节点</button>
830
+ <button on:click={renameNode} disabled={!activeNode}>重命名</button>
831
+ <button on:click={moveNode} disabled={!activeNode}>移动节点</button>
832
+
833
+ <TreeView
834
+ nodes={treeNodes.nodes}
835
+ version={treeNodes.version}
836
+ textField="name"
837
+ bind:activeNode
838
+ checkIsDirectory={node => node.children != null}
839
+ />
840
+ </div>
841
+ ```
842
+
843
+ ### TreeNodes 类方法
844
+
845
+ 如果你只有一个数据项,可以使用 `TreeNodes` 类的方法:
846
+
847
+ ```typescript
848
+ // 添加新节点(会自动根据 parentKeyField 找到父节点)
849
+ treeNodes.append(newItem);
850
+
851
+ // 注意:删除、替换、移动操作建议直接在节点上调用方法
852
+ // 因为这些操作通常针对特定的节点实例
853
+ ```
854
+
855
+ ### Lazy 节点注意事项
856
+
857
+ 对于使用 lazyLoader 的节点(尚未展开加载的节点),直接添加子节点会给出警告:
858
+
859
+ ```javascript
860
+ // 如果父节点是 lazy 节点且尚未加载
861
+ if (parentNode.children == null) {
862
+ console.warn('Cannot add child: parent is a lazy-loaded node');
863
+ // 节点会被添加到 nodeMap,但不会添加到 parent.children
864
+ // 这样不会干扰后续的 lazyLoader 行为
865
+ }
730
866
  ```
731
867
 
868
+ 建议在使用前先展开节点,触发 lazyLoader 加载子节点。
869
+
870
+ **重要提示**:必须将 `treeNodes.version` 传递给 `TreeView` 的 `version` 属性,以确保在数据变化时组件能够正确重新渲染。
871
+
732
872
  ## 可访问性
733
873
 
734
874
  - 支持使用箭头键和回车键进行键盘导航
@@ -19,7 +19,6 @@
19
19
  let loading: boolean = false;
20
20
 
21
21
  const isDirectory = () => {
22
- console.log('检测目录函数', checkIsDirectory);
23
22
  return lazyLoader == null ? checkIsDirectory?.(node) : lazyLoader.isBranch(node);
24
23
  }
25
24
 
@@ -27,7 +26,12 @@
27
26
  if (node.children == null && lazyLoader != null) {
28
27
  try {
29
28
  loading = true;
30
- node.children = await lazyLoader.load(node);
29
+ const loadedChildren = await lazyLoader.load(node);
30
+ // 初始化新加载的子节点,确保它们的 expand 属性为 false
31
+ node.children = loadedChildren.map(child => ({
32
+ ...child,
33
+ expand: child.expand ?? false
34
+ }));
31
35
  } finally {
32
36
  loading = false;
33
37
  }
@@ -40,11 +44,18 @@
40
44
  if (respond) {
41
45
  respond = false;
42
46
  if (isDirectory()) {
43
- if (!node.children) {
44
- node.expand = false;
47
+ // 如果是懒加载模式且没有子节点,先加载子节点
48
+ if (lazyLoader != null && !node.children) {
45
49
  await loadChildren();
50
+ // 加载完成后,如果有子节点则展开
51
+ const children = node.children as TreeNode<any>[] | undefined;
52
+ if (children && children.length > 0) {
53
+ node.expand = true;
54
+ }
55
+ } else {
56
+ // 非懒加载模式或已有子节点,直接切换展开状态
57
+ node.expand = !node.expand;
46
58
  }
47
- node.expand = !node.expand;
48
59
  }
49
60
  await utils.sleep(0.2);
50
61
  respond = true;
@@ -19,6 +19,7 @@
19
19
  export let onblur: ((event: FocusEvent) => void) | null = null;
20
20
  export let onContextMenu: OnContextMenu = null as unknown as OnContextMenu;
21
21
  export let checkIsDirectory: CheckIsDirectory<any>;
22
+ export let version: number | undefined = undefined;
22
23
  let className: string = "";
23
24
 
24
25
  const onNodeSelectionChange = async (node: TreeNode<any>): Promise<boolean> => {
@@ -30,10 +31,32 @@
30
31
  return accept;
31
32
  }
32
33
 
34
+ // 通过 version 触发响应式更新:当 version 变化时,重新读取 nodes
35
+ // 这样可以确保在节点数据变化时,TreeView 能够正确重新渲染
36
+ $: reactiveNodes = version !== undefined ? nodes : nodes;
37
+
38
+ // 当 activeNode 变化时,如果是 lazy 节点且未加载,自动触发加载
39
+ $: if (activeNode && lazyLoader && activeNode.children == null) {
40
+ const loadLazyNode = async () => {
41
+ if (activeNode.children == null && lazyLoader != null) {
42
+ try {
43
+ const loadedChildren = await lazyLoader.load(activeNode);
44
+ activeNode.children = loadedChildren.map(child => ({
45
+ ...child,
46
+ expand: child.expand ?? false
47
+ }));
48
+ } catch (error) {
49
+ console.error('Failed to load lazy node:', error);
50
+ }
51
+ }
52
+ };
53
+ loadLazyNode();
54
+ }
55
+
33
56
  </script>
34
57
  <div class="uniface-tree-view {className}" {style} on:focus={onfocus} on:blur={onblur} tabindex="0">
35
58
  <div>
36
- {#each nodes as node}
59
+ {#each reactiveNodes as node}
37
60
  <TreeNodeView {activeNode} {node} {textField} {lazyLoader} {onNodeSelectionChange} {isVisible} {checkIsDirectory} {onContextMenu}/>
38
61
  {/each}
39
62
  </div>
@@ -27,6 +27,7 @@ declare const TreeView: $$__sveltets_2_IsomorphicComponent<{
27
27
  onblur?: ((event: FocusEvent) => void) | null;
28
28
  onContextMenu?: OnContextMenu;
29
29
  checkIsDirectory: CheckIsDirectory<any>;
30
+ version?: number | undefined;
30
31
  }, {
31
32
  [evt: string]: CustomEvent<any>;
32
33
  }, {}, {
@@ -9,15 +9,19 @@ export type NodeVisibleFun = (node: TreeNode<any>) => boolean;
9
9
  */
10
10
  export type LoadChildrenFun = (item: TreeNode<any>) => Promise<Array<TreeNode<any>>>;
11
11
  /**
12
- * 按需数据加载器
12
+ * 按需加载树节点的子节点
13
13
  */
14
- export interface LazyLoader {
14
+ export interface TreeLazyLoader {
15
15
  /**
16
- * 检查是否是枝节点
16
+ * 检查是否是枝节点(可能包含子节点)
17
17
  */
18
18
  isBranch: CheckIsDirectory<any>;
19
19
  /**
20
- * 数据加载器
20
+ * 加载子节点数据
21
21
  */
22
22
  load: LoadChildrenFun;
23
23
  }
24
+ /**
25
+ * @deprecated 使用 TreeLazyLoader 代替。LazyLoader 已弃用,将在未来版本中移除。
26
+ */
27
+ export type LazyLoader = TreeLazyLoader;