zhangwencui
8 天以前 b9b25d756469e576a765e28689f8f267bd5c2d1a
src/components/imageUpload/index.vue
@@ -1,19 +1,21 @@
<template>
  <view class="camera-upload">
    <!-- 拍照/拍视频按钮 -->
    <view v-if="!disabled" class="camera-buttons">
    <view v-if="!disabled"
          class="camera-buttons">
      <view class="button-row">
        <u-button
          type="primary"
        <u-button type="primary"
          @click="takePhoto"
          :loading="uploading"
          :disabled="fileList.length >= limit"
          :customStyle="{ marginRight: '10px', flex: 1 }"
        >
          <u-icon name="camera" size="18" color="#fff" style="margin-right: 5px;"></u-icon>
                  :customStyle="{ marginRight: '10px', flex: 1 }">
          <u-icon name="camera"
                  size="18"
                  color="#fff"
                  style="margin-right: 5px;"></u-icon>
          {{ uploading ? '上传中...' : '拍照' }}
        </u-button>
        <u-button
        <!-- <u-button
          type="success" 
          @click="takeVideo"
          :loading="uploading"
@@ -22,54 +24,46 @@
        >
          <u-icon name="video" size="18" color="#fff" style="margin-right: 5px;"></u-icon>
          {{ uploading ? '上传中...' : '拍视频' }}
        </u-button>
        </u-button> -->
      </view>
    </view>
    <!-- 提示信息 -->
    <view v-if="showTip && !disabled" class="upload-tip">
    <view v-if="showTip && !disabled"
          class="upload-tip">
      请使用相机
      <text v-if="fileSize" class="tip-text">
      <text v-if="fileSize"
            class="tip-text">
        拍摄大小不超过 <text class="tip-highlight">{{ fileSize }}MB</text>
      </text>
      的
      <text class="tip-highlight">照片或视频</text>
      <text class="tip-highlight">照片</text>
    </view>
    <!-- 媒体文件列表 -->
    <view class="media-list">
      <view
        v-for="(file, index) in fileList"
      <view v-for="(file, index) in fileList"
        :key="file.uid || index"
        class="media-item"
      >
            class="media-item">
        <!-- 预览区域 -->
        <view class="media-preview" @click="previewMedia(file, index)">
          <image
            v-if="file.type === 'image'"
        <view class="media-preview"
              @click="previewMedia(file, index)">
          <image v-if="file.type === 'image'"
            :src="file.url || file.tempFilePath" 
            class="preview-image"
            mode="aspectFill"
          ></image>
          <video
            v-else-if="file.type === 'video'"
                 mode="aspectFill"></image>
          <video v-else-if="file.type === 'video'"
            :src="file.url || file.tempFilePath"
            class="preview-video"
            :controls="false"
          ></video>
                 :controls="false"></video>
          <view class="media-type-icon">
            <u-icon
              :name="file.type === 'image' ? 'photo' : 'video'"
            <u-icon :name="file.type === 'image' ? 'photo' : 'video'"
              size="12" 
              color="#fff"
            ></u-icon>
                    color="#fff"></u-icon>
          </view>
        </view>
        <!-- 操作按钮 -->
        <view class="media-actions" v-if="!disabled">
          <u-button
            type="error"
        <view class="media-actions"
              v-if="!disabled">
          <u-button type="error"
            size="mini" 
            @click="handleDelete(index)"
            :customStyle="{ 
@@ -77,27 +71,24 @@
              height: '24px', 
              fontSize: '10px',
              padding: '0 8px'
            }"
          >
            }">
            删除
          </u-button>
        </view>
      </view>
    </view>
    <!-- 上传进度 -->
    <view v-if="uploading" class="upload-progress">
      <u-line-progress
        :percentage="uploadProgress"
    <view v-if="uploading"
          class="upload-progress">
      <u-line-progress :percentage="uploadProgress"
        :showText="true"
        activeColor="#409eff"
      ></u-line-progress>
                       activeColor="#409eff"></u-line-progress>
    </view>
  </view>
</template>
<script setup>
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue';
  import { ref, computed, watch, onMounted, onUnmounted, nextTick } from "vue";
import { getToken } from "@/utils/auth";
// Props 定义
@@ -109,7 +100,7 @@
  fileSize: { type: Number, default: 10 }, // 默认10MB,适合视频
  fileType: { 
    type: Array, 
    default: () => ["jpg", "jpeg", "png", "mp4", "mov"]
      default: () => ["jpg", "jpeg", "png", "mp4", "mov"],
  },
  isShowTip: { type: Boolean, default: true },
  disabled: { type: Boolean, default: false },
@@ -119,7 +110,7 @@
});
// 事件定义
const emit = defineEmits(['update:modelValue']);
  const emit = defineEmits(["update:modelValue"]);
// 响应式数据
const number = ref(0);
@@ -131,15 +122,15 @@
// 计算属性
const uploadFileUrl = computed(() => {
  // 获取基础API地址,适配uniapp环境
  let baseUrl = '';
    let baseUrl = "";
  
  // 尝试多种方式获取baseUrl
  if (process.env.VUE_APP_BASE_API) {
    baseUrl = process.env.VUE_APP_BASE_API;
  } else if (process.env.NODE_ENV === 'development') {
    baseUrl = 'http://192.168.1.147:9036';
    } else if (process.env.NODE_ENV === "development") {
      baseUrl = "http://192.168.1.147:9036";
  } else {
    baseUrl = 'http://192.168.1.147:9036';
      baseUrl = "http://192.168.1.147:9036";
  }
  
  const fullUrl = baseUrl + props.action;
@@ -149,7 +140,9 @@
  const token = getToken();
  return token ? { Authorization: "Bearer " + token } : {};
});
const showTip = computed(() => props.isShowTip && (props.fileType || props.fileSize));
  const showTip = computed(
    () => props.isShowTip && (props.fileType || props.fileSize)
  );
// 初始化和编辑初始化方法
const init = () => {
  fileList.value = [];
@@ -157,9 +150,9 @@
  number.value = 0;
};
const editInit = (val) => {
  const editInit = val => {
  fileList.value = [];
  val.storageBlobDTO.forEach((element) => {
    val.storageBlobDTO.forEach(element => {
    // 确保文件数据包含所有必要字段,包括id
    const fileData = {
      ...element,
@@ -167,11 +160,13 @@
      url: element.url || element.downloadUrl,
      bucketFilename: element.bucketFilename || element.name,
      downloadUrl: element.downloadUrl || element.url,
      type: element.type || (element.url && element.url.includes('video') ? 'video' : 'image'),
        type:
          element.type ||
          (element.url && element.url.includes("video") ? "video" : "image"),
      name: element.name || element.bucketFilename || `文件_${Date.now()}`,
      size: element.size || 0,
      createTime: element.createTime || new Date().getTime(),
      uid: element.uid || new Date().getTime() + Math.random()
        uid: element.uid || new Date().getTime() + Math.random(),
    };
    fileList.value.push(fileData);
    uploadedSuccessfully();
@@ -180,17 +175,17 @@
// 测试服务器连接
const testServerConnection = () => {
  return new Promise((resolve) => {
    return new Promise(resolve => {
    uni.request({
      url: uploadFileUrl.value.replace('/common/minioUploads', '/common/test'),
      method: 'GET',
        url: uploadFileUrl.value.replace("/common/minioUploads", "/common/test"),
        method: "GET",
      timeout: 5000,
      success: (res) => {
        success: res => {
        resolve(true);
      },
      fail: (err) => {
        fail: err => {
        resolve(false);
      }
        },
    });
  });
};
@@ -199,13 +194,13 @@
onUnmounted(() => {
  // 清理上传状态
  if (uploading.value) {
    uploading.value = false
      uploading.value = false;
  }
  
  // 隐藏可能显示的加载提示
  uni.hideLoading()
  uni.hideToast()
})
    uni.hideLoading();
    uni.hideToast();
  });
// 暴露方法
defineExpose({ init, editInit, testServerConnection });
@@ -213,7 +208,7 @@
// 监听 modelValue 变化
watch(
  () => props.modelValue,
  (val) => {
    val => {
    if (val) {
      let temp = 1;
      let list = [];
@@ -224,7 +219,7 @@
        list = val.split(",").map(url => ({ url: url.trim() }));
      }
      
      fileList.value = list.map((item) => {
        fileList.value = list.map(item => {
        if (typeof item === "string") {
          item = { name: item, url: item };
        }
@@ -233,10 +228,12 @@
          ...item,
          id: item.id, // 保留id字段
          uid: item.uid || new Date().getTime() + temp++,
          type: item.type || (item.url && item.url.includes('video') ? 'video' : 'image'),
            type:
              item.type ||
              (item.url && item.url.includes("video") ? "video" : "image"),
          name: item.name || item.bucketFilename || `文件_${Date.now()}`,
          size: item.size || 0,
          createTime: item.createTime || new Date().getTime()
            createTime: item.createTime || new Date().getTime(),
        };
      });
    } else {
@@ -251,49 +248,50 @@
  if (fileList.value.length >= props.limit) {
    uni.showToast({
      title: `最多只能拍摄${props.limit}个文件`,
      icon: 'none'
        icon: "none",
    });
    return;
  }
  
  uni.chooseImage({
    count: 1,
    sizeType: ['compressed', 'original'],
    sourceType: ['camera'],
    success: (res) => {
      sizeType: ["compressed", "original"],
      sourceType: ["camera"],
      success: res => {
      try {
        if (!res.tempFilePaths || res.tempFilePaths.length === 0) {
          throw new Error('未获取到图片文件');
            throw new Error("未获取到图片文件");
        }
        
        const tempFilePath = res.tempFilePaths[0];
        const tempFile = res.tempFiles && res.tempFiles[0] ? res.tempFiles[0] : {};
          const tempFile =
            res.tempFiles && res.tempFiles[0] ? res.tempFiles[0] : {};
        
        const file = {
          tempFilePath: tempFilePath,
          type: 'image',
            type: "image",
          name: `photo_${Date.now()}.jpg`,
          size: tempFile.size || 0,
          createTime: new Date().getTime(),
          uid: Date.now() + Math.random()
            uid: Date.now() + Math.random(),
        };
        
        handleBeforeUpload(file);
      } catch (error) {
        console.error('处理拍照结果失败:', error);
          console.error("处理拍照结果失败:", error);
        uni.showToast({
          title: '处理图片失败',
          icon: 'error'
            title: "处理图片失败",
            icon: "error",
        });
      }
    },
    fail: (err) => {
      console.error('拍照失败:', err);
      fail: err => {
        console.error("拍照失败:", err);
      uni.showToast({
        title: '拍照失败: ' + (err.errMsg || '未知错误'),
        icon: 'error'
          title: "拍照失败: " + (err.errMsg || "未知错误"),
          icon: "error",
      });
    }
      },
  });
};
@@ -302,52 +300,52 @@
  if (fileList.value.length >= props.limit) {
    uni.showToast({
      title: `最多只能拍摄${props.limit}个文件`,
      icon: 'none'
        icon: "none",
    });
    return;
  }
  
  uni.chooseVideo({
    sourceType: ['camera'],
      sourceType: ["camera"],
    maxDuration: props.maxVideoDuration,
    camera: 'back',
    success: (res) => {
      camera: "back",
      success: res => {
      try {
        if (!res.tempFilePath) {
          throw new Error('未获取到视频文件');
            throw new Error("未获取到视频文件");
        }
        
        const file = {
          tempFilePath: res.tempFilePath,
          type: 'video',
            type: "video",
          name: `video_${Date.now()}.mp4`,
          size: res.size || 0,
          duration: res.duration || 0,
          createTime: new Date().getTime(),
          uid: Date.now() + Math.random()
            uid: Date.now() + Math.random(),
        };
        
        handleBeforeUpload(file);
      } catch (error) {
        console.error('处理拍视频结果失败:', error);
          console.error("处理拍视频结果失败:", error);
        uni.showToast({
          title: '处理视频失败',
          icon: 'error'
            title: "处理视频失败",
            icon: "error",
        });
      }
    },
    fail: (err) => {
      console.error('拍视频失败:', err);
      fail: err => {
        console.error("拍视频失败:", err);
      uni.showToast({
        title: '拍视频失败: ' + (err.errMsg || '未知错误'),
        icon: 'error'
          title: "拍视频失败: " + (err.errMsg || "未知错误"),
          icon: "error",
      });
    }
      },
  });
};
// 文件上传处理
const uploadFile = (file) => {
  const uploadFile = file => {
  uploading.value = true;
  uploadProgress.value = 0;
  number.value++; // 增加上传计数
@@ -355,14 +353,14 @@
  // 确保文件路径正确
  const filePath = file.tempFilePath || file.path;
  if (!filePath) {
    handleUploadError('文件路径不存在');
      handleUploadError("文件路径不存在");
    return;
  }
  
  // 确保token存在
  const token = getToken();
  if (!token) {
    handleUploadError('用户未登录');
      handleUploadError("用户未登录");
    return;
  }
  
@@ -370,53 +368,53 @@
  const uploadParams = {
    url: uploadFileUrl.value,
    filePath: filePath,
    name: 'files',
      name: "files",
    formData: {
      type: props.statusType || 0,
      ...(props.data || {})
        ...(props.data || {}),
    },
    header: {
      'Authorization': `Bearer ${token}`
    }
        Authorization: `Bearer ${token}`,
      },
  };
  
  const uploadTask = uni.uploadFile({
    ...uploadParams,
    success: (res) => {
      success: res => {
      try {
        if (res.statusCode === 200) {
          const response = JSON.parse(res.data);
          if (response.code === 200) {
            handleUploadSuccess(response, file);
            uni.showToast({
              title: '上传成功',
              icon: 'success'
                title: "上传成功",
                icon: "success",
            });
            emit("update:modelValue", fileList.value);
          } else {
            handleUploadError(response.msg || '服务器返回错误');
              handleUploadError(response.msg || "服务器返回错误");
          }
        } else {
          handleUploadError(`服务器错误,状态码: ${res.statusCode}`);
        }
      } catch (e) {
        console.error('解析响应失败:', e);
        console.error('原始响应数据:', res.data);
        handleUploadError('响应数据解析失败: ' + e.message);
          console.error("解析响应失败:", e);
          console.error("原始响应数据:", res.data);
          handleUploadError("响应数据解析失败: " + e.message);
      }
    },
    fail: (err) => {
      console.error('上传失败:', err.errMsg || err);
      fail: err => {
        console.error("上传失败:", err.errMsg || err);
      number.value--; // 上传失败时减少计数
      
      let errorMessage = '上传失败';
        let errorMessage = "上传失败";
      if (err.errMsg) {
        if (err.errMsg.includes('statusCode: null')) {
          errorMessage = '网络连接失败,请检查网络设置';
        } else if (err.errMsg.includes('timeout')) {
          errorMessage = '上传超时,请重试';
        } else if (err.errMsg.includes('fail')) {
          errorMessage = '上传失败,请检查网络连接';
          if (err.errMsg.includes("statusCode: null")) {
            errorMessage = "网络连接失败,请检查网络设置";
          } else if (err.errMsg.includes("timeout")) {
            errorMessage = "上传超时,请重试";
          } else if (err.errMsg.includes("fail")) {
            errorMessage = "上传失败,请检查网络连接";
        } else {
          errorMessage = err.errMsg;
        }
@@ -427,86 +425,88 @@
    complete: () => {
      uploading.value = false;
      uploadProgress.value = 0;
    }
      },
  });
  
  // 监听上传进度
  if (uploadTask && uploadTask.onProgressUpdate) {
    uploadTask.onProgressUpdate((res) => {
      uploadTask.onProgressUpdate(res => {
      uploadProgress.value = res.progress;
    });
  }
};
// 获取媒体文件名
const getMediaName = (file) => {
  const getMediaName = file => {
  if (file.bucketFilename) {
    return file.bucketFilename.length > 15 
      ? file.bucketFilename.substring(0, 15) + '...'
        ? file.bucketFilename.substring(0, 15) + "..."
      : file.bucketFilename;
  }
  if (file.name) {
    return file.name.length > 15 
      ? file.name.substring(0, 15) + '...'
        ? file.name.substring(0, 15) + "..."
      : file.name;
  }
  return file.type === 'image' ? '照片' : '视频';
    return file.type === "image" ? "照片" : "视频";
};
// 格式化文件大小
const formatFileSize = (size) => {
  if (!size) return '';
  if (size < 1024) return size + 'B';
  if (size < 1024 * 1024) return (size / 1024).toFixed(1) + 'KB';
  return (size / (1024 * 1024)).toFixed(1) + 'MB';
  const formatFileSize = size => {
    if (!size) return "";
    if (size < 1024) return size + "B";
    if (size < 1024 * 1024) return (size / 1024).toFixed(1) + "KB";
    return (size / (1024 * 1024)).toFixed(1) + "MB";
};
// 格式化时间
const formatTime = (timestamp) => {
  if (!timestamp) return '';
  const formatTime = timestamp => {
    if (!timestamp) return "";
  const date = new Date(timestamp);
  const now = new Date();
  const diff = now - date;
  
  if (diff < 60000) return '刚刚';
  if (diff < 3600000) return Math.floor(diff / 60000) + '分钟前';
  if (diff < 86400000) return Math.floor(diff / 3600000) + '小时前';
    if (diff < 60000) return "刚刚";
    if (diff < 3600000) return Math.floor(diff / 60000) + "分钟前";
    if (diff < 86400000) return Math.floor(diff / 3600000) + "小时前";
  
  return date.toLocaleDateString();
};
// 预览媒体文件
const previewMedia = (file, index) => {
  if (file.type === 'image') {
    if (file.type === "image") {
    // 预览图片
    const urls = fileList.value
      .filter(item => item.type === 'image')
        .filter(item => item.type === "image")
      .map(item => item.url || item.tempFilePath);
    
    uni.previewImage({
      urls: urls,
      current: file.url || file.tempFilePath
        current: file.url || file.tempFilePath,
    });
  } else if (file.type === 'video') {
    } else if (file.type === "video") {
    // 预览视频
    uni.previewVideo({
      sources: [{
        sources: [
          {
        src: file.url || file.tempFilePath,
        type: 'mp4'
      }],
      current: 0
            type: "mp4",
          },
        ],
        current: 0,
    });
  }
};
// 下载文件
const handleDownload = (index) => {
  const handleDownload = index => {
  const file = fileList.value[index];
  const url = file.url || file.downloadUrl;
  
  if (!url) {
    uni.showToast({
      title: '文件链接不存在,无法下载',
      icon: 'none'
        title: "文件链接不存在,无法下载",
        icon: "none",
    });
    return;
  }
@@ -514,43 +514,43 @@
  // 使用uniapp的下载API
  uni.downloadFile({
    url: url,
    success: (res) => {
      success: res => {
      if (res.statusCode === 200) {
        // 保存到相册或文件系统
        uni.saveFile({
          tempFilePath: res.tempFilePath,
          success: (saveRes) => {
            success: saveRes => {
            uni.showToast({
              title: '下载成功',
              icon: 'success'
                title: "下载成功",
                icon: "success",
            });
          },
          fail: (err) => {
            console.error('保存文件失败:', err);
            fail: err => {
              console.error("保存文件失败:", err);
            uni.showToast({
              title: '保存文件失败',
              icon: 'error'
                title: "保存文件失败",
                icon: "error",
            });
          }
            },
        });
      }
    },
    fail: (err) => {
      console.error('下载失败:', err);
      fail: err => {
        console.error("下载失败:", err);
      uni.showToast({
        title: '下载失败',
        icon: 'error'
          title: "下载失败",
          icon: "error",
      });
    }
      },
  });
};
// 检查网络连接
const checkNetworkConnection = () => {
  return new Promise((resolve) => {
    return new Promise(resolve => {
    uni.getNetworkType({
      success: (res) => {
        if (res.networkType === 'none') {
        success: res => {
          if (res.networkType === "none") {
          resolve(false);
        } else {
          resolve(true);
@@ -558,19 +558,19 @@
      },
      fail: () => {
        resolve(false);
      }
        },
    });
  });
};
// 上传前校验
const handleBeforeUpload = async (file) => {
  const handleBeforeUpload = async file => {
  // 检查网络连接
  const hasNetwork = await checkNetworkConnection();
  if (!hasNetwork) {
    uni.showToast({
      title: '网络连接不可用,请检查网络设置',
      icon: 'none'
        title: "网络连接不可用,请检查网络设置",
        icon: "none",
    });
    return false;
  }
@@ -581,44 +581,54 @@
    if (!isLt) {
      uni.showToast({
        title: `文件大小不能超过 ${props.fileSize} MB!`,
        icon: 'none'
          icon: "none",
      });
      return false;
    }
  }
  // 校验视频时长
  if (file.type === 'video' && file.duration && file.duration > props.maxVideoDuration) {
    if (
      file.type === "video" &&
      file.duration &&
      file.duration > props.maxVideoDuration
    ) {
    uni.showToast({
      title: `视频时长不能超过 ${props.maxVideoDuration} 秒!`,
      icon: 'none'
        icon: "none",
    });
    return false;
  }
  // 校验文件类型
  if (props.fileType && Array.isArray(props.fileType) && props.fileType.length > 0) {
    const fileName = file.name || '';
    const fileExtension = fileName ? fileName.split('.').pop().toLowerCase() : '';
    if (
      props.fileType &&
      Array.isArray(props.fileType) &&
      props.fileType.length > 0
    ) {
      const fileName = file.name || "";
      const fileExtension = fileName
        ? fileName.split(".").pop().toLowerCase()
        : "";
    
    // 根据文件类型确定期望的扩展名
    let expectedTypes = [];
    if (file.type === 'image') {
      expectedTypes = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
    } else if (file.type === 'video') {
      expectedTypes = ['mp4', 'mov', 'avi', 'wmv'];
      if (file.type === "image") {
        expectedTypes = ["jpg", "jpeg", "png", "gif", "webp"];
      } else if (file.type === "video") {
        expectedTypes = ["mp4", "mov", "avi", "wmv"];
    }
    
    // 检查文件扩展名是否在允许的类型中
    if (fileExtension && expectedTypes.length > 0) {
      const isAllowed = expectedTypes.some(type =>
        props.fileType.includes(type) && type === fileExtension
        const isAllowed = expectedTypes.some(
          type => props.fileType.includes(type) && type === fileExtension
      );
      
      if (!isAllowed) {
        uni.showToast({
          title: `文件格式不支持,请拍摄 ${expectedTypes.join('/')} 格式的文件`,
          icon: 'none'
            title: `文件格式不支持,请拍摄 ${expectedTypes.join("/")} 格式的文件`,
            icon: "none",
        });
        return false;
      }
@@ -631,28 +641,33 @@
};
// 上传失败处理
const handleUploadError = (message = '上传文件失败', showRetry = true) => {
  const handleUploadError = (message = "上传文件失败", showRetry = true) => {
  if (showRetry) {
    uni.showModal({
      title: '上传失败',
      content: message + ',是否重试?',
      success: (res) => {
        title: "上传失败",
        content: message + ",是否重试?",
        success: res => {
        if (res.confirm) {
          // 用户选择重试,这里可以重新触发上传
        }
      }
        },
    });
  } else {
    uni.showToast({
      title: message,
      icon: 'error'
        icon: "error",
    });
  }
};
// 上传成功回调
const handleUploadSuccess = (res, file) => {
  if (res.code === 200 && res.data && Array.isArray(res.data) && res.data.length > 0) {
    if (
      res.code === 200 &&
      res.data &&
      Array.isArray(res.data) &&
      res.data.length > 0
    ) {
    const uploadedFile = res.data[0];
    // 确保上传的文件数据完整,包含id
    const fileData = {
@@ -662,32 +677,32 @@
      bucketFilename: uploadedFile.bucketFilename || file.name,
      downloadUrl: uploadedFile.downloadUrl || uploadedFile.url,
      size: uploadedFile.size || file.size,
      createTime: uploadedFile.createTime || new Date().getTime()
        createTime: uploadedFile.createTime || new Date().getTime(),
    };
    
    uploadList.value.push(fileData);
    uploadedSuccessfully();
  } else {
    number.value--; // 上传失败时减少计数
    handleUploadError(res.msg || '上传失败');
      handleUploadError(res.msg || "上传失败");
  }
};
// 删除文件
const handleDelete = (index) => {
  const handleDelete = index => {
  uni.showModal({
    title: '确认删除',
    content: '确定要删除这个文件吗?',
    success: (res) => {
      title: "确认删除",
      content: "确定要删除这个文件吗?",
      success: res => {
      if (res.confirm) {
        fileList.value.splice(index, 1);
        emit("update:modelValue", listToString(fileList.value));
        uni.showToast({
          title: '删除成功',
          icon: 'success'
            title: "删除成功",
            icon: "success",
        });
      }
    }
      },
  });
};
@@ -695,7 +710,7 @@
const uploadedSuccessfully = () => {
  if (number.value > 0 && uploadList.value.length === number.value) {
    // 合并已存在的文件和刚上传的文件
    const existingFiles = fileList.value.filter((f) => f.url !== undefined);
      const existingFiles = fileList.value.filter(f => f.url !== undefined);
    fileList.value = [...existingFiles, ...uploadList.value];
    
    // 重置状态
@@ -775,7 +790,8 @@
  height: 100%;
  cursor: pointer;
  
  .preview-image, .preview-video {
    .preview-image,
    .preview-video {
    width: 100%;
    height: 100%;
    object-fit: cover;