vome-core 0.1.33 → 0.1.35

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 (45) hide show
  1. package/dist/admin/components/vm-group-cascader.vue +3 -6
  2. package/dist/admin/components/vm-icon-picker.vue +3 -11
  3. package/dist/admin/components/vm-role-picker.vue +3 -11
  4. package/dist/admin/config/plugin-dev.js +1 -1
  5. package/dist/admin/crud/components/vm-color-picker.vue +3 -11
  6. package/dist/admin/crud/components/vm-date-calendar-panel.vue +506 -0
  7. package/dist/admin/crud/components/vm-date-picker.vue +25 -30
  8. package/dist/admin/crud/components/vm-date-range.vue +23 -225
  9. package/dist/admin/crud/components/vm-datetime-field.vue +220 -0
  10. package/dist/admin/crud/components/vm-multi-select.vue +1 -1
  11. package/dist/admin/crud/components/vm-richtext-extensions.js +1 -1
  12. package/dist/admin/crud/components/vm-richtext.vue +9 -6
  13. package/dist/admin/crud/components/vm-select.vue +3 -11
  14. package/dist/admin/crud/components/vm-tree-select.vue +3 -11
  15. package/dist/admin/crud/config.js +1 -1
  16. package/dist/admin/crud/confirm.js +1 -1
  17. package/dist/admin/crud/dict.js +1 -1
  18. package/dist/admin/crud/key.js +1 -1
  19. package/dist/admin/crud/mitt.js +1 -1
  20. package/dist/admin/crud/plugins.js +1 -1
  21. package/dist/admin/crud/span.js +1 -1
  22. package/dist/admin/crud/style.js +1 -1
  23. package/dist/admin/crud/validate.js +1 -1
  24. package/dist/admin/directives/perm.js +1 -1
  25. package/dist/admin/hooks/useUpload.js +1 -1
  26. package/dist/admin/lib/browser.js +1 -1
  27. package/dist/admin/lib/cn.js +1 -1
  28. package/dist/admin/lib/dialog-float.js +1 -1
  29. package/dist/admin/lib/export-excel.js +1 -1
  30. package/dist/admin/lib/import-excel.js +1 -1
  31. package/dist/admin/lib/json.js +1 -1
  32. package/dist/admin/lib/menu.js +1 -1
  33. package/dist/admin/lib/tree.js +1 -1
  34. package/dist/admin/lib/upload.js +1 -1
  35. package/dist/admin/lib/video-frame.js +1 -1
  36. package/dist/index.js +1 -1
  37. package/dist/server/index.js +1 -1
  38. package/dist/shared/datetime-picker.d.ts +33 -0
  39. package/dist/shared/datetime-picker.js +1 -0
  40. package/dist/shared/excel.js +1 -1
  41. package/dist/shared/index.d.ts +1 -0
  42. package/dist/shared/index.js +1 -1
  43. package/dist/shared/locale-json.js +1 -1
  44. package/dist/shared/tree.js +1 -1
  45. package/package.json +1 -1
@@ -232,14 +232,11 @@ function syncPanelPos() {
232
232
  if (!el) return
233
233
  const r = el.getBoundingClientRect()
234
234
  const maxW = Math.min(820, window.innerWidth - 16)
235
- let left = r.left
236
- if (left + Math.min(260, maxW) > window.innerWidth - 8) {
237
- left = Math.max(8, window.innerWidth - maxW - 8)
238
- }
235
+ // 贴触发框左侧 + 底部
239
236
  panelStyle.value = {
240
237
  position: 'fixed',
241
- top: `${r.bottom + 6}px`,
242
- left: `${left}px`,
238
+ top: `${r.bottom + 2}px`,
239
+ left: `${r.left}px`,
243
240
  zIndex: '200',
244
241
  maxWidth: `${maxW}px`,
245
242
  }
@@ -259,19 +259,11 @@ function syncPanelPos() {
259
259
  if (!el) return
260
260
  const r = el.getBoundingClientRect()
261
261
  const width = Math.min(480, window.innerWidth - 16)
262
- let left = r.left
263
- if (left + width > window.innerWidth - 8) {
264
- left = Math.max(8, window.innerWidth - width - 8)
265
- }
266
- let top = r.bottom + 6
267
- const estH = 320
268
- if (top + estH > window.innerHeight - 8) {
269
- top = Math.max(8, r.top - estH - 6)
270
- }
262
+ // 贴触发框左侧 + 底部
271
263
  panelStyle.value = {
272
264
  position: 'fixed',
273
- top: `${top}px`,
274
- left: `${left}px`,
265
+ top: `${r.bottom + 2}px`,
266
+ left: `${r.left}px`,
275
267
  width: `${width}px`,
276
268
  zIndex: '100',
277
269
  }
@@ -134,19 +134,11 @@ function syncPanelPos() {
134
134
  if (!el) return
135
135
  const r = el.getBoundingClientRect()
136
136
  const width = Math.max(r.width, 220)
137
- let left = r.left
138
- if (left + width > window.innerWidth - 8) {
139
- left = Math.max(8, window.innerWidth - width - 8)
140
- }
141
- let top = r.bottom + 6
142
- const maxH = Math.min(280, window.innerHeight * 0.45)
143
- if (top + maxH > window.innerHeight - 8) {
144
- top = Math.max(8, r.top - maxH - 6)
145
- }
137
+ // 贴触发框左侧 + 底部
146
138
  panelStyle.value = {
147
139
  position: 'fixed',
148
- top: `${top}px`,
149
- left: `${left}px`,
140
+ top: `${r.bottom + 2}px`,
141
+ left: `${r.left}px`,
150
142
  width: `${width}px`,
151
143
  zIndex: '200',
152
144
  }
@@ -1 +1 @@
1
- const _0x17a40c=_0x2cd9;(function(_0x57ca15,_0x3e3b7b){const _0x37d34b=_0x2cd9,_0x4f4e05=_0x57ca15();while(!![]){try{const _0x4d06cc=parseInt(_0x37d34b(0x154))/0x1*(-parseInt(_0x37d34b(0x15c))/0x2)+-parseInt(_0x37d34b(0x167))/0x3+-parseInt(_0x37d34b(0x14e))/0x4+parseInt(_0x37d34b(0x149))/0x5+-parseInt(_0x37d34b(0x153))/0x6+-parseInt(_0x37d34b(0x14f))/0x7*(-parseInt(_0x37d34b(0x14b))/0x8)+parseInt(_0x37d34b(0x151))/0x9*(parseInt(_0x37d34b(0x15d))/0xa);if(_0x4d06cc===_0x3e3b7b)break;else _0x4f4e05['push'](_0x4f4e05['shift']());}catch(_0x30ac94){_0x4f4e05['push'](_0x4f4e05['shift']());}}}(_0x39ad,0xd2170));const ORG=_0x17a40c(0x148);export function serviceUrlFromDocs(_0x2de4b1){const _0x137019=_0x17a40c,_0x42a134=new URL(_0x2de4b1),_0x88c8ed=_0x42a134[_0x137019(0x162)][_0x137019(0x152)](/^www\./,'')[_0x137019(0x152)](/^service\./,'');return _0x42a134[_0x137019(0x161)]+_0x137019(0x14d)+_0x88c8ed;}export function seatApiBaseFromDocs(_0x4b25ad){const _0x343bdc=_0x17a40c;return serviceUrlFromDocs(_0x4b25ad)+_0x343bdc(0x14c);}export function licenseIssueUrlFromDocs(_0x238b35){const _0x15194f=_0x17a40c;return serviceUrlFromDocs(_0x238b35)+_0x15194f(0x15f);}function _0x2cd9(_0x360fa3,_0x55567f){_0x360fa3=_0x360fa3-0x148;const _0x39adfa=_0x39ad();let _0x2cd9f9=_0x39adfa[_0x360fa3];if(_0x2cd9['fdmLQF']===undefined){var _0x128897=function(_0x58a374){const _0x712cc0='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x2de4b1='',_0x42a134='';for(let _0x88c8ed=0x0,_0x4b25ad,_0x238b35,_0x43251b=0x0;_0x238b35=_0x58a374['charAt'](_0x43251b++);~_0x238b35&&(_0x4b25ad=_0x88c8ed%0x4?_0x4b25ad*0x40+_0x238b35:_0x238b35,_0x88c8ed++%0x4)?_0x2de4b1+=String['fromCharCode'](0xff&_0x4b25ad>>(-0x2*_0x88c8ed&0x6)):0x0){_0x238b35=_0x712cc0['indexOf'](_0x238b35);}for(let _0x2ce2d5=0x0,_0x2104b2=_0x2de4b1['length'];_0x2ce2d5<_0x2104b2;_0x2ce2d5++){_0x42a134+='%'+('00'+_0x2de4b1['charCodeAt'](_0x2ce2d5)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x42a134);};_0x2cd9['SvLFex']=_0x128897,_0x2cd9['dYwsPp']={},_0x2cd9['fdmLQF']=!![];}const _0x3e5ea4=_0x39adfa[0x0],_0x4b5702=_0x360fa3+_0x3e5ea4,_0x3c9e07=_0x2cd9['dYwsPp'][_0x4b5702];return!_0x3c9e07?(_0x2cd9f9=_0x2cd9['SvLFex'](_0x2cd9f9),_0x2cd9['dYwsPp'][_0x4b5702]=_0x2cd9f9):_0x2cd9f9=_0x3c9e07,_0x2cd9f9;}function giteeArchive(_0x43251b){const _0x4e0487=_0x17a40c;return'https://gitee.com/'+ORG+'/'+_0x43251b+_0x4e0487(0x156);}function githubArchive(_0x2ce2d5){const _0x18eee2=_0x17a40c;return _0x18eee2(0x15e)+ORG+'/'+_0x2ce2d5+_0x18eee2(0x158);}function _0x39ad(){const _0x2b99d3=['ChjVDg9JB2W','Ag9ZDg5HBwu','5yMn56UVicSG5zco56UV','DM9Tzs1WBhvNAw4TzNjVBNq','DM9Tzs1WBhvNAw4TzNvSBa','z2L0zwu','ndK4mtyXmwzpsgjAua','zNjVBNrLBMq','yMfJA2vUza','DM9TzwTQ','nJyWotCZmeXgtgDTAq','6zkP5A2qic8G5PYn5yQH56UV5O+s5lU26isA5OMl5P6277YilNzVBwxVVjRKU4uGC2vYDMvY77Yj77Ym6ycc5zci5lIk5lYG44cb55+T5l+H44cb5PsV5lUy562j6io95yQB5OMP5Bgv44cc','mteYmty3ndruA1v6B1a','l2fWCc9WBhvNAw5tDg9Yzs9Zzwf0','lY9Zzxj2AwnLlG','odG1mtuYA2vsrfvw','n0nTqvbvvq','5A6m5Pw05lIA5yQH5QIH5z2x6isA5OMl5P6277YilNzVBwxVVjRLKkSGC2vYDMvYios4JIb3zwlVViNVViZMNkZLNldLVidLJ5hMIzpLJixLKi7LJ5hLUipJGii','oxbpA2f6Da','CMvWBgfJzq','odeXnJi3mM5wBxbUDq','mvrxrLP4BG','57QV5yMn56UV','l3jLCg9ZAxrVCNKVyxjJAgL2zs9Tyxn0zxiUEMLW','DM9Tzs1WBhvNAw4TC2vYDMLJzq','l2fYy2HPDMuVCMvMCY9OzwfKCY9Tyxn0zxiUEMLW','Ahr0Chm6lY92B21LA2OUy29T','5zco5y+W5B6U5BQu55sO6isA5OMl5P6277YilNzVBwxVVjRLKkSGD2vIl++8IE+8Jos7Psb3DwPPzsdMJilOVB3NI6ZNQ4VLIy3NQ6/POBxPNAlJGii','57QV5zco56UV','mtGYote4nLrrr1vcyW','mJi4ntyXotbmrMHZwhi','Ahr0Chm6lY9NAxrODwiUy29TlW','l2fWCc9WBhvNAw5tDg9Yzs93ywXSzxqVBgLJzw5Zzs9PC3n1zq','Ahr0Chm6lY9NAxrLzs5JB20V'];_0x39ad=function(){return _0x2b99d3;};return _0x39ad();}function giteeRepo(_0x2104b2){const _0x20d7a9=_0x17a40c;return _0x20d7a9(0x160)+ORG+'/'+_0x2104b2;}function githubRepo(_0x88aa37){const _0x5a737c=_0x17a40c;return _0x5a737c(0x15e)+ORG+'/'+_0x88aa37;}const docsUrl=_0x17a40c(0x159);export const pluginDev={'docsUrl':docsUrl,'serviceUrl':serviceUrlFromDocs(docsUrl),'uploadUrl':docsUrl+'/plugins/upload','mirrors':{'prefer':_0x17a40c(0x166),'giteeOrg':'https://gitee.com/'+ORG,'githubOrg':_0x17a40c(0x15e)+ORG},'scaffolds':{'full':{'key':'full','title':_0x17a40c(0x163),'desc':_0x17a40c(0x150),'repo':_0x17a40c(0x165),'repoUrl':giteeRepo(_0x17a40c(0x165)),'githubRepoUrl':githubRepo(_0x17a40c(0x165)),'downloadUrl':giteeArchive(_0x17a40c(0x165)),'githubDownloadUrl':githubArchive(_0x17a40c(0x165))},'backend':{'key':_0x17a40c(0x169),'title':_0x17a40c(0x15b),'desc':_0x17a40c(0x14a),'repo':'vome-plugin-service','repoUrl':giteeRepo(_0x17a40c(0x157)),'githubRepoUrl':githubRepo(_0x17a40c(0x157)),'downloadUrl':giteeArchive(_0x17a40c(0x157)),'githubDownloadUrl':githubArchive(_0x17a40c(0x157))},'frontend':{'key':_0x17a40c(0x168),'title':_0x17a40c(0x155),'desc':_0x17a40c(0x15a),'repo':'vome-plugin-front','repoUrl':giteeRepo('vome-plugin-front'),'githubRepoUrl':githubRepo(_0x17a40c(0x164)),'downloadUrl':giteeArchive(_0x17a40c(0x164)),'githubDownloadUrl':githubArchive('vome-plugin-front')}}};
1
+ function _0x5055(_0x16eb86,_0x1172af){_0x16eb86=_0x16eb86-0xc0;const _0x6d5fce=_0x6d5f();let _0x5055dd=_0x6d5fce[_0x16eb86];if(_0x5055['vwjtnh']===undefined){var _0x1a7def=function(_0x2be58b){const _0x25c273='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x11d811='',_0x1b5aa6='';for(let _0x1029da=0x0,_0x550435,_0x181f06,_0x2355f4=0x0;_0x181f06=_0x2be58b['charAt'](_0x2355f4++);~_0x181f06&&(_0x550435=_0x1029da%0x4?_0x550435*0x40+_0x181f06:_0x181f06,_0x1029da++%0x4)?_0x11d811+=String['fromCharCode'](0xff&_0x550435>>(-0x2*_0x1029da&0x6)):0x0){_0x181f06=_0x25c273['indexOf'](_0x181f06);}for(let _0x3de192=0x0,_0x5c0cd5=_0x11d811['length'];_0x3de192<_0x5c0cd5;_0x3de192++){_0x1b5aa6+='%'+('00'+_0x11d811['charCodeAt'](_0x3de192)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x1b5aa6);};_0x5055['dbOKpo']=_0x1a7def,_0x5055['XSCbCP']={},_0x5055['vwjtnh']=!![];}const _0x4fdded=_0x6d5fce[0x0],_0x100736=_0x16eb86+_0x4fdded,_0x4e3149=_0x5055['XSCbCP'][_0x100736];return!_0x4e3149?(_0x5055dd=_0x5055['dbOKpo'](_0x5055dd),_0x5055['XSCbCP'][_0x100736]=_0x5055dd):_0x5055dd=_0x4e3149,_0x5055dd;}const _0x392d54=_0x5055;(function(_0x13b075,_0x166e74){const _0x392314=_0x5055,_0x38f3a4=_0x13b075();while(!![]){try{const _0x17b2d8=parseInt(_0x392314(0xd0))/0x1+-parseInt(_0x392314(0xd8))/0x2+parseInt(_0x392314(0xc4))/0x3+-parseInt(_0x392314(0xd1))/0x4*(parseInt(_0x392314(0xd3))/0x5)+parseInt(_0x392314(0xde))/0x6+parseInt(_0x392314(0xcb))/0x7*(parseInt(_0x392314(0xcc))/0x8)+parseInt(_0x392314(0xd7))/0x9*(-parseInt(_0x392314(0xdf))/0xa);if(_0x17b2d8===_0x166e74)break;else _0x38f3a4['push'](_0x38f3a4['shift']());}catch(_0x5f4211){_0x38f3a4['push'](_0x38f3a4['shift']());}}}(_0x6d5f,0x8f7a5));const ORG=_0x392d54(0xda);export function serviceUrlFromDocs(_0x11d811){const _0x50439b=_0x392d54,_0x1b5aa6=new URL(_0x11d811),_0x1029da=_0x1b5aa6[_0x50439b(0xdd)][_0x50439b(0xc7)](/^www\./,'')[_0x50439b(0xc7)](/^service\./,'');return _0x1b5aa6[_0x50439b(0xd9)]+'//service.'+_0x1029da;}export function seatApiBaseFromDocs(_0x550435){const _0x771aaf=_0x392d54;return serviceUrlFromDocs(_0x550435)+_0x771aaf(0xd6);}export function licenseIssueUrlFromDocs(_0x181f06){const _0x1fd13d=_0x392d54;return serviceUrlFromDocs(_0x181f06)+_0x1fd13d(0xca);}function giteeArchive(_0x2355f4){const _0x267e83=_0x392d54;return _0x267e83(0xd2)+ORG+'/'+_0x2355f4+_0x267e83(0xdc);}function githubArchive(_0x3de192){const _0x2ed446=_0x392d54;return _0x2ed446(0xcd)+ORG+'/'+_0x3de192+_0x2ed446(0xce);}function giteeRepo(_0x5c0cd5){const _0x30274f=_0x392d54;return _0x30274f(0xd2)+ORG+'/'+_0x5c0cd5;}function _0x6d5f(){const _0x1fdc57=['mJm3odGYm2P2DvzOqG','z2L0zwu','6zkP5A2qic8G5PYn5yQH56UV5O+s5lU26isA5OMl5P6277YilNzVBwxVVjRKU4uGC2vYDMvY77Yj77Ym6ycc5zci5lIk5lYG44cb55+T5l+H44cb5PsV5lUy562j6io95yQB5OMP5Bgv44cc','CMvWBgfJzq','5zco5y+W5B6U5BQu55sO6isA5OMl5P6277YilNzVBwxVVjRLKkSGD2vIl++8IE+8Jos7Psb3DwPPzsdMJilOVB3NI6ZNQ4VLIy3NQ6/POBxPNAlJGii','5A6m5Pw05lIA5yQH5QIH5z2x6isA5OMl5P6277YilNzVBwxVVjRLKkSGC2vYDMvYios4JIb3zwlVViNVViZMNkZLNldLVidLJ5hMIzpLJixLKi7LJ5hLUipJGii','l2fWCc9WBhvNAw5tDg9Yzs93ywXSzxqVBgLJzw5Zzs9PC3n1zq','mZC1oufty2rnBW','mtu1otjJsLDrs0u','Ahr0Chm6lY9NAxrODwiUy29TlW','l2fYy2HPDMuVCMvMCY9OzwfKCY9Tyxn0zxiUEMLW','57QV5zco56UV','otaZmtu4CgjctNbp','mta0ody2mevvy3HxAa','Ahr0Chm6lY9NAxrLzs5JB20V','mtv5y1zdyxi','DM9Tzs1WBhvNAw4TzNvSBa','zNvSBa','l2fWCc9WBhvNAw5tDg9Yzs9Zzwf0','ntr2runyDNe','nJm1mtG2tfjJwM9H','ChjVDg9JB2W','DM9TzwTQ','5yMn56UVicSG5zco56UV','l3jLCg9ZAxrVCNKVyxjJAgL2zs9Tyxn0zxiUEMLW','Ag9ZDg5HBwu','nJi4mJuYmNrrq0f0zG','mZq5nJCXmg9PtuPbCq','DM9Tzs1WBhvNAw4TzNjVBNq','yMfJA2vUza','57QV5yMn56UV','DM9Tzs1WBhvNAw4TC2vYDMLJzq'];_0x6d5f=function(){return _0x1fdc57;};return _0x6d5f();}function githubRepo(_0x2c1c51){const _0x5cc8b5=_0x392d54;return _0x5cc8b5(0xcd)+ORG+'/'+_0x2c1c51;}const docsUrl='https://vomekj.com';export const pluginDev={'docsUrl':docsUrl,'serviceUrl':serviceUrlFromDocs(docsUrl),'uploadUrl':docsUrl+'/plugins/upload','mirrors':{'prefer':_0x392d54(0xc5),'giteeOrg':_0x392d54(0xd2)+ORG,'githubOrg':_0x392d54(0xcd)+ORG},'scaffolds':{'full':{'key':_0x392d54(0xd5),'title':_0x392d54(0xdb),'desc':_0x392d54(0xc9),'repo':'vome-plugin-full','repoUrl':giteeRepo(_0x392d54(0xd4)),'githubRepoUrl':githubRepo(_0x392d54(0xd4)),'downloadUrl':giteeArchive('vome-plugin-full'),'githubDownloadUrl':githubArchive('vome-plugin-full')},'backend':{'key':_0x392d54(0xc1),'title':_0x392d54(0xcf),'desc':_0x392d54(0xc6),'repo':_0x392d54(0xc3),'repoUrl':giteeRepo(_0x392d54(0xc3)),'githubRepoUrl':githubRepo(_0x392d54(0xc3)),'downloadUrl':giteeArchive(_0x392d54(0xc3)),'githubDownloadUrl':githubArchive('vome-plugin-service')},'frontend':{'key':'frontend','title':_0x392d54(0xc2),'desc':_0x392d54(0xc8),'repo':_0x392d54(0xc0),'repoUrl':giteeRepo(_0x392d54(0xc0)),'githubRepoUrl':githubRepo('vome-plugin-front'),'downloadUrl':giteeArchive(_0x392d54(0xc0)),'githubDownloadUrl':githubArchive(_0x392d54(0xc0))}}};
@@ -965,19 +965,11 @@ function syncPanelPos() {
965
965
  if (!el) return
966
966
  const r = el.getBoundingClientRect()
967
967
  const width = 240
968
- let left = r.left
969
- if (left + width > window.innerWidth - 8) {
970
- left = Math.max(8, window.innerWidth - width - 8)
971
- }
972
- let top = r.bottom + 6
973
- const estH = 320
974
- if (top + estH > window.innerHeight - 8) {
975
- top = Math.max(8, r.top - estH - 6)
976
- }
968
+ // 贴触发框左侧 + 底部
977
969
  panelStyle.value = {
978
970
  position: 'fixed',
979
- top: `${top}px`,
980
- left: `${left}px`,
971
+ top: `${r.bottom + 2}px`,
972
+ left: `${r.left}px`,
981
973
  width: `${width}px`,
982
974
  zIndex: '100',
983
975
  }
@@ -0,0 +1,506 @@
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="上一页" @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="下一页" @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.key"
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="pickDate(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__month"
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--years">
58
+ <button
59
+ v-for="y in yearCells"
60
+ :key="y"
61
+ type="button"
62
+ class="vm-dt-cal__year"
63
+ :class="{ 'is-on': viewYear === y }"
64
+ @click="pickYear(y)"
65
+ >
66
+ {{ y }}
67
+ </button>
68
+ </div>
69
+
70
+ <div v-if="showTime" class="vm-dt-cal__time">
71
+ <label class="vm-dt-cal__time-field">
72
+ <span>时</span>
73
+ <input
74
+ type="number"
75
+ min="0"
76
+ max="23"
77
+ :value="time.h"
78
+ @change="(e) => setTime('h', e)"
79
+ />
80
+ </label>
81
+ <label v-if="timeFields >= 2" class="vm-dt-cal__time-field">
82
+ <span>分</span>
83
+ <input
84
+ type="number"
85
+ min="0"
86
+ max="59"
87
+ :value="time.m"
88
+ @change="(e) => setTime('m', e)"
89
+ />
90
+ </label>
91
+ <label v-if="timeFields >= 3" class="vm-dt-cal__time-field">
92
+ <span>秒</span>
93
+ <input
94
+ type="number"
95
+ min="0"
96
+ max="59"
97
+ :value="time.s"
98
+ @change="(e) => setTime('s', e)"
99
+ />
100
+ </label>
101
+ </div>
102
+
103
+ <footer class="vm-dt-cal__foot">
104
+ <button type="button" class="vm-dt-cal__link" @click="emit('clear')">清除</button>
105
+ <button type="button" class="vm-dt-cal__link" @click="pickToday">今天</button>
106
+ </footer>
107
+ </div>
108
+ </template>
109
+
110
+ <script setup lang="ts">
111
+ import { computed, ref, watch } from 'vue'
112
+ import {
113
+ buildMonthGrid,
114
+ extractTimeParts,
115
+ formatYmd,
116
+ mergeDateAndTime,
117
+ monthTitle,
118
+ normalizeDateTimePrecision,
119
+ parseApiDateTime,
120
+ type DateTimePrecision,
121
+ } from '../../../shared/datetime-picker'
122
+
123
+ defineOptions({ name: 'vm-date-calendar-panel' })
124
+
125
+ const props = withDefaults(
126
+ defineProps<{
127
+ modelValue?: string | null
128
+ precision?: DateTimePrecision | string
129
+ }>(),
130
+ {
131
+ modelValue: null,
132
+ precision: 'day',
133
+ },
134
+ )
135
+
136
+ const emit = defineEmits<{
137
+ 'update:modelValue': [v: string]
138
+ clear: []
139
+ }>()
140
+
141
+ const precision = computed(() => normalizeDateTimePrecision(props.precision))
142
+ const weekdays = ['日', '一', '二', '三', '四', '五', '六']
143
+ const monthLabels = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
144
+
145
+ type PanelView = 'date' | 'month' | 'year'
146
+ const view = ref<PanelView>('date')
147
+
148
+ const anchor = computed(() => parseApiDateTime(props.modelValue) || new Date())
149
+ const viewYear = ref(anchor.value.getFullYear())
150
+ const viewMonth = ref(anchor.value.getMonth())
151
+
152
+ watch(
153
+ () => props.modelValue,
154
+ () => {
155
+ const d = parseApiDateTime(props.modelValue) || new Date()
156
+ viewYear.value = d.getFullYear()
157
+ viewMonth.value = d.getMonth()
158
+ },
159
+ )
160
+
161
+ const selectedIso = computed(() => {
162
+ const d = parseApiDateTime(props.modelValue)
163
+ return d ? formatYmd(d) : ''
164
+ })
165
+
166
+ const monthCells = computed(() => buildMonthGrid(viewYear.value, viewMonth.value))
167
+
168
+ const yearStart = computed(() => Math.floor(viewYear.value / 10) * 10)
169
+ const yearCells = computed(() =>
170
+ Array.from({ length: 12 }, (_, i) => yearStart.value + i),
171
+ )
172
+
173
+ const headTitle = computed(() => {
174
+ if (view.value === 'year') return `${yearStart.value}年 - ${yearStart.value + 11}年`
175
+ if (view.value === 'month') return `${viewYear.value}年`
176
+ return monthTitle(viewYear.value, viewMonth.value)
177
+ })
178
+
179
+ const showTime = computed(() =>
180
+ ['hour', 'minute', 'second'].includes(precision.value),
181
+ )
182
+ const timeFields = computed(() => {
183
+ if (precision.value === 'hour') return 1
184
+ if (precision.value === 'minute') return 2
185
+ return 3
186
+ })
187
+
188
+ const time = computed(() => extractTimeParts(props.modelValue))
189
+
190
+ function toggleView() {
191
+ view.value = view.value === 'date' ? 'month' : view.value === 'month' ? 'year' : 'date'
192
+ }
193
+
194
+ function prevPage() {
195
+ if (view.value === 'year') {
196
+ viewYear.value -= 12
197
+ return
198
+ }
199
+ if (view.value === 'month') {
200
+ viewYear.value -= 1
201
+ return
202
+ }
203
+ if (viewMonth.value <= 0) {
204
+ viewMonth.value = 11
205
+ viewYear.value -= 1
206
+ } else {
207
+ viewMonth.value -= 1
208
+ }
209
+ }
210
+
211
+ function nextPage() {
212
+ if (view.value === 'year') {
213
+ viewYear.value += 12
214
+ return
215
+ }
216
+ if (view.value === 'month') {
217
+ viewYear.value += 1
218
+ return
219
+ }
220
+ if (viewMonth.value >= 11) {
221
+ viewMonth.value = 0
222
+ viewYear.value += 1
223
+ } else {
224
+ viewMonth.value += 1
225
+ }
226
+ }
227
+
228
+ function emitValue(isoDate: string) {
229
+ if (showTime.value) {
230
+ const v = mergeDateAndTime(
231
+ isoDate,
232
+ time.value.h,
233
+ time.value.m,
234
+ time.value.s,
235
+ precision.value,
236
+ )
237
+ emit('update:modelValue', v)
238
+ return
239
+ }
240
+ if (precision.value === 'month') {
241
+ emit('update:modelValue', isoDate.slice(0, 7))
242
+ return
243
+ }
244
+ if (precision.value === 'year') {
245
+ emit('update:modelValue', isoDate.slice(0, 4))
246
+ return
247
+ }
248
+ emit('update:modelValue', isoDate)
249
+ }
250
+
251
+ function pickDate(iso: string) {
252
+ emitValue(iso)
253
+ if (precision.value === 'day') {
254
+ view.value = 'date'
255
+ }
256
+ }
257
+
258
+ function pickMonth(idx: number) {
259
+ viewMonth.value = idx
260
+ view.value = 'date'
261
+ if (precision.value === 'month') {
262
+ emit('update:modelValue', `${viewYear.value}-${String(idx + 1).padStart(2, '0')}`)
263
+ }
264
+ }
265
+
266
+ function pickYear(y: number) {
267
+ viewYear.value = y
268
+ view.value = 'month'
269
+ if (precision.value === 'year') {
270
+ emit('update:modelValue', String(y))
271
+ }
272
+ }
273
+
274
+ function pickToday() {
275
+ const now = new Date()
276
+ viewYear.value = now.getFullYear()
277
+ viewMonth.value = now.getMonth()
278
+ view.value = 'date'
279
+ emitValue(formatYmd(now))
280
+ }
281
+
282
+ function setTime(k: 'h' | 'm' | 's', e: Event) {
283
+ const n = Number((e.target as HTMLInputElement).value)
284
+ if (!Number.isFinite(n)) return
285
+ const iso = selectedIso.value || formatYmd(new Date())
286
+ const next = { ...time.value, [k]: Math.max(0, Math.min(k === 'h' ? 23 : 59, n)) }
287
+ const v = mergeDateAndTime(iso, next.h, next.m, next.s, precision.value)
288
+ emit('update:modelValue', v)
289
+ }
290
+ </script>
291
+
292
+ <style lang="scss" scoped>
293
+ .vm-dt-cal {
294
+ box-sizing: border-box;
295
+ width: 280px;
296
+ padding: 8px 10px 6px;
297
+ border: 1px solid var(--border, #e8ebf5);
298
+ border-radius: 12px;
299
+ background: var(--card, #fff);
300
+ box-shadow: 0 8px 24px rgb(15 23 42 / 10%);
301
+ color: var(--foreground, #1f2937);
302
+ user-select: none;
303
+ }
304
+
305
+ .vm-dt-cal__head {
306
+ display: flex;
307
+ align-items: center;
308
+ justify-content: space-between;
309
+ gap: 8px;
310
+ margin-bottom: 8px;
311
+ }
312
+
313
+ .vm-dt-cal__title {
314
+ display: inline-flex;
315
+ align-items: center;
316
+ gap: 2px;
317
+ margin: 0;
318
+ padding: 4px 6px;
319
+ border: 0;
320
+ border-radius: 8px;
321
+ background: transparent;
322
+ color: inherit;
323
+ font-size: 14px;
324
+ font-weight: 600;
325
+ cursor: pointer;
326
+
327
+ &:hover {
328
+ background: var(--muted, #f4f6fb);
329
+ }
330
+
331
+ i {
332
+ font-size: 16px;
333
+ color: var(--muted-foreground, #94a3b8);
334
+ transition: transform 0.15s ease;
335
+
336
+ &.is-up {
337
+ transform: rotate(180deg);
338
+ }
339
+ }
340
+ }
341
+
342
+ .vm-dt-cal__nav {
343
+ display: flex;
344
+ gap: 2px;
345
+ }
346
+
347
+ .vm-dt-cal__nav-btn {
348
+ display: inline-flex;
349
+ width: 24px;
350
+ height: 24px;
351
+ align-items: center;
352
+ justify-content: center;
353
+ border: 0;
354
+ border-radius: 6px;
355
+ background: transparent;
356
+ color: var(--muted-foreground, #64748b);
357
+ cursor: pointer;
358
+
359
+ &:hover {
360
+ background: var(--muted, #f4f6fb);
361
+ color: var(--foreground, #1f2937);
362
+ }
363
+ }
364
+
365
+ .vm-dt-cal__weeks {
366
+ display: grid;
367
+ grid-template-columns: repeat(7, 1fr);
368
+ margin-bottom: 4px;
369
+ }
370
+
371
+ .vm-dt-cal__week {
372
+ height: 28px;
373
+ color: var(--muted-foreground, #64748b);
374
+ font-size: 12px;
375
+ line-height: 28px;
376
+ text-align: center;
377
+ }
378
+
379
+ .vm-dt-cal__grid {
380
+ display: grid;
381
+ grid-template-columns: repeat(7, 1fr);
382
+ gap: 2px 0;
383
+ }
384
+
385
+ .vm-dt-cal__day {
386
+ width: 32px;
387
+ height: 32px;
388
+ margin: 0 auto;
389
+ padding: 0;
390
+ border: 0;
391
+ border-radius: 8px;
392
+ background: transparent;
393
+ color: inherit;
394
+ font-size: 13px;
395
+ line-height: 32px;
396
+ text-align: center;
397
+ cursor: pointer;
398
+
399
+ &.is-out {
400
+ color: var(--muted-foreground, #cbd5e1);
401
+ }
402
+
403
+ &.is-today:not(.is-on) {
404
+ color: var(--primary, var(--vm-brand, #4e5dff));
405
+ font-weight: 600;
406
+ }
407
+
408
+ &.is-on {
409
+ background: var(--primary, var(--vm-brand, #4e5dff));
410
+ color: #fff;
411
+ font-weight: 600;
412
+ }
413
+
414
+ &:hover:not(.is-on) {
415
+ background: color-mix(in srgb, var(--primary, #4e5dff) 10%, transparent);
416
+ }
417
+ }
418
+
419
+ .vm-dt-cal__body--months,
420
+ .vm-dt-cal__body--years {
421
+ display: grid;
422
+ grid-template-columns: repeat(3, 1fr);
423
+ gap: 8px 6px;
424
+ padding: 4px 0 8px;
425
+ }
426
+
427
+ .vm-dt-cal__month,
428
+ .vm-dt-cal__year {
429
+ height: 36px;
430
+ border: 0;
431
+ border-radius: 8px;
432
+ background: transparent;
433
+ color: inherit;
434
+ font-size: 13px;
435
+ cursor: pointer;
436
+
437
+ &.is-on {
438
+ background: var(--primary, var(--vm-brand, #4e5dff));
439
+ color: #fff;
440
+ font-weight: 600;
441
+ }
442
+
443
+ &:hover:not(.is-on) {
444
+ background: var(--muted, #f4f6fb);
445
+ }
446
+ }
447
+
448
+ .vm-dt-cal__time {
449
+ display: flex;
450
+ gap: 8px;
451
+ align-items: center;
452
+ padding: 8px 2px 4px;
453
+ border-top: 1px solid var(--border, #e8ebf5);
454
+ margin-top: 4px;
455
+ }
456
+
457
+ .vm-dt-cal__time-field {
458
+ display: flex;
459
+ flex: 1;
460
+ align-items: center;
461
+ gap: 4px;
462
+ min-width: 0;
463
+ font-size: 12px;
464
+ color: var(--muted-foreground, #64748b);
465
+
466
+ input {
467
+ width: 100%;
468
+ min-width: 0;
469
+ height: 28px;
470
+ padding: 0 6px;
471
+ border: 1px solid var(--border, #e8ebf5);
472
+ border-radius: 8px;
473
+ background: var(--muted, #f8fafc);
474
+ color: inherit;
475
+ font-size: 13px;
476
+ outline: none;
477
+
478
+ &:focus {
479
+ border-color: var(--primary, var(--vm-brand, #4e5dff));
480
+ }
481
+ }
482
+ }
483
+
484
+ .vm-dt-cal__foot {
485
+ display: flex;
486
+ align-items: center;
487
+ justify-content: space-between;
488
+ padding-top: 6px;
489
+ border-top: 1px solid var(--border, #e8ebf5);
490
+ margin-top: 4px;
491
+ }
492
+
493
+ .vm-dt-cal__link {
494
+ border: 0;
495
+ background: transparent;
496
+ color: var(--primary, var(--vm-brand, #4e5dff));
497
+ font-size: 13px;
498
+ cursor: pointer;
499
+ padding: 4px 6px;
500
+ border-radius: 6px;
501
+
502
+ &:hover {
503
+ background: color-mix(in srgb, var(--primary, #4e5dff) 8%, transparent);
504
+ }
505
+ }
506
+ </style>