gaoluyang
10 天以前 ae7277fa376166ef2bd66253dd16337a35229d10
阳光彩印
1.web端设备巡检要求也可以上传图片
已添加1个文件
已修改3个文件
613 ■■■■■ 文件已修改
multiple/config.json 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/inspectionManagement/components/uploadFiles.vue 580 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/inspectionManagement/index.vue 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
multiple/config.json
@@ -20,7 +20,7 @@
  "YGYS": {
    "env": {
      "VITE_APP_TITLE": "阳光印刷信息管理",
      "VITE_BASE_API": "http://1.15.17.182:9022",
      "VITE_BASE_API": "http://1.15.17.182:9023",
      "VITE_JAVA_API": "http://1.15.17.182:9022"
    },
    "screen": "screen/login-background.png",
src/views/equipmentManagement/inspectionManagement/components/uploadFiles.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,580 @@
<template>
  <div>
    <el-dialog
      v-model="showUploadDialog"
      title="上传巡检记录"
      width="560px"
      :before-close="closeUploadDialog"
    >
      <el-tabs v-model="currentUploadType">
        <el-tab-pane label="生产前" name="before" />
        <el-tab-pane label="生产中" name="after" />
        <el-tab-pane label="生产后" name="issue" />
      </el-tabs>
      <div class="exception-section">
        <div class="section-title">是否存在异常?</div>
        <el-radio-group v-model="hasException">
          <el-radio :value="false">正常</el-radio>
          <el-radio :value="true">存在异常</el-radio>
        </el-radio-group>
      </div>
      <div class="upload-buttons">
        <el-upload
          ref="uploadRef"
          v-model:file-list="uploadFileList"
          :action="uploadUrl"
          :headers="uploadHeaders"
          :show-file-list="false"
          :accept="uploadAccept"
          :multiple="false"
          :before-upload="handleBeforeUpload"
          :on-success="handleUploadSuccess"
          :on-error="handleUploadError"
          :on-progress="handleUploadProgress"
          :disabled="uploading || getCurrentFiles().length >= uploadConfig.limit"
        >
          <el-button
            type="primary"
            :loading="uploading"
            :disabled="getCurrentFiles().length >= uploadConfig.limit"
          >
            é€‰æ‹©å›¾ç‰‡/视频
          </el-button>
        </el-upload>
      </div>
      <el-progress
        v-if="uploading"
        :percentage="uploadProgress"
        style="margin: 12px 0"
      />
      <div v-if="getCurrentFiles().length" class="file-list">
        <div
          v-for="(file, index) in getCurrentFiles()"
          :key="file.uid || file.id || index"
          class="file-item"
        >
          <div class="file-preview-container">
            <img
              v-if="isImageFile(file)"
              :src="file.url || file.downloadUrl"
              class="file-preview"
              @click="previewAttachment(file)"
            />
            <div
              v-else
              class="video-preview"
              @click="previewAttachment(file)"
            >
              è§†é¢‘
            </div>
            <button class="delete-btn" @click="removeFile(index)">x</button>
          </div>
          <div class="file-name">
            {{ file.bucketFilename || file.name || "附件" }}
          </div>
          <div class="file-size">
            {{ formatFileSize(file.size || file.byteSize) }}
          </div>
        </div>
      </div>
      <el-empty
        v-else
        :description="`请选择要上传的${getUploadTypeText()}图片或视频`"
      />
      <div class="upload-summary">
        ç”Ÿäº§å‰ï¼š{{ beforeModelValue.length }} ä¸ª |
        ç”Ÿäº§ä¸­ï¼š{{ afterModelValue.length }} ä¸ª |
        ç”Ÿäº§åŽï¼š{{ issueModelValue.length }} ä¸ª
      </div>
      <template #footer>
                <el-button type="primary" @click="submitUpload">提交</el-button>
        <el-button @click="closeUploadDialog">取消</el-button>
      </template>
    </el-dialog>
    <el-dialog
      v-model="showVideoDialog"
      :title="currentVideoFile?.originalFilename || '视频预览'"
      width="720px"
    >
      <video
        v-if="currentVideoFile"
        :src="currentVideoFile.url || currentVideoFile.downloadUrl"
        class="video-player"
        controls
        autoplay
      />
    </el-dialog>
  </div>
</template>
<script setup>
import { computed, ref } from "vue";
import { ElLoading, ElMessage, ElMessageBox } from "element-plus";
import { getToken } from "@/utils/auth";
import { uploadInspectionTask } from "@/api/inspectionManagement/index.js";
const emit = defineEmits(["closeDia", "success"]);
const showUploadDialog = ref(false);
const uploading = ref(false);
const uploadProgress = ref(0);
const uploadRef = ref(null);
const uploadFileList = ref([]);
const beforeModelValue = ref([]);
const afterModelValue = ref([]);
const issueModelValue = ref([]);
const currentUploadType = ref("before");
const hasException = ref(null);
const currentTask = ref(null);
const showVideoDialog = ref(false);
const currentVideoFile = ref(null);
const uploadConfig = {
  action: "/file/upload",
  limit: 10,
  fileSize: 50,
  fileType: ["jpg", "jpeg", "png", "gif", "webp", "mp4", "mov", "avi", "wmv"],
};
const uploadUrl = `${import.meta.env.VITE_APP_BASE_API}${uploadConfig.action}`;
const uploadHeaders = {
  Authorization: `Bearer ${getToken()}`,
};
const uploadAccept = computed(() =>
  uploadConfig.fileType.map(item => `.${item}`).join(",")
);
const filePreviewBase = __BASE_API__;
const cloneData = value => JSON.parse(JSON.stringify(value || {}));
const normalizeFileUrl = rawUrl => {
  if (!rawUrl || typeof rawUrl !== "string") return "";
  let fileUrl = rawUrl.trim();
  if (!fileUrl) return "";
  if (/^https?:\/\//i.test(fileUrl)) return fileUrl;
  if (fileUrl.indexOf("\\") > -1) {
    const uploadsIndex = fileUrl.toLowerCase().indexOf("uploads");
    if (uploadsIndex > -1) {
      const relativePath = fileUrl.substring(uploadsIndex).replace(/\\/g, "/");
      fileUrl = `/${relativePath}`;
    } else {
      const parts = fileUrl.split("\\");
      const fileName = parts[parts.length - 1];
      fileUrl = `/uploads/${fileName}`;
    }
  }
  if (!fileUrl.startsWith("http")) {
    if (!fileUrl.startsWith("/")) {
      fileUrl = `/${fileUrl}`;
    }
    fileUrl = `${filePreviewBase}${fileUrl}`;
  }
  return fileUrl;
};
const mapExistingFile = (file, type) => ({
  ...file,
  id: file?.id,
  tempId: file?.tempId ?? file?.tempFileId ?? file?.id,
  tempFileId: file?.tempFileId ?? file?.id,
  url: normalizeFileUrl(file?.url || file?.downloadUrl || file?.fileUrl || ""),
  downloadUrl: normalizeFileUrl(
    file?.downloadUrl || file?.url || file?.fileUrl || ""
  ),
  bucketFilename:
    file?.bucketFilename || file?.originalFilename || file?.fileName || file?.name,
  originalFilename:
    file?.originalFilename || file?.bucketFilename || file?.fileName || file?.name,
  size: file?.size || file?.byteSize,
  byteSize: file?.byteSize || file?.size,
  contentType: file?.contentType || "",
  type,
  uid: file?.uid || `${type}-${file?.id || file?.url || Math.random()}`,
});
const resetDialogState = () => {
  beforeModelValue.value = [];
  afterModelValue.value = [];
  issueModelValue.value = [];
  currentUploadType.value = "before";
  hasException.value = null;
  currentTask.value = null;
  uploadProgress.value = 0;
  uploading.value = false;
  uploadFileList.value = [];
  uploadRef.value?.clearFiles?.();
};
const openDialog = task => {
  const rawTask = cloneData(task?.__raw || task);
  currentTask.value = {
    ...rawTask,
    taskId: rawTask.taskId || rawTask.id,
    storageBlobDTO: [],
  };
  beforeModelValue.value = Array.isArray(rawTask.commonFileListBefore)
    ? rawTask.commonFileListBefore.map(file => mapExistingFile(file, 10))
    : [];
  afterModelValue.value = Array.isArray(rawTask.commonFileListAfter)
    ? rawTask.commonFileListAfter.map(file => mapExistingFile(file, 11))
    : [];
  issueModelValue.value = Array.isArray(rawTask.commonFileList)
    ? rawTask.commonFileList.map(file => mapExistingFile(file, 12))
    : [];
  currentUploadType.value = "before";
  hasException.value =
    typeof rawTask.hasException === "boolean" ? rawTask.hasException : null;
  uploadFileList.value = [];
  showUploadDialog.value = true;
};
const closeUploadDialog = () => {
  showUploadDialog.value = false;
  resetDialogState();
  emit("closeDia");
};
const getCurrentFiles = () => {
  if (currentUploadType.value === "before") return beforeModelValue.value;
  if (currentUploadType.value === "after") return afterModelValue.value;
  return issueModelValue.value;
};
const getUploadTypeText = () => {
  if (currentUploadType.value === "before") return "生产前";
  if (currentUploadType.value === "after") return "生产中";
  return "生产后";
};
const getTabType = () => {
  if (currentUploadType.value === "before") return 10;
  if (currentUploadType.value === "after") return 11;
  return 12;
};
const handleBeforeUpload = file => {
  if (getCurrentFiles().length >= uploadConfig.limit) {
    ElMessage.warning(`最多只能选择${uploadConfig.limit}个文件`);
    return false;
  }
  const ext = file.name.split(".").pop()?.toLowerCase();
  if (!uploadConfig.fileType.includes(ext)) {
    ElMessage.warning(`文件格式不支持,请上传 ${uploadConfig.fileType.join("/")} æ ¼å¼`);
    return false;
  }
  const maxSize = uploadConfig.fileSize * 1024 * 1024;
  if (file.size > maxSize) {
    ElMessage.warning(`文件大小不能超过 ${uploadConfig.fileSize}MB`);
    return false;
  }
  uploading.value = true;
  uploadProgress.value = 0;
  return true;
};
const handleUploadProgress = event => {
  if (event?.percent) {
    uploadProgress.value = Math.round(event.percent);
  }
};
const handleUploadSuccess = (response, file) => {
  uploading.value = false;
  uploadProgress.value = 0;
  uploadFileList.value = [];
  uploadRef.value?.clearFiles?.();
  const uploadedFile = response?.data;
  if (response?.code !== 200 || !uploadedFile) {
    ElMessage.error(response?.msg || "上传响应数据格式错误");
    return;
  }
  const type = getTabType();
  const objectUrl = file?.raw ? URL.createObjectURL(file.raw) : "";
  const fileData = {
    id: uploadedFile.id,
    tempId: uploadedFile.tempId ?? uploadedFile.tempFileId ?? uploadedFile.id,
    tempFileId: uploadedFile.tempFileId ?? uploadedFile.id,
    url: normalizeFileUrl(uploadedFile.url || uploadedFile.downloadUrl || objectUrl),
    downloadUrl: normalizeFileUrl(
      uploadedFile.downloadUrl || uploadedFile.url || objectUrl
    ),
    bucketFilename:
      uploadedFile.bucketFilename || uploadedFile.originalFilename || file.name,
    originalFilename:
      uploadedFile.originalFilename || uploadedFile.bucketFilename || file.name,
    size: uploadedFile.size || uploadedFile.byteSize || file.size,
    byteSize: uploadedFile.byteSize || uploadedFile.size || file.size,
    createTime: uploadedFile.createTime || Date.now(),
    contentType: uploadedFile.contentType || file.raw?.type || "",
    type,
    uid: `${Date.now()}-${Math.random()}`,
  };
  if (currentUploadType.value === "before") {
    beforeModelValue.value.push(fileData);
  } else if (currentUploadType.value === "after") {
    afterModelValue.value.push(fileData);
  } else {
    issueModelValue.value.push(fileData);
  }
  ElMessage.success("上传成功");
};
const handleUploadError = error => {
  uploading.value = false;
  uploadProgress.value = 0;
  uploadFileList.value = [];
  uploadRef.value?.clearFiles?.();
  ElMessage.error(error?.message || "上传失败");
};
const removeFile = async index => {
  try {
    await ElMessageBox.confirm("确定要删除这个文件吗?", "确认删除", {
      type: "warning",
    });
    if (currentUploadType.value === "before") {
      beforeModelValue.value.splice(index, 1);
    } else if (currentUploadType.value === "after") {
      afterModelValue.value.splice(index, 1);
    } else {
      issueModelValue.value.splice(index, 1);
    }
    ElMessage.success("删除成功");
  } catch {}
};
const buildSubmitFiles = () => {
  const list = [
    ...beforeModelValue.value,
    ...afterModelValue.value,
    ...issueModelValue.value,
  ];
  return list.map(item => ({
    ...item,
    url: item?.downloadUrl || item?.url || "",
  }));
};
const submitUpload = async () => {
  if (hasException.value === null) {
    ElMessage.warning("请选择是否存在异常");
    return;
  }
  const files = buildSubmitFiles();
  if (!files.length) {
    ElMessage.warning("请先上传文件");
    return;
  }
  const loadingInstance = ElLoading.service({
    text: "提交中...",
    background: "rgba(0, 0, 0, 0.3)",
  });
  try {
    const tempFileIds = files
      .map(item => item?.tempId ?? item?.tempFileId ?? item?.id)
      .filter(Boolean);
    const payload = {
      ...currentTask.value,
      hasException: hasException.value,
      storageBlobDTO: files,
      tempFileIds,
      commonFileListBefore: beforeModelValue.value,
      commonFileListAfter: afterModelValue.value,
      commonFileList: issueModelValue.value,
    };
    const result = await uploadInspectionTask(payload);
    if (result?.code === 200 || result?.success) {
      ElMessage.success("提交成功");
      showUploadDialog.value = false;
      resetDialogState();
      emit("success");
      emit("closeDia");
    } else {
      ElMessage.error(result?.msg || result?.message || "提交失败");
    }
  } catch (error) {
    ElMessage.error(error?.message || "提交失败");
  } finally {
    loadingInstance.close();
  }
};
const goToRepair = () => {
  const taskInfo = {
    taskId: currentTask.value?.taskId || currentTask.value?.id,
    taskName: currentTask.value?.taskName,
    inspectionLocation: currentTask.value?.inspectionLocation,
    inspector: currentTask.value?.inspector,
    uploadedFiles: {
      before: beforeModelValue.value,
      after: afterModelValue.value,
      issue: issueModelValue.value,
    },
  };
  sessionStorage.setItem("repairTaskInfo", JSON.stringify(taskInfo));
  window.location.href = "/equipmentManagement/repair";
};
const previewAttachment = file => {
  if (isImageFile(file)) {
    window.open(file.url || file.downloadUrl, "_blank");
  } else {
    currentVideoFile.value = file;
    showVideoDialog.value = true;
  }
};
const isImageFile = file => {
  if (file?.contentType?.startsWith("image/")) return true;
  const name =
    file?.bucketFilename || file?.originalFilename || file?.name || "";
  const ext = name.split(".").pop()?.toLowerCase();
  return ["jpg", "jpeg", "png", "gif", "webp"].includes(ext);
};
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`;
};
defineExpose({
  openDialog,
});
</script>
<style scoped>
.exception-section {
  margin-bottom: 16px;
  padding: 12px;
  background: #f8f9fa;
  border-radius: 8px;
}
.section-title {
  font-weight: 600;
  margin-bottom: 8px;
}
.upload-buttons {
  margin-bottom: 12px;
}
.file-list {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}
.file-item {
  width: 120px;
  padding: 8px;
  border: 1px solid #e5e5e5;
  border-radius: 10px;
  background: #fff;
  text-align: center;
}
.file-preview-container {
  position: relative;
}
.file-preview {
  width: 90px;
  height: 90px;
  object-fit: cover;
  border-radius: 8px;
  border: 1px solid #eee;
  cursor: pointer;
}
.video-preview {
  width: 90px;
  height: 90px;
  margin: 0 auto;
  border-radius: 8px;
  background: #eef3f8;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #409eff;
  cursor: pointer;
}
.delete-btn {
  position: absolute;
  top: -6px;
  right: 6px;
  width: 22px;
  height: 22px;
  border: none;
  border-radius: 50%;
  color: #fff;
  background: #f56c6c;
  cursor: pointer;
}
.file-name {
  margin-top: 6px;
  font-size: 12px;
  color: #333;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.file-size {
  font-size: 11px;
  color: #999;
}
.upload-summary {
  margin-top: 16px;
  padding: 10px;
  background: #f8f9fa;
  border-left: 3px solid #409eff;
  font-size: 13px;
  color: #666;
}
.video-player {
  width: 100%;
  max-height: 70vh;
  background: #000;
}
</style>
src/views/equipmentManagement/inspectionManagement/index.vue
@@ -76,6 +76,8 @@
    <form-dia ref="formDia"
              @closeDia="handleQuery"></form-dia>
    <view-files ref="viewFiles"></view-files>
    <upload-files ref="uploadFiles"
                  @closeDia="handleQuery"></upload-files>
  </div>
</template>
@@ -87,6 +89,7 @@
  // ç»„件引入
  import PIMTable from "@/components/PIMTable/PIMTable.vue";
  import FormDia from "@/views/equipmentManagement/inspectionManagement/components/formDia.vue";
  import UploadFiles from "@/views/equipmentManagement/inspectionManagement/components/uploadFiles.vue";
  import ViewFiles from "@/views/equipmentManagement/inspectionManagement/components/viewFiles.vue";
  // æŽ¥å£å¼•å…¥
@@ -100,6 +103,7 @@
  const { proxy } = getCurrentInstance();
  const formDia = ref();
  const viewFiles = ref();
  const uploadFiles = ref();
  // æŸ¥è¯¢å‚æ•°
  const queryParams = reactive({
@@ -184,8 +188,9 @@
    const operationConfig = {
      label: "操作",
      width: 130,
      width: operations.length > 1 ? 180 : 130,
      fixed: "right",
            align: 'center',
      dataType: "action",
      operation: operations
        .map(op => {
@@ -194,6 +199,12 @@
              return {
                name: "编辑",
                clickFun: handleAdd,
                color: "#409EFF",
              };
            case "upload":
              return {
                name: "上传",
                clickFun: openUploadFiles,
                color: "#409EFF",
              };
            case "viewFile":
@@ -226,12 +237,12 @@
      ];
      operationsArr.value = ["edit"];
    } else if (value === "task") {
      const operationColumn = getOperationColumn(["viewFile"]);
      const operationColumn = getOperationColumn(["upload", "viewFile"]);
      tableColumns.value = [
        ...columns.value,
        ...(operationColumn ? [operationColumn] : []),
      ];
      operationsArr.value = ["viewFile"];
      operationsArr.value = ["upload", "viewFile"];
    }
    pageNum.value = 1;
    pageSize.value = 10;
@@ -273,6 +284,7 @@
        // å¤„理 inspector å­—段,将字符串转换为数组(适用于所有情况)
        tableData.value = rawData.map(item => {
          const processedItem = { ...item };
          processedItem.__raw = { ...item };
          // å¤„理 inspector å­—段
          if (processedItem.inspector) {
@@ -322,6 +334,13 @@
  const viewFile = row => {
    nextTick(() => {
      viewFiles.value?.openDialog(row);
    });
  };
  // ä¸Šä¼ é™„ä»¶
  const openUploadFiles = row => {
    nextTick(() => {
      uploadFiles.value?.openDialog(row);
    });
  };
@@ -390,4 +409,4 @@
    color: #909399;
    font-size: 14px;
  }
</style>
</style>
vite.config.js
@@ -8,11 +8,11 @@
  const { VITE_APP_ENV } = env;
  const baseUrl =
      env.VITE_APP_ENV === "development"
          ? "http://1.15.17.182:9023"
          ? "http://1.15.17.182:9038"
          : env.VITE_BASE_API;
  const javaUrl =
      env.VITE_APP_ENV === "development"
          ? "http://1.15.17.182:9023"
          ? "http://1.15.17.182:9039"
          : env.VITE_JAVA_API;
  return {
    define:{