|
|
@@ -23,7 +23,17 @@
|
|
|
</slot>
|
|
|
</div>
|
|
|
<template #file="{ file }">
|
|
|
- <img :src="file.url" class="upload-image" />
|
|
|
+ <img
|
|
|
+ v-if="file.url && file.uid !== undefined && !imageLoadError.has(file.uid)"
|
|
|
+ :src="file.url"
|
|
|
+ class="upload-image"
|
|
|
+ @error="handleImageError"
|
|
|
+ @load="handleImageLoad"
|
|
|
+ />
|
|
|
+ <div v-else class="upload-image-placeholder">
|
|
|
+ <el-icon><Picture /></el-icon>
|
|
|
+ <span>图片预览</span>
|
|
|
+ </div>
|
|
|
<div class="upload-handle" @click.stop>
|
|
|
<div class="handle-icon" @click="handlePictureCardPreview(file)">
|
|
|
<el-icon><ZoomIn /></el-icon>
|
|
|
@@ -45,7 +55,7 @@
|
|
|
|
|
|
<script setup lang="ts" name="UploadImgs">
|
|
|
import { ref, computed, inject, watch } from "vue";
|
|
|
-import { Plus } from "@element-plus/icons-vue";
|
|
|
+import { Plus, Picture } from "@element-plus/icons-vue";
|
|
|
import { uploadImg } from "@/api/modules/upload";
|
|
|
import type { UploadProps, UploadFile, UploadUserFile, UploadRequestOptions } from "element-plus";
|
|
|
import { ElNotification, formContextKey, formItemContextKey } from "element-plus";
|
|
|
@@ -94,6 +104,13 @@ watch(
|
|
|
}
|
|
|
);
|
|
|
|
|
|
+// 记录正在上传的文件数量(使用 Set 跟踪文件 UID,更可靠)
|
|
|
+const uploadingFiles = new Set<string | number>();
|
|
|
+// 标记是否已经显示过成功提示,防止重复提示
|
|
|
+let hasShownSuccessNotification = false;
|
|
|
+// 记录图片加载失败的 UID
|
|
|
+const imageLoadError = ref<Set<string | number>>(new Set());
|
|
|
+
|
|
|
/**
|
|
|
* @description 文件上传之前判断
|
|
|
* @param rawFile 选择的文件
|
|
|
@@ -123,6 +140,13 @@ const beforeUpload: UploadProps["beforeUpload"] = rawFile => {
|
|
|
* @param options upload 所有配置项
|
|
|
* */
|
|
|
const handleHttpUpload = async (options: UploadRequestOptions) => {
|
|
|
+ // 开始上传,记录文件 UID
|
|
|
+ const fileUid = options.file.uid;
|
|
|
+ // 如果这是新的一批上传(Set 为空),重置成功提示标志
|
|
|
+ if (uploadingFiles.size === 0) {
|
|
|
+ hasShownSuccessNotification = false;
|
|
|
+ }
|
|
|
+ uploadingFiles.add(fileUid);
|
|
|
let formData = new FormData();
|
|
|
formData.append("file", options.file);
|
|
|
try {
|
|
|
@@ -130,6 +154,8 @@ const handleHttpUpload = async (options: UploadRequestOptions) => {
|
|
|
const { data } = await api(formData);
|
|
|
options.onSuccess(data);
|
|
|
} catch (error) {
|
|
|
+ // 上传失败,移除文件 UID
|
|
|
+ uploadingFiles.delete(fileUid);
|
|
|
options.onError(error as any);
|
|
|
}
|
|
|
};
|
|
|
@@ -142,17 +168,37 @@ const handleHttpUpload = async (options: UploadRequestOptions) => {
|
|
|
const emit = defineEmits<{
|
|
|
"update:fileList": [value: UploadUserFile[]];
|
|
|
}>();
|
|
|
-const uploadSuccess = (response: { fileUrl: string } | undefined, uploadFile: UploadFile) => {
|
|
|
- if (!response) return;
|
|
|
- uploadFile.url = response.fileUrl ? response.fileUrl : response[0];
|
|
|
- emit("update:fileList", _fileList.value);
|
|
|
- // 调用 el-form 内部的校验方法(可自动校验)
|
|
|
- formItemContext?.prop && formContext?.validateField([formItemContext.prop as string]);
|
|
|
- ElNotification({
|
|
|
- title: "温馨提示",
|
|
|
- message: "图片上传成功!",
|
|
|
- type: "success"
|
|
|
- });
|
|
|
+const uploadSuccess = (response: { fileUrl: string } | string | string[] | undefined, uploadFile: UploadFile) => {
|
|
|
+ // 移除已完成的上传文件
|
|
|
+ uploadingFiles.delete(uploadFile.uid);
|
|
|
+
|
|
|
+ // 处理响应数据
|
|
|
+ if (response) {
|
|
|
+ if (typeof response === "string") {
|
|
|
+ uploadFile.url = response;
|
|
|
+ } else if (Array.isArray(response)) {
|
|
|
+ uploadFile.url = response[0];
|
|
|
+ } else if (response.fileUrl) {
|
|
|
+ uploadFile.url = response.fileUrl;
|
|
|
+ } else {
|
|
|
+ // 如果 response 是对象但没有 fileUrl,尝试使用第一个属性值
|
|
|
+ const values = Object.values(response);
|
|
|
+ uploadFile.url = values[0] as string;
|
|
|
+ }
|
|
|
+ emit("update:fileList", _fileList.value);
|
|
|
+ // 调用 el-form 内部的校验方法(可自动校验)
|
|
|
+ formItemContext?.prop && formContext?.validateField([formItemContext.prop as string]);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 当所有文件上传完成时,显示成功提示(只显示一次)
|
|
|
+ if (uploadingFiles.size === 0 && !hasShownSuccessNotification) {
|
|
|
+ hasShownSuccessNotification = true;
|
|
|
+ ElNotification({
|
|
|
+ title: "温馨提示",
|
|
|
+ message: "图片上传成功!",
|
|
|
+ type: "success"
|
|
|
+ });
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
@@ -162,6 +208,31 @@ const uploadSuccess = (response: { fileUrl: string } | undefined, uploadFile: Up
|
|
|
const handleRemove = (file: UploadFile) => {
|
|
|
_fileList.value = _fileList.value.filter(item => item.url !== file.url || item.name !== file.name);
|
|
|
emit("update:fileList", _fileList.value);
|
|
|
+ // 移除时清除错误状态
|
|
|
+ imageLoadError.value.delete(file.uid);
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * @description 图片加载错误处理
|
|
|
+ * */
|
|
|
+const handleImageError = (event: Event) => {
|
|
|
+ const img = event.target as HTMLImageElement;
|
|
|
+ // 通过查找对应的文件来获取 UID
|
|
|
+ const file = _fileList.value.find(f => f.url === img.src);
|
|
|
+ if (file && file.uid !== undefined) {
|
|
|
+ imageLoadError.value.add(file.uid);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * @description 图片加载成功处理
|
|
|
+ * */
|
|
|
+const handleImageLoad = (event: Event) => {
|
|
|
+ const img = event.target as HTMLImageElement;
|
|
|
+ const file = _fileList.value.find(f => f.url === img.src);
|
|
|
+ if (file && file.uid !== undefined) {
|
|
|
+ imageLoadError.value.delete(file.uid);
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
@@ -258,6 +329,23 @@ const handlePictureCardPreview: UploadProps["onPreview"] = file => {
|
|
|
height: 100%;
|
|
|
object-fit: contain;
|
|
|
}
|
|
|
+ .upload-image-placeholder {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
+ background-color: var(--el-fill-color-lighter);
|
|
|
+ .el-icon {
|
|
|
+ margin-bottom: 8px;
|
|
|
+ font-size: 32px;
|
|
|
+ }
|
|
|
+ span {
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+ }
|
|
|
.upload-handle {
|
|
|
position: absolute;
|
|
|
top: 0;
|