<template>
|
<div>
|
<el-dialog
|
v-model="showUploadDialog"
|
title="上传巡检记录"
|
width="560px"
|
:before-close="closeUploadDialog"
|
>
|
<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 || fileList.length >= uploadConfig.limit"
|
>
|
<el-button
|
type="primary"
|
:loading="uploading"
|
:disabled="fileList.length >= uploadConfig.limit"
|
>
|
选择图片/视频
|
</el-button>
|
</el-upload>
|
</div>
|
|
<el-progress
|
v-if="uploading"
|
:percentage="uploadProgress"
|
style="margin: 12px 0"
|
/>
|
|
<div v-if="fileList.length" class="file-list">
|
<div
|
v-for="(file, index) in fileList"
|
: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="请选择要上传的图片或视频"
|
/>
|
|
<div class="upload-summary">
|
已上传 {{ fileList.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 fileList = ref([]);
|
const hasException = ref(null);
|
const currentTask = ref(null);
|
|
const showVideoDialog = ref(false);
|
const currentVideoFile = ref(null);
|
|
const INSPECTION_TYPE = 10;
|
|
const uploadConfig = {
|
action: "/file/upload",
|
limit: 10,
|
fileSize: 50,
|
fileType: ["jpg", "jpeg", "png", "gif", "webp", "mp4", "mov", "avi", "wmv"],
|
};
|
|
const uploadUrl = computed(() => {
|
return `${import.meta.env.VITE_APP_BASE_API}${uploadConfig.action}?type=${INSPECTION_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) => ({
|
...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: INSPECTION_TYPE,
|
uid: file?.uid || `${INSPECTION_TYPE}-${file?.id || file?.url || Math.random()}`,
|
});
|
|
const resetDialogState = () => {
|
fileList.value = [];
|
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: [],
|
};
|
|
fileList.value = Array.isArray(rawTask.commonFileList)
|
? rawTask.commonFileList.map(file => mapExistingFile(file))
|
: [];
|
|
hasException.value =
|
typeof rawTask.hasException === "boolean" ? rawTask.hasException : null;
|
uploadFileList.value = [];
|
showUploadDialog.value = true;
|
};
|
|
const closeUploadDialog = () => {
|
showUploadDialog.value = false;
|
resetDialogState();
|
emit("closeDia");
|
};
|
|
const handleBeforeUpload = file => {
|
if (fileList.value.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 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: INSPECTION_TYPE,
|
uid: `${Date.now()}-${Math.random()}`,
|
};
|
|
fileList.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",
|
});
|
fileList.value.splice(index, 1);
|
ElMessage.success("删除成功");
|
} catch {}
|
};
|
|
const buildSubmitPayload = () => {
|
const {
|
createTime,
|
updateTime,
|
storageBlobDTO,
|
commonFileListBefore,
|
commonFileListAfter,
|
commonFileList,
|
__raw,
|
...rest
|
} = currentTask.value || {};
|
|
const files = fileList.value.map(item => ({
|
...item,
|
url: item?.downloadUrl || item?.url || "",
|
}));
|
|
const tempFileIds = files
|
.map(item => item?.tempId ?? item?.tempFileId ?? item?.id)
|
.filter(Boolean);
|
|
return {
|
...rest,
|
hasException: hasException.value,
|
tempFileIds,
|
commonFileList: files.map(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,
|
};
|
}).filter(Boolean),
|
};
|
};
|
|
const submitUpload = async () => {
|
if (hasException.value === null) {
|
ElMessage.warning("请选择是否存在异常");
|
return;
|
}
|
|
if (hasException.value === true && !fileList.value.length) {
|
ElMessage.warning("存在异常时请上传图片或视频");
|
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 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>
|