zhangwencui
8 天以前 b9b25d756469e576a765e28689f8f267bd5c2d1a
去掉拍视频
已修改4个文件
1435 ■■■■ 文件已修改
src/components/imageUpload/index.vue 1422 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/upkeep/index.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/upkeep/maintain.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/inspectionUpload/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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"
          @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>
        <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>
          {{ uploading ? '上传中...' : '拍照' }}
        </u-button>
        <u-button
        <!-- <u-button
          type="success" 
          @click="takeVideo"
          :loading="uploading"
@@ -22,798 +24,812 @@
        >
          <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"
        :key="file.uid || index"
        class="media-item"
      >
      <view v-for="(file, index) in fileList"
            :key="file.uid || index"
            class="media-item">
        <!-- 预览区域 -->
        <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'"
            :src="file.url || file.tempFilePath"
            class="preview-video"
            :controls="false"
          ></video>
        <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'"
                 :src="file.url || file.tempFilePath"
                 class="preview-video"
                 :controls="false"></video>
          <view class="media-type-icon">
            <u-icon
              :name="file.type === 'image' ? 'photo' : 'video'"
              size="12"
              color="#fff"
            ></u-icon>
            <u-icon :name="file.type === 'image' ? 'photo' : 'video'"
                    size="12"
                    color="#fff"></u-icon>
          </view>
        </view>
        <!-- 操作按钮 -->
        <view class="media-actions" v-if="!disabled">
          <u-button
            type="error"
            size="mini"
            @click="handleDelete(index)"
            :customStyle="{
        <view class="media-actions"
              v-if="!disabled">
          <u-button type="error"
                    size="mini"
                    @click="handleDelete(index)"
                    :customStyle="{
              minWidth: '40px', 
              height: '24px', 
              fontSize: '10px',
              padding: '0 8px'
            }"
          >
            }">
            删除
          </u-button>
        </view>
      </view>
    </view>
    <!-- 上传进度 -->
    <view v-if="uploading" class="upload-progress">
      <u-line-progress
        :percentage="uploadProgress"
        :showText="true"
        activeColor="#409eff"
      ></u-line-progress>
    <view v-if="uploading"
          class="upload-progress">
      <u-line-progress :percentage="uploadProgress"
                       :showText="true"
                       activeColor="#409eff"></u-line-progress>
    </view>
  </view>
</template>
<script setup>
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue';
import { getToken } from "@/utils/auth";
  import { ref, computed, watch, onMounted, onUnmounted, nextTick } from "vue";
  import { getToken } from "@/utils/auth";
// Props 定义
const props = defineProps({
  modelValue: [String, Object, Array],
  action: { type: String, default: "/common/minioUploads" },
  data: { type: Object },
  limit: { type: Number, default: 5 },
  fileSize: { type: Number, default: 10 }, // 默认10MB,适合视频
  fileType: {
    type: Array,
    default: () => ["jpg", "jpeg", "png", "mp4", "mov"]
  },
  isShowTip: { type: Boolean, default: true },
  disabled: { type: Boolean, default: false },
  drag: { type: Boolean, default: false }, // 拍照不需要拖拽
  statusType: { type: Number, default: "" }, // 用于区分不同状态的上传
  maxVideoDuration: { type: Number, default: 30 }, // 最大视频时长(秒)
});
// 事件定义
const emit = defineEmits(['update:modelValue']);
// 响应式数据
const number = ref(0);
const uploadList = ref([]);
const fileList = ref([]);
const uploading = ref(false);
const uploadProgress = ref(0);
// 计算属性
const uploadFileUrl = computed(() => {
  // 获取基础API地址,适配uniapp环境
  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 {
    baseUrl = 'http://192.168.1.147:9036';
  }
  const fullUrl = baseUrl + props.action;
  return fullUrl;
});
const headers = computed(() => {
  const token = getToken();
  return token ? { Authorization: "Bearer " + token } : {};
});
const showTip = computed(() => props.isShowTip && (props.fileType || props.fileSize));
// 初始化和编辑初始化方法
const init = () => {
  fileList.value = [];
  uploadList.value = [];
  number.value = 0;
};
const editInit = (val) => {
  fileList.value = [];
  val.storageBlobDTO.forEach((element) => {
    // 确保文件数据包含所有必要字段,包括id
    const fileData = {
      ...element,
      id: element.id, // 保留服务器返回的id
      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'),
      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()
    };
    fileList.value.push(fileData);
    uploadedSuccessfully();
  // Props 定义
  const props = defineProps({
    modelValue: [String, Object, Array],
    action: { type: String, default: "/common/minioUploads" },
    data: { type: Object },
    limit: { type: Number, default: 5 },
    fileSize: { type: Number, default: 10 }, // 默认10MB,适合视频
    fileType: {
      type: Array,
      default: () => ["jpg", "jpeg", "png", "mp4", "mov"],
    },
    isShowTip: { type: Boolean, default: true },
    disabled: { type: Boolean, default: false },
    drag: { type: Boolean, default: false }, // 拍照不需要拖拽
    statusType: { type: Number, default: "" }, // 用于区分不同状态的上传
    maxVideoDuration: { type: Number, default: 30 }, // 最大视频时长(秒)
  });
};
// 测试服务器连接
const testServerConnection = () => {
  return new Promise((resolve) => {
    uni.request({
      url: uploadFileUrl.value.replace('/common/minioUploads', '/common/test'),
      method: 'GET',
      timeout: 5000,
      success: (res) => {
        resolve(true);
      },
      fail: (err) => {
        resolve(false);
      }
    });
  });
};
  // 事件定义
  const emit = defineEmits(["update:modelValue"]);
// 组件销毁时的清理
onUnmounted(() => {
  // 清理上传状态
  if (uploading.value) {
    uploading.value = false
  }
  // 隐藏可能显示的加载提示
  uni.hideLoading()
  uni.hideToast()
})
  // 响应式数据
  const number = ref(0);
  const uploadList = ref([]);
  const fileList = ref([]);
  const uploading = ref(false);
  const uploadProgress = ref(0);
// 暴露方法
defineExpose({ init, editInit, testServerConnection });
  // 计算属性
  const uploadFileUrl = computed(() => {
    // 获取基础API地址,适配uniapp环境
    let baseUrl = "";
// 监听 modelValue 变化
watch(
  () => props.modelValue,
  (val) => {
    if (val) {
      let temp = 1;
      let list = [];
      if (Array.isArray(val)) {
        list = val;
      } else if (typeof val === "string") {
        list = val.split(",").map(url => ({ url: url.trim() }));
      }
      fileList.value = list.map((item) => {
        if (typeof item === "string") {
          item = { name: item, url: item };
        }
        // 确保每个文件都有必要的属性,包括id
        return {
          ...item,
          id: item.id, // 保留id字段
          uid: item.uid || new Date().getTime() + temp++,
          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()
        };
      });
    // 尝试多种方式获取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 {
      fileList.value = [];
      baseUrl = "http://192.168.1.147:9036";
    }
  },
  { deep: true, immediate: true }
);
// 拍照
const takePhoto = () => {
  if (fileList.value.length >= props.limit) {
    uni.showToast({
      title: `最多只能拍摄${props.limit}个文件`,
      icon: 'none'
    const fullUrl = baseUrl + props.action;
    return fullUrl;
  });
  const headers = computed(() => {
    const token = getToken();
    return token ? { Authorization: "Bearer " + token } : {};
  });
  const showTip = computed(
    () => props.isShowTip && (props.fileType || props.fileSize)
  );
  // 初始化和编辑初始化方法
  const init = () => {
    fileList.value = [];
    uploadList.value = [];
    number.value = 0;
  };
  const editInit = val => {
    fileList.value = [];
    val.storageBlobDTO.forEach(element => {
      // 确保文件数据包含所有必要字段,包括id
      const fileData = {
        ...element,
        id: element.id, // 保留服务器返回的id
        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"),
        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(),
      };
      fileList.value.push(fileData);
      uploadedSuccessfully();
    });
    return;
  }
  uni.chooseImage({
    count: 1,
    sizeType: ['compressed', 'original'],
    sourceType: ['camera'],
    success: (res) => {
      try {
        if (!res.tempFilePaths || res.tempFilePaths.length === 0) {
          throw new Error('未获取到图片文件');
  };
  // 测试服务器连接
  const testServerConnection = () => {
    return new Promise(resolve => {
      uni.request({
        url: uploadFileUrl.value.replace("/common/minioUploads", "/common/test"),
        method: "GET",
        timeout: 5000,
        success: res => {
          resolve(true);
        },
        fail: err => {
          resolve(false);
        },
      });
    });
  };
  // 组件销毁时的清理
  onUnmounted(() => {
    // 清理上传状态
    if (uploading.value) {
      uploading.value = false;
    }
    // 隐藏可能显示的加载提示
    uni.hideLoading();
    uni.hideToast();
  });
  // 暴露方法
  defineExpose({ init, editInit, testServerConnection });
  // 监听 modelValue 变化
  watch(
    () => props.modelValue,
    val => {
      if (val) {
        let temp = 1;
        let list = [];
        if (Array.isArray(val)) {
          list = val;
        } else if (typeof val === "string") {
          list = val.split(",").map(url => ({ url: url.trim() }));
        }
        const tempFilePath = res.tempFilePaths[0];
        const tempFile = res.tempFiles && res.tempFiles[0] ? res.tempFiles[0] : {};
        const file = {
          tempFilePath: tempFilePath,
          type: 'image',
          name: `photo_${Date.now()}.jpg`,
          size: tempFile.size || 0,
          createTime: new Date().getTime(),
          uid: Date.now() + Math.random()
        };
        handleBeforeUpload(file);
      } catch (error) {
        console.error('处理拍照结果失败:', error);
        uni.showToast({
          title: '处理图片失败',
          icon: 'error'
        fileList.value = list.map(item => {
          if (typeof item === "string") {
            item = { name: item, url: item };
          }
          // 确保每个文件都有必要的属性,包括id
          return {
            ...item,
            id: item.id, // 保留id字段
            uid: item.uid || new Date().getTime() + temp++,
            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(),
          };
        });
      } else {
        fileList.value = [];
      }
    },
    fail: (err) => {
      console.error('拍照失败:', err);
      uni.showToast({
        title: '拍照失败: ' + (err.errMsg || '未知错误'),
        icon: 'error'
      });
    }
  });
};
    { deep: true, immediate: true }
  );
// 拍视频
const takeVideo = () => {
  if (fileList.value.length >= props.limit) {
    uni.showToast({
      title: `最多只能拍摄${props.limit}个文件`,
      icon: 'none'
    });
    return;
  }
  uni.chooseVideo({
    sourceType: ['camera'],
    maxDuration: props.maxVideoDuration,
    camera: 'back',
    success: (res) => {
      try {
        if (!res.tempFilePath) {
          throw new Error('未获取到视频文件');
  // 拍照
  const takePhoto = () => {
    if (fileList.value.length >= props.limit) {
      uni.showToast({
        title: `最多只能拍摄${props.limit}个文件`,
        icon: "none",
      });
      return;
    }
    uni.chooseImage({
      count: 1,
      sizeType: ["compressed", "original"],
      sourceType: ["camera"],
      success: res => {
        try {
          if (!res.tempFilePaths || res.tempFilePaths.length === 0) {
            throw new Error("未获取到图片文件");
          }
          const tempFilePath = res.tempFilePaths[0];
          const tempFile =
            res.tempFiles && res.tempFiles[0] ? res.tempFiles[0] : {};
          const file = {
            tempFilePath: tempFilePath,
            type: "image",
            name: `photo_${Date.now()}.jpg`,
            size: tempFile.size || 0,
            createTime: new Date().getTime(),
            uid: Date.now() + Math.random(),
          };
          handleBeforeUpload(file);
        } catch (error) {
          console.error("处理拍照结果失败:", error);
          uni.showToast({
            title: "处理图片失败",
            icon: "error",
          });
        }
        const file = {
          tempFilePath: res.tempFilePath,
          type: 'video',
          name: `video_${Date.now()}.mp4`,
          size: res.size || 0,
          duration: res.duration || 0,
          createTime: new Date().getTime(),
          uid: Date.now() + Math.random()
        };
        handleBeforeUpload(file);
      } catch (error) {
        console.error('处理拍视频结果失败:', error);
      },
      fail: err => {
        console.error("拍照失败:", err);
        uni.showToast({
          title: '处理视频失败',
          icon: 'error'
          title: "拍照失败: " + (err.errMsg || "未知错误"),
          icon: "error",
        });
      }
    },
    fail: (err) => {
      console.error('拍视频失败:', err);
      uni.showToast({
        title: '拍视频失败: ' + (err.errMsg || '未知错误'),
        icon: 'error'
      });
    }
  });
};
      },
    });
  };
// 文件上传处理
const uploadFile = (file) => {
  uploading.value = true;
  uploadProgress.value = 0;
  number.value++; // 增加上传计数
  // 确保文件路径正确
  const filePath = file.tempFilePath || file.path;
  if (!filePath) {
    handleUploadError('文件路径不存在');
    return;
  }
  // 确保token存在
  const token = getToken();
  if (!token) {
    handleUploadError('用户未登录');
    return;
  }
  // 准备上传参数
  const uploadParams = {
    url: uploadFileUrl.value,
    filePath: filePath,
    name: 'files',
    formData: {
      type: props.statusType || 0,
      ...(props.data || {})
    },
    header: {
      'Authorization': `Bearer ${token}`
  // 拍视频
  const takeVideo = () => {
    if (fileList.value.length >= props.limit) {
      uni.showToast({
        title: `最多只能拍摄${props.limit}个文件`,
        icon: "none",
      });
      return;
    }
    uni.chooseVideo({
      sourceType: ["camera"],
      maxDuration: props.maxVideoDuration,
      camera: "back",
      success: res => {
        try {
          if (!res.tempFilePath) {
            throw new Error("未获取到视频文件");
          }
          const file = {
            tempFilePath: res.tempFilePath,
            type: "video",
            name: `video_${Date.now()}.mp4`,
            size: res.size || 0,
            duration: res.duration || 0,
            createTime: new Date().getTime(),
            uid: Date.now() + Math.random(),
          };
          handleBeforeUpload(file);
        } catch (error) {
          console.error("处理拍视频结果失败:", error);
          uni.showToast({
            title: "处理视频失败",
            icon: "error",
          });
        }
      },
      fail: err => {
        console.error("拍视频失败:", err);
        uni.showToast({
          title: "拍视频失败: " + (err.errMsg || "未知错误"),
          icon: "error",
        });
      },
    });
  };
  // 文件上传处理
  const uploadFile = file => {
    uploading.value = true;
    uploadProgress.value = 0;
    number.value++; // 增加上传计数
    // 确保文件路径正确
    const filePath = file.tempFilePath || file.path;
    if (!filePath) {
      handleUploadError("文件路径不存在");
      return;
    }
    // 确保token存在
    const token = getToken();
    if (!token) {
      handleUploadError("用户未登录");
      return;
    }
    // 准备上传参数
    const uploadParams = {
      url: uploadFileUrl.value,
      filePath: filePath,
      name: "files",
      formData: {
        type: props.statusType || 0,
        ...(props.data || {}),
      },
      header: {
        Authorization: `Bearer ${token}`,
      },
    };
    const uploadTask = uni.uploadFile({
      ...uploadParams,
      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",
              });
              emit("update:modelValue", fileList.value);
            } else {
              handleUploadError(response.msg || "服务器返回错误");
            }
          } else {
            handleUploadError(`服务器错误,状态码: ${res.statusCode}`);
          }
        } catch (e) {
          console.error("解析响应失败:", e);
          console.error("原始响应数据:", res.data);
          handleUploadError("响应数据解析失败: " + e.message);
        }
      },
      fail: err => {
        console.error("上传失败:", err.errMsg || err);
        number.value--; // 上传失败时减少计数
        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 = "上传失败,请检查网络连接";
          } else {
            errorMessage = err.errMsg;
          }
        }
        handleUploadError(errorMessage);
      },
      complete: () => {
        uploading.value = false;
        uploadProgress.value = 0;
      },
    });
    // 监听上传进度
    if (uploadTask && uploadTask.onProgressUpdate) {
      uploadTask.onProgressUpdate(res => {
        uploadProgress.value = res.progress;
      });
    }
  };
  const uploadTask = uni.uploadFile({
    ...uploadParams,
    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'
            });
            emit("update:modelValue", fileList.value);
          } else {
            handleUploadError(response.msg || '服务器返回错误');
          }
        } else {
          handleUploadError(`服务器错误,状态码: ${res.statusCode}`);
        }
      } catch (e) {
        console.error('解析响应失败:', e);
        console.error('原始响应数据:', res.data);
        handleUploadError('响应数据解析失败: ' + e.message);
      }
    },
    fail: (err) => {
      console.error('上传失败:', err.errMsg || err);
      number.value--; // 上传失败时减少计数
      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 = '上传失败,请检查网络连接';
        } else {
          errorMessage = err.errMsg;
        }
      }
      handleUploadError(errorMessage);
    },
    complete: () => {
      uploading.value = false;
      uploadProgress.value = 0;
  // 获取媒体文件名
  const getMediaName = file => {
    if (file.bucketFilename) {
      return file.bucketFilename.length > 15
        ? file.bucketFilename.substring(0, 15) + "..."
        : file.bucketFilename;
    }
  });
  // 监听上传进度
  if (uploadTask && uploadTask.onProgressUpdate) {
    uploadTask.onProgressUpdate((res) => {
      uploadProgress.value = res.progress;
    });
  }
};
// 获取媒体文件名
const getMediaName = (file) => {
  if (file.bucketFilename) {
    return file.bucketFilename.length > 15
      ? file.bucketFilename.substring(0, 15) + '...'
      : file.bucketFilename;
  }
  if (file.name) {
    return file.name.length > 15
      ? file.name.substring(0, 15) + '...'
      : file.name;
  }
  return file.type === 'image' ? '照片' : '视频';
};
    if (file.name) {
      return file.name.length > 15
        ? file.name.substring(0, 15) + "..."
        : file.name;
    }
    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 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) + '小时前';
  return date.toLocaleDateString();
};
  // 格式化时间
  const formatTime = timestamp => {
    if (!timestamp) return "";
    const date = new Date(timestamp);
    const now = new Date();
    const diff = now - date;
// 预览媒体文件
const previewMedia = (file, index) => {
  if (file.type === 'image') {
    // 预览图片
    const urls = fileList.value
      .filter(item => item.type === 'image')
      .map(item => item.url || item.tempFilePath);
    uni.previewImage({
      urls: urls,
      current: file.url || file.tempFilePath
    });
  } else if (file.type === 'video') {
    // 预览视频
    uni.previewVideo({
      sources: [{
        src: file.url || file.tempFilePath,
        type: 'mp4'
      }],
      current: 0
    });
  }
};
    if (diff < 60000) return "刚刚";
    if (diff < 3600000) return Math.floor(diff / 60000) + "分钟前";
    if (diff < 86400000) return Math.floor(diff / 3600000) + "小时前";
// 下载文件
const handleDownload = (index) => {
  const file = fileList.value[index];
  const url = file.url || file.downloadUrl;
  if (!url) {
    uni.showToast({
      title: '文件链接不存在,无法下载',
      icon: 'none'
    });
    return;
  }
  // 使用uniapp的下载API
  uni.downloadFile({
    url: url,
    success: (res) => {
      if (res.statusCode === 200) {
        // 保存到相册或文件系统
        uni.saveFile({
          tempFilePath: res.tempFilePath,
          success: (saveRes) => {
            uni.showToast({
              title: '下载成功',
              icon: 'success'
            });
    return date.toLocaleDateString();
  };
  // 预览媒体文件
  const previewMedia = (file, index) => {
    if (file.type === "image") {
      // 预览图片
      const urls = fileList.value
        .filter(item => item.type === "image")
        .map(item => item.url || item.tempFilePath);
      uni.previewImage({
        urls: urls,
        current: file.url || file.tempFilePath,
      });
    } else if (file.type === "video") {
      // 预览视频
      uni.previewVideo({
        sources: [
          {
            src: file.url || file.tempFilePath,
            type: "mp4",
          },
          fail: (err) => {
            console.error('保存文件失败:', err);
            uni.showToast({
              title: '保存文件失败',
              icon: 'error'
            });
          }
        });
      }
    },
    fail: (err) => {
      console.error('下载失败:', err);
      uni.showToast({
        title: '下载失败',
        icon: 'error'
        ],
        current: 0,
      });
    }
  });
};
  };
// 检查网络连接
const checkNetworkConnection = () => {
  return new Promise((resolve) => {
    uni.getNetworkType({
      success: (res) => {
        if (res.networkType === 'none') {
          resolve(false);
        } else {
          resolve(true);
  // 下载文件
  const handleDownload = index => {
    const file = fileList.value[index];
    const url = file.url || file.downloadUrl;
    if (!url) {
      uni.showToast({
        title: "文件链接不存在,无法下载",
        icon: "none",
      });
      return;
    }
    // 使用uniapp的下载API
    uni.downloadFile({
      url: url,
      success: res => {
        if (res.statusCode === 200) {
          // 保存到相册或文件系统
          uni.saveFile({
            tempFilePath: res.tempFilePath,
            success: saveRes => {
              uni.showToast({
                title: "下载成功",
                icon: "success",
              });
            },
            fail: err => {
              console.error("保存文件失败:", err);
              uni.showToast({
                title: "保存文件失败",
                icon: "error",
              });
            },
          });
        }
      },
      fail: () => {
        resolve(false);
      }
      fail: err => {
        console.error("下载失败:", err);
        uni.showToast({
          title: "下载失败",
          icon: "error",
        });
      },
    });
  });
};
  };
// 上传前校验
const handleBeforeUpload = async (file) => {
  // 检查网络连接
  const hasNetwork = await checkNetworkConnection();
  if (!hasNetwork) {
    uni.showToast({
      title: '网络连接不可用,请检查网络设置',
      icon: 'none'
  const checkNetworkConnection = () => {
    return new Promise(resolve => {
      uni.getNetworkType({
        success: res => {
          if (res.networkType === "none") {
            resolve(false);
          } else {
            resolve(true);
          }
        },
        fail: () => {
          resolve(false);
        },
      });
    });
    return false;
  }
  };
  // 校验文件大小
  if (props.fileSize && file.size) {
    const isLt = file.size / 1024 / 1024 < props.fileSize;
    if (!isLt) {
  // 上传前校验
  const handleBeforeUpload = async file => {
    // 检查网络连接
    const hasNetwork = await checkNetworkConnection();
    if (!hasNetwork) {
      uni.showToast({
        title: `文件大小不能超过 ${props.fileSize} MB!`,
        icon: 'none'
        title: "网络连接不可用,请检查网络设置",
        icon: "none",
      });
      return false;
    }
  }
  // 校验视频时长
  if (file.type === 'video' && file.duration && file.duration > props.maxVideoDuration) {
    uni.showToast({
      title: `视频时长不能超过 ${props.maxVideoDuration} 秒!`,
      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() : '';
    // 根据文件类型确定期望的扩展名
    let expectedTypes = [];
    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
      );
      if (!isAllowed) {
    // 校验文件大小
    if (props.fileSize && file.size) {
      const isLt = file.size / 1024 / 1024 < props.fileSize;
      if (!isLt) {
        uni.showToast({
          title: `文件格式不支持,请拍摄 ${expectedTypes.join('/')} 格式的文件`,
          icon: 'none'
          title: `文件大小不能超过 ${props.fileSize} MB!`,
          icon: "none",
        });
        return false;
      }
    }
  }
  // 校验通过,开始上传
  uploadFile(file);
  return true;
};
    // 校验视频时长
    if (
      file.type === "video" &&
      file.duration &&
      file.duration > props.maxVideoDuration
    ) {
      uni.showToast({
        title: `视频时长不能超过 ${props.maxVideoDuration} 秒!`,
        icon: "none",
      });
      return false;
    }
// 上传失败处理
const handleUploadError = (message = '上传文件失败', showRetry = true) => {
  if (showRetry) {
    uni.showModal({
      title: '上传失败',
      content: message + ',是否重试?',
      success: (res) => {
        if (res.confirm) {
          // 用户选择重试,这里可以重新触发上传
    // 校验文件类型
    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 (fileExtension && expectedTypes.length > 0) {
        const isAllowed = expectedTypes.some(
          type => props.fileType.includes(type) && type === fileExtension
        );
        if (!isAllowed) {
          uni.showToast({
            title: `文件格式不支持,请拍摄 ${expectedTypes.join("/")} 格式的文件`,
            icon: "none",
          });
          return false;
        }
      }
    });
  } else {
    uni.showToast({
      title: message,
      icon: 'error'
    });
  }
};
// 上传成功回调
const handleUploadSuccess = (res, file) => {
  if (res.code === 200 && res.data && Array.isArray(res.data) && res.data.length > 0) {
    const uploadedFile = res.data[0];
    // 确保上传的文件数据完整,包含id
    const fileData = {
      ...file,
      id: uploadedFile.id, // 添加服务器返回的id
      url: uploadedFile.url || uploadedFile.downloadUrl,
      bucketFilename: uploadedFile.bucketFilename || file.name,
      downloadUrl: uploadedFile.downloadUrl || uploadedFile.url,
      size: uploadedFile.size || file.size,
      createTime: uploadedFile.createTime || new Date().getTime()
    };
    uploadList.value.push(fileData);
    uploadedSuccessfully();
  } else {
    number.value--; // 上传失败时减少计数
    handleUploadError(res.msg || '上传失败');
  }
};
// 删除文件
const handleDelete = (index) => {
  uni.showModal({
    title: '确认删除',
    content: '确定要删除这个文件吗?',
    success: (res) => {
      if (res.confirm) {
        fileList.value.splice(index, 1);
        emit("update:modelValue", listToString(fileList.value));
        uni.showToast({
          title: '删除成功',
          icon: 'success'
        });
      }
    }
  });
};
// 上传结束处理
const uploadedSuccessfully = () => {
  if (number.value > 0 && uploadList.value.length === number.value) {
    // 合并已存在的文件和刚上传的文件
    const existingFiles = fileList.value.filter((f) => f.url !== undefined);
    fileList.value = [...existingFiles, ...uploadList.value];
    // 重置状态
    uploadList.value = [];
    number.value = 0;
    // 触发更新事件,传递完整的文件列表
    emit("update:modelValue", fileList.value);
  }
};
    // 校验通过,开始上传
    uploadFile(file);
    return true;
  };
const listToString = (list, separator = ",") => {
  const strs = list
    .filter(item => item.url)
    .map(item => item.url)
    .join(separator);
  return strs;
};
  // 上传失败处理
  const handleUploadError = (message = "上传文件失败", showRetry = true) => {
    if (showRetry) {
      uni.showModal({
        title: "上传失败",
        content: message + ",是否重试?",
        success: res => {
          if (res.confirm) {
            // 用户选择重试,这里可以重新触发上传
          }
        },
      });
    } else {
      uni.showToast({
        title: message,
        icon: "error",
      });
    }
  };
  // 上传成功回调
  const handleUploadSuccess = (res, file) => {
    if (
      res.code === 200 &&
      res.data &&
      Array.isArray(res.data) &&
      res.data.length > 0
    ) {
      const uploadedFile = res.data[0];
      // 确保上传的文件数据完整,包含id
      const fileData = {
        ...file,
        id: uploadedFile.id, // 添加服务器返回的id
        url: uploadedFile.url || uploadedFile.downloadUrl,
        bucketFilename: uploadedFile.bucketFilename || file.name,
        downloadUrl: uploadedFile.downloadUrl || uploadedFile.url,
        size: uploadedFile.size || file.size,
        createTime: uploadedFile.createTime || new Date().getTime(),
      };
      uploadList.value.push(fileData);
      uploadedSuccessfully();
    } else {
      number.value--; // 上传失败时减少计数
      handleUploadError(res.msg || "上传失败");
    }
  };
  // 删除文件
  const handleDelete = index => {
    uni.showModal({
      title: "确认删除",
      content: "确定要删除这个文件吗?",
      success: res => {
        if (res.confirm) {
          fileList.value.splice(index, 1);
          emit("update:modelValue", listToString(fileList.value));
          uni.showToast({
            title: "删除成功",
            icon: "success",
          });
        }
      },
    });
  };
  // 上传结束处理
  const uploadedSuccessfully = () => {
    if (number.value > 0 && uploadList.value.length === number.value) {
      // 合并已存在的文件和刚上传的文件
      const existingFiles = fileList.value.filter(f => f.url !== undefined);
      fileList.value = [...existingFiles, ...uploadList.value];
      // 重置状态
      uploadList.value = [];
      number.value = 0;
      // 触发更新事件,传递完整的文件列表
      emit("update:modelValue", fileList.value);
    }
  };
  const listToString = (list, separator = ",") => {
    const strs = list
      .filter(item => item.url)
      .map(item => item.url)
      .join(separator);
    return strs;
  };
</script>
<style scoped lang="scss">
.camera-upload {
  width: 100%;
}
  .camera-upload {
    width: 100%;
  }
.camera-buttons {
  margin-bottom: 15px;
  .button-row {
  .camera-buttons {
    margin-bottom: 15px;
    .button-row {
      display: flex;
      gap: 10px;
    }
  }
  .upload-tip {
    font-size: 12px;
    color: #909399;
    margin-bottom: 15px;
    text-align: center;
    line-height: 1.5;
    .tip-text {
      margin: 0 2px;
    }
    .tip-highlight {
      color: #f56c6c;
      font-weight: bold;
    }
  }
  .media-list {
    margin-top: 10px;
    display: flex;
    gap: 10px;
    flex-wrap: wrap;
    gap: 8px;
  }
}
.upload-tip {
  font-size: 12px;
  color: #909399;
  margin-bottom: 15px;
  text-align: center;
  line-height: 1.5;
  .tip-text {
    margin: 0 2px;
  .media-item {
    position: relative;
    width: 80px;
    height: 80px;
    border-radius: 8px;
    overflow: hidden;
    background-color: #f5f5f5;
    border: 2px solid #e9ecef;
    transition: all 0.3s ease;
    &:hover {
      border-color: #409eff;
      transform: scale(1.02);
    }
  }
  .tip-highlight {
    color: #f56c6c;
    font-weight: bold;
  }
}
.media-list {
  margin-top: 10px;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.media-item {
  position: relative;
  width: 80px;
  height: 80px;
  border-radius: 8px;
  overflow: hidden;
  background-color: #f5f5f5;
  border: 2px solid #e9ecef;
  transition: all 0.3s ease;
  &:hover {
    border-color: #409eff;
    transform: scale(1.02);
  }
}
.media-preview {
  position: relative;
  width: 100%;
  height: 100%;
  cursor: pointer;
  .preview-image, .preview-video {
  .media-preview {
    position: relative;
    width: 100%;
    height: 100%;
    object-fit: cover;
    cursor: pointer;
    .preview-image,
    .preview-video {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }
    .media-type-icon {
      position: absolute;
      top: 4px;
      right: 4px;
      width: 20px;
      height: 20px;
      background-color: rgba(0, 0, 0, 0.6);
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
    }
  }
  .media-type-icon {
  .media-actions {
    position: absolute;
    top: 4px;
    right: 4px;
    width: 20px;
    height: 20px;
    background-color: rgba(0, 0, 0, 0.6);
    border-radius: 50%;
    bottom: 0;
    left: 0;
    right: 0;
    background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
    padding: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
  }
}
    opacity: 0;
    transition: opacity 0.3s ease;
.media-actions {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
  padding: 4px;
  display: flex;
  justify-content: center;
  opacity: 0;
  transition: opacity 0.3s ease;
  .media-item:hover & {
    opacity: 1;
    .media-item:hover & {
      opacity: 1;
    }
  }
}
.upload-progress {
  margin-top: 15px;
  padding: 0 10px;
}
  .upload-progress {
    margin-top: 15px;
    padding: 0 10px;
  }
</style>
src/pages/equipmentManagement/upkeep/index.vue
@@ -90,7 +90,6 @@
            <u-button type="warning"
                      size="small"
                      class="action-btn"
                      :disabled="item.status === 1"
                      @click.stop="addMaintain(item.id, item)">
              保养
            </u-button>
src/pages/equipmentManagement/upkeep/maintain.vue
@@ -96,7 +96,7 @@
                      style="margin-right: 5px;"></u-icon>
              {{ uploading ? '上传中...' : '拍照' }}
            </u-button>
            <u-button type="success"
            <!-- <u-button type="success"
                      @click="chooseMedia('video')"
                      :loading="uploading"
                      :disabled="uploadFiles.length >= uploadConfig.limit"
@@ -107,7 +107,7 @@
                         color="#fff"
                         style="margin-right: 5px;"></uni-icons>
              {{ uploading ? '上传中...' : '拍视频' }}
            </u-button>
            </u-button> -->
          </view>
          <!-- 上传进度 -->
          <view v-if="uploading"
@@ -153,7 +153,7 @@
          </view>
          <view v-if="uploadFiles.length === 0"
                class="empty-state">
            <text>请选择要上传的保养图片或视频</text>
            <text>请选择要上传的保养图片</text>
          </view>
        </view>
      </u-form-item>
src/pages/inspectionUpload/index.vue
@@ -153,7 +153,7 @@
                          style="margin-right: 5px;"></u-icon>
                  {{ uploading ? '上传中...' : '拍照' }}
                </u-button>
                <u-button type="success"
                <!-- <u-button type="success"
                          @click="chooseMedia('video')"
                          :loading="uploading"
                          :disabled="getCurrentFiles().length >= uploadConfig.limit"
@@ -164,7 +164,7 @@
                             color="#fff"
                             style="margin-right: 5px;"></uni-icons>
                  {{ uploading ? '上传中...' : '拍视频' }}
                </u-button>
                </u-button> -->
              </view>
              <!-- 上传进度 -->
              <view v-if="uploading"
@@ -210,7 +210,7 @@
              </view>
              <view v-if="getCurrentFiles().length === 0"
                    class="empty-state">
                <text>请选择要上传的{{ getUploadTypeText() }}图片或视频</text>
                <text>请选择要上传的{{ getUploadTypeText() }}图片</text>
              </view>
              <!-- 统计信息 -->
              <!-- <view class="upload-summary">