centaline-data-driven-v3 0.1.44 → 0.1.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/centaline-data-driven-v3.umd.js +114 -114
- package/package.json +1 -1
- package/src/components/web/Label.vue +1 -0
- package/src/components/web/TextBox.vue +1 -1
- package/src/components/web/Tree/Tree.vue +499 -352
- package/src/loader/src/Tree.js +304 -33
- package/src/main.js +3 -3
- package/src/views/Form.vue +2 -2
|
@@ -2,23 +2,28 @@
|
|
|
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
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="
|
|
5
|
+
:filter-node-method="filterNode" lazy :load="loadNode" ref="refTree" @node-contextmenu="handleContextMenu"
|
|
6
6
|
node-key="code" current-node-key="currentNodeKey" :default-expanded-keys="defaultExpandedKeys">
|
|
7
7
|
<template #default="{ node, data }">
|
|
8
|
-
<span class="node_content">
|
|
8
|
+
<span class="node_content" @mouseenter="handleMouseEnter(data, node)">
|
|
9
9
|
<el-icon class="node-icon" :class="{ 'is-leaf': node.isLeaf }">
|
|
10
10
|
<Document v-if="node.isLeaf" :size="14" />
|
|
11
11
|
<Folder v-else-if="!node.expanded" :size="14" />
|
|
12
12
|
<FolderOpened v-else :size="14" />
|
|
13
13
|
</el-icon>
|
|
14
|
-
<span style="" :ref="(el: refItem) => handleSetInputMap(el, data)">
|
|
15
|
-
data.name }}
|
|
14
|
+
<span style="" :ref="(el: refItem) => handleSetInputMap(el, data)">
|
|
15
|
+
{{ data.name }}
|
|
16
|
+
</span>
|
|
16
17
|
<span @click.stop>
|
|
17
|
-
<el-popover placement="right" :virtual-ref="nodeRef" virtual-triggering
|
|
18
|
-
:
|
|
19
|
-
<div class="box-menu">
|
|
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' : ''">
|
|
20
|
+
<div class="box-menu" :class="{ 'box-menu-icons': allIconsMode }">
|
|
20
21
|
<template v-for="(item, index) in allowedRoutes">
|
|
21
|
-
<
|
|
22
|
+
<template v-if="allIconsMode">
|
|
23
|
+
<img class="opertion" v-if="item.imgUrl" :src="item.imgUrl" :alt="item.controlLabel || ''"
|
|
24
|
+
:title="item.controlLabel" @click="routerClickHandler(item)" />
|
|
25
|
+
</template>
|
|
26
|
+
<div class="opertion" @click="routerClickHandler(item)" v-else>
|
|
22
27
|
<div>
|
|
23
28
|
<img v-if="item.imgUrl" :src="item.imgUrl" alt="" />
|
|
24
29
|
{{ item.controlLabel }}
|
|
@@ -90,6 +95,13 @@ const retryCount = ref(0);
|
|
|
90
95
|
const treeNodeRefMap = ref({});
|
|
91
96
|
const allowedRoutes = ref([]);
|
|
92
97
|
|
|
98
|
+
// 新增:鼠标悬停相关状态
|
|
99
|
+
const hoverTimer = ref(null)
|
|
100
|
+
const hoverNodeCode = ref('')
|
|
101
|
+
const isHoverMode = ref(false) // 是否是悬停模式
|
|
102
|
+
const isManualClose = ref(false) // 是否是手动关闭
|
|
103
|
+
const allIconsMode = ref(false) // 是否全是图标模式
|
|
104
|
+
|
|
93
105
|
// 默认展开的节点key数组
|
|
94
106
|
const defaultExpandedKeys = ref([])
|
|
95
107
|
// 是否已设置默认展开
|
|
@@ -104,6 +116,7 @@ const fullTreeData = ref([])
|
|
|
104
116
|
const nodeCodeMap = ref(new Map())
|
|
105
117
|
|
|
106
118
|
const isExpandAll = ref(false);
|
|
119
|
+
|
|
107
120
|
/** 动态设置Input Ref */
|
|
108
121
|
const handleSetInputMap = (el: refItem, item) => {
|
|
109
122
|
if (el) {
|
|
@@ -117,53 +130,136 @@ onBeforeUnmount(() => {
|
|
|
117
130
|
clearTimeout(qrtimer.value);
|
|
118
131
|
qrtimer.value = null;
|
|
119
132
|
}
|
|
133
|
+
if (hoverTimer.value) {
|
|
134
|
+
clearTimeout(hoverTimer.value);
|
|
135
|
+
hoverTimer.value = null;
|
|
136
|
+
}
|
|
120
137
|
})
|
|
121
138
|
|
|
122
|
-
|
|
123
|
-
|
|
139
|
+
// 检查allowedRoutes是否全是图标
|
|
140
|
+
function checkIfAllIcons(routes) {
|
|
141
|
+
if (!routes || routes.length === 0) return false;
|
|
142
|
+
return routes.every(item => item.imgUrl && item.imgUrl.trim() !== '');
|
|
143
|
+
}
|
|
124
144
|
|
|
125
|
-
|
|
126
|
-
|
|
145
|
+
// 显示弹出框的条件
|
|
146
|
+
function showPopover(data) {
|
|
147
|
+
if (isManualClose.value) {
|
|
148
|
+
return false;
|
|
127
149
|
}
|
|
128
150
|
|
|
129
|
-
const
|
|
130
|
-
|
|
151
|
+
const isCurrentNode = data.code === currentData.value.code;
|
|
152
|
+
const hasRoutes = allowedRoutes.value.length > 0;
|
|
131
153
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
154
|
+
if (isHoverMode.value) {
|
|
155
|
+
// 悬停模式:当前节点且有路由就显示
|
|
156
|
+
return isCurrentNode && hasRoutes && menuVisible.value;
|
|
157
|
+
} else {
|
|
158
|
+
// 右键模式:右键触发才显示
|
|
159
|
+
return isCurrentNode && hasRoutes && menuVisible.value;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
136
162
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
163
|
+
// 鼠标进入节点
|
|
164
|
+
function handleMouseEnter(data, node) {
|
|
165
|
+
if (isManualClose.value) {
|
|
166
|
+
isManualClose.value = false;
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// 检查当前节点的allowedRoutes是否全是图标
|
|
171
|
+
const routes = getAllowedRoutesForNode(data);
|
|
172
|
+
const allIcons = checkIfAllIcons(routes);
|
|
173
|
+
allIconsMode.value = allIcons;
|
|
174
|
+
if (allIcons) {
|
|
175
|
+
isHoverMode.value = true;
|
|
176
|
+
hoverNodeCode.value = data.code;
|
|
143
177
|
|
|
144
|
-
|
|
145
|
-
|
|
178
|
+
|
|
179
|
+
if (hoverNodeCode.value === data.code) {
|
|
180
|
+
showNodeMenu(data, node, true);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
146
183
|
}
|
|
147
184
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
// 右键菜单事件
|
|
188
|
+
function handleContextMenu(event, object, node) {
|
|
189
|
+
event.preventDefault();
|
|
190
|
+
|
|
191
|
+
// 检查当前节点的allowedRoutes是否全是图标
|
|
192
|
+
const routes = getAllowedRoutesForNode(object);
|
|
193
|
+
const allIcons = checkIfAllIcons(routes);
|
|
194
|
+
|
|
195
|
+
// 如果全是图标,右键时不执行后面代码(不显示菜单)
|
|
196
|
+
if (allIcons) {
|
|
197
|
+
return;
|
|
152
198
|
}
|
|
153
199
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
200
|
+
showNodeMenu(object, node, false);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// 共用方法:显示节点菜单
|
|
204
|
+
function showNodeMenu(object, node, isHoverTrigger = false) {
|
|
205
|
+
nodeRef.value = treeNodeRefMap.value[`node_Ref_${object.code}`]
|
|
206
|
+
|
|
207
|
+
currentData.value = object
|
|
208
|
+
currentNode.value = node
|
|
209
|
+
|
|
210
|
+
allowedRoutes.value = getAllowedRoutesForNode(object);
|
|
211
|
+
|
|
158
212
|
|
|
159
|
-
if (node.children && node.children.length > 0) {
|
|
160
|
-
getAllNodeKeys(node.children, result);
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
213
|
|
|
164
|
-
|
|
214
|
+
if (allowedRoutes.value.length > 0) {
|
|
215
|
+
menuVisible.value = true;
|
|
216
|
+
|
|
217
|
+
// if (isHoverTrigger && allIcons) {
|
|
218
|
+
// // 悬停模式:自动关闭定时器
|
|
219
|
+
// setTimeout(() => {
|
|
220
|
+
// if (menuVisible.value && isHoverMode.value) {
|
|
221
|
+
// closeMenu();
|
|
222
|
+
// }
|
|
223
|
+
// }, 300); // 3秒后自动关闭
|
|
224
|
+
// }
|
|
225
|
+
|
|
226
|
+
// 添加点击事件监听
|
|
227
|
+
document.addEventListener('click', closeMenuOnClick);
|
|
228
|
+
}
|
|
165
229
|
}
|
|
166
230
|
|
|
231
|
+
// 点击关闭菜单
|
|
232
|
+
function closeMenuOnClick(e) {
|
|
233
|
+
// 检查点击是否在菜单内部
|
|
234
|
+
const menu = document.querySelector('.box-menu');
|
|
235
|
+
if (menu && !menu.contains(e.target)) {
|
|
236
|
+
closeMenu();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
//关闭菜单
|
|
241
|
+
function closeMenu() {
|
|
242
|
+
isManualClose.value = true;
|
|
243
|
+
menuVisible.value = false;
|
|
244
|
+
isHoverMode.value = false;
|
|
245
|
+
|
|
246
|
+
hoverNodeCode.value = '';
|
|
247
|
+
nodeRef.value = null;
|
|
248
|
+
// currentData.value = { ...currentData.value, code: "" };
|
|
249
|
+
|
|
250
|
+
// 移除事件监听
|
|
251
|
+
document.removeEventListener('click', closeMenuOnClick);
|
|
252
|
+
|
|
253
|
+
if (hoverTimer.value) {
|
|
254
|
+
clearTimeout(hoverTimer.value);
|
|
255
|
+
hoverTimer.value = null;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
|
|
167
263
|
// 设置默认展开节点
|
|
168
264
|
function setDefaultExpandedNodes() {
|
|
169
265
|
if (!props.expandTwoLevels || !fullTreeData.value.length) {
|
|
@@ -172,70 +268,44 @@ function setDefaultExpandedNodes() {
|
|
|
172
268
|
|
|
173
269
|
if (isSearching.value) {
|
|
174
270
|
isExpandAll.value = true;
|
|
175
|
-
const allKeys = getAllNodeKeys(fullTreeData.value);
|
|
271
|
+
const allKeys = Tree.getAllNodeKeys(fullTreeData.value);
|
|
176
272
|
defaultExpandedKeys.value = allKeys;
|
|
177
273
|
} else {
|
|
178
274
|
isExpandAll.value = false;
|
|
179
275
|
// 检查是否有全展开搜索条件
|
|
180
276
|
const expandValue = findSearchFieldValue(screenPara.value, 'expanded', {
|
|
181
|
-
caseInsensitive: true,
|
|
182
|
-
keyFields: ['fieldName1', 'groupName', 'fieldLabel']
|
|
277
|
+
caseInsensitive: true,
|
|
278
|
+
keyFields: ['fieldName1', 'groupName', 'fieldLabel']
|
|
183
279
|
});
|
|
184
280
|
|
|
185
|
-
// 如果找到expanded=all的搜索条件,设置全展开
|
|
186
281
|
if (expandValue && expandValue.toString().toLowerCase() === 'all') {
|
|
187
282
|
isExpandAll.value = true;
|
|
188
|
-
const allKeys = getAllNodeKeys(fullTreeData.value);
|
|
283
|
+
const allKeys = Tree.getAllNodeKeys(fullTreeData.value);
|
|
189
284
|
defaultExpandedKeys.value = allKeys;
|
|
190
285
|
} else {
|
|
191
|
-
|
|
192
|
-
let expandLevel = 1; // 默认展开级别为1
|
|
286
|
+
let expandLevel = 1;
|
|
193
287
|
|
|
194
288
|
if (expandValue) {
|
|
195
289
|
const numericValue = Number(expandValue);
|
|
196
290
|
if (!isNaN(numericValue) && numericValue > 0) {
|
|
197
|
-
expandLevel = Math.max(0, numericValue - 1);
|
|
198
|
-
|
|
291
|
+
expandLevel = Math.max(0, numericValue - 1);
|
|
199
292
|
}
|
|
200
293
|
}
|
|
201
294
|
|
|
202
|
-
defaultExpandedKeys.value = getFirstNLevelKeys(fullTreeData.value, expandLevel);
|
|
295
|
+
defaultExpandedKeys.value = Tree.getFirstNLevelKeys(fullTreeData.value, expandLevel);
|
|
203
296
|
}
|
|
204
297
|
}
|
|
205
298
|
|
|
206
299
|
hasSetDefaultExpand.value = true;
|
|
207
300
|
}
|
|
208
301
|
|
|
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
302
|
|
|
219
|
-
if (node.children && node.children.length > 0) {
|
|
220
|
-
traverse(node.children);
|
|
221
|
-
}
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
303
|
|
|
225
|
-
traverse(nodes);
|
|
226
|
-
return map;
|
|
227
|
-
}
|
|
228
304
|
|
|
229
|
-
// 新增:复制节点(深拷贝)
|
|
230
|
-
function cloneNode(node) {
|
|
231
|
-
const cloned = { ...node };
|
|
232
|
-
cloned.children = [];
|
|
233
|
-
return cloned;
|
|
234
|
-
}
|
|
235
305
|
|
|
236
306
|
// 搜索(查询条件调用)
|
|
237
307
|
function search(m) {
|
|
238
|
-
const hasValidSearchConditions = checkHasValidSearchConditions(m);
|
|
308
|
+
const hasValidSearchConditions = Tree.checkHasValidSearchConditions(m);
|
|
239
309
|
|
|
240
310
|
isSearching.value = hasValidSearchConditions;
|
|
241
311
|
screenPara.value = m;
|
|
@@ -265,109 +335,6 @@ function search(m) {
|
|
|
265
335
|
}
|
|
266
336
|
}
|
|
267
337
|
|
|
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
338
|
|
|
372
339
|
// 修改:构建完整的过滤树,保持原始层级结构
|
|
373
340
|
function buildFilteredTreeWithOriginalHierarchy(matchedNodes) {
|
|
@@ -381,7 +348,7 @@ function buildFilteredTreeWithOriginalHierarchy(matchedNodes) {
|
|
|
381
348
|
// 第一步:创建所有匹配节点的副本
|
|
382
349
|
matchedNodes.forEach(node => {
|
|
383
350
|
if (!resultMap.has(node.code)) {
|
|
384
|
-
const clonedNode = cloneNode(node);
|
|
351
|
+
const clonedNode = Tree.cloneNode(node);
|
|
385
352
|
resultMap.set(node.code, clonedNode);
|
|
386
353
|
}
|
|
387
354
|
});
|
|
@@ -424,12 +391,11 @@ function buildFilteredTreeWithOriginalHierarchy(matchedNodes) {
|
|
|
424
391
|
if (!originalNode) continue;
|
|
425
392
|
|
|
426
393
|
// 创建节点副本
|
|
427
|
-
const clonedNode = cloneNode(originalNode);
|
|
394
|
+
const clonedNode = Tree.cloneNode(originalNode);
|
|
428
395
|
resultMap.set(currentCode, clonedNode);
|
|
429
396
|
|
|
430
397
|
// 如果是根节点,添加到根节点数组
|
|
431
398
|
if (i === 0) {
|
|
432
|
-
// 检查是否已经存在于根节点中
|
|
433
399
|
const existingRoot = rootNodes.find(n => n.code === currentCode);
|
|
434
400
|
if (!existingRoot) {
|
|
435
401
|
rootNodes.push(clonedNode);
|
|
@@ -442,7 +408,6 @@ function buildFilteredTreeWithOriginalHierarchy(matchedNodes) {
|
|
|
442
408
|
const parentNode = resultMap.get(parentCode);
|
|
443
409
|
|
|
444
410
|
if (parentNode) {
|
|
445
|
-
// 检查是否已经包含该子节点
|
|
446
411
|
const existingChild = parentNode.children.find(child => child.code === currentCode);
|
|
447
412
|
if (!existingChild) {
|
|
448
413
|
parentNode.children.push(clonedNode);
|
|
@@ -458,7 +423,6 @@ function buildFilteredTreeWithOriginalHierarchy(matchedNodes) {
|
|
|
458
423
|
const currentNode = resultMap.get(matchedNode.code);
|
|
459
424
|
|
|
460
425
|
if (parentNode && currentNode) {
|
|
461
|
-
// 检查是否已经包含该子节点
|
|
462
426
|
const existingChild = parentNode.children.find(child => child.code === matchedNode.code);
|
|
463
427
|
if (!existingChild) {
|
|
464
428
|
parentNode.children.push(currentNode);
|
|
@@ -469,11 +433,10 @@ function buildFilteredTreeWithOriginalHierarchy(matchedNodes) {
|
|
|
469
433
|
|
|
470
434
|
// 第三步:清理根节点,确保它们没有重复的父节点
|
|
471
435
|
const finalRootNodes = [];
|
|
472
|
-
const usedNodes = new Set();
|
|
473
436
|
|
|
474
437
|
function isNodeUsedInTree(node) {
|
|
475
438
|
for (const root of rootNodes) {
|
|
476
|
-
if (isNodeInChildren(root, node.code)) {
|
|
439
|
+
if (Tree.isNodeInChildren(root, node.code)) {
|
|
477
440
|
return true;
|
|
478
441
|
}
|
|
479
442
|
}
|
|
@@ -481,7 +444,6 @@ function buildFilteredTreeWithOriginalHierarchy(matchedNodes) {
|
|
|
481
444
|
}
|
|
482
445
|
|
|
483
446
|
rootNodes.forEach(rootNode => {
|
|
484
|
-
// 如果这个节点已经是其他节点的子节点,则不作为根节点
|
|
485
447
|
if (!isNodeUsedInTree(rootNode)) {
|
|
486
448
|
finalRootNodes.push(rootNode);
|
|
487
449
|
}
|
|
@@ -490,6 +452,7 @@ function buildFilteredTreeWithOriginalHierarchy(matchedNodes) {
|
|
|
490
452
|
return finalRootNodes;
|
|
491
453
|
}
|
|
492
454
|
|
|
455
|
+
|
|
493
456
|
// 新增:在全量树中查找父节点code
|
|
494
457
|
function findParentCodeInFullTree(nodeCode) {
|
|
495
458
|
function findParent(nodes, targetCode, parentCode = null) {
|
|
@@ -511,29 +474,6 @@ function findParentCodeInFullTree(nodeCode) {
|
|
|
511
474
|
return findParent(fullTreeData.value, nodeCode);
|
|
512
475
|
}
|
|
513
476
|
|
|
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
477
|
// 修改:过滤全量树数据,保持原始层级
|
|
538
478
|
function filterFullTreeData(filter) {
|
|
539
479
|
if (!fullTreeData.value.length) {
|
|
@@ -550,7 +490,7 @@ function filterFullTreeData(filter) {
|
|
|
550
490
|
searchConditions = filter.fields;
|
|
551
491
|
}
|
|
552
492
|
|
|
553
|
-
const validConditions = filterValidConditions(searchConditions);
|
|
493
|
+
const validConditions = Tree.filterValidConditions(searchConditions);
|
|
554
494
|
|
|
555
495
|
if (!validConditions || validConditions.length === 0) {
|
|
556
496
|
refTree.value.store.setData(fullTreeData.value);
|
|
@@ -559,7 +499,6 @@ function filterFullTreeData(filter) {
|
|
|
559
499
|
hasSetDefaultExpand.value = false;
|
|
560
500
|
defaultExpandedKeys.value = [];
|
|
561
501
|
|
|
562
|
-
// 先收起所有节点
|
|
563
502
|
if (refTree.value && refTree.value.store) {
|
|
564
503
|
Object.values(refTree.value.store.nodesMap).forEach((node: any) => {
|
|
565
504
|
if (node) {
|
|
@@ -575,7 +514,7 @@ function filterFullTreeData(filter) {
|
|
|
575
514
|
// 递归查找所有匹配的节点
|
|
576
515
|
const findMatchedNodes = (nodes, result = []) => {
|
|
577
516
|
nodes.forEach(node => {
|
|
578
|
-
const nodeMatches = nodeMatchesAllConditions(node, validConditions);
|
|
517
|
+
const nodeMatches = Tree.nodeMatchesAllConditions(node, validConditions);
|
|
579
518
|
|
|
580
519
|
if (nodeMatches) {
|
|
581
520
|
result.push(node);
|
|
@@ -590,18 +529,15 @@ function filterFullTreeData(filter) {
|
|
|
590
529
|
|
|
591
530
|
const matchedNodes = findMatchedNodes(fullTreeData.value);
|
|
592
531
|
|
|
593
|
-
// 构建保持原始层级的过滤树
|
|
594
532
|
let filteredData = [];
|
|
595
533
|
if (matchedNodes.length > 0) {
|
|
596
534
|
filteredData = buildFilteredTreeWithOriginalHierarchy(matchedNodes);
|
|
597
535
|
}
|
|
598
536
|
|
|
599
|
-
// 设置过滤后的数据
|
|
600
537
|
refTree.value.store.setData(filteredData);
|
|
601
538
|
|
|
602
|
-
// 搜索状态时展开所有节点
|
|
603
539
|
if (filteredData.length > 0 && isSearching.value) {
|
|
604
|
-
const allKeys = getAllNodeKeys(filteredData);
|
|
540
|
+
const allKeys = Tree.getAllNodeKeys(filteredData);
|
|
605
541
|
defaultExpandedKeys.value = allKeys;
|
|
606
542
|
|
|
607
543
|
nextTick(() => {
|
|
@@ -614,7 +550,6 @@ function filterFullTreeData(filter) {
|
|
|
614
550
|
}
|
|
615
551
|
});
|
|
616
552
|
|
|
617
|
-
// 默认选中第一个可选节点
|
|
618
553
|
const findFirstSelectableNode = (nodes) => {
|
|
619
554
|
for (const node of nodes) {
|
|
620
555
|
if (!node.children || node.children.length === 0) {
|
|
@@ -632,7 +567,8 @@ function filterFullTreeData(filter) {
|
|
|
632
567
|
refTree.value.setCurrentKey(firstNode.code);
|
|
633
568
|
handleNodeClick(firstNode, true);
|
|
634
569
|
});
|
|
635
|
-
}
|
|
570
|
+
}
|
|
571
|
+
|
|
636
572
|
}
|
|
637
573
|
|
|
638
574
|
} catch (error) {
|
|
@@ -642,9 +578,19 @@ function filterFullTreeData(filter) {
|
|
|
642
578
|
}
|
|
643
579
|
}
|
|
644
580
|
|
|
581
|
+
// 获取节点的allowedRoutes
|
|
582
|
+
function getAllowedRoutesForNode(data) {
|
|
583
|
+
if (!model.value || !model.value.actionRouter) return [];
|
|
584
|
+
|
|
585
|
+
return model.value.actionRouter.filter(v => {
|
|
586
|
+
const val = data[v.rightField];
|
|
587
|
+
const hit = val == '1' || val === undefined || val === null;
|
|
588
|
+
return hit;
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
|
|
645
592
|
// 修改:加载节点,保存节点Map
|
|
646
593
|
function loadNode(node, resolve) {
|
|
647
|
-
|
|
648
594
|
if (!lazy.value) {
|
|
649
595
|
const nodeData = node.data;
|
|
650
596
|
resolve(nodeData.children)
|
|
@@ -659,16 +605,14 @@ function loadNode(node, resolve) {
|
|
|
659
605
|
return SearchTree(screenPara.value || undefined).then(data => {
|
|
660
606
|
loading.value = false;
|
|
661
607
|
|
|
662
|
-
|
|
663
|
-
const { tree, hasAnyLeaf } = buildDeptTreeByField(data.rows, {
|
|
608
|
+
const { tree, hasAnyLeaf } = Tree.buildDeptTreeByField(data.rows, {
|
|
664
609
|
pathKey: 'path',
|
|
665
610
|
separator: '.'
|
|
666
611
|
});
|
|
667
612
|
|
|
668
|
-
// 缓存全量数据并构建节点Map
|
|
669
613
|
if (hasAnyLeaf) {
|
|
670
614
|
fullTreeData.value = tree;
|
|
671
|
-
nodeCodeMap.value = buildNodeCodeMap(tree);
|
|
615
|
+
nodeCodeMap.value = Tree.buildNodeCodeMap(tree);
|
|
672
616
|
resolve(tree);
|
|
673
617
|
if (props.expandTwoLevels) {
|
|
674
618
|
nextTick(() => {
|
|
@@ -805,7 +749,6 @@ function clearSearch() {
|
|
|
805
749
|
|
|
806
750
|
//节点点击事件
|
|
807
751
|
function handleNodeClick(data, formType) {
|
|
808
|
-
|
|
809
752
|
closeMenu();
|
|
810
753
|
if (data && formType) {
|
|
811
754
|
refTree.value.setCurrentKey(data.code)
|
|
@@ -833,40 +776,9 @@ function handleNodeClick(data, formType) {
|
|
|
833
776
|
}
|
|
834
777
|
}
|
|
835
778
|
|
|
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
|
-
|
|
868
779
|
// 菜单路由
|
|
869
780
|
function routerClickHandler(field) {
|
|
781
|
+
isManualClose.value = true;
|
|
870
782
|
var submitData = {};
|
|
871
783
|
field.submitFormField.forEach(v => {
|
|
872
784
|
submitData[v] = currentData.value[v];
|
|
@@ -893,29 +805,232 @@ function routerClickHandler(field) {
|
|
|
893
805
|
})
|
|
894
806
|
}
|
|
895
807
|
|
|
896
|
-
//删除树节点
|
|
897
|
-
function removeNode(newData) {
|
|
898
|
-
getNextClickNode(currentNode.value)
|
|
899
|
-
refTree.value.remove(currentNode.value);
|
|
900
|
-
|
|
901
|
-
}
|
|
902
|
-
|
|
903
808
|
//新增树节点
|
|
904
809
|
function addNode(newData) {
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
810
|
+
if (!lazy.value) {
|
|
811
|
+
// 全量数据模式:手动更新全量数据和树显示
|
|
812
|
+
const parentCode = currentData.value.code;
|
|
813
|
+
const parentPath = currentData.value.path || '';
|
|
814
|
+
|
|
815
|
+
// 设置新节点的属性
|
|
816
|
+
const newNode = {
|
|
817
|
+
...newData,
|
|
818
|
+
// 如果后端没有返回path,需要构建
|
|
819
|
+
path: parentPath ? `${parentPath}.${newData.code}` : newData.code,
|
|
820
|
+
// 确保children存在
|
|
821
|
+
children: newData.children || [],
|
|
822
|
+
// 标识是否为叶子节点
|
|
823
|
+
isLeaf: true
|
|
824
|
+
};
|
|
825
|
+
|
|
826
|
+
// 在全量数据中找到父节点并添加
|
|
827
|
+
const findAndAdd = (nodes, parentCode) => {
|
|
828
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
829
|
+
if (nodes[i].code === parentCode) {
|
|
830
|
+
if (!nodes[i].children) {
|
|
831
|
+
nodes[i].children = [];
|
|
832
|
+
}
|
|
833
|
+
nodes[i].children.push(newNode);
|
|
834
|
+
nodes[i].isLeaf = false; // 父节点不再是叶子节点
|
|
835
|
+
return true;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
if (nodes[i].children && nodes[i].children.length > 0) {
|
|
839
|
+
if (findAndAdd(nodes[i].children, parentCode)) {
|
|
840
|
+
return true;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
return false;
|
|
845
|
+
};
|
|
846
|
+
|
|
847
|
+
// 更新全量数据
|
|
848
|
+
findAndAdd(fullTreeData.value, parentCode);
|
|
849
|
+
|
|
850
|
+
// 更新nodeCodeMap
|
|
851
|
+
nodeCodeMap.value.set(newNode.code, newNode);
|
|
852
|
+
|
|
853
|
+
// 如果是搜索状态,需要同时更新当前显示的数据
|
|
854
|
+
if (isSearching.value && screenPara.value) {
|
|
855
|
+
filterFullTreeData(screenPara.value);
|
|
856
|
+
} else {
|
|
857
|
+
// 非搜索状态,直接重新设置数据
|
|
858
|
+
refTree.value.store.setData([...fullTreeData.value]);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// 刷新树显示
|
|
862
|
+
nextTick(() => {
|
|
863
|
+
// 展开父节点
|
|
864
|
+
const parentNode = refTree.value.getNode(parentCode);
|
|
865
|
+
if (parentNode && !parentNode.expanded) {
|
|
866
|
+
refTree.value.store.setCurrentNode(parentNode);
|
|
867
|
+
parentNode.expanded = true;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// 设置新节点为当前选中
|
|
871
|
+
handleNodeClick(newNode, true);
|
|
872
|
+
});
|
|
873
|
+
} else {
|
|
874
|
+
// 懒加载模式:使用原有方法
|
|
875
|
+
refTree.value.append(newData, currentNode.value);
|
|
876
|
+
handleNodeClick(newData, true);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
908
879
|
|
|
909
880
|
//修改树节点
|
|
910
881
|
function updateNode(newData) {
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
882
|
+
if (!lazy.value) {
|
|
883
|
+
// 全量数据模式:更新全量数据
|
|
884
|
+
const updateInTree = (nodes, nodeCode) => {
|
|
885
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
886
|
+
if (nodes[i].code === nodeCode) {
|
|
887
|
+
// 合并数据
|
|
888
|
+
nodes[i] = { ...nodes[i], ...newData };
|
|
889
|
+
return true;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
if (nodes[i].children && nodes[i].children.length > 0) {
|
|
893
|
+
if (updateInTree(nodes[i].children, nodeCode)) {
|
|
894
|
+
return true;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
return false;
|
|
899
|
+
};
|
|
900
|
+
|
|
901
|
+
// 更新全量数据
|
|
902
|
+
updateInTree(fullTreeData.value, currentData.value.code);
|
|
903
|
+
|
|
904
|
+
// 更新nodeCodeMap
|
|
905
|
+
nodeCodeMap.value.set(currentData.value.code, {
|
|
906
|
+
...nodeCodeMap.value.get(currentData.value.code),
|
|
907
|
+
...newData
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
// 如果是搜索状态,重新过滤显示
|
|
911
|
+
if (isSearching.value && screenPara.value) {
|
|
912
|
+
filterFullTreeData(screenPara.value);
|
|
914
913
|
} else {
|
|
915
|
-
|
|
914
|
+
// 非搜索状态,直接重新设置数据
|
|
915
|
+
refTree.value.store.setData([...fullTreeData.value]);
|
|
916
916
|
}
|
|
917
|
+
|
|
918
|
+
// 刷新显示并保持选中状态
|
|
919
|
+
nextTick(() => {
|
|
920
|
+
refTree.value.setCurrentKey(currentData.value.code);
|
|
921
|
+
handleNodeClick({ ...currentData.value, ...newData }, true);
|
|
922
|
+
});
|
|
923
|
+
} else {
|
|
924
|
+
// 懒加载模式:使用原有方法
|
|
925
|
+
for (let key in newData) {
|
|
926
|
+
if (typeof newData[key] === 'object') {
|
|
927
|
+
currentData.value[key] = { ...currentData.value[key], ...newData[key] }
|
|
928
|
+
} else {
|
|
929
|
+
currentData.value[key] = newData[key];
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
handleNodeClick(newData, true);
|
|
917
933
|
}
|
|
918
|
-
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
//删除树节点
|
|
937
|
+
function removeNode(newData) {
|
|
938
|
+
if (!lazy.value) {
|
|
939
|
+
// 全量数据模式:从全量数据中删除
|
|
940
|
+
let parentNode = null;
|
|
941
|
+
let parentCode = null;
|
|
942
|
+
|
|
943
|
+
const removeFromTree = (nodes, nodeCode) => {
|
|
944
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
945
|
+
if (nodes[i].code === nodeCode) {
|
|
946
|
+
// 记录父节点信息(如果是当前循环的节点)
|
|
947
|
+
parentCode = findParentCodeInFullTree(nodeCode);
|
|
948
|
+
if (parentCode) {
|
|
949
|
+
const parent = findNodeByCode(fullTreeData.value, parentCode);
|
|
950
|
+
if (parent) {
|
|
951
|
+
parentNode = parent;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// 删除节点
|
|
956
|
+
nodes.splice(i, 1);
|
|
957
|
+
nodeCodeMap.value.delete(nodeCode);
|
|
958
|
+
return true;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
if (nodes[i].children && nodes[i].children.length > 0) {
|
|
962
|
+
if (removeFromTree(nodes[i].children, nodeCode)) {
|
|
963
|
+
return true;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
return false;
|
|
968
|
+
};
|
|
969
|
+
|
|
970
|
+
// 从全量数据中删除
|
|
971
|
+
removeFromTree(fullTreeData.value, currentData.value.code);
|
|
972
|
+
|
|
973
|
+
// 更新父节点的 isLeaf 状态
|
|
974
|
+
if (parentCode && parentNode) {
|
|
975
|
+
// 重新检查父节点是否还有子节点
|
|
976
|
+
const hasChildren = parentNode.children && parentNode.children.length > 0;
|
|
977
|
+
parentNode.isLeaf = !hasChildren;
|
|
978
|
+
|
|
979
|
+
// 同步更新 nodeCodeMap
|
|
980
|
+
if (nodeCodeMap.value.has(parentCode)) {
|
|
981
|
+
const updatedParent = nodeCodeMap.value.get(parentCode);
|
|
982
|
+
updatedParent.isLeaf = !hasChildren;
|
|
983
|
+
nodeCodeMap.value.set(parentCode, updatedParent);
|
|
984
|
+
}
|
|
985
|
+
} else {
|
|
986
|
+
// 如果是根节点被删除,不需要更新 isLeaf
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// 如果是搜索状态,重新过滤显示
|
|
990
|
+
if (isSearching.value && screenPara.value) {
|
|
991
|
+
filterFullTreeData(screenPara.value);
|
|
992
|
+
} else {
|
|
993
|
+
// 非搜索状态,直接重新设置数据
|
|
994
|
+
refTree.value.store.setData([...fullTreeData.value]);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// 查找下一个可点击的节点
|
|
998
|
+
getNextClickNode();
|
|
999
|
+
} else {
|
|
1000
|
+
// 懒加载模式:使用原有方法
|
|
1001
|
+
getNextClickNode();
|
|
1002
|
+
refTree.value.remove(currentNode.value);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
// 根据 code 查找节点
|
|
1007
|
+
function findNodeByCode(nodes, targetCode) {
|
|
1008
|
+
for (const node of nodes) {
|
|
1009
|
+
if (node.code === targetCode) {
|
|
1010
|
+
return node;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
if (node.children && node.children.length > 0) {
|
|
1014
|
+
const found = findNodeByCode(node.children, targetCode);
|
|
1015
|
+
if (found) return found;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
return null;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// 查找节点的父节点(返回父节点本身,不只是code)
|
|
1022
|
+
function findParentNodeInFullTree(nodes, targetCode, parent = null) {
|
|
1023
|
+
for (const node of nodes) {
|
|
1024
|
+
if (node.code === targetCode) {
|
|
1025
|
+
return parent;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
if (node.children && node.children.length > 0) {
|
|
1029
|
+
const found = findParentNodeInFullTree(node.children, targetCode, node);
|
|
1030
|
+
if (found) return found;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
return null;
|
|
919
1034
|
}
|
|
920
1035
|
|
|
921
1036
|
function getNextClickNode() {
|
|
@@ -937,98 +1052,22 @@ function filterNode(value, data) {
|
|
|
937
1052
|
return data.name.toLowerCase().includes(value.toLowerCase())
|
|
938
1053
|
}
|
|
939
1054
|
|
|
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
1055
|
|
|
1012
1056
|
function findSearchFieldValue(searchFields, targetKey, options = {}) {
|
|
1013
1057
|
const {
|
|
1014
|
-
keyFields = ['fieldName1', 'groupName'],
|
|
1015
|
-
valueField = 'searchValue1',
|
|
1058
|
+
keyFields = ['fieldName1', 'groupName'],
|
|
1059
|
+
valueField = 'searchValue1',
|
|
1016
1060
|
defaultValue = 2
|
|
1017
1061
|
} = options;
|
|
1018
1062
|
|
|
1019
|
-
// 检查数据结构
|
|
1020
1063
|
if (!searchFields?.searchData?.fields?.length) {
|
|
1021
1064
|
return defaultValue;
|
|
1022
1065
|
}
|
|
1023
1066
|
|
|
1024
|
-
// 遍历fields数组
|
|
1025
1067
|
for (const field of searchFields.searchData.fields) {
|
|
1026
|
-
// 检查所有可能的key字段
|
|
1027
1068
|
for (const keyField of keyFields) {
|
|
1028
1069
|
if (field[keyField] === targetKey) {
|
|
1029
|
-
// 找到了匹配的key,返回对应的值
|
|
1030
1070
|
const value = field[valueField];
|
|
1031
|
-
// 如果值为undefined、null或空字符串,返回默认值
|
|
1032
1071
|
return value !== undefined && value !== null && value !== ''
|
|
1033
1072
|
? value
|
|
1034
1073
|
: defaultValue;
|
|
@@ -1039,8 +1078,6 @@ function findSearchFieldValue(searchFields, targetKey, options = {}) {
|
|
|
1039
1078
|
return defaultValue;
|
|
1040
1079
|
}
|
|
1041
1080
|
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
1081
|
// 暴露方法
|
|
1045
1082
|
defineExpose({
|
|
1046
1083
|
search,
|
|
@@ -1099,6 +1136,7 @@ defineExpose({
|
|
|
1099
1136
|
.node_content {
|
|
1100
1137
|
display: flex;
|
|
1101
1138
|
align-items: center;
|
|
1139
|
+
position: relative;
|
|
1102
1140
|
}
|
|
1103
1141
|
|
|
1104
1142
|
.node-icon {
|
|
@@ -1109,20 +1147,129 @@ defineExpose({
|
|
|
1109
1147
|
.box-menu {
|
|
1110
1148
|
z-index: 1000;
|
|
1111
1149
|
background-color: #fff;
|
|
1112
|
-
|
|
1150
|
+
// box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
|
1151
|
+
border-radius: 4px;
|
|
1152
|
+
padding: 4px 0;
|
|
1113
1153
|
|
|
1114
1154
|
div {
|
|
1115
1155
|
cursor: pointer;
|
|
1116
1156
|
}
|
|
1117
1157
|
}
|
|
1118
1158
|
|
|
1159
|
+
.box-menu.box-menu-icons {
|
|
1160
|
+
display: flex;
|
|
1161
|
+
flex-wrap: nowrap;
|
|
1162
|
+
padding: 8px;
|
|
1163
|
+
gap: 0px;
|
|
1164
|
+
line-height: 15px;
|
|
1165
|
+
background: transparent !important;
|
|
1166
|
+
border: none !important;
|
|
1167
|
+
box-shadow: none !important;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
.box-menu-icons .opertion {
|
|
1171
|
+
width: 16px;
|
|
1172
|
+
height: 16px;
|
|
1173
|
+
padding: 0 !important;
|
|
1174
|
+
display: flex;
|
|
1175
|
+
align-items: center;
|
|
1176
|
+
justify-content: center;
|
|
1177
|
+
border-radius: 4px;
|
|
1178
|
+
margin: 0 2px !important;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
.box-menu-icons .opertion:hover {
|
|
1182
|
+
background-color: #f5f7fa;
|
|
1183
|
+
transform: translateY(-1px);
|
|
1184
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
.box-menu-icons .opertion img {
|
|
1188
|
+
width: 20px;
|
|
1189
|
+
height: 20px;
|
|
1190
|
+
margin: 0;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
.box-menu-icons .opertion div {
|
|
1194
|
+
display: flex;
|
|
1195
|
+
align-items: center;
|
|
1196
|
+
justify-content: center;
|
|
1197
|
+
width: 100%;
|
|
1198
|
+
height: 100%;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1119
1201
|
.box-menu .opertion {
|
|
1202
|
+
display: flex;
|
|
1203
|
+
align-items: center;
|
|
1204
|
+
padding: 4px;
|
|
1120
1205
|
cursor: pointer;
|
|
1121
|
-
|
|
1206
|
+
font-size: 14px;
|
|
1207
|
+
color: #606266;
|
|
1208
|
+
transition: all 0.2s ease;
|
|
1209
|
+
line-height: 20px;
|
|
1210
|
+
margin: 0 4px;
|
|
1211
|
+
border-radius: 6px;
|
|
1212
|
+
position: relative;
|
|
1213
|
+
overflow: hidden;
|
|
1122
1214
|
}
|
|
1123
1215
|
|
|
1124
1216
|
.box-menu .opertion:hover {
|
|
1125
1217
|
color: var(--btnHoverRed);
|
|
1126
|
-
background-color:
|
|
1218
|
+
background-color: #f5f7fa;
|
|
1219
|
+
transform: translateY(-1px);
|
|
1220
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
.box-menu .opertion:active {
|
|
1224
|
+
transform: translateY(0);
|
|
1225
|
+
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
.box-menu .opertion:not(:last-child)::after {
|
|
1229
|
+
content: '';
|
|
1230
|
+
position: absolute;
|
|
1231
|
+
bottom: 0;
|
|
1232
|
+
left: 16px;
|
|
1233
|
+
right: 16px;
|
|
1234
|
+
height: 1px;
|
|
1235
|
+
background-color: #f0f0f0;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
.box-menu .opertion:last-child {
|
|
1239
|
+
margin-bottom: 0;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
.box-menu .opertion img {
|
|
1243
|
+
width: 16px;
|
|
1244
|
+
height: 16px;
|
|
1245
|
+
margin-right: 8px;
|
|
1246
|
+
vertical-align: middle;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
.box-menu .opertion div {
|
|
1250
|
+
display: flex;
|
|
1251
|
+
align-items: center;
|
|
1252
|
+
width: 100%;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
/* 菜单激活状态 */
|
|
1256
|
+
.box-menu .opertion.active {
|
|
1257
|
+
background-color: #ecf5ff;
|
|
1258
|
+
color: var(--centalineBlue);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
/* 悬停模式下的节点样式 */
|
|
1262
|
+
.node_content:hover {
|
|
1263
|
+
background-color: #f5f7fa;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
.popper-transparent {
|
|
1267
|
+
background: transparent !important;
|
|
1268
|
+
border: none !important;
|
|
1269
|
+
box-shadow: none !important;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
.popper-transparent .el-popper__arrow::before {
|
|
1273
|
+
background: transparent !important;
|
|
1127
1274
|
}
|
|
1128
1275
|
</style>
|