widget.qw 1.0.83 → 1.0.85

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.
Files changed (108) hide show
  1. package/.env.development +10 -10
  2. package/.env.production +8 -8
  3. package/README.md +75 -75
  4. package/build/style.css +521 -471
  5. package/build/widget.qw.es.js +253 -84
  6. package/build/widget.qw.umd.js +252 -83
  7. package/index.html +17 -17
  8. package/package.json +47 -47
  9. package/src/App.vue +26 -26
  10. package/src/api/index.js +173 -165
  11. package/src/components/AuditBar.vue +396 -396
  12. package/src/components/BillCard.vue +164 -164
  13. package/src/components/CascaderPicker.vue +132 -132
  14. package/src/components/CascaderPop.vue +375 -371
  15. package/src/components/CheckGroup.vue +113 -113
  16. package/src/components/DatetimeDropdown.vue +62 -0
  17. package/src/components/{DatetimePicker/index.vue → DatetimePicker.vue} +124 -124
  18. package/src/components/DayDropdown.vue +56 -56
  19. package/src/components/FilePicker.vue +169 -169
  20. package/src/components/HeadBar.vue +130 -0
  21. package/src/components/Input.vue +80 -80
  22. package/src/components/MonthDropdown.vue +51 -51
  23. package/src/components/MultiPicker.vue +123 -123
  24. package/src/components/ObjsEditor.vue +334 -334
  25. package/src/components/SecretNotify.js +5 -5
  26. package/src/components/Sheet.vue +92 -92
  27. package/src/components/SingleApiPicker.vue +108 -108
  28. package/src/components/SinglePicker.vue +102 -102
  29. package/src/components/SingleUserSelector.vue +425 -425
  30. package/src/components/Switch.vue +64 -64
  31. package/src/components/TreePicker.vue +113 -113
  32. package/src/components/UserPicker.vue +106 -106
  33. package/src/components/UserProfile.vue +129 -129
  34. package/src/components/UsersPicker.vue +118 -118
  35. package/src/components/YearDropdown.vue +59 -59
  36. package/src/components/data_selector.vue +303 -303
  37. package/src/components/image_picker.vue +123 -123
  38. package/src/components/images_picker.vue +195 -195
  39. package/src/components/index.js +172 -162
  40. package/src/components/mult_list_selector.vue +155 -155
  41. package/src/components/subdepartment_selector.vue +481 -481
  42. package/src/components/user_selector.vue +639 -639
  43. package/src/components/widget/DataPop.vue +95 -95
  44. package/src/components/{DatetimePicker → widget}/DatetimePop.vue +342 -342
  45. package/src/components/widget/TreePop.vue +88 -88
  46. package/src/components/widget/UserPop.vue +347 -347
  47. package/src/main.js +117 -117
  48. package/src/router/index.ts +183 -173
  49. package/src/util/array_util.js +32 -32
  50. package/src/util/auth_util.js +72 -72
  51. package/src/util/bool_util.js +5 -5
  52. package/src/util/bus.js +1 -1
  53. package/src/util/cache_util.js +18 -18
  54. package/src/util/errer_code.js +6 -6
  55. package/src/util/eval_util.js +19 -19
  56. package/src/util/icon_util.js +36 -36
  57. package/src/util/image_util.js +27 -27
  58. package/src/util/index.js +57 -57
  59. package/src/util/num_util.js +70 -70
  60. package/src/util/obj_util.js +28 -28
  61. package/src/util/request.js +73 -73
  62. package/src/util/request_json.js +71 -71
  63. package/src/util/request_json_mute.js +65 -65
  64. package/src/util/request_upload.js +79 -79
  65. package/src/util/route_util.js +31 -31
  66. package/src/util/str_util.js +143 -143
  67. package/src/util/time_util.js +406 -406
  68. package/src/util/toast_util.js +24 -24
  69. package/src/util/tree_util.js +153 -153
  70. package/src/util/uuid_util.js +9 -9
  71. package/src/util/validate.js +182 -182
  72. package/src/util/vue_filter.js +223 -223
  73. package/src/views/auditbar/index.vue +78 -78
  74. package/src/views/billcard/index.vue +48 -48
  75. package/src/views/cascaderpicker/index.vue +0 -0
  76. package/src/views/cascaderpicker/userpicker.vue +183 -0
  77. package/src/views/cascaderpop/index.vue +90 -90
  78. package/src/views/checkgroup/index.vue +35 -35
  79. package/src/views/dataSelector/index.vue +48 -48
  80. package/src/views/datetimedropdown/index.vue +34 -0
  81. package/src/views/datetimepicker/index.vue +34 -34
  82. package/src/views/daydropdown/index.vue +31 -31
  83. package/src/views/filepicker/index.vue +32 -32
  84. package/src/views/headbar/index.vue +28 -0
  85. package/src/views/home/index.vue +0 -0
  86. package/src/views/imagepicker/index.vue +31 -31
  87. package/src/views/imagespicker/index.vue +37 -37
  88. package/src/views/input/index.vue +35 -35
  89. package/src/views/monthdropdown/index.vue +31 -31
  90. package/src/views/multListSelector/index.vue +61 -61
  91. package/src/views/multipicker/index.vue +36 -36
  92. package/src/views/objseditor/index.vue +277 -277
  93. package/src/views/productSelector/index.vue +35 -35
  94. package/src/views/projectpicker/index.vue +41 -41
  95. package/src/views/secretnotify/index.vue +27 -27
  96. package/src/views/sheet/index.vue +45 -45
  97. package/src/views/singlepicker/index.vue +35 -35
  98. package/src/views/subdepartmentSelector/index.vue +40 -40
  99. package/src/views/switch/index.vue +34 -34
  100. package/src/views/treepicker/index.vue +41 -41
  101. package/src/views/userSelector/index.vue +54 -54
  102. package/src/views/userSelectorNew/index.vue +45 -45
  103. package/src/views/userpicker/index.vue +43 -43
  104. package/src/views/userprofile/index.vue +30 -30
  105. package/src/views/userspicker/index.vue +43 -43
  106. package/src/views/yeardropdown/index.vue +32 -32
  107. package/src/vm/index.js +1 -1
  108. package/vite.config.ts +122 -122
@@ -1,481 +1,481 @@
1
- <template>
2
- <div class="department-selector">
3
- <van-field v-if="!isGone" :model-value="selectedDepartmentPath" readonly clickable :label="props.label"
4
- :placeholder="props.placeholder" @click="onShow" :right-icon="isReadonly ? '' : 'arrow'"
5
- :required="isRequired" :rules="props.rules" type="textarea" autosize="true" rows="1"/>
6
-
7
- <van-popup v-model:show="showPicker" position="bottom" class="department-popup">
8
- <div class="popup-container">
9
- <div class="picker-header">
10
- <van-button v-if="currentLevel > 0" @click="goBack" icon="arrow-left" size="small"
11
- class="back-button" />
12
- <div class="current-path">{{ currentPath }}</div>
13
- <div class="action-buttons">
14
- <van-button size="small" @click="handleCancel" class="cancel-btn">
15
- 取消
16
- </van-button>
17
- <van-button type="primary" size="small" @click="confirmSelection"
18
- :disabled="!tempSelectedDepartment" class="confirm-btn">
19
- 确定
20
- </van-button>
21
- </div>
22
- </div>
23
-
24
- <van-field left-icon="search" v-model.trim="searchKeyword" clearable placeholder="请输入部门名称搜索"
25
- class="search-field" />
26
- <div class="scroll-container">
27
- <van-cell-group>
28
- <van-cell v-for="(item, index) in filteredDepartments" :key="index" clickable
29
- @click="handleCellClick(item)">
30
- <template #title>
31
- <div class="cell-content">
32
- <div class="circle-selector"
33
- :class="{ 'selected': tempSelectedDepartment?.id === item.id }"
34
- @click.stop="toggleSelection(item)">
35
- <van-icon v-if="tempSelectedDepartment?.id === item.id" name="success" />
36
- </div>
37
- <span class="name">{{ item.fullPath || item.name }}</span>
38
- </div>
39
- </template>
40
- <template #right-icon>
41
- <van-icon v-if="item.subDepartments?.length" name="arrow" />
42
- </template>
43
- </van-cell>
44
- </van-cell-group>
45
- </div>
46
- </div>
47
- </van-popup>
48
- </div>
49
- </template>
50
-
51
- <script setup>
52
- import { ref, computed, onMounted, watch } from 'vue'
53
- import util from '../util'
54
-
55
- const props = defineProps({
56
- modelValue: [String, Number],
57
- label: {
58
- type: String,
59
- default: '选择部门'
60
- },
61
- placeholder: {
62
- type: String,
63
- default: '请选择部门'
64
- },
65
- required: {
66
- type: Boolean,
67
- default: true
68
- },
69
- readonly: {
70
- type: Boolean,
71
- default: false
72
- },
73
- rules: {
74
- type: Array,
75
- default: () => []
76
- },
77
- auth: {
78
- type: String,
79
- default: ''
80
- },
81
- // 查询多少层组织机构
82
- level: {
83
- type: Number,
84
- // 默认查所有
85
- default: 999
86
- },
87
- // 二级部门取值范围字典id
88
- // 取dictionary库.dict的配置id
89
- deptRangeId: {
90
- type: String,
91
- default: ''
92
- }
93
- })
94
-
95
- const emit = defineEmits(['update:modelValue', 'select'])
96
- const { isRequired, isGone, isDisabled, isReadonly } = util.props2auth(props)
97
-
98
- // 部门数据相关状态
99
- const departmentTree = ref([])
100
- const currentDepartments = ref([])
101
- const selectedDepartmentPath = ref('')
102
- const selectionPath = ref([])
103
- const showPicker = ref(false)
104
- const tempSelectedDepartment = ref(null)
105
-
106
- // 在脚本部分添加搜索相关逻辑(约第60行)
107
- // 添加搜索关键词响应式变量
108
- const searchKeyword = ref('')
109
-
110
- // 计算属性
111
- const currentPath = computed(() => {
112
- return selectionPath.value
113
- .filter(d => d.id !== '1') // 过滤一级部门
114
- .map(d => d.name)
115
- .join(' / ') || '请选择部门'
116
- })
117
-
118
- const currentLevel = computed(() => selectionPath.value.length)
119
-
120
- // 部门数据缓存
121
- const departmentCache = {
122
- data: ref([]),
123
- loading: ref(false),
124
- error: ref(null)
125
- }
126
-
127
- // 修改过滤后的部门列表计算属性
128
- // 修改过滤逻辑(约第80行)
129
- // 修改搜索结果的路径生成逻辑(约第89行)
130
- const filteredDepartments = computed(() => {
131
- if (!searchKeyword.value) return currentDepartments.value;
132
-
133
- // 递归搜索整个部门树
134
- const searchTree = (depts, keyword, path = []) => {
135
- return depts.flatMap(dept => {
136
- const currentPath = [...path, dept];
137
- const matches = [];
138
-
139
- if (dept.name.toLowerCase().includes(keyword.toLowerCase())) {
140
- matches.push({
141
- ...dept,
142
- // 过滤掉一级部门(假设一级部门id为1)
143
- fullPath: currentPath
144
- .filter(d => d.id !== '1')
145
- .map(d => d.name)
146
- .join(' / ')
147
- });
148
- }
149
-
150
- if (dept.subDepartments?.length) {
151
- matches.push(...searchTree(dept.subDepartments, keyword, currentPath));
152
- }
153
-
154
- return matches;
155
- });
156
- };
157
-
158
- return searchTree(departmentTree.value, searchKeyword.value);
159
- });
160
-
161
- const onShow = () => {
162
- if (isDisabled.value)
163
- return
164
-
165
- showPicker.value = true
166
- }
167
-
168
- // 修改单元格点击处理
169
- const handleCellClick = (department) => {
170
- // 如果是叶子节点直接选中
171
- if (!department.subDepartments?.length) {
172
- toggleSelection(department)
173
- return
174
- }
175
-
176
- // 非叶子节点才展开层级
177
- selectionPath.value.push(department)
178
- currentDepartments.value = department.subDepartments
179
- tempSelectedDepartment.value = null
180
- }
181
-
182
- // 监听搜索关键词变化
183
- watch(searchKeyword, (newVal) => {
184
- if (newVal) {
185
- // 可根据需要添加搜索建议逻辑
186
- }
187
- })
188
-
189
- // 在工具函数部分新增以下方法
190
- const findDepartmentById = (departments, targetId) => {
191
- for (const dept of departments) {
192
- if (dept.id === targetId) return dept
193
- if (dept.subDepartments?.length) {
194
- const found = findDepartmentById(dept.subDepartments, targetId)
195
- if (found) return found
196
- }
197
- }
198
- return null
199
- }
200
-
201
- // 修改路径构建方法
202
- const buildDepartmentPath = (department) => {
203
- const path = []
204
- let current = department.parent
205
- while (current) {
206
- if (current.id !== '1') { // 不插入一级部门
207
- path.unshift(current.name)
208
- }
209
- current = current.parent
210
- }
211
- // 添加当前选中部门(最后一级)
212
- path.push(department.name)
213
- return path.join(' / ')
214
- }
215
-
216
- // 修改部门数据加载方法
217
- const loadDepartments = async () => {
218
- try {
219
- if (departmentCache.data.value.length > 0) return
220
-
221
- departmentCache.loading.value = true
222
- const response = await util.get_subdepartments({ level: props.level })
223
- console.log('-------subdepartment---------', response)
224
- if (response.code === 200) {
225
- // 添加parent引用
226
- const addParentRef = (depts, parent) => {
227
- depts.forEach(d => {
228
- d.parent = parent
229
- if (d.subDepartments) addParentRef(d.subDepartments, d)
230
- })
231
- }
232
-
233
- // 取配置的二级部门取值范围
234
- let level2DeptIds = []
235
- //只显示指定范围内的二级部门
236
- if (props.deptRangeId) {
237
- let res = await util.dictionary_dict({ id: props.deptRangeId })
238
- if (res.data.parseValue && res.data.parseValue.length > 0) {
239
- level2DeptIds = res.data.parseValue
240
- }
241
- }
242
-
243
- // 只保留指定二级部门列表
244
- if (props.deptRangeId) {
245
- if (level2DeptIds && level2DeptIds.length > 0) {
246
- response.data.subDepartments = response.data.subDepartments.filter(item => {
247
- return level2DeptIds.includes(item.id)
248
- })
249
- }
250
- }
251
-
252
- departmentCache.data.value = [response.data]
253
- addParentRef(departmentCache.data.value, null)
254
- departmentTree.value = departmentCache.data.value
255
- currentDepartments.value = response.data.subDepartments || []
256
- // 存储到sessionStorage中
257
- // sessionStorage.setItem('departmentTree', JSON.stringify(departmentTree.value))
258
- // sessionStorage.setItem('currentDepartments', JSON.stringify(currentDepartments.value))
259
- }
260
- } catch (error) {
261
- console.error('加载部门数据失败:', error)
262
- } finally {
263
- departmentCache.loading.value = false
264
- }
265
- }
266
-
267
- // const handleCellClick = (department) => {
268
- // if (department.subDepartments?.length) {
269
- // selectionPath.value.push(department)
270
- // currentDepartments.value = department.subDepartments
271
- // tempSelectedDepartment.value = null
272
- // } else {
273
- // toggleSelection(department)
274
- // }
275
- // }
276
-
277
- const toggleSelection = (department) => {
278
- tempSelectedDepartment.value =
279
- tempSelectedDepartment.value?.id === department.id
280
- ? null
281
- : department
282
- }
283
-
284
- const confirmSelection = () => {
285
- if (!tempSelectedDepartment.value) return
286
-
287
- const selectedPath = [...selectionPath.value, tempSelectedDepartment.value]
288
- selectedDepartmentPath.value = selectedPath.map(d => d.name).join(' / ')
289
- emit('update:modelValue', tempSelectedDepartment.value.id)
290
- emit('select', tempSelectedDepartment.value)
291
- showPicker.value = false
292
- resetSelectionState()
293
- }
294
-
295
- const handleCancel = () => {
296
- // emit('update:modelValue', null)
297
- // selectedDepartmentPath.value = ''
298
- showPicker.value = false
299
- // resetSelectionState()
300
- }
301
-
302
- const resetSelectionState = () => {
303
- selectionPath.value = []
304
- currentDepartments.value = departmentTree.value[0]?.subDepartments || []
305
- tempSelectedDepartment.value = null
306
- }
307
-
308
- const goBack = () => {
309
- selectionPath.value.pop()
310
- currentDepartments.value = selectionPath.value.length
311
- ? selectionPath.value[selectionPath.value.length - 1].subDepartments
312
- : departmentTree.value[0]?.subDepartments || []
313
- tempSelectedDepartment.value = null
314
- }
315
-
316
- watch(showPicker, (newVal) => {
317
- if (!newVal) {
318
- resetSelectionState()
319
- }
320
- })
321
-
322
- onMounted(async () => {
323
- if (departmentCache.data.value.length === 0) {
324
- await loadDepartments()
325
- } else {
326
- departmentTree.value = departmentCache.data.value
327
- currentDepartments.value = departmentCache.data.value[0]?.subDepartments || []
328
- }
329
- })
330
-
331
- // 修改后的watch监听(补充路径空值处理)
332
- watch(() => props.modelValue, async (newVal) => {
333
- if (newVal) {
334
- await loadDepartments()
335
- const targetDept = findDepartmentById(departmentCache.data.value, newVal)
336
- if (targetDept) {
337
- // 当部门为一级时显示空路径
338
- selectedDepartmentPath.value = targetDept.parent
339
- ? buildDepartmentPath(targetDept)
340
- : ''
341
- }
342
- }
343
- }, { immediate: true })
344
-
345
- // 方法定义
346
- // 在watch部分新增以下代码
347
- watch(() => props.modelValue, async (newVal) => {
348
- if (newVal) {
349
- await loadDepartments()
350
- const targetDept = findDepartmentById(departmentCache.data.value, newVal)
351
- if (targetDept) {
352
- selectedDepartmentPath.value = buildDepartmentPath(targetDept)
353
- }
354
- }
355
- }, { immediate: true })
356
- </script>
357
-
358
- <style scoped>
359
- .department-popup {
360
- width: 100%;
361
- height: 60vh;
362
- border-radius: 16px 16px 0 0;
363
- }
364
-
365
- .popup-container {
366
- display: flex;
367
- flex-direction: column;
368
- flex: 1;
369
- height: 100%;
370
- }
371
-
372
- .picker-header {
373
- display: flex;
374
- align-items: center;
375
- padding: 12px;
376
- border-bottom: 1px solid #ebedf0;
377
- gap: 8px;
378
- background: #fff;
379
- /* flex-shrink: 0; */
380
- position: sticky;
381
- top: 0;
382
- z-index: 999;
383
- height: 56px;
384
- /* 固定头部高度 */
385
- flex-shrink: 0;
386
- /* 防止头部被压缩 */
387
- }
388
-
389
- .scroll-container {
390
- flex: 1;
391
- height: 0;
392
- overflow-y: auto;
393
- -webkit-overflow-scrolling: touch;
394
- /* height: calc(80vh - 170px); */
395
- /* 计算方式:总高度(80vh) - 头部高度(56px) - 搜索框高度(52px) */
396
- position: relative;
397
- top: -1px;
398
- }
399
-
400
- .back-button {
401
- margin-right: 8px;
402
- }
403
-
404
- .current-path {
405
- flex: 1;
406
- font-size: 14px;
407
- color: #323233;
408
- white-space: nowrap;
409
- overflow: hidden;
410
- text-overflow: ellipsis;
411
- }
412
-
413
- .action-buttons {
414
- display: flex;
415
- gap: 8px;
416
- margin-left: auto;
417
- }
418
-
419
- /* .scroll-container {
420
- flex: 1;
421
- overflow-y: auto;
422
- -webkit-overflow-scrolling: touch;
423
- } */
424
-
425
- .cell-content {
426
- display: flex;
427
- align-items: center;
428
- gap: 12px;
429
- }
430
-
431
- .circle-selector {
432
- width: 20px;
433
- height: 20px;
434
- border: 1px solid #ebedf0;
435
- border-radius: 50%;
436
- display: flex;
437
- align-items: center;
438
- justify-content: center;
439
- transition: all 0.2s;
440
- flex-shrink: 0;
441
- }
442
-
443
- .circle-selector.selected {
444
- background-color: #1989fa;
445
- border-color: #1989fa;
446
- }
447
-
448
- .circle-selector.selected .van-icon {
449
- color: white;
450
- font-size: 14px;
451
- }
452
-
453
- .van-cell__right-icon {
454
- color: #969799;
455
- font-size: 16px;
456
- margin-left: 8px;
457
- }
458
-
459
- .name {
460
- flex: 1;
461
- overflow: hidden;
462
- text-overflow: ellipsis;
463
- white-space: nowrap;
464
- }
465
-
466
- .cancel-btn {
467
- color: #969799;
468
- }
469
-
470
- .search-field {
471
- background: #fff;
472
- padding: 10px 16px;
473
- border-bottom: 1px solid #ebedf0;
474
- position: sticky;
475
- top: 0;
476
- z-index: 999;
477
- margin-bottom: 10px;
478
- flex-shrink: 0;
479
- /* 防止压缩 */
480
- }
481
- </style>
1
+ <template>
2
+ <div class="department-selector">
3
+ <van-field v-if="!isGone" :model-value="selectedDepartmentPath" readonly clickable :label="props.label"
4
+ :placeholder="props.placeholder" @click="onShow" :right-icon="isReadonly ? '' : 'arrow'"
5
+ :required="isRequired" :rules="props.rules" type="textarea" autosize="true" rows="1"/>
6
+
7
+ <van-popup v-model:show="showPicker" position="bottom" class="department-popup">
8
+ <div class="popup-container">
9
+ <div class="picker-header">
10
+ <van-button v-if="currentLevel > 0" @click="goBack" icon="arrow-left" size="small"
11
+ class="back-button" />
12
+ <div class="current-path">{{ currentPath }}</div>
13
+ <div class="action-buttons">
14
+ <van-button size="small" @click="handleCancel" class="cancel-btn">
15
+ 取消
16
+ </van-button>
17
+ <van-button type="primary" size="small" @click="confirmSelection"
18
+ :disabled="!tempSelectedDepartment" class="confirm-btn">
19
+ 确定
20
+ </van-button>
21
+ </div>
22
+ </div>
23
+
24
+ <van-field left-icon="search" v-model.trim="searchKeyword" clearable placeholder="请输入部门名称搜索"
25
+ class="search-field" />
26
+ <div class="scroll-container">
27
+ <van-cell-group>
28
+ <van-cell v-for="(item, index) in filteredDepartments" :key="index" clickable
29
+ @click="handleCellClick(item)">
30
+ <template #title>
31
+ <div class="cell-content">
32
+ <div class="circle-selector"
33
+ :class="{ 'selected': tempSelectedDepartment?.id === item.id }"
34
+ @click.stop="toggleSelection(item)">
35
+ <van-icon v-if="tempSelectedDepartment?.id === item.id" name="success" />
36
+ </div>
37
+ <span class="name">{{ item.fullPath || item.name }}</span>
38
+ </div>
39
+ </template>
40
+ <template #right-icon>
41
+ <van-icon v-if="item.subDepartments?.length" name="arrow" />
42
+ </template>
43
+ </van-cell>
44
+ </van-cell-group>
45
+ </div>
46
+ </div>
47
+ </van-popup>
48
+ </div>
49
+ </template>
50
+
51
+ <script setup>
52
+ import { ref, computed, onMounted, watch } from 'vue'
53
+ import util from '../util'
54
+
55
+ const props = defineProps({
56
+ modelValue: [String, Number],
57
+ label: {
58
+ type: String,
59
+ default: '选择部门'
60
+ },
61
+ placeholder: {
62
+ type: String,
63
+ default: '请选择部门'
64
+ },
65
+ required: {
66
+ type: Boolean,
67
+ default: true
68
+ },
69
+ readonly: {
70
+ type: Boolean,
71
+ default: false
72
+ },
73
+ rules: {
74
+ type: Array,
75
+ default: () => []
76
+ },
77
+ auth: {
78
+ type: String,
79
+ default: ''
80
+ },
81
+ // 查询多少层组织机构
82
+ level: {
83
+ type: Number,
84
+ // 默认查所有
85
+ default: 999
86
+ },
87
+ // 二级部门取值范围字典id
88
+ // 取dictionary库.dict的配置id
89
+ deptRangeId: {
90
+ type: String,
91
+ default: ''
92
+ }
93
+ })
94
+
95
+ const emit = defineEmits(['update:modelValue', 'select'])
96
+ const { isRequired, isGone, isDisabled, isReadonly } = util.props2auth(props)
97
+
98
+ // 部门数据相关状态
99
+ const departmentTree = ref([])
100
+ const currentDepartments = ref([])
101
+ const selectedDepartmentPath = ref('')
102
+ const selectionPath = ref([])
103
+ const showPicker = ref(false)
104
+ const tempSelectedDepartment = ref(null)
105
+
106
+ // 在脚本部分添加搜索相关逻辑(约第60行)
107
+ // 添加搜索关键词响应式变量
108
+ const searchKeyword = ref('')
109
+
110
+ // 计算属性
111
+ const currentPath = computed(() => {
112
+ return selectionPath.value
113
+ .filter(d => d.id !== '1') // 过滤一级部门
114
+ .map(d => d.name)
115
+ .join(' / ') || '请选择部门'
116
+ })
117
+
118
+ const currentLevel = computed(() => selectionPath.value.length)
119
+
120
+ // 部门数据缓存
121
+ const departmentCache = {
122
+ data: ref([]),
123
+ loading: ref(false),
124
+ error: ref(null)
125
+ }
126
+
127
+ // 修改过滤后的部门列表计算属性
128
+ // 修改过滤逻辑(约第80行)
129
+ // 修改搜索结果的路径生成逻辑(约第89行)
130
+ const filteredDepartments = computed(() => {
131
+ if (!searchKeyword.value) return currentDepartments.value;
132
+
133
+ // 递归搜索整个部门树
134
+ const searchTree = (depts, keyword, path = []) => {
135
+ return depts.flatMap(dept => {
136
+ const currentPath = [...path, dept];
137
+ const matches = [];
138
+
139
+ if (dept.name.toLowerCase().includes(keyword.toLowerCase())) {
140
+ matches.push({
141
+ ...dept,
142
+ // 过滤掉一级部门(假设一级部门id为1)
143
+ fullPath: currentPath
144
+ .filter(d => d.id !== '1')
145
+ .map(d => d.name)
146
+ .join(' / ')
147
+ });
148
+ }
149
+
150
+ if (dept.subDepartments?.length) {
151
+ matches.push(...searchTree(dept.subDepartments, keyword, currentPath));
152
+ }
153
+
154
+ return matches;
155
+ });
156
+ };
157
+
158
+ return searchTree(departmentTree.value, searchKeyword.value);
159
+ });
160
+
161
+ const onShow = () => {
162
+ if (isDisabled.value)
163
+ return
164
+
165
+ showPicker.value = true
166
+ }
167
+
168
+ // 修改单元格点击处理
169
+ const handleCellClick = (department) => {
170
+ // 如果是叶子节点直接选中
171
+ if (!department.subDepartments?.length) {
172
+ toggleSelection(department)
173
+ return
174
+ }
175
+
176
+ // 非叶子节点才展开层级
177
+ selectionPath.value.push(department)
178
+ currentDepartments.value = department.subDepartments
179
+ tempSelectedDepartment.value = null
180
+ }
181
+
182
+ // 监听搜索关键词变化
183
+ watch(searchKeyword, (newVal) => {
184
+ if (newVal) {
185
+ // 可根据需要添加搜索建议逻辑
186
+ }
187
+ })
188
+
189
+ // 在工具函数部分新增以下方法
190
+ const findDepartmentById = (departments, targetId) => {
191
+ for (const dept of departments) {
192
+ if (dept.id === targetId) return dept
193
+ if (dept.subDepartments?.length) {
194
+ const found = findDepartmentById(dept.subDepartments, targetId)
195
+ if (found) return found
196
+ }
197
+ }
198
+ return null
199
+ }
200
+
201
+ // 修改路径构建方法
202
+ const buildDepartmentPath = (department) => {
203
+ const path = []
204
+ let current = department.parent
205
+ while (current) {
206
+ if (current.id !== '1') { // 不插入一级部门
207
+ path.unshift(current.name)
208
+ }
209
+ current = current.parent
210
+ }
211
+ // 添加当前选中部门(最后一级)
212
+ path.push(department.name)
213
+ return path.join(' / ')
214
+ }
215
+
216
+ // 修改部门数据加载方法
217
+ const loadDepartments = async () => {
218
+ try {
219
+ if (departmentCache.data.value.length > 0) return
220
+
221
+ departmentCache.loading.value = true
222
+ const response = await util.get_subdepartments({ level: props.level })
223
+ console.log('-------subdepartment---------', response)
224
+ if (response.code === 200) {
225
+ // 添加parent引用
226
+ const addParentRef = (depts, parent) => {
227
+ depts.forEach(d => {
228
+ d.parent = parent
229
+ if (d.subDepartments) addParentRef(d.subDepartments, d)
230
+ })
231
+ }
232
+
233
+ // 取配置的二级部门取值范围
234
+ let level2DeptIds = []
235
+ //只显示指定范围内的二级部门
236
+ if (props.deptRangeId) {
237
+ let res = await util.dictionary_dict({ id: props.deptRangeId })
238
+ if (res.data.parseValue && res.data.parseValue.length > 0) {
239
+ level2DeptIds = res.data.parseValue
240
+ }
241
+ }
242
+
243
+ // 只保留指定二级部门列表
244
+ if (props.deptRangeId) {
245
+ if (level2DeptIds && level2DeptIds.length > 0) {
246
+ response.data.subDepartments = response.data.subDepartments.filter(item => {
247
+ return level2DeptIds.includes(item.id)
248
+ })
249
+ }
250
+ }
251
+
252
+ departmentCache.data.value = [response.data]
253
+ addParentRef(departmentCache.data.value, null)
254
+ departmentTree.value = departmentCache.data.value
255
+ currentDepartments.value = response.data.subDepartments || []
256
+ // 存储到sessionStorage中
257
+ // sessionStorage.setItem('departmentTree', JSON.stringify(departmentTree.value))
258
+ // sessionStorage.setItem('currentDepartments', JSON.stringify(currentDepartments.value))
259
+ }
260
+ } catch (error) {
261
+ console.error('加载部门数据失败:', error)
262
+ } finally {
263
+ departmentCache.loading.value = false
264
+ }
265
+ }
266
+
267
+ // const handleCellClick = (department) => {
268
+ // if (department.subDepartments?.length) {
269
+ // selectionPath.value.push(department)
270
+ // currentDepartments.value = department.subDepartments
271
+ // tempSelectedDepartment.value = null
272
+ // } else {
273
+ // toggleSelection(department)
274
+ // }
275
+ // }
276
+
277
+ const toggleSelection = (department) => {
278
+ tempSelectedDepartment.value =
279
+ tempSelectedDepartment.value?.id === department.id
280
+ ? null
281
+ : department
282
+ }
283
+
284
+ const confirmSelection = () => {
285
+ if (!tempSelectedDepartment.value) return
286
+
287
+ const selectedPath = [...selectionPath.value, tempSelectedDepartment.value]
288
+ selectedDepartmentPath.value = selectedPath.map(d => d.name).join(' / ')
289
+ emit('update:modelValue', tempSelectedDepartment.value.id)
290
+ emit('select', tempSelectedDepartment.value)
291
+ showPicker.value = false
292
+ resetSelectionState()
293
+ }
294
+
295
+ const handleCancel = () => {
296
+ // emit('update:modelValue', null)
297
+ // selectedDepartmentPath.value = ''
298
+ showPicker.value = false
299
+ // resetSelectionState()
300
+ }
301
+
302
+ const resetSelectionState = () => {
303
+ selectionPath.value = []
304
+ currentDepartments.value = departmentTree.value[0]?.subDepartments || []
305
+ tempSelectedDepartment.value = null
306
+ }
307
+
308
+ const goBack = () => {
309
+ selectionPath.value.pop()
310
+ currentDepartments.value = selectionPath.value.length
311
+ ? selectionPath.value[selectionPath.value.length - 1].subDepartments
312
+ : departmentTree.value[0]?.subDepartments || []
313
+ tempSelectedDepartment.value = null
314
+ }
315
+
316
+ watch(showPicker, (newVal) => {
317
+ if (!newVal) {
318
+ resetSelectionState()
319
+ }
320
+ })
321
+
322
+ onMounted(async () => {
323
+ if (departmentCache.data.value.length === 0) {
324
+ await loadDepartments()
325
+ } else {
326
+ departmentTree.value = departmentCache.data.value
327
+ currentDepartments.value = departmentCache.data.value[0]?.subDepartments || []
328
+ }
329
+ })
330
+
331
+ // 修改后的watch监听(补充路径空值处理)
332
+ watch(() => props.modelValue, async (newVal) => {
333
+ if (newVal) {
334
+ await loadDepartments()
335
+ const targetDept = findDepartmentById(departmentCache.data.value, newVal)
336
+ if (targetDept) {
337
+ // 当部门为一级时显示空路径
338
+ selectedDepartmentPath.value = targetDept.parent
339
+ ? buildDepartmentPath(targetDept)
340
+ : ''
341
+ }
342
+ }
343
+ }, { immediate: true })
344
+
345
+ // 方法定义
346
+ // 在watch部分新增以下代码
347
+ watch(() => props.modelValue, async (newVal) => {
348
+ if (newVal) {
349
+ await loadDepartments()
350
+ const targetDept = findDepartmentById(departmentCache.data.value, newVal)
351
+ if (targetDept) {
352
+ selectedDepartmentPath.value = buildDepartmentPath(targetDept)
353
+ }
354
+ }
355
+ }, { immediate: true })
356
+ </script>
357
+
358
+ <style scoped>
359
+ .department-popup {
360
+ width: 100%;
361
+ height: 60vh;
362
+ border-radius: 16px 16px 0 0;
363
+ }
364
+
365
+ .popup-container {
366
+ display: flex;
367
+ flex-direction: column;
368
+ flex: 1;
369
+ height: 100%;
370
+ }
371
+
372
+ .picker-header {
373
+ display: flex;
374
+ align-items: center;
375
+ padding: 12px;
376
+ border-bottom: 1px solid #ebedf0;
377
+ gap: 8px;
378
+ background: #fff;
379
+ /* flex-shrink: 0; */
380
+ position: sticky;
381
+ top: 0;
382
+ z-index: 999;
383
+ height: 56px;
384
+ /* 固定头部高度 */
385
+ flex-shrink: 0;
386
+ /* 防止头部被压缩 */
387
+ }
388
+
389
+ .scroll-container {
390
+ flex: 1;
391
+ height: 0;
392
+ overflow-y: auto;
393
+ -webkit-overflow-scrolling: touch;
394
+ /* height: calc(80vh - 170px); */
395
+ /* 计算方式:总高度(80vh) - 头部高度(56px) - 搜索框高度(52px) */
396
+ position: relative;
397
+ top: -1px;
398
+ }
399
+
400
+ .back-button {
401
+ margin-right: 8px;
402
+ }
403
+
404
+ .current-path {
405
+ flex: 1;
406
+ font-size: 14px;
407
+ color: #323233;
408
+ white-space: nowrap;
409
+ overflow: hidden;
410
+ text-overflow: ellipsis;
411
+ }
412
+
413
+ .action-buttons {
414
+ display: flex;
415
+ gap: 8px;
416
+ margin-left: auto;
417
+ }
418
+
419
+ /* .scroll-container {
420
+ flex: 1;
421
+ overflow-y: auto;
422
+ -webkit-overflow-scrolling: touch;
423
+ } */
424
+
425
+ .cell-content {
426
+ display: flex;
427
+ align-items: center;
428
+ gap: 12px;
429
+ }
430
+
431
+ .circle-selector {
432
+ width: 20px;
433
+ height: 20px;
434
+ border: 1px solid #ebedf0;
435
+ border-radius: 50%;
436
+ display: flex;
437
+ align-items: center;
438
+ justify-content: center;
439
+ transition: all 0.2s;
440
+ flex-shrink: 0;
441
+ }
442
+
443
+ .circle-selector.selected {
444
+ background-color: #1989fa;
445
+ border-color: #1989fa;
446
+ }
447
+
448
+ .circle-selector.selected .van-icon {
449
+ color: white;
450
+ font-size: 14px;
451
+ }
452
+
453
+ .van-cell__right-icon {
454
+ color: #969799;
455
+ font-size: 16px;
456
+ margin-left: 8px;
457
+ }
458
+
459
+ .name {
460
+ flex: 1;
461
+ overflow: hidden;
462
+ text-overflow: ellipsis;
463
+ white-space: nowrap;
464
+ }
465
+
466
+ .cancel-btn {
467
+ color: #969799;
468
+ }
469
+
470
+ .search-field {
471
+ background: #fff;
472
+ padding: 10px 16px;
473
+ border-bottom: 1px solid #ebedf0;
474
+ position: sticky;
475
+ top: 0;
476
+ z-index: 999;
477
+ margin-bottom: 10px;
478
+ flex-shrink: 0;
479
+ /* 防止压缩 */
480
+ }
481
+ </style>