@ticatec/uniface-element 0.3.15 → 0.3.17

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,7 +1,296 @@
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 {
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
+ */
24
+ keyField;
25
+ /**
26
+ * 获取节点的文字
27
+ * 可以是字段名或函数
28
+ */
29
+ textField;
30
+ /**
31
+ * 创建节点视图配置
32
+ * @param options 配置选项
33
+ */
34
+ constructor(options) {
35
+ this.keyField = options.keyField;
36
+ this.textField = options.textField;
37
+ }
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] ?? '');
48
+ }
49
+ /**
50
+ * 获取节点的唯一标识
51
+ * @param data 节点数据
52
+ * @returns 唯一标识值
53
+ */
54
+ getKey(data) {
55
+ return data[this.keyField];
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;
87
+ /**
88
+ * 是否正在加载(用于懒加载)
89
+ */
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);
125
+ }
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;
168
+ }
169
+ this.parent.removeNode(this);
170
+ }
171
+ /**
172
+ * 删除指定的子节点
173
+ * @param node 要删除的子节点
174
+ * @private
175
+ */
176
+ removeNode(node) {
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.');
184
+ }
185
+ }
186
+ else {
187
+ console.warn(' The removed node has not been attached to this.');
188
+ }
189
+ }
190
+ /**
191
+ * 移动到另一个父节点下
192
+ * @param newParent 新的父节点
193
+ * @returns 新创建的节点(注意:返回的是新节点,不是原节点)
194
+ *
195
+ * @example
196
+ * node.moveTo(newParentNode);
197
+ */
198
+ moveTo(newParent) {
199
+ return new TreeNode(this._item, newParent, this._children);
200
+ }
201
+ /**
202
+ * 替换当前节点的数据
203
+ * @param newItem 新的数据对象
204
+ *
205
+ * @example
206
+ * node.replace({ id: 1, name: "新名称", ...otherFields });
207
+ */
208
+ replace(newItem) {
209
+ this._item = newItem;
210
+ }
211
+ /**
212
+ * 获取节点数据
213
+ * @returns 节点数据对象
214
+ */
215
+ get item() {
216
+ return this._item;
217
+ }
218
+ /**
219
+ * 删除所有子节点
220
+ *
221
+ * @example
222
+ * node.removeChildren();
223
+ */
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);
257
+ }
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 {
5
294
  checkIsRoot;
6
295
  keyField;
7
296
  parentKeyField;
@@ -11,6 +300,11 @@ export class CommonTreeNodes {
11
300
  compareFun;
12
301
  expendDepth;
13
302
  _nodes = [];
303
+ _version = 0;
304
+ /**
305
+ * 创建 TreeNodes 实例
306
+ * @param options 配置选项
307
+ */
14
308
  constructor(options) {
15
309
  this.checkIsRoot = options.checkIsRoot;
16
310
  this.compareFun = options.compareFun;
@@ -21,34 +315,77 @@ export class CommonTreeNodes {
21
315
  this.expendDepth = options.expendDepth ?? 1;
22
316
  this.checkIsDirectory = options.checkIsDirectory;
23
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
+ */
24
337
  setData(list) {
25
338
  this.nodeMap = new Map();
26
339
  this._nodes = [];
27
- // 初始化每个节点
340
+ // 第一遍:创建所有节点(不设置父子关系)
28
341
  for (const item of list) {
29
- const node = {
30
- item,
31
- expand: false
32
- };
33
342
  const key = item[this.keyField];
343
+ const node = new TreeNode(item); // 创建节点时不设置 parent
34
344
  this.nodeMap.set(key, node);
35
345
  }
346
+ // 第二遍:建立父子关系
36
347
  for (const item of list) {
37
- const node = this.nodeMap.get(item[this.keyField]);
38
- if (node) {
39
- 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
+ }
40
366
  }
41
367
  }
42
- if (this.expendDepth > 1) {
43
- this.setExpandForDepth(this.nodes, 0);
368
+ // 应用展开深度设置
369
+ if (this.expendDepth > 0) {
370
+ this.setExpandForDepth(this._nodes, 0);
44
371
  }
372
+ // 触发版本更新
373
+ this._version++;
45
374
  }
46
375
  /**
47
376
  * 获取节点
48
377
  */
49
378
  get nodes() {
379
+ // 通过访问 _version 让 Svelte 检测到变化
380
+ void this._version;
50
381
  return this._nodes;
51
382
  }
383
+ /**
384
+ * 获取版本号,用于触发 Svelte 响应式更新
385
+ */
386
+ get version() {
387
+ return this._version;
388
+ }
52
389
  /**
53
390
  * 设置展开层级
54
391
  * @param nodes
@@ -58,157 +395,77 @@ export class CommonTreeNodes {
58
395
  setExpandForDepth(nodes, currentLevel) {
59
396
  for (const node of nodes) {
60
397
  node.expand = currentLevel < this.expendDepth;
61
- if (node.children) {
62
- this.setExpandForDepth(node.children, currentLevel + 1);
63
- }
398
+ // if (node.getInternalChildren().length > 0) {
399
+ // this.setExpandForDepth(node.getInternalChildren(), currentLevel + 1);
400
+ // }
64
401
  }
65
402
  }
66
403
  /**
67
- * 添加一个节点
68
- * @param node
69
- * @param doSort
70
- * @protected
404
+ * 获取展开的展示列表
71
405
  */
72
- appendNode(node, doSort = false) {
73
- let item = node.item;
74
- const parentKey = item[this.parentKeyField];
75
- if (this.checkIsRoot(item)) { //|| !this.nodeMap.has(parentKey)
76
- this._nodes.push(node);
77
- this._nodes.sort(this.compareFun);
78
- }
79
- else {
80
- const parentNode = this.nodeMap.get(parentKey);
81
- if (parentNode) {
82
- parentNode.expand = true;
83
- parentNode.children = [...(parentNode.children ?? []), node];
84
- if (doSort && this.compareFun) {
85
- parentNode.children = parentNode.children.sort(this.compareFun);
86
- }
87
- }
88
- else {
89
- 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);
90
416
  }
91
417
  }
92
418
  }
93
- }
94
- export default class TreeNodes extends CommonTreeNodes {
95
419
  /**
96
- * 获取展开的展示列表
420
+ * 不检测匹配情况,在指定的父节点下添加子节点
97
421
  */
98
- getHierarchyList() {
99
- 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;
100
428
  }
101
429
  /**
102
- * 给treeview设定数据,根据数据构建树结构
103
- * @param list
104
- */
105
- /**
106
- * 增加一个新节点
107
- * @param item
430
+ * 增加一个新节点(便捷方法,会自动根据 parentKeyField 找到父节点)
431
+ * @param item 要添加的节点数据
108
432
  */
109
433
  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);
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;
128
440
  }
129
441
  else {
130
- this._nodes.sort(this.compareFun);
442
+ throw new Error(`the entity is not a root data`);
131
443
  }
132
444
  }
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
- }
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;
147
451
  }
148
452
  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);
453
+ throw new Error(`cannot find the parent.`);
173
454
  }
174
455
  }
175
456
  }
176
- /**
177
- * 获取除指定节点外的其他节点
178
- * @param exclusiveData
179
- */
180
- extractDirectories(exclusiveData) {
181
- const getNodes = (nodes, item) => {
182
- let list = [];
183
- for (let node of nodes) {
184
- if (node.item != item && this.checkIsDirectory?.(node)) {
185
- list.push({
186
- item: node.item,
187
- expand: true,
188
- children: getNodes(node.children ?? [], item)
189
- });
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;
190
463
  }
191
- }
192
- return list;
193
- };
194
- return getNodes(this.nodes, exclusiveData);
195
- }
196
- /**
197
- * 获取所有展开的节点
198
- * @param tree
199
- * @private
200
- */
201
- collectExpandedNodes(tree) {
202
- const result = [];
203
- function traverse(nodes) {
204
- for (const node of nodes) {
205
- result.push(node);
206
- if (node.expand && node.children) {
207
- traverse(node.children);
464
+ else {
465
+ found = node.children && node.children.length > 0 ? this.findParent(node.children, item) : null;
208
466
  }
209
467
  }
210
468
  }
211
- traverse(tree);
212
- return result;
469
+ return found;
213
470
  }
214
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,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;
@@ -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
  */
@@ -1,5 +1,6 @@
1
- import { CommonTreeNodes } from "../lib/TreeNodes";
2
- export default class Menus extends CommonTreeNodes {
1
+ import {} from "../lib/TreeNodes";
2
+ import TreeNodes from "../lib/TreeNodes";
3
+ export default class Menus extends TreeNodes {
3
4
  /**
4
5
  * 删除所有没有子节点的目录类型节点
5
6
  */
@@ -12,8 +13,9 @@ export default class Menus extends CommonTreeNodes {
12
13
  }
13
14
  else {
14
15
  if (this.checkIsDirectory?.(node)) {
15
- nodes.splice(i, 1); // 从父节点的 children 中移除
16
- this.nodeMap.delete(node.item[this.keyField]); // nodeMap 中移除
16
+ node.detach();
17
+ //nodes.splice(i, 1); // 从父节点的 children 中移除
18
+ //this.nodeMap.delete(node._item[this.keyField]); // 从 nodeMap 中移除
17
19
  }
18
20
  }
19
21
  }