<template>
|
<FormDialog
|
v-model="visible"
|
:title="id ? '编辑设备报修' : '新增设备报修'"
|
width="880px"
|
@confirm="sendForm"
|
@cancel="handleCancel"
|
@close="handleClose"
|
>
|
<el-form :model="form" label-width="100px">
|
<el-row>
|
<el-col :span="12">
|
<el-form-item label="设备名称">
|
<el-select v-model="form.deviceLedgerId" placeholder="请选择" @change="setDeviceModel" filterable style="width: 100%">
|
<el-option
|
v-for="(item, index) in deviceOptions"
|
:key="index"
|
:label="item.deviceName"
|
:value="item.id"
|
></el-option>
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="规格型号">
|
<el-input
|
v-model="form.deviceModel"
|
placeholder="请输入规格型号"
|
disabled
|
/>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row>
|
<el-col :span="12">
|
<el-form-item label="报修日期">
|
<el-date-picker
|
v-model="form.repairTime"
|
placeholder="请选择报修日期"
|
format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
type="date"
|
clearable
|
style="width: 100%"
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="报修人">
|
<el-input v-model="form.repairName" disabled placeholder="当前登录人" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row>
|
<el-col :span="12">
|
<el-form-item label="验收人">
|
<el-select
|
v-model="form.acceptanceName"
|
placeholder="请选择验收人"
|
filterable
|
clearable
|
style="width: 100%"
|
>
|
<el-option
|
v-for="item in userList"
|
:key="item.userId ?? item.nickName"
|
:label="item.nickName"
|
:value="item.nickName"
|
/>
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="维修人" required>
|
<el-select
|
v-model="form.maintenanceName"
|
placeholder="请选择或输入维修人"
|
filterable
|
allow-create
|
default-first-option
|
clearable
|
style="width: 100%"
|
>
|
<el-option
|
v-for="item in userList"
|
:key="'m-' + (item.userId ?? item.nickName)"
|
:label="item.nickName"
|
:value="item.nickName"
|
/>
|
</el-select>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row v-if="id">
|
<el-col :span="12">
|
<el-form-item label="报修状态">
|
<el-select v-model="form.status">
|
<el-option label="待维修" :value="0"></el-option>
|
<el-option label="完结" :value="1"></el-option>
|
<el-option label="失败" :value="2"></el-option>
|
</el-select>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row>
|
<el-col :span="24">
|
<el-form-item label="故障现象">
|
<el-input
|
v-model="form.remark"
|
:rows="2"
|
type="textarea"
|
placeholder="请输入故障现象"
|
/>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row>
|
<el-col :span="24">
|
<el-form-item label="设备问题图片">
|
<div v-if="id" class="problem-existing-wrap">
|
<div
|
v-for="img in displayedExistingProblems"
|
:key="img.id"
|
class="problem-thumb"
|
>
|
<el-image :src="img.url" fit="cover" class="problem-thumb-img" @click="previewProblem(img.url)" />
|
<el-icon class="problem-thumb-remove" @click.stop="markProblemRemove(img.id)"><Close /></el-icon>
|
</div>
|
</div>
|
<el-upload
|
class="repair-attachment-upload"
|
v-model:file-list="attachmentFileList"
|
drag
|
multiple
|
:auto-upload="false"
|
:limit="9"
|
accept="image/png,image/jpeg,image/jpg"
|
:before-upload="beforeAttachmentUpload"
|
>
|
<el-icon class="repair-upload-icon"><UploadFilled /></el-icon>
|
<div class="el-upload__text">将文件拖到此处,或 <em>点击选择文件</em></div>
|
<template #tip>
|
<div class="el-upload__tip">
|
设备问题图:{{ id ? '编辑时删除需点弹窗底部「确认」后才生效;' : '' }}支持 png / jpg / jpeg,单张不超过 50MB,最多 9 张
|
</div>
|
</template>
|
</el-upload>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
</el-form>
|
</FormDialog>
|
</template>
|
|
<script setup>
|
import { getCurrentInstance, computed } from "vue";
|
import FormDialog from "@/components/Dialog/FormDialog.vue";
|
import {
|
addRepair,
|
editRepair,
|
getRepairById,
|
getRepairFileList,
|
uploadRepairFile,
|
deleteRepairFile,
|
} from "@/api/equipmentManagement/repair";
|
import { REPAIR_FILE_TYPE_PROBLEM, isProblemRepairFile } from "@/api/equipmentManagement/repairFileType.js";
|
import { ElMessage } from "element-plus";
|
import { UploadFilled, Close } from "@element-plus/icons-vue";
|
import dayjs from "dayjs";
|
import useFormData from "@/hooks/useFormData";
|
import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
|
import useUserStore from "@/store/modules/user";
|
import { userListNoPage } from "@/api/system/user.js";
|
|
defineOptions({
|
name: "设备报修弹窗",
|
});
|
|
const emits = defineEmits(["ok"]);
|
|
const id = ref();
|
const visible = ref(false);
|
const loading = ref(false);
|
|
const userStore = useUserStore();
|
const deviceOptions = ref([]);
|
const userList = ref([]);
|
/** 待提交的新增设备问题图(确认保存后上传) */
|
const attachmentFileList = ref([]);
|
/** 编辑时:已存在的设备问题图(服务端) */
|
const existingProblemImages = ref([]);
|
/** 编辑时:标记待删除的图片 id,仅点「确认」后调接口删除 */
|
const pendingRemoveProblemIds = ref([]);
|
|
const displayedExistingProblems = computed(() =>
|
existingProblemImages.value.filter((img) => !pendingRemoveProblemIds.value.includes(img.id))
|
);
|
|
const getFileAccessUrlForModal = (file = {}) => {
|
let raw = file?.link || file?.url || "";
|
if (!raw) return "";
|
if (String(raw).startsWith("http")) return raw;
|
const javaApi = getCurrentInstance()?.proxy?.javaApi || "";
|
let fileUrl = raw;
|
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/, "/profile");
|
if (!fileUrl.startsWith("http")) {
|
if (!fileUrl.startsWith("/")) fileUrl = "/" + fileUrl;
|
fileUrl = javaApi + fileUrl;
|
}
|
return fileUrl;
|
};
|
|
const markProblemRemove = (fileId) => {
|
if (!pendingRemoveProblemIds.value.includes(fileId)) {
|
pendingRemoveProblemIds.value.push(fileId);
|
}
|
};
|
|
const previewProblem = (url) => {
|
window.open(url, "_blank");
|
};
|
|
const loadExistingProblemImages = async (repairId) => {
|
existingProblemImages.value = [];
|
pendingRemoveProblemIds.value = [];
|
if (!repairId) return;
|
try {
|
const res = await getRepairFileList(repairId);
|
const list = Array.isArray(res?.data) ? res.data : [];
|
existingProblemImages.value = list
|
.filter((item) => isProblemRepairFile(item.type))
|
.map((item) => ({
|
id: item.id,
|
name: item.name,
|
url: getFileAccessUrlForModal(item),
|
}));
|
} catch {
|
existingProblemImages.value = [];
|
}
|
};
|
|
const resetProblemAttachmentState = () => {
|
existingProblemImages.value = [];
|
pendingRemoveProblemIds.value = [];
|
};
|
const ATTACH_MAX_MB = 50;
|
|
const loadDeviceName = async () => {
|
const { data } = await getDeviceLedger();
|
deviceOptions.value = data;
|
};
|
|
const loadUserList = async () => {
|
const res = await userListNoPage();
|
userList.value = Array.isArray(res?.data) ? res.data : [];
|
};
|
|
/** 报修人固定为当前登录用户(不可改) */
|
const syncRepairNameToLoginUser = () => {
|
form.repairName = userStore.nickName || "";
|
};
|
|
const beforeAttachmentUpload = (rawFile) => {
|
const okType = ["image/jpeg", "image/jpg", "image/png"].includes(rawFile.type);
|
if (!okType) {
|
ElMessage.error("只能上传 png / jpg / jpeg 图片");
|
return false;
|
}
|
const maxBytes = ATTACH_MAX_MB * 1024 * 1024;
|
if (rawFile.size > maxBytes) {
|
ElMessage.error(`单个文件不能超过 ${ATTACH_MAX_MB}MB`);
|
return false;
|
}
|
return true;
|
};
|
|
const clearAttachmentQueue = () => {
|
attachmentFileList.value = [];
|
};
|
|
const applyPendingProblemDeletes = async () => {
|
for (const fileId of pendingRemoveProblemIds.value) {
|
const { code } = await deleteRepairFile(fileId);
|
if (code !== 200) {
|
throw new Error("删除设备问题图片失败");
|
}
|
}
|
pendingRemoveProblemIds.value = [];
|
};
|
|
const uploadQueuedRepairImages = async (repairId) => {
|
const rows = attachmentFileList.value || [];
|
const files = rows.map((f) => f.raw).filter(Boolean);
|
if (!files.length || repairId == null) return;
|
for (const file of files) {
|
const fd = new FormData();
|
fd.append("file", file);
|
fd.append("deviceRepairId", String(repairId));
|
fd.append("fileType", String(REPAIR_FILE_TYPE_PROBLEM));
|
const res = await uploadRepairFile(fd);
|
if (res.code !== 200) {
|
throw new Error(res.msg || "附件上传失败");
|
}
|
}
|
};
|
|
const { form, resetForm } = useFormData({
|
deviceLedgerId: undefined, // 设备Id
|
deviceName: undefined, // 设备名称
|
deviceModel: undefined, // 规格型号
|
repairTime: dayjs().format("YYYY-MM-DD"), // 报修日期,默认当天
|
repairName: userStore.nickName, // 报修人
|
acceptanceName: undefined, // 验收人
|
maintenanceName: undefined, // 维修人(可选,与列表维修人同源)
|
remark: undefined, // 故障现象
|
status: 0, // 报修状态
|
});
|
|
const setDeviceModel = (deviceId) => {
|
const option = deviceOptions.value.find((item) => item.id === deviceId);
|
form.deviceModel = option?.deviceModel;
|
};
|
|
const setForm = (data) => {
|
form.deviceLedgerId = data.deviceLedgerId;
|
form.deviceName = data.deviceName;
|
form.deviceModel = data.deviceModel;
|
form.repairTime = data.repairTime;
|
form.acceptanceName = data.acceptanceName;
|
form.maintenanceName = data.maintenanceName;
|
form.remark = data.remark;
|
form.status = data.status;
|
syncRepairNameToLoginUser();
|
};
|
|
const sendForm = async () => {
|
syncRepairNameToLoginUser();
|
if (!form.deviceLedgerId) {
|
ElMessage.warning("请选择设备名称");
|
return;
|
}
|
if (!form.acceptanceName) {
|
ElMessage.warning("请选择验收人");
|
return;
|
}
|
if (!form.maintenanceName) {
|
ElMessage.warning("请选择或输入维修人");
|
return;
|
}
|
loading.value = true;
|
try {
|
let repairId = id.value ? unref(id) : undefined;
|
if (repairId) {
|
const { code } = await editRepair({ id: repairId, ...form });
|
if (code !== 200) return;
|
await applyPendingProblemDeletes();
|
} else {
|
const res = await addRepair(form);
|
if (res.code !== 200) return;
|
repairId = res.data;
|
if (repairId == null && attachmentFileList.value.length) {
|
ElMessage.error("保存成功但未返回报修单编号,无法上传附件");
|
return;
|
}
|
}
|
if (attachmentFileList.value.length && repairId != null) {
|
await uploadQueuedRepairImages(repairId);
|
}
|
ElMessage.success(`${id.value ? "编辑" : "新增"}报修成功`);
|
clearAttachmentQueue();
|
resetProblemAttachmentState();
|
visible.value = false;
|
emits("ok");
|
} catch (e) {
|
ElMessage.error(e?.message || "保存或上传附件失败");
|
} finally {
|
loading.value = false;
|
}
|
};
|
|
const handleCancel = () => {
|
resetForm();
|
syncRepairNameToLoginUser();
|
clearAttachmentQueue();
|
resetProblemAttachmentState();
|
visible.value = false;
|
};
|
|
const handleClose = () => {
|
resetForm();
|
syncRepairNameToLoginUser();
|
clearAttachmentQueue();
|
resetProblemAttachmentState();
|
visible.value = false;
|
};
|
|
const openAdd = async () => {
|
id.value = undefined;
|
clearAttachmentQueue();
|
resetProblemAttachmentState();
|
visible.value = true;
|
await nextTick();
|
await Promise.all([loadDeviceName(), loadUserList()]);
|
syncRepairNameToLoginUser();
|
};
|
|
const openEdit = async (editId) => {
|
clearAttachmentQueue();
|
resetProblemAttachmentState();
|
const { data } = await getRepairById(editId);
|
id.value = editId;
|
visible.value = true;
|
await nextTick();
|
await Promise.all([loadDeviceName(), loadUserList(), loadExistingProblemImages(editId)]);
|
setForm(data);
|
};
|
|
defineExpose({
|
openAdd,
|
openEdit,
|
});
|
</script>
|
|
<style lang="scss" scoped>
|
.repair-attachment-upload {
|
width: 100%;
|
:deep(.el-upload) {
|
width: 100%;
|
}
|
:deep(.el-upload-dragger) {
|
width: 100%;
|
padding: 24px 16px;
|
}
|
}
|
.repair-upload-icon {
|
font-size: 48px;
|
color: var(--el-color-primary);
|
margin-bottom: 8px;
|
}
|
|
.problem-existing-wrap {
|
display: flex;
|
flex-wrap: wrap;
|
gap: 10px;
|
margin-bottom: 12px;
|
}
|
|
.problem-thumb {
|
position: relative;
|
width: 88px;
|
height: 88px;
|
border-radius: 6px;
|
overflow: hidden;
|
border: 1px solid var(--el-border-color);
|
}
|
|
.problem-thumb-img {
|
width: 100%;
|
height: 100%;
|
cursor: pointer;
|
}
|
|
.problem-thumb-remove {
|
position: absolute;
|
top: 2px;
|
right: 2px;
|
font-size: 16px;
|
color: #fff;
|
background: rgba(0, 0, 0, 0.45);
|
border-radius: 50%;
|
padding: 2px;
|
cursor: pointer;
|
}
|
</style>
|