vome-core 0.1.34 → 0.1.36

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 +28 -216
  9. package/dist/admin/crud/components/vm-datetime-field.vue +249 -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/crud/vm-search.vue +7 -2
  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 _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
+ const _0x116b80=_0x21b1;(function(_0x2ec633,_0x8b2d61){const _0x2220fb=_0x21b1,_0x560891=_0x2ec633();while(!![]){try{const _0x3ed7b4=parseInt(_0x2220fb(0x1c0))/0x1*(parseInt(_0x2220fb(0x1b6))/0x2)+-parseInt(_0x2220fb(0x1b2))/0x3*(-parseInt(_0x2220fb(0x1ad))/0x4)+-parseInt(_0x2220fb(0x1a9))/0x5+parseInt(_0x2220fb(0x1a6))/0x6+parseInt(_0x2220fb(0x1aa))/0x7*(parseInt(_0x2220fb(0x1b5))/0x8)+parseInt(_0x2220fb(0x1bb))/0x9+-parseInt(_0x2220fb(0x1ba))/0xa;if(_0x3ed7b4===_0x8b2d61)break;else _0x560891['push'](_0x560891['shift']());}catch(_0x2eec5d){_0x560891['push'](_0x560891['shift']());}}}(_0x3f02,0x4fae5));const ORG=_0x116b80(0x1bc);export function serviceUrlFromDocs(_0x38ed1a){const _0x1d94aa=_0x116b80,_0x1f8536=new URL(_0x38ed1a),_0x501804=_0x1f8536[_0x1d94aa(0x1b7)]['replace'](/^www\./,'')[_0x1d94aa(0x1a8)](/^service\./,'');return _0x1f8536['protocol']+_0x1d94aa(0x1be)+_0x501804;}export function seatApiBaseFromDocs(_0x5f0d1d){const _0x2100a9=_0x116b80;return serviceUrlFromDocs(_0x5f0d1d)+_0x2100a9(0x1b3);}export function licenseIssueUrlFromDocs(_0x303c61){return serviceUrlFromDocs(_0x303c61)+'/app/pluginStore/wallet/license/issue';}function _0x21b1(_0x585394,_0x2ac1d6){_0x585394=_0x585394-0x1a4;const _0x3f0265=_0x3f02();let _0x21b11c=_0x3f0265[_0x585394];if(_0x21b1['uQaakx']===undefined){var _0x8c6d1b=function(_0xbd9d8e){const _0x54ee4b='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x38ed1a='',_0x1f8536='';for(let _0x501804=0x0,_0x5f0d1d,_0x303c61,_0x312f86=0x0;_0x303c61=_0xbd9d8e['charAt'](_0x312f86++);~_0x303c61&&(_0x5f0d1d=_0x501804%0x4?_0x5f0d1d*0x40+_0x303c61:_0x303c61,_0x501804++%0x4)?_0x38ed1a+=String['fromCharCode'](0xff&_0x5f0d1d>>(-0x2*_0x501804&0x6)):0x0){_0x303c61=_0x54ee4b['indexOf'](_0x303c61);}for(let _0x51aa6f=0x0,_0x5b1e25=_0x38ed1a['length'];_0x51aa6f<_0x5b1e25;_0x51aa6f++){_0x1f8536+='%'+('00'+_0x38ed1a['charCodeAt'](_0x51aa6f)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x1f8536);};_0x21b1['VsPvOR']=_0x8c6d1b,_0x21b1['xpWLqA']={},_0x21b1['uQaakx']=!![];}const _0x1f393b=_0x3f0265[0x0],_0x5962c7=_0x585394+_0x1f393b,_0x49da89=_0x21b1['xpWLqA'][_0x5962c7];return!_0x49da89?(_0x21b11c=_0x21b1['VsPvOR'](_0x21b11c),_0x21b1['xpWLqA'][_0x5962c7]=_0x21b11c):_0x21b11c=_0x49da89,_0x21b11c;}function giteeArchive(_0x312f86){const _0x4a78ef=_0x116b80;return _0x4a78ef(0x1b0)+ORG+'/'+_0x312f86+'/repository/archive/master.zip';}function githubArchive(_0x51aa6f){const _0x28bdc2=_0x116b80;return _0x28bdc2(0x1b1)+ORG+'/'+_0x51aa6f+_0x28bdc2(0x1a7);}function _0x3f02(){const _0xd7bd74=['mJC1ndC5og9sq0zcCq','l2fYy2HPDMuVCMvMCY9OzwfKCY9Tyxn0zxiUEMLW','CMvWBgfJzq','mty0otu3mhv5CK1lvq','n05zy0P2wa','5A6m5Pw05lIA5yQH5QIH5z2x6isA5OMl5P6277YilNzVBwxVVjRLKkSGC2vYDMvYios4JIb3zwlVViNVViZMNkZLNldLVidLJ5hMIzpLJixLKi7LJ5hLUipJGii','6zkP5A2qic8G5PYn5yQH56UV5O+s5lU26isA5OMl5P6277YilNzVBwxVVjRKU4uGC2vYDMvY77Yj77Ym6ycc5zci5lIk5lYG44cb55+T5l+H44cb5PsV5lUy562j6io95yQB5OMP5Bgv44cc','mJG0otjRsufLqK8','5yMn56UVicSG5zco56UV','zNjVBNrLBMq','Ahr0Chm6lY9NAxrLzs5JB20V','Ahr0Chm6lY9NAxrODwiUy29TlW','odDnvMndAKy','l2fWCc9WBhvNAw5tDg9Yzs9Zzwf0','Ahr0Chm6lY92B21LA2OUy29T','ndiXnZqZmLjxzNzmvG','otaWmNbLr2XzCa','Ag9ZDg5HBwu','57QV5zco56UV','yMfJA2vUza','mtq2mZm2mdb4BvvrwgK','ndG5nZy0n0Pmr29LEG','DM9TzwTQ','57QV5yMn56UV','lY9Zzxj2AwnLlG','DM9Tzs1WBhvNAw4TzNjVBNq','odveEMLKrw0','5zco5y+W5B6U5BQu55sO6isA5OMl5P6277YilNzVBwxVVjRLKkSGD2vIl++8IE+8Jos7Psb3DwPPzsdMJilOVB3NI6ZNQ4VLIy3NQ6/POBxPNAlJGii','DM9Tzs1WBhvNAw4TC2vYDMLJzq','l3bSDwDPBNmVDxbSB2fK','DM9Tzs1WBhvNAw4TzNvSBa'];_0x3f02=function(){return _0xd7bd74;};return _0x3f02();}function giteeRepo(_0x5b1e25){const _0x174c37=_0x116b80;return _0x174c37(0x1b0)+ORG+'/'+_0x5b1e25;}function githubRepo(_0x1f0f23){const _0x3cd781=_0x116b80;return _0x3cd781(0x1b1)+ORG+'/'+_0x1f0f23;}const docsUrl=_0x116b80(0x1b4);export const pluginDev={'docsUrl':docsUrl,'serviceUrl':serviceUrlFromDocs(docsUrl),'uploadUrl':docsUrl+_0x116b80(0x1a4),'mirrors':{'prefer':'gitee','giteeOrg':_0x116b80(0x1b0)+ORG,'githubOrg':_0x116b80(0x1b1)+ORG},'scaffolds':{'full':{'key':'full','title':_0x116b80(0x1ae),'desc':_0x116b80(0x1ab),'repo':_0x116b80(0x1a5),'repoUrl':giteeRepo('vome-plugin-full'),'githubRepoUrl':githubRepo(_0x116b80(0x1a5)),'downloadUrl':giteeArchive('vome-plugin-full'),'githubDownloadUrl':githubArchive(_0x116b80(0x1a5))},'backend':{'key':_0x116b80(0x1b9),'title':_0x116b80(0x1b8),'desc':_0x116b80(0x1ac),'repo':'vome-plugin-service','repoUrl':giteeRepo(_0x116b80(0x1c2)),'githubRepoUrl':githubRepo('vome-plugin-service'),'downloadUrl':giteeArchive('vome-plugin-service'),'githubDownloadUrl':githubArchive(_0x116b80(0x1c2))},'frontend':{'key':_0x116b80(0x1af),'title':_0x116b80(0x1bd),'desc':_0x116b80(0x1c1),'repo':_0x116b80(0x1bf),'repoUrl':giteeRepo('vome-plugin-front'),'githubRepoUrl':githubRepo('vome-plugin-front'),'downloadUrl':giteeArchive(_0x116b80(0x1bf)),'githubDownloadUrl':githubArchive(_0x116b80(0x1bf))}}};
@@ -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>