@sy-common/organize-select-help 1.0.0-beta.10 → 1.0.0-beta.14
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 +957 -174
- 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>
|
|
@@ -75,36 +112,40 @@
|
|
|
75
112
|
</div>
|
|
76
113
|
</div>
|
|
77
114
|
</TabPane>
|
|
78
|
-
<TabPane label="选择人员" name="staff">
|
|
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
|
-
<div>
|
|
148
|
+
<div v-if="name.includes('org')">
|
|
108
149
|
<div class="group-box flex-r-bt">
|
|
109
150
|
<div>组织节点条件:</div>
|
|
110
151
|
<div class="clear-btn" @click="clearGroup">清除全部</div>
|
|
@@ -118,7 +159,7 @@
|
|
|
118
159
|
</div>
|
|
119
160
|
</div>
|
|
120
161
|
<!-- 组织节点+岗位条件://-->
|
|
121
|
-
<div>
|
|
162
|
+
<div v-if="name.includes('post')">
|
|
122
163
|
<div class="group-box flex-r-bt">
|
|
123
164
|
<div>岗位条件+组织节点:</div>
|
|
124
165
|
<div class="clear-btn" @click="clearPost">清除全部</div>
|
|
@@ -132,7 +173,7 @@
|
|
|
132
173
|
</div>
|
|
133
174
|
</div>
|
|
134
175
|
<!-- 直接选择人员:-->
|
|
135
|
-
<div>
|
|
176
|
+
<div v-if="name.includes('staff')">
|
|
136
177
|
<div class="group-box flex-r-bt">
|
|
137
178
|
<div>直接选择人员:</div>
|
|
138
179
|
<div class="clear-btn" @click="clearStaff">清除全部</div>
|
|
@@ -152,11 +193,9 @@
|
|
|
152
193
|
</Modal>
|
|
153
194
|
</template>
|
|
154
195
|
<script>
|
|
155
|
-
import { Icon } from '@lambo-design/core'
|
|
156
196
|
import organizeTree from './organize-tree.vue'
|
|
157
197
|
import { deepCopy } from '@lambo-design/core/src/utils/assist'
|
|
158
198
|
import ajax from '@lambo-design/shared/utils/ajax'
|
|
159
|
-
import { debounce } from '@sy-common/core/src/utils/assits'
|
|
160
199
|
export default {
|
|
161
200
|
components: { organizeTree },
|
|
162
201
|
props: {
|
|
@@ -166,17 +205,21 @@ export default {
|
|
|
166
205
|
},
|
|
167
206
|
data:{
|
|
168
207
|
type:Object,
|
|
169
|
-
default:{}
|
|
208
|
+
default:()=>{}
|
|
170
209
|
},
|
|
171
210
|
name:{
|
|
172
|
-
|
|
173
|
-
default
|
|
211
|
+
type:Array ,
|
|
212
|
+
default(){return ['org','post','staff']}
|
|
213
|
+
},
|
|
214
|
+
isQuickOpen:{
|
|
215
|
+
type: Boolean,
|
|
216
|
+
default: false
|
|
174
217
|
}
|
|
175
218
|
},
|
|
176
219
|
data(){
|
|
177
220
|
return {
|
|
178
|
-
tabName:this.name,
|
|
179
|
-
modal:
|
|
221
|
+
tabName:this.name[0],
|
|
222
|
+
modal:true,
|
|
180
223
|
dialogOpen:false,
|
|
181
224
|
orgSearch:'',
|
|
182
225
|
//组织--right-form
|
|
@@ -203,38 +246,109 @@ export default {
|
|
|
203
246
|
lastLoadingTime:0,
|
|
204
247
|
offset:0,
|
|
205
248
|
staffEnding:false,
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
checked:false,
|
|
213
|
-
key:'08'
|
|
214
|
-
},{
|
|
215
|
-
v:'所有市公司',
|
|
216
|
-
checked:false,
|
|
217
|
-
key:'9'
|
|
218
|
-
},{
|
|
219
|
-
v:'所有市公司',
|
|
220
|
-
checked:false,
|
|
221
|
-
key:'10'
|
|
222
|
-
},{
|
|
223
|
-
v:'所有市公司',
|
|
224
|
-
checked:false,
|
|
225
|
-
key:'11'
|
|
226
|
-
},{
|
|
227
|
-
v:'所有市公司',
|
|
228
|
-
checked:false,
|
|
229
|
-
key:'12'
|
|
230
|
-
}],
|
|
249
|
+
parentOrgList: [],
|
|
250
|
+
orgTagList:[],
|
|
251
|
+
postTagList:[],
|
|
252
|
+
selectedPositionId: null, // 新增:岗位单选的选中状态存储
|
|
253
|
+
selectedOrgTagKey: '', // 存储选中标签的唯一标识
|
|
254
|
+
selectedPostTagKey: '', // 存储选中标签的唯一标识
|
|
231
255
|
}
|
|
232
256
|
},
|
|
233
257
|
mounted() {
|
|
258
|
+
this.queryTagList()
|
|
234
259
|
this.queryPositionList()
|
|
235
260
|
this.loadMore()
|
|
236
261
|
},
|
|
237
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
|
+
},
|
|
238
352
|
queryPositionList(){
|
|
239
353
|
ajax.get('/pub-manage-server/pub/personHelpBox/q/queryPositionList').then((res)=>{
|
|
240
354
|
if(res.data.code === 1){
|
|
@@ -248,64 +362,120 @@ export default {
|
|
|
248
362
|
if (query !== '') {
|
|
249
363
|
this.loadingOrg = true;
|
|
250
364
|
this.getOrgUnitBySearchTerm(query,(res)=>{
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
+
}
|
|
259
373
|
})
|
|
260
374
|
}else{
|
|
261
375
|
this.orgSearchList = [];
|
|
262
376
|
}
|
|
263
377
|
},
|
|
264
|
-
getPostListBySearch(query){
|
|
378
|
+
getPostListBySearch(query) {
|
|
265
379
|
if (query !== '') {
|
|
266
380
|
this.loadingPost = true;
|
|
267
|
-
this.getOrgUnitBySearchTerm(query,(res)=>{
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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一致)
|
|
279
394
|
this.postSearchList = [];
|
|
395
|
+
if (this.$refs.postTree) {
|
|
396
|
+
this.$refs.postTree.initData();
|
|
397
|
+
}
|
|
398
|
+
this.proPostList = []; // 清空岗位暂存列表
|
|
399
|
+
this.$set(this, 'postTree', []); // 清空岗位树数据
|
|
280
400
|
}
|
|
281
401
|
},
|
|
282
402
|
getOrgUnitBySearchTerm(query,callback){
|
|
283
|
-
|
|
403
|
+
ajax.get('/pub-manage-server/pub/personHelpBox/q/getOrgUnitBySearchTerm?searchTerm='+query).then((res)=>{callback(res)})
|
|
284
404
|
},
|
|
285
|
-
async getOrgOption(val){
|
|
405
|
+
async getOrgOption(val) {
|
|
406
|
+
this.selectedOrgTagKey = '';
|
|
407
|
+
this.orgTagList.forEach(tag => {
|
|
408
|
+
tag.checked = false;
|
|
409
|
+
});
|
|
410
|
+
this.proOrgList = [];
|
|
286
411
|
if(!val) return this.$refs.orgTree.initData();
|
|
287
|
-
let item = this.orgSearchList.filter((item)=>
|
|
412
|
+
let item = this.orgSearchList.filter((item)=> item.orgUnitId === val).shift();
|
|
413
|
+
if (!item) return;
|
|
414
|
+
|
|
288
415
|
const leafNode = await this.judgeNodeLeafe(item.orgUnitId);
|
|
289
|
-
const ftem = {
|
|
416
|
+
const ftem = this.safeDeepCopy({
|
|
417
|
+
...item,
|
|
418
|
+
orgNodeName: item.name,
|
|
419
|
+
parentOrgUnitId: item.parentId,
|
|
420
|
+
leafNode: leafNode
|
|
421
|
+
});
|
|
422
|
+
|
|
290
423
|
try{
|
|
291
|
-
let parentsList = await this.getParentOrgNodesByOrgUnitId(val)
|
|
292
|
-
|
|
293
|
-
|
|
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);
|
|
294
429
|
}catch(e){
|
|
295
|
-
this.$Message.error("获取组织节点列表失败!")
|
|
430
|
+
this.$Message.error("获取组织节点列表失败!");
|
|
296
431
|
}
|
|
297
432
|
},
|
|
298
|
-
async getPostOption(val){
|
|
299
|
-
|
|
300
|
-
|
|
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
|
+
|
|
301
454
|
const leafNode = await this.judgeNodeLeafe(item.orgUnitId);
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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("获取组织节点列表失败!");
|
|
309
479
|
}
|
|
310
480
|
},
|
|
311
481
|
getParentOrgNodesByOrgUnitId(val){
|
|
@@ -341,27 +511,28 @@ export default {
|
|
|
341
511
|
})
|
|
342
512
|
},
|
|
343
513
|
buildTree(items) {
|
|
344
|
-
// 创建一个映射表,用于快速查找节点
|
|
345
514
|
const map = {};
|
|
346
515
|
const roots = [];
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
516
|
+
// 对输入数据进行安全拷贝
|
|
517
|
+
const copiedItems = this.safeDeepCopy(items);
|
|
518
|
+
|
|
519
|
+
copiedItems.forEach(item => {
|
|
520
|
+
if (!item.orgUnitId) return; // 过滤无效节点
|
|
521
|
+
map[item.orgUnitId] = this.safeDeepCopy({
|
|
522
|
+
...item,
|
|
523
|
+
orgChildrenList: []
|
|
524
|
+
});
|
|
351
525
|
});
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
526
|
+
|
|
527
|
+
copiedItems.forEach(item => {
|
|
528
|
+
if (!item.orgUnitId || !map[item.orgUnitId]) return;
|
|
529
|
+
|
|
355
530
|
const node = map[item.orgUnitId];
|
|
356
|
-
if (item.parentOrgUnitId
|
|
357
|
-
// 根节点
|
|
531
|
+
if (!item.parentOrgUnitId || !map[item.parentOrgUnitId]) {
|
|
358
532
|
roots.push(node);
|
|
359
533
|
} else {
|
|
360
|
-
// 子节点,找到父节点并添加到其children中
|
|
361
534
|
const parent = map[item.parentOrgUnitId];
|
|
362
|
-
|
|
363
|
-
parent.orgChildrenList.push(node);
|
|
364
|
-
}
|
|
535
|
+
parent.orgChildrenList.push(node);
|
|
365
536
|
}
|
|
366
537
|
});
|
|
367
538
|
return roots;
|
|
@@ -420,12 +591,21 @@ export default {
|
|
|
420
591
|
}
|
|
421
592
|
},
|
|
422
593
|
getOrgList(data){
|
|
423
|
-
this.proOrgList
|
|
594
|
+
if (this.proOrgList){
|
|
595
|
+
this.proOrgList = this.proOrgList.concat(data)
|
|
596
|
+
}else {
|
|
597
|
+
this.proOrgList = data
|
|
598
|
+
}
|
|
599
|
+
|
|
424
600
|
},
|
|
425
601
|
getPostList(data){
|
|
426
|
-
this.proPostList
|
|
602
|
+
if (this.proPostList){
|
|
603
|
+
this.proPostList = this.proPostList.concat(data)
|
|
604
|
+
}else {
|
|
605
|
+
this.proPostList = data
|
|
606
|
+
}
|
|
427
607
|
},
|
|
428
|
-
addOrgList(){
|
|
608
|
+
addOrgList() {
|
|
429
609
|
if(!this.proOrgList.length) return this.$Message.error("请先选择组织节点!")
|
|
430
610
|
let proOrgList = deepCopy(this.proOrgList)
|
|
431
611
|
proOrgList.forEach(item=>{
|
|
@@ -435,11 +615,18 @@ export default {
|
|
|
435
615
|
})
|
|
436
616
|
let list = this.orgList.concat(proOrgList)
|
|
437
617
|
let uniqueArray = list.filter((item, index, self) =>
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
618
|
+
index === self.findIndex(t => (
|
|
619
|
+
t.id === item.id // 基于id属性去重
|
|
620
|
+
))
|
|
441
621
|
);
|
|
442
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
|
+
|
|
443
630
|
this.$refs.orgTree.upDataTree()
|
|
444
631
|
this.proOrgList = []
|
|
445
632
|
},
|
|
@@ -451,36 +638,44 @@ export default {
|
|
|
451
638
|
},
|
|
452
639
|
//岗位
|
|
453
640
|
getPosionId(item){
|
|
454
|
-
item.checked = !item.checked;
|
|
641
|
+
// item.checked = !item.checked;
|
|
642
|
+
this.selectedPositionId = item.positionId
|
|
455
643
|
},
|
|
456
|
-
addPostList(){
|
|
644
|
+
addPostList() {
|
|
457
645
|
if(!this.proPostList.length) return this.$Message.error("请选择组织节点!")
|
|
458
646
|
let proPostList = deepCopy(this.proPostList)
|
|
459
|
-
let checkedPosition = this.positiontList.
|
|
460
|
-
if(!checkedPosition
|
|
461
|
-
//summary data
|
|
647
|
+
let checkedPosition = this.positiontList.find(item => item.positionId === this.selectedPositionId);
|
|
648
|
+
if(!checkedPosition){return this.$Message.error("请选择岗位!!")}
|
|
649
|
+
// summary data
|
|
462
650
|
let totalList = []
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
id:this.includeLevelPost.includes('01')?('01'+'-'+it.positionId + '-' + item.orgUnitId):('00'+'-'+it.positionId + '-' + item.orgUnitId),
|
|
471
|
-
})
|
|
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),
|
|
472
658
|
})
|
|
473
|
-
}
|
|
659
|
+
})
|
|
474
660
|
let list = this.postList.concat(totalList)
|
|
475
661
|
let uniqueArray = list.filter((item, index, self) =>
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
662
|
+
index === self.findIndex(t => (
|
|
663
|
+
t.id === item.id // 基于id属性去重
|
|
664
|
+
))
|
|
479
665
|
);
|
|
480
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
|
+
|
|
481
674
|
this.$refs.postTree.upDataTree()
|
|
482
675
|
this.proPostList = []
|
|
483
|
-
this.
|
|
676
|
+
this.selectedPositionId = null; // 清空岗位单选状态
|
|
677
|
+
this.$refs.postTree.initData()
|
|
678
|
+
this.selectedPostTagKey = '';
|
|
484
679
|
},
|
|
485
680
|
clearPost(){
|
|
486
681
|
this.postList= []
|
|
@@ -490,15 +685,16 @@ export default {
|
|
|
490
685
|
},
|
|
491
686
|
//staff
|
|
492
687
|
addStaffList(){
|
|
493
|
-
let staffList = this.staffAllList.filter((item)=>item.checked===true)
|
|
494
|
-
|
|
495
|
-
let
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
))
|
|
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)
|
|
499
693
|
);
|
|
500
|
-
|
|
501
|
-
this.
|
|
694
|
+
// 合并去重后的列表,右侧条件区域数据结构完全不变
|
|
695
|
+
this.staffList = this.staffList.concat(uniqueStaffList);
|
|
696
|
+
// 清空选中状态(保持原有逻辑)
|
|
697
|
+
this.staffAllList.forEach(item => item.checked = false);
|
|
502
698
|
},
|
|
503
699
|
handlestaff(item){
|
|
504
700
|
item.checked=!item.checked
|
|
@@ -520,9 +716,537 @@ export default {
|
|
|
520
716
|
visibleChange(val){
|
|
521
717
|
this.$emit('input',val);
|
|
522
718
|
},
|
|
523
|
-
|
|
524
|
-
|
|
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
|
+
}
|
|
525
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
|
+
|
|
526
1250
|
},
|
|
527
1251
|
computed:{
|
|
528
1252
|
getCheckedStaff(){
|
|
@@ -541,6 +1265,7 @@ export default {
|
|
|
541
1265
|
},
|
|
542
1266
|
data:{
|
|
543
1267
|
handler(val){
|
|
1268
|
+
if(!val) return
|
|
544
1269
|
let map = deepCopy(val)
|
|
545
1270
|
let orgList = map.orgList || []
|
|
546
1271
|
orgList.forEach(item=>{
|
|
@@ -563,6 +1288,64 @@ export default {
|
|
|
563
1288
|
}
|
|
564
1289
|
</script>
|
|
565
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
|
+
}
|
|
566
1349
|
.modal-tree{
|
|
567
1350
|
.header-text{
|
|
568
1351
|
font-weight: bold;
|
|
@@ -653,10 +1436,10 @@ export default {
|
|
|
653
1436
|
.tag{
|
|
654
1437
|
margin-top: 10px;
|
|
655
1438
|
&.active{
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
1439
|
+
background: var(--primary-color);
|
|
1440
|
+
border-radius: 4px;
|
|
1441
|
+
color: #fff;
|
|
1442
|
+
}
|
|
660
1443
|
}
|
|
661
1444
|
}
|
|
662
1445
|
.post{
|