vome-core 0.3.69 → 0.3.71

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 (280) hide show
  1. package/dist/admin/components/ui/avatar/Avatar.vue +18 -18
  2. package/dist/admin/components/ui/avatar/AvatarFallback.vue +21 -21
  3. package/dist/admin/components/ui/avatar/AvatarImage.vue +16 -16
  4. package/dist/admin/components/ui/avatar/index.js +1 -1
  5. package/dist/admin/components/ui/badge/Badge.vue +26 -26
  6. package/dist/admin/components/ui/button/Button.vue +31 -31
  7. package/dist/admin/components/ui/button/index.d.ts +1 -1
  8. package/dist/admin/components/ui/checkbox/Checkbox.vue +35 -35
  9. package/dist/admin/components/ui/checkbox/index.js +1 -1
  10. package/dist/admin/components/ui/dialog/Dialog.vue +19 -19
  11. package/dist/admin/components/ui/dialog/DialogClose.vue +15 -15
  12. package/dist/admin/components/ui/dialog/DialogContent.vue +267 -267
  13. package/dist/admin/components/ui/dialog/DialogDescription.vue +23 -23
  14. package/dist/admin/components/ui/dialog/DialogFooter.vue +27 -27
  15. package/dist/admin/components/ui/dialog/DialogHeader.vue +17 -17
  16. package/dist/admin/components/ui/dialog/DialogOverlay.vue +21 -21
  17. package/dist/admin/components/ui/dialog/DialogScrollContent.vue +60 -60
  18. package/dist/admin/components/ui/dialog/DialogTitle.vue +23 -23
  19. package/dist/admin/components/ui/dialog/DialogTrigger.vue +15 -15
  20. package/dist/admin/components/ui/dialog/index.js +1 -1
  21. package/dist/admin/components/ui/dropdown-menu/DropdownMenu.vue +19 -19
  22. package/dist/admin/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +39 -39
  23. package/dist/admin/components/ui/dropdown-menu/DropdownMenuContent.vue +39 -39
  24. package/dist/admin/components/ui/dropdown-menu/DropdownMenuGroup.vue +15 -15
  25. package/dist/admin/components/ui/dropdown-menu/DropdownMenuItem.vue +31 -31
  26. package/dist/admin/components/ui/dropdown-menu/DropdownMenuLabel.vue +23 -23
  27. package/dist/admin/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +21 -21
  28. package/dist/admin/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +40 -40
  29. package/dist/admin/components/ui/dropdown-menu/DropdownMenuSeparator.vue +23 -23
  30. package/dist/admin/components/ui/dropdown-menu/DropdownMenuShortcut.vue +17 -17
  31. package/dist/admin/components/ui/dropdown-menu/DropdownMenuSub.vue +18 -18
  32. package/dist/admin/components/ui/dropdown-menu/DropdownMenuSubContent.vue +27 -27
  33. package/dist/admin/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +31 -31
  34. package/dist/admin/components/ui/dropdown-menu/DropdownMenuTrigger.vue +17 -17
  35. package/dist/admin/components/ui/dropdown-menu/index.js +1 -1
  36. package/dist/admin/components/ui/input/Input.vue +183 -183
  37. package/dist/admin/components/ui/input/index.js +1 -1
  38. package/dist/admin/components/ui/label/Label.vue +26 -26
  39. package/dist/admin/components/ui/label/index.js +1 -1
  40. package/dist/admin/components/ui/scroll-area/ScrollArea.vue +33 -33
  41. package/dist/admin/components/ui/scroll-area/ScrollBar.vue +32 -32
  42. package/dist/admin/components/ui/scroll-area/index.js +1 -1
  43. package/dist/admin/components/ui/select/Select.vue +19 -19
  44. package/dist/admin/components/ui/select/SelectContent.vue +51 -51
  45. package/dist/admin/components/ui/select/SelectGroup.vue +15 -15
  46. package/dist/admin/components/ui/select/SelectItem.vue +53 -53
  47. package/dist/admin/components/ui/select/SelectItemText.vue +15 -15
  48. package/dist/admin/components/ui/select/SelectLabel.vue +17 -17
  49. package/dist/admin/components/ui/select/SelectScrollDownButton.vue +26 -26
  50. package/dist/admin/components/ui/select/SelectScrollUpButton.vue +26 -26
  51. package/dist/admin/components/ui/select/SelectSeparator.vue +19 -19
  52. package/dist/admin/components/ui/select/SelectTrigger.vue +44 -44
  53. package/dist/admin/components/ui/select/SelectValue.vue +15 -15
  54. package/dist/admin/components/ui/select/index.js +1 -1
  55. package/dist/admin/components/ui/separator/Separator.vue +29 -29
  56. package/dist/admin/components/ui/separator/index.js +1 -1
  57. package/dist/admin/components/ui/sheet/Sheet.vue +19 -19
  58. package/dist/admin/components/ui/sheet/SheetClose.vue +15 -15
  59. package/dist/admin/components/ui/sheet/SheetContent.vue +63 -63
  60. package/dist/admin/components/ui/sheet/SheetDescription.vue +21 -21
  61. package/dist/admin/components/ui/sheet/SheetFooter.vue +16 -16
  62. package/dist/admin/components/ui/sheet/SheetHeader.vue +15 -15
  63. package/dist/admin/components/ui/sheet/SheetOverlay.vue +21 -21
  64. package/dist/admin/components/ui/sheet/SheetTitle.vue +21 -21
  65. package/dist/admin/components/ui/sheet/SheetTrigger.vue +15 -15
  66. package/dist/admin/components/ui/sheet/index.js +1 -1
  67. package/dist/admin/components/ui/sonner/Toaster.vue +117 -117
  68. package/dist/admin/components/ui/sonner/index.js +1 -1
  69. package/dist/admin/components/ui/table/Table.vue +16 -16
  70. package/dist/admin/components/ui/table/TableBody.vue +17 -17
  71. package/dist/admin/components/ui/table/TableCaption.vue +17 -17
  72. package/dist/admin/components/ui/table/TableCell.vue +22 -22
  73. package/dist/admin/components/ui/table/TableEmpty.vue +34 -34
  74. package/dist/admin/components/ui/table/TableFooter.vue +17 -17
  75. package/dist/admin/components/ui/table/TableHead.vue +17 -17
  76. package/dist/admin/components/ui/table/TableHeader.vue +17 -17
  77. package/dist/admin/components/ui/table/TableRow.vue +17 -17
  78. package/dist/admin/components/ui/table/index.js +1 -1
  79. package/dist/admin/components/ui/tabs/Tabs.vue +24 -24
  80. package/dist/admin/components/ui/tabs/TabsContent.vue +21 -21
  81. package/dist/admin/components/ui/tabs/TabsList.vue +24 -24
  82. package/dist/admin/components/ui/tabs/TabsTrigger.vue +26 -26
  83. package/dist/admin/components/ui/tabs/index.js +1 -1
  84. package/dist/admin/components/ui/tooltip/Tooltip.vue +19 -19
  85. package/dist/admin/components/ui/tooltip/TooltipContent.vue +34 -34
  86. package/dist/admin/components/ui/tooltip/TooltipProvider.vue +14 -14
  87. package/dist/admin/components/ui/tooltip/TooltipTrigger.vue +15 -15
  88. package/dist/admin/components/ui/tooltip/index.js +1 -1
  89. package/dist/admin/components/vm-action-btn.vue +136 -136
  90. package/dist/admin/components/vm-aside-list-item.vue +136 -136
  91. package/dist/admin/components/vm-aside.vue +229 -229
  92. package/dist/admin/components/vm-auto-perm-dialog.vue +371 -371
  93. package/dist/admin/components/vm-card-grid.vue +37 -37
  94. package/dist/admin/components/vm-card-head.vue +45 -45
  95. package/dist/admin/components/vm-card-meta.vue +22 -22
  96. package/dist/admin/components/vm-card-title.vue +56 -56
  97. package/dist/admin/components/vm-card.vue +176 -176
  98. package/dist/admin/components/vm-check-tree.vue +340 -340
  99. package/dist/admin/components/vm-clamp-text.vue +39 -39
  100. package/dist/admin/components/vm-dept-tree.vue +497 -497
  101. package/dist/admin/components/vm-empty.vue +68 -68
  102. package/dist/admin/components/vm-eps-perm-picker.vue +123 -123
  103. package/dist/admin/components/vm-form-dialog.vue +632 -632
  104. package/dist/admin/components/vm-group-cascader.vue +560 -560
  105. package/dist/admin/components/vm-icon-btn.vue +88 -88
  106. package/dist/admin/components/vm-icon-picker.vue +647 -647
  107. package/dist/admin/components/vm-inline-search.vue +61 -61
  108. package/dist/admin/components/vm-loading.vue +114 -114
  109. package/dist/admin/components/vm-markdown.vue +405 -405
  110. package/dist/admin/components/vm-marquee.vue +265 -265
  111. package/dist/admin/components/vm-page-tabs.vue +94 -94
  112. package/dist/admin/components/vm-perm-section.vue +57 -57
  113. package/dist/admin/components/vm-ri-icon.vue +23 -23
  114. package/dist/admin/components/vm-role-picker.vue +314 -314
  115. package/dist/admin/components/vm-split-layout.vue +106 -106
  116. package/dist/admin/components/vm-thumb.vue +49 -49
  117. package/dist/admin/components/vm-view-path-picker.vue +59 -59
  118. package/dist/admin/crud/auto-fields.d.ts +7 -0
  119. package/dist/admin/crud/auto-fields.js +1 -1
  120. package/dist/admin/crud/components/vm-avatar.vue +104 -104
  121. package/dist/admin/crud/components/vm-color-picker.vue +1626 -1626
  122. package/dist/admin/crud/components/vm-column-custom.vue +193 -193
  123. package/dist/admin/crud/components/vm-context-menu.vue +105 -105
  124. package/dist/admin/crud/components/vm-copy-btn.vue +102 -102
  125. package/dist/admin/crud/components/vm-date-calendar-panel.vue +537 -537
  126. package/dist/admin/crud/components/vm-date-picker.vue +75 -75
  127. package/dist/admin/crud/components/vm-date-range.vue +88 -88
  128. package/dist/admin/crud/components/vm-date-text.vue +42 -42
  129. package/dist/admin/crud/components/vm-datetime-field.vue +215 -215
  130. package/dist/admin/crud/components/vm-datetime-picker.vue +9 -9
  131. package/dist/admin/crud/components/vm-dict-tag.vue +112 -112
  132. package/dist/admin/crud/components/vm-ellipsis-text.vue +34 -34
  133. package/dist/admin/crud/components/vm-form-hint.vue +81 -81
  134. package/dist/admin/crud/components/vm-json-code.vue +41 -41
  135. package/dist/admin/crud/components/vm-json-editor.vue +161 -161
  136. package/dist/admin/crud/components/vm-json-kv-editor.vue +318 -318
  137. package/dist/admin/crud/components/vm-kv-row.vue +51 -51
  138. package/dist/admin/crud/components/vm-multi-select.vue +279 -279
  139. package/dist/admin/crud/components/vm-number-range.vue +204 -204
  140. package/dist/admin/crud/components/vm-preview-viewer.vue +894 -894
  141. package/dist/admin/crud/components/vm-radio.vue +87 -87
  142. package/dist/admin/crud/components/vm-richtext.vue +313 -313
  143. package/dist/admin/crud/components/vm-select.vue +570 -570
  144. package/dist/admin/crud/components/vm-status-tag.vue +158 -158
  145. package/dist/admin/crud/components/vm-switch.vue +158 -158
  146. package/dist/admin/crud/components/vm-tag-list.vue +108 -108
  147. package/dist/admin/crud/components/vm-text-link.vue +52 -52
  148. package/dist/admin/crud/components/vm-toggle-switch.vue +84 -84
  149. package/dist/admin/crud/components/vm-tree-select.vue +450 -450
  150. package/dist/admin/crud/components/vm-upload-item.vue +298 -298
  151. package/dist/admin/crud/components/vm-upload.vue +436 -436
  152. package/dist/admin/crud/config.js +1 -1
  153. package/dist/admin/crud/confirm.js +1 -1
  154. package/dist/admin/crud/crud-page-request.js +1 -1
  155. package/dist/admin/crud/data-i18n.js +1 -1
  156. package/dist/admin/crud/dict.js +1 -1
  157. package/dist/admin/crud/export-fetch.js +1 -1
  158. package/dist/admin/crud/index.d.ts +1 -1
  159. package/dist/admin/crud/index.js +1 -0
  160. package/dist/admin/crud/key.js +1 -1
  161. package/dist/admin/crud/plugins.js +1 -1
  162. package/dist/admin/crud/span.js +1 -1
  163. package/dist/admin/crud/style.js +1 -1
  164. package/dist/admin/crud/validate.js +1 -1
  165. package/dist/admin/crud/vm-add-btn.vue +70 -70
  166. package/dist/admin/crud/vm-adv-search.vue +83 -83
  167. package/dist/admin/crud/vm-crud.vue +711 -711
  168. package/dist/admin/crud/vm-data-i18n-btn.vue +456 -456
  169. package/dist/admin/crud/vm-data-i18n-lang.vue +183 -183
  170. package/dist/admin/crud/vm-export-btn.vue +183 -183
  171. package/dist/admin/crud/vm-flex.vue +14 -14
  172. package/dist/admin/crud/vm-import-btn.vue +747 -747
  173. package/dist/admin/crud/vm-multi-delete-btn.vue +49 -49
  174. package/dist/admin/crud/vm-pagination.vue +212 -212
  175. package/dist/admin/crud/vm-refresh-btn.vue +35 -35
  176. package/dist/admin/crud/vm-row.vue +55 -55
  177. package/dist/admin/crud/vm-search-key.vue +78 -78
  178. package/dist/admin/crud/vm-search.vue +466 -466
  179. package/dist/admin/crud/vm-table.vue +1390 -1390
  180. package/dist/admin/crud/vm-tabs.vue +94 -94
  181. package/dist/admin/crud/vm-toolbar.vue +357 -357
  182. package/dist/admin/crud/vm-upsert.vue +765 -765
  183. package/dist/admin/directives/perm.js +1 -1
  184. package/dist/admin/env.d.ts +1 -1
  185. package/dist/admin/hooks/useUpload.js +1 -1
  186. package/dist/admin/hooks/useVome.d.ts +2 -2
  187. package/dist/admin/lib/browser.js +1 -1
  188. package/dist/admin/lib/cn.js +1 -1
  189. package/dist/admin/lib/dialog-float.js +1 -1
  190. package/dist/admin/lib/export-excel.js +1 -1
  191. package/dist/admin/lib/import-excel.js +1 -1
  192. package/dist/admin/lib/json.js +1 -1
  193. package/dist/admin/lib/menu.js +1 -1
  194. package/dist/admin/lib/micro-locale.js +1 -1
  195. package/dist/admin/lib/micro-theme.js +1 -1
  196. package/dist/admin/lib/shell.js +1 -1
  197. package/dist/admin/lib/theme-color-options.js +1 -1
  198. package/dist/admin/lib/tree.js +1 -1
  199. package/dist/admin/lib/upload.js +1 -1
  200. package/dist/admin/lib/video-frame.js +1 -1
  201. package/dist/admin/router/create.js +1 -1
  202. package/dist/admin/router/guards.js +1 -1
  203. package/dist/admin/stores/app.d.ts +13 -1
  204. package/dist/admin/stores/tags.d.ts +14 -1
  205. package/dist/admin/stores/user.d.ts +48 -1
  206. package/dist/admin/styles/base.css +121 -121
  207. package/dist/admin/vendor/browser-image-compression.js +1144 -1144
  208. package/dist/admin/vendor/decimal-js.js +12 -12
  209. package/dist/admin/vendor/xlsx.js +263 -263
  210. package/dist/ai/index.js +1 -1
  211. package/dist/client/ai-model-registry.js +1 -1
  212. package/dist/client/base.js +1 -1
  213. package/dist/client/configure.js +1 -1
  214. package/dist/client/dict.js +1 -1
  215. package/dist/client/eps-payload.js +1 -1
  216. package/dist/client/eps.d.ts +7 -4
  217. package/dist/client/eps.js +1 -1
  218. package/dist/client/index.d.ts +1 -1
  219. package/dist/client/index.js +1 -1
  220. package/dist/client/locale-change.js +1 -1
  221. package/dist/client/locale-store.d.ts +45 -1
  222. package/dist/client/locale-store.js +1 -1
  223. package/dist/client/path.js +1 -1
  224. package/dist/client/service.d.ts +8 -0
  225. package/dist/client/service.js +1 -1
  226. package/dist/client/vite-admin.js +1 -1
  227. package/dist/client/vite-auto-import.js +1 -1
  228. package/dist/client/vite-micro-proxy.js +1 -1
  229. package/dist/client/vite-plugin-eps.d.ts +5 -0
  230. package/dist/client/vite-plugin-eps.js +1 -1
  231. package/dist/client/vite-resolve.js +1 -1
  232. package/dist/config/index.js +1 -1
  233. package/dist/core/index.d.ts +2 -2
  234. package/dist/index.js +1 -1
  235. package/dist/orm/index.d.ts +2 -0
  236. package/dist/orm/patch-bun-sql-jsonb.d.ts +1 -0
  237. package/dist/routing/context.d.ts +12 -2
  238. package/dist/routing/eps.d.ts +4 -1
  239. package/dist/routing/index.d.ts +2 -1
  240. package/dist/routing/metadata.d.ts +7 -3
  241. package/dist/server/index.d.ts +4 -2
  242. package/dist/server/index.js +1 -1
  243. package/dist/shared/datetime-picker.js +1 -1
  244. package/dist/shared/decimal.js +1 -1
  245. package/dist/shared/excel.js +1 -1
  246. package/dist/shared/index.js +1 -1
  247. package/dist/shared/locale-json.js +1 -1
  248. package/dist/shared/tree.js +1 -1
  249. package/dist/typings/routing/eps.d.ts +3 -1
  250. package/dist/typings/routing/meta.d.ts +15 -0
  251. package/package.json +1 -1
  252. package/typings/admin/base/auth.ts +24 -24
  253. package/typings/admin/base/permission.ts +19 -19
  254. package/typings/admin/comm/cascader.ts +7 -7
  255. package/typings/admin/comm/check-tree.ts +7 -7
  256. package/typings/admin/comm/crud.ts +427 -427
  257. package/typings/admin/comm/eps.ts +88 -86
  258. package/typings/admin/comm/request.ts +16 -16
  259. package/typings/admin/comm/service.ts +43 -43
  260. package/typings/admin/comm/upload.ts +39 -39
  261. package/typings/admin/theme.ts +10 -10
  262. package/typings/admin/vome/browser.ts +11 -11
  263. package/typings/admin/vome/module.ts +13 -13
  264. package/typings/admin/vome/tags.ts +9 -9
  265. package/typings/comm/crud.ts +83 -83
  266. package/typings/comm/page.ts +18 -18
  267. package/typings/comm/scan.ts +6 -6
  268. package/typings/common.ts +4 -4
  269. package/typings/context/data.ts +10 -10
  270. package/typings/ext/market-seat.ts +42 -42
  271. package/typings/ext/market.ts +55 -55
  272. package/typings/ext/module.ts +64 -64
  273. package/typings/ext/plugin.ts +38 -38
  274. package/typings/ioc/metadata.ts +34 -34
  275. package/typings/logger/config.ts +6 -6
  276. package/typings/routing/crud.ts +199 -199
  277. package/typings/routing/eps.ts +90 -88
  278. package/typings/routing/meta.ts +80 -63
  279. package/typings/routing/register.ts +21 -21
  280. package/typings/routing/scan-context.ts +7 -7
@@ -1,436 +1,436 @@
1
- <template>
2
- <div
3
- class="vm-upload"
4
- :class="[
5
- `vm-upload--${type}`,
6
- {
7
- 'is-disabled': disabled,
8
- 'is-multiple': multiple,
9
- 'is-drag': drag,
10
- },
11
- ]"
12
- >
13
- <div
14
- v-if="type === 'file' && isAdd"
15
- class="vm-upload__file-btn"
16
- :class="{ 'is-dragging': dragging }"
17
- @dragenter="onDragEnter"
18
- @dragover="onDragOver"
19
- @dragleave="onDragLeave"
20
- @drop="onDrop"
21
- >
22
- <label class="vm-upload__pick">
23
- <input
24
- ref="inputRef"
25
- type="file"
26
- class="vm-upload__input"
27
- :accept="acceptValue"
28
- :multiple="multiple"
29
- :disabled="disabled"
30
- @change="onInputChange"
31
- />
32
- <Button type="button" variant="softSuccess" :disabled="disabled">
33
- {{ text }}
34
- </Button>
35
- </label>
36
- </div>
37
-
38
- <div class="vm-upload__list">
39
- <div
40
- v-for="(item, index) in list"
41
- :key="item.uid"
42
- class="vm-upload__item"
43
- :style="itemStyle"
44
- >
45
- <vm-upload-item
46
- :item="item"
47
- :disabled="disabled"
48
- :show-tag="showTag"
49
- :resolve-download-url="resolveDownloadUrl"
50
- @remove="remove(index)"
51
- />
52
- </div>
53
-
54
- <!-- 拖拽只绑在触发格上:默认即 size 正方形,不是表单 span 整宽 -->
55
- <label
56
- v-if="isAdd && (type === 'image' || drag)"
57
- class="vm-upload__trigger"
58
- :class="{ 'is-drag': drag, 'is-dragging': dragging }"
59
- :style="drag ? undefined : itemStyle"
60
- @dragenter="onDragEnter"
61
- @dragover="onDragOver"
62
- @dragleave="onDragLeave"
63
- @drop="onDrop"
64
- >
65
- <input
66
- ref="inputRef"
67
- type="file"
68
- class="vm-upload__input"
69
- :accept="acceptValue"
70
- :multiple="multiple"
71
- :disabled="disabled"
72
- @change="onInputChange"
73
- />
74
- <slot>
75
- <div v-if="drag" class="vm-upload__drag">
76
- <i class="ri-upload-cloud-2-line" />
77
- <span>{{ crudFormat(crudLabel('uploadHint'), limitSize) }}</span>
78
- </div>
79
- <div v-else class="vm-upload__demo">
80
- <i class="ri-image-add-line" />
81
- <span v-if="text">{{ text }}</span>
82
- </div>
83
- </slot>
84
- </label>
85
- </div>
86
- </div>
87
- </template>
88
-
89
- <script setup lang="ts">
90
- import { crudFormat, crudLabel } from '../style'
91
- import { ref, computed, watch, reactive } from 'vue'
92
- import { toast } from 'vue-sonner'
93
- import { useUpload } from '../../hooks/useUpload'
94
- import {
95
- compressImage,
96
- isCompressibleImage,
97
- } from '../../lib/compress-image'
98
- import { getFileType, getUrls, uploadUid } from '../../lib/upload'
99
- import type { UploadItem } from '../../../../typings/admin/comm/upload'
100
-
101
- defineOptions({ name: 'vm-upload' })
102
-
103
- const props = withDefaults(
104
- defineProps<{
105
- modelValue?: string | string[]
106
- type?: 'image' | 'file'
107
- accept?: string
108
- multiple?: boolean
109
- limit?: number
110
- limitSize?: number
111
- disabled?: boolean
112
- text?: string
113
- size?: number | string | Array<number | string>
114
- showTag?: boolean
115
- drag?: boolean
116
- prefixPath?: string
117
- /** 上传前压缩图片;默认 true,传 false 关闭 */
118
- compress?: boolean
119
- beforeUpload?: (file: File) => boolean | Promise<boolean>
120
- /** 私有文件:预览内点「下载」时先解析可访问短链 */
121
- resolveDownloadUrl?: (url: string) => Promise<string | undefined>
122
- }>(),
123
- {
124
- modelValue: '',
125
- type: 'image',
126
- multiple: false,
127
- limit: 9,
128
- limitSize: 100,
129
- disabled: false,
130
- text: '',
131
- size: 120,
132
- showTag: true,
133
- drag: false,
134
- prefixPath: 'app/public',
135
- compress: true,
136
- },
137
- )
138
-
139
- const emit = defineEmits<{
140
- 'update:modelValue': [v: string | string[]]
141
- change: [v: string | string[]]
142
- success: [item: UploadItem]
143
- error: [item: UploadItem]
144
- }>()
145
-
146
- const { toUpload } = useUpload()
147
- const inputRef = ref<HTMLInputElement | null>(null)
148
- const list = ref<UploadItem[]>([])
149
- /** 拖入高亮;用计数避免子元素 leave 误关 */
150
- const dragging = ref(false)
151
- let dragDepth = 0
152
-
153
- const acceptValue = computed(() => props.accept ?? '')
154
-
155
- const text = computed(() => {
156
- if (props.text) return props.text
157
- return props.type === 'file' ? crudLabel('selectFile') : crudLabel('selectImage')
158
- })
159
-
160
- const itemStyle = computed(() => {
161
- const raw = Array.isArray(props.size) ? props.size[0] : props.size
162
- const n = typeof raw === 'number' ? `${raw}px` : raw
163
- return { width: n, height: n }
164
- })
165
-
166
- const isAdd = computed(() => {
167
- if (props.disabled) return false
168
- if (props.multiple) return list.value.length < props.limit
169
- return list.value.length === 0
170
- })
171
-
172
- function normalizeValue(val: string | string[] | undefined) {
173
- if (Array.isArray(val)) return val.filter(Boolean)
174
- if (val) return [val]
175
- return []
176
- }
177
-
178
- function syncFromModel(val: string | string[] | undefined) {
179
- const urls = normalizeValue(val)
180
- if (list.value.some((item) => item.progress < 100)) return
181
- list.value = urls.map((url, index) => {
182
- const old = list.value[index]
183
- return {
184
- uid: old?.uid || uploadUid(),
185
- name: old?.name || url.split('/').pop() || url,
186
- url,
187
- preload: old?.url === url ? old.preload : url,
188
- type: getFileType(url),
189
- progress: 100,
190
- error: '',
191
- }
192
- })
193
- }
194
-
195
- watch(
196
- () => props.modelValue,
197
- (val) => syncFromModel(val),
198
- { immediate: true },
199
- )
200
-
201
- function emitValue() {
202
- const urls = getUrls(list.value.filter((item) => item.url))
203
- const val = props.multiple ? urls : urls[0] || ''
204
- emit('update:modelValue', val)
205
- emit('change', val)
206
- }
207
-
208
- function remove(index: number) {
209
- const item = list.value[index]
210
- if (item?.preload?.startsWith('blob:')) {
211
- URL.revokeObjectURL(item.preload)
212
- }
213
- list.value.splice(index, 1)
214
- emitValue()
215
- }
216
-
217
- async function onInputChange(e: Event) {
218
- const input = e.target as HTMLInputElement
219
- const files = Array.from(input.files ?? [])
220
- input.value = ''
221
- await ingestFiles(files)
222
- }
223
-
224
- function hasFilePayload(dt: DataTransfer | null) {
225
- return !!dt && Array.from(dt.types || []).includes('Files')
226
- }
227
-
228
- function onDragEnter(e: DragEvent) {
229
- if (props.disabled || !hasFilePayload(e.dataTransfer)) return
230
- e.preventDefault()
231
- e.stopPropagation()
232
- dragDepth += 1
233
- dragging.value = true
234
- }
235
-
236
- function onDragOver(e: DragEvent) {
237
- if (props.disabled || !hasFilePayload(e.dataTransfer)) return
238
- e.preventDefault()
239
- e.stopPropagation()
240
- if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy'
241
- }
242
-
243
- function onDragLeave(e: DragEvent) {
244
- if (props.disabled || !hasFilePayload(e.dataTransfer)) return
245
- e.preventDefault()
246
- e.stopPropagation()
247
- dragDepth = Math.max(0, dragDepth - 1)
248
- if (dragDepth === 0) dragging.value = false
249
- }
250
-
251
- async function onDrop(e: DragEvent) {
252
- if (props.disabled) return
253
- e.preventDefault()
254
- e.stopPropagation()
255
- dragDepth = 0
256
- dragging.value = false
257
- const files = Array.from(e.dataTransfer?.files ?? [])
258
- await ingestFiles(files)
259
- }
260
-
261
- async function ingestFiles(files: File[]) {
262
- for (const file of files) {
263
- if (!isAdd.value) {
264
- toast.warning(crudFormat(crudLabel('maxUpload'), props.limit))
265
- break
266
- }
267
- await appendFile(file)
268
- }
269
- }
270
-
271
- async function appendFile(raw: File) {
272
- if (raw.size / 1024 / 1024 >= props.limitSize) {
273
- toast.error(crudFormat(crudLabel('fileTooLarge'), props.limitSize))
274
- return
275
- }
276
- if (props.beforeUpload) {
277
- const ok = await props.beforeUpload(raw)
278
- if (!ok) return
279
- }
280
-
281
- // 先入列表 + 原图预览,再压缩 / 上传(避免压缩期间空白)
282
- // 必须 reactive:否则改 progress 绕过 Proxy,UI 会 0% 直接跳 100%
283
- const item = reactive<UploadItem>({
284
- uid: uploadUid(),
285
- name: raw.name,
286
- url: '',
287
- type: getFileType(raw.name),
288
- size: raw.size,
289
- progress: 0,
290
- error: '',
291
- preload: URL.createObjectURL(raw),
292
- })
293
- if (props.multiple) list.value.push(item)
294
- else list.value = [item]
295
-
296
- const doCompress = props.compress && isCompressibleImage(raw.type)
297
-
298
- const setProgress = (n: number) => {
299
- item.progress = Math.max(0, Math.min(100, Math.floor(n)))
300
- }
301
-
302
- try {
303
- let file = raw
304
- if (doCompress) {
305
- file = await compressImage(raw, {
306
- onProgress: (p) => {
307
- // 压缩占整体 0–30%
308
- setProgress((p / 100) * 30)
309
- },
310
- })
311
- setProgress(30)
312
- item.size = file.size
313
- item.name = file.name
314
- }
315
-
316
- const res = await toUpload(file, {
317
- prefixPath: props.prefixPath,
318
- onProgress: (n) => {
319
- // 直传 OSS:有压缩则 30–100,否则 0–100(xhr.upload.onprogress)
320
- if (doCompress) {
321
- setProgress(30 + (n / 100) * 70)
322
- } else {
323
- setProgress(n)
324
- }
325
- },
326
- })
327
- item.url = res.url
328
- setProgress(100)
329
- item.error = ''
330
- emit('success', item)
331
- emitValue()
332
- } catch (e) {
333
- item.progress = -1
334
- item.error = e instanceof Error ? e.message : crudLabel('uploadFailed')
335
- emit('error', item)
336
- }
337
- }
338
-
339
- defineExpose({ list, remove })
340
- </script>
341
-
342
- <style lang="scss" scoped>
343
- .vm-upload {
344
- width: 100%;
345
- }
346
-
347
- .vm-upload__input {
348
- display: none;
349
- }
350
-
351
- .vm-upload__list {
352
- display: flex;
353
- flex-wrap: wrap;
354
- gap: 8px;
355
- }
356
-
357
- .vm-upload__item,
358
- .vm-upload__trigger {
359
- flex-shrink: 0;
360
- box-sizing: border-box;
361
- }
362
-
363
- .vm-upload__trigger {
364
- display: grid;
365
- place-items: center;
366
- border: 1px dashed var(--border);
367
- border-radius: 10px;
368
- background: var(--muted);
369
- color: var(--muted-foreground);
370
- cursor: pointer;
371
- transition:
372
- border-color 0.15s ease,
373
- color 0.15s ease,
374
- background 0.15s ease,
375
- outline-color 0.15s ease;
376
-
377
- &:hover {
378
- border-color: var(--brand);
379
- color: var(--brand);
380
- background: var(--brand-soft);
381
- }
382
-
383
- /* 仅 drag 大区铺满字段宽;默认靠 itemStyle 固定正方形 */
384
- &.is-drag {
385
- width: 100%;
386
- min-height: 120px;
387
- }
388
-
389
- &.is-dragging {
390
- border-color: var(--brand);
391
- color: var(--brand);
392
- background: var(--brand-soft);
393
- outline: 2px dashed var(--brand);
394
- outline-offset: 2px;
395
- }
396
- }
397
-
398
- .vm-upload__demo,
399
- .vm-upload__drag {
400
- display: grid;
401
- gap: 6px;
402
- place-items: center;
403
- padding: 10px;
404
- font-size: 12px;
405
- text-align: center;
406
-
407
- i {
408
- font-size: 28px;
409
- }
410
- }
411
-
412
- .vm-upload__file-btn {
413
- display: inline-block;
414
- margin-bottom: 8px;
415
- border-radius: 8px;
416
- transition:
417
- outline-color 0.15s ease,
418
- background 0.15s ease;
419
-
420
- &.is-dragging {
421
- outline: 2px dashed var(--brand);
422
- outline-offset: 4px;
423
- background: var(--brand-soft);
424
- }
425
- }
426
-
427
- .vm-upload__pick {
428
- display: inline-block;
429
- cursor: pointer;
430
- }
431
-
432
- .vm-upload.is-disabled {
433
- opacity: 0.6;
434
- pointer-events: none;
435
- }
436
- </style>
1
+ <template>
2
+ <div
3
+ class="vm-upload"
4
+ :class="[
5
+ `vm-upload--${type}`,
6
+ {
7
+ 'is-disabled': disabled,
8
+ 'is-multiple': multiple,
9
+ 'is-drag': drag,
10
+ },
11
+ ]"
12
+ >
13
+ <div
14
+ v-if="type === 'file' && isAdd"
15
+ class="vm-upload__file-btn"
16
+ :class="{ 'is-dragging': dragging }"
17
+ @dragenter="onDragEnter"
18
+ @dragover="onDragOver"
19
+ @dragleave="onDragLeave"
20
+ @drop="onDrop"
21
+ >
22
+ <label class="vm-upload__pick">
23
+ <input
24
+ ref="inputRef"
25
+ type="file"
26
+ class="vm-upload__input"
27
+ :accept="acceptValue"
28
+ :multiple="multiple"
29
+ :disabled="disabled"
30
+ @change="onInputChange"
31
+ />
32
+ <Button type="button" variant="softSuccess" :disabled="disabled">
33
+ {{ text }}
34
+ </Button>
35
+ </label>
36
+ </div>
37
+
38
+ <div class="vm-upload__list">
39
+ <div
40
+ v-for="(item, index) in list"
41
+ :key="item.uid"
42
+ class="vm-upload__item"
43
+ :style="itemStyle"
44
+ >
45
+ <vm-upload-item
46
+ :item="item"
47
+ :disabled="disabled"
48
+ :show-tag="showTag"
49
+ :resolve-download-url="resolveDownloadUrl"
50
+ @remove="remove(index)"
51
+ />
52
+ </div>
53
+
54
+ <!-- 拖拽只绑在触发格上:默认即 size 正方形,不是表单 span 整宽 -->
55
+ <label
56
+ v-if="isAdd && (type === 'image' || drag)"
57
+ class="vm-upload__trigger"
58
+ :class="{ 'is-drag': drag, 'is-dragging': dragging }"
59
+ :style="drag ? undefined : itemStyle"
60
+ @dragenter="onDragEnter"
61
+ @dragover="onDragOver"
62
+ @dragleave="onDragLeave"
63
+ @drop="onDrop"
64
+ >
65
+ <input
66
+ ref="inputRef"
67
+ type="file"
68
+ class="vm-upload__input"
69
+ :accept="acceptValue"
70
+ :multiple="multiple"
71
+ :disabled="disabled"
72
+ @change="onInputChange"
73
+ />
74
+ <slot>
75
+ <div v-if="drag" class="vm-upload__drag">
76
+ <i class="ri-upload-cloud-2-line" />
77
+ <span>{{ crudFormat(crudLabel('uploadHint'), limitSize) }}</span>
78
+ </div>
79
+ <div v-else class="vm-upload__demo">
80
+ <i class="ri-image-add-line" />
81
+ <span v-if="text">{{ text }}</span>
82
+ </div>
83
+ </slot>
84
+ </label>
85
+ </div>
86
+ </div>
87
+ </template>
88
+
89
+ <script setup lang="ts">
90
+ import { crudFormat, crudLabel } from '../style'
91
+ import { ref, computed, watch, reactive } from 'vue'
92
+ import { toast } from 'vue-sonner'
93
+ import { useUpload } from '../../hooks/useUpload'
94
+ import {
95
+ compressImage,
96
+ isCompressibleImage,
97
+ } from '../../lib/compress-image'
98
+ import { getFileType, getUrls, uploadUid } from '../../lib/upload'
99
+ import type { UploadItem } from '../../../../typings/admin/comm/upload'
100
+
101
+ defineOptions({ name: 'vm-upload' })
102
+
103
+ const props = withDefaults(
104
+ defineProps<{
105
+ modelValue?: string | string[]
106
+ type?: 'image' | 'file'
107
+ accept?: string
108
+ multiple?: boolean
109
+ limit?: number
110
+ limitSize?: number
111
+ disabled?: boolean
112
+ text?: string
113
+ size?: number | string | Array<number | string>
114
+ showTag?: boolean
115
+ drag?: boolean
116
+ prefixPath?: string
117
+ /** 上传前压缩图片;默认 true,传 false 关闭 */
118
+ compress?: boolean
119
+ beforeUpload?: (file: File) => boolean | Promise<boolean>
120
+ /** 私有文件:预览内点「下载」时先解析可访问短链 */
121
+ resolveDownloadUrl?: (url: string) => Promise<string | undefined>
122
+ }>(),
123
+ {
124
+ modelValue: '',
125
+ type: 'image',
126
+ multiple: false,
127
+ limit: 9,
128
+ limitSize: 100,
129
+ disabled: false,
130
+ text: '',
131
+ size: 120,
132
+ showTag: true,
133
+ drag: false,
134
+ prefixPath: 'app/public',
135
+ compress: true,
136
+ },
137
+ )
138
+
139
+ const emit = defineEmits<{
140
+ 'update:modelValue': [v: string | string[]]
141
+ change: [v: string | string[]]
142
+ success: [item: UploadItem]
143
+ error: [item: UploadItem]
144
+ }>()
145
+
146
+ const { toUpload } = useUpload()
147
+ const inputRef = ref<HTMLInputElement | null>(null)
148
+ const list = ref<UploadItem[]>([])
149
+ /** 拖入高亮;用计数避免子元素 leave 误关 */
150
+ const dragging = ref(false)
151
+ let dragDepth = 0
152
+
153
+ const acceptValue = computed(() => props.accept ?? '')
154
+
155
+ const text = computed(() => {
156
+ if (props.text) return props.text
157
+ return props.type === 'file' ? crudLabel('selectFile') : crudLabel('selectImage')
158
+ })
159
+
160
+ const itemStyle = computed(() => {
161
+ const raw = Array.isArray(props.size) ? props.size[0] : props.size
162
+ const n = typeof raw === 'number' ? `${raw}px` : raw
163
+ return { width: n, height: n }
164
+ })
165
+
166
+ const isAdd = computed(() => {
167
+ if (props.disabled) return false
168
+ if (props.multiple) return list.value.length < props.limit
169
+ return list.value.length === 0
170
+ })
171
+
172
+ function normalizeValue(val: string | string[] | undefined) {
173
+ if (Array.isArray(val)) return val.filter(Boolean)
174
+ if (val) return [val]
175
+ return []
176
+ }
177
+
178
+ function syncFromModel(val: string | string[] | undefined) {
179
+ const urls = normalizeValue(val)
180
+ if (list.value.some((item) => item.progress < 100)) return
181
+ list.value = urls.map((url, index) => {
182
+ const old = list.value[index]
183
+ return {
184
+ uid: old?.uid || uploadUid(),
185
+ name: old?.name || url.split('/').pop() || url,
186
+ url,
187
+ preload: old?.url === url ? old.preload : url,
188
+ type: getFileType(url),
189
+ progress: 100,
190
+ error: '',
191
+ }
192
+ })
193
+ }
194
+
195
+ watch(
196
+ () => props.modelValue,
197
+ (val) => syncFromModel(val),
198
+ { immediate: true },
199
+ )
200
+
201
+ function emitValue() {
202
+ const urls = getUrls(list.value.filter((item) => item.url))
203
+ const val = props.multiple ? urls : urls[0] || ''
204
+ emit('update:modelValue', val)
205
+ emit('change', val)
206
+ }
207
+
208
+ function remove(index: number) {
209
+ const item = list.value[index]
210
+ if (item?.preload?.startsWith('blob:')) {
211
+ URL.revokeObjectURL(item.preload)
212
+ }
213
+ list.value.splice(index, 1)
214
+ emitValue()
215
+ }
216
+
217
+ async function onInputChange(e: Event) {
218
+ const input = e.target as HTMLInputElement
219
+ const files = Array.from(input.files ?? [])
220
+ input.value = ''
221
+ await ingestFiles(files)
222
+ }
223
+
224
+ function hasFilePayload(dt: DataTransfer | null) {
225
+ return !!dt && Array.from(dt.types || []).includes('Files')
226
+ }
227
+
228
+ function onDragEnter(e: DragEvent) {
229
+ if (props.disabled || !hasFilePayload(e.dataTransfer)) return
230
+ e.preventDefault()
231
+ e.stopPropagation()
232
+ dragDepth += 1
233
+ dragging.value = true
234
+ }
235
+
236
+ function onDragOver(e: DragEvent) {
237
+ if (props.disabled || !hasFilePayload(e.dataTransfer)) return
238
+ e.preventDefault()
239
+ e.stopPropagation()
240
+ if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy'
241
+ }
242
+
243
+ function onDragLeave(e: DragEvent) {
244
+ if (props.disabled || !hasFilePayload(e.dataTransfer)) return
245
+ e.preventDefault()
246
+ e.stopPropagation()
247
+ dragDepth = Math.max(0, dragDepth - 1)
248
+ if (dragDepth === 0) dragging.value = false
249
+ }
250
+
251
+ async function onDrop(e: DragEvent) {
252
+ if (props.disabled) return
253
+ e.preventDefault()
254
+ e.stopPropagation()
255
+ dragDepth = 0
256
+ dragging.value = false
257
+ const files = Array.from(e.dataTransfer?.files ?? [])
258
+ await ingestFiles(files)
259
+ }
260
+
261
+ async function ingestFiles(files: File[]) {
262
+ for (const file of files) {
263
+ if (!isAdd.value) {
264
+ toast.warning(crudFormat(crudLabel('maxUpload'), props.limit))
265
+ break
266
+ }
267
+ await appendFile(file)
268
+ }
269
+ }
270
+
271
+ async function appendFile(raw: File) {
272
+ if (raw.size / 1024 / 1024 >= props.limitSize) {
273
+ toast.error(crudFormat(crudLabel('fileTooLarge'), props.limitSize))
274
+ return
275
+ }
276
+ if (props.beforeUpload) {
277
+ const ok = await props.beforeUpload(raw)
278
+ if (!ok) return
279
+ }
280
+
281
+ // 先入列表 + 原图预览,再压缩 / 上传(避免压缩期间空白)
282
+ // 必须 reactive:否则改 progress 绕过 Proxy,UI 会 0% 直接跳 100%
283
+ const item = reactive<UploadItem>({
284
+ uid: uploadUid(),
285
+ name: raw.name,
286
+ url: '',
287
+ type: getFileType(raw.name),
288
+ size: raw.size,
289
+ progress: 0,
290
+ error: '',
291
+ preload: URL.createObjectURL(raw),
292
+ })
293
+ if (props.multiple) list.value.push(item)
294
+ else list.value = [item]
295
+
296
+ const doCompress = props.compress && isCompressibleImage(raw.type)
297
+
298
+ const setProgress = (n: number) => {
299
+ item.progress = Math.max(0, Math.min(100, Math.floor(n)))
300
+ }
301
+
302
+ try {
303
+ let file = raw
304
+ if (doCompress) {
305
+ file = await compressImage(raw, {
306
+ onProgress: (p) => {
307
+ // 压缩占整体 0–30%
308
+ setProgress((p / 100) * 30)
309
+ },
310
+ })
311
+ setProgress(30)
312
+ item.size = file.size
313
+ item.name = file.name
314
+ }
315
+
316
+ const res = await toUpload(file, {
317
+ prefixPath: props.prefixPath,
318
+ onProgress: (n) => {
319
+ // 直传 OSS:有压缩则 30–100,否则 0–100(xhr.upload.onprogress)
320
+ if (doCompress) {
321
+ setProgress(30 + (n / 100) * 70)
322
+ } else {
323
+ setProgress(n)
324
+ }
325
+ },
326
+ })
327
+ item.url = res.url
328
+ setProgress(100)
329
+ item.error = ''
330
+ emit('success', item)
331
+ emitValue()
332
+ } catch (e) {
333
+ item.progress = -1
334
+ item.error = e instanceof Error ? e.message : crudLabel('uploadFailed')
335
+ emit('error', item)
336
+ }
337
+ }
338
+
339
+ defineExpose({ list, remove })
340
+ </script>
341
+
342
+ <style lang="scss" scoped>
343
+ .vm-upload {
344
+ width: 100%;
345
+ }
346
+
347
+ .vm-upload__input {
348
+ display: none;
349
+ }
350
+
351
+ .vm-upload__list {
352
+ display: flex;
353
+ flex-wrap: wrap;
354
+ gap: 8px;
355
+ }
356
+
357
+ .vm-upload__item,
358
+ .vm-upload__trigger {
359
+ flex-shrink: 0;
360
+ box-sizing: border-box;
361
+ }
362
+
363
+ .vm-upload__trigger {
364
+ display: grid;
365
+ place-items: center;
366
+ border: 1px dashed var(--border);
367
+ border-radius: 10px;
368
+ background: var(--muted);
369
+ color: var(--muted-foreground);
370
+ cursor: pointer;
371
+ transition:
372
+ border-color 0.15s ease,
373
+ color 0.15s ease,
374
+ background 0.15s ease,
375
+ outline-color 0.15s ease;
376
+
377
+ &:hover {
378
+ border-color: var(--brand);
379
+ color: var(--brand);
380
+ background: var(--brand-soft);
381
+ }
382
+
383
+ /* 仅 drag 大区铺满字段宽;默认靠 itemStyle 固定正方形 */
384
+ &.is-drag {
385
+ width: 100%;
386
+ min-height: 120px;
387
+ }
388
+
389
+ &.is-dragging {
390
+ border-color: var(--brand);
391
+ color: var(--brand);
392
+ background: var(--brand-soft);
393
+ outline: 2px dashed var(--brand);
394
+ outline-offset: 2px;
395
+ }
396
+ }
397
+
398
+ .vm-upload__demo,
399
+ .vm-upload__drag {
400
+ display: grid;
401
+ gap: 6px;
402
+ place-items: center;
403
+ padding: 10px;
404
+ font-size: 12px;
405
+ text-align: center;
406
+
407
+ i {
408
+ font-size: 28px;
409
+ }
410
+ }
411
+
412
+ .vm-upload__file-btn {
413
+ display: inline-block;
414
+ margin-bottom: 8px;
415
+ border-radius: 8px;
416
+ transition:
417
+ outline-color 0.15s ease,
418
+ background 0.15s ease;
419
+
420
+ &.is-dragging {
421
+ outline: 2px dashed var(--brand);
422
+ outline-offset: 4px;
423
+ background: var(--brand-soft);
424
+ }
425
+ }
426
+
427
+ .vm-upload__pick {
428
+ display: inline-block;
429
+ cursor: pointer;
430
+ }
431
+
432
+ .vm-upload.is-disabled {
433
+ opacity: 0.6;
434
+ pointer-events: none;
435
+ }
436
+ </style>