<template>
|
<el-dialog
|
v-model="visible"
|
title="保养计划详情"
|
width="960px"
|
destroy-on-close
|
@closed="onClosed"
|
>
|
<div v-loading="loading" class="upkeep-detail">
|
<template v-if="detail">
|
<el-descriptions :column="2" border size="small">
|
<el-descriptions-item label="设备名称">{{ detail.deviceName || "—" }}</el-descriptions-item>
|
<el-descriptions-item label="规格型号">{{ detail.deviceModel || "—" }}</el-descriptions-item>
|
<el-descriptions-item label="保养部位" :span="2">
|
<div class="multiline">{{ detail.maintenanceLocation || "—" }}</div>
|
</el-descriptions-item>
|
<el-descriptions-item label="保养内容" :span="2">
|
<div class="multiline">{{ detail.maintenanceItems || "—" }}</div>
|
</el-descriptions-item>
|
<el-descriptions-item label="保养人">{{ detail.maintenancePerson || "—" }}</el-descriptions-item>
|
<el-descriptions-item label="计划保养日期">{{ fmtDate(detail.maintenancePlanTime) }}</el-descriptions-item>
|
<el-descriptions-item label="录入人">{{ detail.createUserName || "—" }}</el-descriptions-item>
|
<el-descriptions-item label="实际保养人">{{ detail.maintenanceActuallyName || "—" }}</el-descriptions-item>
|
<el-descriptions-item label="实际保养日期">{{ fmtDateTime(detail.maintenanceActuallyTime) }}</el-descriptions-item>
|
<el-descriptions-item label="状态">
|
<el-tag v-if="detail.status === 2" type="danger" size="small">失败</el-tag>
|
<el-tag v-else-if="detail.status === 1" type="success" size="small">完结</el-tag>
|
<el-tag v-else-if="detail.status === 0" type="warning" size="small">待保养</el-tag>
|
<span v-else>—</span>
|
</el-descriptions-item>
|
<el-descriptions-item label="保养结果" :span="2">
|
<div class="multiline">{{ detail.maintenanceResult || "—" }}</div>
|
</el-descriptions-item>
|
<el-descriptions-item label="录入时间">{{ fmtDateTime(detail.createTime) }}</el-descriptions-item>
|
<el-descriptions-item label="更新时间">{{ fmtDateTime(detail.updateTime) }}</el-descriptions-item>
|
</el-descriptions>
|
|
<div class="attach-block">
|
<div class="attach-title">附件({{ fileList.length }})</div>
|
<div v-if="fileList.length" class="img-grid">
|
<div v-for="file in fileList" :key="file.id" class="attach-item">
|
<el-image
|
v-if="isImageFile(file.name)"
|
:src="file.url"
|
fit="cover"
|
class="thumb"
|
:preview-src-list="imagePreviewList"
|
preview-teleported
|
/>
|
<div v-else class="file-chip">
|
<el-link type="primary" :href="file.url" target="_blank">{{ file.name }}</el-link>
|
</div>
|
<div class="file-name" :title="file.name">{{ file.name }}</div>
|
</div>
|
</div>
|
<el-empty v-else description="暂无附件" :image-size="56" />
|
</div>
|
</template>
|
</div>
|
<template #footer>
|
<el-button type="primary" @click="visible = false">关闭</el-button>
|
</template>
|
</el-dialog>
|
</template>
|
|
<script setup>
|
import { computed, ref } from "vue";
|
import dayjs from "dayjs";
|
import { getUpkeepById } from "@/api/equipmentManagement/upkeep";
|
import { listMaintenanceTaskFiles } from "@/api/equipmentManagement/maintenanceTaskFile";
|
|
defineOptions({ name: "UpkeepDetailModal" });
|
|
const props = defineProps({
|
javaApi: {
|
type: String,
|
default: "",
|
},
|
});
|
|
const visible = ref(false);
|
const loading = ref(false);
|
const detail = ref(null);
|
const fileList = ref([]);
|
|
const apiBase = computed(() => props.javaApi || import.meta.env.VITE_APP_BASE_API || "");
|
|
const imagePreviewList = computed(() =>
|
fileList.value.filter((f) => isImageFile(f.name)).map((f) => f.url)
|
);
|
|
const fmtDate = (v) => (v ? dayjs(v).format("YYYY-MM-DD") : "—");
|
const fmtDateTime = (v) => (v ? dayjs(v).format("YYYY-MM-DD HH:mm:ss") : "—");
|
|
const isImageFile = (name = "") => /\.(jpg|jpeg|png|gif|webp|bmp)$/i.test(name);
|
|
const normalizeFileUrl = (rawUrl = "") => {
|
let fileUrl = rawUrl || "";
|
if (!fileUrl) return "";
|
if (fileUrl.startsWith("http://") || fileUrl.startsWith("https://")) return fileUrl;
|
if (fileUrl.indexOf("\\") > -1) {
|
const lowerPath = fileUrl.toLowerCase();
|
const uploadPathIndex = lowerPath.indexOf("uploadpath");
|
if (uploadPathIndex > -1) {
|
fileUrl = fileUrl.substring(uploadPathIndex).replace(/\\/g, "/");
|
} else {
|
fileUrl = fileUrl.replace(/\\/g, "/");
|
}
|
}
|
fileUrl = fileUrl.replace(/^\/?uploadPath/i, "/profile");
|
if (!fileUrl.startsWith("/")) fileUrl = "/" + fileUrl;
|
if (!fileUrl.startsWith(apiBase.value)) fileUrl = apiBase.value + fileUrl;
|
return fileUrl;
|
};
|
|
const loadFiles = async (id) => {
|
const res = await listMaintenanceTaskFiles({
|
current: 1,
|
size: 100,
|
deviceMaintenanceId: id,
|
});
|
const records = res?.data?.records || [];
|
fileList.value = records.map((item) => ({
|
id: item.id,
|
name: item.name,
|
url: normalizeFileUrl(item.url),
|
}));
|
};
|
|
const open = async (row) => {
|
if (!row?.id) return;
|
visible.value = true;
|
loading.value = true;
|
detail.value = null;
|
fileList.value = [];
|
try {
|
const { data } = await getUpkeepById(row.id);
|
detail.value = { ...row, ...data };
|
await loadFiles(row.id);
|
} finally {
|
loading.value = false;
|
}
|
};
|
|
const onClosed = () => {
|
detail.value = null;
|
fileList.value = [];
|
};
|
|
defineExpose({ open });
|
</script>
|
|
<style lang="scss" scoped>
|
.upkeep-detail {
|
min-height: 120px;
|
}
|
.multiline {
|
white-space: pre-wrap;
|
word-break: break-word;
|
line-height: 1.6;
|
}
|
.attach-block {
|
margin-top: 20px;
|
}
|
.attach-title {
|
font-size: 14px;
|
font-weight: 600;
|
margin-bottom: 12px;
|
color: #303133;
|
}
|
.img-grid {
|
display: flex;
|
flex-wrap: wrap;
|
gap: 12px;
|
}
|
.attach-item {
|
width: 100px;
|
}
|
.thumb {
|
width: 100px;
|
height: 100px;
|
border-radius: 4px;
|
border: 1px solid #ebeef5;
|
}
|
.file-chip {
|
width: 100px;
|
height: 100px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
padding: 8px;
|
border: 1px dashed #dcdfe6;
|
border-radius: 4px;
|
box-sizing: border-box;
|
}
|
.file-name {
|
display: block;
|
margin-top: 6px;
|
font-size: 12px;
|
color: #606266;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
white-space: nowrap;
|
}
|
</style>
|