vome-core 0.3.64 → 0.3.66

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 (345) hide show
  1. package/dist/admin/api/client.js +7 -2
  2. package/dist/admin/components/ui/avatar/Avatar.vue +18 -18
  3. package/dist/admin/components/ui/avatar/AvatarFallback.vue +21 -21
  4. package/dist/admin/components/ui/avatar/AvatarImage.vue +16 -16
  5. package/dist/admin/components/ui/avatar/index.js +1 -1
  6. package/dist/admin/components/ui/badge/Badge.vue +26 -26
  7. package/dist/admin/components/ui/button/Button.vue +31 -31
  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.js +1 -1
  119. package/dist/admin/crud/components/vm-avatar.vue +104 -104
  120. package/dist/admin/crud/components/vm-color-picker.vue +1626 -1626
  121. package/dist/admin/crud/components/vm-column-custom.vue +193 -193
  122. package/dist/admin/crud/components/vm-context-menu.vue +105 -105
  123. package/dist/admin/crud/components/vm-copy-btn.vue +102 -102
  124. package/dist/admin/crud/components/vm-date-calendar-panel.vue +537 -537
  125. package/dist/admin/crud/components/vm-date-picker.vue +75 -75
  126. package/dist/admin/crud/components/vm-date-range.vue +88 -88
  127. package/dist/admin/crud/components/vm-date-text.vue +42 -42
  128. package/dist/admin/crud/components/vm-datetime-field.vue +215 -215
  129. package/dist/admin/crud/components/vm-datetime-picker.vue +9 -9
  130. package/dist/admin/crud/components/vm-dict-tag.vue +112 -112
  131. package/dist/admin/crud/components/vm-ellipsis-text.vue +34 -34
  132. package/dist/admin/crud/components/vm-form-hint.vue +81 -81
  133. package/dist/admin/crud/components/vm-json-code.vue +41 -41
  134. package/dist/admin/crud/components/vm-json-editor.vue +161 -161
  135. package/dist/admin/crud/components/vm-json-kv-editor.vue +318 -318
  136. package/dist/admin/crud/components/vm-kv-row.vue +51 -51
  137. package/dist/admin/crud/components/vm-multi-select.vue +279 -279
  138. package/dist/admin/crud/components/vm-number-range.vue +204 -204
  139. package/dist/admin/crud/components/vm-preview-viewer.vue +894 -894
  140. package/dist/admin/crud/components/vm-radio.vue +87 -87
  141. package/dist/admin/crud/components/vm-richtext.vue +313 -313
  142. package/dist/admin/crud/components/vm-select.vue +570 -570
  143. package/dist/admin/crud/components/vm-status-tag.vue +158 -158
  144. package/dist/admin/crud/components/vm-switch.vue +158 -158
  145. package/dist/admin/crud/components/vm-tag-list.vue +108 -108
  146. package/dist/admin/crud/components/vm-text-link.vue +52 -52
  147. package/dist/admin/crud/components/vm-toggle-switch.vue +84 -84
  148. package/dist/admin/crud/components/vm-tree-select.vue +450 -450
  149. package/dist/admin/crud/components/vm-upload-item.vue +298 -298
  150. package/dist/admin/crud/components/vm-upload.vue +436 -436
  151. package/dist/admin/crud/config.js +1 -1
  152. package/dist/admin/crud/confirm.js +1 -1
  153. package/dist/admin/crud/crud-page-request.js +1 -1
  154. package/dist/admin/crud/data-i18n.js +1 -1
  155. package/dist/admin/crud/dict.js +1 -1
  156. package/dist/admin/crud/export-fetch.js +1 -1
  157. package/dist/admin/crud/index.js +1 -0
  158. package/dist/admin/crud/key.js +1 -1
  159. package/dist/admin/crud/plugins.js +1 -1
  160. package/dist/admin/crud/span.js +1 -1
  161. package/dist/admin/crud/style.js +1 -1
  162. package/dist/admin/crud/useCrud.js +3 -0
  163. package/dist/admin/crud/validate.js +1 -1
  164. package/dist/admin/crud/vm-add-btn.vue +70 -70
  165. package/dist/admin/crud/vm-adv-search.vue +83 -83
  166. package/dist/admin/crud/vm-crud.vue +711 -711
  167. package/dist/admin/crud/vm-data-i18n-btn.vue +456 -456
  168. package/dist/admin/crud/vm-data-i18n-lang.vue +183 -183
  169. package/dist/admin/crud/vm-export-btn.vue +183 -183
  170. package/dist/admin/crud/vm-flex.vue +14 -14
  171. package/dist/admin/crud/vm-form.vue +33 -15
  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/index.d.ts +1 -31
  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/router/index.d.ts +1 -5
  204. package/dist/admin/service/index.d.ts +1 -11
  205. package/dist/admin/styles/base.css +121 -121
  206. package/dist/admin/vendor/browser-image-compression.js +1144 -1144
  207. package/dist/admin/vendor/decimal-js.js +12 -12
  208. package/dist/admin/vendor/xlsx.js +263 -263
  209. package/dist/ai/index.js +1 -1
  210. package/dist/client/admin-vendor.d.ts +1 -13
  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.js +1 -1
  217. package/dist/client/index.d.ts +1 -7
  218. package/dist/client/index.js +1 -1
  219. package/dist/client/locale-change.js +1 -1
  220. package/dist/client/locale-store.js +1 -1
  221. package/dist/client/service.js +1 -1
  222. package/dist/client/vite-admin.d.ts +1 -40
  223. package/dist/client/vite-admin.js +1 -1
  224. package/dist/client/vite-auto-import.d.ts +1 -2
  225. package/dist/client/vite-auto-import.js +1 -1
  226. package/dist/client/vite-micro-proxy.d.ts +1 -20
  227. package/dist/client/vite-micro-proxy.js +1 -1
  228. package/dist/client/vite-plugin-eps.d.ts +1 -20
  229. package/dist/client/vite-plugin-eps.js +1 -1
  230. package/dist/client/vite-resolve.d.ts +1 -16
  231. package/dist/client/vite-resolve.js +1 -1
  232. package/dist/config/index.js +1 -1
  233. package/dist/core/index.d.ts +1 -1
  234. package/dist/index.js +1 -1
  235. package/dist/ioc/index.d.ts +1 -1
  236. package/dist/server/index.d.ts +1 -1
  237. package/dist/server/index.js +1 -1
  238. package/dist/shared/datetime-picker.js +1 -1
  239. package/dist/shared/decimal.js +1 -1
  240. package/dist/shared/excel.js +1 -1
  241. package/dist/shared/index.js +1 -1
  242. package/dist/shared/locale-json.js +1 -1
  243. package/dist/shared/tree.js +1 -1
  244. package/package.json +1 -1
  245. package/typings/admin/base/auth.ts +24 -24
  246. package/typings/admin/base/permission.ts +19 -19
  247. package/typings/admin/comm/cascader.ts +7 -7
  248. package/typings/admin/comm/check-tree.ts +7 -7
  249. package/typings/admin/comm/crud.ts +427 -427
  250. package/typings/admin/comm/eps.ts +86 -86
  251. package/typings/admin/comm/request.ts +16 -16
  252. package/typings/admin/comm/service.ts +43 -43
  253. package/typings/admin/comm/upload.ts +39 -39
  254. package/typings/admin/theme.ts +10 -10
  255. package/typings/admin/vome/browser.ts +11 -11
  256. package/typings/admin/vome/module.ts +13 -13
  257. package/typings/admin/vome/tags.ts +9 -9
  258. package/typings/comm/crud.ts +83 -83
  259. package/typings/comm/page.ts +18 -18
  260. package/typings/comm/scan.ts +6 -6
  261. package/typings/common.ts +4 -4
  262. package/typings/context/data.ts +10 -10
  263. package/typings/ext/market-seat.ts +42 -42
  264. package/typings/ext/market.ts +55 -55
  265. package/typings/ext/module.ts +64 -64
  266. package/typings/ext/plugin.ts +38 -38
  267. package/typings/ioc/metadata.ts +34 -34
  268. package/typings/logger/config.ts +6 -6
  269. package/typings/routing/crud.ts +199 -199
  270. package/typings/routing/eps.ts +88 -88
  271. package/typings/routing/meta.ts +63 -63
  272. package/typings/routing/register.ts +21 -21
  273. package/typings/routing/scan-context.ts +7 -7
  274. package/dist/admin/api/client.d.ts +0 -50
  275. package/dist/admin/components/ui/avatar/index.d.ts +0 -3
  276. package/dist/admin/components/ui/badge/index.d.ts +0 -6
  277. package/dist/admin/components/ui/button/index.d.ts +0 -7
  278. package/dist/admin/components/ui/checkbox/index.d.ts +0 -1
  279. package/dist/admin/components/ui/dialog/index.d.ts +0 -10
  280. package/dist/admin/components/ui/dropdown-menu/index.d.ts +0 -15
  281. package/dist/admin/components/ui/input/index.d.ts +0 -1
  282. package/dist/admin/components/ui/label/index.d.ts +0 -1
  283. package/dist/admin/components/ui/scroll-area/index.d.ts +0 -2
  284. package/dist/admin/components/ui/select/index.d.ts +0 -11
  285. package/dist/admin/components/ui/separator/index.d.ts +0 -1
  286. package/dist/admin/components/ui/sheet/index.d.ts +0 -8
  287. package/dist/admin/components/ui/sonner/index.d.ts +0 -1
  288. package/dist/admin/components/ui/table/index.d.ts +0 -9
  289. package/dist/admin/components/ui/tabs/index.d.ts +0 -4
  290. package/dist/admin/components/ui/tooltip/index.d.ts +0 -4
  291. package/dist/admin/crud/auto-fields.d.ts +0 -68
  292. package/dist/admin/crud/config.d.ts +0 -5
  293. package/dist/admin/crud/confirm.d.ts +0 -19
  294. package/dist/admin/crud/crud-page-request.d.ts +0 -21
  295. package/dist/admin/crud/data-i18n.d.ts +0 -62
  296. package/dist/admin/crud/dict.d.ts +0 -4
  297. package/dist/admin/crud/export-fetch.d.ts +0 -22
  298. package/dist/admin/crud/index.d.ts +0 -60
  299. package/dist/admin/crud/json-kv.d.ts +0 -9
  300. package/dist/admin/crud/key.d.ts +0 -22
  301. package/dist/admin/crud/paging.d.ts +0 -2
  302. package/dist/admin/crud/plugins.d.ts +0 -63
  303. package/dist/admin/crud/registry.d.ts +0 -3
  304. package/dist/admin/crud/span.d.ts +0 -17
  305. package/dist/admin/crud/style.d.ts +0 -82
  306. package/dist/admin/crud/useCrud.d.ts +0 -21
  307. package/dist/admin/crud/validate.d.ts +0 -7
  308. package/dist/admin/directives/perm.d.ts +0 -3
  309. package/dist/admin/hooks/useBrowser.d.ts +0 -6
  310. package/dist/admin/hooks/useUpload.d.ts +0 -8
  311. package/dist/admin/hooks/useVome.d.ts +0 -10
  312. package/dist/admin/lib/browser.d.ts +0 -3
  313. package/dist/admin/lib/cn.d.ts +0 -2
  314. package/dist/admin/lib/compress-image.d.ts +0 -12
  315. package/dist/admin/lib/dialog-float.d.ts +0 -8
  316. package/dist/admin/lib/eps.d.ts +0 -2
  317. package/dist/admin/lib/export-excel.d.ts +0 -9
  318. package/dist/admin/lib/file-preview.d.ts +0 -9
  319. package/dist/admin/lib/import-excel.d.ts +0 -21
  320. package/dist/admin/lib/json.d.ts +0 -11
  321. package/dist/admin/lib/menu.d.ts +0 -3
  322. package/dist/admin/lib/micro-locale.d.ts +0 -5
  323. package/dist/admin/lib/micro-theme.d.ts +0 -14
  324. package/dist/admin/lib/module.d.ts +0 -21
  325. package/dist/admin/lib/scarce-assets.d.ts +0 -5
  326. package/dist/admin/lib/shell.d.ts +0 -8
  327. package/dist/admin/lib/theme-color-options.d.ts +0 -15
  328. package/dist/admin/lib/tree.d.ts +0 -2
  329. package/dist/admin/lib/upload.d.ts +0 -17
  330. package/dist/admin/lib/video-frame.d.ts +0 -6
  331. package/dist/admin/router/create.d.ts +0 -16
  332. package/dist/admin/router/guards.d.ts +0 -6
  333. package/dist/admin/router/menu-routes.d.ts +0 -13
  334. package/dist/admin/stores/app.d.ts +0 -13
  335. package/dist/admin/stores/tags.d.ts +0 -14
  336. package/dist/admin/stores/user.d.ts +0 -48
  337. package/dist/client/ai-model-registry.d.ts +0 -16
  338. package/dist/client/base.d.ts +0 -28
  339. package/dist/client/configure.d.ts +0 -33
  340. package/dist/client/dict.d.ts +0 -31
  341. package/dist/client/eps-payload.d.ts +0 -3
  342. package/dist/client/eps.d.ts +0 -17
  343. package/dist/client/locale-change.d.ts +0 -16
  344. package/dist/client/locale-store.d.ts +0 -105
  345. package/dist/client/service.d.ts +0 -29
@@ -1,560 +1,560 @@
1
- <template>
2
- <div ref="rootRef" class="vm-group-cascader">
3
- <div
4
- ref="triggerRef"
5
- class="vm-group-cascader__trigger"
6
- :class="{ 'is-open': open, 'is-disabled': disabled }"
7
- @click="onTriggerClick"
8
- >
9
- <input
10
- v-if="editable"
11
- class="vm-group-cascader__input"
12
- :value="displayText"
13
- :placeholder="placeholder ?? crudLabel('pleaseSelect')"
14
- :disabled="disabled"
15
- @focus="openPanel"
16
- @input="onInput"
17
- @click.stop="openPanel"
18
- />
19
- <button
20
- v-else
21
- type="button"
22
- class="vm-group-cascader__btn"
23
- :disabled="disabled"
24
- @click.stop="toggle"
25
- >
26
- <span v-if="multiple && selectedList.length" class="vm-group-cascader__tags">
27
- <span class="vm-group-cascader__tag">{{ selectedList[0] }}</span>
28
- <span v-if="selectedList.length > 1" class="vm-group-cascader__more">
29
- + {{ selectedList.length - 1 }}
30
- </span>
31
- </span>
32
- <span v-else-if="displayText" class="vm-group-cascader__value">{{ displayText }}</span>
33
- <span v-else class="vm-group-cascader__ph">{{ placeholder ?? crudLabel('pleaseSelect') }}</span>
34
- </button>
35
- <i
36
- v-if="clearable && hasValue"
37
- class="ri-close-circle-fill vm-group-cascader__clear"
38
- @click.stop="clear"
39
- />
40
- <i class="ri-arrow-down-s-line vm-group-cascader__caret" @click.stop="toggle" />
41
- </div>
42
-
43
- <Teleport to="body">
44
- <div
45
- v-if="open"
46
- ref="panelRef"
47
- class="vm-group-cascader__panel vm-dialog-float"
48
- :style="panelStyle"
49
- >
50
- <div
51
- v-for="(col, colIdx) in columns"
52
- :key="colIdx"
53
- class="vm-group-cascader__col"
54
- :class="{ 'is-leaf': colIdx === columns.length - 1 && col.every((n) => n.isLeaf) }"
55
- >
56
- <button
57
- v-for="node in col"
58
- :key="node.value"
59
- type="button"
60
- class="vm-group-cascader__item"
61
- :class="{
62
- 'is-active': path[colIdx] === node.value || isSelected(node),
63
- 'is-leaf': node.isLeaf,
64
- }"
65
- @click="onPick(node, colIdx)"
66
- >
67
- <label
68
- v-if="multiple && node.isLeaf"
69
- class="vm-group-cascader__check"
70
- @click.stop
71
- >
72
- <input
73
- type="checkbox"
74
- :checked="isSelected(node)"
75
- @change="toggleLeaf(node)"
76
- />
77
- </label>
78
- <span>{{ node.label }}</span>
79
- <i v-if="!node.isLeaf && node.children?.length" class="ri-arrow-right-s-line" />
80
- <i
81
- v-else-if="!multiple && (node.isLeaf || selectAnyLevel) && isSelected(node)"
82
- class="ri-check-line vm-group-cascader__tick"
83
- />
84
- </button>
85
- <p v-if="!col.length" class="vm-group-cascader__empty">{{ crudLabel('empty') }}</p>
86
- </div>
87
- </div>
88
- </Teleport>
89
- </div>
90
- </template>
91
-
92
- <script setup lang="ts">
93
- import { crudLabel } from '../crud/style'
94
- import { ref, computed, watch, nextTick, onMounted, onUnmounted } from 'vue'
95
- import { POPUP_TRIGGER_GAP } from '../lib/dialog-float'
96
- import type { GroupCascaderNode } from '../../../typings/admin/comm/cascader'
97
- defineOptions({ name: 'vm-group-cascader' })
98
-
99
- const props = withDefaults(
100
- defineProps<{
101
- modelValue?: string | string[] | null
102
- tree?: GroupCascaderNode[]
103
- multiple?: boolean
104
- clearable?: boolean
105
- editable?: boolean
106
- disabled?: boolean
107
- placeholder?: string
108
- /** 单选时允许选中非叶子节点(如部门父级) */
109
- selectAnyLevel?: boolean
110
- /** 多选时分隔符 */
111
- separator?: string
112
- }>(),
113
- {
114
- tree: () => [],
115
- multiple: false,
116
- clearable: true,
117
- editable: true,
118
- placeholder: undefined,
119
- separator: ',',
120
- },
121
- )
122
-
123
- const emit = defineEmits<{
124
- 'update:modelValue': [v: string]
125
- }>()
126
-
127
- const rootRef = ref<HTMLElement | null>(null)
128
- const triggerRef = ref<HTMLElement | null>(null)
129
- const panelRef = ref<HTMLElement | null>(null)
130
- const open = ref(false)
131
- const panelStyle = ref<Record<string, string>>({})
132
- /** 当前展开路径上每一列选中的非叶子 value */
133
- const path = ref<string[]>([])
134
-
135
- const selectedSet = computed(() => {
136
- if (props.multiple) {
137
- const raw = Array.isArray(props.modelValue)
138
- ? props.modelValue
139
- : String(props.modelValue ?? '')
140
- .split(props.separator)
141
- .map((s) => s.trim())
142
- .filter(Boolean)
143
- return new Set(raw.map(String))
144
- }
145
- const v = props.modelValue
146
- if (v == null || v === '' || Array.isArray(v)) return new Set<string>()
147
- return new Set([String(v)])
148
- })
149
-
150
- const hasValue = computed(() => selectedSet.value.size > 0)
151
-
152
- const selectedList = computed(() => [...selectedSet.value])
153
-
154
- const displayText = computed(() => {
155
- if (props.multiple) {
156
- const arr = selectedList.value
157
- if (!arr.length) return ''
158
- return arr.length === 1 ? arr[0]! : `${arr[0]} +${arr.length - 1}`
159
- }
160
- return props.modelValue == null ? '' : String(props.modelValue)
161
- })
162
-
163
- /** 找叶子到根的祖先 value 链(不含叶子自身) */
164
- function findAncestorPath(
165
- nodes: GroupCascaderNode[],
166
- target: string,
167
- trail: string[] = [],
168
- ): string[] | null {
169
- for (const n of nodes) {
170
- if (n.value === target) return trail
171
- if (n.children?.length) {
172
- const hit = findAncestorPath(n.children, target, [...trail, n.value])
173
- if (hit) return hit
174
- }
175
- }
176
- return null
177
- }
178
-
179
- const columns = computed(() => {
180
- const cols: GroupCascaderNode[][] = [props.tree || []]
181
- let level = props.tree || []
182
- for (const key of path.value) {
183
- const node = level.find((n) => n.value === key)
184
- if (!node?.children?.length) break
185
- cols.push(node.children)
186
- level = node.children
187
- }
188
- return cols
189
- })
190
-
191
- function isSelected(node: GroupCascaderNode) {
192
- return selectedSet.value.has(node.value)
193
- }
194
-
195
- function emitSingle(v: string) {
196
- emit('update:modelValue', v)
197
- }
198
-
199
- function emitMulti(next: Set<string>) {
200
- emit('update:modelValue', [...next].join(props.separator))
201
- }
202
-
203
- function onPick(node: GroupCascaderNode, colIdx: number) {
204
- if (!node.isLeaf && node.children?.length) {
205
- path.value = [...path.value.slice(0, colIdx), node.value]
206
- if (!props.selectAnyLevel || props.multiple) return
207
- emitSingle(node.value)
208
- open.value = false
209
- return
210
- }
211
- if (node.isLeaf) {
212
- if (props.multiple) {
213
- toggleLeaf(node)
214
- } else {
215
- emitSingle(node.value)
216
- open.value = false
217
- }
218
- }
219
- }
220
-
221
- function toggleLeaf(node: GroupCascaderNode) {
222
- const next = new Set(selectedSet.value)
223
- if (next.has(node.value)) next.delete(node.value)
224
- else next.add(node.value)
225
- emitMulti(next)
226
- }
227
-
228
- function onInput(e: Event) {
229
- const v = (e.target as HTMLInputElement).value
230
- emitSingle(v)
231
- }
232
-
233
- function syncPanelPos() {
234
- const el = triggerRef.value
235
- if (!el) return
236
- const r = el.getBoundingClientRect()
237
- const maxW = Math.min(820, window.innerWidth - 16)
238
- // 贴触发框左侧 + 底部
239
- panelStyle.value = {
240
- position: 'fixed',
241
- top: `${r.bottom + POPUP_TRIGGER_GAP}px`,
242
- left: `${r.left}px`,
243
- zIndex: '200',
244
- maxWidth: `${maxW}px`,
245
- }
246
- }
247
-
248
- async function openPanel() {
249
- if (props.disabled) return
250
- open.value = true
251
- await nextTick()
252
- syncPanelPos()
253
- }
254
-
255
- async function toggle() {
256
- if (props.disabled) return
257
- open.value = !open.value
258
- if (open.value) {
259
- await nextTick()
260
- syncPanelPos()
261
- }
262
- }
263
-
264
- /** 点触发框空白处也展开(editable 时 caret 旁) */
265
- function onTriggerClick(e: MouseEvent) {
266
- if (props.disabled) return
267
- const t = e.target as HTMLElement | null
268
- if (t?.closest('.vm-group-cascader__clear')) return
269
- if (props.editable) openPanel()
270
- }
271
-
272
- function clear() {
273
- emit('update:modelValue', '')
274
- path.value = []
275
- }
276
-
277
- function syncPathFromValue() {
278
- if (props.multiple) return
279
- const v = props.modelValue
280
- if (v == null || v === '' || Array.isArray(v)) {
281
- path.value = []
282
- return
283
- }
284
- const target = String(v)
285
- const found = findAncestorPath(props.tree || [], target)
286
- if (found) {
287
- path.value = found
288
- return
289
- }
290
- const parts = target.replace(/^modules\//, '').split('/')
291
- const keys: string[] = []
292
- let acc = 'modules'
293
- let level = props.tree || []
294
- for (let i = 0; i < parts.length - 1; i++) {
295
- acc += `/${parts[i]}`
296
- const node = level.find((n) => n.value === acc || n.label === parts[i])
297
- if (!node || node.isLeaf) break
298
- keys.push(node.value)
299
- level = node.children || []
300
- }
301
- path.value = keys
302
- }
303
-
304
- watch(
305
- () => [props.modelValue, props.tree],
306
- () => syncPathFromValue(),
307
- { immediate: true, deep: true },
308
- )
309
-
310
- watch(open, (v) => {
311
- if (v) syncPathFromValue()
312
- })
313
-
314
- watch(columns, async () => {
315
- if (!open.value) return
316
- await nextTick()
317
- syncPanelPos()
318
- })
319
-
320
- function isInside(el: Node | null) {
321
- if (!el) return false
322
- if (rootRef.value?.contains(el)) return true
323
- if (panelRef.value?.contains(el)) return true
324
- return el instanceof Element && Boolean(el.closest('.vm-group-cascader__panel'))
325
- }
326
-
327
- function onDocPointer(e: Event) {
328
- const t = e.target as Node | null
329
- if (!isInside(t)) open.value = false
330
- }
331
-
332
- function onWinChange() {
333
- if (open.value) syncPanelPos()
334
- }
335
-
336
- onMounted(() => {
337
- document.addEventListener('pointerdown', onDocPointer, true)
338
- window.addEventListener('resize', onWinChange)
339
- window.addEventListener('scroll', onWinChange, true)
340
- })
341
- onUnmounted(() => {
342
- document.removeEventListener('pointerdown', onDocPointer, true)
343
- window.removeEventListener('resize', onWinChange)
344
- window.removeEventListener('scroll', onWinChange, true)
345
- })
346
-
347
- defineExpose({ open: openPanel, close: () => (open.value = false) })
348
- </script>
349
-
350
- <style lang="scss">
351
- .vm-group-cascader {
352
- position: relative;
353
- width: 100%;
354
- height: auto !important;
355
- min-height: var(--vm-control-height);
356
- }
357
-
358
- .vm-group-cascader__trigger {
359
- display: flex;
360
- width: 100%;
361
- height: var(--vm-control-height);
362
- min-height: var(--vm-control-height);
363
- align-items: center;
364
- gap: 6px;
365
- padding: 0 var(--vm-control-padding-x) 0 12px;
366
- border: 1px solid var(--input);
367
- border-radius: var(--vm-control-radius);
368
- background: var(--background);
369
- transition: border-color 0.15s ease;
370
- cursor: pointer;
371
- outline: none;
372
- /* 弹层触发:不展示打开/聚焦描边 */
373
-
374
- &.is-disabled {
375
- opacity: 0.55;
376
- cursor: not-allowed;
377
- }
378
- }
379
-
380
- .vm-group-cascader__input {
381
- flex: 1;
382
- min-width: 0;
383
- height: 100%;
384
- border: none !important;
385
- background: transparent !important;
386
- box-shadow: none !important;
387
- outline: none;
388
- color: var(--foreground);
389
- font-size: 14px;
390
- font-weight: 500;
391
- cursor: text;
392
-
393
- &::placeholder {
394
- color: var(--muted-foreground);
395
- }
396
- }
397
-
398
- .vm-group-cascader__btn {
399
- display: flex;
400
- flex: 1;
401
- min-width: 0;
402
- height: 100%;
403
- align-items: center;
404
- border: none;
405
- background: transparent;
406
- color: var(--foreground);
407
- font-size: 14px;
408
- font-weight: 500;
409
- text-align: left;
410
- cursor: pointer;
411
- }
412
-
413
- .vm-group-cascader__value {
414
- overflow: hidden;
415
- text-overflow: ellipsis;
416
- white-space: nowrap;
417
- }
418
-
419
- .vm-group-cascader__tags {
420
- display: flex;
421
- flex: 1;
422
- min-width: 0;
423
- flex-wrap: wrap;
424
- align-items: center;
425
- gap: 4px;
426
- }
427
-
428
- .vm-group-cascader__tag {
429
- max-width: 100%;
430
- overflow: hidden;
431
- padding: 2px 8px;
432
- border-radius: 8px;
433
- background: var(--brand-soft);
434
- color: var(--brand);
435
- font-size: 12px;
436
- font-weight: 600;
437
- text-overflow: ellipsis;
438
- white-space: nowrap;
439
- }
440
-
441
- .vm-group-cascader__more {
442
- flex-shrink: 0;
443
- padding: 2px 8px;
444
- border-radius: 8px;
445
- background: var(--muted);
446
- color: var(--muted-foreground);
447
- font-size: 12px;
448
- font-weight: 600;
449
- }
450
-
451
- .vm-group-cascader__ph {
452
- color: var(--muted-foreground);
453
- }
454
-
455
- .vm-group-cascader__clear {
456
- flex-shrink: 0;
457
- color: var(--muted-foreground);
458
- font-size: 14px;
459
- cursor: pointer;
460
-
461
- &:hover {
462
- color: var(--foreground);
463
- }
464
- }
465
-
466
- .vm-group-cascader__caret {
467
- flex-shrink: 0;
468
- opacity: 0.45;
469
- font-size: 16px;
470
- cursor: pointer;
471
- }
472
-
473
- .vm-group-cascader__panel {
474
- display: flex;
475
- height: 280px;
476
- overflow-x: auto;
477
- overflow-y: hidden;
478
- border: 1px solid var(--border);
479
- border-radius: 14px;
480
- background: var(--card);
481
- box-shadow: var(--shadow-soft);
482
- pointer-events: auto;
483
- }
484
-
485
- .vm-group-cascader__col {
486
- display: flex;
487
- width: 260px;
488
- height: 100%;
489
- flex-shrink: 0;
490
- flex-direction: column;
491
- overflow: auto;
492
- overscroll-behavior: contain;
493
- border-right: 1px solid var(--border);
494
-
495
- &.is-leaf,
496
- &:last-child {
497
- border-right: none;
498
- }
499
- }
500
-
501
- .vm-group-cascader__item {
502
- display: flex;
503
- width: 100%;
504
- flex-shrink: 0;
505
- min-height: 28px;
506
- align-items: center;
507
- justify-content: space-between;
508
- gap: 8px;
509
- padding: 5px 10px;
510
- border: none;
511
- background: transparent;
512
- color: var(--foreground);
513
- font-size: 14px;
514
- font-weight: 500;
515
- text-align: left;
516
- cursor: pointer;
517
- box-sizing: border-box;
518
-
519
- > span {
520
- flex: 1;
521
- min-width: 0;
522
- overflow: hidden;
523
- text-overflow: ellipsis;
524
- white-space: nowrap;
525
- }
526
-
527
- > i {
528
- flex-shrink: 0;
529
- opacity: 0.45;
530
- }
531
-
532
- &:hover,
533
- &.is-active {
534
- background: var(--brand-soft);
535
- color: var(--brand);
536
- }
537
-
538
- .vm-group-cascader__tick {
539
- color: var(--brand);
540
- opacity: 1;
541
- }
542
- }
543
-
544
- .vm-group-cascader__check {
545
- display: inline-flex;
546
- flex-shrink: 0;
547
-
548
- input {
549
- accent-color: var(--brand);
550
- }
551
- }
552
-
553
- .vm-group-cascader__empty {
554
- margin: 0;
555
- padding: 16px;
556
- color: var(--muted-foreground);
557
- font-size: 13px;
558
- text-align: center;
559
- }
560
- </style>
1
+ <template>
2
+ <div ref="rootRef" class="vm-group-cascader">
3
+ <div
4
+ ref="triggerRef"
5
+ class="vm-group-cascader__trigger"
6
+ :class="{ 'is-open': open, 'is-disabled': disabled }"
7
+ @click="onTriggerClick"
8
+ >
9
+ <input
10
+ v-if="editable"
11
+ class="vm-group-cascader__input"
12
+ :value="displayText"
13
+ :placeholder="placeholder ?? crudLabel('pleaseSelect')"
14
+ :disabled="disabled"
15
+ @focus="openPanel"
16
+ @input="onInput"
17
+ @click.stop="openPanel"
18
+ />
19
+ <button
20
+ v-else
21
+ type="button"
22
+ class="vm-group-cascader__btn"
23
+ :disabled="disabled"
24
+ @click.stop="toggle"
25
+ >
26
+ <span v-if="multiple && selectedList.length" class="vm-group-cascader__tags">
27
+ <span class="vm-group-cascader__tag">{{ selectedList[0] }}</span>
28
+ <span v-if="selectedList.length > 1" class="vm-group-cascader__more">
29
+ + {{ selectedList.length - 1 }}
30
+ </span>
31
+ </span>
32
+ <span v-else-if="displayText" class="vm-group-cascader__value">{{ displayText }}</span>
33
+ <span v-else class="vm-group-cascader__ph">{{ placeholder ?? crudLabel('pleaseSelect') }}</span>
34
+ </button>
35
+ <i
36
+ v-if="clearable && hasValue"
37
+ class="ri-close-circle-fill vm-group-cascader__clear"
38
+ @click.stop="clear"
39
+ />
40
+ <i class="ri-arrow-down-s-line vm-group-cascader__caret" @click.stop="toggle" />
41
+ </div>
42
+
43
+ <Teleport to="body">
44
+ <div
45
+ v-if="open"
46
+ ref="panelRef"
47
+ class="vm-group-cascader__panel vm-dialog-float"
48
+ :style="panelStyle"
49
+ >
50
+ <div
51
+ v-for="(col, colIdx) in columns"
52
+ :key="colIdx"
53
+ class="vm-group-cascader__col"
54
+ :class="{ 'is-leaf': colIdx === columns.length - 1 && col.every((n) => n.isLeaf) }"
55
+ >
56
+ <button
57
+ v-for="node in col"
58
+ :key="node.value"
59
+ type="button"
60
+ class="vm-group-cascader__item"
61
+ :class="{
62
+ 'is-active': path[colIdx] === node.value || isSelected(node),
63
+ 'is-leaf': node.isLeaf,
64
+ }"
65
+ @click="onPick(node, colIdx)"
66
+ >
67
+ <label
68
+ v-if="multiple && node.isLeaf"
69
+ class="vm-group-cascader__check"
70
+ @click.stop
71
+ >
72
+ <input
73
+ type="checkbox"
74
+ :checked="isSelected(node)"
75
+ @change="toggleLeaf(node)"
76
+ />
77
+ </label>
78
+ <span>{{ node.label }}</span>
79
+ <i v-if="!node.isLeaf && node.children?.length" class="ri-arrow-right-s-line" />
80
+ <i
81
+ v-else-if="!multiple && (node.isLeaf || selectAnyLevel) && isSelected(node)"
82
+ class="ri-check-line vm-group-cascader__tick"
83
+ />
84
+ </button>
85
+ <p v-if="!col.length" class="vm-group-cascader__empty">{{ crudLabel('empty') }}</p>
86
+ </div>
87
+ </div>
88
+ </Teleport>
89
+ </div>
90
+ </template>
91
+
92
+ <script setup lang="ts">
93
+ import { crudLabel } from '../crud/style'
94
+ import { ref, computed, watch, nextTick, onMounted, onUnmounted } from 'vue'
95
+ import { POPUP_TRIGGER_GAP } from '../lib/dialog-float'
96
+ import type { GroupCascaderNode } from '../../../typings/admin/comm/cascader'
97
+ defineOptions({ name: 'vm-group-cascader' })
98
+
99
+ const props = withDefaults(
100
+ defineProps<{
101
+ modelValue?: string | string[] | null
102
+ tree?: GroupCascaderNode[]
103
+ multiple?: boolean
104
+ clearable?: boolean
105
+ editable?: boolean
106
+ disabled?: boolean
107
+ placeholder?: string
108
+ /** 单选时允许选中非叶子节点(如部门父级) */
109
+ selectAnyLevel?: boolean
110
+ /** 多选时分隔符 */
111
+ separator?: string
112
+ }>(),
113
+ {
114
+ tree: () => [],
115
+ multiple: false,
116
+ clearable: true,
117
+ editable: true,
118
+ placeholder: undefined,
119
+ separator: ',',
120
+ },
121
+ )
122
+
123
+ const emit = defineEmits<{
124
+ 'update:modelValue': [v: string]
125
+ }>()
126
+
127
+ const rootRef = ref<HTMLElement | null>(null)
128
+ const triggerRef = ref<HTMLElement | null>(null)
129
+ const panelRef = ref<HTMLElement | null>(null)
130
+ const open = ref(false)
131
+ const panelStyle = ref<Record<string, string>>({})
132
+ /** 当前展开路径上每一列选中的非叶子 value */
133
+ const path = ref<string[]>([])
134
+
135
+ const selectedSet = computed(() => {
136
+ if (props.multiple) {
137
+ const raw = Array.isArray(props.modelValue)
138
+ ? props.modelValue
139
+ : String(props.modelValue ?? '')
140
+ .split(props.separator)
141
+ .map((s) => s.trim())
142
+ .filter(Boolean)
143
+ return new Set(raw.map(String))
144
+ }
145
+ const v = props.modelValue
146
+ if (v == null || v === '' || Array.isArray(v)) return new Set<string>()
147
+ return new Set([String(v)])
148
+ })
149
+
150
+ const hasValue = computed(() => selectedSet.value.size > 0)
151
+
152
+ const selectedList = computed(() => [...selectedSet.value])
153
+
154
+ const displayText = computed(() => {
155
+ if (props.multiple) {
156
+ const arr = selectedList.value
157
+ if (!arr.length) return ''
158
+ return arr.length === 1 ? arr[0]! : `${arr[0]} +${arr.length - 1}`
159
+ }
160
+ return props.modelValue == null ? '' : String(props.modelValue)
161
+ })
162
+
163
+ /** 找叶子到根的祖先 value 链(不含叶子自身) */
164
+ function findAncestorPath(
165
+ nodes: GroupCascaderNode[],
166
+ target: string,
167
+ trail: string[] = [],
168
+ ): string[] | null {
169
+ for (const n of nodes) {
170
+ if (n.value === target) return trail
171
+ if (n.children?.length) {
172
+ const hit = findAncestorPath(n.children, target, [...trail, n.value])
173
+ if (hit) return hit
174
+ }
175
+ }
176
+ return null
177
+ }
178
+
179
+ const columns = computed(() => {
180
+ const cols: GroupCascaderNode[][] = [props.tree || []]
181
+ let level = props.tree || []
182
+ for (const key of path.value) {
183
+ const node = level.find((n) => n.value === key)
184
+ if (!node?.children?.length) break
185
+ cols.push(node.children)
186
+ level = node.children
187
+ }
188
+ return cols
189
+ })
190
+
191
+ function isSelected(node: GroupCascaderNode) {
192
+ return selectedSet.value.has(node.value)
193
+ }
194
+
195
+ function emitSingle(v: string) {
196
+ emit('update:modelValue', v)
197
+ }
198
+
199
+ function emitMulti(next: Set<string>) {
200
+ emit('update:modelValue', [...next].join(props.separator))
201
+ }
202
+
203
+ function onPick(node: GroupCascaderNode, colIdx: number) {
204
+ if (!node.isLeaf && node.children?.length) {
205
+ path.value = [...path.value.slice(0, colIdx), node.value]
206
+ if (!props.selectAnyLevel || props.multiple) return
207
+ emitSingle(node.value)
208
+ open.value = false
209
+ return
210
+ }
211
+ if (node.isLeaf) {
212
+ if (props.multiple) {
213
+ toggleLeaf(node)
214
+ } else {
215
+ emitSingle(node.value)
216
+ open.value = false
217
+ }
218
+ }
219
+ }
220
+
221
+ function toggleLeaf(node: GroupCascaderNode) {
222
+ const next = new Set(selectedSet.value)
223
+ if (next.has(node.value)) next.delete(node.value)
224
+ else next.add(node.value)
225
+ emitMulti(next)
226
+ }
227
+
228
+ function onInput(e: Event) {
229
+ const v = (e.target as HTMLInputElement).value
230
+ emitSingle(v)
231
+ }
232
+
233
+ function syncPanelPos() {
234
+ const el = triggerRef.value
235
+ if (!el) return
236
+ const r = el.getBoundingClientRect()
237
+ const maxW = Math.min(820, window.innerWidth - 16)
238
+ // 贴触发框左侧 + 底部
239
+ panelStyle.value = {
240
+ position: 'fixed',
241
+ top: `${r.bottom + POPUP_TRIGGER_GAP}px`,
242
+ left: `${r.left}px`,
243
+ zIndex: '200',
244
+ maxWidth: `${maxW}px`,
245
+ }
246
+ }
247
+
248
+ async function openPanel() {
249
+ if (props.disabled) return
250
+ open.value = true
251
+ await nextTick()
252
+ syncPanelPos()
253
+ }
254
+
255
+ async function toggle() {
256
+ if (props.disabled) return
257
+ open.value = !open.value
258
+ if (open.value) {
259
+ await nextTick()
260
+ syncPanelPos()
261
+ }
262
+ }
263
+
264
+ /** 点触发框空白处也展开(editable 时 caret 旁) */
265
+ function onTriggerClick(e: MouseEvent) {
266
+ if (props.disabled) return
267
+ const t = e.target as HTMLElement | null
268
+ if (t?.closest('.vm-group-cascader__clear')) return
269
+ if (props.editable) openPanel()
270
+ }
271
+
272
+ function clear() {
273
+ emit('update:modelValue', '')
274
+ path.value = []
275
+ }
276
+
277
+ function syncPathFromValue() {
278
+ if (props.multiple) return
279
+ const v = props.modelValue
280
+ if (v == null || v === '' || Array.isArray(v)) {
281
+ path.value = []
282
+ return
283
+ }
284
+ const target = String(v)
285
+ const found = findAncestorPath(props.tree || [], target)
286
+ if (found) {
287
+ path.value = found
288
+ return
289
+ }
290
+ const parts = target.replace(/^modules\//, '').split('/')
291
+ const keys: string[] = []
292
+ let acc = 'modules'
293
+ let level = props.tree || []
294
+ for (let i = 0; i < parts.length - 1; i++) {
295
+ acc += `/${parts[i]}`
296
+ const node = level.find((n) => n.value === acc || n.label === parts[i])
297
+ if (!node || node.isLeaf) break
298
+ keys.push(node.value)
299
+ level = node.children || []
300
+ }
301
+ path.value = keys
302
+ }
303
+
304
+ watch(
305
+ () => [props.modelValue, props.tree],
306
+ () => syncPathFromValue(),
307
+ { immediate: true, deep: true },
308
+ )
309
+
310
+ watch(open, (v) => {
311
+ if (v) syncPathFromValue()
312
+ })
313
+
314
+ watch(columns, async () => {
315
+ if (!open.value) return
316
+ await nextTick()
317
+ syncPanelPos()
318
+ })
319
+
320
+ function isInside(el: Node | null) {
321
+ if (!el) return false
322
+ if (rootRef.value?.contains(el)) return true
323
+ if (panelRef.value?.contains(el)) return true
324
+ return el instanceof Element && Boolean(el.closest('.vm-group-cascader__panel'))
325
+ }
326
+
327
+ function onDocPointer(e: Event) {
328
+ const t = e.target as Node | null
329
+ if (!isInside(t)) open.value = false
330
+ }
331
+
332
+ function onWinChange() {
333
+ if (open.value) syncPanelPos()
334
+ }
335
+
336
+ onMounted(() => {
337
+ document.addEventListener('pointerdown', onDocPointer, true)
338
+ window.addEventListener('resize', onWinChange)
339
+ window.addEventListener('scroll', onWinChange, true)
340
+ })
341
+ onUnmounted(() => {
342
+ document.removeEventListener('pointerdown', onDocPointer, true)
343
+ window.removeEventListener('resize', onWinChange)
344
+ window.removeEventListener('scroll', onWinChange, true)
345
+ })
346
+
347
+ defineExpose({ open: openPanel, close: () => (open.value = false) })
348
+ </script>
349
+
350
+ <style lang="scss">
351
+ .vm-group-cascader {
352
+ position: relative;
353
+ width: 100%;
354
+ height: auto !important;
355
+ min-height: var(--vm-control-height);
356
+ }
357
+
358
+ .vm-group-cascader__trigger {
359
+ display: flex;
360
+ width: 100%;
361
+ height: var(--vm-control-height);
362
+ min-height: var(--vm-control-height);
363
+ align-items: center;
364
+ gap: 6px;
365
+ padding: 0 var(--vm-control-padding-x) 0 12px;
366
+ border: 1px solid var(--input);
367
+ border-radius: var(--vm-control-radius);
368
+ background: var(--background);
369
+ transition: border-color 0.15s ease;
370
+ cursor: pointer;
371
+ outline: none;
372
+ /* 弹层触发:不展示打开/聚焦描边 */
373
+
374
+ &.is-disabled {
375
+ opacity: 0.55;
376
+ cursor: not-allowed;
377
+ }
378
+ }
379
+
380
+ .vm-group-cascader__input {
381
+ flex: 1;
382
+ min-width: 0;
383
+ height: 100%;
384
+ border: none !important;
385
+ background: transparent !important;
386
+ box-shadow: none !important;
387
+ outline: none;
388
+ color: var(--foreground);
389
+ font-size: 14px;
390
+ font-weight: 500;
391
+ cursor: text;
392
+
393
+ &::placeholder {
394
+ color: var(--muted-foreground);
395
+ }
396
+ }
397
+
398
+ .vm-group-cascader__btn {
399
+ display: flex;
400
+ flex: 1;
401
+ min-width: 0;
402
+ height: 100%;
403
+ align-items: center;
404
+ border: none;
405
+ background: transparent;
406
+ color: var(--foreground);
407
+ font-size: 14px;
408
+ font-weight: 500;
409
+ text-align: left;
410
+ cursor: pointer;
411
+ }
412
+
413
+ .vm-group-cascader__value {
414
+ overflow: hidden;
415
+ text-overflow: ellipsis;
416
+ white-space: nowrap;
417
+ }
418
+
419
+ .vm-group-cascader__tags {
420
+ display: flex;
421
+ flex: 1;
422
+ min-width: 0;
423
+ flex-wrap: wrap;
424
+ align-items: center;
425
+ gap: 4px;
426
+ }
427
+
428
+ .vm-group-cascader__tag {
429
+ max-width: 100%;
430
+ overflow: hidden;
431
+ padding: 2px 8px;
432
+ border-radius: 8px;
433
+ background: var(--brand-soft);
434
+ color: var(--brand);
435
+ font-size: 12px;
436
+ font-weight: 600;
437
+ text-overflow: ellipsis;
438
+ white-space: nowrap;
439
+ }
440
+
441
+ .vm-group-cascader__more {
442
+ flex-shrink: 0;
443
+ padding: 2px 8px;
444
+ border-radius: 8px;
445
+ background: var(--muted);
446
+ color: var(--muted-foreground);
447
+ font-size: 12px;
448
+ font-weight: 600;
449
+ }
450
+
451
+ .vm-group-cascader__ph {
452
+ color: var(--muted-foreground);
453
+ }
454
+
455
+ .vm-group-cascader__clear {
456
+ flex-shrink: 0;
457
+ color: var(--muted-foreground);
458
+ font-size: 14px;
459
+ cursor: pointer;
460
+
461
+ &:hover {
462
+ color: var(--foreground);
463
+ }
464
+ }
465
+
466
+ .vm-group-cascader__caret {
467
+ flex-shrink: 0;
468
+ opacity: 0.45;
469
+ font-size: 16px;
470
+ cursor: pointer;
471
+ }
472
+
473
+ .vm-group-cascader__panel {
474
+ display: flex;
475
+ height: 280px;
476
+ overflow-x: auto;
477
+ overflow-y: hidden;
478
+ border: 1px solid var(--border);
479
+ border-radius: 14px;
480
+ background: var(--card);
481
+ box-shadow: var(--shadow-soft);
482
+ pointer-events: auto;
483
+ }
484
+
485
+ .vm-group-cascader__col {
486
+ display: flex;
487
+ width: 260px;
488
+ height: 100%;
489
+ flex-shrink: 0;
490
+ flex-direction: column;
491
+ overflow: auto;
492
+ overscroll-behavior: contain;
493
+ border-right: 1px solid var(--border);
494
+
495
+ &.is-leaf,
496
+ &:last-child {
497
+ border-right: none;
498
+ }
499
+ }
500
+
501
+ .vm-group-cascader__item {
502
+ display: flex;
503
+ width: 100%;
504
+ flex-shrink: 0;
505
+ min-height: 28px;
506
+ align-items: center;
507
+ justify-content: space-between;
508
+ gap: 8px;
509
+ padding: 5px 10px;
510
+ border: none;
511
+ background: transparent;
512
+ color: var(--foreground);
513
+ font-size: 14px;
514
+ font-weight: 500;
515
+ text-align: left;
516
+ cursor: pointer;
517
+ box-sizing: border-box;
518
+
519
+ > span {
520
+ flex: 1;
521
+ min-width: 0;
522
+ overflow: hidden;
523
+ text-overflow: ellipsis;
524
+ white-space: nowrap;
525
+ }
526
+
527
+ > i {
528
+ flex-shrink: 0;
529
+ opacity: 0.45;
530
+ }
531
+
532
+ &:hover,
533
+ &.is-active {
534
+ background: var(--brand-soft);
535
+ color: var(--brand);
536
+ }
537
+
538
+ .vm-group-cascader__tick {
539
+ color: var(--brand);
540
+ opacity: 1;
541
+ }
542
+ }
543
+
544
+ .vm-group-cascader__check {
545
+ display: inline-flex;
546
+ flex-shrink: 0;
547
+
548
+ input {
549
+ accent-color: var(--brand);
550
+ }
551
+ }
552
+
553
+ .vm-group-cascader__empty {
554
+ margin: 0;
555
+ padding: 16px;
556
+ color: var(--muted-foreground);
557
+ font-size: 13px;
558
+ text-align: center;
559
+ }
560
+ </style>