@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/src/index.vue CHANGED
@@ -1,34 +1,51 @@
1
1
  <template>
2
2
  <Modal
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"
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="选择组织节点" name="org">
15
+ <Tabs :value="tabName" @input="handleTabChange">
16
+ <TabPane label="组织选择" name="org" v-if="name.includes('org')">
17
17
  <div class="tab">
18
- <Select v-model="orgSearch" filterable :remote-method="getOrgListBySearch" @on-change="getOrgOption" :loading="loadingOrg" placeholder="选择组织节点" clearable>
19
- <Option v-for="(option, index) in orgSearchList" :value="option.orgUnitId" :key="index">{{option.name}}</Option>
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="tag-list">
24
- <span :class="['tag', item.checked && 'active']" v-for="item in tagList" :key="item.key" @click="fastChedkOrg(item)">{{item.v}}</span>
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="选择岗位" name="post">
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.checked&&'active']" :key="item.positionId" @click="getPosionId(item)">{{ item.positionName }}</div>
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
- <Select v-model="postSearch" filterable :remote-method="getPostListBySearch" @on-change="getPostOption" placeholder="选择组织节点" :loading="loadingPost" clearable>
53
- <Option v-for="(option, index) in postSearchList" :value="option.orgUnitId" :key="index">{{option.name}}</Option>
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="tag-list">
58
- <span class="tag" v-for="item in tagList" :key="item.key">{{item.v}}</span>
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" @on-enter="searchStaff" @on-search="searchStaff" search placeholder="搜索人员"/>
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
- <div :class="['gust-item',item.checked && 'staff-active']" v-for="item in staffAllList" :key="item.id" @click="handlestaff(item)">
84
- <div class="left-panel">{{item.name && item.name.slice(0,1) || ''}}</div>
85
- <div class="right-panel">
86
- <p>{{item.name}}</p>
87
- <p>{{item.orgNodeName}}</p>
88
- </div>
89
- <div class="checked-icon" v-show="item.checked">✔</div>
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
- <p v-if="staffEnding" style="color:#CCCCCC;text-align: center">---我也是有底线的---</p>
130
+ <div class="checked-icon" v-show="item.checked">✔</div>
92
131
  </div>
93
- <Spin v-if="loadingStaff" size="large" fix />
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
- </TabPane>
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
- value:String,
173
- default:"org"
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:false,
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
- tagList:[{
207
- v:'所选市公司所有部门',
208
- checked:false,
209
- key:'07'
210
- },{
211
- v:'所有市公司',
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
- this.loadingOrg = false
252
- if(res.data.code === 1){
253
- let resp = res.data.data?.items??[]
254
- this.orgSearchList = resp
255
- }else{
256
- this.orgSearchList = []
257
- this.$Message.error("获取组织节点列表失败!")
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
- this.loadingPost = false
269
- if(res.data.code === 1){
270
- let resp = res.data.data?.items??[]
271
- this.postSearchList = resp
272
- }else{
273
- this.postSearchList = []
274
- this.$Message.error("获取组织节点列表失败!")
275
- }
276
- })
277
- }else{
278
- this.$refs.postTree.initData()
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
- ajax.get('/pub-manage-server/pub/personHelpBox/q/getOrgUnitBySearchTerm?searchTerm='+query).then((res)=>{callback(res)})
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)=> {return item.orgUnitId === val}).shift();
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 = {...item,orgNodeName:item.name,parentOrgUnitId:item.parentId,leafNode:leafNode}
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
- let tree = this.buildTree([...parentsList,ftem])
293
- this.orgTree = tree
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
- if(!val) return this.$refs.postTree.initData();
300
- let item = this.postSearchList.filter((item)=> {return item.orgUnitId === val}).shift();
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
- const ftem = {...item,orgNodeName:item.name,parentOrgUnitId:item.parentId,leafNode:leafNode}
303
- try{
304
- let parentsList = await this.getParentOrgNodesByOrgUnitId(val)
305
- let tree = this.buildTree([...parentsList,ftem])
306
- this.postTree = tree
307
- }catch(e){
308
- this.$Message.error("获取组织节点列表失败!")
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
- // 初始化所有节点,添加children属性
349
- items.forEach(item => {
350
- map[item.orgUnitId] = { ...item, orgChildrenList: [] };
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
- items.forEach(item => {
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 === null) {
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
- if (parent) {
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 = data
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 = data
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
- index === self.findIndex(t => (
439
- t.id === item.id // 基于id属性去重
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.filter((item)=>item.checked === true)
460
- if(!checkedPosition.length){return this.$Message.error("请选择岗位!!")}
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
- for(let it of checkedPosition){
464
- proPostList.forEach(item=>{
465
- totalList.push({
466
- ...item,
467
- ...it,
468
- title:`${this.includeLevelPost.length?(item.orgNodeName+'(包含下级组织节点)'):item.orgNodeName}`,
469
- includeLevel:this.includeLevelPost.includes('01'),
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
- index === self.findIndex(t => (
477
- t.id === item.id // 基于id属性去重
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.positiontList.map((item)=>{item.checked=false})
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
- let list = this.staffList.concat(staffList)
495
- let uniqueArray = list.filter((item, index, self) =>
496
- index === self.findIndex(t => (
497
- t.id === item.id // 基于id属性去重
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
- this.staffList = uniqueArray
501
- this.staffAllList.map((item)=>item.checked=false)
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
- fastChedkOrg(item){
524
- item.checked = !item.checked
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
- background: var(--primary-color);
657
- border-radius: 4px;
658
- color: #fff;
659
- }
1439
+ background: var(--primary-color);
1440
+ border-radius: 4px;
1441
+ color: #fff;
1442
+ }
660
1443
  }
661
1444
  }
662
1445
  .post{