vome-core 0.1.34 → 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 (44) 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-select.vue +3 -11
  13. package/dist/admin/crud/components/vm-tree-select.vue +3 -11
  14. package/dist/admin/crud/config.js +1 -1
  15. package/dist/admin/crud/confirm.js +1 -1
  16. package/dist/admin/crud/dict.js +1 -1
  17. package/dist/admin/crud/key.js +1 -1
  18. package/dist/admin/crud/mitt.js +1 -1
  19. package/dist/admin/crud/plugins.js +1 -1
  20. package/dist/admin/crud/span.js +1 -1
  21. package/dist/admin/crud/style.js +1 -1
  22. package/dist/admin/crud/validate.js +1 -1
  23. package/dist/admin/directives/perm.js +1 -1
  24. package/dist/admin/hooks/useUpload.js +1 -1
  25. package/dist/admin/lib/browser.js +1 -1
  26. package/dist/admin/lib/cn.js +1 -1
  27. package/dist/admin/lib/dialog-float.js +1 -1
  28. package/dist/admin/lib/export-excel.js +1 -1
  29. package/dist/admin/lib/import-excel.js +1 -1
  30. package/dist/admin/lib/json.js +1 -1
  31. package/dist/admin/lib/menu.js +1 -1
  32. package/dist/admin/lib/tree.js +1 -1
  33. package/dist/admin/lib/upload.js +1 -1
  34. package/dist/admin/lib/video-frame.js +1 -1
  35. package/dist/index.js +1 -1
  36. package/dist/server/index.js +1 -1
  37. package/dist/shared/datetime-picker.d.ts +33 -0
  38. package/dist/shared/datetime-picker.js +1 -0
  39. package/dist/shared/excel.js +1 -1
  40. package/dist/shared/index.d.ts +1 -0
  41. package/dist/shared/index.js +1 -1
  42. package/dist/shared/locale-json.js +1 -1
  43. package/dist/shared/tree.js +1 -1
  44. 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 _0x5435d3=_0xe66b;(function(_0x323267,_0x56447a){const _0x144203=_0xe66b,_0x103280=_0x323267();while(!![]){try{const _0x4818d1=-parseInt(_0x144203(0x93))/0x1+parseInt(_0x144203(0x8b))/0x2+parseInt(_0x144203(0x85))/0x3+-parseInt(_0x144203(0xa3))/0x4*(-parseInt(_0x144203(0x90))/0x5)+-parseInt(_0x144203(0x86))/0x6*(parseInt(_0x144203(0x9a))/0x7)+parseInt(_0x144203(0x87))/0x8*(-parseInt(_0x144203(0xa4))/0x9)+parseInt(_0x144203(0x8e))/0xa;if(_0x4818d1===_0x56447a)break;else _0x103280['push'](_0x103280['shift']());}catch(_0x443443){_0x103280['push'](_0x103280['shift']());}}}(_0x315b,0x7618e));const ORG=_0x5435d3(0x9b);export function serviceUrlFromDocs(_0x651b39){const _0x1b46ee=_0x5435d3,_0x97439b=new URL(_0x651b39),_0x485329=_0x97439b[_0x1b46ee(0x94)][_0x1b46ee(0x9d)](/^www\./,'')[_0x1b46ee(0x9d)](/^service\./,'');return _0x97439b['protocol']+'//service.'+_0x485329;}export function seatApiBaseFromDocs(_0x3f20da){const _0xa55d1b=_0x5435d3;return serviceUrlFromDocs(_0x3f20da)+_0xa55d1b(0x8d);}function _0x315b(){const _0x52b1b0=['DM9Tzs1WBhvNAw4TzNvSBa','5zco5y+W5B6U5BQu55sO6isA5OMl5P6277YilNzVBwxVVjRLKkSGD2vIl++8IE+8Jos7Psb3DwPPzsdMJilOVB3NI6ZNQ4VLIy3NQ6/POBxPNAlJGii','nJi3odC5D25uyLzY','Ag9ZDg5HBwu','Ahr0Chm6lY92B21LA2OUy29T','l2fYy2HPDMuVCMvMCY9OzwfKCY9Tyxn0zxiUEMLW','zNvSBa','z2L0zwu','57QV5zco56UV','ndLzzNbhs3y','DM9TzwTQ','5yMn56UVicSG5zco56UV','CMvWBgfJzq','DM9Tzs1WBhvNAw4TC2vYDMLJzq','l3bSDwDPBNmVDxbSB2fK','DM9Tzs1WBhvNAw4TzNjVBNq','l3jLCg9ZAxrVCNKVyxjJAgL2zs9Tyxn0zxiUEMLW','5A6m5Pw05lIA5yQH5QIH5z2x6isA5OMl5P6277YilNzVBwxVVjRLKkSGC2vYDMvYios4JIb3zwlVViNVViZMNkZLNldLVidLJ5hMIzpLJixLKi7LJ5hLUipJGii','mty3mJuXnKDQy3DxyW','mJDHB3bgsM0','mtG0ntyXmMnkCM95uq','ntu3ntq0r0zbDfn0','mtuXmZGZmLjXwhrcqq','zNjVBNrLBMq','Ahr0Chm6lY9NAxrODwiUy29TlW','yMfJA2vUza','mZCXmtm4sertAgHo','Ahr0Chm6lY9NAxrLzs5JB20V','l2fWCc9WBhvNAw5tDg9Yzs9Zzwf0','mteXmdG1odbvAMj0zvu','57QV5yMn56UV','nxb4ve5HAW'];_0x315b=function(){return _0x52b1b0;};return _0x315b();}export function licenseIssueUrlFromDocs(_0x3f86f1){return serviceUrlFromDocs(_0x3f86f1)+'/app/pluginStore/wallet/license/issue';}function giteeArchive(_0x33b110){const _0x428a84=_0x5435d3;return _0x428a84(0x8c)+ORG+'/'+_0x33b110+_0x428a84(0xa1);}function githubArchive(_0x2431cb){const _0x3410cf=_0x5435d3;return _0x3410cf(0x89)+ORG+'/'+_0x2431cb+_0x3410cf(0x96);}function giteeRepo(_0x52a54a){return'https://gitee.com/'+ORG+'/'+_0x52a54a;}function _0xe66b(_0x3bfad5,_0x378e71){_0x3bfad5=_0x3bfad5-0x85;const _0x315bf5=_0x315b();let _0xe66bbf=_0x315bf5[_0x3bfad5];if(_0xe66b['kNiiZS']===undefined){var _0x12a87a=function(_0x1b6b00){const _0x9e693e='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x651b39='',_0x97439b='';for(let _0x485329=0x0,_0x3f20da,_0x3f86f1,_0x33b110=0x0;_0x3f86f1=_0x1b6b00['charAt'](_0x33b110++);~_0x3f86f1&&(_0x3f20da=_0x485329%0x4?_0x3f20da*0x40+_0x3f86f1:_0x3f86f1,_0x485329++%0x4)?_0x651b39+=String['fromCharCode'](0xff&_0x3f20da>>(-0x2*_0x485329&0x6)):0x0){_0x3f86f1=_0x9e693e['indexOf'](_0x3f86f1);}for(let _0x2431cb=0x0,_0x52a54a=_0x651b39['length'];_0x2431cb<_0x52a54a;_0x2431cb++){_0x97439b+='%'+('00'+_0x651b39['charCodeAt'](_0x2431cb)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x97439b);};_0xe66b['Wrzwnr']=_0x12a87a,_0xe66b['KILFAc']={},_0xe66b['kNiiZS']=!![];}const _0x21ee24=_0x315bf5[0x0],_0x2b04bc=_0x3bfad5+_0x21ee24,_0x5c934c=_0xe66b['KILFAc'][_0x2b04bc];return!_0x5c934c?(_0xe66bbf=_0xe66b['Wrzwnr'](_0xe66bbf),_0xe66b['KILFAc'][_0x2b04bc]=_0xe66bbf):_0xe66bbf=_0x5c934c,_0xe66bbf;}function githubRepo(_0x3985f7){const _0x4ea88a=_0x5435d3;return _0x4ea88a(0x89)+ORG+'/'+_0x3985f7;}const docsUrl=_0x5435d3(0x95);export const pluginDev={'docsUrl':docsUrl,'serviceUrl':serviceUrlFromDocs(docsUrl),'uploadUrl':docsUrl+_0x5435d3(0x9f),'mirrors':{'prefer':_0x5435d3(0x98),'giteeOrg':'https://gitee.com/'+ORG,'githubOrg':_0x5435d3(0x89)+ORG},'scaffolds':{'full':{'key':_0x5435d3(0x97),'title':_0x5435d3(0x9c),'desc':_0x5435d3(0xa2),'repo':_0x5435d3(0x91),'repoUrl':giteeRepo('vome-plugin-full'),'githubRepoUrl':githubRepo(_0x5435d3(0x91)),'downloadUrl':giteeArchive(_0x5435d3(0x91)),'githubDownloadUrl':githubArchive(_0x5435d3(0x91))},'backend':{'key':_0x5435d3(0x8a),'title':_0x5435d3(0x99),'desc':'钩子\x20/\x20服务端插件脚手架(.vome:仅\x20server),适合上传、短信、支付等能力扩展。','repo':_0x5435d3(0x9e),'repoUrl':giteeRepo(_0x5435d3(0x9e)),'githubRepoUrl':githubRepo('vome-plugin-service'),'downloadUrl':giteeArchive('vome-plugin-service'),'githubDownloadUrl':githubArchive(_0x5435d3(0x9e))},'frontend':{'key':_0x5435d3(0x88),'title':_0x5435d3(0x8f),'desc':_0x5435d3(0x92),'repo':_0x5435d3(0xa0),'repoUrl':giteeRepo(_0x5435d3(0xa0)),'githubRepoUrl':githubRepo(_0x5435d3(0xa0)),'downloadUrl':giteeArchive('vome-plugin-front'),'githubDownloadUrl':githubArchive(_0x5435d3(0xa0))}}};
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>