<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="currentHasException">
|
<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 hasExceptionBefore = ref(null);
|
const hasExceptionAfter = ref(null);
|
const hasExceptionIssue = ref(null);
|
const currentTask = ref(null);
|
|
const showVideoDialog = ref(false);
|
const currentVideoFile = ref(null);
|
|
// 根据当前 Tab 获取/设置对应的异常状态
|
const currentHasException = computed({
|
get: () => {
|
if (currentUploadType.value === "before") return hasExceptionBefore.value;
|
if (currentUploadType.value === "after") return hasExceptionAfter.value;
|
return hasExceptionIssue.value;
|
},
|
set: (val) => {
|
if (currentUploadType.value === "before") hasExceptionBefore.value = val;
|
else if (currentUploadType.value === "after") hasExceptionAfter.value = val;
|
else hasExceptionIssue.value = val;
|
}
|
});
|
|
const uploadConfig = {
|
action: "/file/upload",
|
limit: 10,
|
fileSize: 50,
|
fileType: ["jpg", "jpeg", "png", "gif", "webp", "mp4", "mov", "avi", "wmv"],
|
};
|
|
const uploadUrl = computed(() => {
|
const type = getTabType();
|
return `${import.meta.env.VITE_APP_BASE_API}${uploadConfig.action}?type=${type}`;
|
});
|
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";
|
hasExceptionBefore.value = null;
|
hasExceptionAfter.value = null;
|
hasExceptionIssue.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";
|
hasExceptionBefore.value =
|
typeof rawTask.hasExceptionBefore === "boolean" ? rawTask.hasExceptionBefore : null;
|
hasExceptionAfter.value =
|
typeof rawTask.hasExceptionAfter === "boolean" ? rawTask.hasExceptionAfter : null;
|
hasExceptionIssue.value =
|
typeof rawTask.hasExceptionIssue === "boolean" ? rawTask.hasExceptionIssue : 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 buildSubmitFileItem = item => {
|
if (!item) return null;
|
|
return {
|
id: item.id,
|
tempId: item.tempId,
|
tempFileId: item.tempFileId,
|
type: item.type,
|
url: item?.downloadUrl || item?.url || "",
|
downloadUrl: item?.downloadUrl || item?.url || "",
|
bucketFilename: item.bucketFilename,
|
originalFilename: item.originalFilename,
|
size: item.size,
|
byteSize: item.byteSize,
|
contentType: item.contentType,
|
};
|
};
|
|
const buildGroupedFiles = list => {
|
return (list || []).map(buildSubmitFileItem).filter(Boolean);
|
};
|
|
const buildSubmitPayload = () => {
|
const {
|
createTime,
|
updateTime,
|
storageBlobDTO,
|
commonFileListBefore,
|
commonFileListAfter,
|
commonFileList,
|
__raw,
|
...rest
|
} = currentTask.value || {};
|
|
const files = buildSubmitFiles();
|
const tempFileIds = files
|
.map(item => item?.tempId ?? item?.tempFileId ?? item?.id)
|
.filter(Boolean);
|
|
return {
|
...rest,
|
hasExceptionBefore: hasExceptionBefore.value,
|
hasExceptionAfter: hasExceptionAfter.value,
|
hasExceptionIssue: hasExceptionIssue.value,
|
tempFileIds,
|
commonFileListBefore: buildGroupedFiles(beforeModelValue.value),
|
commonFileListAfter: buildGroupedFiles(afterModelValue.value),
|
commonFileList: buildGroupedFiles(issueModelValue.value),
|
};
|
};
|
|
const submitUpload = async () => {
|
// 只验证当前 Tab 的异常状态
|
const currentException = currentHasException.value;
|
if (currentException === null) {
|
ElMessage.warning(`请选择${getUploadTypeText()}是否存在异常`);
|
return;
|
}
|
|
const currentFiles = getCurrentFiles();
|
// 只有选择"存在异常"时才需要上传文件
|
if (currentException === true && !currentFiles.length) {
|
ElMessage.warning(`${getUploadTypeText()}存在异常时请上传图片或视频`);
|
return;
|
}
|
|
const loadingInstance = ElLoading.service({
|
text: "提交中...",
|
background: "rgba(0, 0, 0, 0.3)",
|
});
|
|
try {
|
const payload = buildSubmitPayload();
|
|
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>
|