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
@@ -0,0 +1,413 @@
1
+ <template>
2
+ <div v-if="src" class="vm-image" :class="{ 'is-video': isVideoKind }">
3
+ <button
4
+ type="button"
5
+ class="vm-image__trigger"
6
+ :style="thumbStyle"
7
+ :disabled="!preview"
8
+ :title="preview ? (isVideoKind ? '播放视频' : '查看大图') : undefined"
9
+ @click="openViewer"
10
+ >
11
+ <img
12
+ v-if="!isVideoKind"
13
+ class="vm-image__media"
14
+ :src="src"
15
+ alt=""
16
+ :style="mediaStyle"
17
+ @error="onThumbError"
18
+ />
19
+ <template v-else>
20
+ <img
21
+ v-if="videoFrameSrc"
22
+ class="vm-image__media"
23
+ :src="videoFrameSrc"
24
+ alt=""
25
+ :style="mediaStyle"
26
+ />
27
+ <video
28
+ v-else-if="frameFailed"
29
+ class="vm-image__media"
30
+ :src="src"
31
+ muted
32
+ playsinline
33
+ preload="metadata"
34
+ :style="mediaStyle"
35
+ />
36
+ <span v-else class="vm-image__loading" aria-hidden="true" />
37
+ <span class="vm-image__play" aria-hidden="true">
38
+ <i class="ri-play-fill" />
39
+ </span>
40
+ </template>
41
+ </button>
42
+ </div>
43
+ <span v-else class="vm-image is-empty">—</span>
44
+
45
+ <Teleport to="body">
46
+ <div
47
+ v-if="viewerOpen"
48
+ class="vm-image-viewer"
49
+ role="dialog"
50
+ aria-modal="true"
51
+ @click.self="closeViewer"
52
+ >
53
+ <button type="button" class="vm-image-viewer__close" title="关闭" @click="closeViewer">
54
+ <i class="ri-close-line" />
55
+ </button>
56
+
57
+ <div v-if="isVideoKind" class="vm-image-viewer__video-wrap">
58
+ <video
59
+ ref="videoRef"
60
+ class="vm-image-viewer__video"
61
+ :src="src"
62
+ controls
63
+ autoplay
64
+ playsinline
65
+ />
66
+ </div>
67
+
68
+ <template v-else>
69
+ <div
70
+ ref="stageRef"
71
+ class="vm-image-viewer__stage"
72
+ @wheel.prevent="onWheel"
73
+ >
74
+ <img
75
+ class="vm-image-viewer__img"
76
+ :src="src"
77
+ alt=""
78
+ :style="viewerImgStyle"
79
+ draggable="false"
80
+ @load="onViewerImgLoad"
81
+ />
82
+ </div>
83
+ <div class="vm-image-viewer__toolbar" @click.stop>
84
+ <button type="button" title="缩小" @click="zoomOut">
85
+ <i class="ri-subtract-line" />
86
+ </button>
87
+ <button type="button" title="放大" @click="zoomIn">
88
+ <i class="ri-add-line" />
89
+ </button>
90
+ <button type="button" title="原始尺寸" @click="resetView">
91
+ <i class="ri-aspect-ratio-line" />
92
+ </button>
93
+ <button type="button" title="逆时针旋转" @click="rotateLeft">
94
+ <i class="ri-anticlockwise-line" />
95
+ </button>
96
+ <button type="button" title="顺时针旋转" @click="rotateRight">
97
+ <i class="ri-clockwise-line" />
98
+ </button>
99
+ </div>
100
+ </template>
101
+ </div>
102
+ </Teleport>
103
+ </template>
104
+
105
+ <script setup lang="ts">
106
+ import { computed, nextTick, onUnmounted, ref, watch } from 'vue'
107
+ import { useVideoFrame } from '../../lib/video-frame'
108
+
109
+ defineOptions({ name: 'vm-image' })
110
+
111
+ const VIDEO_EXT = /\.(mp4|webm|mov|m4v|ogv|ogg)(\?|#|$)/i
112
+
113
+ const props = withDefaults(
114
+ defineProps<{
115
+ /** 图片或视频 URL(表格列绑定 modelValue) */
116
+ modelValue?: string | null
117
+ /** image | video | auto(按后缀推断) */
118
+ type?: 'image' | 'video' | 'auto'
119
+ /** 缩略图边长,默认 40 */
120
+ size?: number | string
121
+ /** 圆角,默认 8 */
122
+ radius?: number | string
123
+ /** 是否可点击预览/播放 */
124
+ preview?: boolean
125
+ fit?: 'cover' | 'contain'
126
+ }>(),
127
+ {
128
+ type: 'auto',
129
+ size: 40,
130
+ radius: 8,
131
+ preview: true,
132
+ fit: 'cover',
133
+ },
134
+ )
135
+
136
+ const viewerOpen = ref(false)
137
+ const scale = ref(1)
138
+ const rotate = ref(0)
139
+ const videoRef = ref<HTMLVideoElement | null>(null)
140
+ const stageRef = ref<HTMLElement | null>(null)
141
+
142
+ const src = computed(() => {
143
+ const v = props.modelValue
144
+ if (v == null) return ''
145
+ return String(v).trim()
146
+ })
147
+
148
+ const isVideoKind = computed(() => {
149
+ if (props.type === 'video') return true
150
+ if (props.type === 'image') return false
151
+ return VIDEO_EXT.test(src.value)
152
+ })
153
+
154
+ const videoThumbSource = computed(() => (isVideoKind.value ? src.value : ''))
155
+ const { videoFrameSrc, frameFailed } = useVideoFrame(videoThumbSource)
156
+
157
+ function toCssUnit(v: number | string) {
158
+ return typeof v === 'number' ? `${v}px` : v
159
+ }
160
+
161
+ const thumbStyle = computed(() => ({
162
+ width: toCssUnit(props.size),
163
+ height: toCssUnit(props.size),
164
+ borderRadius: toCssUnit(props.radius),
165
+ }))
166
+
167
+ const mediaStyle = computed(() => ({
168
+ objectFit: props.fit,
169
+ }))
170
+
171
+ const viewerImgStyle = computed(() => ({
172
+ transform: `scale(${scale.value}) rotate(${rotate.value}deg)`,
173
+ }))
174
+
175
+ function onThumbError() {
176
+ /* 缩略图失败仍允许点开预览 */
177
+ }
178
+
179
+ function resetView() {
180
+ scale.value = 1
181
+ rotate.value = 0
182
+ }
183
+
184
+ function zoomIn() {
185
+ scale.value = Math.min(5, +(scale.value + 0.2).toFixed(2))
186
+ }
187
+
188
+ function zoomOut() {
189
+ scale.value = Math.max(0.2, +(scale.value - 0.2).toFixed(2))
190
+ }
191
+
192
+ function rotateLeft() {
193
+ rotate.value -= 90
194
+ }
195
+
196
+ function rotateRight() {
197
+ rotate.value += 90
198
+ }
199
+
200
+ function onWheel(e: WheelEvent) {
201
+ if (e.deltaY < 0) zoomIn()
202
+ else zoomOut()
203
+ }
204
+
205
+ function onViewerImgLoad() {
206
+ resetView()
207
+ }
208
+
209
+ function lockBody(lock: boolean) {
210
+ if (typeof document === 'undefined') return
211
+ document.body.style.overflow = lock ? 'hidden' : ''
212
+ }
213
+
214
+ function openViewer() {
215
+ if (!props.preview || !src.value) return
216
+ resetView()
217
+ viewerOpen.value = true
218
+ lockBody(true)
219
+ void nextTick(() => {
220
+ videoRef.value?.play?.().catch(() => undefined)
221
+ })
222
+ }
223
+
224
+ function closeViewer() {
225
+ viewerOpen.value = false
226
+ lockBody(false)
227
+ videoRef.value?.pause?.()
228
+ }
229
+
230
+ function onKeydown(e: KeyboardEvent) {
231
+ if (!viewerOpen.value) return
232
+ if (e.key === 'Escape') closeViewer()
233
+ }
234
+
235
+ watch(viewerOpen, (open) => {
236
+ if (typeof window === 'undefined') return
237
+ if (open) window.addEventListener('keydown', onKeydown)
238
+ else window.removeEventListener('keydown', onKeydown)
239
+ })
240
+
241
+ onUnmounted(() => {
242
+ lockBody(false)
243
+ if (typeof window !== 'undefined') {
244
+ window.removeEventListener('keydown', onKeydown)
245
+ }
246
+ })
247
+ </script>
248
+
249
+ <style lang="scss" scoped>
250
+ .vm-image {
251
+ display: inline-flex;
252
+ vertical-align: middle;
253
+ }
254
+
255
+ .vm-image.is-empty {
256
+ color: var(--muted-foreground);
257
+ font-size: 12px;
258
+ }
259
+
260
+ .vm-image__trigger {
261
+ position: relative;
262
+ display: inline-flex;
263
+ flex-shrink: 0;
264
+ align-items: center;
265
+ justify-content: center;
266
+ padding: 0;
267
+ border: 1px solid color-mix(in srgb, var(--border) 85%, transparent);
268
+ background: var(--muted);
269
+ cursor: pointer;
270
+ overflow: hidden;
271
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
272
+
273
+ &:hover:not(:disabled) {
274
+ border-color: var(--focus-border);
275
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--brand) 12%, transparent);
276
+ }
277
+
278
+ &:disabled {
279
+ cursor: default;
280
+ }
281
+ }
282
+
283
+ .vm-image__media {
284
+ display: block;
285
+ width: 100%;
286
+ height: 100%;
287
+ }
288
+
289
+ .vm-image__loading {
290
+ display: block;
291
+ width: 100%;
292
+ height: 100%;
293
+ background: color-mix(in srgb, var(--muted) 70%, var(--foreground) 8%);
294
+ }
295
+
296
+ .vm-image__play {
297
+ position: absolute;
298
+ inset: 0;
299
+ display: grid;
300
+ place-items: center;
301
+ background: rgb(20 24 40 / 28%);
302
+ color: #fff;
303
+ pointer-events: none;
304
+
305
+ i {
306
+ font-size: 18px;
307
+ line-height: 1;
308
+ }
309
+ }
310
+ </style>
311
+
312
+ <style lang="scss">
313
+ .vm-image-viewer {
314
+ position: fixed;
315
+ inset: 0;
316
+ z-index: 2200;
317
+ display: flex;
318
+ flex-direction: column;
319
+ align-items: center;
320
+ justify-content: center;
321
+ padding: 24px;
322
+ background: rgb(10 12 20 / 88%);
323
+ }
324
+
325
+ .vm-image-viewer__close {
326
+ position: absolute;
327
+ top: 16px;
328
+ right: 16px;
329
+ z-index: 2;
330
+ display: inline-flex;
331
+ width: 36px;
332
+ height: 36px;
333
+ align-items: center;
334
+ justify-content: center;
335
+ border: none;
336
+ border-radius: 10px;
337
+ background: rgb(255 255 255 / 12%);
338
+ color: #fff;
339
+ font-size: 20px;
340
+ cursor: pointer;
341
+
342
+ &:hover {
343
+ background: rgb(255 255 255 / 22%);
344
+ }
345
+ }
346
+
347
+ .vm-image-viewer__stage {
348
+ display: flex;
349
+ width: 100%;
350
+ max-width: min(1200px, calc(100vw - 48px));
351
+ height: calc(100vh - 120px);
352
+ align-items: center;
353
+ justify-content: center;
354
+ overflow: hidden;
355
+ }
356
+
357
+ .vm-image-viewer__img {
358
+ max-width: 100%;
359
+ max-height: 100%;
360
+ object-fit: contain;
361
+ transition: transform 0.15s ease;
362
+ user-select: none;
363
+ }
364
+
365
+ .vm-image-viewer__video-wrap {
366
+ display: flex;
367
+ width: min(960px, calc(100vw - 48px));
368
+ max-height: calc(100vh - 80px);
369
+ align-items: center;
370
+ justify-content: center;
371
+ }
372
+
373
+ .vm-image-viewer__video {
374
+ width: 100%;
375
+ max-height: calc(100vh - 80px);
376
+ border-radius: 12px;
377
+ background: #000;
378
+ outline: none;
379
+ }
380
+
381
+ .vm-image-viewer__toolbar {
382
+ position: fixed;
383
+ bottom: 24px;
384
+ left: 50%;
385
+ z-index: 2;
386
+ display: inline-flex;
387
+ align-items: center;
388
+ gap: 4px;
389
+ padding: 6px 8px;
390
+ border-radius: 12px;
391
+ background: rgb(24 28 40 / 92%);
392
+ transform: translateX(-50%);
393
+
394
+ button {
395
+ display: inline-flex;
396
+ width: 36px;
397
+ height: 36px;
398
+ align-items: center;
399
+ justify-content: center;
400
+ border: none;
401
+ border-radius: 8px;
402
+ background: transparent;
403
+ color: rgb(255 255 255 / 88%);
404
+ font-size: 18px;
405
+ cursor: pointer;
406
+
407
+ &:hover {
408
+ background: rgb(255 255 255 / 12%);
409
+ color: #fff;
410
+ }
411
+ }
412
+ }
413
+ </style>