From 4463f257ba0e53b56973bc34b987aea45aa05aaf Mon Sep 17 00:00:00 2001 From: hwj Date: Fri, 30 Jan 2026 11:16:13 +0800 Subject: [PATCH] =?UTF-8?q?style=EF=BC=9A=E4=B8=8A=E4=BC=A0=E6=8E=A7?= =?UTF-8?q?=E4=BB=B6-=E9=80=82=E9=85=8D=E6=96=B0=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E5=93=8D=E5=BA=94=E6=A0=BC=E5=BC=8F/=E5=B7=B2=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=E6=98=BE=E7=A4=BA=E6=96=87=E4=BB=B6=E5=90=8D=E7=A7=B0?= =?UTF-8?q?/=E6=9C=80=E5=A4=A7=E9=99=90=E5=88=B6=E6=94=B9=E4=B8=BA100MB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/UploadFile/src/UploadFile.vue | 63 ++++++++++++++---- src/components/UploadFile/src/UploadImg.vue | 20 +++++- src/components/UploadFile/src/UploadImgs.vue | 67 ++++++++++++++++---- src/components/UploadFile/src/useUpload.ts | 17 +++-- 4 files changed, 131 insertions(+), 36 deletions(-) diff --git a/src/components/UploadFile/src/UploadFile.vue b/src/components/UploadFile/src/UploadFile.vue index 25f8a709..a225953f 100644 --- a/src/components/UploadFile/src/UploadFile.vue +++ b/src/components/UploadFile/src/UploadFile.vue @@ -84,7 +84,7 @@ const emit = defineEmits(['update:modelValue']) const props = defineProps({ modelValue: propTypes.oneOfType([String, Array]).isRequired, fileType: propTypes.array.def(['png', 'jpg', 'jpeg', 'doc', 'xls', 'ppt', 'txt', 'pdf']), // 文件类型, 例如['png', 'jpg', 'jpeg'] - fileSize: propTypes.number.def(5), // 大小限制(MB) + fileSize: propTypes.number.def(100), // 大小限制(MB) limit: propTypes.number.def(5), // 数量限制 autoUpload: propTypes.bool.def(true), // 自动上传 drag: propTypes.bool.def(false), // 拖拽上传 @@ -97,6 +97,7 @@ const uploadRef = ref() const uploadList = ref([]) const fileList = ref([]) const uploadNumber = ref(0) +const nameMap = ref>({}) const { uploadUrl, httpRequest } = useUpload() @@ -130,14 +131,41 @@ const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => { // const handleFileChange = (uploadFile: UploadFile): void => { // uploadRef.value.data.path = uploadFile.name // } -// 文件上传成功 -const handleFileSuccess: UploadProps['onSuccess'] = (res: any): void => { +const getUrlFromRes = (res: any): string => { + if (!res) return '' + if (typeof res === 'string') return res + if (typeof res.data === 'string') return res.data + const data = res.data || res + if (typeof data === 'string') return data + if (data.fileUrl) return data.fileUrl + return '' +} + +const getNameFromRes = (res: any, url: string): string => { + const data = res && res.data ? res.data : res + if (data && typeof data === 'object' && data.fileName) return data.fileName + if (!url) return '' + const idx = url.lastIndexOf('/') + return idx !== -1 ? url.substring(idx + 1) : url +} + +const handleFileSuccess: UploadProps['onSuccess'] = (res: any, uploadFile): void => { + const url = getUrlFromRes(res) + if (!url) { + message.error('上传返回数据异常') + return + } + const name = getNameFromRes(res, url) + if (url && name) { + nameMap.value[url] = name + } message.success('上传成功') - // 删除自身 - const index = fileList.value.findIndex((item) => item.response?.data === res.data) - fileList.value.splice(index, 1) - uploadList.value.push({ name: res.data, url: res.data }) - if (uploadList.value.length == uploadNumber.value) { + const index = fileList.value.findIndex((item) => item.uid === uploadFile.uid) + if (index > -1) { + fileList.value.splice(index, 1) + } + uploadList.value.push({ name, url }) + if (uploadList.value.length === uploadNumber.value) { fileList.value.push(...uploadList.value) uploadList.value = [] uploadNumber.value = 0 @@ -156,7 +184,10 @@ const excelUploadError: UploadProps['onError'] = (): void => { const handleRemove = (file: UploadFile) => { const index = fileList.value.map((f) => f.name).indexOf(file.name) if (index > -1) { - fileList.value.splice(index, 1) + const removed = fileList.value.splice(index, 1)[0] + if (removed && removed.url && nameMap.value[removed.url]) { + delete nameMap.value[removed.url] + } emitUpdateModelValue() } } @@ -164,6 +195,13 @@ const handlePreview: UploadProps['onPreview'] = (uploadFile) => { console.log(uploadFile) } +const getDisplayNameByUrl = (url: string): string => { + const cached = nameMap.value[url] + if (cached) return cached + const idx = url.lastIndexOf('/') + return idx !== -1 ? url.substring(idx + 1) : url +} + // 监听模型绑定值变动 watch( () => props.modelValue, @@ -176,14 +214,13 @@ watch( fileList.value = [] // 保障数据为空 // 情况1:字符串 if (isString(val)) { - fileList.value.push( - ...val.split(',').map((url) => ({ name: url.substring(url.lastIndexOf('/') + 1), url })) - ) + const urls = val.split(',').filter((u) => !!u) + fileList.value.push(...urls.map((url) => ({ name: getDisplayNameByUrl(url), url }))) return } // 情况2:数组 fileList.value.push( - ...(val as string[]).map((url) => ({ name: url.substring(url.lastIndexOf('/') + 1), url })) + ...(val as string[]).map((url) => ({ name: getDisplayNameByUrl(url), url })) ) }, { immediate: true, deep: true } diff --git a/src/components/UploadFile/src/UploadImg.vue b/src/components/UploadFile/src/UploadImg.vue index ac0c162d..5abbf452 100644 --- a/src/components/UploadFile/src/UploadImg.vue +++ b/src/components/UploadFile/src/UploadImg.vue @@ -73,7 +73,7 @@ const props = defineProps({ modelValue: propTypes.string.def(''), drag: propTypes.bool.def(true), // 是否支持拖拽上传 ==> 非必传(默认为 true) disabled: propTypes.bool.def(false), // 是否禁用上传组件 ==> 非必传(默认为 false) - fileSize: propTypes.number.def(5), // 图片大小限制 ==> 非必传(默认为 5M) + fileSize: propTypes.number.def(100), // 图片大小限制 ==> 非必传(默认为 5M) fileType: propTypes.array.def(['image/jpeg', 'image/png', 'image/gif']), // 图片类型限制 ==> 非必传(默认为 ["image/jpeg", "image/png", "image/gif"]) height: propTypes.string.def('150px'), // 组件高度 ==> 非必传(默认为 150px) width: propTypes.string.def('150px'), // 组件宽度 ==> 非必传(默认为 150px) @@ -115,10 +115,24 @@ const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => { return imgType.includes(rawFile.type as FileTypes) && imgSize } -// 图片上传成功提示 +const getUrlFromRes = (res: any): string => { + if (!res) return '' + if (typeof res === 'string') return res + if (typeof res.data === 'string') return res.data + const data = res.data || res + if (typeof data === 'string') return data + if (data.fileUrl) return data.fileUrl + return '' +} + const uploadSuccess: UploadProps['onSuccess'] = (res: any): void => { + const url = getUrlFromRes(res) + if (!url) { + message.notifyError('上传返回数据异常') + return + } message.success('上传成功') - emit('update:modelValue', res.data) + emit('update:modelValue', url) } // 图片上传错误提示 diff --git a/src/components/UploadFile/src/UploadImgs.vue b/src/components/UploadFile/src/UploadImgs.vue index 59857a98..86e1109c 100644 --- a/src/components/UploadFile/src/UploadImgs.vue +++ b/src/components/UploadFile/src/UploadImgs.vue @@ -77,7 +77,7 @@ const props = defineProps({ drag: propTypes.bool.def(true), // 是否支持拖拽上传 ==> 非必传(默认为 true) disabled: propTypes.bool.def(false), // 是否禁用上传组件 ==> 非必传(默认为 false) limit: propTypes.number.def(5), // 最大图片上传数 ==> 非必传(默认为 5张) - fileSize: propTypes.number.def(5), // 图片大小限制 ==> 非必传(默认为 5M) + fileSize: propTypes.number.def(100), // 图片大小限制 ==> 非必传(默认为 5M) fileType: propTypes.array.def(['image/jpeg', 'image/png', 'image/gif']), // 图片类型限制 ==> 非必传(默认为 ["image/jpeg", "image/png", "image/gif"]) height: propTypes.string.def('150px'), // 组件高度 ==> 非必传(默认为 150px) width: propTypes.string.def('150px'), // 组件宽度 ==> 非必传(默认为 150px) @@ -89,6 +89,7 @@ const { uploadUrl, httpRequest } = useUpload() const fileList = ref([]) const uploadNumber = ref(0) const uploadList = ref([]) +const nameMap = ref>({}) /** * @description 文件上传之前判断 * @param rawFile 上传的文件 @@ -118,13 +119,41 @@ interface UploadEmits { } const emit = defineEmits() -const uploadSuccess: UploadProps['onSuccess'] = (res: any): void => { +const getUrlFromRes = (res: any): string => { + if (!res) return '' + if (typeof res === 'string') return res + if (typeof res.data === 'string') return res.data + const data = res.data || res + if (typeof data === 'string') return data + if (data.fileUrl) return data.fileUrl + return '' +} + +const getNameFromRes = (res: any, url: string): string => { + const data = res && res.data ? res.data : res + if (data && typeof data === 'object' && data.fileName) return data.fileName + if (!url) return '' + const idx = url.lastIndexOf('/') + return idx !== -1 ? url.substring(idx + 1) : url +} + +const uploadSuccess: UploadProps['onSuccess'] = (res: any, uploadFile): void => { + const url = getUrlFromRes(res) + if (!url) { + message.error('上传返回数据异常') + return + } + const name = getNameFromRes(res, url) + if (url && name) { + nameMap.value[url] = name + } message.success('上传成功') - // 删除自身 - const index = fileList.value.findIndex((item) => item.response?.data === res.data) - fileList.value.splice(index, 1) - uploadList.value.push({ name: res.data, url: res.data }) - if (uploadList.value.length == uploadNumber.value) { + const index = fileList.value.findIndex((item) => item.uid === uploadFile.uid) + if (index > -1) { + fileList.value.splice(index, 1) + } + uploadList.value.push({ name, url }) + if (uploadList.value.length === uploadNumber.value) { fileList.value.push(...uploadList.value) uploadList.value = [] uploadNumber.value = 0 @@ -132,6 +161,13 @@ const uploadSuccess: UploadProps['onSuccess'] = (res: any): void => { } } +const getDisplayNameByUrl = (url: string): string => { + const cached = nameMap.value[url] + if (cached) return cached + const idx = url.lastIndexOf('/') + return idx !== -1 ? url.substring(idx + 1) : url +} + // 监听模型绑定值变动 watch( () => props.modelValue, @@ -142,9 +178,8 @@ watch( } fileList.value = [] // 保障数据为空 - fileList.value.push( - ...(val as string[]).map((url) => ({ name: url.substring(url.lastIndexOf('/') + 1), url })) - ) + const list = Array.isArray(val) ? (val as string[]) : [] + fileList.value.push(...list.map((url) => ({ name: getDisplayNameByUrl(url), url }))) }, { immediate: true, deep: true } ) @@ -155,9 +190,15 @@ const emitUpdateModelValue = () => { } // 删除图片 const handleRemove = (uploadFile: UploadFile) => { - fileList.value = fileList.value.filter( - (item) => item.url !== uploadFile.url || item.name !== uploadFile.name - ) + fileList.value = fileList.value.filter((item) => { + if (item.url === uploadFile.url && item.name === uploadFile.name) { + if (item.url && nameMap.value[item.url]) { + delete nameMap.value[item.url] + } + return false + } + return true + }) emit( 'update:modelValue', fileList.value.map((file) => file.url!) diff --git a/src/components/UploadFile/src/useUpload.ts b/src/components/UploadFile/src/useUpload.ts index 2981e12d..1e0893be 100644 --- a/src/components/UploadFile/src/useUpload.ts +++ b/src/components/UploadFile/src/useUpload.ts @@ -19,11 +19,8 @@ export const useUpload = () => { const httpRequest = async (options: UploadRequestOptions) => { // 模式一:前端上传 if (isClientUpload) { - // 1.1 生成文件名称 const fileName = await generateFileName(options.file) - // 1.2 获取文件预签名地址 const presignedInfo = await FileApi.getFilePresignedUrl(fileName) - // 1.3 上传文件(不能使用 ElUpload 的 ajaxUpload 方法的原因:其使用的是 FormData 上传,Minio 不支持) return axios .put(presignedInfo.uploadUrl, options.file, { headers: { @@ -31,10 +28,16 @@ export const useUpload = () => { } }) .then(() => { - // 1.4. 记录文件信息到后端(异步) - createFile(presignedInfo, fileName, options.file) - // 通知成功,数据格式保持与后端上传的返回结果一致 - return { data: presignedInfo.url } + const fileVo = createFile(presignedInfo, fileName, options.file) + return { + code: 0, + status: 0, + data: { + fileName: fileVo.path, + fileUrl: fileVo.url + }, + msg: '' + } }) } else { // 模式二:后端上传