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,537 +1,537 @@
1
- <template>
2
- <div class="vm-dt-cal">
3
- <header class="vm-dt-cal__head">
4
- <button type="button" class="vm-dt-cal__title" @click="toggleView">
5
- <span>{{ headTitle }}</span>
6
- <i
7
- class="ri-arrow-down-s-line"
8
- :class="{ 'is-up': view !== 'date' }"
9
- aria-hidden="true"
10
- />
11
- </button>
12
- <div class="vm-dt-cal__nav">
13
- <button type="button" class="vm-dt-cal__nav-btn" :title="crudLabel('prevPage')" @click="prevPage">
14
- <i class="ri-arrow-up-s-line" aria-hidden="true" />
15
- </button>
16
- <button type="button" class="vm-dt-cal__nav-btn" :title="crudLabel('nextPage')" @click="nextPage">
17
- <i class="ri-arrow-down-s-line" aria-hidden="true" />
18
- </button>
19
- </div>
20
- </header>
21
-
22
- <div v-if="view === 'date'" class="vm-dt-cal__body">
23
- <div class="vm-dt-cal__weeks">
24
- <span v-for="w in weekdays" :key="w" class="vm-dt-cal__week">{{ w }}</span>
25
- </div>
26
- <div class="vm-dt-cal__grid">
27
- <button
28
- v-for="cell in monthCells"
29
- :key="cell.iso"
30
- type="button"
31
- class="vm-dt-cal__day"
32
- :class="{
33
- 'is-out': !cell.inMonth,
34
- 'is-today': cell.isToday,
35
- 'is-on': selectedIso === cell.iso,
36
- }"
37
- @click="emitValue(cell.iso)"
38
- >
39
- {{ cell.day }}
40
- </button>
41
- </div>
42
- </div>
43
-
44
- <div v-else-if="view === 'month'" class="vm-dt-cal__body vm-dt-cal__body--months">
45
- <button
46
- v-for="(lab, idx) in monthLabels"
47
- :key="lab"
48
- type="button"
49
- class="vm-dt-cal__cell"
50
- :class="{ 'is-on': viewMonth === idx }"
51
- @click="pickMonth(idx)"
52
- >
53
- {{ lab }}
54
- </button>
55
- </div>
56
-
57
- <div v-else class="vm-dt-cal__body vm-dt-cal__body--months">
58
- <button
59
- v-for="y in yearCells"
60
- :key="y"
61
- type="button"
62
- class="vm-dt-cal__cell"
63
- :class="{ 'is-on': viewYear === y }"
64
- @click="pickYear(y)"
65
- >
66
- {{ y }}
67
- </button>
68
- </div>
69
-
70
- <div v-if="isDatetime" class="vm-dt-cal__time" @pointerdown.stop>
71
- <label v-for="f in timeFields" :key="f.key" class="vm-dt-cal__time-field">
72
- <span>{{ f.label }}</span>
73
- <input
74
- type="text"
75
- inputmode="numeric"
76
- autocomplete="off"
77
- maxlength="2"
78
- :value="draft[f.key]"
79
- @pointerdown.stop
80
- @mousedown.stop
81
- @click.stop
82
- @keydown.stop="onTimeKeydown(f.key, $event)"
83
- @focus="onTimeFocus"
84
- @input="(e) => onTimeInput(f.key, e)"
85
- @blur="onTimeBlur(f.key)"
86
- />
87
- </label>
88
- </div>
89
-
90
- <footer class="vm-dt-cal__foot">
91
- <button type="button" class="vm-dt-cal__link" @click="emit('clear')">{{ crudLabel('clear') }}</button>
92
- <button type="button" class="vm-dt-cal__link" @click="pickToday">{{ crudLabel('today') }}</button>
93
- </footer>
94
- </div>
95
- </template>
96
-
97
- <script setup lang="ts">
98
- import { computed, reactive, ref, watch } from 'vue'
99
- import { crudFormat, crudLabel } from '../style'
100
- import {
101
- buildMonthGrid,
102
- clampTimePart,
103
- formatYmd,
104
- normalizeDateTimePrecision,
105
- parseApiDateTime,
106
- type DateTimePrecision,
107
- } from '../../../shared/datetime-picker'
108
-
109
- defineOptions({ name: 'vm-date-calendar-panel' })
110
-
111
- const props = withDefaults(
112
- defineProps<{
113
- modelValue?: string | null
114
- precision?: DateTimePrecision | string
115
- }>(),
116
- {
117
- modelValue: null,
118
- precision: 'day',
119
- },
120
- )
121
-
122
- const emit = defineEmits<{
123
- 'update:modelValue': [v: string]
124
- clear: []
125
- }>()
126
-
127
- const precision = computed(() => normalizeDateTimePrecision(props.precision))
128
- const isDatetime = computed(() => precision.value === 'datetime')
129
- const weekdays = computed(() => [
130
- crudLabel('weekdaySun'),
131
- crudLabel('weekdayMon'),
132
- crudLabel('weekdayTue'),
133
- crudLabel('weekdayWed'),
134
- crudLabel('weekdayThu'),
135
- crudLabel('weekdayFri'),
136
- crudLabel('weekdaySat'),
137
- ])
138
- const monthLabels = computed(() =>
139
- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map((n) => crudLabel(`month${n}`)),
140
- )
141
- const timeFields = computed(() => [
142
- { key: 'h' as const, label: crudLabel('hour'), max: 23 },
143
- { key: 'm' as const, label: crudLabel('minute'), max: 59 },
144
- { key: 's' as const, label: crudLabel('second'), max: 59 },
145
- ])
146
-
147
- type PanelView = 'date' | 'month' | 'year'
148
- const view = ref<PanelView>('date')
149
- const draft = reactive({ h: '0', m: '0', s: '0' })
150
- const timeFocused = ref(false)
151
- const viewYear = ref(new Date().getFullYear())
152
- const viewMonth = ref(new Date().getMonth())
153
-
154
- watch(
155
- () => props.modelValue,
156
- (v) => {
157
- const d = parseApiDateTime(v) || new Date()
158
- viewYear.value = d.getFullYear()
159
- viewMonth.value = d.getMonth()
160
- if (timeFocused.value) return
161
- draft.h = String(d.getHours())
162
- draft.m = String(d.getMinutes())
163
- draft.s = String(d.getSeconds())
164
- },
165
- { immediate: true },
166
- )
167
-
168
- const selectedIso = computed(() => {
169
- const d = parseApiDateTime(props.modelValue)
170
- return d ? formatYmd(d) : ''
171
- })
172
-
173
- const monthCells = computed(() => buildMonthGrid(viewYear.value, viewMonth.value))
174
- const yearStart = computed(() => Math.floor(viewYear.value / 10) * 10)
175
- const yearCells = computed(() =>
176
- Array.from({ length: 12 }, (_, i) => yearStart.value + i),
177
- )
178
-
179
- const headTitle = computed(() => {
180
- if (view.value === 'year')
181
- return crudFormat(crudLabel('yearRange'), yearStart.value, yearStart.value + 11)
182
- if (view.value === 'month') return crudFormat(crudLabel('yearLabel'), viewYear.value)
183
- return crudFormat(
184
- crudLabel('yearMonth'),
185
- viewYear.value,
186
- String(viewMonth.value + 1).padStart(2, '0'),
187
- )
188
- })
189
-
190
- function currentTime() {
191
- return {
192
- h: clampTimePart('h', Number(draft.h) || 0),
193
- m: clampTimePart('m', Number(draft.m) || 0),
194
- s: clampTimePart('s', Number(draft.s) || 0),
195
- }
196
- }
197
-
198
- function emitDatetime(isoDate: string, t = currentTime()) {
199
- const pad = (n: number) => String(n).padStart(2, '0')
200
- emit(
201
- 'update:modelValue',
202
- `${isoDate} ${pad(t.h)}:${pad(t.m)}:${pad(t.s)}`,
203
- )
204
- }
205
-
206
- function onTimeFocus() {
207
- timeFocused.value = true
208
- }
209
-
210
- /** 手输过程只改 draft,失焦 / Enter 再提交,避免受控回写打乱光标 */
211
- function onTimeInput(k: 'h' | 'm' | 's', e: Event) {
212
- const raw = (e.target as HTMLInputElement).value.replace(/\D/g, '').slice(0, 2)
213
- draft[k] = raw
214
- ;(e.target as HTMLInputElement).value = raw
215
- }
216
-
217
- function commitTimeField(k: 'h' | 'm' | 's') {
218
- const n = Number(draft[k])
219
- const next = currentTime()
220
- next[k] = Number.isFinite(n) ? clampTimePart(k, n) : 0
221
- draft[k] = String(next[k])
222
- emitDatetime(selectedIso.value || formatYmd(new Date()), next)
223
- }
224
-
225
- function onTimeBlur(k: 'h' | 'm' | 's') {
226
- commitTimeField(k)
227
- timeFocused.value = false
228
- }
229
-
230
- function onTimeKeydown(k: 'h' | 'm' | 's', e: KeyboardEvent) {
231
- if (e.key === 'Enter') {
232
- e.preventDefault()
233
- commitTimeField(k)
234
- ;(e.target as HTMLInputElement).blur()
235
- }
236
- }
237
-
238
- function toggleView() {
239
- view.value = view.value === 'date' ? 'month' : view.value === 'month' ? 'year' : 'date'
240
- }
241
-
242
- function prevPage() {
243
- if (view.value === 'year') {
244
- viewYear.value -= 12
245
- return
246
- }
247
- if (view.value === 'month') {
248
- viewYear.value -= 1
249
- return
250
- }
251
- if (viewMonth.value <= 0) {
252
- viewMonth.value = 11
253
- viewYear.value -= 1
254
- } else {
255
- viewMonth.value -= 1
256
- }
257
- }
258
-
259
- function nextPage() {
260
- if (view.value === 'year') {
261
- viewYear.value += 12
262
- return
263
- }
264
- if (view.value === 'month') {
265
- viewYear.value += 1
266
- return
267
- }
268
- if (viewMonth.value >= 11) {
269
- viewMonth.value = 0
270
- viewYear.value += 1
271
- } else {
272
- viewMonth.value += 1
273
- }
274
- }
275
-
276
- function emitValue(isoDate: string) {
277
- if (isDatetime.value) {
278
- emitDatetime(isoDate)
279
- return
280
- }
281
- if (precision.value === 'month') {
282
- emit('update:modelValue', isoDate.slice(0, 7))
283
- return
284
- }
285
- if (precision.value === 'year') {
286
- emit('update:modelValue', isoDate.slice(0, 4))
287
- return
288
- }
289
- emit('update:modelValue', isoDate)
290
- }
291
-
292
- function pickMonth(idx: number) {
293
- viewMonth.value = idx
294
- view.value = 'date'
295
- if (precision.value === 'month') {
296
- emit('update:modelValue', `${viewYear.value}-${String(idx + 1).padStart(2, '0')}`)
297
- }
298
- }
299
-
300
- function pickYear(y: number) {
301
- viewYear.value = y
302
- view.value = 'month'
303
- if (precision.value === 'year') {
304
- emit('update:modelValue', String(y))
305
- }
306
- }
307
-
308
- function pickToday() {
309
- const now = new Date()
310
- viewYear.value = now.getFullYear()
311
- viewMonth.value = now.getMonth()
312
- view.value = 'date'
313
- emitValue(formatYmd(now))
314
- }
315
- </script>
316
-
317
- <style lang="scss" scoped>
318
- .vm-dt-cal {
319
- box-sizing: border-box;
320
- width: 280px;
321
- padding: 8px 10px 6px;
322
- border: 1px solid var(--border, #e8ebf5);
323
- border-radius: 12px;
324
- background: var(--card, #fff);
325
- box-shadow: 0 8px 24px rgb(15 23 42 / 10%);
326
- color: var(--foreground, #1f2937);
327
- }
328
-
329
- .vm-dt-cal__head {
330
- display: flex;
331
- align-items: center;
332
- justify-content: space-between;
333
- gap: 8px;
334
- margin-bottom: 8px;
335
- }
336
-
337
- .vm-dt-cal__title {
338
- display: inline-flex;
339
- align-items: center;
340
- gap: 2px;
341
- margin: 0;
342
- padding: 4px 6px;
343
- border: 0;
344
- border-radius: 8px;
345
- background: transparent;
346
- color: inherit;
347
- font-size: 14px;
348
- font-weight: 600;
349
- cursor: pointer;
350
-
351
- &:hover {
352
- background: var(--muted, #f4f6fb);
353
- }
354
-
355
- i {
356
- font-size: 16px;
357
- color: var(--muted-foreground, #94a3b8);
358
- transition: transform 0.15s ease;
359
-
360
- &.is-up {
361
- transform: rotate(180deg);
362
- }
363
- }
364
- }
365
-
366
- .vm-dt-cal__nav {
367
- display: flex;
368
- gap: 2px;
369
- }
370
-
371
- .vm-dt-cal__nav-btn {
372
- display: inline-flex;
373
- width: 24px;
374
- height: 24px;
375
- align-items: center;
376
- justify-content: center;
377
- border: 0;
378
- border-radius: 6px;
379
- background: transparent;
380
- color: var(--muted-foreground, #64748b);
381
- cursor: pointer;
382
-
383
- &:hover {
384
- background: var(--muted, #f4f6fb);
385
- color: var(--foreground, #1f2937);
386
- }
387
- }
388
-
389
- .vm-dt-cal__weeks {
390
- display: grid;
391
- grid-template-columns: repeat(7, 1fr);
392
- margin-bottom: 4px;
393
- }
394
-
395
- .vm-dt-cal__week {
396
- height: 28px;
397
- color: var(--muted-foreground, #64748b);
398
- font-size: 12px;
399
- line-height: 28px;
400
- text-align: center;
401
- }
402
-
403
- .vm-dt-cal__grid {
404
- display: grid;
405
- grid-template-columns: repeat(7, 1fr);
406
- gap: 2px 0;
407
- }
408
-
409
- .vm-dt-cal__day {
410
- width: 32px;
411
- height: 32px;
412
- margin: 0 auto;
413
- padding: 0;
414
- border: 0;
415
- border-radius: 8px;
416
- background: transparent;
417
- color: inherit;
418
- font-size: 13px;
419
- line-height: 32px;
420
- text-align: center;
421
- cursor: pointer;
422
-
423
- &.is-out {
424
- color: var(--muted-foreground, #cbd5e1);
425
- }
426
-
427
- &.is-today:not(.is-on) {
428
- color: var(--primary, var(--vm-brand, #4e5dff));
429
- font-weight: 600;
430
- }
431
-
432
- &.is-on {
433
- background: var(--primary, var(--vm-brand, #4e5dff));
434
- color: #fff;
435
- font-weight: 600;
436
- }
437
-
438
- &:hover:not(.is-on) {
439
- background: color-mix(in srgb, var(--primary, #4e5dff) 10%, transparent);
440
- }
441
- }
442
-
443
- .vm-dt-cal__body--months {
444
- display: grid;
445
- grid-template-columns: repeat(3, 1fr);
446
- gap: 8px 6px;
447
- padding: 4px 0 8px;
448
- }
449
-
450
- .vm-dt-cal__cell {
451
- height: 36px;
452
- border: 0;
453
- border-radius: 8px;
454
- background: transparent;
455
- color: inherit;
456
- font-size: 13px;
457
- cursor: pointer;
458
-
459
- &.is-on {
460
- background: var(--primary, var(--vm-brand, #4e5dff));
461
- color: #fff;
462
- font-weight: 600;
463
- }
464
-
465
- &:hover:not(.is-on) {
466
- background: var(--muted, #f4f6fb);
467
- }
468
- }
469
-
470
- .vm-dt-cal__time {
471
- display: flex;
472
- gap: 8px;
473
- align-items: center;
474
- padding: 8px 2px 4px;
475
- border-top: 1px solid var(--border, #e8ebf5);
476
- margin-top: 4px;
477
- }
478
-
479
- .vm-dt-cal__time-field {
480
- display: flex;
481
- flex: 1;
482
- align-items: center;
483
- gap: 4px;
484
- min-width: 0;
485
- font-size: 12px;
486
- color: var(--muted-foreground, #64748b);
487
-
488
- input {
489
- box-sizing: border-box;
490
- width: 100%;
491
- min-width: 0;
492
- height: 28px;
493
- padding: 0 6px;
494
- border: 1px solid var(--border, #e8ebf5);
495
- border-radius: 8px;
496
- background: #fff;
497
- color: var(--foreground, #1f2937);
498
- font-size: 13px;
499
- line-height: 28px;
500
- text-align: center;
501
- outline: none;
502
- cursor: text;
503
- user-select: text;
504
- -webkit-user-select: text;
505
- caret-color: var(--primary, var(--vm-brand, #4e5dff));
506
-
507
- &:focus {
508
- border-color: var(--primary, var(--vm-brand, #4e5dff));
509
- background: #fff;
510
- box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary, #4e5dff) 18%, transparent);
511
- }
512
- }
513
- }
514
-
515
- .vm-dt-cal__foot {
516
- display: flex;
517
- align-items: center;
518
- justify-content: space-between;
519
- padding-top: 6px;
520
- border-top: 1px solid var(--border, #e8ebf5);
521
- margin-top: 4px;
522
- }
523
-
524
- .vm-dt-cal__link {
525
- border: 0;
526
- background: transparent;
527
- color: var(--primary, var(--vm-brand, #4e5dff));
528
- font-size: 13px;
529
- cursor: pointer;
530
- padding: 4px 6px;
531
- border-radius: 6px;
532
-
533
- &:hover {
534
- background: color-mix(in srgb, var(--primary, #4e5dff) 8%, transparent);
535
- }
536
- }
537
- </style>
1
+ <template>
2
+ <div class="vm-dt-cal">
3
+ <header class="vm-dt-cal__head">
4
+ <button type="button" class="vm-dt-cal__title" @click="toggleView">
5
+ <span>{{ headTitle }}</span>
6
+ <i
7
+ class="ri-arrow-down-s-line"
8
+ :class="{ 'is-up': view !== 'date' }"
9
+ aria-hidden="true"
10
+ />
11
+ </button>
12
+ <div class="vm-dt-cal__nav">
13
+ <button type="button" class="vm-dt-cal__nav-btn" :title="crudLabel('prevPage')" @click="prevPage">
14
+ <i class="ri-arrow-up-s-line" aria-hidden="true" />
15
+ </button>
16
+ <button type="button" class="vm-dt-cal__nav-btn" :title="crudLabel('nextPage')" @click="nextPage">
17
+ <i class="ri-arrow-down-s-line" aria-hidden="true" />
18
+ </button>
19
+ </div>
20
+ </header>
21
+
22
+ <div v-if="view === 'date'" class="vm-dt-cal__body">
23
+ <div class="vm-dt-cal__weeks">
24
+ <span v-for="w in weekdays" :key="w" class="vm-dt-cal__week">{{ w }}</span>
25
+ </div>
26
+ <div class="vm-dt-cal__grid">
27
+ <button
28
+ v-for="cell in monthCells"
29
+ :key="cell.iso"
30
+ type="button"
31
+ class="vm-dt-cal__day"
32
+ :class="{
33
+ 'is-out': !cell.inMonth,
34
+ 'is-today': cell.isToday,
35
+ 'is-on': selectedIso === cell.iso,
36
+ }"
37
+ @click="emitValue(cell.iso)"
38
+ >
39
+ {{ cell.day }}
40
+ </button>
41
+ </div>
42
+ </div>
43
+
44
+ <div v-else-if="view === 'month'" class="vm-dt-cal__body vm-dt-cal__body--months">
45
+ <button
46
+ v-for="(lab, idx) in monthLabels"
47
+ :key="lab"
48
+ type="button"
49
+ class="vm-dt-cal__cell"
50
+ :class="{ 'is-on': viewMonth === idx }"
51
+ @click="pickMonth(idx)"
52
+ >
53
+ {{ lab }}
54
+ </button>
55
+ </div>
56
+
57
+ <div v-else class="vm-dt-cal__body vm-dt-cal__body--months">
58
+ <button
59
+ v-for="y in yearCells"
60
+ :key="y"
61
+ type="button"
62
+ class="vm-dt-cal__cell"
63
+ :class="{ 'is-on': viewYear === y }"
64
+ @click="pickYear(y)"
65
+ >
66
+ {{ y }}
67
+ </button>
68
+ </div>
69
+
70
+ <div v-if="isDatetime" class="vm-dt-cal__time" @pointerdown.stop>
71
+ <label v-for="f in timeFields" :key="f.key" class="vm-dt-cal__time-field">
72
+ <span>{{ f.label }}</span>
73
+ <input
74
+ type="text"
75
+ inputmode="numeric"
76
+ autocomplete="off"
77
+ maxlength="2"
78
+ :value="draft[f.key]"
79
+ @pointerdown.stop
80
+ @mousedown.stop
81
+ @click.stop
82
+ @keydown.stop="onTimeKeydown(f.key, $event)"
83
+ @focus="onTimeFocus"
84
+ @input="(e) => onTimeInput(f.key, e)"
85
+ @blur="onTimeBlur(f.key)"
86
+ />
87
+ </label>
88
+ </div>
89
+
90
+ <footer class="vm-dt-cal__foot">
91
+ <button type="button" class="vm-dt-cal__link" @click="emit('clear')">{{ crudLabel('clear') }}</button>
92
+ <button type="button" class="vm-dt-cal__link" @click="pickToday">{{ crudLabel('today') }}</button>
93
+ </footer>
94
+ </div>
95
+ </template>
96
+
97
+ <script setup lang="ts">
98
+ import { computed, reactive, ref, watch } from 'vue'
99
+ import { crudFormat, crudLabel } from '../style'
100
+ import {
101
+ buildMonthGrid,
102
+ clampTimePart,
103
+ formatYmd,
104
+ normalizeDateTimePrecision,
105
+ parseApiDateTime,
106
+ type DateTimePrecision,
107
+ } from '../../../shared/datetime-picker'
108
+
109
+ defineOptions({ name: 'vm-date-calendar-panel' })
110
+
111
+ const props = withDefaults(
112
+ defineProps<{
113
+ modelValue?: string | null
114
+ precision?: DateTimePrecision | string
115
+ }>(),
116
+ {
117
+ modelValue: null,
118
+ precision: 'day',
119
+ },
120
+ )
121
+
122
+ const emit = defineEmits<{
123
+ 'update:modelValue': [v: string]
124
+ clear: []
125
+ }>()
126
+
127
+ const precision = computed(() => normalizeDateTimePrecision(props.precision))
128
+ const isDatetime = computed(() => precision.value === 'datetime')
129
+ const weekdays = computed(() => [
130
+ crudLabel('weekdaySun'),
131
+ crudLabel('weekdayMon'),
132
+ crudLabel('weekdayTue'),
133
+ crudLabel('weekdayWed'),
134
+ crudLabel('weekdayThu'),
135
+ crudLabel('weekdayFri'),
136
+ crudLabel('weekdaySat'),
137
+ ])
138
+ const monthLabels = computed(() =>
139
+ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map((n) => crudLabel(`month${n}`)),
140
+ )
141
+ const timeFields = computed(() => [
142
+ { key: 'h' as const, label: crudLabel('hour'), max: 23 },
143
+ { key: 'm' as const, label: crudLabel('minute'), max: 59 },
144
+ { key: 's' as const, label: crudLabel('second'), max: 59 },
145
+ ])
146
+
147
+ type PanelView = 'date' | 'month' | 'year'
148
+ const view = ref<PanelView>('date')
149
+ const draft = reactive({ h: '0', m: '0', s: '0' })
150
+ const timeFocused = ref(false)
151
+ const viewYear = ref(new Date().getFullYear())
152
+ const viewMonth = ref(new Date().getMonth())
153
+
154
+ watch(
155
+ () => props.modelValue,
156
+ (v) => {
157
+ const d = parseApiDateTime(v) || new Date()
158
+ viewYear.value = d.getFullYear()
159
+ viewMonth.value = d.getMonth()
160
+ if (timeFocused.value) return
161
+ draft.h = String(d.getHours())
162
+ draft.m = String(d.getMinutes())
163
+ draft.s = String(d.getSeconds())
164
+ },
165
+ { immediate: true },
166
+ )
167
+
168
+ const selectedIso = computed(() => {
169
+ const d = parseApiDateTime(props.modelValue)
170
+ return d ? formatYmd(d) : ''
171
+ })
172
+
173
+ const monthCells = computed(() => buildMonthGrid(viewYear.value, viewMonth.value))
174
+ const yearStart = computed(() => Math.floor(viewYear.value / 10) * 10)
175
+ const yearCells = computed(() =>
176
+ Array.from({ length: 12 }, (_, i) => yearStart.value + i),
177
+ )
178
+
179
+ const headTitle = computed(() => {
180
+ if (view.value === 'year')
181
+ return crudFormat(crudLabel('yearRange'), yearStart.value, yearStart.value + 11)
182
+ if (view.value === 'month') return crudFormat(crudLabel('yearLabel'), viewYear.value)
183
+ return crudFormat(
184
+ crudLabel('yearMonth'),
185
+ viewYear.value,
186
+ String(viewMonth.value + 1).padStart(2, '0'),
187
+ )
188
+ })
189
+
190
+ function currentTime() {
191
+ return {
192
+ h: clampTimePart('h', Number(draft.h) || 0),
193
+ m: clampTimePart('m', Number(draft.m) || 0),
194
+ s: clampTimePart('s', Number(draft.s) || 0),
195
+ }
196
+ }
197
+
198
+ function emitDatetime(isoDate: string, t = currentTime()) {
199
+ const pad = (n: number) => String(n).padStart(2, '0')
200
+ emit(
201
+ 'update:modelValue',
202
+ `${isoDate} ${pad(t.h)}:${pad(t.m)}:${pad(t.s)}`,
203
+ )
204
+ }
205
+
206
+ function onTimeFocus() {
207
+ timeFocused.value = true
208
+ }
209
+
210
+ /** 手输过程只改 draft,失焦 / Enter 再提交,避免受控回写打乱光标 */
211
+ function onTimeInput(k: 'h' | 'm' | 's', e: Event) {
212
+ const raw = (e.target as HTMLInputElement).value.replace(/\D/g, '').slice(0, 2)
213
+ draft[k] = raw
214
+ ;(e.target as HTMLInputElement).value = raw
215
+ }
216
+
217
+ function commitTimeField(k: 'h' | 'm' | 's') {
218
+ const n = Number(draft[k])
219
+ const next = currentTime()
220
+ next[k] = Number.isFinite(n) ? clampTimePart(k, n) : 0
221
+ draft[k] = String(next[k])
222
+ emitDatetime(selectedIso.value || formatYmd(new Date()), next)
223
+ }
224
+
225
+ function onTimeBlur(k: 'h' | 'm' | 's') {
226
+ commitTimeField(k)
227
+ timeFocused.value = false
228
+ }
229
+
230
+ function onTimeKeydown(k: 'h' | 'm' | 's', e: KeyboardEvent) {
231
+ if (e.key === 'Enter') {
232
+ e.preventDefault()
233
+ commitTimeField(k)
234
+ ;(e.target as HTMLInputElement).blur()
235
+ }
236
+ }
237
+
238
+ function toggleView() {
239
+ view.value = view.value === 'date' ? 'month' : view.value === 'month' ? 'year' : 'date'
240
+ }
241
+
242
+ function prevPage() {
243
+ if (view.value === 'year') {
244
+ viewYear.value -= 12
245
+ return
246
+ }
247
+ if (view.value === 'month') {
248
+ viewYear.value -= 1
249
+ return
250
+ }
251
+ if (viewMonth.value <= 0) {
252
+ viewMonth.value = 11
253
+ viewYear.value -= 1
254
+ } else {
255
+ viewMonth.value -= 1
256
+ }
257
+ }
258
+
259
+ function nextPage() {
260
+ if (view.value === 'year') {
261
+ viewYear.value += 12
262
+ return
263
+ }
264
+ if (view.value === 'month') {
265
+ viewYear.value += 1
266
+ return
267
+ }
268
+ if (viewMonth.value >= 11) {
269
+ viewMonth.value = 0
270
+ viewYear.value += 1
271
+ } else {
272
+ viewMonth.value += 1
273
+ }
274
+ }
275
+
276
+ function emitValue(isoDate: string) {
277
+ if (isDatetime.value) {
278
+ emitDatetime(isoDate)
279
+ return
280
+ }
281
+ if (precision.value === 'month') {
282
+ emit('update:modelValue', isoDate.slice(0, 7))
283
+ return
284
+ }
285
+ if (precision.value === 'year') {
286
+ emit('update:modelValue', isoDate.slice(0, 4))
287
+ return
288
+ }
289
+ emit('update:modelValue', isoDate)
290
+ }
291
+
292
+ function pickMonth(idx: number) {
293
+ viewMonth.value = idx
294
+ view.value = 'date'
295
+ if (precision.value === 'month') {
296
+ emit('update:modelValue', `${viewYear.value}-${String(idx + 1).padStart(2, '0')}`)
297
+ }
298
+ }
299
+
300
+ function pickYear(y: number) {
301
+ viewYear.value = y
302
+ view.value = 'month'
303
+ if (precision.value === 'year') {
304
+ emit('update:modelValue', String(y))
305
+ }
306
+ }
307
+
308
+ function pickToday() {
309
+ const now = new Date()
310
+ viewYear.value = now.getFullYear()
311
+ viewMonth.value = now.getMonth()
312
+ view.value = 'date'
313
+ emitValue(formatYmd(now))
314
+ }
315
+ </script>
316
+
317
+ <style lang="scss" scoped>
318
+ .vm-dt-cal {
319
+ box-sizing: border-box;
320
+ width: 280px;
321
+ padding: 8px 10px 6px;
322
+ border: 1px solid var(--border, #e8ebf5);
323
+ border-radius: 12px;
324
+ background: var(--card, #fff);
325
+ box-shadow: 0 8px 24px rgb(15 23 42 / 10%);
326
+ color: var(--foreground, #1f2937);
327
+ }
328
+
329
+ .vm-dt-cal__head {
330
+ display: flex;
331
+ align-items: center;
332
+ justify-content: space-between;
333
+ gap: 8px;
334
+ margin-bottom: 8px;
335
+ }
336
+
337
+ .vm-dt-cal__title {
338
+ display: inline-flex;
339
+ align-items: center;
340
+ gap: 2px;
341
+ margin: 0;
342
+ padding: 4px 6px;
343
+ border: 0;
344
+ border-radius: 8px;
345
+ background: transparent;
346
+ color: inherit;
347
+ font-size: 14px;
348
+ font-weight: 600;
349
+ cursor: pointer;
350
+
351
+ &:hover {
352
+ background: var(--muted, #f4f6fb);
353
+ }
354
+
355
+ i {
356
+ font-size: 16px;
357
+ color: var(--muted-foreground, #94a3b8);
358
+ transition: transform 0.15s ease;
359
+
360
+ &.is-up {
361
+ transform: rotate(180deg);
362
+ }
363
+ }
364
+ }
365
+
366
+ .vm-dt-cal__nav {
367
+ display: flex;
368
+ gap: 2px;
369
+ }
370
+
371
+ .vm-dt-cal__nav-btn {
372
+ display: inline-flex;
373
+ width: 24px;
374
+ height: 24px;
375
+ align-items: center;
376
+ justify-content: center;
377
+ border: 0;
378
+ border-radius: 6px;
379
+ background: transparent;
380
+ color: var(--muted-foreground, #64748b);
381
+ cursor: pointer;
382
+
383
+ &:hover {
384
+ background: var(--muted, #f4f6fb);
385
+ color: var(--foreground, #1f2937);
386
+ }
387
+ }
388
+
389
+ .vm-dt-cal__weeks {
390
+ display: grid;
391
+ grid-template-columns: repeat(7, 1fr);
392
+ margin-bottom: 4px;
393
+ }
394
+
395
+ .vm-dt-cal__week {
396
+ height: 28px;
397
+ color: var(--muted-foreground, #64748b);
398
+ font-size: 12px;
399
+ line-height: 28px;
400
+ text-align: center;
401
+ }
402
+
403
+ .vm-dt-cal__grid {
404
+ display: grid;
405
+ grid-template-columns: repeat(7, 1fr);
406
+ gap: 2px 0;
407
+ }
408
+
409
+ .vm-dt-cal__day {
410
+ width: 32px;
411
+ height: 32px;
412
+ margin: 0 auto;
413
+ padding: 0;
414
+ border: 0;
415
+ border-radius: 8px;
416
+ background: transparent;
417
+ color: inherit;
418
+ font-size: 13px;
419
+ line-height: 32px;
420
+ text-align: center;
421
+ cursor: pointer;
422
+
423
+ &.is-out {
424
+ color: var(--muted-foreground, #cbd5e1);
425
+ }
426
+
427
+ &.is-today:not(.is-on) {
428
+ color: var(--primary, var(--vm-brand, #4e5dff));
429
+ font-weight: 600;
430
+ }
431
+
432
+ &.is-on {
433
+ background: var(--primary, var(--vm-brand, #4e5dff));
434
+ color: #fff;
435
+ font-weight: 600;
436
+ }
437
+
438
+ &:hover:not(.is-on) {
439
+ background: color-mix(in srgb, var(--primary, #4e5dff) 10%, transparent);
440
+ }
441
+ }
442
+
443
+ .vm-dt-cal__body--months {
444
+ display: grid;
445
+ grid-template-columns: repeat(3, 1fr);
446
+ gap: 8px 6px;
447
+ padding: 4px 0 8px;
448
+ }
449
+
450
+ .vm-dt-cal__cell {
451
+ height: 36px;
452
+ border: 0;
453
+ border-radius: 8px;
454
+ background: transparent;
455
+ color: inherit;
456
+ font-size: 13px;
457
+ cursor: pointer;
458
+
459
+ &.is-on {
460
+ background: var(--primary, var(--vm-brand, #4e5dff));
461
+ color: #fff;
462
+ font-weight: 600;
463
+ }
464
+
465
+ &:hover:not(.is-on) {
466
+ background: var(--muted, #f4f6fb);
467
+ }
468
+ }
469
+
470
+ .vm-dt-cal__time {
471
+ display: flex;
472
+ gap: 8px;
473
+ align-items: center;
474
+ padding: 8px 2px 4px;
475
+ border-top: 1px solid var(--border, #e8ebf5);
476
+ margin-top: 4px;
477
+ }
478
+
479
+ .vm-dt-cal__time-field {
480
+ display: flex;
481
+ flex: 1;
482
+ align-items: center;
483
+ gap: 4px;
484
+ min-width: 0;
485
+ font-size: 12px;
486
+ color: var(--muted-foreground, #64748b);
487
+
488
+ input {
489
+ box-sizing: border-box;
490
+ width: 100%;
491
+ min-width: 0;
492
+ height: 28px;
493
+ padding: 0 6px;
494
+ border: 1px solid var(--border, #e8ebf5);
495
+ border-radius: 8px;
496
+ background: #fff;
497
+ color: var(--foreground, #1f2937);
498
+ font-size: 13px;
499
+ line-height: 28px;
500
+ text-align: center;
501
+ outline: none;
502
+ cursor: text;
503
+ user-select: text;
504
+ -webkit-user-select: text;
505
+ caret-color: var(--primary, var(--vm-brand, #4e5dff));
506
+
507
+ &:focus {
508
+ border-color: var(--primary, var(--vm-brand, #4e5dff));
509
+ background: #fff;
510
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary, #4e5dff) 18%, transparent);
511
+ }
512
+ }
513
+ }
514
+
515
+ .vm-dt-cal__foot {
516
+ display: flex;
517
+ align-items: center;
518
+ justify-content: space-between;
519
+ padding-top: 6px;
520
+ border-top: 1px solid var(--border, #e8ebf5);
521
+ margin-top: 4px;
522
+ }
523
+
524
+ .vm-dt-cal__link {
525
+ border: 0;
526
+ background: transparent;
527
+ color: var(--primary, var(--vm-brand, #4e5dff));
528
+ font-size: 13px;
529
+ cursor: pointer;
530
+ padding: 4px 6px;
531
+ border-radius: 6px;
532
+
533
+ &:hover {
534
+ background: color-mix(in srgb, var(--primary, #4e5dff) 8%, transparent);
535
+ }
536
+ }
537
+ </style>