vome-core 0.3.65 → 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 (342) 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/index.js +1 -1
  234. package/dist/server/index.js +1 -1
  235. package/dist/shared/datetime-picker.js +1 -1
  236. package/dist/shared/decimal.js +1 -1
  237. package/dist/shared/excel.js +1 -1
  238. package/dist/shared/index.js +1 -1
  239. package/dist/shared/locale-json.js +1 -1
  240. package/dist/shared/tree.js +1 -1
  241. package/package.json +1 -1
  242. package/typings/admin/base/auth.ts +24 -24
  243. package/typings/admin/base/permission.ts +19 -19
  244. package/typings/admin/comm/cascader.ts +7 -7
  245. package/typings/admin/comm/check-tree.ts +7 -7
  246. package/typings/admin/comm/crud.ts +427 -427
  247. package/typings/admin/comm/eps.ts +86 -86
  248. package/typings/admin/comm/request.ts +16 -16
  249. package/typings/admin/comm/service.ts +43 -43
  250. package/typings/admin/comm/upload.ts +39 -39
  251. package/typings/admin/theme.ts +10 -10
  252. package/typings/admin/vome/browser.ts +11 -11
  253. package/typings/admin/vome/module.ts +13 -13
  254. package/typings/admin/vome/tags.ts +9 -9
  255. package/typings/comm/crud.ts +83 -83
  256. package/typings/comm/page.ts +18 -18
  257. package/typings/comm/scan.ts +6 -6
  258. package/typings/common.ts +4 -4
  259. package/typings/context/data.ts +10 -10
  260. package/typings/ext/market-seat.ts +42 -42
  261. package/typings/ext/market.ts +55 -55
  262. package/typings/ext/module.ts +64 -64
  263. package/typings/ext/plugin.ts +38 -38
  264. package/typings/ioc/metadata.ts +34 -34
  265. package/typings/logger/config.ts +6 -6
  266. package/typings/routing/crud.ts +199 -199
  267. package/typings/routing/eps.ts +88 -88
  268. package/typings/routing/meta.ts +63 -63
  269. package/typings/routing/register.ts +21 -21
  270. package/typings/routing/scan-context.ts +7 -7
  271. package/dist/admin/api/client.d.ts +0 -50
  272. package/dist/admin/components/ui/avatar/index.d.ts +0 -3
  273. package/dist/admin/components/ui/badge/index.d.ts +0 -6
  274. package/dist/admin/components/ui/button/index.d.ts +0 -7
  275. package/dist/admin/components/ui/checkbox/index.d.ts +0 -1
  276. package/dist/admin/components/ui/dialog/index.d.ts +0 -10
  277. package/dist/admin/components/ui/dropdown-menu/index.d.ts +0 -15
  278. package/dist/admin/components/ui/input/index.d.ts +0 -1
  279. package/dist/admin/components/ui/label/index.d.ts +0 -1
  280. package/dist/admin/components/ui/scroll-area/index.d.ts +0 -2
  281. package/dist/admin/components/ui/select/index.d.ts +0 -11
  282. package/dist/admin/components/ui/separator/index.d.ts +0 -1
  283. package/dist/admin/components/ui/sheet/index.d.ts +0 -8
  284. package/dist/admin/components/ui/sonner/index.d.ts +0 -1
  285. package/dist/admin/components/ui/table/index.d.ts +0 -9
  286. package/dist/admin/components/ui/tabs/index.d.ts +0 -4
  287. package/dist/admin/components/ui/tooltip/index.d.ts +0 -4
  288. package/dist/admin/crud/auto-fields.d.ts +0 -68
  289. package/dist/admin/crud/config.d.ts +0 -5
  290. package/dist/admin/crud/confirm.d.ts +0 -19
  291. package/dist/admin/crud/crud-page-request.d.ts +0 -21
  292. package/dist/admin/crud/data-i18n.d.ts +0 -62
  293. package/dist/admin/crud/dict.d.ts +0 -4
  294. package/dist/admin/crud/export-fetch.d.ts +0 -22
  295. package/dist/admin/crud/index.d.ts +0 -60
  296. package/dist/admin/crud/json-kv.d.ts +0 -9
  297. package/dist/admin/crud/key.d.ts +0 -22
  298. package/dist/admin/crud/paging.d.ts +0 -2
  299. package/dist/admin/crud/plugins.d.ts +0 -63
  300. package/dist/admin/crud/registry.d.ts +0 -3
  301. package/dist/admin/crud/span.d.ts +0 -17
  302. package/dist/admin/crud/style.d.ts +0 -82
  303. package/dist/admin/crud/useCrud.d.ts +0 -21
  304. package/dist/admin/crud/validate.d.ts +0 -7
  305. package/dist/admin/directives/perm.d.ts +0 -3
  306. package/dist/admin/hooks/useBrowser.d.ts +0 -6
  307. package/dist/admin/hooks/useUpload.d.ts +0 -8
  308. package/dist/admin/hooks/useVome.d.ts +0 -10
  309. package/dist/admin/lib/browser.d.ts +0 -3
  310. package/dist/admin/lib/cn.d.ts +0 -2
  311. package/dist/admin/lib/compress-image.d.ts +0 -12
  312. package/dist/admin/lib/dialog-float.d.ts +0 -8
  313. package/dist/admin/lib/eps.d.ts +0 -2
  314. package/dist/admin/lib/export-excel.d.ts +0 -9
  315. package/dist/admin/lib/file-preview.d.ts +0 -9
  316. package/dist/admin/lib/import-excel.d.ts +0 -21
  317. package/dist/admin/lib/json.d.ts +0 -11
  318. package/dist/admin/lib/menu.d.ts +0 -3
  319. package/dist/admin/lib/micro-locale.d.ts +0 -5
  320. package/dist/admin/lib/micro-theme.d.ts +0 -14
  321. package/dist/admin/lib/module.d.ts +0 -21
  322. package/dist/admin/lib/scarce-assets.d.ts +0 -5
  323. package/dist/admin/lib/shell.d.ts +0 -8
  324. package/dist/admin/lib/theme-color-options.d.ts +0 -15
  325. package/dist/admin/lib/tree.d.ts +0 -2
  326. package/dist/admin/lib/upload.d.ts +0 -17
  327. package/dist/admin/lib/video-frame.d.ts +0 -6
  328. package/dist/admin/router/create.d.ts +0 -16
  329. package/dist/admin/router/guards.d.ts +0 -6
  330. package/dist/admin/router/menu-routes.d.ts +0 -13
  331. package/dist/admin/stores/app.d.ts +0 -13
  332. package/dist/admin/stores/tags.d.ts +0 -14
  333. package/dist/admin/stores/user.d.ts +0 -48
  334. package/dist/client/ai-model-registry.d.ts +0 -16
  335. package/dist/client/base.d.ts +0 -28
  336. package/dist/client/configure.d.ts +0 -33
  337. package/dist/client/dict.d.ts +0 -31
  338. package/dist/client/eps-payload.d.ts +0 -3
  339. package/dist/client/eps.d.ts +0 -17
  340. package/dist/client/locale-change.d.ts +0 -16
  341. package/dist/client/locale-store.d.ts +0 -105
  342. package/dist/client/service.d.ts +0 -29
@@ -1,497 +1,497 @@
1
- <template>
2
- <vm-aside
3
- :title="onlyTrashed ? crudLabel('deptTrash') : crudLabel('deptOrg')"
4
- :empty="emptyText"
5
- :loading="loading"
6
- :add="canAdd && !onlyTrashed"
7
- :add-title="crudLabel('deptAddRoot')"
8
- @refresh="emit('refresh')"
9
- @add="openAdd(null)"
10
- >
11
- <button
12
- type="button"
13
- class="vm-dept-tree__node is-root"
14
- :class="{ 'is-active': modelValue == null }"
15
- @click="select(null)"
16
- >
17
- <i class="ri-building-2-line" />
18
- <span>{{ rootLabel ?? crudLabel('all') }}</span>
19
- </button>
20
-
21
- <div
22
- v-for="row in visibleRows"
23
- :key="String(row.id)"
24
- class="vm-dept-tree__node"
25
- :class="{ 'is-active': modelValue === row.id, 'is-hidden': !row.visible }"
26
- :style="{ paddingLeft: `${12 + row.depth * 16}px` }"
27
- @click="select(row.id)"
28
- >
29
- <button
30
- v-if="row.hasChildren"
31
- type="button"
32
- class="vm-dept-tree__expand"
33
- :class="{ 'is-open': expanded.has(row.id) }"
34
- @click.stop="toggleExpand(row.id)"
35
- >
36
- <i class="ri-arrow-right-s-line" />
37
- </button>
38
- <span v-else class="vm-dept-tree__expand is-spacer" />
39
- <span class="vm-dept-tree__label">{{ row.label }}</span>
40
- <div v-if="onlyTrashed && canTrashOps" class="vm-dept-tree__ops" @click.stop>
41
- <button
42
- type="button"
43
- :title="crudLabel('restore')"
44
- :disabled="trashBusyId === row.id"
45
- @click="restoreDept(row)"
46
- >
47
- <i class="ri-arrow-go-back-line" />
48
- </button>
49
- <button
50
- type="button"
51
- :title="crudLabel('forceDelete')"
52
- :disabled="trashBusyId === row.id"
53
- @click="forceDeleteDept(row)"
54
- >
55
- <i class="ri-delete-bin-2-line" />
56
- </button>
57
- </div>
58
- <div v-else-if="!onlyTrashed && canManage" class="vm-dept-tree__ops" @click.stop>
59
- <button v-if="canAdd" type="button" :title="crudLabel('deptAddChild')" @click="openAdd(row.id)">
60
- <i class="ri-add-line" />
61
- </button>
62
- <button v-if="canUpdate" type="button" :title="crudLabel('update')" @click="openEdit(row)">
63
- <i class="ri-edit-line" />
64
- </button>
65
- <button v-if="canDelete" type="button" :title="crudLabel('delete')" @click="remove(row)">
66
- <i class="ri-delete-bin-line" />
67
- </button>
68
- </div>
69
- </div>
70
- </vm-aside>
71
-
72
- <vm-form-dialog
73
- v-model:open="formOpen"
74
- v-model="formData"
75
- :title="formMode === 'add' ? crudLabel('deptAddTitle') : crudLabel('deptEditTitle')"
76
- :items="dialogItems"
77
- @submit="onFormSubmit"
78
- />
79
- </template>
80
-
81
- <script setup lang="ts">
82
- import { ref, computed, watch } from 'vue'
83
- import { flattenTree } from '../lib/tree'
84
- import { vmConfirm } from '../crud/confirm'
85
- import { crudFormat, crudLabel } from '../crud/style'
86
- import { mergeByProp } from '../crud/auto-fields'
87
- import type { CrudFormItem } from '../../../typings/admin/comm/crud'
88
- import type { VmFormDialogSubmitHelpers } from './vm-form-dialog.vue'
89
-
90
- defineOptions({ name: 'vm-dept-tree' })
91
-
92
- /** 调用方须先组装好的树节点(含 children),组件不再做扁平→树转换 */
93
- export type DeptTreeNode = {
94
- id: number
95
- parentId: number | null
96
- orderNum: number
97
- name: string
98
- children?: DeptTreeNode[]
99
- }
100
-
101
- type FlatRow = {
102
- id: number
103
- label: string
104
- depth: number
105
- hasChildren: boolean
106
- visible: boolean
107
- parentId: number | null
108
- orderNum: number
109
- }
110
-
111
- export type DeptFormPayload = {
112
- name: string
113
- orderNum: number
114
- parentId: number | null
115
- id?: number
116
- } & Record<string, unknown>
117
-
118
- const props = withDefaults(
119
- defineProps<{
120
- /** 已是树形结构的部门数据 */
121
- data?: DeptTreeNode[]
122
- loading?: boolean
123
- modelValue?: number | null
124
- rootLabel?: string
125
- /** 回收站 UI:隐藏新增,显示恢复/彻底删除 */
126
- onlyTrashed?: boolean
127
- canAdd?: boolean
128
- canUpdate?: boolean
129
- canDelete?: boolean
130
- canRestore?: boolean
131
- /**
132
- * 追加/覆盖表单字段(CrudFormItem);与内置「上级/名称/排序」合并,同 prop 以传入为准。
133
- * 任意控件:`component: { name: 'vm-select', props: {…} }`
134
- */
135
- formItems?: CrudFormItem[]
136
- }>(),
137
- {
138
- data: () => [],
139
- loading: false,
140
- modelValue: null,
141
- rootLabel: undefined,
142
- onlyTrashed: false,
143
- canAdd: false,
144
- canUpdate: false,
145
- canDelete: false,
146
- canRestore: false,
147
- formItems: () => [],
148
- },
149
- )
150
-
151
- const emit = defineEmits<{
152
- 'update:modelValue': [v: number | null]
153
- change: [v: number | null, meta: { name: string; ids: number[] }]
154
- refresh: []
155
- add: [payload: DeptFormPayload]
156
- update: [payload: DeptFormPayload]
157
- delete: [payload: { id: number }]
158
- restore: [payload: { id: number }]
159
- forceDelete: [payload: { id: number }]
160
- }>()
161
-
162
- const expanded = ref(new Set<number>())
163
- const formOpen = ref(false)
164
- const formMode = ref<'add' | 'edit'>('add')
165
- const formData = ref<Record<string, unknown>>({})
166
- const trashBusyId = ref<number | null>(null)
167
-
168
- const canManage = computed(
169
- () => props.canAdd || props.canUpdate || props.canDelete,
170
- )
171
- const canTrashOps = computed(() => props.canRestore || props.canDelete)
172
-
173
- const builtinFormItems = computed((): CrudFormItem[] => [
174
- {
175
- prop: 'parentLabel',
176
- label: crudLabel('deptParent'),
177
- span: 12,
178
- disabled: true,
179
- },
180
- {
181
- prop: 'name',
182
- label: crudLabel('fieldName'),
183
- span: 12,
184
- required: true,
185
- placeholder: crudLabel('deptNamePh'),
186
- },
187
- {
188
- prop: 'orderNum',
189
- label: crudLabel('orderNum'),
190
- span: 12,
191
- type: 'number',
192
- value: 0,
193
- },
194
- ])
195
-
196
- const dialogItems = computed(() =>
197
- mergeByProp(builtinFormItems.value, props.formItems || []),
198
- )
199
-
200
- const idNameMap = computed(() => {
201
- const map = new Map<number, string>()
202
- const walk = (list: DeptTreeNode[]) => {
203
- for (const n of list) {
204
- map.set(n.id, n.name)
205
- if (n.children?.length) walk(n.children)
206
- }
207
- }
208
- walk(props.data)
209
- return map
210
- })
211
-
212
- const descendantMap = computed(() => {
213
- const map = new Map<number, number[]>()
214
- const walk = (node: DeptTreeNode): number[] => {
215
- let ids: number[] = []
216
- for (const c of node.children || []) {
217
- ids.push(c.id)
218
- ids = ids.concat(walk(c))
219
- }
220
- map.set(node.id, ids)
221
- return ids
222
- }
223
- for (const n of props.data) walk(n)
224
- return map
225
- })
226
-
227
- function collectExpandable(list: DeptTreeNode[], into: Set<number>) {
228
- for (const n of list) {
229
- if (n.children?.length) {
230
- into.add(n.id)
231
- collectExpandable(n.children, into)
232
- }
233
- }
234
- }
235
-
236
- const visibleRows = computed((): FlatRow[] => {
237
- const flat = flattenTree(props.data, expanded.value)
238
- return flat.map((n) => ({
239
- id: n.id as number,
240
- label: n.name,
241
- depth: n.depth,
242
- hasChildren: n.hasChildren,
243
- visible: true,
244
- parentId: n.parentId ?? null,
245
- orderNum: n.orderNum ?? 0,
246
- }))
247
- })
248
-
249
- const emptyText = computed(() => {
250
- if (props.loading || visibleRows.value.length) return ''
251
- return props.onlyTrashed ? crudLabel('trashEmpty') : crudLabel('deptEmpty')
252
- })
253
-
254
- function resolveFilterIds(id: number | null) {
255
- if (id == null) return []
256
- return [id, ...(descendantMap.value.get(id) || [])]
257
- }
258
-
259
- function select(id: number | null) {
260
- emit('update:modelValue', id)
261
- const name =
262
- id == null ? (props.rootLabel ?? '') : idNameMap.value.get(id) || ''
263
- emit('change', id, { name, ids: resolveFilterIds(id) })
264
- }
265
-
266
- function toggleExpand(id: number) {
267
- const next = new Set(expanded.value)
268
- if (next.has(id)) next.delete(id)
269
- else next.add(id)
270
- expanded.value = next
271
- }
272
-
273
- function syncExpandedFromData() {
274
- const next = new Set<number>()
275
- collectExpandable(props.data, next)
276
- expanded.value = next
277
- if (props.modelValue != null && !idNameMap.value.has(props.modelValue)) {
278
- select(null)
279
- }
280
- }
281
-
282
- watch(
283
- () => props.data,
284
- () => syncExpandedFromData(),
285
- { deep: true, immediate: true },
286
- )
287
-
288
- watch(
289
- () => props.onlyTrashed,
290
- () => {
291
- select(null)
292
- },
293
- )
294
-
295
- function parentLabelOf(parentId: number | null) {
296
- return parentId == null ? crudLabel('topLevel') : idNameMap.value.get(parentId) || String(parentId)
297
- }
298
-
299
- function openAdd(parentId: number | null) {
300
- formMode.value = 'add'
301
- formData.value = {
302
- id: 0,
303
- name: '',
304
- orderNum: 0,
305
- parentId,
306
- parentLabel: parentLabelOf(parentId),
307
- }
308
- formOpen.value = true
309
- }
310
-
311
- function openEdit(row: FlatRow) {
312
- formMode.value = 'edit'
313
- formData.value = {
314
- id: row.id,
315
- name: row.label,
316
- orderNum: row.orderNum ?? 0,
317
- parentId: row.parentId,
318
- parentLabel: parentLabelOf(row.parentId),
319
- }
320
- formOpen.value = true
321
- }
322
-
323
- function onFormSubmit(data: Record<string, unknown>, helpers: VmFormDialogSubmitHelpers) {
324
- const { parentLabel: _pl, ...rest } = data
325
- const payload: DeptFormPayload = {
326
- ...rest,
327
- name: String(data.name ?? '').trim(),
328
- orderNum: Number(data.orderNum ?? 0),
329
- parentId:
330
- data.parentId == null || data.parentId === ''
331
- ? null
332
- : Number(data.parentId),
333
- }
334
- if (formMode.value === 'add') {
335
- emit('add', payload)
336
- } else {
337
- emit('update', { ...payload, id: Number(data.id) })
338
- }
339
- helpers.close()
340
- }
341
-
342
- async function remove(row: FlatRow) {
343
- const ok = await vmConfirm({
344
- title: crudLabel('deptDeleteTitle'),
345
- message: crudFormat(crudLabel('deptDeleteMsg'), row.label),
346
- confirmText: crudLabel('delete'),
347
- cancelText: crudLabel('close'),
348
- })
349
- if (!ok) return
350
- if (props.modelValue === row.id) select(null)
351
- emit('delete', { id: row.id })
352
- }
353
-
354
- async function restoreDept(row: FlatRow) {
355
- if (trashBusyId.value === row.id) return
356
- const ok = await vmConfirm({
357
- title: crudLabel('deptRestoreTitle'),
358
- message: crudFormat(crudLabel('deptRestoreMsg'), row.label),
359
- confirmText: crudLabel('restore'),
360
- cancelText: crudLabel('close'),
361
- })
362
- if (!ok) return
363
- trashBusyId.value = row.id
364
- if (props.modelValue === row.id) select(null)
365
- emit('restore', { id: row.id })
366
- trashBusyId.value = null
367
- }
368
-
369
- async function forceDeleteDept(row: FlatRow) {
370
- if (trashBusyId.value === row.id) return
371
- const ok = await vmConfirm({
372
- title: crudLabel('deptForceDeleteTitle'),
373
- message: crudFormat(crudLabel('deptForceDeleteMsg'), row.label),
374
- confirmText: crudLabel('forceDelete'),
375
- cancelText: crudLabel('close'),
376
- })
377
- if (!ok) return
378
- trashBusyId.value = row.id
379
- if (props.modelValue === row.id) select(null)
380
- emit('forceDelete', { id: row.id })
381
- trashBusyId.value = null
382
- }
383
-
384
- defineExpose({ idNameMap })
385
- </script>
386
-
387
- <style lang="scss" scoped>
388
- .vm-dept-tree__node {
389
- display: flex;
390
- min-width: 0;
391
- min-height: 32px;
392
- align-items: center;
393
- gap: 4px;
394
- padding-right: 6px;
395
- border: none;
396
- border-radius: 10px;
397
- background: transparent;
398
- color: var(--foreground);
399
- cursor: pointer;
400
- text-align: left;
401
-
402
- &.is-hidden {
403
- display: none;
404
- }
405
-
406
- &:hover {
407
- background: var(--muted);
408
-
409
- .vm-dept-tree__ops {
410
- opacity: 1;
411
- }
412
- }
413
-
414
- &.is-active {
415
- background: var(--brand-soft);
416
- color: var(--brand-deep);
417
- }
418
-
419
- &.is-root {
420
- width: 100%;
421
- padding-left: 10px;
422
- margin-bottom: 4px;
423
- font-weight: 600;
424
-
425
- i {
426
- font-size: 15px;
427
- color: var(--brand);
428
- }
429
- }
430
- }
431
-
432
- .vm-dept-tree__expand {
433
- display: inline-flex;
434
- width: 20px;
435
- height: 20px;
436
- flex-shrink: 0;
437
- align-items: center;
438
- justify-content: center;
439
- border: none;
440
- border-radius: 6px;
441
- background: transparent;
442
- color: var(--muted-foreground);
443
- cursor: pointer;
444
-
445
- i {
446
- font-size: 14px;
447
- transition: transform 0.15s ease;
448
- }
449
-
450
- &.is-open i {
451
- transform: rotate(90deg);
452
- }
453
-
454
- &.is-spacer {
455
- visibility: hidden;
456
- }
457
- }
458
-
459
- .vm-dept-tree__label {
460
- flex: 1;
461
- min-width: 0;
462
- overflow: hidden;
463
- text-overflow: ellipsis;
464
- white-space: nowrap;
465
- font-size: 13px;
466
- }
467
-
468
- .vm-dept-tree__ops {
469
- display: inline-flex;
470
- gap: 2px;
471
- opacity: 0;
472
- transition: opacity 0.15s ease;
473
-
474
- button {
475
- display: inline-flex;
476
- width: 22px;
477
- height: 22px;
478
- align-items: center;
479
- justify-content: center;
480
- border: none;
481
- border-radius: 6px;
482
- background: transparent;
483
- color: var(--muted-foreground);
484
- cursor: pointer;
485
-
486
- &:hover {
487
- background: var(--card);
488
- color: var(--brand);
489
- }
490
-
491
- &:disabled {
492
- opacity: 0.5;
493
- cursor: not-allowed;
494
- }
495
- }
496
- }
497
- </style>
1
+ <template>
2
+ <vm-aside
3
+ :title="onlyTrashed ? crudLabel('deptTrash') : crudLabel('deptOrg')"
4
+ :empty="emptyText"
5
+ :loading="loading"
6
+ :add="canAdd && !onlyTrashed"
7
+ :add-title="crudLabel('deptAddRoot')"
8
+ @refresh="emit('refresh')"
9
+ @add="openAdd(null)"
10
+ >
11
+ <button
12
+ type="button"
13
+ class="vm-dept-tree__node is-root"
14
+ :class="{ 'is-active': modelValue == null }"
15
+ @click="select(null)"
16
+ >
17
+ <i class="ri-building-2-line" />
18
+ <span>{{ rootLabel ?? crudLabel('all') }}</span>
19
+ </button>
20
+
21
+ <div
22
+ v-for="row in visibleRows"
23
+ :key="String(row.id)"
24
+ class="vm-dept-tree__node"
25
+ :class="{ 'is-active': modelValue === row.id, 'is-hidden': !row.visible }"
26
+ :style="{ paddingLeft: `${12 + row.depth * 16}px` }"
27
+ @click="select(row.id)"
28
+ >
29
+ <button
30
+ v-if="row.hasChildren"
31
+ type="button"
32
+ class="vm-dept-tree__expand"
33
+ :class="{ 'is-open': expanded.has(row.id) }"
34
+ @click.stop="toggleExpand(row.id)"
35
+ >
36
+ <i class="ri-arrow-right-s-line" />
37
+ </button>
38
+ <span v-else class="vm-dept-tree__expand is-spacer" />
39
+ <span class="vm-dept-tree__label">{{ row.label }}</span>
40
+ <div v-if="onlyTrashed && canTrashOps" class="vm-dept-tree__ops" @click.stop>
41
+ <button
42
+ type="button"
43
+ :title="crudLabel('restore')"
44
+ :disabled="trashBusyId === row.id"
45
+ @click="restoreDept(row)"
46
+ >
47
+ <i class="ri-arrow-go-back-line" />
48
+ </button>
49
+ <button
50
+ type="button"
51
+ :title="crudLabel('forceDelete')"
52
+ :disabled="trashBusyId === row.id"
53
+ @click="forceDeleteDept(row)"
54
+ >
55
+ <i class="ri-delete-bin-2-line" />
56
+ </button>
57
+ </div>
58
+ <div v-else-if="!onlyTrashed && canManage" class="vm-dept-tree__ops" @click.stop>
59
+ <button v-if="canAdd" type="button" :title="crudLabel('deptAddChild')" @click="openAdd(row.id)">
60
+ <i class="ri-add-line" />
61
+ </button>
62
+ <button v-if="canUpdate" type="button" :title="crudLabel('update')" @click="openEdit(row)">
63
+ <i class="ri-edit-line" />
64
+ </button>
65
+ <button v-if="canDelete" type="button" :title="crudLabel('delete')" @click="remove(row)">
66
+ <i class="ri-delete-bin-line" />
67
+ </button>
68
+ </div>
69
+ </div>
70
+ </vm-aside>
71
+
72
+ <vm-form-dialog
73
+ v-model:open="formOpen"
74
+ v-model="formData"
75
+ :title="formMode === 'add' ? crudLabel('deptAddTitle') : crudLabel('deptEditTitle')"
76
+ :items="dialogItems"
77
+ @submit="onFormSubmit"
78
+ />
79
+ </template>
80
+
81
+ <script setup lang="ts">
82
+ import { ref, computed, watch } from 'vue'
83
+ import { flattenTree } from '../lib/tree'
84
+ import { vmConfirm } from '../crud/confirm'
85
+ import { crudFormat, crudLabel } from '../crud/style'
86
+ import { mergeByProp } from '../crud/auto-fields'
87
+ import type { CrudFormItem } from '../../../typings/admin/comm/crud'
88
+ import type { VmFormDialogSubmitHelpers } from './vm-form-dialog.vue'
89
+
90
+ defineOptions({ name: 'vm-dept-tree' })
91
+
92
+ /** 调用方须先组装好的树节点(含 children),组件不再做扁平→树转换 */
93
+ export type DeptTreeNode = {
94
+ id: number
95
+ parentId: number | null
96
+ orderNum: number
97
+ name: string
98
+ children?: DeptTreeNode[]
99
+ }
100
+
101
+ type FlatRow = {
102
+ id: number
103
+ label: string
104
+ depth: number
105
+ hasChildren: boolean
106
+ visible: boolean
107
+ parentId: number | null
108
+ orderNum: number
109
+ }
110
+
111
+ export type DeptFormPayload = {
112
+ name: string
113
+ orderNum: number
114
+ parentId: number | null
115
+ id?: number
116
+ } & Record<string, unknown>
117
+
118
+ const props = withDefaults(
119
+ defineProps<{
120
+ /** 已是树形结构的部门数据 */
121
+ data?: DeptTreeNode[]
122
+ loading?: boolean
123
+ modelValue?: number | null
124
+ rootLabel?: string
125
+ /** 回收站 UI:隐藏新增,显示恢复/彻底删除 */
126
+ onlyTrashed?: boolean
127
+ canAdd?: boolean
128
+ canUpdate?: boolean
129
+ canDelete?: boolean
130
+ canRestore?: boolean
131
+ /**
132
+ * 追加/覆盖表单字段(CrudFormItem);与内置「上级/名称/排序」合并,同 prop 以传入为准。
133
+ * 任意控件:`component: { name: 'vm-select', props: {…} }`
134
+ */
135
+ formItems?: CrudFormItem[]
136
+ }>(),
137
+ {
138
+ data: () => [],
139
+ loading: false,
140
+ modelValue: null,
141
+ rootLabel: undefined,
142
+ onlyTrashed: false,
143
+ canAdd: false,
144
+ canUpdate: false,
145
+ canDelete: false,
146
+ canRestore: false,
147
+ formItems: () => [],
148
+ },
149
+ )
150
+
151
+ const emit = defineEmits<{
152
+ 'update:modelValue': [v: number | null]
153
+ change: [v: number | null, meta: { name: string; ids: number[] }]
154
+ refresh: []
155
+ add: [payload: DeptFormPayload]
156
+ update: [payload: DeptFormPayload]
157
+ delete: [payload: { id: number }]
158
+ restore: [payload: { id: number }]
159
+ forceDelete: [payload: { id: number }]
160
+ }>()
161
+
162
+ const expanded = ref(new Set<number>())
163
+ const formOpen = ref(false)
164
+ const formMode = ref<'add' | 'edit'>('add')
165
+ const formData = ref<Record<string, unknown>>({})
166
+ const trashBusyId = ref<number | null>(null)
167
+
168
+ const canManage = computed(
169
+ () => props.canAdd || props.canUpdate || props.canDelete,
170
+ )
171
+ const canTrashOps = computed(() => props.canRestore || props.canDelete)
172
+
173
+ const builtinFormItems = computed((): CrudFormItem[] => [
174
+ {
175
+ prop: 'parentLabel',
176
+ label: crudLabel('deptParent'),
177
+ span: 12,
178
+ disabled: true,
179
+ },
180
+ {
181
+ prop: 'name',
182
+ label: crudLabel('fieldName'),
183
+ span: 12,
184
+ required: true,
185
+ placeholder: crudLabel('deptNamePh'),
186
+ },
187
+ {
188
+ prop: 'orderNum',
189
+ label: crudLabel('orderNum'),
190
+ span: 12,
191
+ type: 'number',
192
+ value: 0,
193
+ },
194
+ ])
195
+
196
+ const dialogItems = computed(() =>
197
+ mergeByProp(builtinFormItems.value, props.formItems || []),
198
+ )
199
+
200
+ const idNameMap = computed(() => {
201
+ const map = new Map<number, string>()
202
+ const walk = (list: DeptTreeNode[]) => {
203
+ for (const n of list) {
204
+ map.set(n.id, n.name)
205
+ if (n.children?.length) walk(n.children)
206
+ }
207
+ }
208
+ walk(props.data)
209
+ return map
210
+ })
211
+
212
+ const descendantMap = computed(() => {
213
+ const map = new Map<number, number[]>()
214
+ const walk = (node: DeptTreeNode): number[] => {
215
+ let ids: number[] = []
216
+ for (const c of node.children || []) {
217
+ ids.push(c.id)
218
+ ids = ids.concat(walk(c))
219
+ }
220
+ map.set(node.id, ids)
221
+ return ids
222
+ }
223
+ for (const n of props.data) walk(n)
224
+ return map
225
+ })
226
+
227
+ function collectExpandable(list: DeptTreeNode[], into: Set<number>) {
228
+ for (const n of list) {
229
+ if (n.children?.length) {
230
+ into.add(n.id)
231
+ collectExpandable(n.children, into)
232
+ }
233
+ }
234
+ }
235
+
236
+ const visibleRows = computed((): FlatRow[] => {
237
+ const flat = flattenTree(props.data, expanded.value)
238
+ return flat.map((n) => ({
239
+ id: n.id as number,
240
+ label: n.name,
241
+ depth: n.depth,
242
+ hasChildren: n.hasChildren,
243
+ visible: true,
244
+ parentId: n.parentId ?? null,
245
+ orderNum: n.orderNum ?? 0,
246
+ }))
247
+ })
248
+
249
+ const emptyText = computed(() => {
250
+ if (props.loading || visibleRows.value.length) return ''
251
+ return props.onlyTrashed ? crudLabel('trashEmpty') : crudLabel('deptEmpty')
252
+ })
253
+
254
+ function resolveFilterIds(id: number | null) {
255
+ if (id == null) return []
256
+ return [id, ...(descendantMap.value.get(id) || [])]
257
+ }
258
+
259
+ function select(id: number | null) {
260
+ emit('update:modelValue', id)
261
+ const name =
262
+ id == null ? (props.rootLabel ?? '') : idNameMap.value.get(id) || ''
263
+ emit('change', id, { name, ids: resolveFilterIds(id) })
264
+ }
265
+
266
+ function toggleExpand(id: number) {
267
+ const next = new Set(expanded.value)
268
+ if (next.has(id)) next.delete(id)
269
+ else next.add(id)
270
+ expanded.value = next
271
+ }
272
+
273
+ function syncExpandedFromData() {
274
+ const next = new Set<number>()
275
+ collectExpandable(props.data, next)
276
+ expanded.value = next
277
+ if (props.modelValue != null && !idNameMap.value.has(props.modelValue)) {
278
+ select(null)
279
+ }
280
+ }
281
+
282
+ watch(
283
+ () => props.data,
284
+ () => syncExpandedFromData(),
285
+ { deep: true, immediate: true },
286
+ )
287
+
288
+ watch(
289
+ () => props.onlyTrashed,
290
+ () => {
291
+ select(null)
292
+ },
293
+ )
294
+
295
+ function parentLabelOf(parentId: number | null) {
296
+ return parentId == null ? crudLabel('topLevel') : idNameMap.value.get(parentId) || String(parentId)
297
+ }
298
+
299
+ function openAdd(parentId: number | null) {
300
+ formMode.value = 'add'
301
+ formData.value = {
302
+ id: 0,
303
+ name: '',
304
+ orderNum: 0,
305
+ parentId,
306
+ parentLabel: parentLabelOf(parentId),
307
+ }
308
+ formOpen.value = true
309
+ }
310
+
311
+ function openEdit(row: FlatRow) {
312
+ formMode.value = 'edit'
313
+ formData.value = {
314
+ id: row.id,
315
+ name: row.label,
316
+ orderNum: row.orderNum ?? 0,
317
+ parentId: row.parentId,
318
+ parentLabel: parentLabelOf(row.parentId),
319
+ }
320
+ formOpen.value = true
321
+ }
322
+
323
+ function onFormSubmit(data: Record<string, unknown>, helpers: VmFormDialogSubmitHelpers) {
324
+ const { parentLabel: _pl, ...rest } = data
325
+ const payload: DeptFormPayload = {
326
+ ...rest,
327
+ name: String(data.name ?? '').trim(),
328
+ orderNum: Number(data.orderNum ?? 0),
329
+ parentId:
330
+ data.parentId == null || data.parentId === ''
331
+ ? null
332
+ : Number(data.parentId),
333
+ }
334
+ if (formMode.value === 'add') {
335
+ emit('add', payload)
336
+ } else {
337
+ emit('update', { ...payload, id: Number(data.id) })
338
+ }
339
+ helpers.close()
340
+ }
341
+
342
+ async function remove(row: FlatRow) {
343
+ const ok = await vmConfirm({
344
+ title: crudLabel('deptDeleteTitle'),
345
+ message: crudFormat(crudLabel('deptDeleteMsg'), row.label),
346
+ confirmText: crudLabel('delete'),
347
+ cancelText: crudLabel('close'),
348
+ })
349
+ if (!ok) return
350
+ if (props.modelValue === row.id) select(null)
351
+ emit('delete', { id: row.id })
352
+ }
353
+
354
+ async function restoreDept(row: FlatRow) {
355
+ if (trashBusyId.value === row.id) return
356
+ const ok = await vmConfirm({
357
+ title: crudLabel('deptRestoreTitle'),
358
+ message: crudFormat(crudLabel('deptRestoreMsg'), row.label),
359
+ confirmText: crudLabel('restore'),
360
+ cancelText: crudLabel('close'),
361
+ })
362
+ if (!ok) return
363
+ trashBusyId.value = row.id
364
+ if (props.modelValue === row.id) select(null)
365
+ emit('restore', { id: row.id })
366
+ trashBusyId.value = null
367
+ }
368
+
369
+ async function forceDeleteDept(row: FlatRow) {
370
+ if (trashBusyId.value === row.id) return
371
+ const ok = await vmConfirm({
372
+ title: crudLabel('deptForceDeleteTitle'),
373
+ message: crudFormat(crudLabel('deptForceDeleteMsg'), row.label),
374
+ confirmText: crudLabel('forceDelete'),
375
+ cancelText: crudLabel('close'),
376
+ })
377
+ if (!ok) return
378
+ trashBusyId.value = row.id
379
+ if (props.modelValue === row.id) select(null)
380
+ emit('forceDelete', { id: row.id })
381
+ trashBusyId.value = null
382
+ }
383
+
384
+ defineExpose({ idNameMap })
385
+ </script>
386
+
387
+ <style lang="scss" scoped>
388
+ .vm-dept-tree__node {
389
+ display: flex;
390
+ min-width: 0;
391
+ min-height: 32px;
392
+ align-items: center;
393
+ gap: 4px;
394
+ padding-right: 6px;
395
+ border: none;
396
+ border-radius: 10px;
397
+ background: transparent;
398
+ color: var(--foreground);
399
+ cursor: pointer;
400
+ text-align: left;
401
+
402
+ &.is-hidden {
403
+ display: none;
404
+ }
405
+
406
+ &:hover {
407
+ background: var(--muted);
408
+
409
+ .vm-dept-tree__ops {
410
+ opacity: 1;
411
+ }
412
+ }
413
+
414
+ &.is-active {
415
+ background: var(--brand-soft);
416
+ color: var(--brand-deep);
417
+ }
418
+
419
+ &.is-root {
420
+ width: 100%;
421
+ padding-left: 10px;
422
+ margin-bottom: 4px;
423
+ font-weight: 600;
424
+
425
+ i {
426
+ font-size: 15px;
427
+ color: var(--brand);
428
+ }
429
+ }
430
+ }
431
+
432
+ .vm-dept-tree__expand {
433
+ display: inline-flex;
434
+ width: 20px;
435
+ height: 20px;
436
+ flex-shrink: 0;
437
+ align-items: center;
438
+ justify-content: center;
439
+ border: none;
440
+ border-radius: 6px;
441
+ background: transparent;
442
+ color: var(--muted-foreground);
443
+ cursor: pointer;
444
+
445
+ i {
446
+ font-size: 14px;
447
+ transition: transform 0.15s ease;
448
+ }
449
+
450
+ &.is-open i {
451
+ transform: rotate(90deg);
452
+ }
453
+
454
+ &.is-spacer {
455
+ visibility: hidden;
456
+ }
457
+ }
458
+
459
+ .vm-dept-tree__label {
460
+ flex: 1;
461
+ min-width: 0;
462
+ overflow: hidden;
463
+ text-overflow: ellipsis;
464
+ white-space: nowrap;
465
+ font-size: 13px;
466
+ }
467
+
468
+ .vm-dept-tree__ops {
469
+ display: inline-flex;
470
+ gap: 2px;
471
+ opacity: 0;
472
+ transition: opacity 0.15s ease;
473
+
474
+ button {
475
+ display: inline-flex;
476
+ width: 22px;
477
+ height: 22px;
478
+ align-items: center;
479
+ justify-content: center;
480
+ border: none;
481
+ border-radius: 6px;
482
+ background: transparent;
483
+ color: var(--muted-foreground);
484
+ cursor: pointer;
485
+
486
+ &:hover {
487
+ background: var(--card);
488
+ color: var(--brand);
489
+ }
490
+
491
+ &:disabled {
492
+ opacity: 0.5;
493
+ cursor: not-allowed;
494
+ }
495
+ }
496
+ }
497
+ </style>