dlsjs 0.1.26 → 1.0.3

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.
package/src/ES/Tree.js ADDED
@@ -0,0 +1,916 @@
1
+ import {uniqBy} from "ramda";
2
+
3
+ /**
4
+ * 列表转树(高性能版,适配100W+数据)
5
+ * 时间复杂度O(n):n 是 list 的长度(输入数据量)
6
+ * 不改变原数组指针,生成的children也会注入原数组
7
+ *
8
+ * @param {Array} list - 原始列表数据
9
+ * @param {Object} extendProps - 自定义属性映射 {id: 'id', children: 'children', parentId: 'parentId'}
10
+ * @returns {Array} 树形结构数据
11
+ */
12
+ export function list2Tree (list, extendProps = {}) {
13
+ // 1. 配置默认属性映射,兼容自定义字段
14
+ const props = {
15
+ id: 'id',
16
+ children: 'children',
17
+ parentId: 'parentId',
18
+ useConcat: false, // 保留原有配置,实际优化后无需concat
19
+ ...extendProps
20
+ };
21
+
22
+ // 校验数据结构
23
+ if(list && list[0] && !list[0].hasOwnProperty(props.id)) {
24
+ console.warn('数据非标准,未找到唯一标识字段,直接返回')
25
+ return list
26
+ }
27
+
28
+ const { id: idKey, children: childrenKey, parentId: parentIdKey, useConcat } = props;
29
+
30
+ // -1.先清空children。防止重复叠加调用。
31
+ if(useConcat === false) {
32
+ for (const node of list) {
33
+ delete node[childrenKey];
34
+ }
35
+ }
36
+
37
+ // 2. 构建ID到节点的哈希映射(O(n)),同时初始化children数组
38
+ const nodeMap = new Map();
39
+ const rootNodes = []; // 存储根节点(parentId为空/0/不存在的节点)
40
+
41
+ // 第一次遍历:初始化映射和children,O(n)
42
+ for (const node of list) {
43
+ const nodeId = node[idKey];
44
+ // 初始化children数组(避免后续判断undefined)
45
+ if (!node[childrenKey]) {
46
+ node[childrenKey] = [];
47
+ }
48
+ // 将节点存入Map,方便后续快速查找父节点
49
+ nodeMap.set(nodeId, node);
50
+ }
51
+
52
+ // 第二次遍历:关联父子节点,O(n)
53
+ for (const node of list) {
54
+ const parentId = node[parentIdKey];
55
+ const parentNode = nodeMap.get(parentId);
56
+
57
+ if (parentNode) {
58
+ // 有父节点:添加到父节点的children中
59
+ if (useConcat && Array.isArray(parentNode[childrenKey])) {
60
+ parentNode[childrenKey] = parentNode[childrenKey].concat(node);
61
+ } else {
62
+ parentNode[childrenKey].push(node);
63
+ }
64
+ } else {
65
+ // 无父节点:归为根节点(parentId为空/0/不存在)
66
+ rootNodes.push(node);
67
+ }
68
+ }
69
+
70
+ // 3. 清理空children(可选,保持和原逻辑一致)
71
+ for (const node of list) {
72
+ if (Array.isArray(node[childrenKey]) && node[childrenKey].length === 0) {
73
+ delete node[childrenKey];
74
+ }
75
+ }
76
+
77
+ return rootNodes;
78
+ }
79
+
80
+ /**
81
+ * 为list添加_parentIds属性,属性为不含自身的数组,顺序从最外层开始
82
+ * 该函数将遍历列表中的每个节点,并为其添加一个_parentIds属性,
83
+ * 该属性包含从根节点到当前节点父节点的完整路径(不包括自身)
84
+ * 时间复杂度O(n):n 是 list 的长度(输入数据量)
85
+ *
86
+ * @param {Array} list - 包含树节点的平铺数组,每个节点应具有id和parentId属性
87
+ *
88
+ * @example
89
+ * const list = [
90
+ * { id: '1', name: 'Root' },
91
+ * { id: '2', name: 'Child1', parentId: '1' },
92
+ * { id: '3', name: 'GrandChild1', parentId: '2' }
93
+ * ];
94
+ *
95
+ * setParentIds4List(list);
96
+ * // 结果:
97
+ * // list[0] = { id: '1', name: 'Root', _parentIds: [] }
98
+ * // list[1] = { id: '2', name: 'Child1', parentId: '1', _parentIds: ['1'] }
99
+ * // list[2] = { id: '3', name: 'GrandChild1', parentId: '2', _parentIds: ['1', '2'] }
100
+ *
101
+ * @returns {Array} list - 直接返回入参数组,指针不变
102
+ */
103
+ export function setParentIds4List(list) {
104
+ if (!list || list.length === 0) return;
105
+
106
+ // 创建ID到节点的映射表
107
+ const nodeMap = new Map();
108
+ for (const node of list) {
109
+ nodeMap.set(node.id, node);
110
+ node._parentIds = []; // 初始化_parentIds
111
+ }
112
+
113
+ // 计算每个节点的_parentIds,使用记忆化避免重复计算
114
+ function calculateParentIds(node) {
115
+ if (node._parentIds.length > 0 || !node.parentId) {
116
+ // 如果已经计算过或者没有父节点,直接返回
117
+ return node._parentIds;
118
+ }
119
+
120
+ const parentNode = nodeMap.get(node.parentId);
121
+ if (!parentNode) {
122
+ // 父节点不存在,清空_parentIds
123
+ node._parentIds = [];
124
+ return node._parentIds;
125
+ }
126
+
127
+ // 先确保父节点的_parentIds已经计算完成
128
+ const parentParentIds = calculateParentIds(parentNode);
129
+
130
+ // 当前节点的_parentIds = 父节点的_parentIds + 父节点ID
131
+ node._parentIds = [...parentParentIds, node.parentId];
132
+
133
+ return node._parentIds;
134
+ }
135
+
136
+ // 遍历所有节点,计算_parentIds
137
+ for (const node of list) {
138
+ if (node._parentIds.length === 0) { // 只对未计算过的节点进行计算
139
+ calculateParentIds(node);
140
+ }
141
+ }
142
+ return list
143
+ }
144
+
145
+ /**
146
+ * 查找树上的点
147
+ * 在树形结构中查找满足指定条件的第一个节点
148
+ * 时间复杂度O(n):n 是 treeLike 的节点总数(输入数据量)
149
+ *
150
+ * @param {Array|Object|null|undefined} treeLike - 待处理的树形结构数据,可以是数组、单个对象或null/undefined
151
+ * @param {Function} func - 判断函数,接收节点作为参数,返回true表示匹配
152
+ * @returns {Object|null} 找到的第一个匹配节点,未找到则返回null
153
+ *
154
+ * @example
155
+ * const tree = [
156
+ * { id: 1, name: 'A', children: [
157
+ * { id: 2, name: 'B' },
158
+ * { id: 3, name: 'C', children: [
159
+ * { id: 4, name: 'D' }
160
+ * ]}
161
+ * ]},
162
+ * { id: 5, name: 'E' }
163
+ * ];
164
+ *
165
+ * // 查找id为3的节点
166
+ * const result = treeFind(tree, node => node.id === 3);
167
+ * console.log(result); // { id: 3, name: 'C', children: [{ id: 4, name: 'D' }] }
168
+ */
169
+ export function treeFind(treeLike, func) {
170
+ // 统一处理为数组格式
171
+ const tree = treeSafe(treeLike)
172
+
173
+ // 使用迭代方式遍历,避免深层递归可能导致的栈溢出
174
+ const stack = [...tree];
175
+
176
+ while (stack.length > 0) {
177
+ const currentNode = stack.pop();
178
+
179
+ if (func(currentNode)) {
180
+ return currentNode;
181
+ }
182
+
183
+ // 将子节点添加到栈中(反向添加以保持遍历顺序)
184
+ if (currentNode?.children && Array.isArray(currentNode.children)) {
185
+ for (let i = currentNode.children.length - 1; i >= 0; i--) {
186
+ stack.push(currentNode.children[i]);
187
+ }
188
+ }
189
+ }
190
+
191
+ return null;
192
+ }
193
+
194
+ /**
195
+ * 查找节点的父级
196
+ * 在树结构中查找满足条件的节点的父节点
197
+ *
198
+ * @param {Array|Object|null|undefined} treeLike - 待处理的树形结构数据,可以是数组、单个对象或null/undefined
199
+ * @param {Function} func - 用于判断节点是否满足条件的函数
200
+ * @param {*} parent - 当前层级的父节点,默认为null
201
+ *
202
+ * @returns {*} 找到的第一个满足条件的节点的父节点,如果没找到则返回null
203
+ *
204
+ * 时间复杂度: O(n) - 其中n是树中节点总数,最坏情况下需要遍历所有节点
205
+ * 空间复杂度: O(h) - 其中h是树的高度,用于存储迭代栈的空间
206
+ *
207
+ * @example
208
+ * const tree = [
209
+ * {
210
+ * id: '1',
211
+ * name: 'parent',
212
+ * children: [
213
+ * { id: '11', name: 'child1' },
214
+ * { id: '12', name: 'child2' }
215
+ * ]
216
+ * }
217
+ * ];
218
+ * const parent = treeFindParent(tree, item => item.id === '11');
219
+ * // 返回 id 为 '1' 的节点
220
+ */
221
+ export function treeFindParent(treeLike, func, parent) {
222
+ // 统一处理为数组格式
223
+ const tree = treeSafe(treeLike)
224
+
225
+ // 输入验证
226
+ if (typeof func !== 'function') {
227
+ return null;
228
+ }
229
+
230
+ // 使用栈进行迭代遍历,避免深层递归导致的栈溢出
231
+ const stack = tree.map(node => ({ node, parent }));
232
+
233
+ while (stack.length > 0) {
234
+ const { node, parent: parentNode } = stack.pop();
235
+
236
+ // 检查当前节点是否满足条件
237
+ if (func(node)) {
238
+ return parentNode; // 如果满足条件,返回其父节点
239
+ }
240
+
241
+ // 如果当前节点有子节点,将子节点加入栈中继续遍历
242
+ if (node.children && Array.isArray(node.children)) {
243
+ // 反向添加子节点以保持原有遍历顺序
244
+ for (let i = node.children.length - 1; i >= 0; i--) {
245
+ stack.push({ node: node.children[i], parent: node });
246
+ }
247
+ }
248
+ }
249
+
250
+ // 遍历完所有节点仍未找到满足条件的节点,返回null
251
+ return null;
252
+ }
253
+
254
+
255
+ /**
256
+ * 修改树上的点
257
+ * 对树形结构中的每个节点应用变换函数,返回一个新的树结构
258
+ *
259
+ * @param {Array|Object} treeLike - 树形结构数据,可以是数组或单个对象
260
+ * @param {Function} func - 应用于每个节点的变换函数,接收节点作为参数并返回新节点
261
+ *
262
+ * @returns {Array} 经过变换后的新树形结构数组
263
+ *
264
+ * 时间复杂度: O(n) - 其中n是树中节点总数,需要访问每个节点一次
265
+ * 空间复杂度: O(n + h) - 其中n用于存储新树结构,h是树的高度用于递归调用栈
266
+ *
267
+ * @example
268
+ * const tree = [
269
+ * {
270
+ * id: '1',
271
+ * name: 'parent',
272
+ * children: [
273
+ * { id: '11', name: 'child1' },
274
+ * { id: '12', name: 'child2' }
275
+ * ]
276
+ * }
277
+ * ];
278
+ * const newTree = treeMap(tree, node => ({ ...node, processed: true }));
279
+ */
280
+ export function treeMap(treeLike, func) {
281
+ // 统一处理为数组格式
282
+ const tree = treeSafe(treeLike)
283
+ // 输入验证
284
+ if (typeof func !== 'function') {
285
+ console.warn('treeMap: func 参数必须是函数类型');
286
+ return tree;
287
+ }
288
+
289
+ // 创建新数组以避免修改原数据
290
+ const newTree = [];
291
+
292
+ for (let i = 0; i < tree.length; i++) {
293
+ const item = tree[i];
294
+
295
+ // 对当前节点应用变换函数
296
+ const newItem = func(item);
297
+
298
+ // 递归处理子节点(如果存在)
299
+ if (item.children && Array.isArray(item.children) && item.children.length > 0) {
300
+ // 注意:应该对newItem的children进行映射,而不是item的children
301
+ newTree[i] = {
302
+ ...newItem,
303
+ children: treeMap(item.children, func)
304
+ };
305
+ } else {
306
+ newTree[i] = newItem;
307
+ }
308
+ }
309
+
310
+ return newTree;
311
+ }
312
+
313
+
314
+ /**
315
+ * 遍历树上的点
316
+ * 对树形结构中的每个节点执行指定的函数
317
+ *
318
+ * @param {Array|Object} treeLike - 树形结构数据,可以是数组或单个对象
319
+ * @param {Function} func - 对每个节点执行的回调函数,接收节点作为参数
320
+ *
321
+ * @returns {void}
322
+ *
323
+ * 时间复杂度: O(n) - 其中n是树中节点总数,需要访问每个节点一次
324
+ * 空间复杂度: O(h) - 其中h是树的高度,用于递归调用栈的存储空间
325
+ *
326
+ * @example
327
+ * const tree = [
328
+ * {
329
+ * id: '1',
330
+ * name: 'parent',
331
+ * children: [
332
+ * { id: '11', name: 'child1' },
333
+ * { id: '12', name: 'child2' }
334
+ * ]
335
+ * }
336
+ * ];
337
+ * treeForEach(tree, node => console.log(node.id));
338
+ * // 输出: '1', '11', '12'
339
+ */
340
+ export function treeForEach(treeLike, func) {
341
+ // 输入验证
342
+ if (typeof func !== 'function') {
343
+ console.warn('treeForEach: func 参数必须是函数类型');
344
+ return;
345
+ }
346
+
347
+ // 统一处理为数组格式
348
+ const tree = treeSafe(treeLike);
349
+
350
+ // 使用迭代方式替代递归,避免深层树导致的栈溢出
351
+ const stack = [...tree];
352
+
353
+ while (stack.length > 0) {
354
+ const currentItem = stack.pop();
355
+
356
+ // 对当前节点执行函数
357
+ func(currentItem);
358
+
359
+ // 如果当前节点有子节点,将子节点添加到栈中(逆序添加以保持原有遍历顺序)
360
+ if (currentItem.children && Array.isArray(currentItem.children) && currentItem.children.length > 0) {
361
+ for (let i = currentItem.children.length - 1; i >= 0; i--) {
362
+ stack.push(currentItem.children[i]);
363
+ }
364
+ }
365
+ }
366
+ }
367
+
368
+
369
+ /**
370
+ * 删除树上的点,所有符合条件的节点都将被删除,直接操作原始树,返回被删除的节点数组。
371
+ *
372
+ * @param {Array|Object} treeLike - 树形结构数据,可以是数组或单个对象
373
+ * @param {Function} func - 判断节点是否需要删除的函数,接收节点作为参数
374
+ *
375
+ * @returns {Array} 入参treeLike,直接返回,指针不变
376
+ *
377
+ * 时间复杂度: O(n) - 其中n是树中节点总数,需要访问每个节点一次
378
+ * 空间复杂度: O(n) - 最坏情况下需要存储所有被删除的节点和栈空间
379
+ *
380
+ * @example
381
+ * const tree = [
382
+ * {
383
+ * id: '1',
384
+ * name: 'parent',
385
+ * children: [
386
+ * { id: '11', name: 'child1' },
387
+ * { id: '12', name: 'child2' }
388
+ * ]
389
+ * }
390
+ * ];
391
+ * const deleted = treeDel(tree, node => node.id === '11');
392
+ * // deleted 包含被删除的节点
393
+ * // tree 中id为'11'的节点及其所有子节点已被删除
394
+ */
395
+ export function treeDel(treeLike, func) {
396
+ // 输入验证
397
+ if (typeof func !== 'function') {
398
+ console.warn('dlsjs: treeDel函数的第二个参数必须是函数类型');
399
+ return [];
400
+ }
401
+
402
+ // 统一处理为数组格式
403
+ const tree = treeSafe(treeLike);
404
+
405
+ // 存储被删除的节点
406
+ const removedNodes = [];
407
+
408
+ // 使用迭代方式替代递归,避免深层树导致的栈溢出
409
+ // 使用栈来存储需要处理的节点及其父节点的children数组和索引
410
+ const stack = [];
411
+
412
+ // 初始化栈,存储根节点数组
413
+ for (let i = tree.length - 1; i >= 0; i--) {
414
+ stack.push({
415
+ node: tree[i],
416
+ parentArray: tree,
417
+ index: i
418
+ });
419
+ }
420
+
421
+ // 处理栈中的节点(从后往前处理,避免索引变化影响)
422
+ const nodesToDelete = [];
423
+
424
+ while (stack.length > 0) {
425
+ const { node, parentArray, index } = stack.pop();
426
+
427
+ // 检查当前节点是否符合删除条件
428
+ if (func(node)) {
429
+ // 标记节点待删除
430
+ nodesToDelete.push({ node, parentArray, index });
431
+ } else {
432
+ // 如果节点不符合删除条件,将其子节点加入栈中继续处理
433
+ if (node.children && Array.isArray(node.children) && node.children.length > 0) {
434
+ // 从后向前添加子节点,以保持正确的处理顺序
435
+ for (let i = node.children.length - 1; i >= 0; i--) {
436
+ stack.push({
437
+ node: node.children[i],
438
+ parentArray: node.children,
439
+ index: i
440
+ });
441
+ }
442
+ }
443
+ }
444
+ }
445
+
446
+ // 按照从后往前的顺序删除节点,避免索引变化影响
447
+ nodesToDelete.sort((a, b) => {
448
+ // 按照在各自数组中的索引从大到小排序
449
+ if (a.parentArray === b.parentArray) {
450
+ return b.index - a.index;
451
+ }
452
+ // 如果在不同数组中,我们简单地按删除顺序处理
453
+ return 0;
454
+ });
455
+
456
+ // 实际删除节点
457
+ for (const deletion of nodesToDelete) {
458
+ const { node, parentArray, index } = deletion;
459
+
460
+ // 从数组中移除节点并添加到已删除节点数组
461
+ const removed = parentArray.splice(index, 1)[0];
462
+ removedNodes.push(removed);
463
+ }
464
+
465
+ return tree;
466
+ }
467
+
468
+
469
+
470
+ /**
471
+ * 寻找叶子对象
472
+ * 沿着指定的键路径递归查找,直到找到没有该键的对象
473
+ *
474
+ * @param {Object} obj - 要查找的对象
475
+ * @param {string} leafKey - 用于查找的键名
476
+ *
477
+ * @returns {Object|null} 找到的叶子对象,如果输入无效则返回null
478
+ *
479
+ * 时间复杂度: O(k) - 其中k是叶子键的嵌套深度
480
+ * 空间复杂度: O(1) - 使用迭代方式,仅需常量额外空间
481
+ *
482
+ * @example
483
+ * const obj = {
484
+ * a: {
485
+ * b: {
486
+ * c: 'leaf value'
487
+ * }
488
+ * }
489
+ * };
490
+ * const result = findLeaf(obj, 'a'); // 返回 { b: { c: 'leaf value' } }
491
+ * const result2 = findLeaf(obj, 'c'); // 返回 'leaf value'
492
+ */
493
+ export function findLeaf(obj, leafKey) {
494
+ // 输入验证
495
+ if (obj === null || obj === undefined || typeof obj !== 'object') {
496
+ console.warn('dlsjs: findLeaf函数的第一个参数必须是对象类型');
497
+ return null;
498
+ }
499
+
500
+ if (typeof leafKey !== 'string' || leafKey.trim() === '') {
501
+ console.warn('dlsjs: findLeaf函数的第二个参数必须是有效的字符串');
502
+ return null;
503
+ }
504
+
505
+ // 使用迭代方式替代递归,避免深层嵌套导致的栈溢出
506
+ let current = obj;
507
+
508
+ while (current !== null && typeof current === 'object') {
509
+ // 检查当前对象是否包含指定的键
510
+ if (current.hasOwnProperty(leafKey)) {
511
+ // 如果包含键,继续向下查找
512
+ current = current[leafKey];
513
+ } else {
514
+ // 如果不包含键,说明已经到达叶子节点
515
+ break;
516
+ }
517
+ }
518
+
519
+ return current;
520
+ }
521
+
522
+
523
+ /**
524
+ * 沿着叶子键路径查找属性值
525
+ * 在当前对象中查找指定属性,如果不存在则沿着叶子键路径递归查找
526
+ *
527
+ * @param {Object} obj - 要查找的对象
528
+ * @param {string} leafKey - 用于递归查找的叶子键名
529
+ * @param {string} propName - 要查找的属性名
530
+ *
531
+ * @returns {any|null} 找到的属性值,如果未找到则返回null
532
+ *
533
+ * 时间复杂度: O(k) - 其中k是叶子键的嵌套深度
534
+ * 空间复杂度: O(1) - 使用迭代方式,仅需常量额外空间
535
+ *
536
+ * @example
537
+ * const obj = {
538
+ * a: {
539
+ * b: {
540
+ * c: 'leaf value',
541
+ * targetProp: 'found value'
542
+ * }
543
+ * },
544
+ * targetProp: 'root value'
545
+ * };
546
+ * const result = forkProp(obj, 'a', 'targetProp'); // 返回 'root value'
547
+ * const result2 = forkProp(obj, 'a', 'nonexistent'); // 返回 null
548
+ */
549
+ export function forkProp(obj, leafKey, propName) {
550
+ // 输入验证
551
+ if (obj === null || obj === undefined || typeof obj !== 'object') {
552
+ console.warn('dlsjs: forkProp函数的第一个参数必须是对象类型');
553
+ return null;
554
+ }
555
+
556
+ if (typeof leafKey !== 'string' || leafKey.trim() === '') {
557
+ console.warn('dlsjs: forkProp函数的第二个参数必须是有效的字符串');
558
+ return null;
559
+ }
560
+
561
+ if (typeof propName !== 'string' || propName.trim() === '') {
562
+ console.warn('dlsjs: forkProp函数的第三个参数必须是有效的字符串');
563
+ return null;
564
+ }
565
+
566
+ // 使用迭代方式替代递归,避免深层嵌套导致的栈溢出
567
+ let current = obj;
568
+
569
+ while (current !== null && typeof current === 'object') {
570
+ // 1. 优先在当前对象中查找目标属性
571
+ if (current.hasOwnProperty(propName)) {
572
+ return current[propName];
573
+ }
574
+
575
+ // 2. 如果当前对象包含叶子键,继续向下查找
576
+ if (current.hasOwnProperty(leafKey)) {
577
+ current = current[leafKey];
578
+ } else {
579
+ // 3. 如果不包含叶子键,说明已经到达叶子节点,查找结束
580
+ break;
581
+ }
582
+ }
583
+
584
+ // 遍历完所有层级仍未找到目标属性
585
+ return null;
586
+ }
587
+
588
+
589
+ /**
590
+ * 维表转树
591
+ * @param dimList 平铺维表
592
+ * @param dimLink 维表关联关系 e.g.['市','区县','乡镇']
593
+ * @param dimMap 维表关联映射字段 e.g.{'市': {propFieldName: 'code1', textFieldName: 'name1'}, '区': {propFieldName: 'code2', textFieldName: 'name2'}, '乡镇': {propFieldName: 'code3', textFieldName: 'name3'}}
594
+ */
595
+ export function dim2Tree(dimList, dimLink, dimMap) {
596
+ let list = dim2List(dimList, dimLink, dimMap)
597
+ return list2Tree(list);
598
+ }
599
+
600
+ /**
601
+ * 维度数据转平铺列表
602
+ * 将多维度数据转换为带有层级关系的平铺列表,用于构建树形结构
603
+ * 时间复杂度O(m × k):
604
+ * m 是 dimList 的长度(输入数据量)
605
+ * k 是 dimLink 的长度(维度层级数量)
606
+ *
607
+ * @param {Array} dimList - 维度数据源,包含各个维度信息的对象数组
608
+ * @param {Array} dimLink - 维度关联关系,按层级顺序排列的维度名称数组
609
+ * @param {Object} dimMap - 维度映射配置,定义各维度的属性字段
610
+ *
611
+ * @returns {Array} 转换后的节点列表,每个节点包含 id、parentId、label、value 等属性
612
+ *
613
+ * @example
614
+ * const dimList = [
615
+ * { code1: '01', name1: '北京', code2: '0101', name2: '东城区' }
616
+ * ];
617
+ * const dimLink = ['市', '区'];
618
+ * const dimMap = {
619
+ * '市': { propFieldName: 'code1', textFieldName: 'name1' },
620
+ * '区': { propFieldName: 'code2', textFieldName: 'name2' }
621
+ * };
622
+ *
623
+ * const result = dim2List(dimList, dimLink, dimMap);
624
+ */
625
+ export function dim2List(dimList, dimLink, dimMap) {
626
+ // 验证输入参数
627
+ if (!Array.isArray(dimList) || !Array.isArray(dimLink) || !dimMap) {
628
+ return [];
629
+ }
630
+
631
+ // 构建字段排序数组,按维度关联顺序排列
632
+ const fieldSort = [];
633
+ for (let i = 0; i < dimLink.length; i++) {
634
+ const propName = dimLink[i];
635
+ if (propName) {
636
+ const match = dimMap[propName];
637
+ if (match) {
638
+ fieldSort.push(match);
639
+ }
640
+ }
641
+ }
642
+
643
+ // 如果没有有效的字段配置,直接返回空数组
644
+ if (fieldSort.length === 0) {
645
+ return [];
646
+ }
647
+
648
+ const dataTreeOrg = [];
649
+
650
+ // 遍历每条维度数据
651
+ for (let i = 0; i < dimList.length; i++) {
652
+ const record = dimList[i];
653
+
654
+ // 为每个维度层级生成对应的节点
655
+ for (let j = 0; j < fieldSort.length; j++) {
656
+ const fieldConfig = fieldSort[j];
657
+
658
+ // 创建节点对象
659
+ const node = {
660
+ id: '',
661
+ parentId: '',
662
+ label: '',
663
+ value: ''
664
+ };
665
+
666
+ // 构建节点ID:连接当前层级及之前所有层级的编码
667
+ for (let k = 0; k <= j; k++) {
668
+ const currentField = fieldSort[k];
669
+ node.id += record[currentField.propFieldName] || '';
670
+ }
671
+
672
+ // 设置父节点ID:连接当前层级之前所有层级的编码
673
+ if (j > 0) {
674
+ for (let k = 0; k < j; k++) {
675
+ const prevField = fieldSort[k];
676
+ node.parentId += record[prevField.propFieldName] || '';
677
+ }
678
+ } else {
679
+ // 顶层节点的父ID设为'-1'
680
+ node.parentId = '-1';
681
+ }
682
+
683
+ // 设置显示标签和值
684
+ node.label = record[fieldConfig.textFieldName] || '';
685
+ node.value = record[fieldConfig.propFieldName] || '';
686
+
687
+ dataTreeOrg.push(node);
688
+ }
689
+ }
690
+
691
+ // 使用uniqBy去重,确保每个ID只对应一个节点
692
+ // 注意:这里假设uniqBy函数已正确导入
693
+ return uniqBy(item => item.id, dataTreeOrg);
694
+ }
695
+
696
+ /**
697
+ * 树筛选(包含父节点)
698
+ * 筛选出符合条件的节点及其所有父节点,返回包含这些节点的树形结构
699
+ *
700
+ * @param {Array|Object} treeLike - 树形结构数据,可以是数组或单个对象
701
+ * @param {Function} func - 判断节点是否满足条件的函数
702
+ *
703
+ * @returns {Array} 包含符合条件的节点及其所有父节点的树形结构
704
+ *
705
+ * 时间复杂度: O(n) - 其中n是树中节点总数,需要访问每个节点一次
706
+ * 空间复杂度: O(n) - 需要存储所有节点和父节点关系
707
+ *
708
+ * @example
709
+ * const tree = [
710
+ * {
711
+ * id: '1',
712
+ * name: 'parent',
713
+ * children: [
714
+ * { id: '11', name: 'child1' },
715
+ * { id: '12', name: 'child2' }
716
+ * ]
717
+ * }
718
+ * ];
719
+ * const result = treeFilter(tree, node => node.id === '11');
720
+ * // 返回包含id为'1'和'11'的节点的树形结构
721
+ */
722
+ export function treeFilter(treeLike, func) {
723
+ // 输入验证
724
+ if (typeof func !== 'function') {
725
+ console.warn('dlsjs: treeFilter函数的第二个参数必须是函数类型');
726
+ return [];
727
+ }
728
+
729
+ // 统一处理为数组格式
730
+ const tree = treeSafe(treeLike);
731
+
732
+ // 如果树为空,直接返回空数组
733
+ if (tree.length === 0) {
734
+ return [];
735
+ }
736
+
737
+ // 将树转换为平铺列表
738
+ const list = tree2List(tree);
739
+
740
+ // 使用迭代方式设置父ID列表,避免递归栈溢出
741
+ setParentIds4List(list);
742
+
743
+ // 筛选符合条件的节点
744
+ const matchedNodes = list.filter(func);
745
+
746
+ // 如果没有符合条件的节点,返回空数组
747
+ if (matchedNodes.length === 0) {
748
+ return [];
749
+ }
750
+
751
+ // 收集所有需要保留的节点ID(符合条件的节点及其所有父节点)
752
+ const nodeIdsToKeep = new Set();
753
+
754
+ // 添加符合条件的节点ID
755
+ for (let i = 0; i < matchedNodes.length; i++) {
756
+ nodeIdsToKeep.add(matchedNodes[i].id);
757
+ }
758
+
759
+ // 添加所有父节点ID
760
+ for (let i = 0; i < matchedNodes.length; i++) {
761
+ const parentIds = matchedNodes[i]._parentIds || [];
762
+ for (let j = 0; j < parentIds.length; j++) {
763
+ nodeIdsToKeep.add(parentIds[j]);
764
+ }
765
+ }
766
+
767
+ // 筛选出需要保留的节点
768
+ const filteredList = list.filter(item => nodeIdsToKeep.has(item.id));
769
+
770
+ // 将筛选后的列表重新构建为树形结构
771
+ return list2Tree(filteredList);
772
+ }
773
+
774
+ /**
775
+ * 树转平铺
776
+ * 将树形结构数据转换为平铺的数组形式
777
+ *
778
+ * @param {Array|Object|null|undefined} treeLike - 待处理的树形结构数据,可以是数组、单个对象或null/undefined
779
+ * @param {Array} list - 可选的初始数组,用于累积结果,默认为空数组
780
+ *
781
+ * @returns {Array} 包含所有节点的平铺数组
782
+ *
783
+ * 时间复杂度: O(n) - 其中n是树中节点总数
784
+ * 空间复杂度: O(n) - 用于存储结果数组和递归调用栈
785
+ *
786
+ * @example
787
+ * const tree = [
788
+ * {
789
+ * id: '1',
790
+ * name: 'a',
791
+ * children: [
792
+ * { id: '11', name: 'a1', parentId: '1' },
793
+ * { id: '12', name: 'a2', parentId: '1' }
794
+ * ]
795
+ * }
796
+ * ];
797
+ * console.log(tree2List(tree));
798
+ * // 输出: [{ id: '1', name: 'a', children: [...] }, { id: '11', name: 'a1', parentId: '1' }, { id: '12', name: 'a2', parentId: '1' }]
799
+ */
800
+ export function tree2List(treeLike, list = []) {
801
+ // 统一处理为数组格式
802
+ const tree = treeSafe(treeLike)
803
+
804
+ // 使用迭代方法替代递归,避免深层树导致的栈溢出
805
+ const stack = [...tree];
806
+
807
+ while (stack.length > 0) {
808
+ const currentNode = stack.pop();
809
+
810
+ // 添加当前节点到结果列表
811
+ list.push(currentNode);
812
+
813
+ // 如果当前节点有子节点,则将子节点添加到栈中(逆序添加以保持原有顺序)
814
+ if (currentNode.children && Array.isArray(currentNode.children) && currentNode.children.length > 0) {
815
+ // 逆序添加子节点以保持与原递归实现相同的遍历顺序
816
+ for (let i = currentNode.children.length - 1; i >= 0; i--) {
817
+ stack.push(currentNode.children[i]);
818
+ }
819
+ }
820
+ }
821
+
822
+ return list;
823
+ }
824
+
825
+
826
+ /**
827
+ * 裁剪树形结构到指定层级(高性能版,不裁剪原始树,返回一颗新树)
828
+ * @param {Array|Object|null|undefined} treeLike - 待处理的树形结构数据,可以是数组、单个对象或null/undefined
829
+ * @param {number} level - 要保留的层级(从1开始,level=1保留根节点,level=2保留根+一级子节点,以此类推)
830
+ * @returns {Array} 裁剪后的指定层级节点列表
831
+ */
832
+ export function treeCutLevel(treeLike, level) {
833
+ // 统一处理为数组格式
834
+ treeLike = treeSafe(treeLike)
835
+ // 边界值处理:层级≤0 返回空,层级=1 直接返回根节点(浅拷贝避免修改原数据)
836
+ if (level <= 0) return [];
837
+ if (level === 1) {
838
+ // 只保留根节点,浅拷贝根节点(删除children),不影响原数据
839
+ return treeLike.map(node => {
840
+ const newNode = { ...node }; // 浅拷贝当前节点
841
+ delete newNode.children; // 移除子节点,只保留当前层级
842
+ return newNode;
843
+ });
844
+ }
845
+
846
+ // 初始化:当前处理的层级节点列表(初始为根节点)
847
+ let currentLevelNodes = [...treeLike]; // 浅拷贝当前层级节点,不修改原数组
848
+ // 存储最终要返回的目标层级节点
849
+ let targetNodes = [];
850
+
851
+ // 遍历到指定层级(从第2层开始,到level层结束)
852
+ for (let currentLevel = 2; currentLevel <= level; currentLevel++) {
853
+ // 收集下一层的所有节点
854
+ const nextLevelNodes = [];
855
+
856
+ for (const node of currentLevelNodes) {
857
+ // 只处理有children的节点
858
+ if (node.children && node.children.length > 0) {
859
+ if (currentLevel === level) {
860
+ // 到达目标层级:浅拷贝当前子节点,删除其children后加入结果
861
+ const cutNodes = node.children.map(child => {
862
+ const newChild = { ...child };
863
+ delete newChild.children;
864
+ return newChild;
865
+ });
866
+ targetNodes.push(...cutNodes);
867
+ } else {
868
+ // 未到目标层级:收集子节点,继续遍历下一层
869
+ nextLevelNodes.push(...node.children);
870
+ }
871
+ }
872
+ }
873
+
874
+ // 更新当前层级节点为下一层
875
+ currentLevelNodes = nextLevelNodes;
876
+
877
+ // 提前终止:如果下一层没有节点,无需继续循环
878
+ if (currentLevelNodes.length === 0) break;
879
+ }
880
+
881
+ return targetNodes;
882
+ }
883
+
884
+
885
+ /**
886
+ * 安全处理树形结构,确保树为数组格式
887
+ * 将输入的树形结构统一转换为数组格式,便于后续处理
888
+ *
889
+ * @param {Array|Object|null|undefined} treeLike - 待处理的树形结构数据,可以是数组、单个对象或null/undefined
890
+ *
891
+ * @returns {Array} 统一格式的数组,如果输入为数组则直接返回,如果是对象则包装成数组,其他情况返回空数组
892
+ *
893
+ * 时间复杂度: O(1) - 仅执行简单的类型检查和转换操作
894
+ * 空间复杂度: O(1) - 不考虑输出数组的空间,仅使用常量额外空间
895
+ *
896
+ * @example
897
+ * treeSafe([]); // 返回 []
898
+ * treeSafe({id: 1, name: 'node'}); // 返回 [{id: 1, name: 'node'}]
899
+ * treeSafe([{id: 1}, {id: 2}]); // 返回 [{id: 1}, {id: 2}]
900
+ * treeSafe(null); // 返回 []
901
+ * treeSafe(undefined); // 返回 []
902
+ */
903
+ function treeSafe(treeLike) {
904
+ // 统一处理为数组格式
905
+ let tree = [];
906
+ if (Array.isArray(treeLike)) {
907
+ // 如果输入已经是数组,直接返回
908
+ tree = treeLike;
909
+ } else if (typeof treeLike === 'object' && treeLike !== null) {
910
+ // 如果输入是对象且不为null,将其包装在数组中
911
+ tree = [treeLike];
912
+ }
913
+ // 对于其他情况(null, undefined, 基本类型等),返回空数组
914
+ return tree;
915
+ }
916
+