centaline-data-driven-v3 0.1.44 → 0.1.46

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,24 +1,31 @@
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"
5
- :filter-node-method="filterNode" lazy :load="loadNode" ref="refTree" @node-contextmenu="rightClick"
4
+ <el-tree class="tree-line" :props="defaultProps" :expand-on-click-node="false"
5
+ @node-click="handleNodeClick"
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">
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" />
12
13
  <FolderOpened v-else :size="14" />
13
14
  </el-icon>
14
- <span style="" :ref="(el: refItem) => handleSetInputMap(el, data)">{{
15
- data.name }}</span>
15
+ <span style="" :ref="(el: refItem) => handleSetInputMap(el, data)">
16
+ {{ data.name }}
17
+ </span>
16
18
  <span @click.stop>
17
- <el-popover placement="right" :virtual-ref="nodeRef" virtual-triggering
18
- :visible="((data.code == currentData.code && menuVisible && allowedRoutes?.length > 0))">
19
- <div class="box-menu">
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">
22
+ <div class="box-menu" :class="{ 'box-menu-icons': allIconsMode }">
20
23
  <template v-for="(item, index) in allowedRoutes">
21
- <div class="opertion" @click="routerClickHandler(item)">
24
+ <template v-if="allIconsMode">
25
+ <img class="opertion" v-if="item.imgUrl" :src="item.imgUrl" :alt="item.controlLabel || ''"
26
+ :title="item.controlLabel" @click="routerClickHandler(item)" />
27
+ </template>
28
+ <div class="opertion" @click="routerClickHandler(item)" v-else>
22
29
  <div>
23
30
  <img v-if="item.imgUrl" :src="item.imgUrl" alt="" />
24
31
  {{ item.controlLabel }}
@@ -90,6 +97,18 @@ const retryCount = ref(0);
90
97
  const treeNodeRefMap = ref({});
91
98
  const allowedRoutes = ref([]);
92
99
 
100
+ // 新增:鼠标悬停相关状态
101
+ const hoverTimer = ref(null)
102
+ const hoverNodeCode = ref('')
103
+ const isHoverMode = ref(false) // 是否是悬停模式
104
+ const isManualClose = ref(false) // 是否是手动关闭
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) // 当前节点的位置是否已锁定
111
+
93
112
  // 默认展开的节点key数组
94
113
  const defaultExpandedKeys = ref([])
95
114
  // 是否已设置默认展开
@@ -104,6 +123,11 @@ const fullTreeData = ref([])
104
123
  const nodeCodeMap = ref(new Map())
105
124
 
106
125
  const isExpandAll = ref(false);
126
+ const isLazyInitialized = ref(false) // 标记是否已经初始化过lazy值
127
+
128
+ // 新增:记录节点加载状态的Map
129
+ const nodeLoadedStatus = ref(new Map())
130
+
107
131
  /** 动态设置Input Ref */
108
132
  const handleSetInputMap = (el: refItem, item) => {
109
133
  if (el) {
@@ -117,129 +141,292 @@ onBeforeUnmount(() => {
117
141
  clearTimeout(qrtimer.value);
118
142
  qrtimer.value = null;
119
143
  }
144
+ if (hoverTimer.value) {
145
+ clearTimeout(hoverTimer.value);
146
+ hoverTimer.value = null;
147
+ }
120
148
  })
121
149
 
122
- function getFirstNLevelKeys(nodes, targetLevel = 1) {
123
- const result = [];
150
+ // 检查allowedRoutes是否全是图标
151
+ function checkIfAllIcons(routes) {
152
+ if (!routes || routes.length === 0) return false;
153
+ return routes.every(item => item.imgUrl && item.imgUrl.trim() !== '');
154
+ }
124
155
 
125
- if (!nodes || nodes.length === 0 || targetLevel < 0) {
126
- return result;
156
+ // 显示弹出框的条件
157
+ function showPopover(data) {
158
+ if (isManualClose.value) {
159
+ return false;
127
160
  }
128
161
 
129
- const traverse = (nodes, currentLevel) => {
130
- if (currentLevel > targetLevel || !nodes) return;
162
+ const isCurrentNode = data.code === currentData.value.code;
163
+ const hasRoutes = allowedRoutes.value.length > 0;
164
+ if (isHoverMode.value) {
165
+ // 悬停模式:当前节点且有路由就显示
166
+ return isCurrentNode && hasRoutes && menuVisible.value;
167
+ } else {
168
+ // 右键模式:右键触发才显示
169
+ return isCurrentNode && hasRoutes && menuVisible.value;
170
+ }
171
+ }
131
172
 
132
- nodes.forEach(node => {
133
- if (node.code) {
134
- result.push(node.code);
173
+ // 鼠标进入节点
174
+ function handleMouseEnter(data, node) {
175
+ // if (isManualClose.value) {
176
+ // isManualClose.value = false;
177
+ // return;
178
+ // }
179
+ // 检查当前节点的allowedRoutes是否全是图标
180
+ const routes = getAllowedRoutesForNode(data);
181
+ const allIcons = checkIfAllIcons(routes);
182
+ allIconsMode.value = allIcons;
183
+ if (allIcons) {
184
+ isHoverMode.value = true;
185
+ hoverNodeCode.value = data.code;
186
+ isManualClose.value = false;
187
+
188
+ if (hoverNodeCode.value === data.code) {
189
+ showNodeMenu(data, node, true);
190
+ }
191
+ }
192
+ }
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; // 预估菜单宽度
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
+ }
236
+
237
+ // 右键菜单事件
238
+ function handleContextMenu(event, object, node) {
239
+ event.preventDefault();
240
+
241
+ // 记录鼠标位置
242
+ currentMousePosition.value = {
243
+ x: event.clientX,
244
+ y: event.clientY
245
+ };
246
+ // 检查当前节点的allowedRoutes是否全是图标
247
+ const routes = getAllowedRoutesForNode(object);
248
+ const allIcons = checkIfAllIcons(routes);
249
+
250
+ // 如果全是图标,右键时不执行后面代码(不显示菜单)
251
+ if (allIcons) {
252
+ return;
253
+ }
254
+ isManualClose.value = false;
255
+ showNodeMenu(object, node, false);
256
+ }
257
+
258
+ // 共用方法:显示节点菜单
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
+
269
+ nodeRef.value = treeNodeRefMap.value[`node_Ref_${object.code}`]
270
+
271
+ currentData.value = object
272
+ currentNode.value = node
273
+
274
+ allowedRoutes.value = getAllowedRoutesForNode(object);
275
+
276
+ if (allowedRoutes.value.length > 0) {
277
+ menuVisible.value = true;
278
+
279
+ // 使用nextTick确保DOM已更新,然后设置位置
280
+ nextTick(() => {
281
+ // 如果是右键触发,设置弹出框位置
282
+ if (!isHoverTrigger && !allIconsMode.value) {
283
+ updatePopoverPosition();
135
284
  }
136
285
 
137
- // 如果还有下一层且没超过目标层级,继续遍历
138
- if (currentLevel < targetLevel && node.children && node.children.length > 0) {
139
- traverse(node.children, currentLevel + 1);
286
+ // 如果是悬停模式且全是图标,设置跟随鼠标
287
+ if (isHoverTrigger && allIconsMode.value) {
288
+ updatePopoverPosition();
140
289
  }
141
290
  });
142
- };
143
291
 
144
- traverse(nodes, 0);
145
- return result;
292
+ // 添加点击事件监听
293
+ document.addEventListener('click', closeMenuOnClick);
294
+ }
146
295
  }
147
296
 
148
- // 递归获取所有节点的key(用于搜索时全展开)
149
- function getAllNodeKeys(nodes, result = []) {
150
- if (!nodes || nodes.length === 0) {
151
- return result;
297
+ // 点击关闭菜单
298
+ function closeMenuOnClick(e) {
299
+ // 检查点击是否在菜单内部
300
+ const menu = document.querySelector('.box-menu');
301
+ if (menu && !menu.contains(e.target)) {
302
+ closeMenu();
152
303
  }
304
+ }
153
305
 
154
- nodes.forEach(node => {
155
- if (node.code) {
156
- result.push(node.code);
157
- }
306
+ //关闭菜单
307
+ function closeMenu() {
308
+ isManualClose.value = true;
309
+ menuVisible.value = false;
310
+ isHoverMode.value = false;
311
+ lastNodeCode.value = '';
312
+ isPositionLockedForCurrentNode.value = false;
158
313
 
159
- if (node.children && node.children.length > 0) {
160
- getAllNodeKeys(node.children, result);
161
- }
162
- });
314
+ hoverNodeCode.value = '';
315
+ nodeRef.value = null;
316
+ // 重置弹出框样式
317
+ popoverStyle.value = {};
163
318
 
164
- return result;
319
+ // 移除事件监听
320
+ document.removeEventListener('click', closeMenuOnClick);
321
+
322
+ if (hoverTimer.value) {
323
+ clearTimeout(hoverTimer.value);
324
+ hoverTimer.value = null;
325
+ }
165
326
  }
166
327
 
167
328
  // 设置默认展开节点
168
329
  function setDefaultExpandedNodes() {
169
- if (!props.expandTwoLevels || !fullTreeData.value.length) {
330
+ if (!props.expandTwoLevels) {
170
331
  return;
171
332
  }
172
333
 
334
+ // 如果是懒加载模式,使用特定的展开逻辑
335
+ if (lazy.value) {
336
+ setLazyModeExpandedNodes();
337
+ return;
338
+ }
339
+
340
+ // 全量数据模式
173
341
  if (isSearching.value) {
174
342
  isExpandAll.value = true;
175
- const allKeys = getAllNodeKeys(fullTreeData.value);
343
+ const allKeys = Tree.getAllNodeKeys(fullTreeData.value);
176
344
  defaultExpandedKeys.value = allKeys;
177
345
  } else {
178
346
  isExpandAll.value = false;
179
347
  // 检查是否有全展开搜索条件
180
348
  const expandValue = findSearchFieldValue(screenPara.value, 'expanded', {
181
- caseInsensitive: true, // 添加不区分大小写的选项
182
- keyFields: ['fieldName1', 'groupName', 'fieldLabel'] // 扩展可能的key字段
349
+ caseInsensitive: true,
350
+ keyFields: ['fieldName1', 'groupName', 'fieldLabel']
183
351
  });
184
352
 
185
- // 如果找到expanded=all的搜索条件,设置全展开
186
353
  if (expandValue && expandValue.toString().toLowerCase() === 'all') {
187
354
  isExpandAll.value = true;
188
- const allKeys = getAllNodeKeys(fullTreeData.value);
355
+ const allKeys = Tree.getAllNodeKeys(fullTreeData.value);
189
356
  defaultExpandedKeys.value = allKeys;
190
357
  } else {
191
- // 处理非'all'的情况
192
- let expandLevel = 1; // 默认展开级别为1
358
+ let expandLevel = 1;
193
359
 
194
360
  if (expandValue) {
195
361
  const numericValue = Number(expandValue);
196
362
  if (!isNaN(numericValue) && numericValue > 0) {
197
- expandLevel = Math.max(0, numericValue - 1); // 减1但最小为1
198
-
363
+ expandLevel = Math.max(0, numericValue - 1);
199
364
  }
200
365
  }
201
366
 
202
- defaultExpandedKeys.value = getFirstNLevelKeys(fullTreeData.value, expandLevel);
367
+ defaultExpandedKeys.value = Tree.getFirstNLevelKeys(fullTreeData.value, expandLevel);
203
368
  }
204
369
  }
205
370
 
206
371
  hasSetDefaultExpand.value = true;
207
372
  }
208
373
 
209
- // 新增:构建节点Map,便于快速查找
210
- function buildNodeCodeMap(nodes) {
211
- const map = new Map();
374
+ // 修改:懒加载模式下的展开逻辑
375
+ function setLazyModeExpandedNodes() {
376
+ if (!fullTreeData.value || fullTreeData.value.length === 0) {
377
+ return;
378
+ }
212
379
 
213
- function traverse(nodeList) {
214
- nodeList.forEach(node => {
215
- if (node.code) {
216
- map.set(node.code, node);
217
- }
380
+ // 获取所有非叶子节点(最后一层不展开)
381
+ const nonLeafKeys = [];
218
382
 
219
- if (node.children && node.children.length > 0) {
220
- traverse(node.children);
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
+ }
221
397
  }
222
398
  });
223
399
  }
224
400
 
225
- traverse(nodes);
226
- return map;
227
- }
401
+ traverse(fullTreeData.value);
402
+
403
+ defaultExpandedKeys.value = nonLeafKeys;
404
+ hasSetDefaultExpand.value = true;
228
405
 
229
- // 新增:复制节点(深拷贝)
230
- function cloneNode(node) {
231
- const cloned = { ...node };
232
- cloned.children = [];
233
- return cloned;
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
+ });
234
419
  }
235
420
 
236
421
  // 搜索(查询条件调用)
237
422
  function search(m) {
238
- const hasValidSearchConditions = checkHasValidSearchConditions(m);
423
+ const hasValidSearchConditions = Tree.checkHasValidSearchConditions(m);
239
424
 
240
425
  isSearching.value = hasValidSearchConditions;
241
426
  screenPara.value = m;
242
427
 
428
+
429
+
243
430
  if (!lazy.value) {
244
431
  filterFullTreeData(screenPara.value);
245
432
  return;
@@ -264,111 +451,19 @@ function search(m) {
264
451
  }, 200);
265
452
  }
266
453
  }
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;
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;
313
459
  }
314
- }
460
+ });
315
461
 
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
- });
462
+ // 清空默认展开的key
463
+ defaultExpandedKeys.value = [];
464
+ hasSetDefaultExpand.value = false;
465
+ }
370
466
  }
371
-
372
467
  // 修改:构建完整的过滤树,保持原始层级结构
373
468
  function buildFilteredTreeWithOriginalHierarchy(matchedNodes) {
374
469
  if (!matchedNodes || matchedNodes.length === 0) {
@@ -381,7 +476,7 @@ function buildFilteredTreeWithOriginalHierarchy(matchedNodes) {
381
476
  // 第一步:创建所有匹配节点的副本
382
477
  matchedNodes.forEach(node => {
383
478
  if (!resultMap.has(node.code)) {
384
- const clonedNode = cloneNode(node);
479
+ const clonedNode = Tree.cloneNode(node);
385
480
  resultMap.set(node.code, clonedNode);
386
481
  }
387
482
  });
@@ -424,12 +519,11 @@ function buildFilteredTreeWithOriginalHierarchy(matchedNodes) {
424
519
  if (!originalNode) continue;
425
520
 
426
521
  // 创建节点副本
427
- const clonedNode = cloneNode(originalNode);
522
+ const clonedNode = Tree.cloneNode(originalNode);
428
523
  resultMap.set(currentCode, clonedNode);
429
524
 
430
525
  // 如果是根节点,添加到根节点数组
431
526
  if (i === 0) {
432
- // 检查是否已经存在于根节点中
433
527
  const existingRoot = rootNodes.find(n => n.code === currentCode);
434
528
  if (!existingRoot) {
435
529
  rootNodes.push(clonedNode);
@@ -442,7 +536,6 @@ function buildFilteredTreeWithOriginalHierarchy(matchedNodes) {
442
536
  const parentNode = resultMap.get(parentCode);
443
537
 
444
538
  if (parentNode) {
445
- // 检查是否已经包含该子节点
446
539
  const existingChild = parentNode.children.find(child => child.code === currentCode);
447
540
  if (!existingChild) {
448
541
  parentNode.children.push(clonedNode);
@@ -458,7 +551,6 @@ function buildFilteredTreeWithOriginalHierarchy(matchedNodes) {
458
551
  const currentNode = resultMap.get(matchedNode.code);
459
552
 
460
553
  if (parentNode && currentNode) {
461
- // 检查是否已经包含该子节点
462
554
  const existingChild = parentNode.children.find(child => child.code === matchedNode.code);
463
555
  if (!existingChild) {
464
556
  parentNode.children.push(currentNode);
@@ -469,11 +561,10 @@ function buildFilteredTreeWithOriginalHierarchy(matchedNodes) {
469
561
 
470
562
  // 第三步:清理根节点,确保它们没有重复的父节点
471
563
  const finalRootNodes = [];
472
- const usedNodes = new Set();
473
564
 
474
565
  function isNodeUsedInTree(node) {
475
566
  for (const root of rootNodes) {
476
- if (isNodeInChildren(root, node.code)) {
567
+ if (Tree.isNodeInChildren(root, node.code)) {
477
568
  return true;
478
569
  }
479
570
  }
@@ -481,7 +572,6 @@ function buildFilteredTreeWithOriginalHierarchy(matchedNodes) {
481
572
  }
482
573
 
483
574
  rootNodes.forEach(rootNode => {
484
- // 如果这个节点已经是其他节点的子节点,则不作为根节点
485
575
  if (!isNodeUsedInTree(rootNode)) {
486
576
  finalRootNodes.push(rootNode);
487
577
  }
@@ -511,29 +601,6 @@ function findParentCodeInFullTree(nodeCode) {
511
601
  return findParent(fullTreeData.value, nodeCode);
512
602
  }
513
603
 
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
604
  // 修改:过滤全量树数据,保持原始层级
538
605
  function filterFullTreeData(filter) {
539
606
  if (!fullTreeData.value.length) {
@@ -550,7 +617,7 @@ function filterFullTreeData(filter) {
550
617
  searchConditions = filter.fields;
551
618
  }
552
619
 
553
- const validConditions = filterValidConditions(searchConditions);
620
+ const validConditions = Tree.filterValidConditions(searchConditions);
554
621
 
555
622
  if (!validConditions || validConditions.length === 0) {
556
623
  refTree.value.store.setData(fullTreeData.value);
@@ -559,7 +626,6 @@ function filterFullTreeData(filter) {
559
626
  hasSetDefaultExpand.value = false;
560
627
  defaultExpandedKeys.value = [];
561
628
 
562
- // 先收起所有节点
563
629
  if (refTree.value && refTree.value.store) {
564
630
  Object.values(refTree.value.store.nodesMap).forEach((node: any) => {
565
631
  if (node) {
@@ -575,7 +641,7 @@ function filterFullTreeData(filter) {
575
641
  // 递归查找所有匹配的节点
576
642
  const findMatchedNodes = (nodes, result = []) => {
577
643
  nodes.forEach(node => {
578
- const nodeMatches = nodeMatchesAllConditions(node, validConditions);
644
+ const nodeMatches = Tree.nodeMatchesAllConditions(node, validConditions);
579
645
 
580
646
  if (nodeMatches) {
581
647
  result.push(node);
@@ -590,18 +656,15 @@ function filterFullTreeData(filter) {
590
656
 
591
657
  const matchedNodes = findMatchedNodes(fullTreeData.value);
592
658
 
593
- // 构建保持原始层级的过滤树
594
659
  let filteredData = [];
595
660
  if (matchedNodes.length > 0) {
596
661
  filteredData = buildFilteredTreeWithOriginalHierarchy(matchedNodes);
597
662
  }
598
663
 
599
- // 设置过滤后的数据
600
664
  refTree.value.store.setData(filteredData);
601
665
 
602
- // 搜索状态时展开所有节点
603
666
  if (filteredData.length > 0 && isSearching.value) {
604
- const allKeys = getAllNodeKeys(filteredData);
667
+ const allKeys = Tree.getAllNodeKeys(filteredData);
605
668
  defaultExpandedKeys.value = allKeys;
606
669
 
607
670
  nextTick(() => {
@@ -614,7 +677,6 @@ function filterFullTreeData(filter) {
614
677
  }
615
678
  });
616
679
 
617
- // 默认选中第一个可选节点
618
680
  const findFirstSelectableNode = (nodes) => {
619
681
  for (const node of nodes) {
620
682
  if (!node.children || node.children.length === 0) {
@@ -630,9 +692,10 @@ function filterFullTreeData(filter) {
630
692
  if (firstNode && firstNode.code) {
631
693
  nextTick(() => {
632
694
  refTree.value.setCurrentKey(firstNode.code);
633
- handleNodeClick(firstNode, true);
695
+ handleNodeClick(firstNode);
634
696
  });
635
697
  }
698
+
636
699
  }
637
700
 
638
701
  } catch (error) {
@@ -642,74 +705,168 @@ function filterFullTreeData(filter) {
642
705
  }
643
706
  }
644
707
 
645
- // 修改:加载节点,保存节点Map
646
- function loadNode(node, resolve) {
708
+ // 获取节点的allowedRoutes
709
+ function getAllowedRoutesForNode(data) {
710
+ if (!model.value || !model.value.actionRouter) return [];
647
711
 
648
- if (!lazy.value) {
649
- const nodeData = node.data;
650
- resolve(nodeData.children)
712
+ return model.value.actionRouter.filter(v => {
713
+ const val = data[v.rightField];
714
+ const hit = val == '1' || val === undefined || val === null;
715
+ return hit;
716
+ });
717
+ }
718
+
719
+ // 修改:加载节点,保存节点Map
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);
651
738
  return;
652
739
  }
653
740
 
654
- if (node.level === 0) {
741
+ // 懒加载模式:第一次加载或需要加载子节点
742
+ if (lazy.value && node.level === 0) {
655
743
  loading.value = true;
656
- rootNode.value = node
744
+ rootNode.value = node;
657
745
  rootResolve.value = resolve;
658
-
746
+
659
747
  return SearchTree(screenPara.value || undefined).then(data => {
660
748
  loading.value = false;
749
+ // 重置前折叠所有节点
750
+ collapseAllNodes();
661
751
 
752
+ let lazyMode = lazy.value;
753
+ if (!isLazyInitialized.value) {
754
+ lazyMode = false;
755
+ }
662
756
 
663
- const { tree, hasAnyLeaf } = buildDeptTreeByField(data.rows, {
757
+ const { tree, hasAnyLeaf } = Tree.buildDeptTreeByField(data.rows, {
664
758
  pathKey: 'path',
665
- separator: '.'
759
+ separator: '.',
760
+ lazyMode: lazyMode,
666
761
  });
667
762
 
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
- });
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);
678
771
  }
679
772
  }
680
- else {
681
- resolve(data.rows);
682
- }
683
- lazy.value = !hasAnyLeaf;
684
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
+ }
685
787
  load(data, true);
788
+
686
789
  }).catch(error => {
687
790
  loading.value = false;
688
791
  console.error('加载树数据失败:', error);
689
792
  });
690
793
  }
691
794
 
692
- if (node.level >= 1 && lazy.value) {
693
- let fields = model.value.searchData("code", node.data.code, 9, 3);
694
- let filter = {
695
- "searchData": {
696
- ...(screenPara.value?.searchData || {}),
697
- fields: [
698
- ...fields.fields,
699
- ...(screenPara.value?.searchData?.fields || [])
700
- ]
701
- }
702
- };
795
+ // 非懒加载模式或懒加载但已有数据
796
+ if (!lazy.value && node.data && node.data.children) {
797
+ resolve(node.data.children);
798
+ return;
799
+ }
703
800
 
704
- return SearchTree(filter).then(data => {
705
- loading.value = false;
706
- resolve(data.rows);
707
- load(data, false);
708
- }).catch(error => {
709
- loading.value = false;
710
- console.error('加载子节点失败:', error);
711
- });
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;
712
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
+ }
713
870
  }
714
871
 
715
872
  // 加载数据
@@ -724,13 +881,26 @@ async function load(data, flagRootNode) {
724
881
 
725
882
  if (flagRootNode) {
726
883
  if (model.value.rows && model.value.rows[0]) {
727
- handleNodeClick(model.value.rows[0], true);
884
+ handleNodeClick(model.value.rows[0]);
728
885
  }
729
886
  nextTick(() => {
730
887
  if (model.value.rows && model.value.rows[0] && model.value.rows[0].code) {
731
888
  refTree.value.setCurrentKey(model.value.rows[0].code)
732
889
  }
733
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
+ }
734
904
  }
735
905
  }
736
906
 
@@ -750,6 +920,10 @@ function SearchTree(m) {
750
920
  // 重置树到全量数据
751
921
  function resetTree() {
752
922
  isSearching.value = false;
923
+
924
+ // 重置前折叠所有节点
925
+ collapseAllNodes();
926
+
753
927
  if (fullTreeData.value.length > 0) {
754
928
  refTree.value.store.setData(fullTreeData.value);
755
929
  hasSetDefaultExpand.value = false;
@@ -757,16 +931,8 @@ function resetTree() {
757
931
 
758
932
  if (fullTreeData.value[0] && fullTreeData.value[0].code) {
759
933
  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
934
  refTree.value.setCurrentKey(fullTreeData.value[0].code);
769
- handleNodeClick(fullTreeData.value[0], true);
935
+ handleNodeClick(fullTreeData.value[0]);
770
936
  setDefaultExpandedNodes();
771
937
  });
772
938
  }
@@ -778,38 +944,78 @@ function clearSearch() {
778
944
  screenPara.value = {};
779
945
  isSearching.value = false;
780
946
 
781
- defaultExpandedKeys.value = [];
782
- hasSetDefaultExpand.value = false;
947
+ // 清除前折叠所有节点
948
+ collapseAllNodes();
783
949
 
784
950
  if (fullTreeData.value.length > 0) {
785
951
  refTree.value.store.setData(fullTreeData.value);
786
952
 
787
953
  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
954
  setDefaultExpandedNodes();
797
955
 
798
956
  if (fullTreeData.value[0] && fullTreeData.value[0].code) {
799
957
  refTree.value.setCurrentKey(fullTreeData.value[0].code);
800
- handleNodeClick(fullTreeData.value[0], true);
958
+ handleNodeClick(fullTreeData.value[0]);
801
959
  }
802
960
  });
803
961
  }
804
962
  }
805
963
 
806
- //节点点击事件
807
- function handleNodeClick(data, formType) {
808
-
964
+ // 修改:节点点击事件,支持自动展开和懒加载
965
+ function handleNodeClick(data) {
966
+
809
967
  closeMenu();
810
- if (data && formType) {
811
- 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
+ }
812
1017
  }
1018
+
813
1019
  if (LastClickNode.value != data) {
814
1020
  LastClickNode.value = data;
815
1021
  let rowRouter = model.value.rowSelectRouter;
@@ -833,40 +1039,10 @@ function handleNodeClick(data, formType) {
833
1039
  }
834
1040
  }
835
1041
 
836
- //右键菜单
837
- function rightClick(event, object, Node) {
838
- nodeRef.value = treeNodeRefMap.value[`node_Ref_${object.code}`]
839
-
840
- currentData.value = object
841
- currentNode.value = Node
842
- let actionIndex = 0;
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
-
855
- if (menuVisible.value) {
856
- document.addEventListener('click', closeMenu)
857
- }
858
- }
859
-
860
- //关闭菜单
861
- function closeMenu() {
862
- menuVisible.value = false;
863
- nodeRef.value = null
864
- currentData.value = { ...currentData.value, code: "" }
865
- document.removeEventListener('click', closeMenu)
866
- }
867
1042
 
868
1043
  // 菜单路由
869
1044
  function routerClickHandler(field) {
1045
+ isManualClose.value = true;
870
1046
  var submitData = {};
871
1047
  field.submitFormField.forEach(v => {
872
1048
  submitData[v] = currentData.value[v];
@@ -893,29 +1069,232 @@ function routerClickHandler(field) {
893
1069
  })
894
1070
  }
895
1071
 
896
- //删除树节点
897
- function removeNode(newData) {
898
- getNextClickNode(currentNode.value)
899
- refTree.value.remove(currentNode.value);
900
-
901
- }
902
-
903
1072
  //新增树节点
904
1073
  function addNode(newData) {
905
- refTree.value.append(newData, currentNode.value)
906
- handleNodeClick(newData, true)
1074
+ if (!lazy.value) {
1075
+ // 全量数据模式:手动更新全量数据和树显示
1076
+ const parentCode = currentData.value.code;
1077
+ const parentPath = currentData.value.path || '';
1078
+
1079
+ // 设置新节点的属性
1080
+ const newNode = {
1081
+ ...newData,
1082
+ // 如果后端没有返回path,需要构建
1083
+ path: parentPath ? `${parentPath}.${newData.code}` : newData.code,
1084
+ // 确保children存在
1085
+ children: newData.children || [],
1086
+ // 标识是否为叶子节点
1087
+ isLeaf: true
1088
+ };
1089
+
1090
+ // 在全量数据中找到父节点并添加
1091
+ const findAndAdd = (nodes, parentCode) => {
1092
+ for (let i = 0; i < nodes.length; i++) {
1093
+ if (nodes[i].code === parentCode) {
1094
+ if (!nodes[i].children) {
1095
+ nodes[i].children = [];
1096
+ }
1097
+ nodes[i].children.push(newNode);
1098
+ nodes[i].isLeaf = false; // 父节点不再是叶子节点
1099
+ return true;
1100
+ }
1101
+
1102
+ if (nodes[i].children && nodes[i].children.length > 0) {
1103
+ if (findAndAdd(nodes[i].children, parentCode)) {
1104
+ return true;
1105
+ }
1106
+ }
1107
+ }
1108
+ return false;
1109
+ };
1110
+
1111
+ // 更新全量数据
1112
+ findAndAdd(fullTreeData.value, parentCode);
1113
+
1114
+ // 更新nodeCodeMap
1115
+ nodeCodeMap.value.set(newNode.code, newNode);
1116
+
1117
+ // 如果是搜索状态,需要同时更新当前显示的数据
1118
+ if (isSearching.value && screenPara.value) {
1119
+ filterFullTreeData(screenPara.value);
1120
+ } else {
1121
+ // 非搜索状态,直接重新设置数据
1122
+ refTree.value.store.setData([...fullTreeData.value]);
1123
+ }
1124
+
1125
+ // 刷新树显示
1126
+ nextTick(() => {
1127
+ // 展开父节点
1128
+ const parentNode = refTree.value.getNode(parentCode);
1129
+ if (parentNode && !parentNode.expanded) {
1130
+ refTree.value.store.setCurrentNode(parentNode);
1131
+ parentNode.expanded = true;
1132
+ }
1133
+
1134
+ // 设置新节点为当前选中
1135
+ handleNodeClick(newNode);
1136
+ });
1137
+ } else {
1138
+ // 懒加载模式:使用原有方法
1139
+ refTree.value.append(newData, currentNode.value);
1140
+ handleNodeClick(newData);
1141
+ }
907
1142
  }
908
1143
 
909
1144
  //修改树节点
910
1145
  function updateNode(newData) {
911
- for (let key in newData) {
912
- if (typeof newData[key] === 'object') {
913
- currentData.value[key] = { ...currentData.value[key], ...newData[key] }
1146
+ if (!lazy.value) {
1147
+ // 全量数据模式:更新全量数据
1148
+ const updateInTree = (nodes, nodeCode) => {
1149
+ for (let i = 0; i < nodes.length; i++) {
1150
+ if (nodes[i].code === nodeCode) {
1151
+ // 合并数据
1152
+ nodes[i] = { ...nodes[i], ...newData };
1153
+ return true;
1154
+ }
1155
+
1156
+ if (nodes[i].children && nodes[i].children.length > 0) {
1157
+ if (updateInTree(nodes[i].children, nodeCode)) {
1158
+ return true;
1159
+ }
1160
+ }
1161
+ }
1162
+ return false;
1163
+ };
1164
+
1165
+ // 更新全量数据
1166
+ updateInTree(fullTreeData.value, currentData.value.code);
1167
+
1168
+ // 更新nodeCodeMap
1169
+ nodeCodeMap.value.set(currentData.value.code, {
1170
+ ...nodeCodeMap.value.get(currentData.value.code),
1171
+ ...newData
1172
+ });
1173
+
1174
+ // 如果是搜索状态,重新过滤显示
1175
+ if (isSearching.value && screenPara.value) {
1176
+ filterFullTreeData(screenPara.value);
914
1177
  } else {
915
- currentData.value[key] = newData[key];
1178
+ // 非搜索状态,直接重新设置数据
1179
+ refTree.value.store.setData([...fullTreeData.value]);
916
1180
  }
1181
+
1182
+ // 刷新显示并保持选中状态
1183
+ nextTick(() => {
1184
+ refTree.value.setCurrentKey(currentData.value.code);
1185
+ handleNodeClick({ ...currentData.value, ...newData });
1186
+ });
1187
+ } else {
1188
+ // 懒加载模式:使用原有方法
1189
+ for (let key in newData) {
1190
+ if (typeof newData[key] === 'object') {
1191
+ currentData.value[key] = { ...currentData.value[key], ...newData[key] }
1192
+ } else {
1193
+ currentData.value[key] = newData[key];
1194
+ }
1195
+ }
1196
+ handleNodeClick(newData);
917
1197
  }
918
- handleNodeClick(newData, true)
1198
+ }
1199
+
1200
+ //删除树节点
1201
+ function removeNode(newData) {
1202
+ if (!lazy.value) {
1203
+ // 全量数据模式:从全量数据中删除
1204
+ let parentNode = null;
1205
+ let parentCode = null;
1206
+
1207
+ const removeFromTree = (nodes, nodeCode) => {
1208
+ for (let i = 0; i < nodes.length; i++) {
1209
+ if (nodes[i].code === nodeCode) {
1210
+ // 记录父节点信息(如果是当前循环的节点)
1211
+ parentCode = findParentCodeInFullTree(nodeCode);
1212
+ if (parentCode) {
1213
+ const parent = findNodeByCode(fullTreeData.value, parentCode);
1214
+ if (parent) {
1215
+ parentNode = parent;
1216
+ }
1217
+ }
1218
+
1219
+ // 删除节点
1220
+ nodes.splice(i, 1);
1221
+ nodeCodeMap.value.delete(nodeCode);
1222
+ return true;
1223
+ }
1224
+
1225
+ if (nodes[i].children && nodes[i].children.length > 0) {
1226
+ if (removeFromTree(nodes[i].children, nodeCode)) {
1227
+ return true;
1228
+ }
1229
+ }
1230
+ }
1231
+ return false;
1232
+ };
1233
+
1234
+ // 从全量数据中删除
1235
+ removeFromTree(fullTreeData.value, currentData.value.code);
1236
+
1237
+ // 更新父节点的 isLeaf 状态
1238
+ if (parentCode && parentNode) {
1239
+ // 重新检查父节点是否还有子节点
1240
+ const hasChildren = parentNode.children && parentNode.children.length > 0;
1241
+ parentNode.isLeaf = !hasChildren;
1242
+
1243
+ // 同步更新 nodeCodeMap
1244
+ if (nodeCodeMap.value.has(parentCode)) {
1245
+ const updatedParent = nodeCodeMap.value.get(parentCode);
1246
+ updatedParent.isLeaf = !hasChildren;
1247
+ nodeCodeMap.value.set(parentCode, updatedParent);
1248
+ }
1249
+ } else {
1250
+ // 如果是根节点被删除,不需要更新 isLeaf
1251
+ }
1252
+
1253
+ // 如果是搜索状态,重新过滤显示
1254
+ if (isSearching.value && screenPara.value) {
1255
+ filterFullTreeData(screenPara.value);
1256
+ } else {
1257
+ // 非搜索状态,直接重新设置数据
1258
+ refTree.value.store.setData([...fullTreeData.value]);
1259
+ }
1260
+
1261
+ // 查找下一个可点击的节点
1262
+ getNextClickNode();
1263
+ } else {
1264
+ // 懒加载模式:使用原有方法
1265
+ getNextClickNode();
1266
+ refTree.value.remove(currentNode.value);
1267
+ }
1268
+ }
1269
+
1270
+ // 根据 code 查找节点
1271
+ function findNodeByCode(nodes, targetCode) {
1272
+ for (const node of nodes) {
1273
+ if (node.code === targetCode) {
1274
+ return node;
1275
+ }
1276
+
1277
+ if (node.children && node.children.length > 0) {
1278
+ const found = findNodeByCode(node.children, targetCode);
1279
+ if (found) return found;
1280
+ }
1281
+ }
1282
+ return null;
1283
+ }
1284
+
1285
+ // 查找节点的父节点(返回父节点本身,不只是code)
1286
+ function findParentNodeInFullTree(nodes, targetCode, parent = null) {
1287
+ for (const node of nodes) {
1288
+ if (node.code === targetCode) {
1289
+ return parent;
1290
+ }
1291
+
1292
+ if (node.children && node.children.length > 0) {
1293
+ const found = findParentNodeInFullTree(node.children, targetCode, node);
1294
+ if (found) return found;
1295
+ }
1296
+ }
1297
+ return null;
919
1298
  }
920
1299
 
921
1300
  function getNextClickNode() {
@@ -928,7 +1307,7 @@ function getNextClickNode() {
928
1307
  } else if (node?.parent) {
929
1308
  data = node.parent.data;
930
1309
  }
931
- handleNodeClick(data, true)
1310
+ handleNodeClick(data)
932
1311
  }
933
1312
 
934
1313
  /** 过滤节点(全量数据模式下使用) */
@@ -937,98 +1316,21 @@ function filterNode(value, data) {
937
1316
  return data.name.toLowerCase().includes(value.toLowerCase())
938
1317
  }
939
1318
 
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 : pathKey;
950
-
951
- // 按路径排序,确保父节点在前
952
- const sortedList = [...flatList].sort((a, b) => {
953
- const aPath = a[pathKey] || '';
954
- const bPath = b[pathKey] || '';
955
- return aPath.length - bPath.length || aPath.localeCompare(bPath);
956
- });
957
-
958
- const pathMap = new Map(); // path -> node
959
- const root = [];
960
-
961
- for (const item of sortedList) {
962
- const path = item[pathKey];
963
- if (!path) continue;
964
-
965
- const node = {
966
- ...item,
967
- children: [],
968
- isLeaf: true // 默认为叶子节点,后续会更新
969
- };
970
-
971
- // 添加到路径映射
972
- pathMap.set(path, node);
973
-
974
- // 查找父节点
975
- const lastDotIndex = path.lastIndexOf(separator);
976
- if (lastDotIndex > -1) {
977
- const parentPath = path.substring(0, lastDotIndex);
978
- const parentNode = pathMap.get(parentPath);
979
-
980
- if (parentNode) {
981
- parentNode.children.push(node);
982
- parentNode.isLeaf = false; // 有子节点,所以不是叶子
983
- continue;
984
- }
985
- }
986
-
987
- // 没有找到父节点,或者没有父路径,则作为根节点
988
- root.push(node);
989
- }
990
-
991
- // 排序:按 sortKey 或 path 排序
992
- const sortTree = (nodes) => {
993
- nodes.sort((a, b) => {
994
- const aVal = a[actualSortKey] || a[pathKey] || '';
995
- const bVal = b[actualSortKey] || b[pathKey] || '';
996
- return String(aVal).localeCompare(String(bVal));
997
- });
998
-
999
- nodes.forEach(node => {
1000
- if (node.children && node.children.length > 0) {
1001
- sortTree(node.children);
1002
- }
1003
- });
1004
- };
1005
-
1006
- sortTree(root);
1007
-
1008
- const hasAnyLeaf = root.some(n => !n.isLeaf);
1009
- return { tree: root, hasAnyLeaf };
1010
- }
1011
-
1012
1319
  function findSearchFieldValue(searchFields, targetKey, options = {}) {
1013
1320
  const {
1014
- keyFields = ['fieldName1', 'groupName'], // 要查找的key字段列表
1015
- valueField = 'searchValue1', // 要返回值的字段
1321
+ keyFields = ['fieldName1', 'groupName'],
1322
+ valueField = 'searchValue1',
1016
1323
  defaultValue = 2
1017
1324
  } = options;
1018
1325
 
1019
- // 检查数据结构
1020
1326
  if (!searchFields?.searchData?.fields?.length) {
1021
1327
  return defaultValue;
1022
1328
  }
1023
1329
 
1024
- // 遍历fields数组
1025
1330
  for (const field of searchFields.searchData.fields) {
1026
- // 检查所有可能的key字段
1027
1331
  for (const keyField of keyFields) {
1028
1332
  if (field[keyField] === targetKey) {
1029
- // 找到了匹配的key,返回对应的值
1030
1333
  const value = field[valueField];
1031
- // 如果值为undefined、null或空字符串,返回默认值
1032
1334
  return value !== undefined && value !== null && value !== ''
1033
1335
  ? value
1034
1336
  : defaultValue;
@@ -1039,8 +1341,6 @@ function findSearchFieldValue(searchFields, targetKey, options = {}) {
1039
1341
  return defaultValue;
1040
1342
  }
1041
1343
 
1042
-
1043
-
1044
1344
  // 暴露方法
1045
1345
  defineExpose({
1046
1346
  search,
@@ -1099,6 +1399,8 @@ defineExpose({
1099
1399
  .node_content {
1100
1400
  display: flex;
1101
1401
  align-items: center;
1402
+ position: relative;
1403
+ width: 100%;
1102
1404
  }
1103
1405
 
1104
1406
  .node-icon {
@@ -1109,20 +1411,140 @@ defineExpose({
1109
1411
  .box-menu {
1110
1412
  z-index: 1000;
1111
1413
  background-color: #fff;
1112
- padding: 10px;
1414
+ // box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
1415
+ border-radius: 4px;
1416
+ padding: 4px 0;
1113
1417
 
1114
1418
  div {
1115
1419
  cursor: pointer;
1420
+
1116
1421
  }
1117
1422
  }
1118
1423
 
1424
+ .box-menu.box-menu-icons {
1425
+ display: flex;
1426
+ flex-wrap: nowrap;
1427
+ padding: 8px 0px;
1428
+ gap: 0px;
1429
+ line-height: 15px;
1430
+ background: transparent !important;
1431
+
1432
+ }
1433
+
1434
+ .box-menu-icons .opertion {
1435
+ width: 16px;
1436
+ height: 16px;
1437
+ padding: 0 !important;
1438
+ display: flex;
1439
+ align-items: center;
1440
+ justify-content: center;
1441
+ border-radius: 4px;
1442
+ margin: 0 2px !important;
1443
+ }
1444
+
1445
+ .box-menu-icons .opertion:hover {
1446
+ background-color: #f5f7fa;
1447
+ transform: translateY(-1px);
1448
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
1449
+ }
1450
+
1451
+ .box-menu-icons .opertion img {
1452
+ width: 20px;
1453
+ height: 20px;
1454
+ margin: 0;
1455
+ }
1456
+
1457
+ .box-menu-icons .opertion div {
1458
+ display: flex;
1459
+ align-items: center;
1460
+ justify-content: center;
1461
+ width: 100%;
1462
+ height: 100%;
1463
+ }
1464
+
1119
1465
  .box-menu .opertion {
1466
+ display: flex;
1467
+ align-items: center;
1468
+ padding: 4px;
1120
1469
  cursor: pointer;
1121
- line-height: 30px;
1470
+ font-size: 14px;
1471
+ color: #606266;
1472
+ transition: all 0.2s ease;
1473
+ line-height: 20px;
1474
+ margin: 0 4px;
1475
+ border-radius: 6px;
1476
+ position: relative;
1477
+ overflow: hidden;
1478
+
1479
+
1122
1480
  }
1123
1481
 
1124
1482
  .box-menu .opertion:hover {
1125
1483
  color: var(--btnHoverRed);
1126
- background-color: var(--el-fill-color-light);
1484
+ background-color: #f5f7fa;
1485
+ transform: translateY(-1px);
1486
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
1487
+ }
1488
+
1489
+ .box-menu .opertion:active {
1490
+ transform: translateY(0);
1491
+ box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
1492
+ }
1493
+
1494
+ .box-menu .opertion:not(:last-child)::after {
1495
+ content: '';
1496
+ position: absolute;
1497
+ bottom: 0;
1498
+ left: 16px;
1499
+ right: 16px;
1500
+ height: 1px;
1501
+ background-color: #f0f0f0;
1502
+ }
1503
+
1504
+ .box-menu .opertion:last-child {
1505
+ margin-bottom: 0;
1506
+ }
1507
+
1508
+ .box-menu .opertion img {
1509
+ width: 16px;
1510
+ height: 16px;
1511
+ margin-right: 8px;
1512
+ vertical-align: middle;
1513
+ }
1514
+
1515
+ .box-menu .opertion div {
1516
+ display: flex;
1517
+ align-items: center;
1518
+ width: 100%;
1519
+ justify-content: center; // 垂直居中
1520
+ text-align: center;
1521
+ }
1522
+
1523
+ /* 菜单激活状态 */
1524
+ .box-menu .opertion.active {
1525
+ background-color: #ecf5ff;
1526
+ color: var(--centalineBlue);
1527
+ }
1528
+
1529
+ /* 悬停模式下的节点样式 */
1530
+ .node_content:hover {
1531
+ background-color: #f5f7fa;
1532
+ }
1533
+
1534
+ .popper-transparent {
1535
+ background: transparent !important;
1536
+ border: none !important;
1537
+ box-shadow: none !important;
1538
+ padding: 0 !important;
1539
+
1540
+ }
1541
+
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;
1127
1549
  }
1128
1550
  </style>