vome-core 0.0.19 → 0.0.21

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 (72) hide show
  1. package/dist/admin/config/plugin-dev.js +1 -1
  2. package/dist/admin/crud/components/vm-audio.vue +217 -0
  3. package/dist/admin/crud/components/vm-column-custom.vue +50 -56
  4. package/dist/admin/crud/components/vm-file-preview.vue +90 -0
  5. package/dist/admin/crud/components/vm-image.vue +413 -0
  6. package/dist/admin/crud/components/vm-preview-viewer.vue +520 -0
  7. package/dist/admin/crud/components/vm-richtext.vue +275 -0
  8. package/dist/admin/crud/components/vm-upload-item.vue +92 -31
  9. package/dist/admin/crud/components/vm-upload.vue +2 -6
  10. package/dist/admin/crud/components/vm-user-select.vue +48 -49
  11. package/dist/admin/crud/config.js +1 -1
  12. package/dist/admin/crud/confirm.js +1 -1
  13. package/dist/admin/crud/dict.js +1 -1
  14. package/dist/admin/crud/index.d.ts +6 -3
  15. package/dist/admin/crud/index.js +1 -1
  16. package/dist/admin/crud/key.js +1 -1
  17. package/dist/admin/crud/mitt.js +1 -1
  18. package/dist/admin/crud/plugins.js +1 -1
  19. package/dist/admin/crud/span.js +1 -1
  20. package/dist/admin/crud/style.js +1 -1
  21. package/dist/admin/crud/validate.js +1 -1
  22. package/dist/admin/crud/vm-adv-search.vue +39 -32
  23. package/dist/admin/crud/vm-crud.vue +3 -8
  24. package/dist/admin/crud/vm-export-btn.vue +169 -0
  25. package/dist/admin/crud/{vm-flex1.vue → vm-flex.vue} +3 -3
  26. package/dist/admin/crud/vm-search.vue +18 -3
  27. package/dist/admin/crud/vm-toolbar.vue +20 -0
  28. package/dist/admin/crud/vm-upsert.vue +166 -119
  29. package/dist/admin/directives/perm.js +1 -1
  30. package/dist/admin/hooks/useUpload.js +1 -1
  31. package/dist/admin/lib/browser.js +1 -1
  32. package/dist/admin/lib/cn.js +1 -1
  33. package/dist/admin/lib/dialog-float.d.ts +2 -0
  34. package/dist/admin/lib/dialog-float.js +1 -1
  35. package/dist/admin/lib/export-excel.d.ts +11 -0
  36. package/dist/admin/lib/export-excel.js +1 -0
  37. package/dist/admin/lib/file-preview.d.ts +11 -0
  38. package/dist/admin/lib/file-preview.js +1 -0
  39. package/dist/admin/lib/menu.js +1 -1
  40. package/dist/admin/lib/tree.js +1 -1
  41. package/dist/admin/lib/upload.js +1 -1
  42. package/dist/admin/lib/video-frame.d.ts +9 -0
  43. package/dist/admin/lib/video-frame.js +1 -0
  44. package/dist/admin/styles/base.css +100 -0
  45. package/dist/index.js +1 -1
  46. package/dist/server/index.js +1 -1
  47. package/dist/shared/excel.d.ts +13 -0
  48. package/dist/shared/excel.js +1 -0
  49. package/dist/shared/index.js +1 -1
  50. package/dist/shared/tree.js +1 -1
  51. package/dist/src/core/context/index.d.ts +11 -1
  52. package/dist/src/routing/base.d.ts +2 -0
  53. package/dist/src/server/index.d.ts +2 -0
  54. package/dist/src/shared/excel.d.ts +13 -0
  55. package/dist/src/shared/index.d.ts +2 -0
  56. package/dist/typings/routing/crud.d.ts +2 -0
  57. package/package.json +3 -2
  58. package/src/shared/excel.d.ts +13 -0
  59. package/src/shared/excel.ts +46 -0
  60. package/src/shared/index.ts +6 -0
  61. package/typings/admin/comm/crud.d.ts +8 -2
  62. package/typings/admin/comm/crud.ts +8 -3
  63. package/dist/admin/crud/components/vm-file-icon.vue +0 -55
  64. package/dist/admin/crud/vm-dialog.vue +0 -235
  65. package/dist/admin/static/fileicon/demo.css +0 -539
  66. package/dist/admin/static/fileicon/demo_index.html +0 -901
  67. package/dist/admin/static/fileicon/iconfont.css +0 -139
  68. package/dist/admin/static/fileicon/iconfont.js +0 -1
  69. package/dist/admin/static/fileicon/iconfont.json +0 -226
  70. package/dist/admin/static/fileicon/iconfont.ttf +0 -0
  71. package/dist/admin/static/fileicon/iconfont.woff +0 -0
  72. package/dist/admin/static/fileicon/iconfont.woff2 +0 -0
@@ -1 +1 @@
1
- const _0x26ea7d=_0x4b68;(function(_0x2b1e3e,_0x2e6f85){const _0x3deacb=_0x4b68,_0x1daaec=_0x2b1e3e();while(!![]){try{const _0x38f475=parseInt(_0x3deacb(0x145))/0x1+parseInt(_0x3deacb(0x139))/0x2*(-parseInt(_0x3deacb(0x142))/0x3)+parseInt(_0x3deacb(0x147))/0x4+parseInt(_0x3deacb(0x13b))/0x5*(parseInt(_0x3deacb(0x13d))/0x6)+parseInt(_0x3deacb(0x13c))/0x7+parseInt(_0x3deacb(0x148))/0x8+-parseInt(_0x3deacb(0x146))/0x9*(parseInt(_0x3deacb(0x141))/0xa);if(_0x38f475===_0x2e6f85)break;else _0x1daaec['push'](_0x1daaec['shift']());}catch(_0x5b8426){_0x1daaec['push'](_0x1daaec['shift']());}}}(_0xc7b4,0x31fb1));export const pluginDev={'docsUrl':'https://vome.dev/docs/plugin','uploadUrl':_0x26ea7d(0x13e),'scaffolds':{'full':{'key':_0x26ea7d(0x135),'title':'前端\x20+\x20后端','desc':_0x26ea7d(0x13a),'downloadUrl':_0x26ea7d(0x13f)},'backend':{'key':'backend','title':_0x26ea7d(0x138),'desc':_0x26ea7d(0x140),'downloadUrl':'https://gitee.com/YOUR_ORG/YOUR_REPO/raw/master/plugin-backend.zip'},'frontend':{'key':_0x26ea7d(0x144),'title':_0x26ea7d(0x136),'desc':_0x26ea7d(0x137),'downloadUrl':_0x26ea7d(0x143)}}};function _0x4b68(_0x40cccc,_0x5abaa2){_0x40cccc=_0x40cccc-0x135;const _0xc7b4a7=_0xc7b4();let _0x4b6834=_0xc7b4a7[_0x40cccc];if(_0x4b68['UaRtNi']===undefined){var _0x26b8cb=function(_0x239109){const _0x3a8043='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0xb1ae90='',_0x56caf7='';for(let _0x40d200=0x0,_0x44e32d,_0x5035c3,_0x430833=0x0;_0x5035c3=_0x239109['charAt'](_0x430833++);~_0x5035c3&&(_0x44e32d=_0x40d200%0x4?_0x44e32d*0x40+_0x5035c3:_0x5035c3,_0x40d200++%0x4)?_0xb1ae90+=String['fromCharCode'](0xff&_0x44e32d>>(-0x2*_0x40d200&0x6)):0x0){_0x5035c3=_0x3a8043['indexOf'](_0x5035c3);}for(let _0x552a5e=0x0,_0x3c3221=_0xb1ae90['length'];_0x552a5e<_0x3c3221;_0x552a5e++){_0x56caf7+='%'+('00'+_0xb1ae90['charCodeAt'](_0x552a5e)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x56caf7);};_0x4b68['UgQiGb']=_0x26b8cb,_0x4b68['hELLgE']={},_0x4b68['UaRtNi']=!![];}const _0x4e447e=_0xc7b4a7[0x0],_0x31d8d8=_0x40cccc+_0x4e447e,_0xff64f8=_0x4b68['hELLgE'][_0x31d8d8];return!_0xff64f8?(_0x4b6834=_0x4b68['UgQiGb'](_0x4b6834),_0x4b68['hELLgE'][_0x31d8d8]=_0x4b6834):_0x4b6834=_0xff64f8,_0x4b6834;}function _0xc7b4(){const _0x16381a=['Ahr0Chm6lY9NAxrLzs5JB20Vwu9vuL9puKCVwu9vuL9srvbpl3jHDY9Tyxn0zxiVCgX1z2LUlwz1BgWUEMLW','6zkP5A2qic8G5PYn5yQH56UV5O+s5lU26isA5OMl5P6277YilNzVBwxVVjRKU4uGC2vYDMvY77Yj77Ym6ycc5zci5lIk5lYG44cb55+T5l+H44cb5PsV5lUy562j6io95yQB5OMP5Bgv44cc','odbRAwPxr3C','ntyYmtaXr21MB1rM','Ahr0Chm6lY9NAxrLzs5JB20Vwu9vuL9puKCVwu9vuL9srvbpl3jHDY9Tyxn0zxiVCgX1z2LUlwzYB250zw5KlNPPCa','zNjVBNrLBMq','mZm1oda4AfzOwgTc','nZKZodi3Dwz1q3v2','nZi4mtG0BvDXqwj5','mJu3ndy3mMDgBvLnwq','zNvSBa','57QV5yMn56UV','5zco5y+W5B6U5BQu55sO6isA5OMl5P6277YilNzVBwxVVjRLKkSGD2vIl++8IE+8Jos7Psb3DwPPzsdMJilOVB3NI6ZNQ4VLIy3NQ6/POBxPNAlJGii','57QV5zco56UV','nhDVvePqvW','5A6m5Pw05lIA5yQH5QIH5z2x6isA5OMl5P6277YilNzVBwxVVjRLKkSGC2vYDMvYios4JIb3zwlVViNVViZMNkZLNldLVidLJ5hMIzpLJixLKi7LJ5hLUipJGii','mtu1nxLHENvOAW','mte2mJC5munNA0rgza','ntm4ogHrtgn6qG','Ahr0Chm6lY92B21LlMrLDI9WBhvNAw5Zl3vWBg9Hza'];_0xc7b4=function(){return _0x16381a;};return _0xc7b4();}
1
+ const _0x34aae3=_0x1a71;function _0x5f66(){const _0xabba46=['nJqWoeLbCgjXtG','mJu5nZrUDurpB0S','Ahr0Chm6lY92B21LlMrLDI9KB2nZl3bSDwDPBG','6zkP5A2qic8G5PYn5yQH56UV5O+s5lU26isA5OMl5P6277YilNzVBwxVVjRKU4uGC2vYDMvY77Yj77Ym6ycc5zci5lIk5lYG44cb55+T5l+H44cb5PsV5lUy562j6io95yQB5OMP5Bgv44cc','57QV5yMn56UV','mtu4meLrzLbLza','yMfJA2vUza','5yMn56UVicSG5zco56UV','Ahr0Chm6lY9NAxrLzs5JB20Vwu9vuL9puKCVwu9vuL9srvbpl3jHDY9Tyxn0zxiVCgX1z2LUlwjHy2TLBMqUEMLW','mtzrD2jNEgK','mZCWmJmYwhPLALrg','5A6m5Pw05lIA5yQH5QIH5z2x6isA5OMl5P6277YilNzVBwxVVjRLKkSGC2vYDMvYios4JIb3zwlVViNVViZMNkZLNldLVidLJ5hMIzpLJixLKi7LJ5hLUipJGii','57QV5zco56UV','mty0mJHWvM9ZEMS','mtCWmZy3nw9IDvzJEa','Ahr0Chm6lY92B21LlMrLDI9WBhvNAw5Zl3vWBg9Hza','mtKXnZKYD1rRD1b5','mte3rgP1C0P4','nJmXmJLzsxnVvxi','mZqWoufcq252yW','5zco5y+W5B6U5BQu55sO6isA5OMl5P6277YilNzVBwxVVjRLKkSGD2vIl++8IE+8Jos7Psb3DwPPzsdMJilOVB3NI6ZNQ4VLIy3NQ6/POBxPNAlJGii','mtyWmMz3ue1cwG','mtbKyuHUBum'];_0x5f66=function(){return _0xabba46;};return _0x5f66();}(function(_0x3cc1a5,_0x51c644){const _0x50da3f=_0x1a71,_0x4cc675=_0x3cc1a5();while(!![]){try{const _0x3318a7=parseInt(_0x50da3f(0xbd))/0x1*(parseInt(_0x50da3f(0xc8))/0x2)+parseInt(_0x50da3f(0xbe))/0x3+-parseInt(_0x50da3f(0xc7))/0x4*(-parseInt(_0x50da3f(0xcc))/0x5)+parseInt(_0x50da3f(0xbc))/0x6*(-parseInt(_0x50da3f(0xba))/0x7)+-parseInt(_0x50da3f(0xce))/0x8*(parseInt(_0x50da3f(0xb8))/0x9)+parseInt(_0x50da3f(0xc3))/0xa*(parseInt(_0x50da3f(0xb9))/0xb)+parseInt(_0x50da3f(0xcb))/0xc*(-parseInt(_0x50da3f(0xbf))/0xd);if(_0x3318a7===_0x51c644)break;else _0x4cc675['push'](_0x4cc675['shift']());}catch(_0x3f3a50){_0x4cc675['push'](_0x4cc675['shift']());}}}(_0x5f66,0xe6f7d));function _0x1a71(_0x3ae3c2,_0x318268){_0x3ae3c2=_0x3ae3c2-0xb8;const _0x5f661a=_0x5f66();let _0x1a71eb=_0x5f661a[_0x3ae3c2];if(_0x1a71['raAcQj']===undefined){var _0x2d3cfb=function(_0x4265d3){const _0x3febc9='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x59a690='',_0x5e5605='';for(let _0x2fbe08=0x0,_0x520887,_0x2c6971,_0x27d5bb=0x0;_0x2c6971=_0x4265d3['charAt'](_0x27d5bb++);~_0x2c6971&&(_0x520887=_0x2fbe08%0x4?_0x520887*0x40+_0x2c6971:_0x2c6971,_0x2fbe08++%0x4)?_0x59a690+=String['fromCharCode'](0xff&_0x520887>>(-0x2*_0x2fbe08&0x6)):0x0){_0x2c6971=_0x3febc9['indexOf'](_0x2c6971);}for(let _0x4689bd=0x0,_0x576ef8=_0x59a690['length'];_0x4689bd<_0x576ef8;_0x4689bd++){_0x5e5605+='%'+('00'+_0x59a690['charCodeAt'](_0x4689bd)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x5e5605);};_0x1a71['BDCumX']=_0x2d3cfb,_0x1a71['piBleH']={},_0x1a71['raAcQj']=!![];}const _0x555c44=_0x5f661a[0x0],_0x5265a6=_0x3ae3c2+_0x555c44,_0x2993bd=_0x1a71['piBleH'][_0x5265a6];return!_0x2993bd?(_0x1a71eb=_0x1a71['BDCumX'](_0x1a71eb),_0x1a71['piBleH'][_0x5265a6]=_0x1a71eb):_0x1a71eb=_0x2993bd,_0x1a71eb;}export const pluginDev={'docsUrl':_0x34aae3(0xc0),'uploadUrl':_0x34aae3(0xcd),'scaffolds':{'full':{'key':'full','title':_0x34aae3(0xc5),'desc':_0x34aae3(0xc9),'downloadUrl':'https://gitee.com/YOUR_ORG/YOUR_REPO/raw/master/plugin-full.zip'},'backend':{'key':_0x34aae3(0xc4),'title':_0x34aae3(0xca),'desc':_0x34aae3(0xc1),'downloadUrl':_0x34aae3(0xc6)},'frontend':{'key':'frontend','title':_0x34aae3(0xc2),'desc':_0x34aae3(0xbb),'downloadUrl':'https://gitee.com/YOUR_ORG/YOUR_REPO/raw/master/plugin-frontend.zip'}}};
@@ -0,0 +1,217 @@
1
+ <template>
2
+ <div v-if="src" class="vm-audio">
3
+ <button
4
+ type="button"
5
+ class="vm-audio__play"
6
+ :disabled="!preview"
7
+ :title="preview ? '播放音频' : undefined"
8
+ @click="openPlayer"
9
+ >
10
+ <i class="ri-play-circle-line" aria-hidden="true" />
11
+ <span>{{ label }}</span>
12
+ </button>
13
+ </div>
14
+ <span v-else class="vm-audio is-empty">—</span>
15
+
16
+ <Teleport to="body">
17
+ <div
18
+ v-if="playerOpen"
19
+ class="vm-audio-player"
20
+ role="dialog"
21
+ aria-modal="true"
22
+ @click.self="closePlayer"
23
+ >
24
+ <button type="button" class="vm-audio-player__close" title="关闭" @click="closePlayer">
25
+ <i class="ri-close-line" />
26
+ </button>
27
+ <div class="vm-audio-player__panel">
28
+ <p v-if="titleText" class="vm-audio-player__title">{{ titleText }}</p>
29
+ <audio
30
+ ref="audioRef"
31
+ class="vm-audio-player__media"
32
+ :src="src"
33
+ controls
34
+ autoplay
35
+ playsinline
36
+ @ended="closePlayer"
37
+ />
38
+ </div>
39
+ </div>
40
+ </Teleport>
41
+ </template>
42
+
43
+ <script setup lang="ts">
44
+ import { computed, nextTick, onUnmounted, ref, watch } from 'vue'
45
+
46
+ defineOptions({ name: 'vm-audio' })
47
+
48
+ const props = withDefaults(
49
+ defineProps<{
50
+ /** 音频 URL(表格列绑定 modelValue) */
51
+ modelValue?: string | null
52
+ /** 按钮文案 */
53
+ label?: string
54
+ /** 弹层标题,默认取文件名 */
55
+ title?: string
56
+ /** 是否可播放 */
57
+ preview?: boolean
58
+ }>(),
59
+ {
60
+ label: '播放',
61
+ preview: true,
62
+ },
63
+ )
64
+
65
+ const playerOpen = ref(false)
66
+ const audioRef = ref<HTMLAudioElement | null>(null)
67
+
68
+ const src = computed(() => {
69
+ const v = props.modelValue
70
+ if (v == null) return ''
71
+ return String(v).trim()
72
+ })
73
+
74
+ const titleText = computed(() => {
75
+ if (props.title) return props.title
76
+ const s = src.value
77
+ if (!s) return ''
78
+ return s.split('?')[0]?.split('/').pop() || ''
79
+ })
80
+
81
+ function lockBody(lock: boolean) {
82
+ if (typeof document === 'undefined') return
83
+ document.body.style.overflow = lock ? 'hidden' : ''
84
+ }
85
+
86
+ function openPlayer() {
87
+ if (!props.preview || !src.value) return
88
+ playerOpen.value = true
89
+ lockBody(true)
90
+ void nextTick(() => {
91
+ audioRef.value?.play?.().catch(() => undefined)
92
+ })
93
+ }
94
+
95
+ function closePlayer() {
96
+ playerOpen.value = false
97
+ lockBody(false)
98
+ audioRef.value?.pause?.()
99
+ }
100
+
101
+ function onKeydown(e: KeyboardEvent) {
102
+ if (!playerOpen.value) return
103
+ if (e.key === 'Escape') closePlayer()
104
+ }
105
+
106
+ watch(playerOpen, (open) => {
107
+ if (typeof window === 'undefined') return
108
+ if (open) window.addEventListener('keydown', onKeydown)
109
+ else window.removeEventListener('keydown', onKeydown)
110
+ })
111
+
112
+ onUnmounted(() => {
113
+ lockBody(false)
114
+ if (typeof window !== 'undefined') {
115
+ window.removeEventListener('keydown', onKeydown)
116
+ }
117
+ })
118
+ </script>
119
+
120
+ <style lang="scss" scoped>
121
+ .vm-audio {
122
+ display: inline-flex;
123
+ vertical-align: middle;
124
+ }
125
+
126
+ .vm-audio.is-empty {
127
+ color: var(--muted-foreground);
128
+ font-size: 12px;
129
+ }
130
+
131
+ .vm-audio__play {
132
+ display: inline-flex;
133
+ align-items: center;
134
+ gap: 4px;
135
+ padding: 0;
136
+ border: none;
137
+ background: transparent;
138
+ color: var(--brand);
139
+ font-size: 13px;
140
+ font-weight: 600;
141
+ cursor: pointer;
142
+ transition: color 0.15s ease;
143
+
144
+ i {
145
+ font-size: 16px;
146
+ line-height: 1;
147
+ }
148
+
149
+ &:hover:not(:disabled) {
150
+ color: var(--brand-deep);
151
+ }
152
+
153
+ &:disabled {
154
+ cursor: default;
155
+ opacity: 0.55;
156
+ }
157
+ }
158
+ </style>
159
+
160
+ <style lang="scss">
161
+ .vm-audio-player {
162
+ position: fixed;
163
+ inset: 0;
164
+ z-index: 2200;
165
+ display: flex;
166
+ align-items: center;
167
+ justify-content: center;
168
+ padding: 24px;
169
+ background: rgb(10 12 20 / 72%);
170
+ }
171
+
172
+ .vm-audio-player__close {
173
+ position: absolute;
174
+ top: 16px;
175
+ right: 16px;
176
+ z-index: 2;
177
+ display: inline-flex;
178
+ width: 36px;
179
+ height: 36px;
180
+ align-items: center;
181
+ justify-content: center;
182
+ border: none;
183
+ border-radius: 10px;
184
+ background: rgb(255 255 255 / 12%);
185
+ color: #fff;
186
+ font-size: 20px;
187
+ cursor: pointer;
188
+
189
+ &:hover {
190
+ background: rgb(255 255 255 / 22%);
191
+ }
192
+ }
193
+
194
+ .vm-audio-player__panel {
195
+ width: min(480px, calc(100vw - 48px));
196
+ padding: 20px 22px;
197
+ border-radius: 16px;
198
+ background: var(--card);
199
+ box-shadow: 0 24px 48px rgb(15 23 42 / 18%);
200
+ }
201
+
202
+ .vm-audio-player__title {
203
+ margin: 0 0 12px;
204
+ overflow: hidden;
205
+ text-overflow: ellipsis;
206
+ white-space: nowrap;
207
+ font-size: 14px;
208
+ font-weight: 650;
209
+ color: var(--foreground);
210
+ }
211
+
212
+ .vm-audio-player__media {
213
+ display: block;
214
+ width: 100%;
215
+ outline: none;
216
+ }
217
+ </style>
@@ -3,44 +3,58 @@
3
3
  <Button size="sm" variant="outline" class="h-8" @click="open = true">
4
4
  自定义列
5
5
  </Button>
6
- <vm-dialog v-model:open="open" title="自定义列" width="420px">
7
- <div class="vm-col-custom">
8
- <label
9
- v-for="(item, i) in list"
10
- :key="String(item.prop)"
11
- class="vm-col-custom__item"
12
- >
13
- <input v-model="item.checked" type="checkbox" />
14
- <span>{{ item.label }}</span>
15
- <span class="vm-col-custom__move">
16
- <button type="button" :disabled="i === 0" @click.prevent="move(i, -1)">↑</button>
17
- <button
18
- type="button"
19
- :disabled="i === list.length - 1"
20
- @click.prevent="move(i, 1)"
6
+ <Dialog :open="open" @update:open="onOpenChange">
7
+ <DialogContent
8
+ shell="crud"
9
+ class="vm-col-custom__dialog"
10
+ @pointer-down-outside="preventDialogOutsideClose"
11
+ @focus-outside="preventDialogOutsideClose"
12
+ @interact-outside="preventDialogOutsideClose"
13
+ >
14
+ <DialogHeader class="vm-crud-shell__head">
15
+ <DialogTitle class="vm-crud-shell__title">自定义列</DialogTitle>
16
+ </DialogHeader>
17
+ <div class="vm-crud-shell__body">
18
+ <div class="vm-col-custom">
19
+ <label
20
+ v-for="(item, i) in list"
21
+ :key="String(item.prop)"
22
+ class="vm-col-custom__item"
21
23
  >
22
-
23
- </button>
24
- </span>
25
- </label>
26
- </div>
27
- <template #footer>
28
- <button type="button" class="vm-col-custom__btn is-ghost" @click="open = false">
29
- 取消
30
- </button>
31
- <button type="button" class="vm-col-custom__btn is-danger" @click="reset">
32
- 重置
33
- </button>
34
- <button type="button" class="vm-col-custom__btn is-primary" @click="save">
35
- 保存
36
- </button>
37
- </template>
38
- </vm-dialog>
24
+ <input v-model="item.checked" type="checkbox" />
25
+ <span>{{ item.label }}</span>
26
+ <span class="vm-col-custom__move">
27
+ <button type="button" :disabled="i === 0" @click.prevent="move(i, -1)">↑</button>
28
+ <button
29
+ type="button"
30
+ :disabled="i === list.length - 1"
31
+ @click.prevent="move(i, 1)"
32
+ >
33
+
34
+ </button>
35
+ </span>
36
+ </label>
37
+ </div>
38
+ </div>
39
+ <DialogFooter class="vm-crud-shell__foot">
40
+ <button type="button" class="vm-crud-shell__btn is-ghost" @click="open = false">
41
+ 取消
42
+ </button>
43
+ <button type="button" class="vm-crud-shell__btn is-danger" @click="reset">
44
+ 重置
45
+ </button>
46
+ <button type="button" class="vm-crud-shell__btn is-primary" @click="save">
47
+ 保存
48
+ </button>
49
+ </DialogFooter>
50
+ </DialogContent>
51
+ </Dialog>
39
52
  </div>
40
53
  </template>
41
54
 
42
55
  <script setup lang="ts">
43
56
  import { ref, watch, inject } from 'vue'
57
+ import { preventDialogOutsideClose } from '../../lib/dialog-float'
44
58
  import { injectTableOptions, TABLE_API_KEY } from '../key'
45
59
  defineOptions({ name: 'vm-column-custom' })
46
60
 
@@ -63,6 +77,10 @@ const open = ref(false)
63
77
  const list = ref<Item[]>([])
64
78
  const storageKey = computedKey()
65
79
 
80
+ function onOpenChange(v: boolean) {
81
+ open.value = v
82
+ }
83
+
66
84
  function computedKey() {
67
85
  return `column-custom__${props.name || (typeof location !== 'undefined' ? location.pathname : 'default')}`
68
86
  }
@@ -170,28 +188,4 @@ function save() {
170
188
  }
171
189
  }
172
190
  }
173
-
174
- .vm-col-custom__btn {
175
- height: 36px;
176
- padding: 0 14px;
177
- border: none;
178
- border-radius: 12px;
179
- font-size: 13px;
180
- font-weight: 600;
181
- cursor: pointer;
182
-
183
- &.is-ghost {
184
- background: var(--muted);
185
- }
186
-
187
- &.is-danger {
188
- background: var(--danger-soft, #fceeee);
189
- color: var(--danger, #c45b5e);
190
- }
191
-
192
- &.is-primary {
193
- background: var(--brand);
194
- color: #fff;
195
- }
196
- }
197
191
  </style>
@@ -0,0 +1,90 @@
1
+ <template>
2
+ <div v-if="src" class="vm-file-preview">
3
+ <button
4
+ type="button"
5
+ class="vm-file-preview__btn"
6
+ :disabled="!preview"
7
+ @click="openViewer"
8
+ >
9
+ <i class="ri-file-search-line" aria-hidden="true" />
10
+ <span>{{ label }}</span>
11
+ </button>
12
+ </div>
13
+ <span v-else class="vm-file-preview is-empty">—</span>
14
+
15
+ <vm-preview-viewer v-model:open="viewerOpen" :src="src" :file-name="fileName" />
16
+ </template>
17
+
18
+ <script setup lang="ts">
19
+ import { computed, ref } from 'vue'
20
+ import VmPreviewViewer from './vm-preview-viewer.vue'
21
+
22
+ defineOptions({ name: 'vm-file-preview' })
23
+
24
+ const props = withDefaults(
25
+ defineProps<{
26
+ /** 文件 URL(表格列绑定 modelValue) */
27
+ modelValue?: string | null
28
+ /** 展示用文件名 */
29
+ fileName?: string
30
+ label?: string
31
+ preview?: boolean
32
+ }>(),
33
+ {
34
+ label: '查看',
35
+ preview: true,
36
+ },
37
+ )
38
+
39
+ const viewerOpen = ref(false)
40
+
41
+ const src = computed(() => {
42
+ const v = props.modelValue
43
+ if (v == null) return ''
44
+ return String(v).trim()
45
+ })
46
+
47
+ function openViewer() {
48
+ if (!props.preview || !src.value) return
49
+ viewerOpen.value = true
50
+ }
51
+ </script>
52
+
53
+ <style lang="scss" scoped>
54
+ .vm-file-preview {
55
+ display: inline-flex;
56
+ vertical-align: middle;
57
+ }
58
+
59
+ .vm-file-preview.is-empty {
60
+ color: var(--muted-foreground);
61
+ font-size: 12px;
62
+ }
63
+
64
+ .vm-file-preview__btn {
65
+ display: inline-flex;
66
+ align-items: center;
67
+ gap: 4px;
68
+ padding: 0;
69
+ border: none;
70
+ background: transparent;
71
+ color: var(--brand);
72
+ font-size: 13px;
73
+ font-weight: 600;
74
+ cursor: pointer;
75
+
76
+ i {
77
+ font-size: 16px;
78
+ line-height: 1;
79
+ }
80
+
81
+ &:hover:not(:disabled) {
82
+ color: var(--brand-deep);
83
+ }
84
+
85
+ &:disabled {
86
+ opacity: 0.55;
87
+ cursor: default;
88
+ }
89
+ }
90
+ </style>