wui-components-v2 1.1.69 → 1.1.71

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 (117) hide show
  1. package/api/core/index.ts +74 -74
  2. package/api/feishu.ts +20 -20
  3. package/api/index.ts +4 -4
  4. package/api/login.ts +51 -51
  5. package/api/menu.ts +45 -45
  6. package/api/page.ts +114 -114
  7. package/api/sys.ts +12 -12
  8. package/api/tree.ts +28 -28
  9. package/components/action-popup/action-popup.vue +116 -116
  10. package/components/add-address-list/add-address-list.vue +187 -187
  11. package/components/add-address-page/add-address-page.vue +77 -77
  12. package/components/add-address-page/config.ts +297 -297
  13. package/components/audio-play/audio-play.vue +220 -220
  14. package/components/batch-upload-file/batch-upload-file.vue +75 -75
  15. package/components/card-botom-buttons/card-botom-buttons.vue +279 -279
  16. package/components/custom-date-picker/custom-date-picker.vue +106 -106
  17. package/components/custom-select-picker/custom-select-picker.vue +95 -95
  18. package/components/demo-block/demo-block.vue +63 -63
  19. package/components/demo-card/demo-card.vue +44 -44
  20. package/components/detail-popup/detail-popup.vue +99 -99
  21. package/components/evaluation-page/evaluation-page.vue +196 -196
  22. package/components/fold-card/fold-card.vue +171 -171
  23. package/components/form-control/form-control.vue +661 -661
  24. package/components/global-loading/GlobalLoading.md +366 -366
  25. package/components/global-loading/global-loading.vue +55 -55
  26. package/components/global-message/GlobalMessage.md +570 -570
  27. package/components/global-message/global-message.vue +68 -68
  28. package/components/global-toast/GlobalToast.md +261 -261
  29. package/components/global-toast/global-toast.vue +56 -56
  30. package/components/label-value/label-value.vue +144 -144
  31. package/components/label-value/open-value-more.vue +56 -56
  32. package/components/list-top-buttons/list-top-buttons.vue +19 -19
  33. package/components/loading/loading.vue +5 -5
  34. package/components/login-form/login-form.vue +126 -126
  35. package/components/login-form-box/login-form-box.vue +65 -65
  36. package/components/mulselect-picker/mulselect-picker.vue +86 -86
  37. package/components/phone-login-form/phone-login-form.vue +204 -204
  38. package/components/privacy-popup/privacy-popup.vue +191 -191
  39. package/components/product-card/product-card.vue +201 -201
  40. package/components/scan-input/scan-input.vue +406 -406
  41. package/components/search/search.vue +128 -128
  42. package/components/select-col-picker/select-col-picker.vue +168 -168
  43. package/components/tab-search/tab-search.vue +73 -73
  44. package/components/tree-select/components/TreeNode.vue +200 -200
  45. package/components/tree-select/components/index.vue +471 -471
  46. package/components/tree-select/components/tree-select.vue +56 -56
  47. package/components/tree-select/index.vue +93 -93
  48. package/components/user-choose/user-choose.vue +169 -151
  49. package/components/video-play/video-play.vue +56 -56
  50. package/components/wui-auto-update-component/wui-auto-update-component.vue +132 -132
  51. package/components/wui-default/wui-default.vue +49 -49
  52. package/components/wui-details-page/wui-details-page.vue +147 -147
  53. package/components/wui-edit-page/wui-edit-page-copy.vue +757 -757
  54. package/components/wui-edit-page/wui-edit-page.vue +793 -793
  55. package/components/wui-enume-select-control/wui-enume-select-control.vue +92 -92
  56. package/components/wui-list/wui-list.vue +235 -235
  57. package/components/wui-login/wui-login.vue +43 -43
  58. package/components/wui-login1/wui-login.vue +41 -41
  59. package/components/wui-menus/wui-menus-top.vue +137 -137
  60. package/components/wui-menus/wui-menus-top1.vue +8 -8
  61. package/components/wui-menus/wui-menus.vue +247 -247
  62. package/components/wui-menus1/components/banner-carousel.vue +320 -320
  63. package/components/wui-menus1/components/navbar.vue +43 -43
  64. package/components/wui-menus1/components/quick-panel.vue +55 -55
  65. package/components/wui-menus1/components/search-bar.vue +49 -49
  66. package/components/wui-menus1/components/section-menus.vue +208 -208
  67. package/components/wui-menus1/wui-menus-top.vue +718 -718
  68. package/components/wui-menus1/wui-menus-top1.vue +398 -398
  69. package/components/wui-menus1/wui-menus.vue +564 -564
  70. package/components/wui-notify-info/notify-handle.vue +53 -53
  71. package/components/wui-notify-info/wui-notify-info.vue +280 -280
  72. package/components/wui-scan-binding-sensor/wui-scan-binding-sensor.vue +75 -75
  73. package/components/wui-search-history-babbar/components/SearchBar.vue +70 -70
  74. package/components/wui-search-history-babbar/wui-search-history-babbar.vue +204 -204
  75. package/components/wui-select-list/wui-select-list.vue +310 -310
  76. package/components/wui-select-popup/wui-select-popup.vue +613 -612
  77. package/components/wui-system-settings/wui-system-settings.vue +144 -144
  78. package/components/wui-tabbar/wui-tabbar.vue +106 -106
  79. package/components/wui-tree-page/components/tree-item.vue +238 -238
  80. package/components/wui-tree-page/wui-tree-page.vue +297 -297
  81. package/components/wui-update-component/wui-update-component.vue +221 -221
  82. package/components/wui-user/wui-user.vue +202 -202
  83. package/composables/types/theme.ts +65 -65
  84. package/composables/types/user.ts +5 -5
  85. package/composables/useColorGenerator.ts +221 -221
  86. package/composables/useCompanyFieldFilter.ts +91 -91
  87. package/composables/useEnumes.ts +43 -43
  88. package/composables/useEventBus.ts +37 -37
  89. package/composables/useGlobalLoading.ts +47 -47
  90. package/composables/useGlobalMessage.ts +53 -53
  91. package/composables/useGlobalToast.ts +63 -63
  92. package/composables/useLocale.ts +54 -54
  93. package/composables/useManualTheme.ts +179 -179
  94. package/composables/useMenus.ts +193 -193
  95. package/composables/useSectionMenus.ts +120 -120
  96. package/composables/useTabbar.ts +50 -50
  97. package/composables/useTheme.ts +74 -74
  98. package/composables/useUser.ts +24 -24
  99. package/index.d.ts +90 -90
  100. package/index.ts +83 -83
  101. package/package.json +24 -24
  102. package/static/iconfont/iconfont.css +63 -63
  103. package/store/language.ts +151 -151
  104. package/store/manualThemeStore.ts +143 -143
  105. package/store/persist.ts +41 -41
  106. package/store/themeStore.ts +74 -74
  107. package/store/userStore.ts +22 -22
  108. package/styles/dark-mode.css +523 -523
  109. package/styles/dark-mode.min.css +1 -1
  110. package/styles/dark-mode.scss +498 -498
  111. package/type.ts +154 -154
  112. package/utils/control-tree.ts +353 -353
  113. package/utils/control-type-supportor.ts +148 -148
  114. package/utils/eventBus.ts +36 -36
  115. package/utils/idata-scan.ts +99 -99
  116. package/utils/index.ts +979 -979
  117. package/utils/rsaEncrypt.ts +10 -10
@@ -1,471 +1,471 @@
1
- <script setup lang="ts">
2
- import { computed, defineEmits, defineOptions, defineProps, onMounted, ref, toRaw } from 'vue'
3
- import { useManualTheme } from '../../../composables/useManualTheme'
4
- import { treeConfigApi, treeDataApi, treeNodeDataApi } from '../../../api/tree'
5
- import { pageConfig } from '../../../api/page'
6
- import loadingCom from '../../loading/loading.vue'
7
- import TreeSelect from './tree-select.vue'
8
-
9
- defineOptions({
10
- name: 'TreeSelectIndex',
11
- })
12
- const props = defineProps<{
13
- sourceId: string
14
- selectMode: SelectMode
15
- }>()
16
- const emit = defineEmits(['handleConfirm', 'handleClose'])
17
- // 树形节点数据结构定义
18
- interface TreeNode {
19
- id: string
20
- name: string
21
- children?: TreeNode[]
22
- [key: string]: any // 允许其他自定义属性
23
- }
24
-
25
- const { currentThemeColor } = useManualTheme()
26
- // 选择模式类型
27
- type SelectMode = 'single' | 'multiple'
28
-
29
- // 示例树形数据
30
- // const treeData: TreeNode[] = [
31
- // {
32
- // id: '1',
33
- // name: '产品管理',
34
- // children: [
35
- // {
36
- // id: '1-1',
37
- // name: '产品列表',
38
- // children: [
39
- // { id: '1-1-1', name: '产品查询' },
40
- // { id: '1-1-2', name: '产品新增' },
41
- // { id: '1-1-3', name: '产品编辑' },
42
- // { id: '1-1-4', name: '产品删除' },
43
- // ],
44
- // },
45
- // {
46
- // id: '1-2',
47
- // name: '产品分类',
48
- // children: [
49
- // { id: '1-2-1', name: '分类管理' },
50
- // { id: '1-2-2', name: '分类排序' },
51
- // ],
52
- // },
53
- // ],
54
- // },
55
- // {
56
- // id: '2',
57
- // name: '订单管理',
58
- // children: [
59
- // { id: '2-1', name: '订单列表' },
60
- // { id: '2-2', name: '订单详情' },
61
- // { id: '2-3', name: '订单导出' },
62
- // ],
63
- // },
64
- // {
65
- // id: '3',
66
- // name: '用户管理',
67
- // children: [
68
- // {
69
- // id: '3-1',
70
- // name: '用户列表',
71
- // children: [
72
- // { id: '3-1-1', name: '用户查询' },
73
- // { id: '3-1-2', name: '用户编辑' },
74
- // ],
75
- // },
76
- // { id: '3-2', name: '角色管理' },
77
- // ],
78
- // },
79
- // { id: '4', name: '系统设置' },
80
- // ]
81
- const treeData = ref<TreeNode[]>([])
82
-
83
- // 选择模式
84
- const selectMode = ref<SelectMode>(props.selectMode)
85
-
86
- // 选中的节点ID
87
- const selectedKeys = ref<string[]>([])
88
- // 选中的节点
89
- const selectedNode = ref<TreeNode[]>([])
90
-
91
- // 搜索关键词
92
- const searchKeyword = ref('')
93
-
94
- // 所有节点的引用,用于滚动定位
95
- const nodeRefs = ref<Record<string, HTMLElement | null>>({})
96
-
97
- // 处理选择模式变更
98
- // function handleModeChange(mode: SelectMode) {
99
- // // 切换模式时清空选择
100
- // selectedKeys.value = []
101
- // }
102
- const treeConfigs = reactive({
103
- branchRatmplId: '',
104
- branchTitle: '',
105
- id: '',
106
- rootGtmplId: '',
107
- primaryColumnId1: '',
108
- primaryColumnId2: '',
109
- leafRatmplId: '',
110
- })
111
-
112
- const loading = ref(false)
113
- // 获取配置
114
- async function treeConfig1() {
115
- try {
116
- const res = await treeConfigApi(props.sourceId)
117
- treeConfigs.branchRatmplId = res.ttmplConfig.branchRatmplId
118
- treeConfigs.branchTitle = res.ttmplConfig.branchTitle
119
- treeConfigs.id = res.ttmplConfig.id
120
- treeConfigs.rootGtmplId = res.ttmplConfig.rootGtmplId
121
- treeConfigs.leafRatmplId = res.ttmplConfig.leafRatmplId
122
- treeConfig2(treeConfigs.branchRatmplId)
123
- }
124
- catch (error) {
125
- console.log('error:', error)
126
- // TODO handle the exception
127
- }
128
- }
129
-
130
- // 获取配置2
131
- async function treeConfig2(branchRatmplId: string) {
132
- try {
133
- const res1 = await pageConfig(branchRatmplId)
134
- treeConfigs.primaryColumnId1 = res1.ltmplConfig.primaryColumn.sourceId
135
- getTreeTopData(treeConfigs.id)
136
- }
137
- catch (error) {
138
- console.log('error:', error)
139
- // TODO handle the exception
140
- }
141
- }
142
-
143
- // 根据id找到树节点
144
- async function findTreeNodeandAppend(id: string, arr: TreeNode[], appendData: any) {
145
- for (let index = 0; index < arr.length; index++) {
146
- const element = arr[index]
147
- if (element.id === id) {
148
- element.children = appendData.map((item: any) => {
149
- return {
150
- id: item.code,
151
- name: item.fieldMap[treeConfigs.primaryColumnId1],
152
- children: [],
153
- }
154
- })
155
- }
156
- else {
157
- if (element.children)
158
- findTreeNodeandAppend(id, element.children, appendData)
159
- }
160
- }
161
- }
162
-
163
- // 获取树顶层数据
164
- async function getTreeTopData(id: string) {
165
- try {
166
- loading.value = true
167
- const res = await treeDataApi(id)
168
- treeData.value = res.entities.map((item: any) => {
169
- return {
170
- id: item.code,
171
- name: item.fieldMap[treeConfigs.primaryColumnId1],
172
- children: [],
173
- }
174
- })
175
- }
176
- catch (error) {
177
- console.log('error:', error)
178
- // TODO handle the exception
179
- }
180
- finally {
181
- loading.value = false
182
- }
183
- }
184
-
185
- // 获取树节点数据
186
- async function getTreeNodeData(node: TreeNode) {
187
- try {
188
- const res = await treeNodeDataApi(treeConfigs.branchRatmplId, node.id)
189
- if (!res.entities.length)
190
- return
191
- // 追加节点
192
- findTreeNodeandAppend(node.id, treeData.value, res.entities)
193
- }
194
- catch (error) {
195
- console.log('error:', error)
196
- }
197
- }
198
-
199
- function handleToggleExpand(node: TreeNode) {
200
- getTreeNodeData(node)
201
- }
202
-
203
- onMounted(() => {
204
- treeConfig1()
205
- })
206
-
207
- // 处理选择变更
208
- function handleSelectChange(keys: string[], node: TreeNode) {
209
- selectedNode.value = [node]
210
- selectedKeys.value = keys
211
- }
212
-
213
- // 处理标签关闭
214
- function handleTagClose(key: string) {
215
- selectedKeys.value = selectedKeys.value.filter(k => k !== key)
216
- }
217
-
218
- // 根据ID获取节点名称
219
- function getNodeNameByKey(key: string): string {
220
- const findNode = (nodes: TreeNode[]): TreeNode | null => {
221
- for (const node of nodes) {
222
- if (node.id === key) {
223
- return node
224
- }
225
- if (node.children && node.children.length > 0) {
226
- const found = findNode(node.children)
227
- if (found)
228
- return found
229
- }
230
- }
231
- return null
232
- }
233
-
234
- const node = findNode(treeData.value)
235
- return node ? node.name : ''
236
- }
237
-
238
- // 处理搜索
239
- function handleSearch(keyword?: string) {
240
- // 搜索逻辑在filteredTreeData计算属性中处理
241
- if (keyword !== undefined) {
242
- searchKeyword.value = keyword
243
- }
244
- }
245
-
246
- // 过滤后的树形数据
247
- const filteredTreeData = computed<TreeNode[]>(() => {
248
- if (!searchKeyword.value) {
249
- return treeData.value
250
- }
251
-
252
- const keyword = searchKeyword.value.toLowerCase()
253
-
254
- // 过滤节点的递归函数
255
- const filterNodes = (nodes: TreeNode[]): TreeNode[] => {
256
- return nodes
257
- .map((node) => {
258
- // 复制节点,避免修改原数据
259
- const newNode = { ...node }
260
-
261
- // 如果有子节点,递归过滤
262
- if (newNode.children && newNode.children.length > 0) {
263
- newNode.children = filterNodes(newNode.children)
264
- }
265
-
266
- // 节点名称包含关键词,或者有符合条件的子节点,则保留该节点
267
- const nodeNameMatch = newNode.name.toLowerCase().includes(keyword)
268
- const hasMatchingChildren = newNode.children && newNode.children.length > 0
269
-
270
- return nodeNameMatch || hasMatchingChildren ? newNode : null
271
- })
272
- .filter(Boolean) as TreeNode[]
273
- }
274
-
275
- return filterNodes(treeData.value)
276
- })
277
-
278
- // 记录节点引用
279
- function setNodeRef(id: string, el: HTMLElement | null) {
280
- if (el) {
281
- nodeRefs.value[id] = el
282
- }
283
- }
284
-
285
- // 滚动到指定节点
286
- function scrollToNode(id: string) {
287
- const node = nodeRefs.value[id]
288
- if (node) {
289
- node.scrollIntoView({ behavior: 'smooth', block: 'center' })
290
- // 高亮效果
291
- node.classList.add('node-highlight')
292
- setTimeout(() => {
293
- node.classList.remove('node-highlight')
294
- }, 2000)
295
- }
296
- }
297
-
298
- // 页面加载时展开有子节点的节点
299
- onMounted(() => {
300
- // 可以在这里添加默认展开逻辑
301
- })
302
-
303
- // 确定
304
- function handleConfirm() {
305
- emit('handleConfirm', toRaw(selectedKeys.value), toRaw(selectedNode.value), treeConfigs.leafRatmplId)
306
- }
307
-
308
- // 取消
309
- function handleClose() {
310
- emit('handleClose')
311
- }
312
- </script>
313
-
314
- <template>
315
- <view class="tree-select-page">
316
- <!-- 选择模式切换 -->
317
- <!-- <view class="mode-switch-container">
318
- <view class="mode-label">
319
- 选择模式:
320
- </view>
321
- <wd-radio-group
322
- v-model="selectMode"
323
- direction="horizontal"
324
- @change="handleModeChange"
325
- >
326
- <wd-radio name="single" label="单选" />
327
- <wd-radio name="multiple" label="多选" />
328
- </wd-radio-group>
329
- </view> -->
330
-
331
- <!-- 搜索框 -->
332
- <view class="search-container">
333
- <wd-search
334
- v-model="searchKeyword"
335
- placeholder-class="splaceholderClass"
336
- placeholder="搜索节点..."
337
- clearable
338
- @cancel="handleClose"
339
- @input="handleSearch"
340
- @clear="handleSearch"
341
- />
342
- </view>
343
- <!-- 已选结果 -->
344
- <view v-if="selectedKeys.length > 0" class="selected-result">
345
- <view class="result-title">
346
- 已选 {{ selectedKeys.length }} 项:
347
- </view>
348
- <view class="result-tags">
349
- <wd-tag
350
- v-for="key in selectedKeys"
351
- :key="key"
352
- :bg-color="currentThemeColor.primary"
353
- color="#fff"
354
- closable
355
- round
356
- @close="handleTagClose(key)"
357
- @click="scrollToNode(key)"
358
- >
359
- {{ getNodeNameByKey(key) }}
360
- </wd-tag>
361
- </view>
362
- </view>
363
-
364
- <!-- 树形选择区域 -->
365
- <loadingCom v-if="loading" />
366
- <view v-else class="tree-container">
367
- <tree-select
368
- :set-node-ref="setNodeRef"
369
- :tree-data="filteredTreeData"
370
- :select-mode="selectMode"
371
- :selected-keys="selectedKeys"
372
- @update:selected-keys="handleSelectChange"
373
- @toggle-expand="handleToggleExpand"
374
- />
375
-
376
- <!-- 空状态 -->
377
- <view v-if="filteredTreeData.length === 0" class="empty-state">
378
- <wd-icon name="search" size="48" color="#ccc" />
379
- <view class="empty-text">
380
- 没有找到匹配的节点
381
- </view>
382
- </view>
383
- </view>
384
- <view class="h-100px" />
385
- <!-- 底部按钮 -->
386
- <view class="fixed bottom-0 left-0 right-0 box-border w-100% flex p-2 bg-white dark:bg-[var(--wot-dark-background)]">
387
- <wd-button
388
- custom-class="flex-1"
389
- type="primary"
390
- @click="handleConfirm"
391
- >
392
- 确定
393
- </wd-button>
394
- </view>
395
- </view>
396
- </template>
397
-
398
- <style scoped>
399
- .mode-switch-container {
400
- padding: 16px;
401
- /* background-color: #fff; */
402
- margin-bottom: 10px;
403
- display: flex;
404
- align-items: center;
405
- }
406
-
407
- .mode-label {
408
- font-size: 14px;
409
- color: #666;
410
- margin-right: 12px;
411
- }
412
-
413
- .tree-container {
414
- /* background-color: #fff; */
415
- padding: 10px 0;
416
- min-height: 300px;
417
- /* max-height: calc(100vh - 220px); */
418
- }
419
-
420
- .empty-state {
421
- display: flex;
422
- flex-direction: column;
423
- align-items: center;
424
- justify-content: center;
425
- padding: 40px 0;
426
- color: #999;
427
- }
428
-
429
- .empty-text {
430
- margin-top: 16px;
431
- font-size: 14px;
432
- }
433
-
434
- .selected-result {
435
- /* background-color: #fff; */
436
- padding: 16px;
437
- }
438
-
439
- .result-title {
440
- text-align: left;
441
- font-size: 14px;
442
- color: #666;
443
- margin-bottom: 12px;
444
- }
445
-
446
- .result-tags {
447
- display: flex;
448
- flex-wrap: wrap;
449
- gap: 8px;
450
- }
451
-
452
- /* 节点高亮动画 */
453
- .node-highlight {
454
- animation: highlight 2s ease;
455
- }
456
- :deep(.wd-tag__close){
457
- line-height: 0.8;
458
- color:#fff;
459
- }
460
- :deep(.uni-input-input){
461
- text-align: left;
462
- }
463
- .splaceholderClass{
464
- text-align: left;
465
- }
466
-
467
- @keyframes highlight {
468
- 0% { background-color: rgba(64, 158, 255, 0.2); }
469
- 100% { background-color: transparent; }
470
- }
471
- </style>
1
+ <script setup lang="ts">
2
+ import { computed, defineEmits, defineOptions, defineProps, onMounted, ref, toRaw } from 'vue'
3
+ import { useManualTheme } from '../../../composables/useManualTheme'
4
+ import { treeConfigApi, treeDataApi, treeNodeDataApi } from '../../../api/tree'
5
+ import { pageConfig } from '../../../api/page'
6
+ import loadingCom from '../../loading/loading.vue'
7
+ import TreeSelect from './tree-select.vue'
8
+
9
+ defineOptions({
10
+ name: 'TreeSelectIndex',
11
+ })
12
+ const props = defineProps<{
13
+ sourceId: string
14
+ selectMode: SelectMode
15
+ }>()
16
+ const emit = defineEmits(['handleConfirm', 'handleClose'])
17
+ // 树形节点数据结构定义
18
+ interface TreeNode {
19
+ id: string
20
+ name: string
21
+ children?: TreeNode[]
22
+ [key: string]: any // 允许其他自定义属性
23
+ }
24
+
25
+ const { currentThemeColor } = useManualTheme()
26
+ // 选择模式类型
27
+ type SelectMode = 'single' | 'multiple'
28
+
29
+ // 示例树形数据
30
+ // const treeData: TreeNode[] = [
31
+ // {
32
+ // id: '1',
33
+ // name: '产品管理',
34
+ // children: [
35
+ // {
36
+ // id: '1-1',
37
+ // name: '产品列表',
38
+ // children: [
39
+ // { id: '1-1-1', name: '产品查询' },
40
+ // { id: '1-1-2', name: '产品新增' },
41
+ // { id: '1-1-3', name: '产品编辑' },
42
+ // { id: '1-1-4', name: '产品删除' },
43
+ // ],
44
+ // },
45
+ // {
46
+ // id: '1-2',
47
+ // name: '产品分类',
48
+ // children: [
49
+ // { id: '1-2-1', name: '分类管理' },
50
+ // { id: '1-2-2', name: '分类排序' },
51
+ // ],
52
+ // },
53
+ // ],
54
+ // },
55
+ // {
56
+ // id: '2',
57
+ // name: '订单管理',
58
+ // children: [
59
+ // { id: '2-1', name: '订单列表' },
60
+ // { id: '2-2', name: '订单详情' },
61
+ // { id: '2-3', name: '订单导出' },
62
+ // ],
63
+ // },
64
+ // {
65
+ // id: '3',
66
+ // name: '用户管理',
67
+ // children: [
68
+ // {
69
+ // id: '3-1',
70
+ // name: '用户列表',
71
+ // children: [
72
+ // { id: '3-1-1', name: '用户查询' },
73
+ // { id: '3-1-2', name: '用户编辑' },
74
+ // ],
75
+ // },
76
+ // { id: '3-2', name: '角色管理' },
77
+ // ],
78
+ // },
79
+ // { id: '4', name: '系统设置' },
80
+ // ]
81
+ const treeData = ref<TreeNode[]>([])
82
+
83
+ // 选择模式
84
+ const selectMode = ref<SelectMode>(props.selectMode)
85
+
86
+ // 选中的节点ID
87
+ const selectedKeys = ref<string[]>([])
88
+ // 选中的节点
89
+ const selectedNode = ref<TreeNode[]>([])
90
+
91
+ // 搜索关键词
92
+ const searchKeyword = ref('')
93
+
94
+ // 所有节点的引用,用于滚动定位
95
+ const nodeRefs = ref<Record<string, HTMLElement | null>>({})
96
+
97
+ // 处理选择模式变更
98
+ // function handleModeChange(mode: SelectMode) {
99
+ // // 切换模式时清空选择
100
+ // selectedKeys.value = []
101
+ // }
102
+ const treeConfigs = reactive({
103
+ branchRatmplId: '',
104
+ branchTitle: '',
105
+ id: '',
106
+ rootGtmplId: '',
107
+ primaryColumnId1: '',
108
+ primaryColumnId2: '',
109
+ leafRatmplId: '',
110
+ })
111
+
112
+ const loading = ref(false)
113
+ // 获取配置
114
+ async function treeConfig1() {
115
+ try {
116
+ const res = await treeConfigApi(props.sourceId)
117
+ treeConfigs.branchRatmplId = res.ttmplConfig.branchRatmplId
118
+ treeConfigs.branchTitle = res.ttmplConfig.branchTitle
119
+ treeConfigs.id = res.ttmplConfig.id
120
+ treeConfigs.rootGtmplId = res.ttmplConfig.rootGtmplId
121
+ treeConfigs.leafRatmplId = res.ttmplConfig.leafRatmplId
122
+ treeConfig2(treeConfigs.branchRatmplId)
123
+ }
124
+ catch (error) {
125
+ console.log('error:', error)
126
+ // TODO handle the exception
127
+ }
128
+ }
129
+
130
+ // 获取配置2
131
+ async function treeConfig2(branchRatmplId: string) {
132
+ try {
133
+ const res1 = await pageConfig(branchRatmplId)
134
+ treeConfigs.primaryColumnId1 = res1.ltmplConfig.primaryColumn.sourceId
135
+ getTreeTopData(treeConfigs.id)
136
+ }
137
+ catch (error) {
138
+ console.log('error:', error)
139
+ // TODO handle the exception
140
+ }
141
+ }
142
+
143
+ // 根据id找到树节点
144
+ async function findTreeNodeandAppend(id: string, arr: TreeNode[], appendData: any) {
145
+ for (let index = 0; index < arr.length; index++) {
146
+ const element = arr[index]
147
+ if (element.id === id) {
148
+ element.children = appendData.map((item: any) => {
149
+ return {
150
+ id: item.code,
151
+ name: item.fieldMap[treeConfigs.primaryColumnId1],
152
+ children: [],
153
+ }
154
+ })
155
+ }
156
+ else {
157
+ if (element.children)
158
+ findTreeNodeandAppend(id, element.children, appendData)
159
+ }
160
+ }
161
+ }
162
+
163
+ // 获取树顶层数据
164
+ async function getTreeTopData(id: string) {
165
+ try {
166
+ loading.value = true
167
+ const res = await treeDataApi(id)
168
+ treeData.value = res.entities.map((item: any) => {
169
+ return {
170
+ id: item.code,
171
+ name: item.fieldMap[treeConfigs.primaryColumnId1],
172
+ children: [],
173
+ }
174
+ })
175
+ }
176
+ catch (error) {
177
+ console.log('error:', error)
178
+ // TODO handle the exception
179
+ }
180
+ finally {
181
+ loading.value = false
182
+ }
183
+ }
184
+
185
+ // 获取树节点数据
186
+ async function getTreeNodeData(node: TreeNode) {
187
+ try {
188
+ const res = await treeNodeDataApi(treeConfigs.branchRatmplId, node.id)
189
+ if (!res.entities.length)
190
+ return
191
+ // 追加节点
192
+ findTreeNodeandAppend(node.id, treeData.value, res.entities)
193
+ }
194
+ catch (error) {
195
+ console.log('error:', error)
196
+ }
197
+ }
198
+
199
+ function handleToggleExpand(node: TreeNode) {
200
+ getTreeNodeData(node)
201
+ }
202
+
203
+ onMounted(() => {
204
+ treeConfig1()
205
+ })
206
+
207
+ // 处理选择变更
208
+ function handleSelectChange(keys: string[], node: TreeNode) {
209
+ selectedNode.value = [node]
210
+ selectedKeys.value = keys
211
+ }
212
+
213
+ // 处理标签关闭
214
+ function handleTagClose(key: string) {
215
+ selectedKeys.value = selectedKeys.value.filter(k => k !== key)
216
+ }
217
+
218
+ // 根据ID获取节点名称
219
+ function getNodeNameByKey(key: string): string {
220
+ const findNode = (nodes: TreeNode[]): TreeNode | null => {
221
+ for (const node of nodes) {
222
+ if (node.id === key) {
223
+ return node
224
+ }
225
+ if (node.children && node.children.length > 0) {
226
+ const found = findNode(node.children)
227
+ if (found)
228
+ return found
229
+ }
230
+ }
231
+ return null
232
+ }
233
+
234
+ const node = findNode(treeData.value)
235
+ return node ? node.name : ''
236
+ }
237
+
238
+ // 处理搜索
239
+ function handleSearch(keyword?: string) {
240
+ // 搜索逻辑在filteredTreeData计算属性中处理
241
+ if (keyword !== undefined) {
242
+ searchKeyword.value = keyword
243
+ }
244
+ }
245
+
246
+ // 过滤后的树形数据
247
+ const filteredTreeData = computed<TreeNode[]>(() => {
248
+ if (!searchKeyword.value) {
249
+ return treeData.value
250
+ }
251
+
252
+ const keyword = searchKeyword.value.toLowerCase()
253
+
254
+ // 过滤节点的递归函数
255
+ const filterNodes = (nodes: TreeNode[]): TreeNode[] => {
256
+ return nodes
257
+ .map((node) => {
258
+ // 复制节点,避免修改原数据
259
+ const newNode = { ...node }
260
+
261
+ // 如果有子节点,递归过滤
262
+ if (newNode.children && newNode.children.length > 0) {
263
+ newNode.children = filterNodes(newNode.children)
264
+ }
265
+
266
+ // 节点名称包含关键词,或者有符合条件的子节点,则保留该节点
267
+ const nodeNameMatch = newNode.name.toLowerCase().includes(keyword)
268
+ const hasMatchingChildren = newNode.children && newNode.children.length > 0
269
+
270
+ return nodeNameMatch || hasMatchingChildren ? newNode : null
271
+ })
272
+ .filter(Boolean) as TreeNode[]
273
+ }
274
+
275
+ return filterNodes(treeData.value)
276
+ })
277
+
278
+ // 记录节点引用
279
+ function setNodeRef(id: string, el: HTMLElement | null) {
280
+ if (el) {
281
+ nodeRefs.value[id] = el
282
+ }
283
+ }
284
+
285
+ // 滚动到指定节点
286
+ function scrollToNode(id: string) {
287
+ const node = nodeRefs.value[id]
288
+ if (node) {
289
+ node.scrollIntoView({ behavior: 'smooth', block: 'center' })
290
+ // 高亮效果
291
+ node.classList.add('node-highlight')
292
+ setTimeout(() => {
293
+ node.classList.remove('node-highlight')
294
+ }, 2000)
295
+ }
296
+ }
297
+
298
+ // 页面加载时展开有子节点的节点
299
+ onMounted(() => {
300
+ // 可以在这里添加默认展开逻辑
301
+ })
302
+
303
+ // 确定
304
+ function handleConfirm() {
305
+ emit('handleConfirm', toRaw(selectedKeys.value), toRaw(selectedNode.value), treeConfigs.leafRatmplId)
306
+ }
307
+
308
+ // 取消
309
+ function handleClose() {
310
+ emit('handleClose')
311
+ }
312
+ </script>
313
+
314
+ <template>
315
+ <view class="tree-select-page">
316
+ <!-- 选择模式切换 -->
317
+ <!-- <view class="mode-switch-container">
318
+ <view class="mode-label">
319
+ 选择模式:
320
+ </view>
321
+ <wd-radio-group
322
+ v-model="selectMode"
323
+ direction="horizontal"
324
+ @change="handleModeChange"
325
+ >
326
+ <wd-radio name="single" label="单选" />
327
+ <wd-radio name="multiple" label="多选" />
328
+ </wd-radio-group>
329
+ </view> -->
330
+
331
+ <!-- 搜索框 -->
332
+ <view class="search-container">
333
+ <wd-search
334
+ v-model="searchKeyword"
335
+ placeholder-class="splaceholderClass"
336
+ placeholder="搜索节点..."
337
+ clearable
338
+ @cancel="handleClose"
339
+ @input="handleSearch"
340
+ @clear="handleSearch"
341
+ />
342
+ </view>
343
+ <!-- 已选结果 -->
344
+ <view v-if="selectedKeys.length > 0" class="selected-result">
345
+ <view class="result-title">
346
+ 已选 {{ selectedKeys.length }} 项:
347
+ </view>
348
+ <view class="result-tags">
349
+ <wd-tag
350
+ v-for="key in selectedKeys"
351
+ :key="key"
352
+ :bg-color="currentThemeColor.primary"
353
+ color="#fff"
354
+ closable
355
+ round
356
+ @close="handleTagClose(key)"
357
+ @click="scrollToNode(key)"
358
+ >
359
+ {{ getNodeNameByKey(key) }}
360
+ </wd-tag>
361
+ </view>
362
+ </view>
363
+
364
+ <!-- 树形选择区域 -->
365
+ <loadingCom v-if="loading" />
366
+ <view v-else class="tree-container">
367
+ <tree-select
368
+ :set-node-ref="setNodeRef"
369
+ :tree-data="filteredTreeData"
370
+ :select-mode="selectMode"
371
+ :selected-keys="selectedKeys"
372
+ @update:selected-keys="handleSelectChange"
373
+ @toggle-expand="handleToggleExpand"
374
+ />
375
+
376
+ <!-- 空状态 -->
377
+ <view v-if="filteredTreeData.length === 0" class="empty-state">
378
+ <wd-icon name="search" size="48" color="#ccc" />
379
+ <view class="empty-text">
380
+ 没有找到匹配的节点
381
+ </view>
382
+ </view>
383
+ </view>
384
+ <view class="h-100px" />
385
+ <!-- 底部按钮 -->
386
+ <view class="fixed bottom-0 left-0 right-0 box-border w-100% flex p-2 bg-white dark:bg-[var(--wot-dark-background)]">
387
+ <wd-button
388
+ custom-class="flex-1"
389
+ type="primary"
390
+ @click="handleConfirm"
391
+ >
392
+ 确定
393
+ </wd-button>
394
+ </view>
395
+ </view>
396
+ </template>
397
+
398
+ <style scoped>
399
+ .mode-switch-container {
400
+ padding: 16px;
401
+ /* background-color: #fff; */
402
+ margin-bottom: 10px;
403
+ display: flex;
404
+ align-items: center;
405
+ }
406
+
407
+ .mode-label {
408
+ font-size: 14px;
409
+ color: #666;
410
+ margin-right: 12px;
411
+ }
412
+
413
+ .tree-container {
414
+ /* background-color: #fff; */
415
+ padding: 10px 0;
416
+ min-height: 300px;
417
+ /* max-height: calc(100vh - 220px); */
418
+ }
419
+
420
+ .empty-state {
421
+ display: flex;
422
+ flex-direction: column;
423
+ align-items: center;
424
+ justify-content: center;
425
+ padding: 40px 0;
426
+ color: #999;
427
+ }
428
+
429
+ .empty-text {
430
+ margin-top: 16px;
431
+ font-size: 14px;
432
+ }
433
+
434
+ .selected-result {
435
+ /* background-color: #fff; */
436
+ padding: 16px;
437
+ }
438
+
439
+ .result-title {
440
+ text-align: left;
441
+ font-size: 14px;
442
+ color: #666;
443
+ margin-bottom: 12px;
444
+ }
445
+
446
+ .result-tags {
447
+ display: flex;
448
+ flex-wrap: wrap;
449
+ gap: 8px;
450
+ }
451
+
452
+ /* 节点高亮动画 */
453
+ .node-highlight {
454
+ animation: highlight 2s ease;
455
+ }
456
+ :deep(.wd-tag__close){
457
+ line-height: 0.8;
458
+ color:#fff;
459
+ }
460
+ :deep(.uni-input-input){
461
+ text-align: left;
462
+ }
463
+ .splaceholderClass{
464
+ text-align: left;
465
+ }
466
+
467
+ @keyframes highlight {
468
+ 0% { background-color: rgba(64, 158, 255, 0.2); }
469
+ 100% { background-color: transparent; }
470
+ }
471
+ </style>