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.
- package/dist/admin/config/plugin-dev.js +1 -1
- package/dist/admin/crud/components/vm-audio.vue +217 -0
- package/dist/admin/crud/components/vm-column-custom.vue +50 -56
- package/dist/admin/crud/components/vm-file-preview.vue +90 -0
- package/dist/admin/crud/components/vm-image.vue +413 -0
- package/dist/admin/crud/components/vm-preview-viewer.vue +520 -0
- package/dist/admin/crud/components/vm-richtext.vue +275 -0
- package/dist/admin/crud/components/vm-upload-item.vue +92 -31
- package/dist/admin/crud/components/vm-upload.vue +2 -6
- package/dist/admin/crud/components/vm-user-select.vue +48 -49
- package/dist/admin/crud/config.js +1 -1
- package/dist/admin/crud/confirm.js +1 -1
- package/dist/admin/crud/dict.js +1 -1
- package/dist/admin/crud/index.d.ts +6 -3
- package/dist/admin/crud/index.js +1 -1
- package/dist/admin/crud/key.js +1 -1
- package/dist/admin/crud/mitt.js +1 -1
- package/dist/admin/crud/plugins.js +1 -1
- package/dist/admin/crud/span.js +1 -1
- package/dist/admin/crud/style.js +1 -1
- package/dist/admin/crud/validate.js +1 -1
- package/dist/admin/crud/vm-adv-search.vue +39 -32
- package/dist/admin/crud/vm-crud.vue +3 -8
- package/dist/admin/crud/vm-export-btn.vue +169 -0
- package/dist/admin/crud/{vm-flex1.vue → vm-flex.vue} +3 -3
- package/dist/admin/crud/vm-search.vue +18 -3
- package/dist/admin/crud/vm-toolbar.vue +20 -0
- package/dist/admin/crud/vm-upsert.vue +166 -119
- package/dist/admin/directives/perm.js +1 -1
- package/dist/admin/hooks/useUpload.js +1 -1
- package/dist/admin/lib/browser.js +1 -1
- package/dist/admin/lib/cn.js +1 -1
- package/dist/admin/lib/dialog-float.d.ts +2 -0
- package/dist/admin/lib/dialog-float.js +1 -1
- package/dist/admin/lib/export-excel.d.ts +11 -0
- package/dist/admin/lib/export-excel.js +1 -0
- package/dist/admin/lib/file-preview.d.ts +11 -0
- package/dist/admin/lib/file-preview.js +1 -0
- package/dist/admin/lib/menu.js +1 -1
- package/dist/admin/lib/tree.js +1 -1
- package/dist/admin/lib/upload.js +1 -1
- package/dist/admin/lib/video-frame.d.ts +9 -0
- package/dist/admin/lib/video-frame.js +1 -0
- package/dist/admin/styles/base.css +100 -0
- package/dist/index.js +1 -1
- package/dist/server/index.js +1 -1
- package/dist/shared/excel.d.ts +13 -0
- package/dist/shared/excel.js +1 -0
- package/dist/shared/index.js +1 -1
- package/dist/shared/tree.js +1 -1
- package/dist/src/core/context/index.d.ts +11 -1
- package/dist/src/routing/base.d.ts +2 -0
- package/dist/src/server/index.d.ts +2 -0
- package/dist/src/shared/excel.d.ts +13 -0
- package/dist/src/shared/index.d.ts +2 -0
- package/dist/typings/routing/crud.d.ts +2 -0
- package/package.json +3 -2
- package/src/shared/excel.d.ts +13 -0
- package/src/shared/excel.ts +46 -0
- package/src/shared/index.ts +6 -0
- package/typings/admin/comm/crud.d.ts +8 -2
- package/typings/admin/comm/crud.ts +8 -3
- package/dist/admin/crud/components/vm-file-icon.vue +0 -55
- package/dist/admin/crud/vm-dialog.vue +0 -235
- package/dist/admin/static/fileicon/demo.css +0 -539
- package/dist/admin/static/fileicon/demo_index.html +0 -901
- package/dist/admin/static/fileicon/iconfont.css +0 -139
- package/dist/admin/static/fileicon/iconfont.js +0 -1
- package/dist/admin/static/fileicon/iconfont.json +0 -226
- package/dist/admin/static/fileicon/iconfont.ttf +0 -0
- package/dist/admin/static/fileicon/iconfont.woff +0 -0
- package/dist/admin/static/fileicon/iconfont.woff2 +0 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="vm-richtext"
|
|
4
|
+
:class="{ 'is-readonly': readonly, 'is-disabled': disabled }"
|
|
5
|
+
>
|
|
6
|
+
<div v-if="!readonly" class="vm-richtext__toolbar" role="toolbar">
|
|
7
|
+
<button
|
|
8
|
+
v-for="btn in toolbar"
|
|
9
|
+
:key="btn.cmd"
|
|
10
|
+
type="button"
|
|
11
|
+
class="vm-richtext__tool"
|
|
12
|
+
:title="btn.title"
|
|
13
|
+
:disabled="disabled"
|
|
14
|
+
@mousedown.prevent
|
|
15
|
+
@click="runCmd(btn.cmd, btn.value)"
|
|
16
|
+
>
|
|
17
|
+
<i :class="btn.icon" aria-hidden="true" />
|
|
18
|
+
</button>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<div
|
|
22
|
+
v-if="readonly"
|
|
23
|
+
class="vm-richtext__html"
|
|
24
|
+
v-html="safeHtml"
|
|
25
|
+
/>
|
|
26
|
+
|
|
27
|
+
<div
|
|
28
|
+
v-else
|
|
29
|
+
ref="editorRef"
|
|
30
|
+
class="vm-richtext__editor"
|
|
31
|
+
:style="editorStyle"
|
|
32
|
+
:contenteditable="!disabled"
|
|
33
|
+
:data-placeholder="placeholder"
|
|
34
|
+
@input="onInput"
|
|
35
|
+
@blur="onInput"
|
|
36
|
+
@paste="onPaste"
|
|
37
|
+
/>
|
|
38
|
+
</div>
|
|
39
|
+
</template>
|
|
40
|
+
|
|
41
|
+
<script setup lang="ts">
|
|
42
|
+
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
|
43
|
+
import DOMPurify from 'dompurify'
|
|
44
|
+
|
|
45
|
+
defineOptions({ name: 'vm-richtext' })
|
|
46
|
+
|
|
47
|
+
const RICH_TAGS = [
|
|
48
|
+
'p',
|
|
49
|
+
'br',
|
|
50
|
+
'strong',
|
|
51
|
+
'b',
|
|
52
|
+
'em',
|
|
53
|
+
'i',
|
|
54
|
+
'u',
|
|
55
|
+
'a',
|
|
56
|
+
'ul',
|
|
57
|
+
'ol',
|
|
58
|
+
'li',
|
|
59
|
+
'h1',
|
|
60
|
+
'h2',
|
|
61
|
+
'h3',
|
|
62
|
+
'h4',
|
|
63
|
+
'blockquote',
|
|
64
|
+
'code',
|
|
65
|
+
'pre',
|
|
66
|
+
'span',
|
|
67
|
+
'div',
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
const props = withDefaults(
|
|
71
|
+
defineProps<{
|
|
72
|
+
modelValue?: string | null
|
|
73
|
+
/** 只读展示(表格列) */
|
|
74
|
+
readonly?: boolean
|
|
75
|
+
disabled?: boolean
|
|
76
|
+
placeholder?: string
|
|
77
|
+
/** 编辑区高度 */
|
|
78
|
+
height?: number | string
|
|
79
|
+
}>(),
|
|
80
|
+
{
|
|
81
|
+
readonly: false,
|
|
82
|
+
placeholder: '请输入内容…',
|
|
83
|
+
height: 220,
|
|
84
|
+
},
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
const emit = defineEmits<{
|
|
88
|
+
'update:modelValue': [v: string]
|
|
89
|
+
}>()
|
|
90
|
+
|
|
91
|
+
const editorRef = ref<HTMLElement | null>(null)
|
|
92
|
+
const innerHtml = ref('')
|
|
93
|
+
|
|
94
|
+
const toolbar = [
|
|
95
|
+
{ cmd: 'bold', icon: 'ri-bold', title: '加粗' },
|
|
96
|
+
{ cmd: 'italic', icon: 'ri-italic', title: '斜体' },
|
|
97
|
+
{ cmd: 'underline', icon: 'ri-underline', title: '下划线' },
|
|
98
|
+
{ cmd: 'insertUnorderedList', icon: 'ri-list-unordered', title: '无序列表' },
|
|
99
|
+
{ cmd: 'insertOrderedList', icon: 'ri-list-ordered', title: '有序列表' },
|
|
100
|
+
{ cmd: 'formatBlock', icon: 'ri-heading', title: '小标题', value: 'h3' },
|
|
101
|
+
{ cmd: 'createLink', icon: 'ri-link', title: '链接' },
|
|
102
|
+
{ cmd: 'removeFormat', icon: 'ri-format-clear', title: '清除格式' },
|
|
103
|
+
] as const
|
|
104
|
+
|
|
105
|
+
function sanitize(html: string) {
|
|
106
|
+
return DOMPurify.sanitize(html, {
|
|
107
|
+
ALLOWED_TAGS: RICH_TAGS,
|
|
108
|
+
ALLOWED_ATTR: ['href', 'target', 'rel', 'class'],
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const safeHtml = computed(() => sanitize(String(props.modelValue || '')))
|
|
113
|
+
|
|
114
|
+
const editorStyle = computed(() => ({
|
|
115
|
+
minHeight: typeof props.height === 'number' ? `${props.height}px` : props.height,
|
|
116
|
+
}))
|
|
117
|
+
|
|
118
|
+
function syncEditorFromModel() {
|
|
119
|
+
if (props.readonly || !editorRef.value) return
|
|
120
|
+
const next = safeHtml.value
|
|
121
|
+
if (editorRef.value.innerHTML !== next) {
|
|
122
|
+
editorRef.value.innerHTML = next || ''
|
|
123
|
+
}
|
|
124
|
+
innerHtml.value = next
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function emitHtml(html: string) {
|
|
128
|
+
const clean = sanitize(html)
|
|
129
|
+
innerHtml.value = clean
|
|
130
|
+
emit('update:modelValue', clean)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function onInput() {
|
|
134
|
+
if (!editorRef.value) return
|
|
135
|
+
emitHtml(editorRef.value.innerHTML)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function onPaste(e: ClipboardEvent) {
|
|
139
|
+
e.preventDefault()
|
|
140
|
+
const text = e.clipboardData?.getData('text/plain') || ''
|
|
141
|
+
document.execCommand('insertText', false, text)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function runCmd(cmd: string, value?: string) {
|
|
145
|
+
if (props.disabled || props.readonly) return
|
|
146
|
+
editorRef.value?.focus()
|
|
147
|
+
if (cmd === 'createLink') {
|
|
148
|
+
const url = window.prompt('链接地址', 'https://')
|
|
149
|
+
if (!url) return
|
|
150
|
+
document.execCommand('createLink', false, url)
|
|
151
|
+
onInput()
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
if (cmd === 'formatBlock' && value) {
|
|
155
|
+
document.execCommand('formatBlock', false, value)
|
|
156
|
+
} else {
|
|
157
|
+
document.execCommand(cmd, false, value)
|
|
158
|
+
}
|
|
159
|
+
onInput()
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
watch(
|
|
163
|
+
() => props.modelValue,
|
|
164
|
+
() => {
|
|
165
|
+
syncEditorFromModel()
|
|
166
|
+
},
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
onMounted(() => {
|
|
170
|
+
void nextTick(() => syncEditorFromModel())
|
|
171
|
+
})
|
|
172
|
+
</script>
|
|
173
|
+
|
|
174
|
+
<style lang="scss" scoped>
|
|
175
|
+
.vm-richtext {
|
|
176
|
+
width: 100%;
|
|
177
|
+
min-width: 0;
|
|
178
|
+
border: 1px solid var(--input);
|
|
179
|
+
border-radius: var(--vm-control-radius);
|
|
180
|
+
background: var(--card);
|
|
181
|
+
overflow: hidden;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.vm-richtext.is-disabled {
|
|
185
|
+
opacity: 0.65;
|
|
186
|
+
pointer-events: none;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.vm-richtext__toolbar {
|
|
190
|
+
display: flex;
|
|
191
|
+
flex-wrap: wrap;
|
|
192
|
+
gap: 4px;
|
|
193
|
+
padding: 8px 10px;
|
|
194
|
+
border-bottom: 1px solid var(--border);
|
|
195
|
+
background: var(--muted);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.vm-richtext__tool {
|
|
199
|
+
display: inline-flex;
|
|
200
|
+
width: 30px;
|
|
201
|
+
height: 30px;
|
|
202
|
+
align-items: center;
|
|
203
|
+
justify-content: center;
|
|
204
|
+
border: none;
|
|
205
|
+
border-radius: 8px;
|
|
206
|
+
background: transparent;
|
|
207
|
+
color: var(--foreground);
|
|
208
|
+
font-size: 16px;
|
|
209
|
+
cursor: pointer;
|
|
210
|
+
|
|
211
|
+
&:hover:not(:disabled) {
|
|
212
|
+
background: color-mix(in srgb, var(--foreground) 8%, var(--card));
|
|
213
|
+
color: var(--brand);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.vm-richtext__editor {
|
|
218
|
+
padding: 12px 14px;
|
|
219
|
+
outline: none;
|
|
220
|
+
color: var(--foreground);
|
|
221
|
+
font-size: 14px;
|
|
222
|
+
line-height: 1.65;
|
|
223
|
+
overflow: auto;
|
|
224
|
+
word-break: break-word;
|
|
225
|
+
|
|
226
|
+
&:empty::before {
|
|
227
|
+
content: attr(data-placeholder);
|
|
228
|
+
color: var(--muted-foreground);
|
|
229
|
+
pointer-events: none;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
:deep(a) {
|
|
233
|
+
color: var(--brand);
|
|
234
|
+
text-decoration: underline;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.vm-richtext__html {
|
|
239
|
+
padding: 0;
|
|
240
|
+
color: var(--foreground);
|
|
241
|
+
font-size: 14px;
|
|
242
|
+
line-height: 1.65;
|
|
243
|
+
word-break: break-word;
|
|
244
|
+
|
|
245
|
+
:deep(p) {
|
|
246
|
+
margin: 0.5em 0;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
:deep(ul),
|
|
250
|
+
:deep(ol) {
|
|
251
|
+
margin: 0.5em 0;
|
|
252
|
+
padding-left: 1.25em;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
:deep(a) {
|
|
256
|
+
color: var(--brand);
|
|
257
|
+
text-decoration: underline;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
:deep(h1),
|
|
261
|
+
:deep(h2),
|
|
262
|
+
:deep(h3),
|
|
263
|
+
:deep(h4) {
|
|
264
|
+
margin: 0.65em 0 0.35em;
|
|
265
|
+
font-weight: 650;
|
|
266
|
+
line-height: 1.35;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.vm-richtext.is-readonly .vm-richtext__html {
|
|
271
|
+
padding: 0;
|
|
272
|
+
border: none;
|
|
273
|
+
background: transparent;
|
|
274
|
+
}
|
|
275
|
+
</style>
|
|
@@ -2,20 +2,37 @@
|
|
|
2
2
|
<div class="vm-upload-item">
|
|
3
3
|
<template v-if="item.type === 'image' && !item.error && !imgBroken">
|
|
4
4
|
<img
|
|
5
|
-
class="vm-upload-
|
|
5
|
+
class="vm-upload-item__media"
|
|
6
6
|
:src="item.preload || item.url"
|
|
7
7
|
alt=""
|
|
8
8
|
@error="imgBroken = true"
|
|
9
9
|
/>
|
|
10
10
|
</template>
|
|
11
11
|
|
|
12
|
-
<template v-else-if="item.type === 'video' &&
|
|
13
|
-
<
|
|
12
|
+
<template v-else-if="item.type === 'video' && videoSource">
|
|
13
|
+
<img
|
|
14
|
+
v-if="videoFrameSrc"
|
|
15
|
+
class="vm-upload-item__media"
|
|
16
|
+
:src="videoFrameSrc"
|
|
17
|
+
alt=""
|
|
18
|
+
/>
|
|
19
|
+
<video
|
|
20
|
+
v-else-if="frameFailed"
|
|
21
|
+
class="vm-upload-item__media"
|
|
22
|
+
:src="videoSource"
|
|
23
|
+
muted
|
|
24
|
+
playsinline
|
|
25
|
+
preload="metadata"
|
|
26
|
+
/>
|
|
27
|
+
<span v-else class="vm-upload-item__loading" aria-hidden="true" />
|
|
28
|
+
<span class="vm-upload-item__play" aria-hidden="true">
|
|
29
|
+
<i class="ri-play-fill" />
|
|
30
|
+
</span>
|
|
14
31
|
</template>
|
|
15
32
|
|
|
16
33
|
<template v-else>
|
|
17
34
|
<div class="vm-upload-item__file">
|
|
18
|
-
<vm-
|
|
35
|
+
<i class="vm-upload-item__file-icon" :class="fileIcon" aria-hidden="true" />
|
|
19
36
|
<span class="vm-upload-item__name">{{ item.name || item.url }}</span>
|
|
20
37
|
<span v-if="item.error || imgBroken" class="vm-upload-item__error">
|
|
21
38
|
{{ item.error || '加载失败' }}
|
|
@@ -38,13 +55,8 @@
|
|
|
38
55
|
{{ tag.label }}
|
|
39
56
|
</span>
|
|
40
57
|
|
|
41
|
-
<div v-if="
|
|
42
|
-
<button
|
|
43
|
-
v-if="item.type === 'image'"
|
|
44
|
-
type="button"
|
|
45
|
-
title="预览"
|
|
46
|
-
@click.stop="previewOpen = true"
|
|
47
|
-
>
|
|
58
|
+
<div v-if="previewSrc" class="vm-upload-item__actions">
|
|
59
|
+
<button type="button" title="预览" @click.stop="previewOpen = true">
|
|
48
60
|
<i class="ri-zoom-in-line" />
|
|
49
61
|
</button>
|
|
50
62
|
<button v-if="!disabled" type="button" title="删除" @click.stop="emit('remove')">
|
|
@@ -52,17 +64,19 @@
|
|
|
52
64
|
</button>
|
|
53
65
|
</div>
|
|
54
66
|
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
67
|
+
<vm-preview-viewer
|
|
68
|
+
v-model:open="previewOpen"
|
|
69
|
+
:src="previewSrc"
|
|
70
|
+
:file-name="item.name || item.url"
|
|
71
|
+
/>
|
|
60
72
|
</div>
|
|
61
73
|
</template>
|
|
62
74
|
|
|
63
75
|
<script setup lang="ts">
|
|
64
76
|
import { ref, computed, watch } from 'vue'
|
|
77
|
+
import { useVideoFrame } from '../../lib/video-frame'
|
|
65
78
|
import { extname, getUploadRule } from '../../lib/upload'
|
|
79
|
+
import VmPreviewViewer from './vm-preview-viewer.vue'
|
|
66
80
|
import type { UploadItem } from '../../../../typings/admin/comm/upload'
|
|
67
81
|
|
|
68
82
|
defineOptions({ name: 'vm-upload-item' })
|
|
@@ -84,7 +98,14 @@ const emit = defineEmits<{ remove: [] }>()
|
|
|
84
98
|
const previewOpen = ref(false)
|
|
85
99
|
const imgBroken = ref(false)
|
|
86
100
|
|
|
87
|
-
const
|
|
101
|
+
const previewSrc = computed(() => props.item.preload || props.item.url || '')
|
|
102
|
+
|
|
103
|
+
const videoSource = computed(() => {
|
|
104
|
+
if (props.item.type !== 'video') return ''
|
|
105
|
+
return props.item.preload || props.item.url || ''
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
const { videoFrameSrc, frameFailed } = useVideoFrame(videoSource)
|
|
88
109
|
|
|
89
110
|
const tag = computed(() => {
|
|
90
111
|
const rule = getUploadRule(props.item.type)
|
|
@@ -94,6 +115,27 @@ const tag = computed(() => {
|
|
|
94
115
|
}
|
|
95
116
|
})
|
|
96
117
|
|
|
118
|
+
const fileIcon = computed(() => {
|
|
119
|
+
switch (props.item.type) {
|
|
120
|
+
case 'pdf':
|
|
121
|
+
return 'ri-file-pdf-line'
|
|
122
|
+
case 'word':
|
|
123
|
+
return 'ri-file-word-line'
|
|
124
|
+
case 'excel':
|
|
125
|
+
return 'ri-file-excel-line'
|
|
126
|
+
case 'ppt':
|
|
127
|
+
return 'ri-file-ppt-line'
|
|
128
|
+
case 'audio':
|
|
129
|
+
return 'ri-file-music-line'
|
|
130
|
+
case 'video':
|
|
131
|
+
return 'ri-file-video-line'
|
|
132
|
+
case 'rar':
|
|
133
|
+
return 'ri-file-zip-line'
|
|
134
|
+
default:
|
|
135
|
+
return 'ri-file-3-line'
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
|
|
97
139
|
watch(
|
|
98
140
|
() => props.item.url,
|
|
99
141
|
() => {
|
|
@@ -113,12 +155,33 @@ watch(
|
|
|
113
155
|
background: var(--muted);
|
|
114
156
|
}
|
|
115
157
|
|
|
116
|
-
.vm-upload-
|
|
117
|
-
.vm-upload-item__video {
|
|
158
|
+
.vm-upload-item__media {
|
|
118
159
|
width: 100%;
|
|
119
160
|
height: 100%;
|
|
120
|
-
object-fit:
|
|
161
|
+
object-fit: cover;
|
|
162
|
+
display: block;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.vm-upload-item__loading {
|
|
121
166
|
display: block;
|
|
167
|
+
width: 100%;
|
|
168
|
+
height: 100%;
|
|
169
|
+
background: color-mix(in srgb, var(--muted) 70%, var(--foreground) 8%);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.vm-upload-item__play {
|
|
173
|
+
position: absolute;
|
|
174
|
+
inset: 0;
|
|
175
|
+
display: grid;
|
|
176
|
+
place-items: center;
|
|
177
|
+
background: rgb(20 24 40 / 22%);
|
|
178
|
+
color: #fff;
|
|
179
|
+
pointer-events: none;
|
|
180
|
+
|
|
181
|
+
i {
|
|
182
|
+
font-size: 28px;
|
|
183
|
+
line-height: 1;
|
|
184
|
+
}
|
|
122
185
|
}
|
|
123
186
|
|
|
124
187
|
.vm-upload-item__file {
|
|
@@ -130,6 +193,12 @@ watch(
|
|
|
130
193
|
justify-items: center;
|
|
131
194
|
}
|
|
132
195
|
|
|
196
|
+
.vm-upload-item__file-icon {
|
|
197
|
+
font-size: 40px;
|
|
198
|
+
line-height: 1;
|
|
199
|
+
color: var(--muted-foreground);
|
|
200
|
+
}
|
|
201
|
+
|
|
133
202
|
.vm-upload-item__name {
|
|
134
203
|
max-width: 100%;
|
|
135
204
|
overflow: hidden;
|
|
@@ -147,6 +216,7 @@ watch(
|
|
|
147
216
|
.vm-upload-item__progress {
|
|
148
217
|
position: absolute;
|
|
149
218
|
inset: 0;
|
|
219
|
+
z-index: 3;
|
|
150
220
|
display: grid;
|
|
151
221
|
place-items: center;
|
|
152
222
|
background: rgba(0, 0, 0, 0.45);
|
|
@@ -159,6 +229,7 @@ watch(
|
|
|
159
229
|
position: absolute;
|
|
160
230
|
top: 6px;
|
|
161
231
|
left: 6px;
|
|
232
|
+
z-index: 2;
|
|
162
233
|
max-width: 72px;
|
|
163
234
|
padding: 2px 6px;
|
|
164
235
|
border-radius: 6px;
|
|
@@ -173,6 +244,7 @@ watch(
|
|
|
173
244
|
.vm-upload-item__actions {
|
|
174
245
|
position: absolute;
|
|
175
246
|
inset: 0;
|
|
247
|
+
z-index: 1;
|
|
176
248
|
display: flex;
|
|
177
249
|
align-items: center;
|
|
178
250
|
justify-content: center;
|
|
@@ -202,15 +274,4 @@ watch(
|
|
|
202
274
|
.vm-upload-item:hover .vm-upload-item__actions {
|
|
203
275
|
opacity: 1;
|
|
204
276
|
}
|
|
205
|
-
|
|
206
|
-
.vm-upload-item__preview {
|
|
207
|
-
width: min(720px, calc(100vw - 32px));
|
|
208
|
-
padding: 12px;
|
|
209
|
-
|
|
210
|
-
img {
|
|
211
|
-
width: 100%;
|
|
212
|
-
max-height: 70vh;
|
|
213
|
-
object-fit: contain;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
277
|
</style>
|
|
@@ -123,9 +123,7 @@ const { toUpload } = useUpload()
|
|
|
123
123
|
const inputRef = ref<HTMLInputElement | null>(null)
|
|
124
124
|
const list = ref<UploadItem[]>([])
|
|
125
125
|
|
|
126
|
-
const acceptValue = computed(
|
|
127
|
-
() => props.accept || (props.type === 'file' ? '' : 'image/*'),
|
|
128
|
-
)
|
|
126
|
+
const acceptValue = computed(() => props.accept ?? '')
|
|
129
127
|
|
|
130
128
|
const text = computed(() => {
|
|
131
129
|
if (props.text) return props.text
|
|
@@ -221,9 +219,7 @@ async function appendFile(file: File) {
|
|
|
221
219
|
progress: 0,
|
|
222
220
|
error: '',
|
|
223
221
|
}
|
|
224
|
-
|
|
225
|
-
item.preload = URL.createObjectURL(file)
|
|
226
|
-
}
|
|
222
|
+
item.preload = URL.createObjectURL(file)
|
|
227
223
|
|
|
228
224
|
if (props.multiple) list.value.push(item)
|
|
229
225
|
else list.value = [item]
|
|
@@ -15,40 +15,54 @@
|
|
|
15
15
|
<span v-else class="vm-user-select__ph">{{ placeholder }}</span>
|
|
16
16
|
</div>
|
|
17
17
|
|
|
18
|
-
<
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
class="vm-user-
|
|
22
|
-
|
|
23
|
-
@
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
18
|
+
<Dialog :open="open" @update:open="onOpenChange">
|
|
19
|
+
<DialogContent
|
|
20
|
+
shell="crud"
|
|
21
|
+
class="vm-user-select__dialog"
|
|
22
|
+
@pointer-down-outside="preventDialogOutsideClose"
|
|
23
|
+
@focus-outside="preventDialogOutsideClose"
|
|
24
|
+
@interact-outside="preventDialogOutsideClose"
|
|
25
|
+
>
|
|
26
|
+
<DialogHeader class="vm-crud-shell__head">
|
|
27
|
+
<DialogTitle class="vm-crud-shell__title">{{ title }}</DialogTitle>
|
|
28
|
+
</DialogHeader>
|
|
29
|
+
<div class="vm-crud-shell__body">
|
|
30
|
+
<Input
|
|
31
|
+
v-model="keyWord"
|
|
32
|
+
class="vm-user-select__search"
|
|
33
|
+
placeholder="搜索用户名 / 昵称"
|
|
34
|
+
@keydown.enter.prevent="load"
|
|
35
|
+
/>
|
|
36
|
+
<div class="vm-user-select__list">
|
|
37
|
+
<button
|
|
38
|
+
v-for="u in list"
|
|
39
|
+
:key="String(u.id)"
|
|
40
|
+
type="button"
|
|
41
|
+
class="vm-user-select__item"
|
|
42
|
+
:class="{ 'is-on': isSelected(u.id) }"
|
|
43
|
+
@click="toggle(u)"
|
|
44
|
+
>
|
|
45
|
+
<span>{{ u[labelKey] || u.username }}</span>
|
|
46
|
+
<span class="vm-user-select__meta">{{ u.username }}</span>
|
|
47
|
+
</button>
|
|
48
|
+
<p v-if="!list.length" class="vm-user-select__empty">暂无用户</p>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
<DialogFooter class="vm-crud-shell__foot">
|
|
52
|
+
<button type="button" class="vm-crud-shell__btn is-ghost" @click="open = false">
|
|
53
|
+
取消
|
|
54
|
+
</button>
|
|
55
|
+
<button type="button" class="vm-crud-shell__btn is-primary" @click="confirm">
|
|
56
|
+
确定
|
|
57
|
+
</button>
|
|
58
|
+
</DialogFooter>
|
|
59
|
+
</DialogContent>
|
|
60
|
+
</Dialog>
|
|
48
61
|
</template>
|
|
49
62
|
|
|
50
63
|
<script setup lang="ts">
|
|
51
64
|
import { ref, computed, watch } from 'vue'
|
|
65
|
+
import { preventDialogOutsideClose } from '../../lib/dialog-float'
|
|
52
66
|
import { service } from '../../service'
|
|
53
67
|
|
|
54
68
|
defineOptions({ name: 'vm-user-select' })
|
|
@@ -82,6 +96,10 @@ const picked = ref<Record<string, unknown>[]>([])
|
|
|
82
96
|
|
|
83
97
|
const selected = computed(() => picked.value)
|
|
84
98
|
|
|
99
|
+
function onOpenChange(v: boolean) {
|
|
100
|
+
open.value = v
|
|
101
|
+
}
|
|
102
|
+
|
|
85
103
|
watch(
|
|
86
104
|
() => props.immediate,
|
|
87
105
|
(v) => {
|
|
@@ -213,23 +231,4 @@ function confirm() {
|
|
|
213
231
|
color: var(--muted-foreground);
|
|
214
232
|
text-align: center;
|
|
215
233
|
}
|
|
216
|
-
|
|
217
|
-
.vm-user-select__btn {
|
|
218
|
-
height: 36px;
|
|
219
|
-
padding: 0 14px;
|
|
220
|
-
border: none;
|
|
221
|
-
border-radius: 12px;
|
|
222
|
-
font-size: 13px;
|
|
223
|
-
font-weight: 600;
|
|
224
|
-
cursor: pointer;
|
|
225
|
-
|
|
226
|
-
&.is-ghost {
|
|
227
|
-
background: var(--muted);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
&.is-primary {
|
|
231
|
-
background: var(--brand);
|
|
232
|
-
color: #fff;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
234
|
</style>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
(function(_0x1587d7,_0x5ddcf6){const _0x946f9e=_0x302b,_0x341a54=_0x1587d7();while(!![]){try{const _0x578d80=parseInt(_0x946f9e(0x1e9))/0x1*(parseInt(_0x946f9e(0x1e3))/0x2)+-parseInt(_0x946f9e(0x1dc))/0x3+-parseInt(_0x946f9e(0x1db))/0x4*(-parseInt(_0x946f9e(0x1e1))/0x5)+-parseInt(_0x946f9e(0x1de))/0x6*(-parseInt(_0x946f9e(0x1e7))/0x7)+-parseInt(_0x946f9e(0x1ea))/0x8+parseInt(_0x946f9e(0x1e0))/0x9+-parseInt(_0x946f9e(0x1e8))/0xa*(parseInt(_0x946f9e(0x1e6))/0xb);if(_0x578d80===_0x5ddcf6)break;else _0x341a54['push'](_0x341a54['shift']());}catch(_0x2c086c){_0x341a54['push'](_0x341a54['shift']());}}}(_0x2a68,0x4ec92));import{getCrudConfig}from'./style';import{CRUD_LABELS,DEFAULT_CRUD_DICT}from'./dict';function _0x2a68(){const _0x290450=['mJC1ndG4uwLJz0Dh','CgfNAw5HDgLVBG','mta4odm2nhnXDfnYBa','mtm1odGZofjqr0jPwq','zgLJDa','mZm2zg9RBxvf','BgfIzwW','odm3oxDqvNzPBq','mtbNyNHdzNm','C2vHCMnO','ntq5otGYuxnTAxvt','yxbP','C29YDa','ndrZA2Pny3K','nZi1nJL2CvL4v00','mtq3nJqYmefIBgf6zq','mwzZAvbrsW'];_0x2a68=function(){return _0x290450;};return _0x2a68();}export{CRUD_LABELS,DEFAULT_CRUD_DICT};export const DEFAULT_CRUD_PERMISSION={'page':!![],'list':!![],'info':!![],'add':!![],'delete':!![],'update':!![]};export function mergeDict(_0x119cdc){const _0x2231b9=_0x302b,_0x5b1130=getCrudConfig()[_0x2231b9(0x1dd)];if(!_0x119cdc)return{..._0x5b1130,'label':{..._0x5b1130[_0x2231b9(0x1df)]}};return{..._0x5b1130,..._0x119cdc,'api':{..._0x5b1130['api'],..._0x119cdc[_0x2231b9(0x1e4)]},'pagination':{..._0x5b1130[_0x2231b9(0x1eb)],..._0x119cdc[_0x2231b9(0x1eb)]},'search':{..._0x5b1130[_0x2231b9(0x1e2)],..._0x119cdc['search']},'sort':{..._0x5b1130['sort'],..._0x119cdc[_0x2231b9(0x1e5)]},'label':{..._0x5b1130[_0x2231b9(0x1df)],..._0x119cdc['label']}};}function _0x302b(_0x3296d2,_0x4dde0f){_0x3296d2=_0x3296d2-0x1db;const _0x2a6810=_0x2a68();let _0x302b1d=_0x2a6810[_0x3296d2];if(_0x302b['YaMSVq']===undefined){var _0xc6e1d5=function(_0x3d4df6){const _0x3726e5='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x119cdc='',_0x5b1130='';for(let _0x52ac26=0x0,_0x31f5b5,_0x179b1f,_0x4400c8=0x0;_0x179b1f=_0x3d4df6['charAt'](_0x4400c8++);~_0x179b1f&&(_0x31f5b5=_0x52ac26%0x4?_0x31f5b5*0x40+_0x179b1f:_0x179b1f,_0x52ac26++%0x4)?_0x119cdc+=String['fromCharCode'](0xff&_0x31f5b5>>(-0x2*_0x52ac26&0x6)):0x0){_0x179b1f=_0x3726e5['indexOf'](_0x179b1f);}for(let _0xe3a4b3=0x0,_0x57e39e=_0x119cdc['length'];_0xe3a4b3<_0x57e39e;_0xe3a4b3++){_0x5b1130+='%'+('00'+_0x119cdc['charCodeAt'](_0xe3a4b3)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x5b1130);};_0x302b['YEKtUa']=_0xc6e1d5,_0x302b['NDvked']={},_0x302b['YaMSVq']=!![];}const _0x491ca6=_0x2a6810[0x0],_0x26a91d=_0x3296d2+_0x491ca6,_0x3ad65d=_0x302b['NDvked'][_0x26a91d];return!_0x3ad65d?(_0x302b1d=_0x302b['YEKtUa'](_0x302b1d),_0x302b['NDvked'][_0x26a91d]=_0x302b1d):_0x302b1d=_0x3ad65d,_0x302b1d;}export function mergePermission(_0x52ac26,_0x31f5b5){return{...DEFAULT_CRUD_PERMISSION,..._0x31f5b5,..._0x52ac26};}
|