潘志宝
6 天以前 ca22cdd5550cfa0defb0f430c538698182cdaec1
提交 | 用户 | 时间
820397 1 <template>
H 2   <div class="upload-box">
3     <el-upload
4       v-model:file-list="fileList"
5       :accept="fileType.join(',')"
6       :action="uploadUrl"
7       :before-upload="beforeUpload"
8       :class="['upload', drag ? 'no-border' : '']"
9       :disabled="disabled"
10       :drag="drag"
11       :http-request="httpRequest"
12       :limit="limit"
13       :multiple="true"
14       :on-error="uploadError"
15       :on-exceed="handleExceed"
16       :on-success="uploadSuccess"
17       list-type="picture-card"
18     >
19       <div class="upload-empty">
20         <slot name="empty">
21           <Icon icon="ep:plus" />
22           <!-- <span>请上传图片</span> -->
23         </slot>
24       </div>
25       <template #file="{ file }">
26         <img :src="file.url" class="upload-image" />
27         <div class="upload-handle" @click.stop>
9259c2 28           <div class="handle-icon" @click="imagePreview(file.url!)">
820397 29             <Icon icon="ep:zoom-in" />
H 30             <span>查看</span>
31           </div>
32           <div v-if="!disabled" class="handle-icon" @click="handleRemove(file)">
33             <Icon icon="ep:delete" />
34             <span>删除</span>
35           </div>
36         </div>
37       </template>
38     </el-upload>
39     <div class="el-upload__tip">
40       <slot name="tip"></slot>
41     </div>
42   </div>
43 </template>
44 <script lang="ts" setup>
45 import type { UploadFile, UploadProps, UploadUserFile } from 'element-plus'
46 import { ElNotification } from 'element-plus'
9259c2 47 import { createImageViewer } from '@/components/ImageViewer'
820397 48
H 49 import { propTypes } from '@/utils/propTypes'
50 import { useUpload } from '@/components/UploadFile/src/useUpload'
51
52 defineOptions({ name: 'UploadImgs' })
53
54 const message = useMessage() // 消息弹窗
9259c2 55 // 查看图片
H 56 const imagePreview = (imgUrl: string) => {
57   createImageViewer({
58     zIndex: 9999999,
59     urlList: [imgUrl]
60   })
61 }
820397 62
H 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'
74
75 const props = defineProps({
76   modelValue: propTypes.oneOfType<string | string[]>([String, Array<String>]).isRequired,
77   drag: propTypes.bool.def(true), // 是否支持拖拽上传 ==> 非必传(默认为 true)
78   disabled: propTypes.bool.def(false), // 是否禁用上传组件 ==> 非必传(默认为 false)
79   limit: propTypes.number.def(5), // 最大图片上传数 ==> 非必传(默认为 5张)
80   fileSize: propTypes.number.def(5), // 图片大小限制 ==> 非必传(默认为 5M)
81   fileType: propTypes.array.def(['image/jpeg', 'image/png', 'image/gif']), // 图片类型限制 ==> 非必传(默认为 ["image/jpeg", "image/png", "image/gif"])
82   height: propTypes.string.def('150px'), // 组件高度 ==> 非必传(默认为 150px)
83   width: propTypes.string.def('150px'), // 组件宽度 ==> 非必传(默认为 150px)
84   borderradius: propTypes.string.def('8px') // 组件边框圆角 ==> 非必传(默认为 8px)
85 })
86
87 const { uploadUrl, httpRequest } = useUpload()
88
89 const fileList = ref<UploadUserFile[]>([])
90 const uploadNumber = ref<number>(0)
91 const uploadList = ref<UploadUserFile[]>([])
92 /**
93  * @description 文件上传之前判断
94  * @param rawFile 上传的文件
95  * */
96 const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
97   const imgSize = rawFile.size / 1024 / 1024 < props.fileSize
98   const imgType = props.fileType
99   if (!imgType.includes(rawFile.type as FileTypes))
100     ElNotification({
101       title: '温馨提示',
102       message: '上传图片不符合所需的格式!',
103       type: 'warning'
104     })
105   if (!imgSize)
106     ElNotification({
107       title: '温馨提示',
108       message: `上传图片大小不能超过 ${props.fileSize}M!`,
109       type: 'warning'
110     })
111   uploadNumber.value++
112   return imgType.includes(rawFile.type as FileTypes) && imgSize
113 }
114
115 // 图片上传成功
116 interface UploadEmits {
117   (e: 'update:modelValue', value: string[]): void
118 }
119
120 const emit = defineEmits<UploadEmits>()
121 const uploadSuccess: UploadProps['onSuccess'] = (res: any): void => {
122   message.success('上传成功')
123   // 删除自身
124   const index = fileList.value.findIndex((item) => item.response?.data === res.data)
125   fileList.value.splice(index, 1)
126   uploadList.value.push({ name: res.data, url: res.data })
127   if (uploadList.value.length == uploadNumber.value) {
128     fileList.value.push(...uploadList.value)
129     uploadList.value = []
130     uploadNumber.value = 0
131     emitUpdateModelValue()
132   }
133 }
134
135 // 监听模型绑定值变动
136 watch(
137   () => props.modelValue,
138   (val: string | string[]) => {
139     if (!val) {
140       fileList.value = [] // fix:处理掉缓存,表单重置后上传组件的内容并没有重置
141       return
142     }
143
144     fileList.value = [] // 保障数据为空
145     fileList.value.push(
146       ...(val as string[]).map((url) => ({ name: url.substring(url.lastIndexOf('/') + 1), url }))
147     )
148   },
149   { immediate: true, deep: true }
150 )
151 // 发送图片链接列表更新
152 const emitUpdateModelValue = () => {
153   let result: string[] = fileList.value.map((file) => file.url!)
154   emit('update:modelValue', result)
155 }
156 // 删除图片
157 const handleRemove = (uploadFile: UploadFile) => {
158   fileList.value = fileList.value.filter(
159     (item) => item.url !== uploadFile.url || item.name !== uploadFile.name
160   )
161   emit(
162     'update:modelValue',
163     fileList.value.map((file) => file.url!)
164   )
165 }
166
167 // 图片上传错误提示
168 const uploadError = () => {
169   ElNotification({
170     title: '温馨提示',
171     message: '图片上传失败,请您重新上传!',
172     type: 'error'
173   })
174 }
175
176 // 文件数超出提示
177 const handleExceed = () => {
178   ElNotification({
179     title: '温馨提示',
180     message: `当前最多只能上传 ${props.limit} 张图片,请移除后上传!`,
181     type: 'warning'
182   })
183 }
184 </script>
185
186 <style lang="scss" scoped>
187 .is-error {
188   .upload {
189     :deep(.el-upload--picture-card),
190     :deep(.el-upload-dragger) {
191       border: 1px dashed var(--el-color-danger) !important;
192
193       &:hover {
194         border-color: var(--el-color-primary) !important;
195       }
196     }
197   }
198 }
199
200 :deep(.disabled) {
201   .el-upload--picture-card,
202   .el-upload-dragger {
203     cursor: not-allowed;
204     background: var(--el-disabled-bg-color) !important;
205     border: 1px dashed var(--el-border-color-darker);
206
207     &:hover {
208       border-color: var(--el-border-color-darker) !important;
209     }
210   }
211 }
212
213 .upload-box {
214   .no-border {
215     :deep(.el-upload--picture-card) {
216       border: none !important;
217     }
218   }
219
220   :deep(.upload) {
221     .el-upload-dragger {
222       display: flex;
223       align-items: center;
224       justify-content: center;
225       width: 100%;
226       height: 100%;
227       padding: 0;
228       overflow: hidden;
229       border: 1px dashed var(--el-border-color-darker);
230       border-radius: v-bind(borderradius);
231
232       &:hover {
233         border: 1px dashed var(--el-color-primary);
234       }
235     }
236
237     .el-upload-dragger.is-dragover {
238       background-color: var(--el-color-primary-light-9);
239       border: 2px dashed var(--el-color-primary) !important;
240     }
241
242     .el-upload-list__item,
243     .el-upload--picture-card {
244       width: v-bind(width);
245       height: v-bind(height);
246       background-color: transparent;
247       border-radius: v-bind(borderradius);
248     }
249
250     .upload-image {
251       width: 100%;
252       height: 100%;
253       object-fit: contain;
254     }
255
256     .upload-handle {
257       position: absolute;
258       top: 0;
259       right: 0;
260       display: flex;
261       width: 100%;
262       height: 100%;
263       cursor: pointer;
264       background: rgb(0 0 0 / 60%);
265       opacity: 0;
266       box-sizing: border-box;
267       transition: var(--el-transition-duration-fast);
268       align-items: center;
269       justify-content: center;
270
271       .handle-icon {
272         display: flex;
273         flex-direction: column;
274         align-items: center;
275         justify-content: center;
276         padding: 0 6%;
277         color: aliceblue;
278
279         .el-icon {
280           margin-bottom: 15%;
281           font-size: 140%;
282         }
283
284         span {
285           font-size: 100%;
286         }
287       }
288     }
289
290     .el-upload-list__item {
291       &:hover {
292         .upload-handle {
293           opacity: 1;
294         }
295       }
296     }
297
298     .upload-empty {
299       display: flex;
300       flex-direction: column;
301       align-items: center;
302       font-size: 12px;
303       line-height: 30px;
304       color: var(--el-color-info);
305
306       .el-icon {
307         font-size: 28px;
308         color: var(--el-text-color-secondary);
309       }
310     }
311   }
312
313   .el-upload__tip {
314     line-height: 15px;
315     text-align: center;
316   }
317 }
318 </style>