@ticatec/uniface-element 0.3.16 → 0.3.18

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.
@@ -1,184 +1,376 @@
1
+ /**
2
+ * 比较两个数字
3
+ * @param a 第一个数字
4
+ * @param b 第二个数字
5
+ * @returns 如果相等返回 0,a > b 返回 1,否则返回 -1
6
+ */
1
7
  export function compareNumber(a, b) {
2
8
  return a === b ? 0 : a > b ? 1 : -1;
3
9
  }
4
- export class CommonTreeNodes {
5
- checkIsRoot;
10
+ /**
11
+ * 节点视图配置类 - 定义节点的显示方式
12
+ * @template T 节点数据的类型
13
+ *
14
+ * @example
15
+ * const options = new NodeViewOptions<MyData>({
16
+ * keyField: 'id',
17
+ * textField: 'name'
18
+ * });
19
+ */
20
+ export class NodeViewOptions {
21
+ /**
22
+ * 获取节点的唯一标识字段
23
+ */
6
24
  keyField;
7
- parentKeyField;
25
+ /**
26
+ * 获取节点的文字
27
+ * 可以是字段名或函数
28
+ */
8
29
  textField;
9
- checkIsDirectory;
10
- nodeMap;
11
- compareFun;
12
- expendDepth;
13
- _nodes = [];
14
- _version = 0;
30
+ /**
31
+ * 创建节点视图配置
32
+ * @param options 配置选项
33
+ */
15
34
  constructor(options) {
16
- this.checkIsRoot = options.checkIsRoot;
17
- this.compareFun = options.compareFun;
18
- this.keyField = options.keyField ?? 'id';
19
- this.textField = options.textField ?? 'text';
20
- this.parentKeyField = options.parentKeyField ?? 'parentId';
21
- this.nodeMap = new Map();
22
- this.expendDepth = options.expendDepth ?? 1;
23
- this.checkIsDirectory = options.checkIsDirectory;
35
+ this.keyField = options.keyField;
36
+ this.textField = options.textField;
24
37
  }
25
- touch() {
26
- this._version++;
38
+ /**
39
+ * 获取节点的文字
40
+ * @param data 节点数据
41
+ * @returns 显示文字
42
+ */
43
+ getText(data) {
44
+ if (typeof this.textField === 'function') {
45
+ return this.textField(data);
46
+ }
47
+ return String(data[this.textField] ?? '');
27
48
  }
28
49
  /**
29
- * 创建带有方法的节点
50
+ * 获取节点的唯一标识
51
+ * @param data 节点数据
52
+ * @returns 唯一标识值
30
53
  */
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;
54
+ getKey(data) {
55
+ return data[this.keyField];
43
56
  }
57
+ }
58
+ /**
59
+ * TreeNode 类 - 树节点,包含数据和方法
60
+ * @template T 节点数据的类型
61
+ *
62
+ * @description
63
+ * TreeNode 是树结构的基本单元,每个节点包含:
64
+ * - 节点数据(item)
65
+ * - 父节点引用(parent)
66
+ * - 子节点列表(children)
67
+ * - 展开状态(expand)
68
+ * - 加载状态(loading)
69
+ *
70
+ * 节点提供了丰富的操作方法,可以直接在节点上进行增删改查操作。
71
+ */
72
+ export class TreeNode {
73
+ /**
74
+ * 节点数据(私有,只读)
75
+ * @private
76
+ */
77
+ _item;
78
+ /**
79
+ * 节点层级(私有)
80
+ * @private
81
+ */
82
+ _level;
83
+ /**
84
+ * 是否展开(显示子节点)
85
+ */
86
+ expand = false;
44
87
  /**
45
- * 添加子节点到指定节点
88
+ * 是否正在加载(用于懒加载)
46
89
  */
47
- addNodeToNode(parentNode, childItem) {
48
- // 如果父节点还没有 children(可能是 lazy 节点)
49
- // 自动触发 lazyLoader 加载或初始化 children 数组
50
- if (parentNode.children == null) {
51
- parentNode.children = [];
90
+ loading = false;
91
+ /**
92
+ * 父节点(只读)
93
+ */
94
+ parent = null;
95
+ /**
96
+ * 子节点数组(私有)
97
+ * @private
98
+ */
99
+ _children = null;
100
+ /**
101
+ * 创建树节点
102
+ * @param item 节点数据
103
+ * @param parent 父节点(可选)
104
+ * @param children 子节点列表(可选)
105
+ *
106
+ * @example
107
+ * // 创建根节点
108
+ * const rootNode = new TreeNode({ id: 1, name: "根节点" });
109
+ *
110
+ * // 创建子节点
111
+ * const childNode = new TreeNode({ id: 2, name: "子节点" }, rootNode);
112
+ */
113
+ constructor(item, parent = null, children = null) {
114
+ this._item = item;
115
+ this._level = parent ? parent._level + 1 : 0;
116
+ this.parent = parent;
117
+ this._children = children;
118
+ this.expand = false;
119
+ // 如果有父节点,将自己添加到父节点的 children 中
120
+ if (parent) {
121
+ if (!parent._children) {
122
+ parent._children = [];
123
+ }
124
+ parent._children.push(this);
52
125
  }
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
- });
126
+ }
127
+ /**
128
+ * 获取节点层级
129
+ * @returns 层级值(根节点为 0)
130
+ */
131
+ get level() {
132
+ return this._level;
133
+ }
134
+ /**
135
+ * 获取子节点列表(只读)
136
+ * @returns 子节点数组的浅拷贝,如果没有子节点返回 null
137
+ *
138
+ * @example
139
+ * const children = node.children;
140
+ * if (children) {
141
+ * children.forEach(child => console.log(child.item));
142
+ * }
143
+ */
144
+ get children() {
145
+ return this._children ? [...this._children] : null;
146
+ }
147
+ /**
148
+ * 添加子节点
149
+ * @param childItem 子节点数据
150
+ * @returns 新创建的子节点
151
+ *
152
+ * @example
153
+ * const childNode = parentNode.append({ id: 1, name: "子节点" });
154
+ */
155
+ append(childItem) {
156
+ return new TreeNode(childItem, this);
157
+ }
158
+ /**
159
+ * 从父节点中分离(删除当前节点)
160
+ *
161
+ * @example
162
+ * node.detach();
163
+ */
164
+ detach() {
165
+ if (!this.parent) {
166
+ console.warn('TreeNode is not attached to a tree, cannot detach');
167
+ return;
59
168
  }
60
- parentNode.expand = true;
61
- this.nodeMap.set(childItem[this.keyField], childNode);
62
- this.touch();
169
+ this.parent.removeNode(this);
63
170
  }
64
171
  /**
65
- * 删除指定节点
172
+ * 删除指定的子节点
173
+ * @param node 要删除的子节点
174
+ * @private
66
175
  */
67
176
  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);
177
+ if (this._children) {
178
+ let idx = this._children.indexOf(node);
179
+ if (idx >= 0) {
180
+ this._children.splice(idx, 1);
181
+ }
182
+ else {
183
+ console.warn(' The removed node has not been attached to this.');
75
184
  }
76
185
  }
77
186
  else {
78
- // 是根节点
79
- this._nodes = this._nodes.filter(el => el.item[this.keyField] != idKey);
187
+ console.warn(' The removed node has not been attached to this.');
80
188
  }
81
- this.touch();
82
189
  }
83
190
  /**
84
- * 替换节点的数据
191
+ * 移动到另一个父节点下
192
+ * @param newParent 新的父节点
193
+ * @returns 新创建的节点(注意:返回的是新节点,不是原节点)
194
+ *
195
+ * @example
196
+ * node.moveTo(newParentNode);
85
197
  */
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();
198
+ moveTo(newParent) {
199
+ return new TreeNode(this._item, newParent, this._children);
102
200
  }
103
201
  /**
104
- * 将节点移动到新的父节点下
202
+ * 替换当前节点的数据
203
+ * @param newItem 新的数据对象
204
+ *
205
+ * @example
206
+ * node.replace({ id: 1, name: "新名称", ...otherFields });
105
207
  */
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();
208
+ replace(newItem) {
209
+ this._item = newItem;
132
210
  }
133
211
  /**
134
- * 删除指定节点的某个子节点
212
+ * 获取节点数据
213
+ * @returns 节点数据对象
135
214
  */
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();
215
+ get item() {
216
+ return this._item;
147
217
  }
148
218
  /**
149
- * 删除指定节点的所有子节点
219
+ * 删除所有子节点
220
+ *
221
+ * @example
222
+ * node.removeChildren();
150
223
  */
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);
224
+ removeChildren() {
225
+ this._children = [];
226
+ }
227
+ /**
228
+ * @internal 仅供 TreeNodes 内部使用
229
+ * 设置父节点,并将自己添加到父节点的 children
230
+ * @param parent 新的父节点
231
+ *
232
+ * @description
233
+ * 此方法会:
234
+ * 1. 从原父节点的 children 中移除自己
235
+ * 2. 更新父节点引用
236
+ * 3. 重新计算层级
237
+ * 4. 将自己添加到新父节点的 children 中
238
+ */
239
+ _setParent(parent) {
240
+ // 从原父节点的 children 中移除自己
241
+ const oldParent = this.parent;
242
+ if (oldParent && oldParent._children) {
243
+ const index = oldParent._children.indexOf(this);
244
+ if (index >= 0) {
245
+ oldParent._children.splice(index, 1);
246
+ }
247
+ }
248
+ // 设置新父节点
249
+ this.parent = parent;
250
+ this._level = parent ? parent._level + 1 : 0;
251
+ // 将自己添加到新父节点的 children 中
252
+ if (parent) {
253
+ if (!parent._children) {
254
+ parent._children = [];
255
+ }
256
+ parent._children.push(this);
158
257
  }
159
- // 清空 children 数组
160
- parentNode.children = [];
161
- this.touch();
162
258
  }
259
+ }
260
+ /**
261
+ * TreeNodes 类 - 树节点管理器
262
+ * @template T 节点数据的类型
263
+ *
264
+ * @description
265
+ * TreeNodes 负责管理树结构的数据,包括:
266
+ * - 树的构建和初始化
267
+ * - 节点的映射和查找
268
+ * - 版本管理(用于响应式更新)
269
+ * - 层级展开控制
270
+ *
271
+ * @example
272
+ * interface MyData {
273
+ * id: number;
274
+ * name: string;
275
+ * parentId: number | null;
276
+ * }
277
+ *
278
+ * const treeNodes = new TreeNodes<MyData>({
279
+ * keyField: 'id',
280
+ * textField: 'name',
281
+ * parentKeyField: 'parentId',
282
+ * checkIsRoot: (item) => item.parentId === null,
283
+ * checkIsDirectory: (node) => node.children !== null,
284
+ * expendDepth: 2
285
+ * });
286
+ *
287
+ * treeNodes.setData([
288
+ * { id: 1, name: "根节点", parentId: null },
289
+ * { id: 2, name: "子节点1", parentId: 1 },
290
+ * { id: 3, name: "子节点2", parentId: 1 }
291
+ * ]);
292
+ */
293
+ export default class TreeNodes {
294
+ checkIsRoot;
295
+ keyField;
296
+ parentKeyField;
297
+ textField;
298
+ checkIsDirectory;
299
+ nodeMap;
300
+ compareFun;
301
+ expendDepth;
302
+ _nodes = [];
303
+ _version = 0;
304
+ /**
305
+ * 创建 TreeNodes 实例
306
+ * @param options 配置选项
307
+ */
308
+ constructor(options) {
309
+ this.checkIsRoot = options.checkIsRoot;
310
+ this.compareFun = options.compareFun;
311
+ this.keyField = options.keyField ?? 'id';
312
+ this.textField = options.textField ?? 'text';
313
+ this.parentKeyField = options.parentKeyField ?? 'parentId';
314
+ this.nodeMap = new Map();
315
+ this.expendDepth = options.expendDepth ?? 1;
316
+ this.checkIsDirectory = options.checkIsDirectory;
317
+ }
318
+ /**
319
+ * 设置树数据
320
+ * @param list 扁平化的节点数据数组
321
+ *
322
+ * @description
323
+ * 此方法会:
324
+ * 1. 清空现有树结构
325
+ * 2. 创建所有节点
326
+ * 3. 根据 parentKeyField 建立父子关系
327
+ * 4. 应用展开深度设置
328
+ * 5. 触发版本更新
329
+ *
330
+ * @example
331
+ * treeNodes.setData([
332
+ * { id: 1, name: "根节点", parentId: null },
333
+ * { id: 2, name: "子节点1", parentId: 1 },
334
+ * { id: 3, name: "子节点2", parentId: 1 }
335
+ * ]);
336
+ */
163
337
  setData(list) {
164
338
  this.nodeMap = new Map();
165
339
  this._nodes = [];
166
- // 初始化每个节点
340
+ // 第一遍:创建所有节点(不设置父子关系)
167
341
  for (const item of list) {
168
- const node = this.createNodeWithMethods(item);
169
342
  const key = item[this.keyField];
343
+ const node = new TreeNode(item); // 创建节点时不设置 parent
170
344
  this.nodeMap.set(key, node);
171
345
  }
346
+ // 第二遍:建立父子关系
172
347
  for (const item of list) {
173
- const node = this.nodeMap.get(item[this.keyField]);
174
- if (node) {
175
- this.appendNode(node);
348
+ const key = item[this.keyField];
349
+ const node = this.nodeMap.get(key);
350
+ if (this.checkIsRoot(item)) {
351
+ // 是根节点
352
+ this._nodes.push(node);
353
+ }
354
+ else {
355
+ // 不是根节点,找到父节点并建立关系
356
+ const parentKey = item[this.parentKeyField];
357
+ const parentNode = this.nodeMap.get(parentKey);
358
+ if (parentNode) {
359
+ // 使用 _setParent 方法建立父子关系
360
+ node._setParent(parentNode);
361
+ }
362
+ else {
363
+ console.warn(`Parent node ${parentKey} not found for item ${key}, treating as root node`);
364
+ this._nodes.push(node);
365
+ }
176
366
  }
177
367
  }
178
- if (this.expendDepth > 1) {
179
- this.setExpandForDepth(this.nodes, 0);
368
+ // 应用展开深度设置
369
+ if (this.expendDepth > 0) {
370
+ this.setExpandForDepth(this._nodes, 0);
180
371
  }
181
- this.touch();
372
+ // 触发版本更新
373
+ this._version++;
182
374
  }
183
375
  /**
184
376
  * 获取节点
@@ -203,99 +395,77 @@ export class CommonTreeNodes {
203
395
  setExpandForDepth(nodes, currentLevel) {
204
396
  for (const node of nodes) {
205
397
  node.expand = currentLevel < this.expendDepth;
206
- if (node.children) {
207
- this.setExpandForDepth(node.children, currentLevel + 1);
208
- }
398
+ // if (node.getInternalChildren().length > 0) {
399
+ // this.setExpandForDepth(node.getInternalChildren(), currentLevel + 1);
400
+ // }
209
401
  }
210
402
  }
211
403
  /**
212
- * 添加一个节点
213
- * @param node
214
- * @param doSort
215
- * @protected
404
+ * 获取展开的展示列表
216
405
  */
217
- appendNode(node, doSort = false) {
218
- let item = node.item;
219
- const parentKey = item[this.parentKeyField];
220
- if (this.checkIsRoot(item)) {
221
- this._nodes.push(node);
222
- this._nodes.sort((n1, n2) => {
223
- return this.compareFun?.(n1.item, n2.item);
224
- });
225
- }
226
- else {
227
- const parentNode = this.nodeMap.get(parentKey);
228
- if (parentNode) {
229
- parentNode.expand = true;
230
- parentNode.children = [...(parentNode.children ?? []), node];
231
- if (doSort && this.compareFun) {
232
- parentNode.children = parentNode.children.sort((n1, n2) => {
233
- return this.compareFun(n1.item, n2.item);
234
- });
235
- }
236
- }
237
- else {
238
- console.warn('Ignore the isolate item:', item);
406
+ getHierarchyList() {
407
+ let arr = [];
408
+ this.addExpandNodesToList(this.nodes, arr);
409
+ return arr;
410
+ }
411
+ addExpandNodesToList(nodes, list) {
412
+ for (let node of nodes) {
413
+ list.push(node);
414
+ if (node.expand) {
415
+ this.addExpandNodesToList(node.children ?? [], list);
239
416
  }
240
417
  }
241
418
  }
242
- }
243
- export default class TreeNodes extends CommonTreeNodes {
244
419
  /**
245
- * 获取展开的展示列表
420
+ * 不检测匹配情况,在指定的父节点下添加子节点
246
421
  */
247
- getHierarchyList() {
248
- return this.collectExpandedNodes(this._nodes);
422
+ batchAttachToParent(parent, list) {
423
+ let nodes = [];
424
+ for (let item of list) {
425
+ nodes.push(new TreeNode(item, parent));
426
+ }
427
+ return nodes;
249
428
  }
250
429
  /**
251
430
  * 增加一个新节点(便捷方法,会自动根据 parentKeyField 找到父节点)
252
431
  * @param item 要添加的节点数据
253
432
  */
254
433
  append(item) {
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;
434
+ if (parent) {
435
+ if (this.checkIsRoot(item)) {
436
+ let node = new TreeNode(item);
437
+ this._nodes.push(node);
438
+ this._version++;
439
+ return node;
440
+ }
441
+ else {
442
+ throw new Error(`the entity is not a root data`);
443
+ }
260
444
  }
261
- parentNode.append(item);
262
- }
263
- /**
264
- * 获取除指定节点外的其他节点
265
- * @param exclusiveData
266
- */
267
- extractDirectories(exclusiveData) {
268
- const getNodes = (nodes, item) => {
269
- let list = [];
270
- for (let node of nodes) {
271
- if (node.item != item && this.checkIsDirectory?.(node)) {
272
- list.push({
273
- item: node.item,
274
- expand: true,
275
- children: getNodes(node.children ?? [], item)
276
- });
277
- }
445
+ else {
446
+ let parent = this.findParent(this.nodes, item);
447
+ if (!parent) {
448
+ let node = new TreeNode(item, parent);
449
+ this._version++;
450
+ return node;
278
451
  }
279
- return list;
280
- };
281
- return getNodes(this.nodes, exclusiveData);
452
+ else {
453
+ throw new Error(`cannot find the parent.`);
454
+ }
455
+ }
282
456
  }
283
- /**
284
- * 获取所有展开的节点
285
- * @param tree
286
- * @private
287
- */
288
- collectExpandedNodes(tree) {
289
- const result = [];
290
- function traverse(nodes) {
291
- for (const node of nodes) {
292
- result.push(node);
293
- if (node.expand && node.children) {
294
- traverse(node.children);
457
+ findParent(nodes, item) {
458
+ let found = null;
459
+ for (const node of nodes) {
460
+ if (!found) {
461
+ if (node.item[this.keyField] == item[this.parentKeyField]) {
462
+ found = node;
463
+ }
464
+ else {
465
+ found = node.children && node.children.length > 0 ? this.findParent(node.children, item) : null;
295
466
  }
296
467
  }
297
468
  }
298
- traverse(tree);
299
- return result;
469
+ return found;
300
470
  }
301
471
  }
@@ -1,9 +1,8 @@
1
1
  import TreeNodes from "./TreeNodes";
2
- import type { TreeNode } from "./TreeNodes";
3
- import { CommonTreeNodes } from "./TreeNodes";
2
+ import type { ITreeNode } from "./TreeNodes";
4
3
  import type { CheckIsRoot, CompareFun, GetText, CheckIsDirectory, TreeNodeOptions } from "./TreeNodes";
5
4
  import { compareNumber } from "./TreeNodes";
6
5
  export default TreeNodes;
7
- export { CommonTreeNodes, compareNumber };
8
- export type { TreeNode };
6
+ export { compareNumber };
7
+ export type { ITreeNode };
9
8
  export type { CheckIsRoot, CompareFun, GetText, CheckIsDirectory, TreeNodeOptions };
package/dist/lib/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import TreeNodes from "./TreeNodes";
2
- import { CommonTreeNodes } from "./TreeNodes";
3
2
  import { compareNumber } from "./TreeNodes";
4
3
  export default TreeNodes;
5
- export { CommonTreeNodes, compareNumber };
4
+ export { compareNumber };
@@ -1,6 +1,6 @@
1
- import { CommonTreeNodes } from "../lib/TreeNodes";
1
+ import TreeNodes from "../lib/TreeNodes";
2
2
  import type MenuItem from "./MenuItem";
3
- export default class Menus extends CommonTreeNodes<MenuItem> {
3
+ export default class Menus extends TreeNodes<MenuItem> {
4
4
  /**
5
5
  * 删除所有没有子节点的目录类型节点
6
6
  */