widget.qw 1.0.81 → 1.0.83

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 (104) 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 +471 -471
  5. package/build/widget.qw.es.js +64 -65
  6. package/build/widget.qw.umd.js +64 -65
  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 +165 -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 +371 -371
  15. package/src/components/CheckGroup.vue +113 -113
  16. package/src/components/DatetimePicker/DatetimePop.vue +342 -342
  17. package/src/components/DatetimePicker/index.vue +124 -124
  18. package/src/components/DayDropdown.vue +56 -56
  19. package/src/components/FilePicker.vue +169 -169
  20. package/src/components/Input.vue +80 -80
  21. package/src/components/MonthDropdown.vue +51 -51
  22. package/src/components/MultiPicker.vue +123 -123
  23. package/src/components/ObjsEditor.vue +334 -334
  24. package/src/components/SecretNotify.js +5 -5
  25. package/src/components/Sheet.vue +92 -92
  26. package/src/components/SingleApiPicker.vue +108 -108
  27. package/src/components/SinglePicker.vue +102 -102
  28. package/src/components/SingleUserSelector.vue +425 -425
  29. package/src/components/Switch.vue +64 -64
  30. package/src/components/TreePicker.vue +113 -113
  31. package/src/components/UserPicker.vue +106 -106
  32. package/src/components/UserProfile.vue +129 -129
  33. package/src/components/UsersPicker.vue +118 -118
  34. package/src/components/YearDropdown.vue +59 -59
  35. package/src/components/data_selector.vue +303 -303
  36. package/src/components/image_picker.vue +123 -123
  37. package/src/components/images_picker.vue +195 -195
  38. package/src/components/index.js +162 -162
  39. package/src/components/mult_list_selector.vue +155 -155
  40. package/src/components/subdepartment_selector.vue +481 -481
  41. package/src/components/user_selector.vue +639 -639
  42. package/src/components/widget/DataPop.vue +95 -95
  43. package/src/components/widget/TreePop.vue +88 -88
  44. package/src/components/widget/UserPop.vue +347 -347
  45. package/src/env.d.ts +8 -8
  46. package/src/main.js +117 -117
  47. package/src/router/index.ts +173 -173
  48. package/src/util/array_util.js +32 -32
  49. package/src/util/auth_util.js +72 -73
  50. package/src/util/bool_util.js +5 -5
  51. package/src/util/bus.js +1 -1
  52. package/src/util/cache_util.js +18 -18
  53. package/src/util/errer_code.js +6 -6
  54. package/src/util/eval_util.js +19 -19
  55. package/src/util/icon_util.js +36 -36
  56. package/src/util/image_util.js +27 -27
  57. package/src/util/index.js +57 -57
  58. package/src/util/num_util.js +70 -70
  59. package/src/util/obj_util.js +28 -28
  60. package/src/util/request.js +73 -73
  61. package/src/util/request_json.js +71 -71
  62. package/src/util/request_json_mute.js +65 -65
  63. package/src/util/request_upload.js +79 -79
  64. package/src/util/route_util.js +31 -31
  65. package/src/util/str_util.js +143 -143
  66. package/src/util/time_util.js +406 -406
  67. package/src/util/toast_util.js +24 -24
  68. package/src/util/tree_util.js +153 -153
  69. package/src/util/uuid_util.js +9 -9
  70. package/src/util/validate.js +182 -182
  71. package/src/util/vue_filter.js +223 -223
  72. package/src/views/auditbar/index.vue +78 -78
  73. package/src/views/billcard/index.vue +48 -48
  74. package/src/views/cascaderpop/index.vue +90 -90
  75. package/src/views/checkgroup/index.vue +35 -35
  76. package/src/views/dataSelector/index.vue +48 -48
  77. package/src/views/datetimepicker/index.vue +34 -34
  78. package/src/views/daydropdown/index.vue +31 -31
  79. package/src/views/filepicker/index.vue +32 -32
  80. package/src/views/imagepicker/index.vue +31 -31
  81. package/src/views/imagespicker/index.vue +37 -37
  82. package/src/views/input/index.vue +35 -35
  83. package/src/views/monthdropdown/index.vue +31 -31
  84. package/src/views/multListSelector/index.vue +61 -61
  85. package/src/views/multipicker/index.vue +36 -36
  86. package/src/views/objseditor/index.vue +277 -277
  87. package/src/views/productSelector/index.vue +35 -35
  88. package/src/views/projectpicker/index.vue +41 -41
  89. package/src/views/secretnotify/index.vue +27 -27
  90. package/src/views/sheet/index.vue +45 -45
  91. package/src/views/singlepicker/index.vue +35 -35
  92. package/src/views/subdepartmentSelector/index.vue +40 -40
  93. package/src/views/switch/index.vue +34 -34
  94. package/src/views/treepicker/index.vue +41 -41
  95. package/src/views/userSelector/index.vue +54 -54
  96. package/src/views/userSelectorNew/index.vue +45 -45
  97. package/src/views/userpicker/index.vue +43 -43
  98. package/src/views/userprofile/index.vue +30 -30
  99. package/src/views/userspicker/index.vue +43 -43
  100. package/src/views/yeardropdown/index.vue +32 -32
  101. package/src/vm/index.js +1 -1
  102. package/tsconfig.json +19 -19
  103. package/tsconfig.node.json +8 -8
  104. 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>