<template>
|
<FormDialog
|
v-model="dialogVisible"
|
title="上传巡检记录"
|
width="980px"
|
@close="handleClose"
|
@cancel="handleClose"
|
>
|
<main class="upload-content">
|
<el-card v-if="taskInfo" class="section-card">
|
<el-descriptions :column="1" border>
|
<el-descriptions-item label="巡检任务名称">
|
{{ taskInfo.taskName || "-" }}
|
</el-descriptions-item>
|
<el-descriptions-item label="巡检项目">
|
{{ taskInfo.inspectionProject || "-" }}
|
</el-descriptions-item>
|
<el-descriptions-item label="备注">
|
{{ taskInfo.remarks || "-" }}
|
</el-descriptions-item>
|
</el-descriptions>
|
</el-card>
|
|
<el-card class="section-card">
|
<h3>巡检状态</h3>
|
<el-radio-group v-model="hasException">
|
<el-radio-button :value="false">正常</el-radio-button>
|
<el-radio-button :value="true">存在异常</el-radio-button>
|
</el-radio-group>
|
</el-card>
|
|
<el-card v-if="hasException === true" class="section-card">
|
<h3>异常描述</h3>
|
<el-input
|
v-model="abnormalDescription"
|
type="textarea"
|
maxlength="500"
|
show-word-limit
|
:rows="4"
|
placeholder="请描述异常情况..."
|
/>
|
</el-card>
|
|
<el-card v-if="hasException === true" class="section-card">
|
<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="upload-buttons">
|
<el-upload
|
:show-file-list="false"
|
:http-request="uploadFile"
|
:disabled="getCurrentFiles().length >= uploadConfig.limit || uploading"
|
accept="image/*"
|
>
|
<el-button type="primary" :loading="uploading">
|
<el-icon><Camera /></el-icon>
|
选择图片
|
</el-button>
|
</el-upload>
|
|
<el-upload
|
:show-file-list="false"
|
:http-request="uploadFile"
|
:disabled="getCurrentFiles().length >= uploadConfig.limit || uploading"
|
accept="video/*"
|
>
|
<el-button type="success" :loading="uploading">
|
<el-icon><VideoCamera /></el-icon>
|
选择视频
|
</el-button>
|
</el-upload>
|
</div>
|
|
<el-progress
|
v-if="uploading"
|
:percentage="uploadProgress"
|
class="upload-progress"
|
/>
|
|
<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">
|
<el-image
|
v-if="file.type === 'image' || !file.type"
|
:src="file.url || file.tempFilePath || file.path || file.downloadUrl"
|
fit="cover"
|
class="file-preview"
|
:preview-src-list="[file.url || file.tempFilePath || file.path || file.downloadUrl]"
|
preview-teleported
|
/>
|
|
<div v-else class="video-preview" @click="previewVideo(file)">
|
<el-icon><VideoCamera /></el-icon>
|
<span>视频</span>
|
</div>
|
|
<el-button
|
class="delete-btn"
|
type="danger"
|
circle
|
size="small"
|
@click="removeFile(index)"
|
>
|
<el-icon><Close /></el-icon>
|
</el-button>
|
</div>
|
|
<div class="file-info">
|
<div class="file-name">
|
{{ file.bucketFilename || file.name || (file.type === "image" ? "图片" : "视频") }}
|
</div>
|
<div class="file-size">{{ formatFileSize(file.size) }}</div>
|
</div>
|
</div>
|
</div>
|
|
<el-empty
|
v-else
|
:description="`请选择要上传的${getUploadTypeText()}图片或视频`"
|
/>
|
|
<el-alert
|
class="upload-summary"
|
type="info"
|
:closable="false"
|
:title="`生产前:${beforeModelValue.length}个文件 | 生产中:${afterModelValue.length}个文件 | 生产后:${issueModelValue.length}个文件`"
|
/>
|
</el-card>
|
|
<el-result
|
v-if="hasException === false"
|
icon="success"
|
title="设备运行正常"
|
sub-title="无需上传照片"
|
/>
|
</main>
|
|
<template #footer>
|
<footer class="footer-buttons">
|
<el-button type="primary" @click="submitUpload">提交</el-button>
|
<el-button v-if="hasException === true" type="warning" @click="goToRepair">
|
新增报修
|
</el-button>
|
<el-button @click="handleClose">取消</el-button>
|
</footer>
|
</template>
|
</FormDialog>
|
|
<el-dialog
|
v-model="showVideoDialog"
|
:title="currentVideoFile?.originalFilename || currentVideoFile?.name || '视频预览'"
|
width="720px"
|
>
|
<video
|
v-if="currentVideoFile"
|
:src="currentVideoFile.url || currentVideoFile.downloadUrl"
|
class="video-player"
|
controls
|
autoplay
|
/>
|
</el-dialog>
|
</template>
|
|
<script setup>
|
import { computed, ref } from "vue";
|
import { useRouter } from "vue-router";
|
import { ElLoading, ElMessage, ElMessageBox } from "element-plus";
|
import { Camera, Close, VideoCamera } from "@element-plus/icons-vue";
|
import axios from "axios";
|
import FormDialog from "@/components/Dialog/FormDialog.vue";
|
import { uploadInspectionTask } from "@/api/inspectionManagement/index.js";
|
import { getToken } from "@/utils/auth";
|
|
const emit = defineEmits(["closeDia", "success"]);
|
const router = useRouter();
|
|
const dialogVisible = ref(false);
|
const taskInfo = ref(null);
|
const uploading = ref(false);
|
const uploadProgress = ref(0);
|
|
const beforeModelValue = ref([]);
|
const afterModelValue = ref([]);
|
const issueModelValue = ref([]);
|
|
const currentUploadType = ref("before");
|
const hasException = ref(null);
|
const abnormalDescription = ref("");
|
|
const showVideoDialog = ref(false);
|
const currentVideoFile = ref(null);
|
|
const uploadConfig = {
|
action: "/common/upload",
|
limit: 10,
|
fileSize: 50,
|
fileType: ["jpg", "jpeg", "png", "mp4", "mov"],
|
};
|
|
const uploadFileUrl = computed(
|
() => `${import.meta.env.VITE_APP_BASE_API}${uploadConfig.action}`
|
);
|
|
const processFileUrl = fileUrl => {
|
if (!fileUrl) return "";
|
|
let currentUrl = String(fileUrl);
|
if (currentUrl.includes("\\")) {
|
const uploadsIndex = currentUrl.toLowerCase().indexOf("uploads");
|
if (uploadsIndex > -1) {
|
currentUrl = `/${currentUrl.substring(uploadsIndex).replace(/\\/g, "/")}`;
|
} else {
|
const fileName = currentUrl.split("\\").pop();
|
currentUrl = `/uploads/${fileName}`;
|
}
|
}
|
|
if (currentUrl && !currentUrl.startsWith("http")) {
|
if (!currentUrl.startsWith("/")) {
|
currentUrl = `/${currentUrl}`;
|
}
|
currentUrl = __BASE_API__ + currentUrl;
|
}
|
|
return currentUrl;
|
};
|
|
const normalizeList = (list, fileType) => {
|
if (!Array.isArray(list)) return [];
|
|
return list.filter(Boolean).map(item => {
|
let currentType = item.type;
|
if (!currentType && item.contentType) {
|
currentType = item.contentType.startsWith("video") ? "video" : "image";
|
} else if (!currentType) {
|
currentType = fileType || "image";
|
}
|
|
return {
|
...item,
|
url: processFileUrl(item.url || item.previewURL || item.downloadUrl || item.path || ""),
|
downloadUrl: processFileUrl(
|
item.downloadUrl || item.url || item.previewURL || item.path || ""
|
),
|
name: item.name || item.originalFilename || item.bucketFilename,
|
tempId: item.tempId || item.id || item.tempFileId,
|
tempFileId: item.tempFileId || item.tempId || item.id,
|
size: item.size || item.byteSize || 0,
|
type: currentType,
|
status: "success",
|
uid: item.uid || `${Date.now()}-${Math.random()}`,
|
};
|
});
|
};
|
|
const resetState = () => {
|
taskInfo.value = null;
|
beforeModelValue.value = [];
|
afterModelValue.value = [];
|
issueModelValue.value = [];
|
currentUploadType.value = "before";
|
hasException.value = null;
|
abnormalDescription.value = "";
|
uploading.value = false;
|
uploadProgress.value = 0;
|
showVideoDialog.value = false;
|
currentVideoFile.value = null;
|
};
|
|
const openDialog = row => {
|
const raw = JSON.parse(JSON.stringify(row?.__raw || row || {}));
|
taskInfo.value = raw;
|
|
beforeModelValue.value = normalizeList(
|
raw.commonFileListBeforeVO || raw.commonFileListBefore || [],
|
"image"
|
);
|
afterModelValue.value = normalizeList(
|
raw.commonFileListVO || raw.commonFileList || [],
|
"image"
|
);
|
issueModelValue.value = normalizeList(
|
raw.commonFileListAfterVO || raw.commonFileListAfter || [],
|
"image"
|
);
|
|
abnormalDescription.value = raw.abnormalDescription || "";
|
|
if (raw.hasException !== undefined && raw.hasException !== null) {
|
hasException.value = raw.hasException;
|
} else if (raw.inspectionResult !== undefined && raw.inspectionResult !== null) {
|
hasException.value = String(raw.inspectionResult) === "0";
|
} else {
|
hasException.value = null;
|
}
|
|
if (
|
hasException.value !== true &&
|
(beforeModelValue.value.length || afterModelValue.value.length || issueModelValue.value.length)
|
) {
|
hasException.value = true;
|
}
|
|
dialogVisible.value = true;
|
};
|
|
const handleClose = () => {
|
dialogVisible.value = false;
|
resetState();
|
emit("closeDia");
|
};
|
|
const getCurrentFiles = () => {
|
if (currentUploadType.value === "before") return beforeModelValue.value;
|
if (currentUploadType.value === "after") return afterModelValue.value;
|
if (currentUploadType.value === "issue") return issueModelValue.value;
|
return [];
|
};
|
|
const getUploadTypeText = () => {
|
if (currentUploadType.value === "before") return "生产前";
|
if (currentUploadType.value === "after") return "生产中";
|
if (currentUploadType.value === "issue") return "生产后";
|
return "";
|
};
|
|
const getTabType = () => {
|
if (currentUploadType.value === "before") return 10;
|
if (currentUploadType.value === "after") return 11;
|
if (currentUploadType.value === "issue") return 12;
|
return 10;
|
};
|
|
const previewVideo = file => {
|
currentVideoFile.value = file;
|
showVideoDialog.value = true;
|
};
|
|
const uploadFile = async uploadRequest => {
|
const rawFile = uploadRequest.file;
|
|
if (getCurrentFiles().length >= uploadConfig.limit) {
|
ElMessage.warning(`最多只能选择${uploadConfig.limit}个文件`);
|
return;
|
}
|
|
const ext = rawFile.name.split(".").pop()?.toLowerCase();
|
if (!uploadConfig.fileType.includes(ext)) {
|
ElMessage.warning(`文件格式不支持,请上传 ${uploadConfig.fileType.join("/")} 格式`);
|
return;
|
}
|
|
if (rawFile.size > uploadConfig.fileSize * 1024 * 1024) {
|
ElMessage.warning(`文件大小不能超过 ${uploadConfig.fileSize}MB`);
|
return;
|
}
|
|
const token = getToken();
|
if (!token) {
|
ElMessage.warning("用户未登录");
|
return;
|
}
|
|
const formData = new FormData();
|
formData.append("files", rawFile);
|
formData.append("type", getTabType());
|
|
uploading.value = true;
|
uploadProgress.value = 0;
|
|
try {
|
const { data } = await axios.post(uploadFileUrl.value, formData, {
|
headers: {
|
Authorization: `Bearer ${token}`,
|
"Content-Type": "multipart/form-data",
|
},
|
onUploadProgress: event => {
|
if (event.total) {
|
uploadProgress.value = Math.round((event.loaded / event.total) * 100);
|
}
|
},
|
});
|
|
if (data.code !== 200) {
|
ElMessage.error(data.msg || "上传失败");
|
return;
|
}
|
|
const resultData = Array.isArray(data.data) ? data.data[0] : data.data;
|
const finalUrl = processFileUrl(
|
resultData.url || resultData.previewURL || resultData.downloadUrl || ""
|
);
|
const finalName = resultData.name || resultData.originalFilename || resultData.bucketFilename;
|
const finalId = resultData.tempId || resultData.id || resultData.tempFileId;
|
|
const uploadedFile = {
|
...resultData,
|
url: finalUrl,
|
downloadUrl: finalUrl,
|
name: finalName,
|
tempId: finalId,
|
tempFileId: resultData.tempFileId || finalId,
|
size: rawFile.size || resultData.size || resultData.byteSize || 0,
|
type: rawFile.type?.startsWith("video") ? "video" : "image",
|
status: "success",
|
uid: `${Date.now()}-${Math.random()}`,
|
};
|
|
getCurrentFiles().push(uploadedFile);
|
ElMessage.success("上传成功");
|
} catch (error) {
|
ElMessage.error(error?.message || "上传失败");
|
} finally {
|
uploading.value = false;
|
}
|
};
|
|
const buildFileItem = item => ({
|
id: item?.id,
|
tempId: item?.tempId,
|
tempFileId: item?.tempFileId,
|
url: item?.downloadUrl || item?.url || "",
|
downloadUrl: item?.downloadUrl || item?.url || "",
|
name: item?.name,
|
bucketFilename: item?.bucketFilename || item?.name,
|
originalFilename: item?.originalFilename || item?.name,
|
size: item?.size || 0,
|
byteSize: item?.byteSize || item?.size || 0,
|
contentType: item?.contentType || "",
|
type: item?.type,
|
});
|
|
const submitUpload = async () => {
|
if (hasException.value === null) {
|
ElMessage.warning("请选择巡检状态");
|
return;
|
}
|
|
if (hasException.value === true) {
|
const totalFiles =
|
beforeModelValue.value.length +
|
afterModelValue.value.length +
|
issueModelValue.value.length;
|
|
if (!totalFiles) {
|
ElMessage.warning("请上传异常照片或视频");
|
return;
|
}
|
|
if (!abnormalDescription.value.trim()) {
|
ElMessage.warning("请填写异常描述");
|
return;
|
}
|
}
|
|
const loading = ElLoading.service({
|
text: "提交中...",
|
background: "rgba(0, 0, 0, 0.3)",
|
});
|
|
try {
|
const allFiles = [
|
...beforeModelValue.value,
|
...afterModelValue.value,
|
...issueModelValue.value,
|
];
|
|
const tempFileIds = allFiles
|
.map(item => item?.tempId ?? item?.tempFileId ?? item?.id)
|
.filter(Boolean);
|
|
const {
|
createTime,
|
updateTime,
|
storageBlobDTO,
|
commonFileListAfterVO,
|
commonFileListVO,
|
commonFileListBeforeVO,
|
commonFileListAfter,
|
commonFileList,
|
commonFileListBefore,
|
__raw,
|
...baseTaskInfo
|
} = taskInfo.value || {};
|
|
const submitData = {
|
...baseTaskInfo,
|
commonFileListBeforeDTO: beforeModelValue.value.map(buildFileItem),
|
commonFileListDTO: afterModelValue.value.map(buildFileItem),
|
commonFileListAfterDTO: issueModelValue.value.map(buildFileItem),
|
hasException: hasException.value,
|
inspectionResult: hasException.value ? 0 : 1,
|
abnormalDescription: abnormalDescription.value,
|
tempFileIds,
|
};
|
|
const result = await uploadInspectionTask(submitData);
|
|
if (result && (result.code === 200 || result.success)) {
|
ElMessage.success("提交成功");
|
dialogVisible.value = false;
|
resetState();
|
emit("success");
|
emit("closeDia");
|
} else {
|
ElMessage.error(result?.msg || result?.message || "提交失败");
|
}
|
} catch (error) {
|
ElMessage.error(error?.message || "提交失败");
|
} finally {
|
loading.close();
|
}
|
};
|
|
const removeFile = async index => {
|
try {
|
await ElMessageBox.confirm("确定要删除这个文件吗?", "确认删除", {
|
type: "warning",
|
});
|
getCurrentFiles().splice(index, 1);
|
} catch {}
|
};
|
|
const goToRepair = () => {
|
const taskData = {
|
taskId: taskInfo.value?.taskId || taskInfo.value?.id,
|
taskName: taskInfo.value?.taskName,
|
inspectionLocation: taskInfo.value?.inspectionLocation,
|
inspector: taskInfo.value?.inspector,
|
hasException: hasException.value,
|
inspectionResult: hasException.value ? 0 : 1,
|
commonFileListBeforeDTO: beforeModelValue.value.map(buildFileItem),
|
commonFileListDTO: afterModelValue.value.map(buildFileItem),
|
commonFileListAfterDTO: issueModelValue.value.map(buildFileItem),
|
uploadedFiles: {
|
before: beforeModelValue.value,
|
after: afterModelValue.value,
|
issue: issueModelValue.value,
|
},
|
};
|
|
sessionStorage.setItem("repairTaskInfo", JSON.stringify(taskData));
|
router.push("/equipmentManagement/repair/add");
|
};
|
|
const formatFileSize = size => {
|
if (!size) return "0 B";
|
|
const units = ["B", "KB", "MB", "GB"];
|
let index = 0;
|
let fileSize = size;
|
|
while (fileSize >= 1024 && index < units.length - 1) {
|
fileSize /= 1024;
|
index += 1;
|
}
|
|
return `${fileSize.toFixed(2)} ${units[index]}`;
|
};
|
|
defineExpose({
|
openDialog,
|
});
|
</script>
|
|
<style scoped>
|
.inspection-upload-page {
|
min-height: 70vh;
|
background: #f5f7fa;
|
padding: 20px 20px 90px;
|
box-sizing: border-box;
|
}
|
|
.upload-content {
|
max-width: 960px;
|
margin: 20px auto 0;
|
}
|
|
.section-card {
|
margin-bottom: 16px;
|
}
|
|
.section-card h3 {
|
margin: 0 0 16px;
|
font-size: 16px;
|
}
|
|
.upload-buttons {
|
display: flex;
|
gap: 12px;
|
margin: 16px 0;
|
}
|
|
.upload-progress {
|
margin-bottom: 16px;
|
}
|
|
.file-list {
|
display: grid;
|
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
gap: 12px;
|
}
|
|
.file-preview-container {
|
position: relative;
|
aspect-ratio: 1;
|
border-radius: 8px;
|
overflow: hidden;
|
background: #f2f3f5;
|
}
|
|
.file-preview {
|
width: 100%;
|
height: 100%;
|
}
|
|
.video-preview {
|
width: 100%;
|
height: 100%;
|
background: #303133;
|
color: #fff;
|
display: flex;
|
gap: 6px;
|
align-items: center;
|
justify-content: center;
|
cursor: pointer;
|
}
|
|
.delete-btn {
|
position: absolute;
|
top: 6px;
|
right: 6px;
|
}
|
|
.file-info {
|
margin-top: 6px;
|
font-size: 12px;
|
}
|
|
.file-name {
|
color: #606266;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
white-space: nowrap;
|
}
|
|
.file-size {
|
color: #909399;
|
margin-top: 2px;
|
}
|
|
.upload-summary {
|
margin-top: 16px;
|
}
|
|
.footer-buttons {
|
position: sticky;
|
left: 0;
|
right: 0;
|
bottom: 0;
|
padding: 14px 20px 0;
|
background: #f5f7fa;
|
display: flex;
|
justify-content: center;
|
gap: 12px;
|
}
|
|
.video-player {
|
width: 100%;
|
max-height: 70vh;
|
background: #000;
|
}
|
</style>
|