centaline-data-driven-v3 0.1.45 → 0.1.47

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,11 +1,12 @@
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" :expand-on-click-node="false" @node-click="handleNodeClick"
4
+ <el-tree class="tree-line" :props="defaultProps" :expand-on-click-node="false"
5
+ @node-click="handleNodeClick"
5
6
  :filter-node-method="filterNode" lazy :load="loadNode" ref="refTree" @node-contextmenu="handleContextMenu"
6
7
  node-key="code" current-node-key="currentNodeKey" :default-expanded-keys="defaultExpandedKeys">
7
8
  <template #default="{ node, data }">
8
- <span class="node_content" @mouseenter="handleMouseEnter(data, node)">
9
+ <span class="node_content" @mouseenter="handleMouseEnter(data, node)" @mousemove="handleMouseMove">
9
10
  <el-icon class="node-icon" :class="{ 'is-leaf': node.isLeaf }">
10
11
  <Document v-if="node.isLeaf" :size="14" />
11
12
  <Folder v-else-if="!node.expanded" :size="14" />
@@ -15,8 +16,9 @@
15
16
  {{ data.name }}
16
17
  </span>
17
18
  <span @click.stop>
18
- <el-popover placement="right" :virtual-ref="nodeRef" virtual-triggering :visible="showPopover(data)"
19
- :show-after="300" :hide-after="300" :popper-class="allIconsMode ? 'popper-transparent' : ''">
19
+ <el-popover placement="right" :show-arrow="false" :virtual-ref="nodeRef" virtual-triggering :visible="showPopover(data)" v-if="showPopover(data)" :show-after="0"
20
+ :hide-after="0" :popper-class="allIconsMode ? 'popper-transparent' : 'popper-menu'"
21
+ :popper-style="popoverStyle">
20
22
  <div class="box-menu" :class="{ 'box-menu-icons': allIconsMode }">
21
23
  <template v-for="(item, index) in allowedRoutes">
22
24
  <template v-if="allIconsMode">
@@ -101,6 +103,11 @@ const hoverNodeCode = ref('')
101
103
  const isHoverMode = ref(false) // 是否是悬停模式
102
104
  const isManualClose = ref(false) // 是否是手动关闭
103
105
  const allIconsMode = ref(false) // 是否全是图标模式
106
+ // 新增:弹出框位置控制
107
+ const popoverStyle = ref({})
108
+ const currentMousePosition = ref({ x: 0, y: 0 }) // 当前鼠标位置
109
+ const lastNodeCode = ref('') // 上次显示菜单的节点code
110
+ const isPositionLockedForCurrentNode = ref(false) // 当前节点的位置是否已锁定
104
111
 
105
112
  // 默认展开的节点key数组
106
113
  const defaultExpandedKeys = ref([])
@@ -116,6 +123,10 @@ const fullTreeData = ref([])
116
123
  const nodeCodeMap = ref(new Map())
117
124
 
118
125
  const isExpandAll = ref(false);
126
+ const isLazyInitialized = ref(false) // 标记是否已经初始化过lazy值
127
+
128
+ // 新增:记录节点加载状态的Map
129
+ const nodeLoadedStatus = ref(new Map())
119
130
 
120
131
  /** 动态设置Input Ref */
121
132
  const handleSetInputMap = (el: refItem, item) => {
@@ -150,7 +161,6 @@ function showPopover(data) {
150
161
 
151
162
  const isCurrentNode = data.code === currentData.value.code;
152
163
  const hasRoutes = allowedRoutes.value.length > 0;
153
-
154
164
  if (isHoverMode.value) {
155
165
  // 悬停模式:当前节点且有路由就显示
156
166
  return isCurrentNode && hasRoutes && menuVisible.value;
@@ -162,11 +172,10 @@ function showPopover(data) {
162
172
 
163
173
  // 鼠标进入节点
164
174
  function handleMouseEnter(data, node) {
165
- if (isManualClose.value) {
166
- isManualClose.value = false;
167
- return;
168
- }
169
-
175
+ // if (isManualClose.value) {
176
+ // isManualClose.value = false;
177
+ // return;
178
+ // }
170
179
  // 检查当前节点的allowedRoutes是否全是图标
171
180
  const routes = getAllowedRoutesForNode(data);
172
181
  const allIcons = checkIfAllIcons(routes);
@@ -174,7 +183,7 @@ function handleMouseEnter(data, node) {
174
183
  if (allIcons) {
175
184
  isHoverMode.value = true;
176
185
  hoverNodeCode.value = data.code;
177
-
186
+ isManualClose.value = false;
178
187
 
179
188
  if (hoverNodeCode.value === data.code) {
180
189
  showNodeMenu(data, node, true);
@@ -182,12 +191,58 @@ function handleMouseEnter(data, node) {
182
191
  }
183
192
  }
184
193
 
194
+ // 鼠标移动事件
195
+ function handleMouseMove(event) {
196
+ // 更新鼠标位置
197
+ currentMousePosition.value = {
198
+ x: event.clientX,
199
+ y: event.clientY
200
+ };
201
+
202
+ // 如果是悬停模式且全是图标,并且当前节点的位置尚未锁定,则更新位置
203
+ if (menuVisible.value && isHoverMode.value && allIconsMode.value && !isPositionLockedForCurrentNode.value) {
204
+ updatePopoverPosition();
205
+ }
206
+ }
207
+
208
+ // 更新弹出框位置(只对当前节点第一次调用时生效)
209
+ function updatePopoverPosition() {
210
+ // 如果当前节点的位置已锁定,不再更新
211
+ if (isPositionLockedForCurrentNode.value) {
212
+ return;
213
+ }
214
+
215
+ // 计算菜单位置,确保不会超出屏幕
216
+ const screenWidth = window.innerWidth;
217
+ const menuWidth = allIconsMode.value ? 150 : 200; // 预估菜单宽度
185
218
 
219
+ let left = currentMousePosition.value.x + 15;
220
+
221
+ // 检查是否超出右边界
222
+ if (left + menuWidth > screenWidth) {
223
+ left = currentMousePosition.value.x - menuWidth - 10;
224
+ }
225
+
226
+ // 只设置水平位置,垂直位置使用 Element UI 默认的
227
+ popoverStyle.value = {
228
+ left: `${left}px`
229
+ };
230
+
231
+ // 设置当前节点的位置为已锁定
232
+ if (currentData.value.code) {
233
+ isPositionLockedForCurrentNode.value = true;
234
+ }
235
+ }
186
236
 
187
237
  // 右键菜单事件
188
238
  function handleContextMenu(event, object, node) {
189
239
  event.preventDefault();
190
240
 
241
+ // 记录鼠标位置
242
+ currentMousePosition.value = {
243
+ x: event.clientX,
244
+ y: event.clientY
245
+ };
191
246
  // 检查当前节点的allowedRoutes是否全是图标
192
247
  const routes = getAllowedRoutesForNode(object);
193
248
  const allIcons = checkIfAllIcons(routes);
@@ -196,12 +251,21 @@ function handleContextMenu(event, object, node) {
196
251
  if (allIcons) {
197
252
  return;
198
253
  }
199
-
254
+ isManualClose.value = false;
200
255
  showNodeMenu(object, node, false);
201
256
  }
202
257
 
203
258
  // 共用方法:显示节点菜单
204
259
  function showNodeMenu(object, node, isHoverTrigger = false) {
260
+ // 检查是否是新的节点
261
+ const isNewNode = object.code !== lastNodeCode.value;
262
+
263
+ if (isNewNode) {
264
+ // 如果是新节点,重置位置锁定状态
265
+ isPositionLockedForCurrentNode.value = false;
266
+ lastNodeCode.value = object.code;
267
+ }
268
+
205
269
  nodeRef.value = treeNodeRefMap.value[`node_Ref_${object.code}`]
206
270
 
207
271
  currentData.value = object
@@ -209,19 +273,21 @@ function showNodeMenu(object, node, isHoverTrigger = false) {
209
273
 
210
274
  allowedRoutes.value = getAllowedRoutesForNode(object);
211
275
 
212
-
213
-
214
276
  if (allowedRoutes.value.length > 0) {
215
277
  menuVisible.value = true;
216
278
 
217
- // if (isHoverTrigger && allIcons) {
218
- // // 悬停模式:自动关闭定时器
219
- // setTimeout(() => {
220
- // if (menuVisible.value && isHoverMode.value) {
221
- // closeMenu();
222
- // }
223
- // }, 300); // 3秒后自动关闭
224
- // }
279
+ // 使用nextTick确保DOM已更新,然后设置位置
280
+ nextTick(() => {
281
+ // 如果是右键触发,设置弹出框位置
282
+ if (!isHoverTrigger && !allIconsMode.value) {
283
+ updatePopoverPosition();
284
+ }
285
+
286
+ // 如果是悬停模式且全是图标,设置跟随鼠标
287
+ if (isHoverTrigger && allIconsMode.value) {
288
+ updatePopoverPosition();
289
+ }
290
+ });
225
291
 
226
292
  // 添加点击事件监听
227
293
  document.addEventListener('click', closeMenuOnClick);
@@ -242,10 +308,13 @@ function closeMenu() {
242
308
  isManualClose.value = true;
243
309
  menuVisible.value = false;
244
310
  isHoverMode.value = false;
311
+ lastNodeCode.value = '';
312
+ isPositionLockedForCurrentNode.value = false;
245
313
 
246
314
  hoverNodeCode.value = '';
247
315
  nodeRef.value = null;
248
- // currentData.value = { ...currentData.value, code: "" };
316
+ // 重置弹出框样式
317
+ popoverStyle.value = {};
249
318
 
250
319
  // 移除事件监听
251
320
  document.removeEventListener('click', closeMenuOnClick);
@@ -256,16 +325,19 @@ function closeMenu() {
256
325
  }
257
326
  }
258
327
 
259
-
260
-
261
-
262
-
263
328
  // 设置默认展开节点
264
329
  function setDefaultExpandedNodes() {
265
- if (!props.expandTwoLevels || !fullTreeData.value.length) {
330
+ if (!props.expandTwoLevels) {
331
+ return;
332
+ }
333
+
334
+ // 如果是懒加载模式,使用特定的展开逻辑
335
+ if (lazy.value) {
336
+ setLazyModeExpandedNodes();
266
337
  return;
267
338
  }
268
339
 
340
+ // 全量数据模式
269
341
  if (isSearching.value) {
270
342
  isExpandAll.value = true;
271
343
  const allKeys = Tree.getAllNodeKeys(fullTreeData.value);
@@ -299,9 +371,52 @@ function setDefaultExpandedNodes() {
299
371
  hasSetDefaultExpand.value = true;
300
372
  }
301
373
 
374
+ // 修改:懒加载模式下的展开逻辑
375
+ function setLazyModeExpandedNodes() {
376
+ if (!fullTreeData.value || fullTreeData.value.length === 0) {
377
+ return;
378
+ }
379
+
380
+ // 获取所有非叶子节点(最后一层不展开)
381
+ const nonLeafKeys = [];
382
+
383
+ function traverse(nodes) {
384
+ nodes.forEach(node => {
385
+ // 关键判断:如果是叶子节点,就不展开
386
+ const isLeafNode = node.isLeaf === true ||
387
+ (!node.children || node.children.length === 0);
388
+
389
+ if (!isLeafNode) {
390
+ // 非叶子节点,可以展开
391
+ nonLeafKeys.push(node.code);
392
+
393
+ // 递归处理子节点
394
+ if (node.children && node.children.length > 0) {
395
+ traverse(node.children);
396
+ }
397
+ }
398
+ });
399
+ }
302
400
 
401
+ traverse(fullTreeData.value);
303
402
 
403
+ defaultExpandedKeys.value = nonLeafKeys;
404
+ hasSetDefaultExpand.value = true;
304
405
 
406
+ // 延迟设置,确保树已渲染
407
+ nextTick(() => {
408
+ if (refTree.value && refTree.value.store) {
409
+ // 只展开已有的非叶子节点,不触发懒加载
410
+ nonLeafKeys.forEach(key => {
411
+ const node = refTree.value.getNode(key);
412
+ if (node && !node.expanded) {
413
+ // 安全地设置展开状态
414
+ node.expanded = true;
415
+ }
416
+ });
417
+ }
418
+ });
419
+ }
305
420
 
306
421
  // 搜索(查询条件调用)
307
422
  function search(m) {
@@ -310,6 +425,8 @@ function search(m) {
310
425
  isSearching.value = hasValidSearchConditions;
311
426
  screenPara.value = m;
312
427
 
428
+
429
+
313
430
  if (!lazy.value) {
314
431
  filterFullTreeData(screenPara.value);
315
432
  return;
@@ -334,8 +451,19 @@ function search(m) {
334
451
  }, 200);
335
452
  }
336
453
  }
454
+ function collapseAllNodes() {
455
+ if (refTree.value && refTree.value.store) {
456
+ Object.values(refTree.value.store.nodesMap).forEach((node: any) => {
457
+ if (node) {
458
+ node.expanded = false;
459
+ }
460
+ });
337
461
 
338
-
462
+ // 清空默认展开的key
463
+ defaultExpandedKeys.value = [];
464
+ hasSetDefaultExpand.value = false;
465
+ }
466
+ }
339
467
  // 修改:构建完整的过滤树,保持原始层级结构
340
468
  function buildFilteredTreeWithOriginalHierarchy(matchedNodes) {
341
469
  if (!matchedNodes || matchedNodes.length === 0) {
@@ -452,7 +580,6 @@ function buildFilteredTreeWithOriginalHierarchy(matchedNodes) {
452
580
  return finalRootNodes;
453
581
  }
454
582
 
455
-
456
583
  // 新增:在全量树中查找父节点code
457
584
  function findParentCodeInFullTree(nodeCode) {
458
585
  function findParent(nodes, targetCode, parentCode = null) {
@@ -565,9 +692,9 @@ function filterFullTreeData(filter) {
565
692
  if (firstNode && firstNode.code) {
566
693
  nextTick(() => {
567
694
  refTree.value.setCurrentKey(firstNode.code);
568
- handleNodeClick(firstNode, true);
695
+ handleNodeClick(firstNode);
569
696
  });
570
- }
697
+ }
571
698
 
572
699
  }
573
700
 
@@ -590,70 +717,156 @@ function getAllowedRoutesForNode(data) {
590
717
  }
591
718
 
592
719
  // 修改:加载节点,保存节点Map
593
- function loadNode(node, resolve) {
594
- if (!lazy.value) {
595
- const nodeData = node.data;
596
- resolve(nodeData.children)
720
+ function loadNode(node, resolve) {
721
+ // 如果是根节点(level === 0),清空节点加载状态
722
+ if (node.level === 0) {
723
+ nodeLoadedStatus.value.clear();
724
+ }
725
+ // 如果节点已经加载过,直接返回子节点
726
+ if (nodeLoadedStatus.value.has(node.key) && nodeLoadedStatus.value.get(node.key) === 'loaded') {
727
+ if (node.data && node.data.children) {
728
+ resolve(node.data.children);
729
+ } else {
730
+ resolve([]);
731
+ }
732
+ return;
733
+ }
734
+
735
+ // 如果节点已经有数据且不是懒加载模式,直接返回子节点
736
+ if (node.data && node.data.children && node.data.children.length > 0 && !lazy.value) {
737
+ resolve(node.data.children);
597
738
  return;
598
739
  }
599
740
 
600
- if (node.level === 0) {
741
+ // 懒加载模式:第一次加载或需要加载子节点
742
+ if (lazy.value && node.level === 0) {
601
743
  loading.value = true;
602
- rootNode.value = node
744
+ rootNode.value = node;
603
745
  rootResolve.value = resolve;
604
-
746
+
605
747
  return SearchTree(screenPara.value || undefined).then(data => {
606
748
  loading.value = false;
749
+ // 重置前折叠所有节点
750
+ collapseAllNodes();
751
+
752
+ let lazyMode = lazy.value;
753
+ if (!isLazyInitialized.value) {
754
+ lazyMode = false;
755
+ }
607
756
 
608
757
  const { tree, hasAnyLeaf } = Tree.buildDeptTreeByField(data.rows, {
609
758
  pathKey: 'path',
610
- separator: '.'
759
+ separator: '.',
760
+ lazyMode: lazyMode,
611
761
  });
612
762
 
613
- if (hasAnyLeaf) {
614
- fullTreeData.value = tree;
615
- nodeCodeMap.value = Tree.buildNodeCodeMap(tree);
616
- resolve(tree);
617
- if (props.expandTwoLevels) {
618
- nextTick(() => {
619
- hasSetDefaultExpand.value = false;
620
- setDefaultExpandedNodes();
621
- });
763
+ // 只在第一次加载时初始化
764
+ if (!isLazyInitialized.value) {
765
+ isLazyInitialized.value = true;
766
+
767
+ if (hasAnyLeaf) {
768
+ lazy.value = !hasAnyLeaf;
769
+ fullTreeData.value = tree;
770
+ nodeCodeMap.value = Tree.buildNodeCodeMap(tree);
622
771
  }
623
772
  }
624
- else {
625
- resolve(data.rows);
626
- }
627
- lazy.value = !hasAnyLeaf;
628
773
 
774
+ // 无论是否第一次加载,都解析树数据
775
+ const resultData = hasAnyLeaf ? tree : data.rows;
776
+ resolve(resultData);
777
+
778
+
779
+
780
+ if (props.expandTwoLevels && hasAnyLeaf) {
781
+ fullTreeData.value = tree;
782
+ nextTick(() => {
783
+ hasSetDefaultExpand.value = false;
784
+ setDefaultExpandedNodes();
785
+ });
786
+ }
629
787
  load(data, true);
788
+
630
789
  }).catch(error => {
631
790
  loading.value = false;
632
791
  console.error('加载树数据失败:', error);
633
792
  });
634
793
  }
635
794
 
636
- if (node.level >= 1 && lazy.value) {
637
- let fields = model.value.searchData("code", node.data.code, 9, 3);
638
- let filter = {
639
- "searchData": {
640
- ...(screenPara.value?.searchData || {}),
641
- fields: [
642
- ...fields.fields,
643
- ...(screenPara.value?.searchData?.fields || [])
644
- ]
645
- }
646
- };
795
+ // 非懒加载模式或懒加载但已有数据
796
+ if (!lazy.value && node.data && node.data.children) {
797
+ resolve(node.data.children);
798
+ return;
799
+ }
647
800
 
648
- return SearchTree(filter).then(data => {
649
- loading.value = false;
650
- resolve(data.rows);
651
- load(data, false);
652
- }).catch(error => {
653
- loading.value = false;
654
- console.error('加载子节点失败:', error);
655
- });
801
+ // 懒加载模式加载子节点
802
+ // 懒加载模式加载子节点
803
+ if (node.level >= 1 && lazy.value) {
804
+
805
+ // 设置节点为加载中状态
806
+ if (node && node.key) {
807
+ nodeLoadedStatus.value.set(node.key, 'loading');
808
+ }
809
+
810
+ // 检查是否已经有子节点数据
811
+ if (node.data.children && node.data.children.length > 0) {
812
+ if (node && node.key) {
813
+ nodeLoadedStatus.value.set(node.key, 'loaded');
814
+ }
815
+ resolve(node.data.children);
816
+ return;
656
817
  }
818
+
819
+ let fields = model.value.searchData("code", node.data.code, 9, 3);
820
+ let filter = {
821
+ "searchData": {
822
+ ...(screenPara.value?.searchData || {}),
823
+ fields: [
824
+ ...fields.fields,
825
+ ...(screenPara.value?.searchData?.fields || [])
826
+ ]
827
+ }
828
+ };
829
+
830
+ return SearchTree(filter).then(data => {
831
+ // 标记节点为已加载
832
+ if (node && node.key) {
833
+ nodeLoadedStatus.value.set(node.key, 'loaded');
834
+ }
835
+
836
+ // 关键:更新节点的数据,添加children属性
837
+ if (node.data) {
838
+ node.data.children = data.rows;
839
+
840
+ // 判断是否没有子节点
841
+ if (!data.rows || data.rows.length === 0) {
842
+ // 没有子节点,设置节点为叶子节点
843
+ node.data.isLeaf = true;
844
+ node.isLeaf = true;
845
+ } else {
846
+ // 有子节点,设置节点为非叶子节点
847
+ node.data.isLeaf = false;
848
+ node.isLeaf = false;
849
+ }
850
+ }
851
+
852
+ resolve(data.rows || []);
853
+ load(data, false);
854
+ }).catch(error => {
855
+ loading.value = false;
856
+ if (node && node.key) {
857
+ nodeLoadedStatus.value.delete(node.key);
858
+ }
859
+ console.error('加载子节点失败:', error);
860
+
861
+ // 加载失败时,如果没有子节点数据,也标记为叶子节点
862
+ if (node && node.data) {
863
+ node.data.isLeaf = true;
864
+ node.isLeaf = true;
865
+ }
866
+
867
+ resolve([]);
868
+ });
869
+ }
657
870
  }
658
871
 
659
872
  // 加载数据
@@ -668,13 +881,26 @@ async function load(data, flagRootNode) {
668
881
 
669
882
  if (flagRootNode) {
670
883
  if (model.value.rows && model.value.rows[0]) {
671
- handleNodeClick(model.value.rows[0], true);
884
+ handleNodeClick(model.value.rows[0]);
672
885
  }
673
886
  nextTick(() => {
674
887
  if (model.value.rows && model.value.rows[0] && model.value.rows[0].code) {
675
888
  refTree.value.setCurrentKey(model.value.rows[0].code)
676
889
  }
677
890
  });
891
+ } else {
892
+ // 对于子节点加载,检查是否有数据返回
893
+ const currentNodeCode = currentData.value?.code;
894
+ if (currentNodeCode) {
895
+ const treeNode = refTree.value.getNode(currentNodeCode);
896
+ if (treeNode && (!data.rows || data.rows.length === 0)) {
897
+ // 没有子节点数据,标记为叶子节点
898
+ treeNode.isLeaf = true;
899
+ if (treeNode.data) {
900
+ treeNode.data.isLeaf = true;
901
+ }
902
+ }
903
+ }
678
904
  }
679
905
  }
680
906
 
@@ -694,6 +920,10 @@ function SearchTree(m) {
694
920
  // 重置树到全量数据
695
921
  function resetTree() {
696
922
  isSearching.value = false;
923
+
924
+ // 重置前折叠所有节点
925
+ collapseAllNodes();
926
+
697
927
  if (fullTreeData.value.length > 0) {
698
928
  refTree.value.store.setData(fullTreeData.value);
699
929
  hasSetDefaultExpand.value = false;
@@ -701,16 +931,8 @@ function resetTree() {
701
931
 
702
932
  if (fullTreeData.value[0] && fullTreeData.value[0].code) {
703
933
  nextTick(() => {
704
- if (refTree.value && refTree.value.store) {
705
- Object.values(refTree.value.store.nodesMap).forEach((node: any) => {
706
- if (node) {
707
- node.expanded = false;
708
- }
709
- });
710
- }
711
-
712
934
  refTree.value.setCurrentKey(fullTreeData.value[0].code);
713
- handleNodeClick(fullTreeData.value[0], true);
935
+ handleNodeClick(fullTreeData.value[0]);
714
936
  setDefaultExpandedNodes();
715
937
  });
716
938
  }
@@ -722,37 +944,78 @@ function clearSearch() {
722
944
  screenPara.value = {};
723
945
  isSearching.value = false;
724
946
 
725
- defaultExpandedKeys.value = [];
726
- hasSetDefaultExpand.value = false;
947
+ // 清除前折叠所有节点
948
+ collapseAllNodes();
727
949
 
728
950
  if (fullTreeData.value.length > 0) {
729
951
  refTree.value.store.setData(fullTreeData.value);
730
952
 
731
953
  nextTick(() => {
732
- if (refTree.value && refTree.value.store) {
733
- Object.values(refTree.value.store.nodesMap).forEach((node: any) => {
734
- if (node) {
735
- node.expanded = false;
736
- }
737
- });
738
- }
739
-
740
954
  setDefaultExpandedNodes();
741
955
 
742
956
  if (fullTreeData.value[0] && fullTreeData.value[0].code) {
743
957
  refTree.value.setCurrentKey(fullTreeData.value[0].code);
744
- handleNodeClick(fullTreeData.value[0], true);
958
+ handleNodeClick(fullTreeData.value[0]);
745
959
  }
746
960
  });
747
961
  }
748
962
  }
749
963
 
750
- //节点点击事件
751
- function handleNodeClick(data, formType) {
964
+ // 修改:节点点击事件,支持自动展开和懒加载
965
+ function handleNodeClick(data) {
966
+
752
967
  closeMenu();
753
- if (data && formType) {
754
- refTree.value.setCurrentKey(data.code)
968
+
969
+ // 设置当前选中节点
970
+ if (data && data.code) {
971
+ refTree.value?.setCurrentKey(data.code);
972
+
973
+ // 如果是懒加载模式,检查节点是否需要加载子节点
974
+ if (lazy.value) {
975
+ const treeNode = refTree.value.getNode(data.code);
976
+ if (treeNode) {
977
+ // 如果节点不是叶子节点但尚未加载,触发加载
978
+ if (!treeNode.isLeaf && !treeNode.loaded && !treeNode.loading) {
979
+ loadNode(treeNode, (children) => {
980
+ if (children && children.length > 0) {
981
+ // 有子节点,展开节点
982
+ if (!treeNode.expanded) {
983
+ treeNode.expanded = true;
984
+ }
985
+ } else {
986
+ // 没有子节点,标记为叶子节点
987
+ treeNode.isLeaf = true;
988
+ treeNode.loaded = true;
989
+
990
+ // 更新原始数据中的 isLeaf 属性
991
+ if (treeNode.data) {
992
+ treeNode.data.isLeaf = true;
993
+ }
994
+
995
+ // 如果有父节点,更新父节点的children数组
996
+ if (treeNode.parent && treeNode.parent.data) {
997
+ const parentData = treeNode.parent.data;
998
+ if (parentData.children) {
999
+ // 找到当前节点在父节点children中的索引
1000
+ const index = parentData.children.findIndex(child => child.code === data.code);
1001
+ if (index !== -1) {
1002
+ parentData.children[index].isLeaf = true;
1003
+ }
1004
+ }
1005
+ }
1006
+ }
1007
+ });
1008
+ } else if (treeNode.children && treeNode.children.length > 0 && !treeNode.expanded) {
1009
+ // 如果节点已经有子节点但未展开,展开节点
1010
+ treeNode.expanded = true;
1011
+ } else if (treeNode.loaded && treeNode.children && treeNode.children.length === 0) {
1012
+ // 节点已加载但没有子节点,确保标记为叶子节点
1013
+ treeNode.isLeaf = true;
1014
+ }
1015
+ }
1016
+ }
755
1017
  }
1018
+
756
1019
  if (LastClickNode.value != data) {
757
1020
  LastClickNode.value = data;
758
1021
  let rowRouter = model.value.rowSelectRouter;
@@ -776,6 +1039,7 @@ function handleNodeClick(data, formType) {
776
1039
  }
777
1040
  }
778
1041
 
1042
+
779
1043
  // 菜单路由
780
1044
  function routerClickHandler(field) {
781
1045
  isManualClose.value = true;
@@ -811,7 +1075,7 @@ function addNode(newData) {
811
1075
  // 全量数据模式:手动更新全量数据和树显示
812
1076
  const parentCode = currentData.value.code;
813
1077
  const parentPath = currentData.value.path || '';
814
-
1078
+
815
1079
  // 设置新节点的属性
816
1080
  const newNode = {
817
1081
  ...newData,
@@ -822,7 +1086,7 @@ function addNode(newData) {
822
1086
  // 标识是否为叶子节点
823
1087
  isLeaf: true
824
1088
  };
825
-
1089
+
826
1090
  // 在全量数据中找到父节点并添加
827
1091
  const findAndAdd = (nodes, parentCode) => {
828
1092
  for (let i = 0; i < nodes.length; i++) {
@@ -834,7 +1098,7 @@ function addNode(newData) {
834
1098
  nodes[i].isLeaf = false; // 父节点不再是叶子节点
835
1099
  return true;
836
1100
  }
837
-
1101
+
838
1102
  if (nodes[i].children && nodes[i].children.length > 0) {
839
1103
  if (findAndAdd(nodes[i].children, parentCode)) {
840
1104
  return true;
@@ -843,13 +1107,13 @@ function addNode(newData) {
843
1107
  }
844
1108
  return false;
845
1109
  };
846
-
1110
+
847
1111
  // 更新全量数据
848
1112
  findAndAdd(fullTreeData.value, parentCode);
849
-
1113
+
850
1114
  // 更新nodeCodeMap
851
1115
  nodeCodeMap.value.set(newNode.code, newNode);
852
-
1116
+
853
1117
  // 如果是搜索状态,需要同时更新当前显示的数据
854
1118
  if (isSearching.value && screenPara.value) {
855
1119
  filterFullTreeData(screenPara.value);
@@ -857,7 +1121,7 @@ function addNode(newData) {
857
1121
  // 非搜索状态,直接重新设置数据
858
1122
  refTree.value.store.setData([...fullTreeData.value]);
859
1123
  }
860
-
1124
+
861
1125
  // 刷新树显示
862
1126
  nextTick(() => {
863
1127
  // 展开父节点
@@ -866,16 +1130,16 @@ function addNode(newData) {
866
1130
  refTree.value.store.setCurrentNode(parentNode);
867
1131
  parentNode.expanded = true;
868
1132
  }
869
-
1133
+
870
1134
  // 设置新节点为当前选中
871
- handleNodeClick(newNode, true);
1135
+ handleNodeClick(newNode);
872
1136
  });
873
1137
  } else {
874
1138
  // 懒加载模式:使用原有方法
875
1139
  refTree.value.append(newData, currentNode.value);
876
- handleNodeClick(newData, true);
1140
+ handleNodeClick(newData);
877
1141
  }
878
- }
1142
+ }
879
1143
 
880
1144
  //修改树节点
881
1145
  function updateNode(newData) {
@@ -888,7 +1152,7 @@ function updateNode(newData) {
888
1152
  nodes[i] = { ...nodes[i], ...newData };
889
1153
  return true;
890
1154
  }
891
-
1155
+
892
1156
  if (nodes[i].children && nodes[i].children.length > 0) {
893
1157
  if (updateInTree(nodes[i].children, nodeCode)) {
894
1158
  return true;
@@ -897,16 +1161,16 @@ function updateNode(newData) {
897
1161
  }
898
1162
  return false;
899
1163
  };
900
-
1164
+
901
1165
  // 更新全量数据
902
1166
  updateInTree(fullTreeData.value, currentData.value.code);
903
-
1167
+
904
1168
  // 更新nodeCodeMap
905
1169
  nodeCodeMap.value.set(currentData.value.code, {
906
1170
  ...nodeCodeMap.value.get(currentData.value.code),
907
1171
  ...newData
908
1172
  });
909
-
1173
+
910
1174
  // 如果是搜索状态,重新过滤显示
911
1175
  if (isSearching.value && screenPara.value) {
912
1176
  filterFullTreeData(screenPara.value);
@@ -914,11 +1178,11 @@ function updateNode(newData) {
914
1178
  // 非搜索状态,直接重新设置数据
915
1179
  refTree.value.store.setData([...fullTreeData.value]);
916
1180
  }
917
-
1181
+
918
1182
  // 刷新显示并保持选中状态
919
1183
  nextTick(() => {
920
1184
  refTree.value.setCurrentKey(currentData.value.code);
921
- handleNodeClick({ ...currentData.value, ...newData }, true);
1185
+ handleNodeClick({ ...currentData.value, ...newData });
922
1186
  });
923
1187
  } else {
924
1188
  // 懒加载模式:使用原有方法
@@ -929,7 +1193,7 @@ function updateNode(newData) {
929
1193
  currentData.value[key] = newData[key];
930
1194
  }
931
1195
  }
932
- handleNodeClick(newData, true);
1196
+ handleNodeClick(newData);
933
1197
  }
934
1198
  }
935
1199
 
@@ -939,7 +1203,7 @@ function removeNode(newData) {
939
1203
  // 全量数据模式:从全量数据中删除
940
1204
  let parentNode = null;
941
1205
  let parentCode = null;
942
-
1206
+
943
1207
  const removeFromTree = (nodes, nodeCode) => {
944
1208
  for (let i = 0; i < nodes.length; i++) {
945
1209
  if (nodes[i].code === nodeCode) {
@@ -951,13 +1215,13 @@ function removeNode(newData) {
951
1215
  parentNode = parent;
952
1216
  }
953
1217
  }
954
-
1218
+
955
1219
  // 删除节点
956
1220
  nodes.splice(i, 1);
957
1221
  nodeCodeMap.value.delete(nodeCode);
958
1222
  return true;
959
1223
  }
960
-
1224
+
961
1225
  if (nodes[i].children && nodes[i].children.length > 0) {
962
1226
  if (removeFromTree(nodes[i].children, nodeCode)) {
963
1227
  return true;
@@ -966,16 +1230,16 @@ function removeNode(newData) {
966
1230
  }
967
1231
  return false;
968
1232
  };
969
-
1233
+
970
1234
  // 从全量数据中删除
971
1235
  removeFromTree(fullTreeData.value, currentData.value.code);
972
-
1236
+
973
1237
  // 更新父节点的 isLeaf 状态
974
1238
  if (parentCode && parentNode) {
975
1239
  // 重新检查父节点是否还有子节点
976
1240
  const hasChildren = parentNode.children && parentNode.children.length > 0;
977
1241
  parentNode.isLeaf = !hasChildren;
978
-
1242
+
979
1243
  // 同步更新 nodeCodeMap
980
1244
  if (nodeCodeMap.value.has(parentCode)) {
981
1245
  const updatedParent = nodeCodeMap.value.get(parentCode);
@@ -985,7 +1249,7 @@ function removeNode(newData) {
985
1249
  } else {
986
1250
  // 如果是根节点被删除,不需要更新 isLeaf
987
1251
  }
988
-
1252
+
989
1253
  // 如果是搜索状态,重新过滤显示
990
1254
  if (isSearching.value && screenPara.value) {
991
1255
  filterFullTreeData(screenPara.value);
@@ -993,7 +1257,7 @@ function removeNode(newData) {
993
1257
  // 非搜索状态,直接重新设置数据
994
1258
  refTree.value.store.setData([...fullTreeData.value]);
995
1259
  }
996
-
1260
+
997
1261
  // 查找下一个可点击的节点
998
1262
  getNextClickNode();
999
1263
  } else {
@@ -1009,7 +1273,7 @@ function findNodeByCode(nodes, targetCode) {
1009
1273
  if (node.code === targetCode) {
1010
1274
  return node;
1011
1275
  }
1012
-
1276
+
1013
1277
  if (node.children && node.children.length > 0) {
1014
1278
  const found = findNodeByCode(node.children, targetCode);
1015
1279
  if (found) return found;
@@ -1024,7 +1288,7 @@ function findParentNodeInFullTree(nodes, targetCode, parent = null) {
1024
1288
  if (node.code === targetCode) {
1025
1289
  return parent;
1026
1290
  }
1027
-
1291
+
1028
1292
  if (node.children && node.children.length > 0) {
1029
1293
  const found = findParentNodeInFullTree(node.children, targetCode, node);
1030
1294
  if (found) return found;
@@ -1043,7 +1307,7 @@ function getNextClickNode() {
1043
1307
  } else if (node?.parent) {
1044
1308
  data = node.parent.data;
1045
1309
  }
1046
- handleNodeClick(data, true)
1310
+ handleNodeClick(data)
1047
1311
  }
1048
1312
 
1049
1313
  /** 过滤节点(全量数据模式下使用) */
@@ -1052,7 +1316,6 @@ function filterNode(value, data) {
1052
1316
  return data.name.toLowerCase().includes(value.toLowerCase())
1053
1317
  }
1054
1318
 
1055
-
1056
1319
  function findSearchFieldValue(searchFields, targetKey, options = {}) {
1057
1320
  const {
1058
1321
  keyFields = ['fieldName1', 'groupName'],
@@ -1137,6 +1400,7 @@ defineExpose({
1137
1400
  display: flex;
1138
1401
  align-items: center;
1139
1402
  position: relative;
1403
+ width: 100%;
1140
1404
  }
1141
1405
 
1142
1406
  .node-icon {
@@ -1153,18 +1417,18 @@ defineExpose({
1153
1417
 
1154
1418
  div {
1155
1419
  cursor: pointer;
1420
+
1156
1421
  }
1157
1422
  }
1158
1423
 
1159
1424
  .box-menu.box-menu-icons {
1160
1425
  display: flex;
1161
1426
  flex-wrap: nowrap;
1162
- padding: 8px;
1427
+ padding: 8px 0px;
1163
1428
  gap: 0px;
1164
1429
  line-height: 15px;
1165
1430
  background: transparent !important;
1166
- border: none !important;
1167
- box-shadow: none !important;
1431
+
1168
1432
  }
1169
1433
 
1170
1434
  .box-menu-icons .opertion {
@@ -1211,6 +1475,8 @@ defineExpose({
1211
1475
  border-radius: 6px;
1212
1476
  position: relative;
1213
1477
  overflow: hidden;
1478
+
1479
+
1214
1480
  }
1215
1481
 
1216
1482
  .box-menu .opertion:hover {
@@ -1250,6 +1516,8 @@ defineExpose({
1250
1516
  display: flex;
1251
1517
  align-items: center;
1252
1518
  width: 100%;
1519
+ justify-content: center; // 垂直居中
1520
+ text-align: center;
1253
1521
  }
1254
1522
 
1255
1523
  /* 菜单激活状态 */
@@ -1267,9 +1535,16 @@ defineExpose({
1267
1535
  background: transparent !important;
1268
1536
  border: none !important;
1269
1537
  box-shadow: none !important;
1538
+ padding: 0 !important;
1539
+
1270
1540
  }
1271
1541
 
1272
- .popper-transparent .el-popper__arrow::before {
1273
- background: transparent !important;
1542
+ // .popper-transparent .el-popper__arrow::before,
1543
+ // .popper-menu .el-popper__arrow::before {
1544
+ // background: transparent !important;
1545
+ // }
1546
+
1547
+ .popper-menu {
1548
+ padding: 0 !important;
1274
1549
  }
1275
1550
  </style>