Commit 9de323e09ca121bc24ff5fb21771d766f107036c

Authored by xingyu
1 parent 2d791301

perf: 完善上传组件

yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/data.ts.vm
... ... @@ -78,10 +78,7 @@ const crudSchemas = reactive<VxeCrudSchema>({
78 78 },
79 79 #elseif($column.htmlType == "imageUpload")## 图片上传
80 80 form: {
81   - component: 'UploadImg',
82   - componentProps: {
83   - limit: 1
84   - }
  81 + component: 'UploadImg' // 单图上传,多图为UploadImgs
85 82 },
86 83 #elseif($column.htmlType == "fileUpload")## 图片上传
87 84 form: {
... ...
yudao-ui-admin-vue3/src/components/Form/src/componentMap.ts
... ... @@ -21,7 +21,7 @@ import {
21 21 } from 'element-plus'
22 22 import { InputPassword } from '@/components/InputPassword'
23 23 import { Editor } from '@/components/Editor'
24   -import { UploadImg, UploadFile } from '@/components/UploadFile'
  24 +import { UploadImg, UploadImgs, UploadFile } from '@/components/UploadFile'
25 25 import { ComponentName } from '@/types/components'
26 26  
27 27 const componentMap: Recordable<Component, ComponentName> = {
... ... @@ -48,6 +48,7 @@ const componentMap: Recordable&lt;Component, ComponentName&gt; = {
48 48 InputPassword: InputPassword,
49 49 Editor: Editor,
50 50 UploadImg: UploadImg,
  51 + UploadImgs: UploadImgs,
51 52 UploadFile: UploadFile
52 53 }
53 54  
... ...
yudao-ui-admin-vue3/src/components/UploadFile/index.ts
1 1 import UploadImg from './src/UploadImg.vue'
  2 +import UploadImgs from './src/UploadImgs.vue'
2 3 import UploadFile from './src/UploadFile.vue'
3 4  
4   -export { UploadImg, UploadFile }
  5 +export { UploadImg, UploadImgs, UploadFile }
... ...
yudao-ui-admin-vue3/src/components/UploadFile/src/UploadFile.vue
... ... @@ -32,8 +32,8 @@
32 32 </el-upload>
33 33 </div>
34 34 </template>
35   -<script setup lang="ts">
36   -import { ref, watch } from 'vue'
  35 +<script setup lang="ts" name="UploadFile">
  36 +import { PropType, ref } from 'vue'
37 37 import { useMessage } from '@/hooks/web/useMessage'
38 38 import { propTypes } from '@/utils/propTypes'
39 39 import { getAccessToken, getTenantId } from '@/utils/auth'
... ... @@ -43,7 +43,10 @@ const message = useMessage() // 消息弹窗
43 43 const emit = defineEmits(['update:modelValue'])
44 44  
45 45 const props = defineProps({
46   - modelValue: propTypes.oneOfType([String, Object, Array]),
  46 + modelValue: {
  47 + type: Array as PropType<UploadUserFile[]>,
  48 + required: true
  49 + },
47 50 title: propTypes.string.def('文件上传'),
48 51 updateUrl: propTypes.string.def(import.meta.env.VITE_UPLOAD_URL),
49 52 fileType: propTypes.array.def(['doc', 'xls', 'ppt', 'txt', 'pdf']), // 文件类型, 例如['png', 'jpg', 'jpeg']
... ... @@ -57,40 +60,12 @@ const props = defineProps({
57 60 const valueRef = ref(props.modelValue)
58 61 const uploadRef = ref<UploadInstance>()
59 62 const uploadList = ref<UploadUserFile[]>([])
60   -const fileList = ref<UploadUserFile[]>([])
  63 +const fileList = ref<UploadUserFile[]>(props.modelValue)
61 64 const uploadNumber = ref<number>(0)
62 65 const uploadHeaders = ref({
63 66 Authorization: 'Bearer ' + getAccessToken(),
64 67 'tenant-id': getTenantId()
65 68 })
66   -watch(
67   - () => props.modelValue,
68   - (val) => {
69   - if (val) {
70   - // 首先将值转为数组, 当只穿了一个图片时,会报map方法错误
71   - const list = Array.isArray(props.modelValue)
72   - ? props.modelValue
73   - : Array.isArray(props.modelValue?.split(','))
74   - ? props.modelValue?.split(',')
75   - : Array.of(props.modelValue)
76   - // 然后将数组转为对象数组
77   - fileList.value = list.map((item) => {
78   - if (typeof item === 'string') {
79   - // edit by 芋道源码
80   - item = { name: item, url: item }
81   - }
82   - return item
83   - })
84   - } else {
85   - fileList.value = []
86   - return []
87   - }
88   - },
89   - {
90   - deep: true,
91   - immediate: true
92   - }
93   -)
94 69 // 文件上传之前判断
95 70 const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
96 71 if (fileList.value.length >= props.limit) {
... ...
yudao-ui-admin-vue3/src/components/UploadFile/src/UploadImg.vue
1 1 <template>
2   - <div class="component-upload-image">
  2 + <div class="upload-box">
3 3 <el-upload
4   - ref="uploadRef"
5   - :multiple="props.limit > 1"
6   - name="file"
7   - v-model="valueRef"
8   - list-type="picture-card"
9   - v-model:file-list="fileList"
10   - :show-file-list="true"
11 4 :action="updateUrl"
  5 + :id="uuid"
  6 + :class="['upload', drag ? 'no-border' : '']"
  7 + :multiple="false"
  8 + :show-file-list="false"
12 9 :headers="uploadHeaders"
13   - :limit="props.limit"
14 10 :before-upload="beforeUpload"
15   - :on-exceed="handleExceed"
16   - :on-success="handleFileSuccess"
17   - :on-error="excelUploadError"
18   - :on-remove="handleRemove"
19   - :on-preview="handlePictureCardPreview"
20   - :class="{ hide: fileList.length >= props.limit }"
  11 + :on-success="uploadSuccess"
  12 + :on-error="uploadError"
  13 + :drag="drag"
  14 + :accept="fileType.join(',')"
21 15 >
22   - <Icon icon="ep:upload-filled" />
  16 + <template v-if="modelValue">
  17 + <img :src="modelValue" class="upload-image" />
  18 + <div class="upload-handle" @click.stop>
  19 + <div class="handle-icon" @click="editImg">
  20 + <Icon icon="ep:edit" />
  21 + <span>{{ t('action.edit') }}</span>
  22 + </div>
  23 + <div class="handle-icon" @click="imgViewVisible = true">
  24 + <Icon icon="ep:zoom-in" />
  25 + <span>{{ t('action.detail') }}</span>
  26 + </div>
  27 + <div class="handle-icon" @click="deleteImg">
  28 + <Icon icon="ep:delete" />
  29 + <span>{{ t('action.del') }}</span>
  30 + </div>
  31 + </div>
  32 + </template>
  33 + <template v-else>
  34 + <div class="upload-empty">
  35 + <slot name="empty">
  36 + <Icon icon="ep:plus" />
  37 + <!-- <span>请上传图片</span> -->
  38 + </slot>
  39 + </div>
  40 + </template>
23 41 </el-upload>
  42 + <div class="el-upload__tip">
  43 + <slot name="tip"></slot>
  44 + </div>
  45 + <el-image-viewer
  46 + v-if="imgViewVisible"
  47 + @close="imgViewVisible = false"
  48 + :url-list="[modelValue]"
  49 + />
24 50 </div>
25   - <!-- 文件列表 -->
26   - <Dialog v-model="dialogVisible" title="预览" width="800" append-to-body>
27   - <img :src="dialogImageUrl" style="display: block; max-width: 100%; margin: 0 auto" />
28   - </Dialog>
29 51 </template>
30   -<script setup lang="ts">
31   -import { ref, watch } from 'vue'
32   -import { Dialog } from '@/components/Dialog'
  52 +
  53 +<script setup lang="ts" name="UploadImg">
  54 +import { ref } from 'vue'
  55 +import type { UploadProps } from 'element-plus'
  56 +import { ElUpload, ElNotification, ElImageViewer } from 'element-plus'
  57 +import { useI18n } from '@/hooks/web/useI18n'
33 58 import { useMessage } from '@/hooks/web/useMessage'
  59 +import { generateUUID } from '@/utils'
34 60 import { propTypes } from '@/utils/propTypes'
35 61 import { getAccessToken, getTenantId } from '@/utils/auth'
36   -import { ElUpload, UploadInstance, UploadProps, UploadRawFile, UploadUserFile } from 'element-plus'
37 62  
38   -const message = useMessage() // 消息弹窗
39   -const emit = defineEmits(['update:modelValue'])
  63 +type FileTypes =
  64 + | 'image/apng'
  65 + | 'image/bmp'
  66 + | 'image/gif'
  67 + | 'image/jpeg'
  68 + | 'image/pjpeg'
  69 + | 'image/png'
  70 + | 'image/svg+xml'
  71 + | 'image/tiff'
  72 + | 'image/webp'
  73 + | 'image/x-icon'
40 74  
  75 +// 接受父组件参数
41 76 const props = defineProps({
42   - modelValue: propTypes.oneOfType([String, Object, Array]),
43   - title: propTypes.string.def('图片上传'),
  77 + modelValue: propTypes.string.def(''),
44 78 updateUrl: propTypes.string.def(import.meta.env.VITE_UPLOAD_URL),
45   - fileType: propTypes.array.def(['jpg', 'png', 'gif', 'jpeg']), // 文件类型, 例如['png', 'jpg', 'jpeg']
46   - fileSize: propTypes.number.def(5), // 大小限制(MB)
47   - limit: propTypes.number.def(1), // 数量限制
48   - isShowTip: propTypes.bool.def(false) // 是否显示提示
  79 + drag: propTypes.bool.def(true), // 是否支持拖拽上传 ==> 非必传(默认为 true)
  80 + disabled: propTypes.bool.def(false), // 是否禁用上传组件 ==> 非必传(默认为 false)
  81 + fileSize: propTypes.number.def(5), // 图片大小限制 ==> 非必传(默认为 5M)
  82 + fileType: propTypes.array.def(['image/jpeg', 'image/png', 'image/gif']), // 图片类型限制 ==> 非必传(默认为 ["image/jpeg", "image/png", "image/gif"])
  83 + height: propTypes.string.def('150px'), // 组件高度 ==> 非必传(默认为 150px)
  84 + width: propTypes.string.def('150px'), // 组件宽度 ==> 非必传(默认为 150px)
  85 + borderRadius: propTypes.string.def('8px') // 组件边框圆角 ==> 非必传(默认为 8px)
49 86 })
50   -// ========== 上传相关 ==========
51   -const valueRef = ref(props.modelValue)
52   -const uploadRef = ref<UploadInstance>()
53   -const uploadList = ref<UploadUserFile[]>([])
54   -const fileList = ref<UploadUserFile[]>([])
55   -const uploadNumber = ref<number>(0)
56   -const dialogImageUrl = ref()
57   -const dialogVisible = ref(false)
  87 +const { t } = useI18n() // 国际化
  88 +const message = useMessage() // 消息弹窗
  89 +// 生成组件唯一id
  90 +const uuid = ref('id-' + generateUUID())
  91 +// 查看图片
  92 +const imgViewVisible = ref(false)
  93 +
  94 +const emit = defineEmits(['update:modelValue'])
  95 +
  96 +const deleteImg = () => {
  97 + emit('update:modelValue', '')
  98 +}
  99 +
58 100 const uploadHeaders = ref({
59 101 Authorization: 'Bearer ' + getAccessToken(),
60 102 'tenant-id': getTenantId()
61 103 })
62   -watch(
63   - () => props.modelValue,
64   - (val) => {
65   - if (val) {
66   - // 首先将值转为数组, 当只穿了一个图片时,会报map方法错误
67   - const list = Array.isArray(props.modelValue)
68   - ? props.modelValue
69   - : Array.isArray(props.modelValue?.split(','))
70   - ? props.modelValue?.split(',')
71   - : Array.of(props.modelValue)
72   - // 然后将数组转为对象数组
73   - fileList.value = list.map((item) => {
74   - if (typeof item === 'string') {
75   - // edit by 芋道源码
76   - item = { name: item, url: item }
77   - }
78   - return item
79   - })
80   - } else {
81   - fileList.value = []
82   - return []
83   - }
84   - },
85   - {
86   - deep: true,
87   - immediate: true
88   - }
89   -)
90   -// 文件上传之前判断
91   -const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
92   - if (fileList.value.length >= props.limit) {
93   - message.error(`上传文件数量不能超过${props.limit}个!`)
94   - return false
95   - }
96   - let fileExtension = ''
97   - if (file.name.lastIndexOf('.') > -1) {
98   - fileExtension = file.name.slice(file.name.lastIndexOf('.') + 1)
99   - }
100   - const isImg = props.fileType.some((type: string) => {
101   - if (file.type.indexOf(type) > -1) return true
102   - return !!(fileExtension && fileExtension.indexOf(type) > -1)
103   - })
104   - const isLimit = file.size < props.fileSize * 1024 * 1024
105   - if (!isImg) {
106   - message.error(`文件格式不正确, 请上传${props.fileType.join('/')}格式!`)
107   - return false
108   - }
109   - if (!isLimit) {
110   - message.error(`上传文件大小不能超过${props.fileSize}MB!`)
111   - return false
112   - }
113   - message.success('正在上传文件,请稍候...')
114   - uploadNumber.value++
115   -}
116   -// 处理上传的文件发生变化
117   -// const handleFileChange = (uploadFile: UploadFile): void => {
118   -// uploadRef.value.data.path = uploadFile.name
119   -// }
120   -// 文件上传成功
121   -const handleFileSuccess: UploadProps['onSuccess'] = (res: any): void => {
122   - message.success('上传成功')
123   - uploadList.value.push({ name: res.data, url: res.data })
124   - if (uploadList.value.length == uploadNumber.value) {
125   - fileList.value = fileList.value.concat(uploadList.value)
126   - uploadList.value = []
127   - uploadNumber.value = 0
128   - emit('update:modelValue', listToString(fileList.value))
129   - }
130   -}
131   -// 文件数超出提示
132   -const handleExceed: UploadProps['onExceed'] = (): void => {
133   - message.error(`上传文件数量不能超过${props.limit}个!`)
134   -}
135   -// 上传错误提示
136   -const excelUploadError: UploadProps['onError'] = (): void => {
137   - message.error('导入数据失败,请您重新上传!')
  104 +
  105 +const editImg = () => {
  106 + const dom = document.querySelector(`#${uuid.value} .el-upload__input`)
  107 + dom && dom.dispatchEvent(new MouseEvent('click'))
138 108 }
139   -// 删除上传文件
140   -const handleRemove = (file) => {
141   - const findex = fileList.value.map((f) => f.name).indexOf(file.name)
142   - if (findex > -1) {
143   - fileList.value.splice(findex, 1)
144   - emit('update:modelValue', listToString(fileList.value))
145   - }
  109 +
  110 +const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
  111 + const imgSize = rawFile.size / 1024 / 1024 < props.fileSize
  112 + const imgType = props.fileType
  113 + if (!imgType.includes(rawFile.type as FileTypes))
  114 + ElNotification({
  115 + title: '温馨提示',
  116 + message: '上传图片不符合所需的格式!',
  117 + type: 'warning'
  118 + })
  119 + if (!imgSize)
  120 + ElNotification({
  121 + title: '温馨提示',
  122 + message: `上传图片大小不能超过 ${props.fileSize}M!`,
  123 + type: 'warning'
  124 + })
  125 + return imgType.includes(rawFile.type as FileTypes) && imgSize
146 126 }
147   -// 对象转成指定字符串分隔
148   -const listToString = (list: UploadUserFile[], separator?: string) => {
149   - let strs = ''
150   - separator = separator || ','
151   - for (let i in list) {
152   - strs += list[i].url + separator
153   - }
154   - return strs != '' ? strs.substr(0, strs.length - 1) : ''
  127 +
  128 +// 图片上传成功提示
  129 +const uploadSuccess: UploadProps['onSuccess'] = (res: any): void => {
  130 + message.success('上传成功')
  131 + emit('update:modelValue', res.data)
155 132 }
156   -// 预览
157   -const handlePictureCardPreview: UploadProps['onPreview'] = (file) => {
158   - dialogImageUrl.value = file.url
159   - dialogVisible.value = true
  133 +
  134 +// 图片上传错误提示
  135 +const uploadError = () => {
  136 + ElNotification({
  137 + title: '温馨提示',
  138 + message: '图片上传失败,请您重新上传!',
  139 + type: 'error'
  140 + })
160 141 }
161 142 </script>
162 143 <style scoped lang="scss">
163   -// .el-upload--picture-card 控制加号部分
164   -:deep(.hide .el-upload--picture-card) {
165   - display: none;
  144 +.is-error {
  145 + .upload {
  146 + :deep(.el-upload),
  147 + :deep(.el-upload-dragger) {
  148 + border: 1px dashed var(--el-color-danger) !important;
  149 + &:hover {
  150 + border-color: var(--el-color-primary) !important;
  151 + }
  152 + }
  153 + }
166 154 }
167   -// 去掉动画效果
168   -:deep(.el-list-enter-active, .el-list-leave-active) {
169   - transition: all 0s;
  155 +:deep(.disabled) {
  156 + .el-upload,
  157 + .el-upload-dragger {
  158 + cursor: not-allowed !important;
  159 + background: var(--el-disabled-bg-color);
  160 + border: 1px dashed var(--el-border-color-darker) !important;
  161 + &:hover {
  162 + border: 1px dashed var(--el-border-color-darker) !important;
  163 + }
  164 + }
170 165 }
171   -
172   -:deep(.el-list-enter, .el-list-leave-active) {
173   - opacity: 0;
174   - transform: translateY(0);
  166 +.upload-box {
  167 + .no-border {
  168 + :deep(.el-upload) {
  169 + border: none !important;
  170 + }
  171 + }
  172 + :deep(.upload) {
  173 + .el-upload {
  174 + position: relative;
  175 + display: flex;
  176 + align-items: center;
  177 + justify-content: center;
  178 + width: v-bind(width);
  179 + height: v-bind(height);
  180 + overflow: hidden;
  181 + border: 1px dashed var(--el-border-color-darker);
  182 + border-radius: v-bind(borderRadius);
  183 + transition: var(--el-transition-duration-fast);
  184 + &:hover {
  185 + border-color: var(--el-color-primary);
  186 + .upload-handle {
  187 + opacity: 1;
  188 + }
  189 + }
  190 + .el-upload-dragger {
  191 + display: flex;
  192 + align-items: center;
  193 + justify-content: center;
  194 + width: 100%;
  195 + height: 100%;
  196 + padding: 0;
  197 + overflow: hidden;
  198 + background-color: transparent;
  199 + border: 1px dashed var(--el-border-color-darker);
  200 + border-radius: v-bind(borderRadius);
  201 + &:hover {
  202 + border: 1px dashed var(--el-color-primary);
  203 + }
  204 + }
  205 + .el-upload-dragger.is-dragover {
  206 + background-color: var(--el-color-primary-light-9);
  207 + border: 2px dashed var(--el-color-primary) !important;
  208 + }
  209 + .upload-image {
  210 + width: 100%;
  211 + height: 100%;
  212 + object-fit: contain;
  213 + }
  214 + .upload-empty {
  215 + position: relative;
  216 + display: flex;
  217 + flex-direction: column;
  218 + align-items: center;
  219 + justify-content: center;
  220 + font-size: 12px;
  221 + line-height: 30px;
  222 + color: var(--el-color-info);
  223 + .el-icon {
  224 + font-size: 28px;
  225 + color: var(--el-text-color-secondary);
  226 + }
  227 + }
  228 + .upload-handle {
  229 + position: absolute;
  230 + top: 0;
  231 + right: 0;
  232 + box-sizing: border-box;
  233 + display: flex;
  234 + align-items: center;
  235 + justify-content: center;
  236 + width: 100%;
  237 + height: 100%;
  238 + cursor: pointer;
  239 + background: rgb(0 0 0 / 60%);
  240 + opacity: 0;
  241 + transition: var(--el-transition-duration-fast);
  242 + .handle-icon {
  243 + display: flex;
  244 + flex-direction: column;
  245 + align-items: center;
  246 + justify-content: center;
  247 + padding: 0 6%;
  248 + color: aliceblue;
  249 + .el-icon {
  250 + margin-bottom: 40%;
  251 + font-size: 130%;
  252 + line-height: 130%;
  253 + }
  254 + span {
  255 + font-size: 85%;
  256 + line-height: 85%;
  257 + }
  258 + }
  259 + }
  260 + }
  261 + }
  262 + .el-upload__tip {
  263 + line-height: 18px;
  264 + text-align: center;
  265 + }
175 266 }
176 267 </style>
... ...
yudao-ui-admin-vue3/src/components/UploadFile/src/UploadImgs.vue 0 → 100644
  1 +<template>
  2 + <div class="upload-box">
  3 + <el-upload
  4 + :action="updateUrl"
  5 + list-type="picture-card"
  6 + :class="['upload', drag ? 'no-border' : '']"
  7 + v-model:file-list="fileList"
  8 + :multiple="true"
  9 + :limit="limit"
  10 + :headers="uploadHeaders"
  11 + :before-upload="beforeUpload"
  12 + :on-exceed="handleExceed"
  13 + :on-success="uploadSuccess"
  14 + :on-error="uploadError"
  15 + :drag="drag"
  16 + :accept="fileType.join(',')"
  17 + >
  18 + <div class="upload-empty">
  19 + <slot name="empty">
  20 + <Icon icon="ep:plus" />
  21 + <!-- <span>请上传图片</span> -->
  22 + </slot>
  23 + </div>
  24 + <template #file="{ file }">
  25 + <img :src="file.url" class="upload-image" />
  26 + <div class="upload-handle" @click.stop>
  27 + <div class="handle-icon" @click="handlePictureCardPreview(file)">
  28 + <Icon icon="ep:zoom-in" />
  29 + <span>查看</span>
  30 + </div>
  31 + <div class="handle-icon" @click="handleRemove(file)">
  32 + <Icon icon="ep:delete" />
  33 + <span>删除</span>
  34 + </div>
  35 + </div>
  36 + </template>
  37 + </el-upload>
  38 + <div class="el-upload__tip">
  39 + <slot name="tip"></slot>
  40 + </div>
  41 + <el-image-viewer
  42 + v-if="imgViewVisible"
  43 + @close="imgViewVisible = false"
  44 + :url-list="[viewImageUrl]"
  45 + />
  46 + </div>
  47 +</template>
  48 +<script setup lang="ts" name="UploadImgs">
  49 +import { PropType, ref } from 'vue'
  50 +import { ElUpload, ElNotification, ElImageViewer } from 'element-plus'
  51 +import type { UploadProps, UploadFile, UploadUserFile } from 'element-plus'
  52 +import { useMessage } from '@/hooks/web/useMessage'
  53 +import { propTypes } from '@/utils/propTypes'
  54 +import { getAccessToken, getTenantId } from '@/utils/auth'
  55 +
  56 +const message = useMessage() // 消息弹窗
  57 +
  58 +type FileTypes =
  59 + | 'image/apng'
  60 + | 'image/bmp'
  61 + | 'image/gif'
  62 + | 'image/jpeg'
  63 + | 'image/pjpeg'
  64 + | 'image/png'
  65 + | 'image/svg+xml'
  66 + | 'image/tiff'
  67 + | 'image/webp'
  68 + | 'image/x-icon'
  69 +
  70 +const props = defineProps({
  71 + modelValue: {
  72 + type: Array as PropType<UploadUserFile[]>,
  73 + required: true
  74 + },
  75 + updateUrl: propTypes.string.def(import.meta.env.VITE_UPLOAD_URL),
  76 + drag: propTypes.bool.def(true), // 是否支持拖拽上传 ==> 非必传(默认为 true)
  77 + disabled: propTypes.bool.def(false), // 是否禁用上传组件 ==> 非必传(默认为 false)
  78 + limit: propTypes.number.def(5), // 最大图片上传数 ==> 非必传(默认为 5张)
  79 + fileSize: propTypes.number.def(5), // 图片大小限制 ==> 非必传(默认为 5M)
  80 + fileType: propTypes.array.def(['image/jpeg', 'image/png', 'image/gif']), // 图片类型限制 ==> 非必传(默认为 ["image/jpeg", "image/png", "image/gif"])
  81 + height: propTypes.string.def('150px'), // 组件高度 ==> 非必传(默认为 150px)
  82 + width: propTypes.string.def('150px'), // 组件宽度 ==> 非必传(默认为 150px)
  83 + borderRadius: propTypes.string.def('8px') // 组件边框圆角 ==> 非必传(默认为 8px)
  84 +})
  85 +
  86 +const uploadHeaders = ref({
  87 + Authorization: 'Bearer ' + getAccessToken(),
  88 + 'tenant-id': getTenantId()
  89 +})
  90 +
  91 +const fileList = ref<UploadUserFile[]>(props.modelValue)
  92 +
  93 +/**
  94 + * @description 文件上传之前判断
  95 + * @param rawFile 上传的文件
  96 + * */
  97 +const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
  98 + const imgSize = rawFile.size / 1024 / 1024 < props.fileSize
  99 + const imgType = props.fileType
  100 + if (!imgType.includes(rawFile.type as FileTypes))
  101 + ElNotification({
  102 + title: '温馨提示',
  103 + message: '上传图片不符合所需的格式!',
  104 + type: 'warning'
  105 + })
  106 + if (!imgSize)
  107 + ElNotification({
  108 + title: '温馨提示',
  109 + message: `上传图片大小不能超过 ${props.fileSize}M!`,
  110 + type: 'warning'
  111 + })
  112 + return imgType.includes(rawFile.type as FileTypes) && imgSize
  113 +}
  114 +
  115 +// 图片上传成功
  116 +interface UploadEmits {
  117 + (e: 'update:modelValue', value: UploadUserFile[]): void
  118 +}
  119 +const emit = defineEmits<UploadEmits>()
  120 +const uploadSuccess = (response, uploadFile: UploadFile) => {
  121 + if (!response) return
  122 + uploadFile.url = response.data
  123 + emit('update:modelValue', fileList.value)
  124 + message.success('上传成功')
  125 +}
  126 +
  127 +// 删除图片
  128 +const handleRemove = (uploadFile: UploadFile) => {
  129 + fileList.value = fileList.value.filter(
  130 + (item) => item.url !== uploadFile.url || item.name !== uploadFile.name
  131 + )
  132 + emit('update:modelValue', fileList.value)
  133 +}
  134 +
  135 +// 图片上传错误提示
  136 +const uploadError = () => {
  137 + ElNotification({
  138 + title: '温馨提示',
  139 + message: '图片上传失败,请您重新上传!',
  140 + type: 'error'
  141 + })
  142 +}
  143 +
  144 +// 文件数超出提示
  145 +const handleExceed = () => {
  146 + ElNotification({
  147 + title: '温馨提示',
  148 + message: `当前最多只能上传 ${props.limit} 张图片,请移除后上传!`,
  149 + type: 'warning'
  150 + })
  151 +}
  152 +
  153 +// 图片预览
  154 +const viewImageUrl = ref('')
  155 +const imgViewVisible = ref(false)
  156 +const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
  157 + viewImageUrl.value = uploadFile.url!
  158 + imgViewVisible.value = true
  159 +}
  160 +</script>
  161 +
  162 +<style scoped lang="scss">
  163 +.is-error {
  164 + .upload {
  165 + :deep(.el-upload--picture-card),
  166 + :deep(.el-upload-dragger) {
  167 + border: 1px dashed var(--el-color-danger) !important;
  168 + &:hover {
  169 + border-color: var(--el-color-primary) !important;
  170 + }
  171 + }
  172 + }
  173 +}
  174 +:deep(.disabled) {
  175 + .el-upload--picture-card,
  176 + .el-upload-dragger {
  177 + cursor: not-allowed;
  178 + background: var(--el-disabled-bg-color) !important;
  179 + border: 1px dashed var(--el-border-color-darker);
  180 + &:hover {
  181 + border-color: var(--el-border-color-darker) !important;
  182 + }
  183 + }
  184 +}
  185 +.upload-box {
  186 + .no-border {
  187 + :deep(.el-upload--picture-card) {
  188 + border: none !important;
  189 + }
  190 + }
  191 + :deep(.upload) {
  192 + .el-upload-dragger {
  193 + display: flex;
  194 + align-items: center;
  195 + justify-content: center;
  196 + width: 100%;
  197 + height: 100%;
  198 + padding: 0;
  199 + overflow: hidden;
  200 + border: 1px dashed var(--el-border-color-darker);
  201 + border-radius: v-bind(borderRadius);
  202 + &:hover {
  203 + border: 1px dashed var(--el-color-primary);
  204 + }
  205 + }
  206 + .el-upload-dragger.is-dragover {
  207 + background-color: var(--el-color-primary-light-9);
  208 + border: 2px dashed var(--el-color-primary) !important;
  209 + }
  210 + .el-upload-list__item,
  211 + .el-upload--picture-card {
  212 + width: v-bind(width);
  213 + height: v-bind(height);
  214 + background-color: transparent;
  215 + border-radius: v-bind(borderRadius);
  216 + }
  217 + .upload-image {
  218 + width: 100%;
  219 + height: 100%;
  220 + object-fit: contain;
  221 + }
  222 + .upload-handle {
  223 + position: absolute;
  224 + top: 0;
  225 + right: 0;
  226 + box-sizing: border-box;
  227 + display: flex;
  228 + align-items: center;
  229 + justify-content: center;
  230 + width: 100%;
  231 + height: 100%;
  232 + cursor: pointer;
  233 + background: rgb(0 0 0 / 60%);
  234 + opacity: 0;
  235 + transition: var(--el-transition-duration-fast);
  236 + .handle-icon {
  237 + display: flex;
  238 + flex-direction: column;
  239 + align-items: center;
  240 + justify-content: center;
  241 + padding: 0 6%;
  242 + color: aliceblue;
  243 + .el-icon {
  244 + margin-bottom: 15%;
  245 + font-size: 140%;
  246 + }
  247 + span {
  248 + font-size: 100%;
  249 + }
  250 + }
  251 + }
  252 + .el-upload-list__item {
  253 + &:hover {
  254 + .upload-handle {
  255 + opacity: 1;
  256 + }
  257 + }
  258 + }
  259 + .upload-empty {
  260 + display: flex;
  261 + flex-direction: column;
  262 + align-items: center;
  263 + font-size: 12px;
  264 + line-height: 30px;
  265 + color: var(--el-color-info);
  266 + .el-icon {
  267 + font-size: 28px;
  268 + color: var(--el-text-color-secondary);
  269 + }
  270 + }
  271 + }
  272 + .el-upload__tip {
  273 + line-height: 15px;
  274 + text-align: center;
  275 + }
  276 +}
  277 +</style>
... ...
yudao-ui-admin-vue3/src/types/components.d.ts
... ... @@ -22,6 +22,7 @@ export type ComponentName =
22 22 | 'InputPassword'
23 23 | 'Editor'
24 24 | 'UploadImg'
  25 + | 'UploadImgs'
25 26 | 'UploadFile'
26 27  
27 28 export type ColProps = {
... ...
yudao-ui-admin-vue3/src/utils/index.ts
... ... @@ -69,7 +69,7 @@ export const trim = (str: string) =&gt; {
69 69 * @param {Date | number | string} time 需要转换的时间
70 70 * @param {String} fmt 需要转换的格式 如 yyyy-MM-dd、yyyy-MM-dd HH:mm:ss
71 71 */
72   -export function formatTime(time: Date | number | string, fmt: string) {
  72 +export const formatTime = (time: Date | number | string, fmt: string) => {
73 73 if (!time) return ''
74 74 else {
75 75 const date = new Date(time)
... ... @@ -100,7 +100,7 @@ export function formatTime(time: Date | number | string, fmt: string) {
100 100 /**
101 101 * 生成随机字符串
102 102 */
103   -export function toAnyString() {
  103 +export const toAnyString = () => {
104 104 const str: string = 'xxxxx-xxxxx-4xxxx-yxxxx-xxxxx'.replace(/[xy]/g, (c: string) => {
105 105 const r: number = (Math.random() * 16) | 0
106 106 const v: number = c === 'x' ? r : (r & 0x3) | 0x8
... ... @@ -108,3 +108,34 @@ export function toAnyString() {
108 108 })
109 109 return str
110 110 }
  111 +
  112 +export const generateUUID = () => {
  113 + if (typeof crypto === 'object') {
  114 + if (typeof crypto.randomUUID === 'function') {
  115 + return crypto.randomUUID()
  116 + }
  117 + if (typeof crypto.getRandomValues === 'function' && typeof Uint8Array === 'function') {
  118 + const callback = (c: any) => {
  119 + const num = Number(c)
  120 + return (num ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (num / 4)))).toString(
  121 + 16
  122 + )
  123 + }
  124 + return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, callback)
  125 + }
  126 + }
  127 + let timestamp = new Date().getTime()
  128 + let performanceNow =
  129 + (typeof performance !== 'undefined' && performance.now && performance.now() * 1000) || 0
  130 + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
  131 + let random = Math.random() * 16
  132 + if (timestamp > 0) {
  133 + random = (timestamp + random) % 16 | 0
  134 + timestamp = Math.floor(timestamp / 16)
  135 + } else {
  136 + random = (performanceNow + random) % 16 | 0
  137 + performanceNow = Math.floor(performanceNow / 16)
  138 + }
  139 + return (c === 'x' ? random : (random & 0x3) | 0x8).toString(16)
  140 + })
  141 +}
... ...
yudao-ui-admin-vue3/src/views/system/oauth2/client/client.data.ts
... ... @@ -43,10 +43,7 @@ const crudSchemas = reactive&lt;VxeCrudSchema&gt;({
43 43 }
44 44 },
45 45 form: {
46   - component: 'UploadImg',
47   - componentProps: {
48   - limit: 1
49   - }
  46 + component: 'UploadImg'
50 47 }
51 48 },
52 49 {
... ...