centaline-data-driven-v3 0.1.41 → 0.1.43

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,19 +1,23 @@
1
1
  <template>
2
2
  <div id="ct-tree" class="ct-tree" oncontextmenu="event.preventDefault()" style="overflow:auto;" v-loading="loading"
3
3
  :style="{ height: props.treeHeight - 10 + 'px' }">
4
- <el-tree class="tree-line" :props="defaultProps" :load="loadNode" :expand-on-click-node="false"
5
- @node-click="handleNodeClick" lazy ref="refTree" @node-contextmenu="rightClick" node-key="code"
6
- current-node-key="currentNodeKey">
7
-
4
+ <el-tree class="tree-line" :props="defaultProps" :expand-on-click-node="false" @node-click="handleNodeClick"
5
+ :filter-node-method="filterNode" lazy :load="loadNode" ref="refTree" @node-contextmenu="rightClick"
6
+ node-key="code" current-node-key="currentNodeKey" :default-expanded-keys="defaultExpandedKeys">
8
7
  <template #default="{ node, data }">
9
- <span>
10
- <span style="margin-left: 10px" :ref="(el: refItem) => handleSetInputMap(el, data)">{{
8
+ <span class="node_content">
9
+ <el-icon class="node-icon" :class="{ 'is-leaf': node.isLeaf }">
10
+ <Document v-if="node.isLeaf" :size="14" />
11
+ <Folder v-else-if="!node.expanded" :size="14" />
12
+ <FolderOpened v-else :size="14" />
13
+ </el-icon>
14
+ <span style="" :ref="(el: refItem) => handleSetInputMap(el, data)">{{
11
15
  data.name }}</span>
12
16
  <span @click.stop>
13
17
  <el-popover placement="right" :virtual-ref="nodeRef" virtual-triggering
14
- :visible="((data.code == currentData.code && menuVisible && model.actionRouter?.length > 0))">
18
+ :visible="((data.code == currentData.code && menuVisible && allowedRoutes?.length > 0))">
15
19
  <div class="box-menu">
16
- <template v-for="(item, index) in model.actionRouter">
20
+ <template v-for="(item, index) in allowedRoutes">
17
21
  <div class="opertion" @click="routerClickHandler(item)">
18
22
  <div>
19
23
  <img v-if="item.imgUrl" :src="item.imgUrl" alt="" />
@@ -22,22 +26,21 @@
22
26
  </div>
23
27
  </template>
24
28
  </div>
25
-
26
29
  </el-popover>
27
30
  </span>
28
31
  </span>
29
32
  </template>
30
33
  </el-tree>
31
-
32
-
33
34
  </div>
34
35
  </template>
36
+
35
37
  <script setup lang="ts">
36
- import { ref, nextTick, ComponentPublicInstance, onBeforeUnmount } from 'vue'
38
+ import { ref, nextTick, ComponentPublicInstance, onBeforeUnmount, computed, watch } from 'vue'
37
39
  import common from '../../../utils/common'
38
40
  import { RouterClickHandler } from '../../../utils/mixins';
39
41
  import Enum from '../../../utils/Enum'
40
42
  import Tree from '../../../loader/src/Tree';
43
+
41
44
  type refItem = Element | ComponentPublicInstance | null;
42
45
 
43
46
  const emit = defineEmits(['loaded'])
@@ -54,8 +57,15 @@ const props = defineProps({
54
57
  apiParam: {
55
58
  type: Object,
56
59
  default: () => { },
60
+ },
61
+ // 新增:是否默认展开两级
62
+ expandTwoLevels: {
63
+ type: Boolean,
64
+ default: true
57
65
  }
58
66
  })
67
+
68
+ const lazy = ref(true)
59
69
  const refTree = ref()
60
70
  const rootNode = ref();
61
71
  const rootResolve = ref();
@@ -69,15 +79,31 @@ const defaultProps = ref({
69
79
  })
70
80
  const searchStatus = ref([])
71
81
  const screenPara = ref(props.apiParam);
72
- const menuVisible = ref(false)//右键菜单显示控制
73
- const currentData = ref({ code: "" })// 当前节点所对应的数据
74
- const currentNode = ref(null)//当前节点对应的 Node
75
- const loading = ref(false)//数据理否查询完成
82
+ const menuVisible = ref(false)
83
+ const currentData = ref({ code: "" })
84
+ const currentNode = ref(null)
85
+ const loading = ref(false)
76
86
 
77
87
  const model = ref(null)
78
88
  const nodeRef = ref()
79
89
  const retryCount = ref(0);
80
90
  const treeNodeRefMap = ref({});
91
+ const allowedRoutes = ref([]);
92
+
93
+ // 默认展开的节点key数组
94
+ const defaultExpandedKeys = ref([])
95
+ // 是否已设置默认展开
96
+ const hasSetDefaultExpand = ref(false)
97
+ // 是否是搜索状态
98
+ const isSearching = ref(false)
99
+
100
+ // 全量数据缓存
101
+ const fullTreeData = ref([])
102
+
103
+ // 新增:缓存所有节点的Map,用于快速查找
104
+ const nodeCodeMap = ref(new Map())
105
+
106
+ const isExpandAll = ref(false);
81
107
  /** 动态设置Input Ref */
82
108
  const handleSetInputMap = (el: refItem, item) => {
83
109
  if (el) {
@@ -93,11 +119,133 @@ onBeforeUnmount(() => {
93
119
  }
94
120
  })
95
121
 
96
- //搜索(查询条件调用)
122
+ function getFirstNLevelKeys(nodes, targetLevel = 1) {
123
+ const result = [];
124
+
125
+ if (!nodes || nodes.length === 0 || targetLevel < 0) {
126
+ return result;
127
+ }
128
+
129
+ const traverse = (nodes, currentLevel) => {
130
+ if (currentLevel > targetLevel || !nodes) return;
131
+
132
+ nodes.forEach(node => {
133
+ if (node.code) {
134
+ result.push(node.code);
135
+ }
136
+
137
+ // 如果还有下一层且没超过目标层级,继续遍历
138
+ if (currentLevel < targetLevel && node.children && node.children.length > 0) {
139
+ traverse(node.children, currentLevel + 1);
140
+ }
141
+ });
142
+ };
143
+
144
+ traverse(nodes, 0);
145
+ return result;
146
+ }
147
+
148
+ // 递归获取所有节点的key(用于搜索时全展开)
149
+ function getAllNodeKeys(nodes, result = []) {
150
+ if (!nodes || nodes.length === 0) {
151
+ return result;
152
+ }
153
+
154
+ nodes.forEach(node => {
155
+ if (node.code) {
156
+ result.push(node.code);
157
+ }
158
+
159
+ if (node.children && node.children.length > 0) {
160
+ getAllNodeKeys(node.children, result);
161
+ }
162
+ });
163
+
164
+ return result;
165
+ }
166
+
167
+ // 设置默认展开节点
168
+ function setDefaultExpandedNodes() {
169
+ if (!props.expandTwoLevels || !fullTreeData.value.length) {
170
+ return;
171
+ }
172
+
173
+ if (isSearching.value) {
174
+ isExpandAll.value = true;
175
+ const allKeys = getAllNodeKeys(fullTreeData.value);
176
+ defaultExpandedKeys.value = allKeys;
177
+ } else {
178
+ isExpandAll.value = false;
179
+ // 检查是否有全展开搜索条件
180
+ const expandValue = findSearchFieldValue(screenPara.value, 'expanded', {
181
+ caseInsensitive: true, // 添加不区分大小写的选项
182
+ keyFields: ['fieldName1', 'groupName', 'fieldLabel'] // 扩展可能的key字段
183
+ });
184
+
185
+ // 如果找到expanded=all的搜索条件,设置全展开
186
+ if (expandValue && expandValue.toString().toLowerCase() === 'all') {
187
+ isExpandAll.value = true;
188
+ const allKeys = getAllNodeKeys(fullTreeData.value);
189
+ defaultExpandedKeys.value = allKeys;
190
+ } else {
191
+ // 处理非'all'的情况
192
+ let expandLevel = 1; // 默认展开级别为1
193
+
194
+ if (expandValue) {
195
+ const numericValue = Number(expandValue);
196
+ if (!isNaN(numericValue) && numericValue > 0) {
197
+ expandLevel = Math.max(0, numericValue - 1); // 减1但最小为1
198
+
199
+ }
200
+ }
201
+
202
+ defaultExpandedKeys.value = getFirstNLevelKeys(fullTreeData.value, expandLevel);
203
+ }
204
+ }
205
+
206
+ hasSetDefaultExpand.value = true;
207
+ }
208
+
209
+ // 新增:构建节点Map,便于快速查找
210
+ function buildNodeCodeMap(nodes) {
211
+ const map = new Map();
212
+
213
+ function traverse(nodeList) {
214
+ nodeList.forEach(node => {
215
+ if (node.code) {
216
+ map.set(node.code, node);
217
+ }
218
+
219
+ if (node.children && node.children.length > 0) {
220
+ traverse(node.children);
221
+ }
222
+ });
223
+ }
224
+
225
+ traverse(nodes);
226
+ return map;
227
+ }
228
+
229
+ // 新增:复制节点(深拷贝)
230
+ function cloneNode(node) {
231
+ const cloned = { ...node };
232
+ cloned.children = [];
233
+ return cloned;
234
+ }
235
+
236
+ // 搜索(查询条件调用)
97
237
  function search(m) {
238
+ const hasValidSearchConditions = checkHasValidSearchConditions(m);
239
+
240
+ isSearching.value = hasValidSearchConditions;
98
241
  screenPara.value = m;
242
+
243
+ if (!lazy.value) {
244
+ filterFullTreeData(screenPara.value);
245
+ return;
246
+ }
247
+
99
248
  if (searchStatus.value.length == 0) {
100
- // 重置计数器
101
249
  retryCount.value = 0
102
250
  if (qrtimer.value) clearTimeout(qrtimer.value);
103
251
  searchStatus.value.push('a');
@@ -116,47 +264,465 @@ function search(m) {
116
264
  }, 200);
117
265
  }
118
266
  }
119
- //加载节点(点击节点加号时调用)
267
+
268
+ // 检查是否有有效的搜索条件
269
+ function checkHasValidSearchConditions(filter) {
270
+ if (!filter) return false;
271
+
272
+ let searchConditions = [];
273
+
274
+ if (filter?.searchData?.fields) {
275
+ searchConditions = filter.searchData.fields;
276
+ } else if (filter?.fields) {
277
+ searchConditions = filter.fields;
278
+ }
279
+
280
+ const validConditions = filterValidConditions(searchConditions);
281
+ return validConditions && validConditions.length > 0;
282
+ }
283
+
284
+
285
+
286
+ // 过滤掉 operation 为 1 的条件
287
+ function filterValidConditions(conditions) {
288
+ if (!conditions || !Array.isArray(conditions)) return [];
289
+
290
+ return conditions.filter(condition => {
291
+ return condition.operation !== 1;
292
+ });
293
+ }
294
+
295
+ // 判断节点是否匹配所有搜索条件
296
+ function nodeMatchesAllConditions(node, conditions) {
297
+ const validConditions = filterValidConditions(conditions);
298
+
299
+ if (!validConditions || validConditions.length === 0) return true;
300
+
301
+ return validConditions.every(condition => {
302
+ const { fieldName1, operation, searchValue1, searchValue2, searchDataType } = condition;
303
+ let nodeValue = node[fieldName1];
304
+
305
+ if (nodeValue === undefined) {
306
+ const mappedField = Object.keys(node).find(key =>
307
+ key.toLowerCase() === fieldName1.toLowerCase()
308
+ );
309
+ if (mappedField) {
310
+ nodeValue = node[mappedField];
311
+ } else {
312
+ return false;
313
+ }
314
+ }
315
+
316
+ const strNodeValue = String(nodeValue || '').toLowerCase();
317
+ const strSearchValue = String(searchValue1 || '').toLowerCase();
318
+
319
+ switch (operation) {
320
+ case 2:
321
+ return strNodeValue === strSearchValue;
322
+ case 3:
323
+ return strNodeValue !== strSearchValue;
324
+ case 4:
325
+ return Number(nodeValue) > Number(searchValue1);
326
+ case 5:
327
+ return Number(nodeValue) >= Number(searchValue1);
328
+ case 6:
329
+ return Number(nodeValue) < Number(searchValue1);
330
+ case 7:
331
+ return Number(nodeValue) <= Number(searchValue1);
332
+ case 8:
333
+ return strNodeValue.includes(strSearchValue);
334
+ case 9:
335
+ return strNodeValue.startsWith(strSearchValue);
336
+ case 10:
337
+ return strNodeValue.endsWith(strSearchValue);
338
+ case 11:
339
+ return !strNodeValue.includes(strSearchValue);
340
+ case 12:
341
+ try {
342
+ const regex = new RegExp(searchValue1, 'i');
343
+ return regex.test(nodeValue);
344
+ } catch (e) {
345
+ console.warn('正则表达式错误:', e);
346
+ return false;
347
+ }
348
+ case 13:
349
+ if (!searchValue1) return false;
350
+ try {
351
+ const values = searchValue1.split(',').map(v => v.trim().toLowerCase());
352
+ return values.includes(strNodeValue);
353
+ } catch (e) {
354
+ console.warn('解析IN条件错误:', e);
355
+ return false;
356
+ }
357
+ case 14:
358
+ if (!searchValue1) return true;
359
+ try {
360
+ const values = searchValue1.split(',').map(v => v.trim().toLowerCase());
361
+ return !values.includes(strNodeValue);
362
+ } catch (e) {
363
+ console.warn('解析NotIN条件错误:', e);
364
+ return false;
365
+ }
366
+ default:
367
+ return false;
368
+ }
369
+ });
370
+ }
371
+
372
+ // 修改:构建完整的过滤树,保持原始层级结构
373
+ function buildFilteredTreeWithOriginalHierarchy(matchedNodes) {
374
+ if (!matchedNodes || matchedNodes.length === 0) {
375
+ return [];
376
+ }
377
+
378
+ const resultMap = new Map();
379
+ const rootNodes = [];
380
+
381
+ // 第一步:创建所有匹配节点的副本
382
+ matchedNodes.forEach(node => {
383
+ if (!resultMap.has(node.code)) {
384
+ const clonedNode = cloneNode(node);
385
+ resultMap.set(node.code, clonedNode);
386
+ }
387
+ });
388
+
389
+ // 第二步:为每个匹配节点建立完整的父子关系链
390
+ matchedNodes.forEach(matchedNode => {
391
+ // 从全量数据中查找该节点的完整路径
392
+ const fullNode = nodeCodeMap.value.get(matchedNode.code);
393
+ if (!fullNode) return;
394
+
395
+ // 递归向上查找父节点链
396
+ function findParentChain(currentNode, chain = []) {
397
+ chain.unshift(currentNode.code);
398
+
399
+ // 在全量数据中查找父节点
400
+ const parentCode = findParentCodeInFullTree(currentNode.code);
401
+ if (parentCode) {
402
+ const parentNode = nodeCodeMap.value.get(parentCode);
403
+ if (parentNode) {
404
+ findParentChain(parentNode, chain);
405
+ }
406
+ }
407
+
408
+ return chain;
409
+ }
410
+
411
+ const parentChain = findParentChain(fullNode);
412
+
413
+ // 构建完整的树结构
414
+ for (let i = 0; i < parentChain.length; i++) {
415
+ const currentCode = parentChain[i];
416
+
417
+ // 如果节点已经存在,跳过
418
+ if (resultMap.has(currentCode)) {
419
+ continue;
420
+ }
421
+
422
+ // 从全量数据中获取节点
423
+ const originalNode = nodeCodeMap.value.get(currentCode);
424
+ if (!originalNode) continue;
425
+
426
+ // 创建节点副本
427
+ const clonedNode = cloneNode(originalNode);
428
+ resultMap.set(currentCode, clonedNode);
429
+
430
+ // 如果是根节点,添加到根节点数组
431
+ if (i === 0) {
432
+ // 检查是否已经存在于根节点中
433
+ const existingRoot = rootNodes.find(n => n.code === currentCode);
434
+ if (!existingRoot) {
435
+ rootNodes.push(clonedNode);
436
+ }
437
+ }
438
+
439
+ // 连接到父节点
440
+ if (i > 0) {
441
+ const parentCode = parentChain[i - 1];
442
+ const parentNode = resultMap.get(parentCode);
443
+
444
+ if (parentNode) {
445
+ // 检查是否已经包含该子节点
446
+ const existingChild = parentNode.children.find(child => child.code === currentCode);
447
+ if (!existingChild) {
448
+ parentNode.children.push(clonedNode);
449
+ }
450
+ }
451
+ }
452
+ }
453
+
454
+ // 最后,确保当前匹配节点连接到其父节点
455
+ const parentCode = findParentCodeInFullTree(matchedNode.code);
456
+ if (parentCode && resultMap.has(parentCode)) {
457
+ const parentNode = resultMap.get(parentCode);
458
+ const currentNode = resultMap.get(matchedNode.code);
459
+
460
+ if (parentNode && currentNode) {
461
+ // 检查是否已经包含该子节点
462
+ const existingChild = parentNode.children.find(child => child.code === matchedNode.code);
463
+ if (!existingChild) {
464
+ parentNode.children.push(currentNode);
465
+ }
466
+ }
467
+ }
468
+ });
469
+
470
+ // 第三步:清理根节点,确保它们没有重复的父节点
471
+ const finalRootNodes = [];
472
+ const usedNodes = new Set();
473
+
474
+ function isNodeUsedInTree(node) {
475
+ for (const root of rootNodes) {
476
+ if (isNodeInChildren(root, node.code)) {
477
+ return true;
478
+ }
479
+ }
480
+ return false;
481
+ }
482
+
483
+ rootNodes.forEach(rootNode => {
484
+ // 如果这个节点已经是其他节点的子节点,则不作为根节点
485
+ if (!isNodeUsedInTree(rootNode)) {
486
+ finalRootNodes.push(rootNode);
487
+ }
488
+ });
489
+
490
+ return finalRootNodes;
491
+ }
492
+
493
+ // 新增:在全量树中查找父节点code
494
+ function findParentCodeInFullTree(nodeCode) {
495
+ function findParent(nodes, targetCode, parentCode = null) {
496
+ for (const node of nodes) {
497
+ if (node.code === targetCode) {
498
+ return parentCode;
499
+ }
500
+
501
+ if (node.children && node.children.length > 0) {
502
+ const found = findParent(node.children, targetCode, node.code);
503
+ if (found !== null) {
504
+ return found;
505
+ }
506
+ }
507
+ }
508
+ return null;
509
+ }
510
+
511
+ return findParent(fullTreeData.value, nodeCode);
512
+ }
513
+
514
+ // 新增:检查节点是否在子树中
515
+ function isNodeInChildren(parentNode, targetCode) {
516
+ if (parentNode.code === targetCode) {
517
+ return false; // 不检查自身
518
+ }
519
+
520
+ if (!parentNode.children || parentNode.children.length === 0) {
521
+ return false;
522
+ }
523
+
524
+ for (const child of parentNode.children) {
525
+ if (child.code === targetCode) {
526
+ return true;
527
+ }
528
+
529
+ if (isNodeInChildren(child, targetCode)) {
530
+ return true;
531
+ }
532
+ }
533
+
534
+ return false;
535
+ }
536
+
537
+ // 修改:过滤全量树数据,保持原始层级
538
+ function filterFullTreeData(filter) {
539
+ if (!fullTreeData.value.length) {
540
+ return;
541
+ }
542
+
543
+ loading.value = true;
544
+ try {
545
+ let searchConditions = [];
546
+
547
+ if (filter?.searchData?.fields) {
548
+ searchConditions = filter.searchData.fields;
549
+ } else if (filter?.fields) {
550
+ searchConditions = filter.fields;
551
+ }
552
+
553
+ const validConditions = filterValidConditions(searchConditions);
554
+
555
+ if (!validConditions || validConditions.length === 0) {
556
+ refTree.value.store.setData(fullTreeData.value);
557
+ loading.value = false;
558
+ isSearching.value = false;
559
+ hasSetDefaultExpand.value = false;
560
+ defaultExpandedKeys.value = [];
561
+
562
+ // 先收起所有节点
563
+ if (refTree.value && refTree.value.store) {
564
+ Object.values(refTree.value.store.nodesMap).forEach((node: any) => {
565
+ if (node) {
566
+ node.expanded = false;
567
+ }
568
+ });
569
+ }
570
+
571
+ setDefaultExpandedNodes();
572
+ return;
573
+ }
574
+
575
+ // 递归查找所有匹配的节点
576
+ const findMatchedNodes = (nodes, result = []) => {
577
+ nodes.forEach(node => {
578
+ const nodeMatches = nodeMatchesAllConditions(node, validConditions);
579
+
580
+ if (nodeMatches) {
581
+ result.push(node);
582
+ }
583
+
584
+ if (node.children && node.children.length > 0) {
585
+ findMatchedNodes(node.children, result);
586
+ }
587
+ });
588
+ return result;
589
+ };
590
+
591
+ const matchedNodes = findMatchedNodes(fullTreeData.value);
592
+
593
+ // 构建保持原始层级的过滤树
594
+ let filteredData = [];
595
+ if (matchedNodes.length > 0) {
596
+ filteredData = buildFilteredTreeWithOriginalHierarchy(matchedNodes);
597
+ }
598
+
599
+ // 设置过滤后的数据
600
+ refTree.value.store.setData(filteredData);
601
+
602
+ // 搜索状态时展开所有节点
603
+ if (filteredData.length > 0 && isSearching.value) {
604
+ const allKeys = getAllNodeKeys(filteredData);
605
+ defaultExpandedKeys.value = allKeys;
606
+
607
+ nextTick(() => {
608
+ if (refTree.value && refTree.value.store) {
609
+ Object.values(refTree.value.store.nodesMap).forEach((node: any) => {
610
+ if (node) {
611
+ node.expanded = true;
612
+ }
613
+ });
614
+ }
615
+ });
616
+
617
+ // 默认选中第一个可选节点
618
+ const findFirstSelectableNode = (nodes) => {
619
+ for (const node of nodes) {
620
+ if (!node.children || node.children.length === 0) {
621
+ return node;
622
+ }
623
+ const childResult = findFirstSelectableNode(node.children);
624
+ if (childResult) return childResult;
625
+ }
626
+ return nodes[0];
627
+ };
628
+
629
+ const firstNode = findFirstSelectableNode(filteredData);
630
+ if (firstNode && firstNode.code) {
631
+ nextTick(() => {
632
+ refTree.value.setCurrentKey(firstNode.code);
633
+ handleNodeClick(firstNode, true);
634
+ });
635
+ }
636
+ }
637
+
638
+ } catch (error) {
639
+ console.error('过滤树数据失败:', error);
640
+ } finally {
641
+ loading.value = false;
642
+ }
643
+ }
644
+
645
+ // 修改:加载节点,保存节点Map
120
646
  function loadNode(node, resolve) {
647
+
648
+ if (!lazy.value) {
649
+ const nodeData = node.data;
650
+ resolve(nodeData.children)
651
+ return;
652
+ }
653
+
121
654
  if (node.level === 0) {
122
655
  loading.value = true;
123
656
  rootNode.value = node
124
657
  rootResolve.value = resolve;
658
+
125
659
  return SearchTree(screenPara.value || undefined).then(data => {
126
660
  loading.value = false;
127
- resolve(data.rows);
128
- load(data, true)
661
+
662
+
663
+ const { tree, hasAnyLeaf } = buildDeptTreeByField(data.rows, {
664
+ pathKey: 'path',
665
+ separator: '.'
666
+ });
667
+
668
+ // 缓存全量数据并构建节点Map
669
+ if (hasAnyLeaf) {
670
+ fullTreeData.value = tree;
671
+ nodeCodeMap.value = buildNodeCodeMap(tree);
672
+ resolve(tree);
673
+ if (props.expandTwoLevels) {
674
+ nextTick(() => {
675
+ hasSetDefaultExpand.value = false;
676
+ setDefaultExpandedNodes();
677
+ });
678
+ }
679
+ }
680
+ else {
681
+ resolve(data.rows);
682
+ }
683
+ lazy.value = !hasAnyLeaf;
684
+
685
+ load(data, true);
686
+ }).catch(error => {
687
+ loading.value = false;
688
+ console.error('加载树数据失败:', error);
129
689
  });
130
690
  }
131
- if (node.level >= 1) {
691
+
692
+ if (node.level >= 1 && lazy.value) {
132
693
  let fields = model.value.searchData("code", node.data.code, 9, 3);
133
694
  let filter = {
134
695
  "searchData": {
135
- // 先保留searchData的其他属性
136
696
  ...(screenPara.value?.searchData || {}),
137
- // 再合并fields数组(将两个数组合并,而不是覆盖)
138
697
  fields: [
139
698
  ...fields.fields,
140
699
  ...(screenPara.value?.searchData?.fields || [])
141
700
  ]
142
701
  }
143
702
  };
703
+
144
704
  return SearchTree(filter).then(data => {
145
705
  loading.value = false;
146
706
  resolve(data.rows);
147
- load(data, false)
707
+ load(data, false);
708
+ }).catch(error => {
709
+ loading.value = false;
710
+ console.error('加载子节点失败:', error);
148
711
  });
149
712
  }
150
713
  }
151
- //加载数据
152
- async function load(data, firstLoad) {
714
+
715
+ // 加载数据
716
+ async function load(data, flagRootNode) {
153
717
  model.value = data;
154
718
  if (searchStatus.value.length > 0) {
155
719
  searchStatus.value = searchStatus.value.slice(1)
156
720
  }
721
+
157
722
  loading.value = false;
158
723
  menuVisible.value = false;
159
- if (firstLoad) {
724
+
725
+ if (flagRootNode) {
160
726
  if (model.value.rows && model.value.rows[0]) {
161
727
  handleNodeClick(model.value.rows[0], true);
162
728
  }
@@ -167,18 +733,79 @@ async function load(data, firstLoad) {
167
733
  });
168
734
  }
169
735
  }
170
- //查询数据
171
- function SearchTree(m) {
172
736
 
737
+ // 查询数据
738
+ function SearchTree(m) {
173
739
  return new Promise((resolve, reject) => {
174
740
  if (typeof props.api !== 'undefined') {
175
-
176
- resolve(Tree.loadSearchTreeApi(props.api, m));
741
+ Tree.loadSearchTreeApi(props.api, m)
742
+ .then(resolve)
743
+ .catch(reject);
744
+ } else {
745
+ reject(new Error('API未定义'));
177
746
  }
178
747
  });
179
748
  }
180
- //节点击点事件
749
+
750
+ // 重置树到全量数据
751
+ function resetTree() {
752
+ isSearching.value = false;
753
+ if (fullTreeData.value.length > 0) {
754
+ refTree.value.store.setData(fullTreeData.value);
755
+ hasSetDefaultExpand.value = false;
756
+ defaultExpandedKeys.value = [];
757
+
758
+ if (fullTreeData.value[0] && fullTreeData.value[0].code) {
759
+ nextTick(() => {
760
+ if (refTree.value && refTree.value.store) {
761
+ Object.values(refTree.value.store.nodesMap).forEach((node: any) => {
762
+ if (node) {
763
+ node.expanded = false;
764
+ }
765
+ });
766
+ }
767
+
768
+ refTree.value.setCurrentKey(fullTreeData.value[0].code);
769
+ handleNodeClick(fullTreeData.value[0], true);
770
+ setDefaultExpandedNodes();
771
+ });
772
+ }
773
+ }
774
+ }
775
+
776
+ // 清除搜索条件
777
+ function clearSearch() {
778
+ screenPara.value = {};
779
+ isSearching.value = false;
780
+
781
+ defaultExpandedKeys.value = [];
782
+ hasSetDefaultExpand.value = false;
783
+
784
+ if (fullTreeData.value.length > 0) {
785
+ refTree.value.store.setData(fullTreeData.value);
786
+
787
+ nextTick(() => {
788
+ if (refTree.value && refTree.value.store) {
789
+ Object.values(refTree.value.store.nodesMap).forEach((node: any) => {
790
+ if (node) {
791
+ node.expanded = false;
792
+ }
793
+ });
794
+ }
795
+
796
+ setDefaultExpandedNodes();
797
+
798
+ if (fullTreeData.value[0] && fullTreeData.value[0].code) {
799
+ refTree.value.setCurrentKey(fullTreeData.value[0].code);
800
+ handleNodeClick(fullTreeData.value[0], true);
801
+ }
802
+ });
803
+ }
804
+ }
805
+
806
+ //节点点击事件
181
807
  function handleNodeClick(data, formType) {
808
+
182
809
  closeMenu();
183
810
  if (data && formType) {
184
811
  refTree.value.setCurrentKey(data.code)
@@ -192,35 +819,44 @@ function handleNodeClick(data, formType) {
192
819
  SearchData[v] = data[v];
193
820
  }
194
821
  })
822
+
195
823
  model.value.requestAction(rowRouter.action, SearchData, function (rowdata) {
196
824
  var newdata = common.deepClone(data);
825
+ delete newdata.children;
826
+
197
827
  if (rowdata) {
198
- newdata.rowRouter = rowdata
828
+ newdata.rowRouter = rowdata;
199
829
  }
200
830
  newdata.title = model.value.source?.content?.title || '';
201
831
  emit('loaded', newdata);
202
832
  });
203
833
  }
204
834
  }
835
+
205
836
  //右键菜单
206
- function rightClick(event, object, Node) { // event、object该节点所对应的对象、节点对应的 Node、节点组件本身
837
+ function rightClick(event, object, Node) {
207
838
  nodeRef.value = treeNodeRefMap.value[`node_Ref_${object.code}`]
208
839
 
209
840
  currentData.value = object
210
841
  currentNode.value = Node
211
842
  let actionIndex = 0;
212
- if (model.value.actionRouter) {
213
- model.value.actionRouter.forEach(v => {
214
- if (currentData.value[v.rightField] == '1') {
215
- menuVisible.value = true //显示增删改的div
216
- actionIndex += 1;
217
- }
218
- })
219
- }
843
+
844
+ allowedRoutes.value = (model.value.actionRouter || []).filter(v => {
845
+ const val = currentData.value[v.rightField]
846
+ const hit = val == '1' || val === undefined || val === null;
847
+ return hit;
848
+ })
849
+
850
+ allowedRoutes.value?.forEach(() => {
851
+ menuVisible.value = true
852
+ actionIndex += 1
853
+ })
854
+
220
855
  if (menuVisible.value) {
221
- document.addEventListener('click', closeMenu) // 监听事件鼠标点击事件,若点击则隐藏菜单
856
+ document.addEventListener('click', closeMenu)
222
857
  }
223
858
  }
859
+
224
860
  //关闭菜单
225
861
  function closeMenu() {
226
862
  menuVisible.value = false;
@@ -228,6 +864,7 @@ function closeMenu() {
228
864
  currentData.value = { ...currentData.value, code: "" }
229
865
  document.removeEventListener('click', closeMenu)
230
866
  }
867
+
231
868
  // 菜单路由
232
869
  function routerClickHandler(field) {
233
870
  var submitData = {};
@@ -238,63 +875,193 @@ function routerClickHandler(field) {
238
875
 
239
876
  RouterClickHandler(field, submitData, null, model.value, 'tree', function (newData) {
240
877
  switch (field.actionType) {
241
- case Enum.ActionType.Delete://删除
242
- case Enum.ActionType.CloseTabThenDelete://删除
243
- removeNode(newData);//删除树节点
878
+ case Enum.ActionType.Delete:
879
+ case Enum.ActionType.CloseTabThenDelete:
880
+ removeNode(newData);
244
881
  break;
245
- case Enum.ActionType.New://新增
246
- case Enum.ActionType.CloseTabThenNew://新增
247
- addNode(newData)//新增树节点
882
+ case Enum.ActionType.New:
883
+ case Enum.ActionType.CloseTabThenNew:
884
+ addNode(newData)
248
885
  break;
249
- case Enum.ActionType.Update://修改
250
- case Enum.ActionType.CloseTabThenUpdate://修改
251
- updateNode(newData)//修改树节点
886
+ case Enum.ActionType.Update:
887
+ case Enum.ActionType.CloseTabThenUpdate:
888
+ updateNode(newData)
252
889
  break;
253
890
  default:
254
891
  break;
255
892
  }
256
-
257
893
  })
258
894
  }
895
+
259
896
  //删除树节点
260
897
  function removeNode(newData) {
261
- getNextClickNode()
898
+ getNextClickNode(currentNode.value)
262
899
  refTree.value.remove(currentNode.value);
900
+
263
901
  }
902
+
264
903
  //新增树节点
265
904
  function addNode(newData) {
266
905
  refTree.value.append(newData, currentNode.value)
267
906
  handleNodeClick(newData, true)
268
907
  }
908
+
269
909
  //修改树节点
270
910
  function updateNode(newData) {
271
-
272
911
  for (let key in newData) {
273
-
274
912
  if (typeof newData[key] === 'object') {
275
913
  currentData.value[key] = { ...currentData.value[key], ...newData[key] }
276
914
  } else {
277
915
  currentData.value[key] = newData[key];
278
916
  }
279
-
280
917
  }
281
918
  handleNodeClick(newData, true)
282
919
  }
920
+
283
921
  function getNextClickNode() {
284
- const node = refTree.value.getNode(currentData.value.code);
922
+ const node = refTree.value.getNode((currentData.value.code || currentNode.value.key));
285
923
  let data = {};
286
- if (node.nextSibling) {
924
+ if (node?.nextSibling) {
287
925
  data = node.nextSibling.data;
288
926
  } else if (node.previousSibling) {
289
927
  data = node.previousSibling.data;
290
- } else if (node.parent) {
928
+ } else if (node?.parent) {
291
929
  data = node.parent.data;
292
930
  }
293
931
  handleNodeClick(data, true)
294
932
  }
295
- // //把属性和方法暴露
933
+
934
+ /** 过滤节点(全量数据模式下使用) */
935
+ function filterNode(value, data) {
936
+ if (!value) return true
937
+ return data.name.toLowerCase().includes(value.toLowerCase())
938
+ }
939
+
940
+ function buildDeptTreeByField(flatList, opt = {}) {
941
+ const {
942
+ pathKey = 'path',
943
+ separator = '.',
944
+ sortKey = 'sort'
945
+ } = opt;
946
+
947
+ // 检查第一行是否有 sort 字段
948
+ const firstItem = flatList.length > 0 ? flatList[0] : null;
949
+ const actualSortKey = firstItem && firstItem.hasOwnProperty(sortKey) ? sortKey : 'path';
950
+
951
+ const createNode = (tplItem, path) => {
952
+ const node = { children: [] };
953
+
954
+ for (const key in tplItem) {
955
+ if (key !== 'children') {
956
+ node[key] = tplItem[key];
957
+ }
958
+ }
959
+
960
+ node[pathKey] = path;
961
+ return node;
962
+ };
963
+
964
+ const root = [];
965
+ const nodeMap = new Map();
966
+
967
+ for (const item of flatList) {
968
+ if (!item[pathKey]) {
969
+ break;
970
+ }
971
+ const path = item[pathKey];
972
+ const levels = path.split(separator);
973
+
974
+ let curPath = '';
975
+ let parentPath = '';
976
+
977
+ levels.forEach((lvl, idx) => {
978
+ curPath = idx === 0 ? lvl : `${parentPath}${separator}${lvl}`;
979
+ let curNode = nodeMap.get(curPath);
980
+
981
+ if (!curNode) {
982
+ curNode = createNode(item, curPath);
983
+ nodeMap.set(curPath, curNode);
984
+
985
+ if (idx === 0) {
986
+ root.push(curNode);
987
+ } else {
988
+ const pNode = nodeMap.get(parentPath);
989
+ if (pNode) {
990
+ pNode.children.push(curNode);
991
+ }
992
+ }
993
+ }
994
+ parentPath = curPath;
995
+ });
996
+
997
+ const lastNode = nodeMap.get(path);
998
+ for (const key in item) {
999
+ if (key !== 'children') {
1000
+ lastNode[key] = item[key];
1001
+ }
1002
+ }
1003
+ }
1004
+
1005
+ // 按实际排序键排序子节点
1006
+ for (const node of nodeMap.values()) {
1007
+ if (node.children && node.children.length > 0) {
1008
+ node.children.sort((a, b) => {
1009
+ const aVal = a[actualSortKey] || '';
1010
+ const bVal = b[actualSortKey] || '';
1011
+ return String(aVal).localeCompare(String(bVal));
1012
+ });
1013
+ }
1014
+ node.isLeaf = node.children.length === 0;
1015
+ }
1016
+
1017
+ // 按实际排序键排序根节点
1018
+ root.sort((a, b) => {
1019
+ const aVal = a[actualSortKey] || '';
1020
+ const bVal = b[actualSortKey] || '';
1021
+ return String(aVal).localeCompare(String(bVal));
1022
+ });
1023
+
1024
+ const hasAnyLeaf = [...nodeMap.values()].some(n => n.isLeaf == false);
1025
+ return { tree: root, hasAnyLeaf };
1026
+ }
1027
+
1028
+ function findSearchFieldValue(searchFields, targetKey, options = {}) {
1029
+ const {
1030
+ keyFields = ['fieldName1', 'groupName'], // 要查找的key字段列表
1031
+ valueField = 'searchValue1', // 要返回值的字段
1032
+ defaultValue = 2
1033
+ } = options;
1034
+
1035
+ // 检查数据结构
1036
+ if (!searchFields?.searchData?.fields?.length) {
1037
+ return defaultValue;
1038
+ }
1039
+
1040
+ // 遍历fields数组
1041
+ for (const field of searchFields.searchData.fields) {
1042
+ // 检查所有可能的key字段
1043
+ for (const keyField of keyFields) {
1044
+ if (field[keyField] === targetKey) {
1045
+ // 找到了匹配的key,返回对应的值
1046
+ const value = field[valueField];
1047
+ // 如果值为undefined、null或空字符串,返回默认值
1048
+ return value !== undefined && value !== null && value !== ''
1049
+ ? value
1050
+ : defaultValue;
1051
+ }
1052
+ }
1053
+ }
1054
+
1055
+ return defaultValue;
1056
+ }
1057
+
1058
+
1059
+
1060
+ // 暴露方法
296
1061
  defineExpose({
297
- search
1062
+ search,
1063
+ resetTree,
1064
+ clearSearch
298
1065
  })
299
1066
  </script>
300
1067
 
@@ -310,7 +1077,6 @@ defineExpose({
310
1077
 
311
1078
  .ct-tree::-webkit-scrollbar-thumb {
312
1079
  background-color: #bebebe;
313
- /* 设置滚动条滑块颜色 */
314
1080
  height: 5px;
315
1081
  border-radius: 4px;
316
1082
  display: block;
@@ -319,7 +1085,6 @@ defineExpose({
319
1085
  .ct-tree::-webkit-scrollbar {
320
1086
  width: 6px;
321
1087
  height: 6px;
322
- /* 设置滚动条高度 */
323
1088
  background-color: #f1f1f1;
324
1089
  display: block;
325
1090
  }
@@ -347,121 +1112,14 @@ defineExpose({
347
1112
  padding-right: 2px !important;
348
1113
  }
349
1114
 
350
- /* 竖线 */
351
- .el-tree-node::before {
352
- content: "";
353
- height: 100%;
354
- width: 1px;
355
- position: absolute;
356
- left: 27px;
357
- top: -1px;
358
- border-width: 1px;
359
- border-left: 1px dashed #52627C;
360
- }
361
-
362
- .tree-line .el-tree-node:before:last-child {
363
- top: -35px;
364
- }
365
-
366
- .el-tree-node__children .el-tree-node::before {
367
- content: "";
368
- height: 100%;
369
- width: 1px;
370
- position: absolute;
371
- left: 27px;
372
- top: -2px;
373
- border-width: 1px;
374
- border-left: 1px dashed #52627C;
375
- }
376
-
377
- /* 当前最后一个接点的竖线高度 */
378
- .el-tree-node:last-child::before {
379
- height: 15px;
380
- /*可以自己调整 */
381
- }
382
-
383
- /* 横线 */
384
- .el-tree-node::after {
385
- content: "";
386
- width: 12px;
387
- height: 20px;
388
- position: absolute;
389
- left: 27px;
390
- top: 12px;
391
- right: 2px;
392
- border-width: 1px;
393
- border-top: 1px dashed #52627C;
1115
+ .node_content {
1116
+ display: flex;
1117
+ align-items: center;
394
1118
  }
395
1119
 
396
- .el-tree:first-child .el-tree-node:before {
397
- /* height: 46px;可以自己调整 */
398
- }
399
-
400
- .el-tree-node__expand-icon {
401
- font-size: 16px;
402
- }
403
-
404
- /* 去掉顶层虚线 */
405
- .tree>.el-tree-node:first-child::after {
406
- border-top: none;
407
- }
408
-
409
- .el-tree>.el-tree-node:first-child:before {
410
- /* border-left: none; */
411
- }
412
-
413
- .tree>.is-leaf {
414
- color: transparent;
415
- }
416
-
417
- .el-tree .el-tree-node__expand-icon.expanded {
418
- -webkit-transform: rotate(0deg);
419
- transform: rotate(0deg);
420
- }
421
-
422
- .el-tree .el-tree-node__expand-icon.expanded {
423
- -webkit-transform: rotate(0deg);
424
- transform: rotate(0deg);
425
- }
426
-
427
-
428
- .el-tree .el-tree-node__expand-icon {
429
- background: url("@/assets/images/node-collapse.png") no-repeat;
430
- content: '';
431
- display: block;
432
- width: 12px;
433
- height: 12px;
434
- font-size: 12px;
435
- background-size: 10px;
436
-
437
- color: transparent;
438
- background-position: left 6px top 6px;
439
- }
440
-
441
- /*
442
- //有子节点 且已展开*/
443
- .el-tree .el-tree-node__expand-icon.expanded {
444
- background: url("@/assets/images/node-expand.png") no-repeat;
445
- content: '';
446
- display: block;
447
- width: 12px;
448
- height: 12px;
449
- font-size: 12px;
450
- background-size: 10px;
451
-
452
- color: transparent;
453
- background-position: left 6px top 6px;
454
- }
455
-
456
- /* //没有子节点*/
457
- .el-tree .el-tree-node__expand-icon.is-leaf::before {
458
- background: transparent no-repeat 0 3px;
459
- content: '';
460
- display: block;
461
- width: 12px;
462
- height: 12px;
463
- font-size: 12px;
464
- background-size: 10px;
1120
+ .node-icon {
1121
+ margin-right: 5px;
1122
+ color: var(--el-color-warning);
465
1123
  }
466
1124
 
467
1125
  .box-menu {
@@ -481,5 +1139,6 @@ defineExpose({
481
1139
 
482
1140
  .box-menu .opertion:hover {
483
1141
  color: var(--btnHoverRed);
1142
+ background-color: var(--el-fill-color-light);
484
1143
  }
485
- </style>
1144
+ </style>