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,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-item__image"
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' && item.url">
13
- <video class="vm-upload-item__video" :src="item.url" muted />
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-file-icon :name="iconName" :size="56" />
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="item.url || item.preload" class="vm-upload-item__actions">
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
- <Dialog v-model:open="previewOpen">
56
- <DialogContent class="vm-upload-item__preview">
57
- <img :src="item.preload || item.url" alt="" />
58
- </DialogContent>
59
- </Dialog>
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 iconName = computed(() => extname(props.item.name || props.item.url) || 'file')
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-item__image,
117
- .vm-upload-item__video {
158
+ .vm-upload-item__media {
118
159
  width: 100%;
119
160
  height: 100%;
120
- object-fit: contain;
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
- if (item.type === 'image') {
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
- <vm-dialog v-model:open="open" :title="title" width="520px">
19
- <Input
20
- v-model="keyWord"
21
- class="vm-user-select__search"
22
- placeholder="搜索用户名 / 昵称"
23
- @keydown.enter.prevent="load"
24
- />
25
- <div class="vm-user-select__list">
26
- <button
27
- v-for="u in list"
28
- :key="String(u.id)"
29
- type="button"
30
- class="vm-user-select__item"
31
- :class="{ 'is-on': isSelected(u.id) }"
32
- @click="toggle(u)"
33
- >
34
- <span>{{ u[labelKey] || u.username }}</span>
35
- <span class="vm-user-select__meta">{{ u.username }}</span>
36
- </button>
37
- <p v-if="!list.length" class="vm-user-select__empty">暂无用户</p>
38
- </div>
39
- <template #footer>
40
- <button type="button" class="vm-user-select__btn is-ghost" @click="open = false">
41
- 取消
42
- </button>
43
- <button type="button" class="vm-user-select__btn is-primary" @click="confirm">
44
- 确定
45
- </button>
46
- </template>
47
- </vm-dialog>
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(_0x528241,_0x3c027b){const _0x47a297=_0x1117,_0x2c769f=_0x528241();while(!![]){try{const _0x5987c4=-parseInt(_0x47a297(0x192))/0x1+-parseInt(_0x47a297(0x18c))/0x2*(-parseInt(_0x47a297(0x18b))/0x3)+parseInt(_0x47a297(0x195))/0x4*(-parseInt(_0x47a297(0x191))/0x5)+-parseInt(_0x47a297(0x18d))/0x6+parseInt(_0x47a297(0x188))/0x7*(parseInt(_0x47a297(0x189))/0x8)+-parseInt(_0x47a297(0x190))/0x9+-parseInt(_0x47a297(0x18e))/0xa*(-parseInt(_0x47a297(0x196))/0xb);if(_0x5987c4===_0x3c027b)break;else _0x2c769f['push'](_0x2c769f['shift']());}catch(_0x4b6064){_0x2c769f['push'](_0x2c769f['shift']());}}}(_0x35de,0xe830f));import{getCrudConfig}from'./style';function _0x1117(_0x5d2070,_0x26aa07){_0x5d2070=_0x5d2070-0x187;const _0x35dea5=_0x35de();let _0x1117de=_0x35dea5[_0x5d2070];if(_0x1117['PzCGMG']===undefined){var _0x19cf10=function(_0x43c24a){const _0x3015ba='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x26e201='',_0x1dc808='';for(let _0x1705ed=0x0,_0x1899ab,_0x5a2e7a,_0x260729=0x0;_0x5a2e7a=_0x43c24a['charAt'](_0x260729++);~_0x5a2e7a&&(_0x1899ab=_0x1705ed%0x4?_0x1899ab*0x40+_0x5a2e7a:_0x5a2e7a,_0x1705ed++%0x4)?_0x26e201+=String['fromCharCode'](0xff&_0x1899ab>>(-0x2*_0x1705ed&0x6)):0x0){_0x5a2e7a=_0x3015ba['indexOf'](_0x5a2e7a);}for(let _0x41ea68=0x0,_0x1b23b3=_0x26e201['length'];_0x41ea68<_0x1b23b3;_0x41ea68++){_0x1dc808+='%'+('00'+_0x26e201['charCodeAt'](_0x41ea68)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x1dc808);};_0x1117['HNUVRV']=_0x19cf10,_0x1117['QEbcVj']={},_0x1117['PzCGMG']=!![];}const _0x4f2cac=_0x35dea5[0x0],_0x22f4d3=_0x5d2070+_0x4f2cac,_0x28ef7d=_0x1117['QEbcVj'][_0x22f4d3];return!_0x28ef7d?(_0x1117de=_0x1117['HNUVRV'](_0x1117de),_0x1117['QEbcVj'][_0x22f4d3]=_0x1117de):_0x1117de=_0x28ef7d,_0x1117de;}import{CRUD_LABELS,DEFAULT_CRUD_DICT}from'./dict';export{CRUD_LABELS,DEFAULT_CRUD_DICT};export const DEFAULT_CRUD_PERMISSION={'page':!![],'list':!![],'info':!![],'add':!![],'delete':!![],'update':!![]};export function mergeDict(_0x26e201){const _0x4b3a8f=_0x1117,_0x1dc808=getCrudConfig()['dict'];if(!_0x26e201)return{..._0x1dc808,'label':{..._0x1dc808[_0x4b3a8f(0x18a)]}};return{..._0x1dc808,..._0x26e201,'api':{..._0x1dc808[_0x4b3a8f(0x18f)],..._0x26e201[_0x4b3a8f(0x18f)]},'pagination':{..._0x1dc808[_0x4b3a8f(0x193)],..._0x26e201['pagination']},'search':{..._0x1dc808[_0x4b3a8f(0x187)],..._0x26e201['search']},'sort':{..._0x1dc808[_0x4b3a8f(0x194)],..._0x26e201[_0x4b3a8f(0x194)]},'label':{..._0x1dc808['label'],..._0x26e201[_0x4b3a8f(0x18a)]}};}function _0x35de(){const _0x3842d1=['mJe4mtq3mwnuquvAuG','mKfYD3PXta','otK5mte0CeTTAvvY','mtbZv3vPq3q','yxbP','nZyYmtC3nNzxseniAq','mtG2mZe1vKzmAenP','mZG1mZK4DKfmEuz5','CgfNAw5HDgLVBG','C29YDa','odHZtwzNvMW','mtu0nJK4nJfiEuvcs0e','C2vHCMnO','mZyYnJm5owPOvLDPvq','mtzvy1PjuNu','BgfIzwW'];_0x35de=function(){return _0x3842d1;};return _0x35de();}export function mergePermission(_0x1705ed,_0x1899ab){return{...DEFAULT_CRUD_PERMISSION,..._0x1899ab,..._0x1705ed};}
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};}