vome-core 0.3.68 → 0.3.70

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 (280) hide show
  1. package/dist/admin/components/ui/avatar/Avatar.vue +18 -18
  2. package/dist/admin/components/ui/avatar/AvatarFallback.vue +21 -21
  3. package/dist/admin/components/ui/avatar/AvatarImage.vue +16 -16
  4. package/dist/admin/components/ui/avatar/index.js +1 -1
  5. package/dist/admin/components/ui/badge/Badge.vue +26 -26
  6. package/dist/admin/components/ui/button/Button.vue +31 -31
  7. package/dist/admin/components/ui/button/index.d.ts +1 -1
  8. package/dist/admin/components/ui/checkbox/Checkbox.vue +35 -35
  9. package/dist/admin/components/ui/checkbox/index.js +1 -1
  10. package/dist/admin/components/ui/dialog/Dialog.vue +19 -19
  11. package/dist/admin/components/ui/dialog/DialogClose.vue +15 -15
  12. package/dist/admin/components/ui/dialog/DialogContent.vue +267 -267
  13. package/dist/admin/components/ui/dialog/DialogDescription.vue +23 -23
  14. package/dist/admin/components/ui/dialog/DialogFooter.vue +27 -27
  15. package/dist/admin/components/ui/dialog/DialogHeader.vue +17 -17
  16. package/dist/admin/components/ui/dialog/DialogOverlay.vue +21 -21
  17. package/dist/admin/components/ui/dialog/DialogScrollContent.vue +60 -60
  18. package/dist/admin/components/ui/dialog/DialogTitle.vue +23 -23
  19. package/dist/admin/components/ui/dialog/DialogTrigger.vue +15 -15
  20. package/dist/admin/components/ui/dialog/index.js +1 -1
  21. package/dist/admin/components/ui/dropdown-menu/DropdownMenu.vue +19 -19
  22. package/dist/admin/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +39 -39
  23. package/dist/admin/components/ui/dropdown-menu/DropdownMenuContent.vue +39 -39
  24. package/dist/admin/components/ui/dropdown-menu/DropdownMenuGroup.vue +15 -15
  25. package/dist/admin/components/ui/dropdown-menu/DropdownMenuItem.vue +31 -31
  26. package/dist/admin/components/ui/dropdown-menu/DropdownMenuLabel.vue +23 -23
  27. package/dist/admin/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +21 -21
  28. package/dist/admin/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +40 -40
  29. package/dist/admin/components/ui/dropdown-menu/DropdownMenuSeparator.vue +23 -23
  30. package/dist/admin/components/ui/dropdown-menu/DropdownMenuShortcut.vue +17 -17
  31. package/dist/admin/components/ui/dropdown-menu/DropdownMenuSub.vue +18 -18
  32. package/dist/admin/components/ui/dropdown-menu/DropdownMenuSubContent.vue +27 -27
  33. package/dist/admin/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +31 -31
  34. package/dist/admin/components/ui/dropdown-menu/DropdownMenuTrigger.vue +17 -17
  35. package/dist/admin/components/ui/dropdown-menu/index.js +1 -1
  36. package/dist/admin/components/ui/input/Input.vue +183 -183
  37. package/dist/admin/components/ui/input/index.js +1 -1
  38. package/dist/admin/components/ui/label/Label.vue +26 -26
  39. package/dist/admin/components/ui/label/index.js +1 -1
  40. package/dist/admin/components/ui/scroll-area/ScrollArea.vue +33 -33
  41. package/dist/admin/components/ui/scroll-area/ScrollBar.vue +32 -32
  42. package/dist/admin/components/ui/scroll-area/index.js +1 -1
  43. package/dist/admin/components/ui/select/Select.vue +19 -19
  44. package/dist/admin/components/ui/select/SelectContent.vue +51 -51
  45. package/dist/admin/components/ui/select/SelectGroup.vue +15 -15
  46. package/dist/admin/components/ui/select/SelectItem.vue +53 -53
  47. package/dist/admin/components/ui/select/SelectItemText.vue +15 -15
  48. package/dist/admin/components/ui/select/SelectLabel.vue +17 -17
  49. package/dist/admin/components/ui/select/SelectScrollDownButton.vue +26 -26
  50. package/dist/admin/components/ui/select/SelectScrollUpButton.vue +26 -26
  51. package/dist/admin/components/ui/select/SelectSeparator.vue +19 -19
  52. package/dist/admin/components/ui/select/SelectTrigger.vue +44 -44
  53. package/dist/admin/components/ui/select/SelectValue.vue +15 -15
  54. package/dist/admin/components/ui/select/index.js +1 -1
  55. package/dist/admin/components/ui/separator/Separator.vue +29 -29
  56. package/dist/admin/components/ui/separator/index.js +1 -1
  57. package/dist/admin/components/ui/sheet/Sheet.vue +19 -19
  58. package/dist/admin/components/ui/sheet/SheetClose.vue +15 -15
  59. package/dist/admin/components/ui/sheet/SheetContent.vue +63 -63
  60. package/dist/admin/components/ui/sheet/SheetDescription.vue +21 -21
  61. package/dist/admin/components/ui/sheet/SheetFooter.vue +16 -16
  62. package/dist/admin/components/ui/sheet/SheetHeader.vue +15 -15
  63. package/dist/admin/components/ui/sheet/SheetOverlay.vue +21 -21
  64. package/dist/admin/components/ui/sheet/SheetTitle.vue +21 -21
  65. package/dist/admin/components/ui/sheet/SheetTrigger.vue +15 -15
  66. package/dist/admin/components/ui/sheet/index.js +1 -1
  67. package/dist/admin/components/ui/sonner/Toaster.vue +117 -117
  68. package/dist/admin/components/ui/sonner/index.js +1 -1
  69. package/dist/admin/components/ui/table/Table.vue +16 -16
  70. package/dist/admin/components/ui/table/TableBody.vue +17 -17
  71. package/dist/admin/components/ui/table/TableCaption.vue +17 -17
  72. package/dist/admin/components/ui/table/TableCell.vue +22 -22
  73. package/dist/admin/components/ui/table/TableEmpty.vue +34 -34
  74. package/dist/admin/components/ui/table/TableFooter.vue +17 -17
  75. package/dist/admin/components/ui/table/TableHead.vue +17 -17
  76. package/dist/admin/components/ui/table/TableHeader.vue +17 -17
  77. package/dist/admin/components/ui/table/TableRow.vue +17 -17
  78. package/dist/admin/components/ui/table/index.js +1 -1
  79. package/dist/admin/components/ui/tabs/Tabs.vue +24 -24
  80. package/dist/admin/components/ui/tabs/TabsContent.vue +21 -21
  81. package/dist/admin/components/ui/tabs/TabsList.vue +24 -24
  82. package/dist/admin/components/ui/tabs/TabsTrigger.vue +26 -26
  83. package/dist/admin/components/ui/tabs/index.js +1 -1
  84. package/dist/admin/components/ui/tooltip/Tooltip.vue +19 -19
  85. package/dist/admin/components/ui/tooltip/TooltipContent.vue +34 -34
  86. package/dist/admin/components/ui/tooltip/TooltipProvider.vue +14 -14
  87. package/dist/admin/components/ui/tooltip/TooltipTrigger.vue +15 -15
  88. package/dist/admin/components/ui/tooltip/index.js +1 -1
  89. package/dist/admin/components/vm-action-btn.vue +136 -136
  90. package/dist/admin/components/vm-aside-list-item.vue +136 -136
  91. package/dist/admin/components/vm-aside.vue +229 -229
  92. package/dist/admin/components/vm-auto-perm-dialog.vue +371 -371
  93. package/dist/admin/components/vm-card-grid.vue +37 -37
  94. package/dist/admin/components/vm-card-head.vue +45 -45
  95. package/dist/admin/components/vm-card-meta.vue +22 -22
  96. package/dist/admin/components/vm-card-title.vue +56 -56
  97. package/dist/admin/components/vm-card.vue +176 -176
  98. package/dist/admin/components/vm-check-tree.vue +340 -340
  99. package/dist/admin/components/vm-clamp-text.vue +39 -39
  100. package/dist/admin/components/vm-dept-tree.vue +497 -497
  101. package/dist/admin/components/vm-empty.vue +68 -68
  102. package/dist/admin/components/vm-eps-perm-picker.vue +123 -123
  103. package/dist/admin/components/vm-form-dialog.vue +632 -632
  104. package/dist/admin/components/vm-group-cascader.vue +560 -560
  105. package/dist/admin/components/vm-icon-btn.vue +88 -88
  106. package/dist/admin/components/vm-icon-picker.vue +647 -647
  107. package/dist/admin/components/vm-inline-search.vue +61 -61
  108. package/dist/admin/components/vm-loading.vue +114 -114
  109. package/dist/admin/components/vm-markdown.vue +405 -405
  110. package/dist/admin/components/vm-marquee.vue +265 -265
  111. package/dist/admin/components/vm-page-tabs.vue +94 -94
  112. package/dist/admin/components/vm-perm-section.vue +57 -57
  113. package/dist/admin/components/vm-ri-icon.vue +23 -23
  114. package/dist/admin/components/vm-role-picker.vue +314 -314
  115. package/dist/admin/components/vm-split-layout.vue +106 -106
  116. package/dist/admin/components/vm-thumb.vue +49 -49
  117. package/dist/admin/components/vm-view-path-picker.vue +59 -59
  118. package/dist/admin/crud/auto-fields.d.ts +7 -0
  119. package/dist/admin/crud/auto-fields.js +1 -1
  120. package/dist/admin/crud/components/vm-avatar.vue +104 -104
  121. package/dist/admin/crud/components/vm-color-picker.vue +1626 -1626
  122. package/dist/admin/crud/components/vm-column-custom.vue +193 -193
  123. package/dist/admin/crud/components/vm-context-menu.vue +105 -105
  124. package/dist/admin/crud/components/vm-copy-btn.vue +102 -102
  125. package/dist/admin/crud/components/vm-date-calendar-panel.vue +537 -537
  126. package/dist/admin/crud/components/vm-date-picker.vue +75 -75
  127. package/dist/admin/crud/components/vm-date-range.vue +88 -88
  128. package/dist/admin/crud/components/vm-date-text.vue +42 -42
  129. package/dist/admin/crud/components/vm-datetime-field.vue +215 -215
  130. package/dist/admin/crud/components/vm-datetime-picker.vue +9 -9
  131. package/dist/admin/crud/components/vm-dict-tag.vue +112 -112
  132. package/dist/admin/crud/components/vm-ellipsis-text.vue +34 -34
  133. package/dist/admin/crud/components/vm-form-hint.vue +81 -81
  134. package/dist/admin/crud/components/vm-json-code.vue +41 -41
  135. package/dist/admin/crud/components/vm-json-editor.vue +161 -161
  136. package/dist/admin/crud/components/vm-json-kv-editor.vue +318 -318
  137. package/dist/admin/crud/components/vm-kv-row.vue +51 -51
  138. package/dist/admin/crud/components/vm-multi-select.vue +279 -279
  139. package/dist/admin/crud/components/vm-number-range.vue +204 -204
  140. package/dist/admin/crud/components/vm-preview-viewer.vue +894 -894
  141. package/dist/admin/crud/components/vm-radio.vue +87 -87
  142. package/dist/admin/crud/components/vm-richtext.vue +313 -313
  143. package/dist/admin/crud/components/vm-select.vue +570 -570
  144. package/dist/admin/crud/components/vm-status-tag.vue +158 -158
  145. package/dist/admin/crud/components/vm-switch.vue +158 -158
  146. package/dist/admin/crud/components/vm-tag-list.vue +108 -108
  147. package/dist/admin/crud/components/vm-text-link.vue +52 -52
  148. package/dist/admin/crud/components/vm-toggle-switch.vue +84 -84
  149. package/dist/admin/crud/components/vm-tree-select.vue +450 -450
  150. package/dist/admin/crud/components/vm-upload-item.vue +298 -298
  151. package/dist/admin/crud/components/vm-upload.vue +436 -436
  152. package/dist/admin/crud/config.js +1 -1
  153. package/dist/admin/crud/confirm.js +1 -1
  154. package/dist/admin/crud/crud-page-request.js +1 -1
  155. package/dist/admin/crud/data-i18n.js +1 -1
  156. package/dist/admin/crud/dict.js +1 -1
  157. package/dist/admin/crud/export-fetch.js +1 -1
  158. package/dist/admin/crud/index.d.ts +1 -1
  159. package/dist/admin/crud/index.js +1 -0
  160. package/dist/admin/crud/key.js +1 -1
  161. package/dist/admin/crud/plugins.js +1 -1
  162. package/dist/admin/crud/span.js +1 -1
  163. package/dist/admin/crud/style.js +1 -1
  164. package/dist/admin/crud/validate.js +1 -1
  165. package/dist/admin/crud/vm-add-btn.vue +70 -70
  166. package/dist/admin/crud/vm-adv-search.vue +83 -83
  167. package/dist/admin/crud/vm-crud.vue +711 -711
  168. package/dist/admin/crud/vm-data-i18n-btn.vue +456 -456
  169. package/dist/admin/crud/vm-data-i18n-lang.vue +183 -183
  170. package/dist/admin/crud/vm-export-btn.vue +183 -183
  171. package/dist/admin/crud/vm-flex.vue +14 -14
  172. package/dist/admin/crud/vm-import-btn.vue +747 -747
  173. package/dist/admin/crud/vm-multi-delete-btn.vue +49 -49
  174. package/dist/admin/crud/vm-pagination.vue +212 -212
  175. package/dist/admin/crud/vm-refresh-btn.vue +35 -35
  176. package/dist/admin/crud/vm-row.vue +55 -55
  177. package/dist/admin/crud/vm-search-key.vue +78 -78
  178. package/dist/admin/crud/vm-search.vue +466 -466
  179. package/dist/admin/crud/vm-table.vue +1390 -1390
  180. package/dist/admin/crud/vm-tabs.vue +94 -94
  181. package/dist/admin/crud/vm-toolbar.vue +357 -357
  182. package/dist/admin/crud/vm-upsert.vue +765 -765
  183. package/dist/admin/directives/perm.js +1 -1
  184. package/dist/admin/env.d.ts +1 -1
  185. package/dist/admin/hooks/useUpload.js +1 -1
  186. package/dist/admin/hooks/useVome.d.ts +2 -2
  187. package/dist/admin/lib/browser.js +1 -1
  188. package/dist/admin/lib/cn.js +1 -1
  189. package/dist/admin/lib/dialog-float.js +1 -1
  190. package/dist/admin/lib/export-excel.js +1 -1
  191. package/dist/admin/lib/import-excel.js +1 -1
  192. package/dist/admin/lib/json.js +1 -1
  193. package/dist/admin/lib/menu.js +1 -1
  194. package/dist/admin/lib/micro-locale.js +1 -1
  195. package/dist/admin/lib/micro-theme.js +1 -1
  196. package/dist/admin/lib/shell.js +1 -1
  197. package/dist/admin/lib/theme-color-options.js +1 -1
  198. package/dist/admin/lib/tree.js +1 -1
  199. package/dist/admin/lib/upload.js +1 -1
  200. package/dist/admin/lib/video-frame.js +1 -1
  201. package/dist/admin/router/create.js +1 -1
  202. package/dist/admin/router/guards.js +1 -1
  203. package/dist/admin/stores/app.d.ts +13 -1
  204. package/dist/admin/stores/tags.d.ts +14 -1
  205. package/dist/admin/stores/user.d.ts +48 -1
  206. package/dist/admin/styles/base.css +121 -121
  207. package/dist/admin/vendor/browser-image-compression.js +1144 -1144
  208. package/dist/admin/vendor/decimal-js.js +12 -12
  209. package/dist/admin/vendor/xlsx.js +263 -263
  210. package/dist/ai/index.js +1 -1
  211. package/dist/client/ai-model-registry.js +1 -1
  212. package/dist/client/base.js +1 -1
  213. package/dist/client/configure.js +1 -1
  214. package/dist/client/dict.js +1 -1
  215. package/dist/client/eps-payload.js +1 -1
  216. package/dist/client/eps.d.ts +7 -4
  217. package/dist/client/eps.js +1 -1
  218. package/dist/client/index.d.ts +1 -1
  219. package/dist/client/index.js +1 -1
  220. package/dist/client/locale-change.js +1 -1
  221. package/dist/client/locale-store.d.ts +45 -1
  222. package/dist/client/locale-store.js +1 -1
  223. package/dist/client/path.js +1 -1
  224. package/dist/client/service.d.ts +8 -0
  225. package/dist/client/service.js +1 -1
  226. package/dist/client/vite-admin.js +1 -1
  227. package/dist/client/vite-auto-import.js +1 -1
  228. package/dist/client/vite-micro-proxy.js +1 -1
  229. package/dist/client/vite-plugin-eps.d.ts +5 -0
  230. package/dist/client/vite-plugin-eps.js +1 -1
  231. package/dist/client/vite-resolve.js +1 -1
  232. package/dist/config/index.js +1 -1
  233. package/dist/core/index.d.ts +2 -2
  234. package/dist/index.js +1 -1
  235. package/dist/orm/entity-cache.d.ts +1 -1
  236. package/dist/orm/service.d.ts +5 -5
  237. package/dist/routing/context.d.ts +12 -2
  238. package/dist/routing/eps.d.ts +4 -1
  239. package/dist/routing/index.d.ts +2 -1
  240. package/dist/routing/metadata.d.ts +7 -3
  241. package/dist/server/index.d.ts +2 -2
  242. package/dist/server/index.js +1 -1
  243. package/dist/shared/datetime-picker.js +1 -1
  244. package/dist/shared/decimal.js +1 -1
  245. package/dist/shared/excel.js +1 -1
  246. package/dist/shared/index.js +1 -1
  247. package/dist/shared/locale-json.js +1 -1
  248. package/dist/shared/tree.js +1 -1
  249. package/dist/typings/routing/eps.d.ts +3 -1
  250. package/dist/typings/routing/meta.d.ts +15 -0
  251. package/package.json +1 -1
  252. package/typings/admin/base/auth.ts +24 -24
  253. package/typings/admin/base/permission.ts +19 -19
  254. package/typings/admin/comm/cascader.ts +7 -7
  255. package/typings/admin/comm/check-tree.ts +7 -7
  256. package/typings/admin/comm/crud.ts +427 -427
  257. package/typings/admin/comm/eps.ts +88 -86
  258. package/typings/admin/comm/request.ts +16 -16
  259. package/typings/admin/comm/service.ts +43 -43
  260. package/typings/admin/comm/upload.ts +39 -39
  261. package/typings/admin/theme.ts +10 -10
  262. package/typings/admin/vome/browser.ts +11 -11
  263. package/typings/admin/vome/module.ts +13 -13
  264. package/typings/admin/vome/tags.ts +9 -9
  265. package/typings/comm/crud.ts +83 -83
  266. package/typings/comm/page.ts +18 -18
  267. package/typings/comm/scan.ts +6 -6
  268. package/typings/common.ts +4 -4
  269. package/typings/context/data.ts +10 -10
  270. package/typings/ext/market-seat.ts +42 -42
  271. package/typings/ext/market.ts +55 -55
  272. package/typings/ext/module.ts +64 -64
  273. package/typings/ext/plugin.ts +38 -38
  274. package/typings/ioc/metadata.ts +34 -34
  275. package/typings/logger/config.ts +6 -6
  276. package/typings/routing/crud.ts +199 -199
  277. package/typings/routing/eps.ts +90 -88
  278. package/typings/routing/meta.ts +80 -63
  279. package/typings/routing/register.ts +21 -21
  280. package/typings/routing/scan-context.ts +7 -7
@@ -1,450 +1,450 @@
1
- <template>
2
- <div ref="rootRef" class="vm-tree-select">
3
- <button
4
- ref="triggerRef"
5
- type="button"
6
- class="vm-tree-select__trigger"
7
- :class="{ 'is-open': open, 'is-disabled': disabled }"
8
- :disabled="disabled"
9
- @click="toggle"
10
- >
11
- <span v-if="displayLabel" class="vm-tree-select__value">{{ displayLabel }}</span>
12
- <span v-else class="vm-tree-select__ph">{{ placeholder || crudLabel('pleaseSelect') }}</span>
13
- <i
14
- v-if="clearable && hasValue"
15
- class="ri-close-circle-fill vm-tree-select__clear"
16
- @click.stop="clear"
17
- />
18
- <i class="ri-arrow-down-s-line vm-tree-select__caret" />
19
- </button>
20
-
21
- <Teleport to="body">
22
- <div
23
- v-if="open"
24
- ref="panelRef"
25
- class="vm-tree-select__panel vm-dialog-float"
26
- :style="panelStyle"
27
- >
28
- <div
29
- v-for="row in visibleRows"
30
- :key="row.key"
31
- class="vm-tree-select__row"
32
- :class="{ 'is-active': row.key === inner }"
33
- :style="{ paddingLeft: `${10 + row.depth * 16}px` }"
34
- >
35
- <button
36
- v-if="row.hasKids"
37
- type="button"
38
- class="vm-tree-select__expand"
39
- :class="{ 'is-open': expanded.has(row.key) }"
40
- @click.stop="toggleExpand(row.key)"
41
- >
42
- <i class="ri-arrow-right-s-line" />
43
- </button>
44
- <span v-else class="vm-tree-select__expand is-spacer" />
45
- <button type="button" class="vm-tree-select__label" @click="pick(row.key)">
46
- {{ row.label }}
47
- </button>
48
- </div>
49
- <p v-if="!visibleRows.length" class="vm-tree-select__empty">{{ crudLabel('empty') }}</p>
50
- </div>
51
- </Teleport>
52
- </div>
53
- </template>
54
-
55
- <script setup lang="ts">
56
- import { crudLabel } from '../style'
57
- import { ref, computed, watch, nextTick, onMounted, onUnmounted } from 'vue'
58
- import { POPUP_TRIGGER_GAP } from '../../lib/dialog-float'
59
- import type { TreeSelectOption } from '../../../../typings/admin/comm/crud'
60
- type FlatRow = {
61
- key: string
62
- label: string
63
- depth: number
64
- hasKids: boolean
65
- }
66
-
67
- defineOptions({ name: 'vm-tree-select' })
68
-
69
- const props = withDefaults(
70
- defineProps<{
71
- modelValue?: string | number | boolean | null
72
- options?: TreeSelectOption[]
73
- labelKey?: string
74
- valueKey?: string
75
- placeholder?: string
76
- disabled?: boolean
77
- clearable?: boolean
78
- /** 默认全部展开(为 true 时忽略 defaultExpandLevel) */
79
- defaultExpandAll?: boolean
80
- /** 默认展开到第几级(1=仅根节点,2=展开根的下级,以此类推) */
81
- defaultExpandLevel?: number
82
- }>(),
83
- {
84
- labelKey: 'label',
85
- valueKey: 'value',
86
- clearable: true,
87
- defaultExpandAll: false,
88
- /** 1=仅展示一级(根收起);选中深层值时仍会展开祖先路径 */
89
- defaultExpandLevel: 1,
90
- },
91
- )
92
-
93
- const emit = defineEmits<{
94
- 'update:modelValue': [v: string]
95
- change: [v: string]
96
- }>()
97
-
98
- const rootRef = ref<HTMLElement | null>(null)
99
- const triggerRef = ref<HTMLElement | null>(null)
100
- const panelRef = ref<HTMLElement | null>(null)
101
- const open = ref(false)
102
- const expanded = ref<Set<string>>(new Set())
103
- const panelStyle = ref<Record<string, string>>({})
104
-
105
- const tree = computed(() => props.options || [])
106
-
107
- const inner = computed(() =>
108
- props.modelValue == null || props.modelValue === ''
109
- ? ''
110
- : String(props.modelValue),
111
- )
112
-
113
- const hasValue = computed(() => inner.value !== '')
114
-
115
- function findLabel(nodes: TreeSelectOption[], value: string): string | undefined {
116
- for (const n of nodes) {
117
- if (String(n[props.valueKey]) === value) {
118
- return String(n[props.labelKey] ?? '')
119
- }
120
- const kids = n.children
121
- if (kids?.length) {
122
- const hit = findLabel(kids, value)
123
- if (hit != null) return hit
124
- }
125
- }
126
- return undefined
127
- }
128
-
129
- const displayLabel = computed(() => {
130
- if (!inner.value) return ''
131
- return findLabel(tree.value, inner.value) || inner.value
132
- })
133
-
134
- function collectExpandable(nodes: TreeSelectOption[], into: Set<string>) {
135
- for (const n of nodes) {
136
- const kids = n.children
137
- if (kids?.length) {
138
- into.add(String(n[props.valueKey]))
139
- collectExpandable(kids, into)
140
- }
141
- }
142
- }
143
-
144
- /** 展开到指定层级:level=2 时根节点展开、二级可见、三级收起 */
145
- function collectExpandToLevel(
146
- nodes: TreeSelectOption[],
147
- depth: number,
148
- maxLevel: number,
149
- into: Set<string>,
150
- ) {
151
- if (depth >= maxLevel - 1) return
152
- for (const n of nodes) {
153
- const kids = n.children
154
- if (!kids?.length) continue
155
- into.add(String(n[props.valueKey]))
156
- collectExpandToLevel(kids, depth + 1, maxLevel, into)
157
- }
158
- }
159
-
160
- function findAncestorKeys(nodes: TreeSelectOption[], value: string, trail: string[] = []): string[] | null {
161
- for (const n of nodes) {
162
- const key = String(n[props.valueKey] ?? '')
163
- if (key === value) return trail
164
- const kids = n.children
165
- if (kids?.length) {
166
- const hit = findAncestorKeys(kids, value, [...trail, key])
167
- if (hit) return hit
168
- }
169
- }
170
- return null
171
- }
172
-
173
- function syncExpanded() {
174
- const next = new Set<string>()
175
- const opts = tree.value
176
- if (props.defaultExpandAll) {
177
- collectExpandable(opts, next)
178
- } else {
179
- const level = Math.max(1, props.defaultExpandLevel ?? 1)
180
- collectExpandToLevel(opts, 0, level, next)
181
- }
182
- if (inner.value) {
183
- for (const key of findAncestorKeys(opts, inner.value) || []) {
184
- next.add(key)
185
- }
186
- }
187
- expanded.value = next
188
- }
189
-
190
- function flatten(
191
- nodes: TreeSelectOption[],
192
- depth: number,
193
- out: FlatRow[],
194
- ) {
195
- for (const n of nodes) {
196
- const key = String(n[props.valueKey] ?? '')
197
- const kids = n.children || []
198
- const hasKids = kids.length > 0
199
- out.push({
200
- key,
201
- label: String(n[props.labelKey] ?? ''),
202
- depth,
203
- hasKids,
204
- })
205
- if (hasKids && expanded.value.has(key)) {
206
- flatten(kids, depth + 1, out)
207
- }
208
- }
209
- }
210
-
211
- const visibleRows = computed(() => {
212
- const out: FlatRow[] = []
213
- flatten(tree.value, 0, out)
214
- return out
215
- })
216
-
217
- watch(
218
- () => [props.options, props.defaultExpandAll, props.defaultExpandLevel, inner.value],
219
- () => syncExpanded(),
220
- { immediate: true, deep: true },
221
- )
222
-
223
- function syncPanelPos() {
224
- const el = triggerRef.value
225
- if (!el) return
226
- const r = el.getBoundingClientRect()
227
- const width = Math.max(r.width, 260)
228
- // 贴触发框左侧 + 底部
229
- panelStyle.value = {
230
- position: 'fixed',
231
- top: `${r.bottom + POPUP_TRIGGER_GAP}px`,
232
- left: `${r.left}px`,
233
- width: `${width}px`,
234
- zIndex: '200',
235
- }
236
- }
237
-
238
- async function toggle() {
239
- if (props.disabled) return
240
- open.value = !open.value
241
- if (open.value) {
242
- syncExpanded()
243
- await nextTick()
244
- syncPanelPos()
245
- }
246
- }
247
-
248
- function toggleExpand(key: string) {
249
- const next = new Set(expanded.value)
250
- if (next.has(key)) next.delete(key)
251
- else next.add(key)
252
- expanded.value = next
253
- }
254
-
255
- function pick(value: string) {
256
- emit('update:modelValue', value)
257
- emit('change', value)
258
- open.value = false
259
- }
260
-
261
- function clear() {
262
- emit('update:modelValue', '')
263
- emit('change', '')
264
- }
265
-
266
- function isInside(el: Node | null) {
267
- if (!el) return false
268
- if (rootRef.value?.contains(el)) return true
269
- if (panelRef.value?.contains(el)) return true
270
- return el instanceof Element && Boolean(el.closest('.vm-tree-select__panel'))
271
- }
272
-
273
- function onDocPointer(e: Event) {
274
- const t = e.target as Node | null
275
- if (!isInside(t)) open.value = false
276
- }
277
-
278
- function onWinChange() {
279
- if (open.value) syncPanelPos()
280
- }
281
-
282
- onMounted(() => {
283
- document.addEventListener('pointerdown', onDocPointer, true)
284
- window.addEventListener('resize', onWinChange)
285
- window.addEventListener('scroll', onWinChange, true)
286
- })
287
- onUnmounted(() => {
288
- document.removeEventListener('pointerdown', onDocPointer, true)
289
- window.removeEventListener('resize', onWinChange)
290
- window.removeEventListener('scroll', onWinChange, true)
291
- })
292
- </script>
293
-
294
- <style lang="scss">
295
- .vm-tree-select {
296
- position: relative;
297
- width: 100%;
298
- }
299
-
300
- .vm-tree-select__trigger {
301
- display: flex;
302
- width: 100%;
303
- height: var(--vm-control-height);
304
- min-height: var(--vm-control-height);
305
- align-items: center;
306
- gap: 6px;
307
- padding: 0 var(--vm-control-padding-x) 0 12px;
308
- border: 1px solid var(--input);
309
- border-radius: var(--vm-control-radius);
310
- background: var(--background);
311
- color: var(--foreground);
312
- font-size: var(--vm-control-font-size);
313
- text-align: left;
314
- cursor: pointer;
315
- transition:
316
- border-color 0.15s ease,
317
- box-shadow 0.15s ease;
318
-
319
- outline: none;
320
-
321
- &:hover:not(.is-disabled) {
322
- border-color: color-mix(in srgb, var(--brand) 45%, var(--input));
323
- }
324
-
325
- /* 弹层触发:不展示打开/聚焦描边 */
326
-
327
- &.is-disabled {
328
- cursor: not-allowed;
329
- opacity: 0.55;
330
- }
331
- }
332
-
333
- .vm-tree-select__value {
334
- flex: 1;
335
- min-width: 0;
336
- overflow: hidden;
337
- text-overflow: ellipsis;
338
- white-space: nowrap;
339
- }
340
-
341
- .vm-tree-select__ph {
342
- flex: 1;
343
- color: var(--muted-foreground);
344
- }
345
-
346
- .vm-tree-select__clear {
347
- flex-shrink: 0;
348
- color: var(--muted-foreground);
349
- font-size: 14px;
350
- cursor: pointer;
351
-
352
- &:hover {
353
- color: var(--foreground);
354
- }
355
- }
356
-
357
- .vm-tree-select__caret {
358
- flex-shrink: 0;
359
- opacity: 0.45;
360
- font-size: 16px;
361
- }
362
-
363
- .vm-tree-select__label {
364
- flex: 1;
365
- min-width: 0;
366
- padding: 6px 8px 6px 2px;
367
- border: none;
368
- background: transparent;
369
- color: var(--foreground);
370
- font-size: 14px;
371
- text-align: left;
372
- cursor: pointer;
373
- overflow: hidden;
374
- text-overflow: ellipsis;
375
- white-space: nowrap;
376
- }
377
-
378
- /* Teleport 到 body,不可 scoped */
379
- .vm-tree-select__panel {
380
- max-height: min(320px, 50vh);
381
- overflow: auto;
382
- overscroll-behavior: contain;
383
- padding: 6px;
384
- border: 1px solid var(--border);
385
- border-radius: 12px;
386
- background: var(--card);
387
- box-shadow: var(--shadow-soft);
388
- }
389
-
390
- .vm-tree-select__row {
391
- display: flex;
392
- min-height: 28px;
393
- align-items: center;
394
- gap: 2px;
395
- border-radius: 8px;
396
-
397
- &.is-active {
398
- background: var(--brand-soft);
399
-
400
- .vm-tree-select__label {
401
- color: var(--brand);
402
- font-weight: 650;
403
- }
404
- }
405
-
406
- &:hover:not(.is-active) {
407
- background: var(--muted);
408
- }
409
- }
410
-
411
- .vm-tree-select__expand {
412
- display: inline-flex;
413
- width: 22px;
414
- height: 22px;
415
- flex-shrink: 0;
416
- align-items: center;
417
- justify-content: center;
418
- border: none;
419
- border-radius: 6px;
420
- background: transparent;
421
- color: var(--muted-foreground);
422
- cursor: pointer;
423
-
424
- i {
425
- font-size: 16px;
426
- transition: transform 0.15s ease;
427
- }
428
-
429
- &.is-open i {
430
- transform: rotate(90deg);
431
- }
432
-
433
- &.is-spacer {
434
- cursor: default;
435
- visibility: hidden;
436
- }
437
-
438
- &:hover:not(.is-spacer) {
439
- background: color-mix(in srgb, var(--muted) 80%, var(--foreground) 8%);
440
- }
441
- }
442
-
443
- .vm-tree-select__empty {
444
- margin: 0;
445
- padding: 16px;
446
- color: var(--muted-foreground);
447
- font-size: 14px;
448
- text-align: center;
449
- }
450
- </style>
1
+ <template>
2
+ <div ref="rootRef" class="vm-tree-select">
3
+ <button
4
+ ref="triggerRef"
5
+ type="button"
6
+ class="vm-tree-select__trigger"
7
+ :class="{ 'is-open': open, 'is-disabled': disabled }"
8
+ :disabled="disabled"
9
+ @click="toggle"
10
+ >
11
+ <span v-if="displayLabel" class="vm-tree-select__value">{{ displayLabel }}</span>
12
+ <span v-else class="vm-tree-select__ph">{{ placeholder || crudLabel('pleaseSelect') }}</span>
13
+ <i
14
+ v-if="clearable && hasValue"
15
+ class="ri-close-circle-fill vm-tree-select__clear"
16
+ @click.stop="clear"
17
+ />
18
+ <i class="ri-arrow-down-s-line vm-tree-select__caret" />
19
+ </button>
20
+
21
+ <Teleport to="body">
22
+ <div
23
+ v-if="open"
24
+ ref="panelRef"
25
+ class="vm-tree-select__panel vm-dialog-float"
26
+ :style="panelStyle"
27
+ >
28
+ <div
29
+ v-for="row in visibleRows"
30
+ :key="row.key"
31
+ class="vm-tree-select__row"
32
+ :class="{ 'is-active': row.key === inner }"
33
+ :style="{ paddingLeft: `${10 + row.depth * 16}px` }"
34
+ >
35
+ <button
36
+ v-if="row.hasKids"
37
+ type="button"
38
+ class="vm-tree-select__expand"
39
+ :class="{ 'is-open': expanded.has(row.key) }"
40
+ @click.stop="toggleExpand(row.key)"
41
+ >
42
+ <i class="ri-arrow-right-s-line" />
43
+ </button>
44
+ <span v-else class="vm-tree-select__expand is-spacer" />
45
+ <button type="button" class="vm-tree-select__label" @click="pick(row.key)">
46
+ {{ row.label }}
47
+ </button>
48
+ </div>
49
+ <p v-if="!visibleRows.length" class="vm-tree-select__empty">{{ crudLabel('empty') }}</p>
50
+ </div>
51
+ </Teleport>
52
+ </div>
53
+ </template>
54
+
55
+ <script setup lang="ts">
56
+ import { crudLabel } from '../style'
57
+ import { ref, computed, watch, nextTick, onMounted, onUnmounted } from 'vue'
58
+ import { POPUP_TRIGGER_GAP } from '../../lib/dialog-float'
59
+ import type { TreeSelectOption } from '../../../../typings/admin/comm/crud'
60
+ type FlatRow = {
61
+ key: string
62
+ label: string
63
+ depth: number
64
+ hasKids: boolean
65
+ }
66
+
67
+ defineOptions({ name: 'vm-tree-select' })
68
+
69
+ const props = withDefaults(
70
+ defineProps<{
71
+ modelValue?: string | number | boolean | null
72
+ options?: TreeSelectOption[]
73
+ labelKey?: string
74
+ valueKey?: string
75
+ placeholder?: string
76
+ disabled?: boolean
77
+ clearable?: boolean
78
+ /** 默认全部展开(为 true 时忽略 defaultExpandLevel) */
79
+ defaultExpandAll?: boolean
80
+ /** 默认展开到第几级(1=仅根节点,2=展开根的下级,以此类推) */
81
+ defaultExpandLevel?: number
82
+ }>(),
83
+ {
84
+ labelKey: 'label',
85
+ valueKey: 'value',
86
+ clearable: true,
87
+ defaultExpandAll: false,
88
+ /** 1=仅展示一级(根收起);选中深层值时仍会展开祖先路径 */
89
+ defaultExpandLevel: 1,
90
+ },
91
+ )
92
+
93
+ const emit = defineEmits<{
94
+ 'update:modelValue': [v: string]
95
+ change: [v: string]
96
+ }>()
97
+
98
+ const rootRef = ref<HTMLElement | null>(null)
99
+ const triggerRef = ref<HTMLElement | null>(null)
100
+ const panelRef = ref<HTMLElement | null>(null)
101
+ const open = ref(false)
102
+ const expanded = ref<Set<string>>(new Set())
103
+ const panelStyle = ref<Record<string, string>>({})
104
+
105
+ const tree = computed(() => props.options || [])
106
+
107
+ const inner = computed(() =>
108
+ props.modelValue == null || props.modelValue === ''
109
+ ? ''
110
+ : String(props.modelValue),
111
+ )
112
+
113
+ const hasValue = computed(() => inner.value !== '')
114
+
115
+ function findLabel(nodes: TreeSelectOption[], value: string): string | undefined {
116
+ for (const n of nodes) {
117
+ if (String(n[props.valueKey]) === value) {
118
+ return String(n[props.labelKey] ?? '')
119
+ }
120
+ const kids = n.children
121
+ if (kids?.length) {
122
+ const hit = findLabel(kids, value)
123
+ if (hit != null) return hit
124
+ }
125
+ }
126
+ return undefined
127
+ }
128
+
129
+ const displayLabel = computed(() => {
130
+ if (!inner.value) return ''
131
+ return findLabel(tree.value, inner.value) || inner.value
132
+ })
133
+
134
+ function collectExpandable(nodes: TreeSelectOption[], into: Set<string>) {
135
+ for (const n of nodes) {
136
+ const kids = n.children
137
+ if (kids?.length) {
138
+ into.add(String(n[props.valueKey]))
139
+ collectExpandable(kids, into)
140
+ }
141
+ }
142
+ }
143
+
144
+ /** 展开到指定层级:level=2 时根节点展开、二级可见、三级收起 */
145
+ function collectExpandToLevel(
146
+ nodes: TreeSelectOption[],
147
+ depth: number,
148
+ maxLevel: number,
149
+ into: Set<string>,
150
+ ) {
151
+ if (depth >= maxLevel - 1) return
152
+ for (const n of nodes) {
153
+ const kids = n.children
154
+ if (!kids?.length) continue
155
+ into.add(String(n[props.valueKey]))
156
+ collectExpandToLevel(kids, depth + 1, maxLevel, into)
157
+ }
158
+ }
159
+
160
+ function findAncestorKeys(nodes: TreeSelectOption[], value: string, trail: string[] = []): string[] | null {
161
+ for (const n of nodes) {
162
+ const key = String(n[props.valueKey] ?? '')
163
+ if (key === value) return trail
164
+ const kids = n.children
165
+ if (kids?.length) {
166
+ const hit = findAncestorKeys(kids, value, [...trail, key])
167
+ if (hit) return hit
168
+ }
169
+ }
170
+ return null
171
+ }
172
+
173
+ function syncExpanded() {
174
+ const next = new Set<string>()
175
+ const opts = tree.value
176
+ if (props.defaultExpandAll) {
177
+ collectExpandable(opts, next)
178
+ } else {
179
+ const level = Math.max(1, props.defaultExpandLevel ?? 1)
180
+ collectExpandToLevel(opts, 0, level, next)
181
+ }
182
+ if (inner.value) {
183
+ for (const key of findAncestorKeys(opts, inner.value) || []) {
184
+ next.add(key)
185
+ }
186
+ }
187
+ expanded.value = next
188
+ }
189
+
190
+ function flatten(
191
+ nodes: TreeSelectOption[],
192
+ depth: number,
193
+ out: FlatRow[],
194
+ ) {
195
+ for (const n of nodes) {
196
+ const key = String(n[props.valueKey] ?? '')
197
+ const kids = n.children || []
198
+ const hasKids = kids.length > 0
199
+ out.push({
200
+ key,
201
+ label: String(n[props.labelKey] ?? ''),
202
+ depth,
203
+ hasKids,
204
+ })
205
+ if (hasKids && expanded.value.has(key)) {
206
+ flatten(kids, depth + 1, out)
207
+ }
208
+ }
209
+ }
210
+
211
+ const visibleRows = computed(() => {
212
+ const out: FlatRow[] = []
213
+ flatten(tree.value, 0, out)
214
+ return out
215
+ })
216
+
217
+ watch(
218
+ () => [props.options, props.defaultExpandAll, props.defaultExpandLevel, inner.value],
219
+ () => syncExpanded(),
220
+ { immediate: true, deep: true },
221
+ )
222
+
223
+ function syncPanelPos() {
224
+ const el = triggerRef.value
225
+ if (!el) return
226
+ const r = el.getBoundingClientRect()
227
+ const width = Math.max(r.width, 260)
228
+ // 贴触发框左侧 + 底部
229
+ panelStyle.value = {
230
+ position: 'fixed',
231
+ top: `${r.bottom + POPUP_TRIGGER_GAP}px`,
232
+ left: `${r.left}px`,
233
+ width: `${width}px`,
234
+ zIndex: '200',
235
+ }
236
+ }
237
+
238
+ async function toggle() {
239
+ if (props.disabled) return
240
+ open.value = !open.value
241
+ if (open.value) {
242
+ syncExpanded()
243
+ await nextTick()
244
+ syncPanelPos()
245
+ }
246
+ }
247
+
248
+ function toggleExpand(key: string) {
249
+ const next = new Set(expanded.value)
250
+ if (next.has(key)) next.delete(key)
251
+ else next.add(key)
252
+ expanded.value = next
253
+ }
254
+
255
+ function pick(value: string) {
256
+ emit('update:modelValue', value)
257
+ emit('change', value)
258
+ open.value = false
259
+ }
260
+
261
+ function clear() {
262
+ emit('update:modelValue', '')
263
+ emit('change', '')
264
+ }
265
+
266
+ function isInside(el: Node | null) {
267
+ if (!el) return false
268
+ if (rootRef.value?.contains(el)) return true
269
+ if (panelRef.value?.contains(el)) return true
270
+ return el instanceof Element && Boolean(el.closest('.vm-tree-select__panel'))
271
+ }
272
+
273
+ function onDocPointer(e: Event) {
274
+ const t = e.target as Node | null
275
+ if (!isInside(t)) open.value = false
276
+ }
277
+
278
+ function onWinChange() {
279
+ if (open.value) syncPanelPos()
280
+ }
281
+
282
+ onMounted(() => {
283
+ document.addEventListener('pointerdown', onDocPointer, true)
284
+ window.addEventListener('resize', onWinChange)
285
+ window.addEventListener('scroll', onWinChange, true)
286
+ })
287
+ onUnmounted(() => {
288
+ document.removeEventListener('pointerdown', onDocPointer, true)
289
+ window.removeEventListener('resize', onWinChange)
290
+ window.removeEventListener('scroll', onWinChange, true)
291
+ })
292
+ </script>
293
+
294
+ <style lang="scss">
295
+ .vm-tree-select {
296
+ position: relative;
297
+ width: 100%;
298
+ }
299
+
300
+ .vm-tree-select__trigger {
301
+ display: flex;
302
+ width: 100%;
303
+ height: var(--vm-control-height);
304
+ min-height: var(--vm-control-height);
305
+ align-items: center;
306
+ gap: 6px;
307
+ padding: 0 var(--vm-control-padding-x) 0 12px;
308
+ border: 1px solid var(--input);
309
+ border-radius: var(--vm-control-radius);
310
+ background: var(--background);
311
+ color: var(--foreground);
312
+ font-size: var(--vm-control-font-size);
313
+ text-align: left;
314
+ cursor: pointer;
315
+ transition:
316
+ border-color 0.15s ease,
317
+ box-shadow 0.15s ease;
318
+
319
+ outline: none;
320
+
321
+ &:hover:not(.is-disabled) {
322
+ border-color: color-mix(in srgb, var(--brand) 45%, var(--input));
323
+ }
324
+
325
+ /* 弹层触发:不展示打开/聚焦描边 */
326
+
327
+ &.is-disabled {
328
+ cursor: not-allowed;
329
+ opacity: 0.55;
330
+ }
331
+ }
332
+
333
+ .vm-tree-select__value {
334
+ flex: 1;
335
+ min-width: 0;
336
+ overflow: hidden;
337
+ text-overflow: ellipsis;
338
+ white-space: nowrap;
339
+ }
340
+
341
+ .vm-tree-select__ph {
342
+ flex: 1;
343
+ color: var(--muted-foreground);
344
+ }
345
+
346
+ .vm-tree-select__clear {
347
+ flex-shrink: 0;
348
+ color: var(--muted-foreground);
349
+ font-size: 14px;
350
+ cursor: pointer;
351
+
352
+ &:hover {
353
+ color: var(--foreground);
354
+ }
355
+ }
356
+
357
+ .vm-tree-select__caret {
358
+ flex-shrink: 0;
359
+ opacity: 0.45;
360
+ font-size: 16px;
361
+ }
362
+
363
+ .vm-tree-select__label {
364
+ flex: 1;
365
+ min-width: 0;
366
+ padding: 6px 8px 6px 2px;
367
+ border: none;
368
+ background: transparent;
369
+ color: var(--foreground);
370
+ font-size: 14px;
371
+ text-align: left;
372
+ cursor: pointer;
373
+ overflow: hidden;
374
+ text-overflow: ellipsis;
375
+ white-space: nowrap;
376
+ }
377
+
378
+ /* Teleport 到 body,不可 scoped */
379
+ .vm-tree-select__panel {
380
+ max-height: min(320px, 50vh);
381
+ overflow: auto;
382
+ overscroll-behavior: contain;
383
+ padding: 6px;
384
+ border: 1px solid var(--border);
385
+ border-radius: 12px;
386
+ background: var(--card);
387
+ box-shadow: var(--shadow-soft);
388
+ }
389
+
390
+ .vm-tree-select__row {
391
+ display: flex;
392
+ min-height: 28px;
393
+ align-items: center;
394
+ gap: 2px;
395
+ border-radius: 8px;
396
+
397
+ &.is-active {
398
+ background: var(--brand-soft);
399
+
400
+ .vm-tree-select__label {
401
+ color: var(--brand);
402
+ font-weight: 650;
403
+ }
404
+ }
405
+
406
+ &:hover:not(.is-active) {
407
+ background: var(--muted);
408
+ }
409
+ }
410
+
411
+ .vm-tree-select__expand {
412
+ display: inline-flex;
413
+ width: 22px;
414
+ height: 22px;
415
+ flex-shrink: 0;
416
+ align-items: center;
417
+ justify-content: center;
418
+ border: none;
419
+ border-radius: 6px;
420
+ background: transparent;
421
+ color: var(--muted-foreground);
422
+ cursor: pointer;
423
+
424
+ i {
425
+ font-size: 16px;
426
+ transition: transform 0.15s ease;
427
+ }
428
+
429
+ &.is-open i {
430
+ transform: rotate(90deg);
431
+ }
432
+
433
+ &.is-spacer {
434
+ cursor: default;
435
+ visibility: hidden;
436
+ }
437
+
438
+ &:hover:not(.is-spacer) {
439
+ background: color-mix(in srgb, var(--muted) 80%, var(--foreground) 8%);
440
+ }
441
+ }
442
+
443
+ .vm-tree-select__empty {
444
+ margin: 0;
445
+ padding: 16px;
446
+ color: var(--muted-foreground);
447
+ font-size: 14px;
448
+ text-align: center;
449
+ }
450
+ </style>