@sy-common/organize-select-help 1.0.0-beta.11 → 1.0.0-beta.17
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/package.json +2 -2
- package/src/index.vue +945 -161
- package/src/organize-tree.vue +213 -26
package/src/index.vue
CHANGED
|
@@ -1,34 +1,51 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<Modal
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
title=""
|
|
4
|
+
v-model="modal"
|
|
5
|
+
@on-ok="confirm"
|
|
6
|
+
@on-visible-change="visibleChange "
|
|
7
|
+
width="1180"
|
|
8
|
+
:mask-closable="false"
|
|
9
|
+
class="modal-tree"
|
|
10
10
|
>
|
|
11
11
|
<div slot="header" class="header-text"><Icon type="md-information" class="icon-tip" />人员选择</div>
|
|
12
12
|
<div class="content-container">
|
|
13
13
|
<div class="tree-orig">
|
|
14
14
|
<div class="tab-content">
|
|
15
|
-
<Tabs :value="tabName">
|
|
16
|
-
<TabPane label="
|
|
15
|
+
<Tabs :value="tabName" @input="handleTabChange">
|
|
16
|
+
<TabPane label="组织选择" name="org" v-if="name.includes('org')">
|
|
17
17
|
<div class="tab">
|
|
18
|
-
<
|
|
19
|
-
|
|
18
|
+
<span>组织搜索:</span>
|
|
19
|
+
<Select v-model="orgSearch" filterable :remote-method="getOrgListBySearch" @on-change="getOrgOption" :loading="loadingOrg" placeholder="请输入组织名称" style="width: 600px;" clearable>
|
|
20
|
+
<Option v-for="(option, index) in orgSearchList" :value="option.orgUnitId" :key="index">{{option.name}}</Option>
|
|
20
21
|
</Select>
|
|
21
|
-
<div class="tag-content">
|
|
22
|
+
<div class="tag-content" v-if="isQuickOpen && orgTagList.length > 0">
|
|
22
23
|
<span>快捷选择:</span>
|
|
23
|
-
<div class="
|
|
24
|
-
<
|
|
24
|
+
<div class="custom-select-wrapper">
|
|
25
|
+
<Select
|
|
26
|
+
v-model="selectedOrgTagKey"
|
|
27
|
+
placeholder="请选择快捷标签"
|
|
28
|
+
transfer
|
|
29
|
+
clearable
|
|
30
|
+
@on-change="handleOrgTagSelect"
|
|
31
|
+
@on-clear="handleOrgTagClear"
|
|
32
|
+
style="width: 600px;"
|
|
33
|
+
:disabled="!orgTagList.length"
|
|
34
|
+
>
|
|
35
|
+
<Option
|
|
36
|
+
v-for="item in orgTagList"
|
|
37
|
+
:value="item.quickPickKey"
|
|
38
|
+
:key="item.quickPickKey"
|
|
39
|
+
:class="{ 'active-option': item.checked }"
|
|
40
|
+
>
|
|
41
|
+
{{ item.quickPickName }}
|
|
42
|
+
</Option>
|
|
43
|
+
<!-- 无数据提示 -->
|
|
44
|
+
<Option value="" v-if="!postTagList.length" disabled>
|
|
45
|
+
暂无快捷标签
|
|
46
|
+
</Option>
|
|
47
|
+
</Select>
|
|
25
48
|
</div>
|
|
26
|
-
<Poptip content="content" placement="right-end">
|
|
27
|
-
<div slot="content" class="pop-content">
|
|
28
|
-
<span :class="['tag', item.checked && 'active']" v-for="item in tagList" :key="item.key" @click="fastChedkOrg(item)">{{item.v}}</span>
|
|
29
|
-
</div>
|
|
30
|
-
<Icon type="ios-arrow-down" />
|
|
31
|
-
</Poptip>
|
|
32
49
|
</div>
|
|
33
50
|
<div class="tree">
|
|
34
51
|
<organizeTree @handleChange="getOrgList" ref="orgTree" :treeList="orgTree"></organizeTree>
|
|
@@ -43,23 +60,43 @@
|
|
|
43
60
|
</div>
|
|
44
61
|
</div>
|
|
45
62
|
</TabPane>
|
|
46
|
-
<TabPane label="
|
|
63
|
+
<TabPane label="岗位选择" name="post" v-if="name.includes('post')">
|
|
47
64
|
<div class="tab post">
|
|
48
65
|
<div class="left">
|
|
49
|
-
<div v-for="item in positiontList" :class="[item.
|
|
66
|
+
<div v-for="item in positiontList" :class="[selectedPositionId === item.positionId ? 'active' : '']" :key="item.positionId" @click="getPosionId(item)">{{ item.positionName }}</div>
|
|
50
67
|
</div>
|
|
51
68
|
<div class="right">
|
|
52
|
-
<
|
|
53
|
-
|
|
69
|
+
<span>组织搜索:</span>
|
|
70
|
+
<Select v-model="postSearch" filterable :remote-method="getPostListBySearch" @on-change="getPostOption" placeholder="组织选择" :loading="loadingPost" clearable @on-clear="handlePostSearchClear" style="width: 365px;">
|
|
71
|
+
<Option v-for="(option, index) in postSearchList" :value="option.orgUnitId" :key="index">{{option.name}}</Option>
|
|
54
72
|
</Select>
|
|
55
|
-
<div class="tag-content">
|
|
73
|
+
<div class="tag-content" v-if="isQuickOpen && postTagList.length > 0">
|
|
56
74
|
<span>快捷选择:</span>
|
|
57
|
-
<div class="
|
|
58
|
-
<
|
|
75
|
+
<div class="custom-select-wrapper">
|
|
76
|
+
<Select
|
|
77
|
+
v-model="selectedPostTagKey"
|
|
78
|
+
placeholder="请选择快捷标签"
|
|
79
|
+
transfer
|
|
80
|
+
clearable
|
|
81
|
+
@on-change="handlePostTagSelect"
|
|
82
|
+
@on-clear="handlePostTagClear"
|
|
83
|
+
style="width: 365px;"
|
|
84
|
+
:disabled="!postTagList.length"
|
|
85
|
+
>
|
|
86
|
+
<Option
|
|
87
|
+
v-for="item in postTagList"
|
|
88
|
+
:value="item.quickPickKey"
|
|
89
|
+
:key="item.quickPickKey"
|
|
90
|
+
:class="{ 'active-option': item.checked }"
|
|
91
|
+
>
|
|
92
|
+
{{ item.quickPickName }}
|
|
93
|
+
</Option>
|
|
94
|
+
<!-- 无数据提示 -->
|
|
95
|
+
<Option value="" v-if="!postTagList.length" disabled>
|
|
96
|
+
暂无快捷标签
|
|
97
|
+
</Option>
|
|
98
|
+
</Select>
|
|
59
99
|
</div>
|
|
60
|
-
<Poptip title="Title" content="content">
|
|
61
|
-
<Icon type="ios-arrow-down" />
|
|
62
|
-
</Poptip>
|
|
63
100
|
</div>
|
|
64
101
|
<div class="tree">
|
|
65
102
|
<organizeTree @handleChange="getPostList" ref="postTree" :treeList="postTree"></organizeTree>
|
|
@@ -77,31 +114,35 @@
|
|
|
77
114
|
</TabPane>
|
|
78
115
|
<TabPane label="选择人员" name="staff" v-if="name.includes('staff')">
|
|
79
116
|
<div class="tab">
|
|
80
|
-
<Input v-model="staffSearch"
|
|
117
|
+
<Input v-model="staffSearch" @on-enter="searchStaff" @on-search="searchStaff" search placeholder="搜索人员"/>
|
|
81
118
|
<div style="position:relative;">
|
|
82
119
|
<div class="tree staff-content" @scroll="handleScroll">
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
120
|
+
<!-- 关键修改:用“index + 下划线 + item.id”做key,确保唯一且不影响数据 -->
|
|
121
|
+
<div :class="['gust-item',item.checked && 'staff-active']"
|
|
122
|
+
v-for="(item, index) in staffAllList"
|
|
123
|
+
:key="`staff_${index}_${item.id}`"
|
|
124
|
+
@click="handlestaff(item)">
|
|
125
|
+
<div class="left-panel">{{item.name && item.name.slice(0,1) || ''}}</div>
|
|
126
|
+
<div class="right-panel">
|
|
127
|
+
<p>{{item.name}}</p>
|
|
128
|
+
<p>{{item.orgNodeName || item.orgUnitName}}</p>
|
|
90
129
|
</div>
|
|
91
|
-
<
|
|
130
|
+
<div class="checked-icon" v-show="item.checked">✔</div>
|
|
92
131
|
</div>
|
|
93
|
-
<
|
|
94
|
-
</div>
|
|
95
|
-
<div class="bottom-select">
|
|
96
|
-
<div>当前已选择 <span class="num">{{getCheckedStaff}}</span>人</div>
|
|
97
|
-
<Button type="primary" icon="md-add" @click="addStaffList">添加人员</Button>
|
|
132
|
+
<p v-if="staffEnding" style="color:#CCCCCC;text-align: center">---我也是有底线的---</p>
|
|
98
133
|
</div>
|
|
134
|
+
<Spin v-if="loadingStaff" size="large" fix />
|
|
99
135
|
</div>
|
|
100
|
-
|
|
136
|
+
<div class="bottom-select">
|
|
137
|
+
<div>当前已选择 <span class="num">{{getCheckedStaff}}</span>人</div>
|
|
138
|
+
<Button type="primary" icon="md-add" @click="addStaffList">添加人员</Button>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
</TabPane>
|
|
101
142
|
</Tabs>
|
|
102
143
|
</div>
|
|
103
144
|
<div class="form-content">
|
|
104
|
-
<p>已选择条件 <span class="num">({{getCheckTypenum}})</span></p>
|
|
145
|
+
<p v-if="name.length > 1">已选择条件 <span class="num">({{getCheckTypenum}})</span></p>
|
|
105
146
|
<div class="node-list">
|
|
106
147
|
<!-- 组织节点条件:-->
|
|
107
148
|
<div v-if="name.includes('org')">
|
|
@@ -169,6 +210,10 @@ export default {
|
|
|
169
210
|
name:{
|
|
170
211
|
type:Array ,
|
|
171
212
|
default(){return ['org','post','staff']}
|
|
213
|
+
},
|
|
214
|
+
isQuickOpen:{
|
|
215
|
+
type: Boolean,
|
|
216
|
+
default: false
|
|
172
217
|
}
|
|
173
218
|
},
|
|
174
219
|
data(){
|
|
@@ -201,38 +246,109 @@ export default {
|
|
|
201
246
|
lastLoadingTime:0,
|
|
202
247
|
offset:0,
|
|
203
248
|
staffEnding:false,
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
checked:false,
|
|
211
|
-
key:'08'
|
|
212
|
-
},{
|
|
213
|
-
v:'所有市公司',
|
|
214
|
-
checked:false,
|
|
215
|
-
key:'9'
|
|
216
|
-
},{
|
|
217
|
-
v:'所有市公司',
|
|
218
|
-
checked:false,
|
|
219
|
-
key:'10'
|
|
220
|
-
},{
|
|
221
|
-
v:'所有市公司',
|
|
222
|
-
checked:false,
|
|
223
|
-
key:'11'
|
|
224
|
-
},{
|
|
225
|
-
v:'所有市公司',
|
|
226
|
-
checked:false,
|
|
227
|
-
key:'12'
|
|
228
|
-
}],
|
|
249
|
+
parentOrgList: [],
|
|
250
|
+
orgTagList:[],
|
|
251
|
+
postTagList:[],
|
|
252
|
+
selectedPositionId: null, // 新增:岗位单选的选中状态存储
|
|
253
|
+
selectedOrgTagKey: '', // 存储选中标签的唯一标识
|
|
254
|
+
selectedPostTagKey: '', // 存储选中标签的唯一标识
|
|
229
255
|
}
|
|
230
256
|
},
|
|
231
257
|
mounted() {
|
|
258
|
+
this.queryTagList()
|
|
232
259
|
this.queryPositionList()
|
|
233
260
|
this.loadMore()
|
|
234
261
|
},
|
|
235
262
|
methods:{
|
|
263
|
+
handlePostTagSelect(quickPickKey) {
|
|
264
|
+
// 清空暂存列表
|
|
265
|
+
this.proPostList = [];
|
|
266
|
+
// 找到选中的标签对象
|
|
267
|
+
const selectedItem = this.postTagList.find(
|
|
268
|
+
item => item.quickPickKey === quickPickKey
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
if (selectedItem) {
|
|
272
|
+
// 重置其他标签的选中状态
|
|
273
|
+
this.postTagList.forEach(tag => tag.checked = false);
|
|
274
|
+
// 触发原有的处理方法
|
|
275
|
+
this.fastChedkPost(selectedItem);
|
|
276
|
+
// 清空搜索框绑定的值
|
|
277
|
+
this.postSearch = '';
|
|
278
|
+
// 清空搜索结果列表
|
|
279
|
+
this.postSearchList = [];
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
handleOrgTagSelect(quickPickKey) {
|
|
283
|
+
// 清空暂存列表
|
|
284
|
+
this.proOrgList = [];
|
|
285
|
+
// 找到选中的标签对象
|
|
286
|
+
const selectedItem = this.orgTagList.find(
|
|
287
|
+
item => item.quickPickKey === quickPickKey
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
if (selectedItem) {
|
|
291
|
+
// 重置其他标签的选中状态
|
|
292
|
+
this.orgTagList.forEach(tag => tag.checked = false);
|
|
293
|
+
// 触发原有的处理方法
|
|
294
|
+
this.fastChedkOrg(selectedItem);
|
|
295
|
+
// 清空搜索框绑定的值
|
|
296
|
+
this.orgSearch = '';
|
|
297
|
+
// 清空搜索结果列表
|
|
298
|
+
this.orgSearchList = [];
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
handleOrgTagClear() {
|
|
302
|
+
this.selectedOrgTagKey = '';
|
|
303
|
+
// 重置所有标签的选中状态
|
|
304
|
+
this.orgTagList.forEach(tag => {
|
|
305
|
+
tag.checked = false;
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// 3. 清空树选择状态和暂存列表
|
|
309
|
+
if (this.$refs.orgTree && this.orgSearchList) {
|
|
310
|
+
this.$refs.orgTree.initData();
|
|
311
|
+
}
|
|
312
|
+
// 4. 可选:添加清空提示
|
|
313
|
+
this.$Message.info("已清空快捷标签选择");
|
|
314
|
+
},
|
|
315
|
+
// 修正清空逻辑方法(与事件名称对应)
|
|
316
|
+
handlePostTagClear() {
|
|
317
|
+
// 1. 重置选中的标签key
|
|
318
|
+
this.selectedPostTagKey = '';
|
|
319
|
+
|
|
320
|
+
// 2. 重置所有标签的选中状态
|
|
321
|
+
this.postTagList.forEach(tag => {
|
|
322
|
+
tag.checked = false;
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// 3. 清空树选择状态和暂存列表
|
|
326
|
+
if (this.$refs.postTree) {
|
|
327
|
+
this.$refs.postTree.initData();
|
|
328
|
+
this.proPostList = [];
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// 4. 可选:添加清空提示
|
|
332
|
+
this.$Message.info("已清空快捷标签选择");
|
|
333
|
+
},
|
|
334
|
+
handlePostSearchClear() {
|
|
335
|
+
// 清空搜索框绑定的值
|
|
336
|
+
this.postSearch = '';
|
|
337
|
+
|
|
338
|
+
// 清空搜索结果列表
|
|
339
|
+
this.postSearchList = [];
|
|
340
|
+
|
|
341
|
+
// 重置树组件状态(与原有逻辑保持一致)
|
|
342
|
+
if (this.$refs.postTree) {
|
|
343
|
+
this.$refs.postTree.initData();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// 清空暂存列表
|
|
347
|
+
this.proPostList = [];
|
|
348
|
+
|
|
349
|
+
// 可选:添加清空提示
|
|
350
|
+
this.$Message.info("已清空组织选择搜索");
|
|
351
|
+
},
|
|
236
352
|
queryPositionList(){
|
|
237
353
|
ajax.get('/pub-manage-server/pub/personHelpBox/q/queryPositionList').then((res)=>{
|
|
238
354
|
if(res.data.code === 1){
|
|
@@ -246,64 +362,120 @@ export default {
|
|
|
246
362
|
if (query !== '') {
|
|
247
363
|
this.loadingOrg = true;
|
|
248
364
|
this.getOrgUnitBySearchTerm(query,(res)=>{
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
365
|
+
this.loadingOrg = false
|
|
366
|
+
if(res.data.code === 1){
|
|
367
|
+
let resp = res.data.data?.items??[]
|
|
368
|
+
this.orgSearchList = resp
|
|
369
|
+
}else{
|
|
370
|
+
this.orgSearchList = []
|
|
371
|
+
this.$Message.error("获取组织节点列表失败!")
|
|
372
|
+
}
|
|
257
373
|
})
|
|
258
374
|
}else{
|
|
259
375
|
this.orgSearchList = [];
|
|
260
376
|
}
|
|
261
377
|
},
|
|
262
|
-
getPostListBySearch(query){
|
|
378
|
+
getPostListBySearch(query) {
|
|
263
379
|
if (query !== '') {
|
|
264
380
|
this.loadingPost = true;
|
|
265
|
-
this.getOrgUnitBySearchTerm(query,(res)=>{
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
381
|
+
this.getOrgUnitBySearchTerm(query, (res) => {
|
|
382
|
+
this.loadingPost = false;
|
|
383
|
+
if (res.data.code === 1) {
|
|
384
|
+
let resp = res.data.data?.items ?? [];
|
|
385
|
+
// 修复:岗位搜索结果深拷贝,避免引用污染
|
|
386
|
+
this.postSearchList = this.safeDeepCopy(resp);
|
|
387
|
+
} else {
|
|
388
|
+
this.postSearchList = [];
|
|
389
|
+
this.$Message.error("获取组织节点列表失败!");
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
} else {
|
|
393
|
+
// 修复:清空搜索时,同时清空树数据和暂存列表(与组织Tab一致)
|
|
277
394
|
this.postSearchList = [];
|
|
395
|
+
if (this.$refs.postTree) {
|
|
396
|
+
this.$refs.postTree.initData();
|
|
397
|
+
}
|
|
398
|
+
this.proPostList = []; // 清空岗位暂存列表
|
|
399
|
+
this.$set(this, 'postTree', []); // 清空岗位树数据
|
|
278
400
|
}
|
|
279
401
|
},
|
|
280
402
|
getOrgUnitBySearchTerm(query,callback){
|
|
281
|
-
|
|
403
|
+
ajax.get('/pub-manage-server/pub/personHelpBox/q/getOrgUnitBySearchTerm?searchTerm='+query).then((res)=>{callback(res)})
|
|
282
404
|
},
|
|
283
|
-
async getOrgOption(val){
|
|
405
|
+
async getOrgOption(val) {
|
|
406
|
+
this.selectedOrgTagKey = '';
|
|
407
|
+
this.orgTagList.forEach(tag => {
|
|
408
|
+
tag.checked = false;
|
|
409
|
+
});
|
|
410
|
+
this.proOrgList = [];
|
|
284
411
|
if(!val) return this.$refs.orgTree.initData();
|
|
285
|
-
let item = this.orgSearchList.filter((item)=>
|
|
412
|
+
let item = this.orgSearchList.filter((item)=> item.orgUnitId === val).shift();
|
|
413
|
+
if (!item) return;
|
|
414
|
+
|
|
286
415
|
const leafNode = await this.judgeNodeLeafe(item.orgUnitId);
|
|
287
|
-
const ftem = {
|
|
416
|
+
const ftem = this.safeDeepCopy({
|
|
417
|
+
...item,
|
|
418
|
+
orgNodeName: item.name,
|
|
419
|
+
parentOrgUnitId: item.parentId,
|
|
420
|
+
leafNode: leafNode
|
|
421
|
+
});
|
|
422
|
+
|
|
288
423
|
try{
|
|
289
|
-
let parentsList = await this.getParentOrgNodesByOrgUnitId(val)
|
|
290
|
-
|
|
291
|
-
|
|
424
|
+
let parentsList = await this.getParentOrgNodesByOrgUnitId(val);
|
|
425
|
+
// 对父节点列表进行安全拷贝
|
|
426
|
+
const safeParents = this.safeDeepCopy(parentsList);
|
|
427
|
+
let tree = this.buildTree([...safeParents, ftem]);
|
|
428
|
+
this.orgTree = this.safeDeepCopy(tree);
|
|
292
429
|
}catch(e){
|
|
293
|
-
this.$Message.error("获取组织节点列表失败!")
|
|
430
|
+
this.$Message.error("获取组织节点列表失败!");
|
|
294
431
|
}
|
|
295
432
|
},
|
|
296
|
-
async getPostOption(val){
|
|
297
|
-
|
|
298
|
-
|
|
433
|
+
async getPostOption(val) {
|
|
434
|
+
// 1. 重置选中的标签key
|
|
435
|
+
this.selectedPostTagKey = '';
|
|
436
|
+
|
|
437
|
+
// 2. 重置所有标签的选中状态
|
|
438
|
+
this.postTagList.forEach(tag => {
|
|
439
|
+
tag.checked = false;
|
|
440
|
+
});
|
|
441
|
+
if (!val) {
|
|
442
|
+
// 修复:岗位树清空时,同时清空暂存列表和搜索框
|
|
443
|
+
if (this.$refs.postTree) {
|
|
444
|
+
this.$refs.postTree.initData();
|
|
445
|
+
}
|
|
446
|
+
this.proPostList = []; // 清空岗位暂存列表(与组织Tab保持一致)
|
|
447
|
+
this.postSearch = ''; // 清空搜索框
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
let item = this.postSearchList.filter(item => item.orgUnitId === val).shift();
|
|
452
|
+
if (!item) return;
|
|
453
|
+
|
|
299
454
|
const leafNode = await this.judgeNodeLeafe(item.orgUnitId);
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
455
|
+
// 修复:岗位节点深拷贝,避免引用污染
|
|
456
|
+
const ftem = this.safeDeepCopy({
|
|
457
|
+
...item,
|
|
458
|
+
orgNodeName: item.name || `未命名组织(${item.orgUnitId})`,
|
|
459
|
+
parentOrgUnitId: item.parentId,
|
|
460
|
+
leafNode: leafNode
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
try {
|
|
464
|
+
let parentsList = await this.getParentOrgNodesByOrgUnitId(val);
|
|
465
|
+
const safeParents = this.safeDeepCopy(parentsList);
|
|
466
|
+
let tree = this.buildTree([...safeParents, ftem]);
|
|
467
|
+
// 修复:用$set确保岗位树数据响应式更新
|
|
468
|
+
this.$set(this, 'postTree', this.safeDeepCopy(tree));
|
|
469
|
+
|
|
470
|
+
// 修复:设置节点展开和选中(与组织Tab逻辑一致)
|
|
471
|
+
this.$nextTick(() => {
|
|
472
|
+
if (this.$refs.postTree && this.$refs.postTree.setCheckedNodes) {
|
|
473
|
+
this.$refs.postTree.setCheckedNodes([val]); // 选中当前节点
|
|
474
|
+
this.proPostList = [ftem]; // 同步暂存列表
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
} catch (e) {
|
|
478
|
+
this.$Message.error("获取组织节点列表失败!");
|
|
307
479
|
}
|
|
308
480
|
},
|
|
309
481
|
getParentOrgNodesByOrgUnitId(val){
|
|
@@ -339,27 +511,28 @@ export default {
|
|
|
339
511
|
})
|
|
340
512
|
},
|
|
341
513
|
buildTree(items) {
|
|
342
|
-
// 创建一个映射表,用于快速查找节点
|
|
343
514
|
const map = {};
|
|
344
515
|
const roots = [];
|
|
516
|
+
// 对输入数据进行安全拷贝
|
|
517
|
+
const copiedItems = this.safeDeepCopy(items);
|
|
345
518
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
map[item.orgUnitId] = {
|
|
519
|
+
copiedItems.forEach(item => {
|
|
520
|
+
if (!item.orgUnitId) return; // 过滤无效节点
|
|
521
|
+
map[item.orgUnitId] = this.safeDeepCopy({
|
|
522
|
+
...item,
|
|
523
|
+
orgChildrenList: []
|
|
524
|
+
});
|
|
349
525
|
});
|
|
350
526
|
|
|
351
|
-
|
|
352
|
-
|
|
527
|
+
copiedItems.forEach(item => {
|
|
528
|
+
if (!item.orgUnitId || !map[item.orgUnitId]) return;
|
|
529
|
+
|
|
353
530
|
const node = map[item.orgUnitId];
|
|
354
|
-
if (item.parentOrgUnitId
|
|
355
|
-
// 根节点
|
|
531
|
+
if (!item.parentOrgUnitId || !map[item.parentOrgUnitId]) {
|
|
356
532
|
roots.push(node);
|
|
357
533
|
} else {
|
|
358
|
-
// 子节点,找到父节点并添加到其children中
|
|
359
534
|
const parent = map[item.parentOrgUnitId];
|
|
360
|
-
|
|
361
|
-
parent.orgChildrenList.push(node);
|
|
362
|
-
}
|
|
535
|
+
parent.orgChildrenList.push(node);
|
|
363
536
|
}
|
|
364
537
|
});
|
|
365
538
|
return roots;
|
|
@@ -418,12 +591,21 @@ export default {
|
|
|
418
591
|
}
|
|
419
592
|
},
|
|
420
593
|
getOrgList(data){
|
|
421
|
-
this.proOrgList
|
|
594
|
+
if (this.proOrgList){
|
|
595
|
+
this.proOrgList = this.proOrgList.concat(data)
|
|
596
|
+
}else {
|
|
597
|
+
this.proOrgList = data
|
|
598
|
+
}
|
|
599
|
+
|
|
422
600
|
},
|
|
423
601
|
getPostList(data){
|
|
424
|
-
this.proPostList
|
|
602
|
+
if (this.proPostList){
|
|
603
|
+
this.proPostList = this.proPostList.concat(data)
|
|
604
|
+
}else {
|
|
605
|
+
this.proPostList = data
|
|
606
|
+
}
|
|
425
607
|
},
|
|
426
|
-
addOrgList(){
|
|
608
|
+
addOrgList() {
|
|
427
609
|
if(!this.proOrgList.length) return this.$Message.error("请先选择组织节点!")
|
|
428
610
|
let proOrgList = deepCopy(this.proOrgList)
|
|
429
611
|
proOrgList.forEach(item=>{
|
|
@@ -433,11 +615,18 @@ export default {
|
|
|
433
615
|
})
|
|
434
616
|
let list = this.orgList.concat(proOrgList)
|
|
435
617
|
let uniqueArray = list.filter((item, index, self) =>
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
618
|
+
index === self.findIndex(t => (
|
|
619
|
+
t.id === item.id // 基于id属性去重
|
|
620
|
+
))
|
|
439
621
|
);
|
|
440
622
|
this.orgList = uniqueArray
|
|
623
|
+
|
|
624
|
+
// 新增:清空组织树节点选中状态(关键优化)
|
|
625
|
+
if (this.$refs.orgTree && this.$refs.orgTree.clearAllChecked) {
|
|
626
|
+
this.$refs.orgTree.clearAllChecked(this.$refs.orgTree.data);
|
|
627
|
+
this.$refs.orgTree.$emit('handleChange', []); // 通知父组件清空暂存列表
|
|
628
|
+
}
|
|
629
|
+
|
|
441
630
|
this.$refs.orgTree.upDataTree()
|
|
442
631
|
this.proOrgList = []
|
|
443
632
|
},
|
|
@@ -449,36 +638,44 @@ export default {
|
|
|
449
638
|
},
|
|
450
639
|
//岗位
|
|
451
640
|
getPosionId(item){
|
|
452
|
-
item.checked = !item.checked;
|
|
641
|
+
// item.checked = !item.checked;
|
|
642
|
+
this.selectedPositionId = item.positionId
|
|
453
643
|
},
|
|
454
|
-
addPostList(){
|
|
644
|
+
addPostList() {
|
|
455
645
|
if(!this.proPostList.length) return this.$Message.error("请选择组织节点!")
|
|
456
646
|
let proPostList = deepCopy(this.proPostList)
|
|
457
|
-
let checkedPosition = this.positiontList.
|
|
458
|
-
if(!checkedPosition
|
|
459
|
-
//summary data
|
|
647
|
+
let checkedPosition = this.positiontList.find(item => item.positionId === this.selectedPositionId);
|
|
648
|
+
if(!checkedPosition){return this.$Message.error("请选择岗位!!")}
|
|
649
|
+
// summary data
|
|
460
650
|
let totalList = []
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
id:this.includeLevelPost.includes('01')?('01'+'-'+it.positionId + '-' + item.orgUnitId):('00'+'-'+it.positionId + '-' + item.orgUnitId),
|
|
469
|
-
})
|
|
651
|
+
proPostList.forEach(item=>{
|
|
652
|
+
totalList.push({
|
|
653
|
+
...item,
|
|
654
|
+
...checkedPosition,
|
|
655
|
+
title:`${this.includeLevelPost.length?(item.orgNodeName+'(包含下级组织节点)'):item.orgNodeName}`,
|
|
656
|
+
includeLevel:this.includeLevelPost.includes('01'),
|
|
657
|
+
id:this.includeLevelPost.includes('01')?('01'+'-'+checkedPosition.positionId + '-' + item.orgUnitId):('00'+'-'+checkedPosition.positionId + '-' + item.orgUnitId),
|
|
470
658
|
})
|
|
471
|
-
}
|
|
659
|
+
})
|
|
472
660
|
let list = this.postList.concat(totalList)
|
|
473
661
|
let uniqueArray = list.filter((item, index, self) =>
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
662
|
+
index === self.findIndex(t => (
|
|
663
|
+
t.id === item.id // 基于id属性去重
|
|
664
|
+
))
|
|
477
665
|
);
|
|
478
666
|
this.postList = uniqueArray
|
|
667
|
+
|
|
668
|
+
// 新增:清空岗位树节点选中状态(关键优化)
|
|
669
|
+
if (this.$refs.postTree && this.$refs.postTree.clearAllChecked) {
|
|
670
|
+
this.$refs.postTree.clearAllChecked(this.$refs.postTree.data);
|
|
671
|
+
this.$refs.postTree.$emit('handleChange', []); // 通知父组件清空暂存列表
|
|
672
|
+
}
|
|
673
|
+
|
|
479
674
|
this.$refs.postTree.upDataTree()
|
|
480
675
|
this.proPostList = []
|
|
481
|
-
this.
|
|
676
|
+
this.selectedPositionId = null; // 清空岗位单选状态
|
|
677
|
+
this.$refs.postTree.initData()
|
|
678
|
+
this.selectedPostTagKey = '';
|
|
482
679
|
},
|
|
483
680
|
clearPost(){
|
|
484
681
|
this.postList= []
|
|
@@ -488,15 +685,16 @@ export default {
|
|
|
488
685
|
},
|
|
489
686
|
//staff
|
|
490
687
|
addStaffList(){
|
|
491
|
-
let staffList = this.staffAllList.filter((item)=>item.checked===true)
|
|
492
|
-
|
|
493
|
-
let
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
))
|
|
688
|
+
let staffList = this.staffAllList.filter((item)=>item.checked===true);
|
|
689
|
+
// 关键优化:基于item.id去重,不修改原数据字段,仅过滤重复项
|
|
690
|
+
let uniqueStaffList = staffList.filter(newItem =>
|
|
691
|
+
// 检查右侧条件区域(staffList)是否已有该人员,避免重复添加
|
|
692
|
+
!this.staffList.some(existItem => existItem.id === newItem.id)
|
|
497
693
|
);
|
|
498
|
-
|
|
499
|
-
this.
|
|
694
|
+
// 合并去重后的列表,右侧条件区域数据结构完全不变
|
|
695
|
+
this.staffList = this.staffList.concat(uniqueStaffList);
|
|
696
|
+
// 清空选中状态(保持原有逻辑)
|
|
697
|
+
this.staffAllList.forEach(item => item.checked = false);
|
|
500
698
|
},
|
|
501
699
|
handlestaff(item){
|
|
502
700
|
item.checked=!item.checked
|
|
@@ -518,9 +716,537 @@ export default {
|
|
|
518
716
|
visibleChange(val){
|
|
519
717
|
this.$emit('input',val);
|
|
520
718
|
},
|
|
521
|
-
|
|
522
|
-
|
|
719
|
+
// 处理Tab切换,同步更新tabName
|
|
720
|
+
handleTabChange(tabName) {
|
|
721
|
+
this.tabName = tabName;
|
|
722
|
+
// 可选:切换到岗位Tab时,初始化岗位树选中状态(避免残留组织Tab数据)
|
|
723
|
+
if (tabName === 'post' && this.$refs.postTree && this.proPostList.length === 0) {
|
|
724
|
+
this.proPostList = [];
|
|
725
|
+
this.$refs.postTree.initData();
|
|
726
|
+
}
|
|
727
|
+
},
|
|
728
|
+
async fastChedkOrg(item) {
|
|
729
|
+
// 1. 改用组织 Tab 专属标签列表判断
|
|
730
|
+
if (!this.orgTagList.length) {
|
|
731
|
+
this.$Message.warning("快捷选择标签正在加载中,请稍后");
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
const treeRef = this.$refs.orgTree;
|
|
735
|
+
const proListKey = 'proOrgList';
|
|
736
|
+
const treeDataKey = 'orgTree';
|
|
737
|
+
this[proListKey] = [];
|
|
738
|
+
if (treeRef) await treeRef.initData();
|
|
739
|
+
if (!treeRef) {
|
|
740
|
+
this.$Message.error("组织树组件未初始化,请稍后再试");
|
|
741
|
+
item.checked = !item.checked;
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// 2. 清空组织 Tab 标签选中状态(仅操作 orgTagList)
|
|
746
|
+
this.orgTagList.forEach(tag => {
|
|
747
|
+
tag.checked = false;
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
// 3. 后续逻辑保持不变,仅操作当前 item(属于 orgTagList)
|
|
751
|
+
const isCurrentlyChecked = item.checked;
|
|
752
|
+
if (!isCurrentlyChecked) {
|
|
753
|
+
item.checked = true;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
if (!item.checked) {
|
|
757
|
+
this.$Message.info("已取消该快捷选择");
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
try {
|
|
762
|
+
await this.querySpecificParam(item, true, treeRef, proListKey, treeDataKey);
|
|
763
|
+
} catch (error) {
|
|
764
|
+
this.$Message.error(`快捷选择处理失败:${error.message.slice(0, 50)}`);
|
|
765
|
+
item.checked = !item.checked;
|
|
766
|
+
}
|
|
767
|
+
},
|
|
768
|
+
async fastChedkPost(item) {
|
|
769
|
+
// 1. 改用岗位 Tab 专属标签列表判断
|
|
770
|
+
if (!this.postTagList.length) {
|
|
771
|
+
this.$Message.warning("快捷选择标签正在加载中,请稍后");
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const isOrgTab = this.tabName === 'org';
|
|
776
|
+
const treeRef = isOrgTab ? this.$refs.orgTree : this.$refs.postTree;
|
|
777
|
+
const proListKey = isOrgTab ? 'proOrgList' : 'proPostList';
|
|
778
|
+
const treeDataKey = isOrgTab ? 'orgTree' : 'postTree';
|
|
779
|
+
|
|
780
|
+
this[proListKey] = [];
|
|
781
|
+
const isCurrentlyChecked = item.checked;
|
|
782
|
+
if (treeRef) await treeRef.initData();
|
|
783
|
+
|
|
784
|
+
// 2. 清空岗位 Tab 标签选中状态(仅操作 postTagList)
|
|
785
|
+
this.postTagList.forEach(tag => {
|
|
786
|
+
tag.checked = false;
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
// 3. 后续逻辑保持不变,仅操作当前 item(属于 postTagList)
|
|
790
|
+
if (!isCurrentlyChecked) {
|
|
791
|
+
item.checked = true;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
if (!item.checked) {
|
|
795
|
+
this.$Message.info("已取消该快捷选择");
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
try {
|
|
800
|
+
await this.querySpecificParam(item, isOrgTab, treeRef, proListKey, treeDataKey);
|
|
801
|
+
} catch (error) {
|
|
802
|
+
this.$Message.error(`快捷选择处理失败:${error.message.slice(0, 50)}`);
|
|
803
|
+
item.checked = !item.checked;
|
|
804
|
+
}
|
|
523
805
|
},
|
|
806
|
+
queryTagList() {
|
|
807
|
+
ajax.get('/pub-manage-server/pub/helpBoxTag/q/queryQuickPickTags').then((res) => {
|
|
808
|
+
if (res.data.code === 1) {
|
|
809
|
+
let baseTagList = res.data.data || [];
|
|
810
|
+
// 组织 Tab 标签:深拷贝并初始化选中状态
|
|
811
|
+
this.orgTagList = this.safeDeepCopy(baseTagList).map(item => ({
|
|
812
|
+
...item,
|
|
813
|
+
checked: false // 独立选中状态
|
|
814
|
+
}));
|
|
815
|
+
// 岗位 Tab 标签:深拷贝并初始化选中状态(与组织 Tab 完全独立)
|
|
816
|
+
this.postTagList = this.safeDeepCopy(baseTagList).map(item => ({
|
|
817
|
+
...item,
|
|
818
|
+
checked: false // 独立选中状态
|
|
819
|
+
}));
|
|
820
|
+
}
|
|
821
|
+
});
|
|
822
|
+
},
|
|
823
|
+
|
|
824
|
+
querySpecificParam: function (item, isOrgTab, treeRef, proListKey, treeDataKey) {
|
|
825
|
+
return new Promise((resolve, reject) => {
|
|
826
|
+
// 取消选中时处理(仅当手动点击已选中的标签时触发)
|
|
827
|
+
if (!item.checked) {
|
|
828
|
+
if (treeRef && treeRef.initData) {
|
|
829
|
+
treeRef.initData(); // 清空对应树组件
|
|
830
|
+
}
|
|
831
|
+
this[proListKey] = []; // 清空对应暂存列表
|
|
832
|
+
this.$Message.info("已取消该快捷选择");
|
|
833
|
+
resolve();
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// 请求快捷选择对应的orgUnitId列表
|
|
838
|
+
ajax.get(`/pub-manage-server/pub/helpBoxTag/q/querySpecificParam?quickPickKey=${item.quickPickKey}`)
|
|
839
|
+
.then(async (res) => {
|
|
840
|
+
if (res.data.code !== 1) {
|
|
841
|
+
this.$Message.error("获取快捷选择配置失败");
|
|
842
|
+
item.checked = !item.checked;
|
|
843
|
+
reject(new Error("获取配置失败"));
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
const orgUnitIds = res.data.data?.param || [];
|
|
848
|
+
const safeOrgUnitIds = Array.isArray(orgUnitIds) ? orgUnitIds : [];
|
|
849
|
+
if (safeOrgUnitIds.length === 0) {
|
|
850
|
+
this.$Message.warning("该快捷选择未配置任何组织节点");
|
|
851
|
+
item.checked = !item.checked;
|
|
852
|
+
reject(new Error("无配置节点"));
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// 校验树组件是否存在(通用校验)
|
|
857
|
+
if (!treeRef) {
|
|
858
|
+
this.$Message.error("树组件未初始化");
|
|
859
|
+
item.checked = !item.checked;
|
|
860
|
+
reject(new Error("树组件不存在"));
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
const originalTreeData = treeRef.data && treeRef.data.length ? this.safeDeepCopy(treeRef.data) : [];
|
|
864
|
+
const matchedNodes = [];
|
|
865
|
+
|
|
866
|
+
// 批量处理每个orgUnitId(循环逻辑不变,通用处理)
|
|
867
|
+
for (const orgUnitId of safeOrgUnitIds) {
|
|
868
|
+
try {
|
|
869
|
+
// 节点详情查询(复用原有方法)
|
|
870
|
+
let currentNode = await this.queryOrgNodeDetail(orgUnitId);
|
|
871
|
+
if (!currentNode || !currentNode.orgUnitId) {
|
|
872
|
+
this.$Message.warning(`节点【${orgUnitId}】数据异常,跳过处理`);
|
|
873
|
+
continue;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// 循环引用检测与处理(复用原有方法)
|
|
877
|
+
const cycle = this.detectCircularReferences(currentNode);
|
|
878
|
+
if (cycle) {
|
|
879
|
+
this.$Message.warning(`节点【${orgUnitId}】存在循环引用,已自动修复`);
|
|
880
|
+
currentNode = this.safeDeepCopy(currentNode);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// 节点名称默认值处理(复用原有逻辑)
|
|
884
|
+
currentNode.orgNodeName = currentNode.orgNodeName || currentNode.orgUnitName || `未命名组织(${orgUnitId})`;
|
|
885
|
+
currentNode.orgUnitName = currentNode.orgNodeName;
|
|
886
|
+
|
|
887
|
+
// 节点是否已在树中检测(复用原有方法)
|
|
888
|
+
const nodeInTree = this.findNodeInTree(originalTreeData, orgUnitId);
|
|
889
|
+
if (nodeInTree) {
|
|
890
|
+
if (this.detectCircularReferences(nodeInTree)) {
|
|
891
|
+
this.$Message.warning(`树中节点【${orgUnitId}】存在循环引用,已跳过`);
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
matchedNodes.push(nodeInTree);
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// 父节点追溯与节点链挂载(修复:使用当前Tab的树数据originalTreeData,而非固定orgTree)
|
|
899
|
+
let targetParent = null;
|
|
900
|
+
let nodeChain = [currentNode];
|
|
901
|
+
const MAX_DEPTH = 8;
|
|
902
|
+
let depth = 0;
|
|
903
|
+
let lastParentId = '';
|
|
904
|
+
|
|
905
|
+
while (!targetParent && depth < MAX_DEPTH) {
|
|
906
|
+
const parentId = currentNode.parentOrgUnitId;
|
|
907
|
+
if (!parentId || typeof parentId !== 'string' || parentId === lastParentId) {
|
|
908
|
+
break;
|
|
909
|
+
}
|
|
910
|
+
lastParentId = parentId;
|
|
911
|
+
|
|
912
|
+
const parentNode = await this.queryOrgNodeDetail(parentId);
|
|
913
|
+
if (!parentNode || !parentNode.orgUnitId) {
|
|
914
|
+
break;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
if (this.detectCircularReferences(parentNode)) {
|
|
918
|
+
this.$Message.warning(`父节点【${parentId}】存在循环引用,已跳过`);
|
|
919
|
+
break;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
parentNode.orgNodeName = parentNode.orgNodeName || parentNode.orgUnitName || `未命名组织(${parentId})`;
|
|
923
|
+
// 修复:查询父节点是否在当前Tab的树数据中(originalTreeData),而非固定orgTree
|
|
924
|
+
const parentInTree = this.findNodeInTree(originalTreeData, parentNode.orgUnitId);
|
|
925
|
+
|
|
926
|
+
if (parentInTree) {
|
|
927
|
+
targetParent = parentInTree;
|
|
928
|
+
break;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
nodeChain.unshift(parentNode);
|
|
932
|
+
currentNode = parentNode;
|
|
933
|
+
depth++;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// 节点链挂载(复用原有方法)
|
|
937
|
+
if (targetParent) {
|
|
938
|
+
let currentMountParent = targetParent;
|
|
939
|
+
for (const node of nodeChain) {
|
|
940
|
+
const formattedNode = this.safeDeepCopy({
|
|
941
|
+
...node,
|
|
942
|
+
leafNode: await this.judgeNodeLeafe(node.orgUnitId),
|
|
943
|
+
expand: true,
|
|
944
|
+
checked: false,
|
|
945
|
+
orgChildrenList: [],
|
|
946
|
+
children: []
|
|
947
|
+
});
|
|
948
|
+
|
|
949
|
+
if (!Array.isArray(currentMountParent.orgChildrenList)) {
|
|
950
|
+
currentMountParent.orgChildrenList = [];
|
|
951
|
+
currentMountParent.children = [];
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
const isDuplicate = currentMountParent.orgChildrenList.some(
|
|
955
|
+
child => child.orgUnitId === formattedNode.orgUnitId
|
|
956
|
+
);
|
|
957
|
+
if (!isDuplicate) {
|
|
958
|
+
currentMountParent.orgChildrenList.push(formattedNode);
|
|
959
|
+
currentMountParent.leafNode = false;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
currentMountParent = currentMountParent.orgChildrenList.find(
|
|
963
|
+
child => child.orgUnitId === formattedNode.orgUnitId
|
|
964
|
+
) || formattedNode;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
const mountedTarget = this.findNodeInTree(originalTreeData, orgUnitId);
|
|
968
|
+
if (mountedTarget) {
|
|
969
|
+
matchedNodes.push(mountedTarget);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
} catch (error) {
|
|
974
|
+
this.$Message.error(`处理节点【${orgUnitId}】失败:${error.message.slice(0, 50)}`);
|
|
975
|
+
continue;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
// 最终数据赋值(根据Tab类型区分,核心统一逻辑)
|
|
980
|
+
|
|
981
|
+
if (matchedNodes.length > 0) {
|
|
982
|
+
// 同步暂存列表(组织proOrgList/岗位proPostList)
|
|
983
|
+
this[proListKey] = matchedNodes;
|
|
984
|
+
const finalTreeData = this.safeDeepCopy(originalTreeData);
|
|
985
|
+
// 响应式更新对应树数据(组织orgTree/岗位postTree)
|
|
986
|
+
this.$set(this, treeDataKey, finalTreeData);
|
|
987
|
+
|
|
988
|
+
await this.$nextTick(async () => {
|
|
989
|
+
// 更新树组件数据(通用逻辑)
|
|
990
|
+
if (treeRef.updateTreeData) {
|
|
991
|
+
treeRef.updateTreeData(this.safeDeepCopy(finalTreeData));
|
|
992
|
+
} else {
|
|
993
|
+
treeRef.data = this.safeDeepCopy(finalTreeData);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// 设置树选中状态(通用逻辑)
|
|
997
|
+
const safeNodeIds = matchedNodes.map(node => node.orgUnitId);
|
|
998
|
+
if (treeRef.setCheckedNodes) {
|
|
999
|
+
treeRef.setCheckedNodes([]);
|
|
1000
|
+
treeRef.setCheckedNodes(safeNodeIds);
|
|
1001
|
+
treeRef.handleChange(matchedNodes); // 触发子组件选中事件
|
|
1002
|
+
} else {
|
|
1003
|
+
this.updateTreeNodesStatus(
|
|
1004
|
+
finalTreeData,
|
|
1005
|
+
new Set(safeNodeIds),
|
|
1006
|
+
new Set(safeNodeIds)
|
|
1007
|
+
);
|
|
1008
|
+
treeRef.handleChange(matchedNodes);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// 提示信息(通用逻辑)
|
|
1012
|
+
this.$Message.success(`成功选中${matchedNodes.length}个组织节点`);
|
|
1013
|
+
});
|
|
1014
|
+
} else {
|
|
1015
|
+
this.$Message.warning("未找到任何可匹配的组织节点");
|
|
1016
|
+
item.checked = !item.checked;
|
|
1017
|
+
}
|
|
1018
|
+
resolve();
|
|
1019
|
+
})
|
|
1020
|
+
.catch((error) => {
|
|
1021
|
+
const errMsg = error.message || '网络异常';
|
|
1022
|
+
this.$Message.error(`快捷选择失败:${errMsg.slice(0, 50)}`);
|
|
1023
|
+
item.checked = !item.checked;
|
|
1024
|
+
reject(error);
|
|
1025
|
+
});
|
|
1026
|
+
});
|
|
1027
|
+
},
|
|
1028
|
+
|
|
1029
|
+
// 新增:移除树数据中的 parent 引用,避免子组件处理时形成循环
|
|
1030
|
+
removeParentReferences(treeData) {
|
|
1031
|
+
if (!Array.isArray(treeData)) return;
|
|
1032
|
+
treeData.forEach(node => {
|
|
1033
|
+
// 删除可能存在的 parent 引用(如果节点有此属性)
|
|
1034
|
+
if (node.parent) delete node.parent;
|
|
1035
|
+
// 递归处理子节点
|
|
1036
|
+
if (Array.isArray(node.orgChildrenList)) {
|
|
1037
|
+
this.removeParentReferences(node.orgChildrenList);
|
|
1038
|
+
}
|
|
1039
|
+
if (Array.isArray(node.children)) {
|
|
1040
|
+
this.removeParentReferences(node.children);
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
},
|
|
1044
|
+
// 在 methods 中添加循环引用检测工具函数
|
|
1045
|
+
detectCircularReferences(obj, path = [], visited = new Map()) {
|
|
1046
|
+
if (obj === null || typeof obj !== 'object') return null;
|
|
1047
|
+
|
|
1048
|
+
if (visited.has(obj)) {
|
|
1049
|
+
return { path: [...path, visited.get(obj)], node: obj };
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
visited.set(obj, Array.isArray(obj) ? 'Array' : `Object(${obj.orgUnitId || 'unknown'})`);
|
|
1053
|
+
|
|
1054
|
+
const keys = Array.isArray(obj) ? obj.map((_, i) => i) : Object.keys(obj);
|
|
1055
|
+
for (const key of keys) {
|
|
1056
|
+
const value = obj[key];
|
|
1057
|
+
const newPath = [...path, key];
|
|
1058
|
+
if (value && typeof value === 'object') {
|
|
1059
|
+
const cycle = this.detectCircularReferences(value, newPath, visited);
|
|
1060
|
+
if (cycle) return cycle;
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
visited.delete(obj);
|
|
1065
|
+
return null;
|
|
1066
|
+
},
|
|
1067
|
+
|
|
1068
|
+
// 增强安全深拷贝,主动移除循环引用字段
|
|
1069
|
+
safeDeepCopy(obj, hash = new WeakMap()) {
|
|
1070
|
+
if (obj === null || typeof obj !== 'object') {
|
|
1071
|
+
return obj;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// 检测到循环引用时,仅保留基础字段
|
|
1075
|
+
if (hash.has(obj)) {
|
|
1076
|
+
const safeObj = { orgUnitId: obj.orgUnitId, name: obj.name || obj.orgNodeName };
|
|
1077
|
+
hash.set(obj, safeObj);
|
|
1078
|
+
return safeObj;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
let copy;
|
|
1082
|
+
if (obj instanceof Array) {
|
|
1083
|
+
copy = [];
|
|
1084
|
+
hash.set(obj, copy);
|
|
1085
|
+
for (let i = 0; i < obj.length; i++) {
|
|
1086
|
+
copy[i] = this.safeDeepCopy(obj[i], hash);
|
|
1087
|
+
}
|
|
1088
|
+
} else if (obj instanceof Object) {
|
|
1089
|
+
copy = {};
|
|
1090
|
+
hash.set(obj, copy);
|
|
1091
|
+
for (let key in obj) {
|
|
1092
|
+
if (obj.hasOwnProperty(key)) {
|
|
1093
|
+
// 严格过滤可能导致循环的字段
|
|
1094
|
+
if (['parent', '__parent', 'parentNode', '__vue__', '__ob__', '$parent'].includes(key)) {
|
|
1095
|
+
continue;
|
|
1096
|
+
}
|
|
1097
|
+
// 对子节点数组进行特殊处理
|
|
1098
|
+
if (['orgChildrenList', 'children'].includes(key) && Array.isArray(obj[key])) {
|
|
1099
|
+
copy[key] = obj[key].map(child => this.safeDeepCopy(child, hash));
|
|
1100
|
+
} else {
|
|
1101
|
+
copy[key] = this.safeDeepCopy(obj[key], hash);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
} else {
|
|
1106
|
+
copy = obj;
|
|
1107
|
+
}
|
|
1108
|
+
return copy;
|
|
1109
|
+
},
|
|
1110
|
+
|
|
1111
|
+
/**
|
|
1112
|
+
* 查询单个节点的原生详情(适配后端接口,返回原生字段)
|
|
1113
|
+
* @param {string} orgUnitId - 节点ID(如11510101)
|
|
1114
|
+
* @returns {Promise<Object|null>} 原生节点详情(无数据返回null)
|
|
1115
|
+
*/
|
|
1116
|
+
getOrgNodeDetail(orgUnitId) {
|
|
1117
|
+
return new Promise((resolve) => {
|
|
1118
|
+
ajax.get(`/pub-manage-server/pub/organ/q/queryOrg`, {
|
|
1119
|
+
params: { orgUnitId: orgUnitId }
|
|
1120
|
+
}).then((res) => {
|
|
1121
|
+
if (res.data.code === 1 && res.data.data) {
|
|
1122
|
+
// 仅保留原生树结构必需字段(过滤业务冗余字段)
|
|
1123
|
+
const { orgUnitId, parentOrgUnitId, orgNodeName, orgChildrenList } = res.data.data;
|
|
1124
|
+
resolve({
|
|
1125
|
+
orgUnitId,
|
|
1126
|
+
parentOrgUnitId,
|
|
1127
|
+
orgNodeName,
|
|
1128
|
+
orgChildrenList: orgChildrenList || [] // 兼容接口返回空的情况
|
|
1129
|
+
});
|
|
1130
|
+
} else {
|
|
1131
|
+
resolve(null);
|
|
1132
|
+
}
|
|
1133
|
+
}).catch(() => {
|
|
1134
|
+
this.$Message.error(`查询节点【${orgUnitId}】详情失败`);
|
|
1135
|
+
resolve(null);
|
|
1136
|
+
});
|
|
1137
|
+
});
|
|
1138
|
+
},
|
|
1139
|
+
|
|
1140
|
+
/**
|
|
1141
|
+
* 迭代查找节点(替代递归,避免栈溢出)
|
|
1142
|
+
* @param {Array} treeData - 树形数据
|
|
1143
|
+
* @param {string} targetOrgUnitId - 目标节点ID
|
|
1144
|
+
* @returns {Object|null} 找到的节点
|
|
1145
|
+
*/
|
|
1146
|
+
findNodeInTree(treeData, targetOrgUnitId) {
|
|
1147
|
+
if (!Array.isArray(treeData) || !targetOrgUnitId || typeof targetOrgUnitId !== 'string') {
|
|
1148
|
+
return null;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
// 用队列实现广度优先搜索(BFS),避免递归栈溢出
|
|
1152
|
+
const queue = [...treeData];
|
|
1153
|
+
while (queue.length > 0) {
|
|
1154
|
+
const currentNode = queue.shift(); // 取出队列头部节点
|
|
1155
|
+
// 找到目标节点,直接返回
|
|
1156
|
+
if (currentNode.orgUnitId === targetOrgUnitId) {
|
|
1157
|
+
return currentNode;
|
|
1158
|
+
}
|
|
1159
|
+
// 将当前节点的子节点加入队列(继续查找)
|
|
1160
|
+
if (Array.isArray(currentNode.orgChildrenList) && currentNode.orgChildrenList.length > 0) {
|
|
1161
|
+
queue.push(...currentNode.orgChildrenList);
|
|
1162
|
+
}
|
|
1163
|
+
// 兼容子组件可能使用的children字段
|
|
1164
|
+
if (Array.isArray(currentNode.children) && currentNode.children.length > 0 && !currentNode.orgChildrenList) {
|
|
1165
|
+
queue.push(...currentNode.children);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
// 遍历完所有节点未找到
|
|
1170
|
+
return null;
|
|
1171
|
+
},
|
|
1172
|
+
|
|
1173
|
+
/**
|
|
1174
|
+
* 辅助方法:查询单个orgUnitId的节点详情
|
|
1175
|
+
* @param {string} orgUnitId - 组织节点ID
|
|
1176
|
+
* @returns {Promise<Object|null>} 节点详情
|
|
1177
|
+
*/
|
|
1178
|
+
queryOrgNodeDetail(orgUnitId) {
|
|
1179
|
+
return new Promise((resolve) => {
|
|
1180
|
+
// 前置校验:避免无效请求
|
|
1181
|
+
if (!orgUnitId || typeof orgUnitId !== 'string') {
|
|
1182
|
+
resolve(null);
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
ajax.get(`/pub-manage-server/pub/organ/q/queryOrg?orgUnitId=${orgUnitId}`)
|
|
1187
|
+
.then((res) => {
|
|
1188
|
+
if (res.data.code === 1 && res.data.data && res.data.data.orgUnitId) {
|
|
1189
|
+
// 深拷贝并清理无效字段
|
|
1190
|
+
const node = this.safeDeepCopy(res.data.data);
|
|
1191
|
+
// 核心修复:确保节点名称有默认值,避免空显示
|
|
1192
|
+
const nodeName = node.orgNodeName || node.orgUnitName || node.name || `未命名组织(${orgUnitId})`;
|
|
1193
|
+
node.orgNodeName = nodeName;
|
|
1194
|
+
node.orgUnitName = nodeName; // 同步更新orgUnitName,确保所有使用场景都有值
|
|
1195
|
+
// 确保关键字段存在
|
|
1196
|
+
node.parentOrgUnitId = node.parentOrgUnitId || null;
|
|
1197
|
+
node.orgChildrenList = node.orgChildrenList || [];
|
|
1198
|
+
resolve(node);
|
|
1199
|
+
} else {
|
|
1200
|
+
// 接口返回空时,返回默认结构避免报错
|
|
1201
|
+
resolve({
|
|
1202
|
+
orgUnitId,
|
|
1203
|
+
orgNodeName: `未命名组织(${orgUnitId})`,
|
|
1204
|
+
orgUnitName: `未命名组织(${orgUnitId})`,
|
|
1205
|
+
parentOrgUnitId: null,
|
|
1206
|
+
orgChildrenList: []
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
})
|
|
1210
|
+
.catch(() => {
|
|
1211
|
+
// 请求失败时,返回默认结构避免报错
|
|
1212
|
+
resolve({
|
|
1213
|
+
orgUnitId,
|
|
1214
|
+
orgNodeName: `未命名组织(${orgUnitId})`,
|
|
1215
|
+
orgUnitName: `未命名组织(${orgUnitId})`,
|
|
1216
|
+
parentOrgUnitId: null,
|
|
1217
|
+
orgChildrenList: []
|
|
1218
|
+
});
|
|
1219
|
+
});
|
|
1220
|
+
});
|
|
1221
|
+
},
|
|
1222
|
+
|
|
1223
|
+
|
|
1224
|
+
/**
|
|
1225
|
+
* 手动更新树节点状态(如果树组件没有提供update方法)
|
|
1226
|
+
* @param {Array} nodeList - 节点列表
|
|
1227
|
+
* @param {Set} expandIds - 需要展开的节点ID集合
|
|
1228
|
+
* @param {Set} checkedIds - 需要选中的节点ID集合
|
|
1229
|
+
*/
|
|
1230
|
+
updateTreeNodesStatus(nodeList, expandIds, checkedIds) {
|
|
1231
|
+
nodeList.forEach(node => {
|
|
1232
|
+
// 更新展开状态
|
|
1233
|
+
if (expandIds.has(node.orgUnitId)) {
|
|
1234
|
+
node.expand = true;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// 更新选中状态
|
|
1238
|
+
if (checkedIds.has(node.orgUnitId)) {
|
|
1239
|
+
node.checked = true;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
// 递归处理子节点
|
|
1243
|
+
const children = node.children || node.orgChildrenList;
|
|
1244
|
+
if (children && children.length > 0) {
|
|
1245
|
+
this.updateTreeNodesStatus(children, expandIds, checkedIds);
|
|
1246
|
+
}
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
|
|
524
1250
|
},
|
|
525
1251
|
computed:{
|
|
526
1252
|
getCheckedStaff(){
|
|
@@ -562,6 +1288,64 @@ export default {
|
|
|
562
1288
|
}
|
|
563
1289
|
</script>
|
|
564
1290
|
<style lang="less" scoped>
|
|
1291
|
+
.custom-select-wrapper {
|
|
1292
|
+
position: relative;
|
|
1293
|
+
|
|
1294
|
+
/* 优化清空按钮样式 */
|
|
1295
|
+
/deep/ .ivu-select-clear {
|
|
1296
|
+
right: 32px; /* 调整位置,避免与下拉箭头重叠 */
|
|
1297
|
+
color: #999;
|
|
1298
|
+
font-size: 16px;
|
|
1299
|
+
opacity: 1 !important; /* 始终显示(有值时) */
|
|
1300
|
+
transition: all 0.2s ease;
|
|
1301
|
+
|
|
1302
|
+
&:hover {
|
|
1303
|
+
color: #ff4d4f; /* hover时变红 */
|
|
1304
|
+
transform: scale(1.1); /* 轻微放大效果 */
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
/* 调整下拉箭头位置 */
|
|
1309
|
+
/deep/ .ivu-select-arrow {
|
|
1310
|
+
right: 12px;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
/* 优化输入框内边距,避免文字被按钮遮挡 */
|
|
1314
|
+
/deep/ .ivu-select-input {
|
|
1315
|
+
padding-right: 45px !important;
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
/* 选中选项样式 */
|
|
1319
|
+
/deep/ .active-option {
|
|
1320
|
+
background-color: #f2f8ff;
|
|
1321
|
+
color: var(--primary-color);
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
/* 无数据选项样式 */
|
|
1325
|
+
/deep/ .ivu-select-option-disabled {
|
|
1326
|
+
color: #ccc !important;
|
|
1327
|
+
background: #f5f5f5 !important;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
.tag-select-container {
|
|
1331
|
+
margin-left: 10px;
|
|
1332
|
+
|
|
1333
|
+
/deep/ .ivu-select-dropdown {
|
|
1334
|
+
max-height: 200px;
|
|
1335
|
+
overflow-y: auto;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
/deep/ .active-option {
|
|
1339
|
+
background-color: #f2f8ff;
|
|
1340
|
+
color: var(--primary-color);
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
/deep/ .ivu-select-selected-value {
|
|
1344
|
+
max-width: 150px;
|
|
1345
|
+
overflow: hidden;
|
|
1346
|
+
text-overflow: ellipsis;
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
565
1349
|
.modal-tree{
|
|
566
1350
|
.header-text{
|
|
567
1351
|
font-weight: bold;
|
|
@@ -652,10 +1436,10 @@ export default {
|
|
|
652
1436
|
.tag{
|
|
653
1437
|
margin-top: 10px;
|
|
654
1438
|
&.active{
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
1439
|
+
background: var(--primary-color);
|
|
1440
|
+
border-radius: 4px;
|
|
1441
|
+
color: #fff;
|
|
1442
|
+
}
|
|
659
1443
|
}
|
|
660
1444
|
}
|
|
661
1445
|
.post{
|