| | |
| | | <div class="actions"> |
| | | <el-text class="mx-1" size="large">设备报修</el-text> |
| | | <div> |
| | | <el-button |
| | | type="primary" |
| | | icon="Plus" |
| | | :disabled="multipleList.length !== 1" |
| | | @click="addMaintain" |
| | | > |
| | | 新增维修 |
| | | </el-button> |
| | | <el-button type="success" icon="Van" @click="addRepair"> |
| | | 新增报修 |
| | | </el-button> |
| | | <el-button @click="handleOut"> |
| | | 导出 |
| | | </el-button> |
| | | <el-button |
| | | type="danger" |
| | | icon="Delete" |
| | | :disabled="multipleList.length <= 0" |
| | | :disabled="multipleList.length <= 0 || hasFinishedStatus" |
| | | @click="delRepairByIds(multipleList.map((item) => item.id))" |
| | | > |
| | | 批量删除 |
| | |
| | | </div> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | isSelection |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | | rowKey="id" |
| | | isSelection |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @selection-change="handleSelectionChange" |
| | | @pagination="changePage" |
| | | @selection-change="handleSelectionChange" |
| | | @pagination="changePage" |
| | | > |
| | | <template #statusRef="{ row }"> |
| | | <el-tag v-if="row.status === 2" type="danger">失败</el-tag> |
| | | <el-tag v-if="row.status === 1" type="success">完结</el-tag> |
| | | <el-tag v-if="row.status === 0" type="danger">待维修</el-tag> |
| | | <el-tag v-if="row.status === 0" type="warning">待维修</el-tag> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button |
| | | type="primary" |
| | | text |
| | | icon="editPen" |
| | | link |
| | | :disabled="row.status === 1" |
| | | @click="editRepair(row.id)" |
| | | > |
| | | 编辑 |
| | | </el-button> |
| | | <el-button |
| | | type="info" |
| | | link |
| | | :disabled="row.status === 1" |
| | | @click="openAttachment(row)" |
| | | > |
| | | 附件 |
| | | </el-button> |
| | | <el-button |
| | | type="success" |
| | | link |
| | | :disabled="row.status === 1" |
| | | @click="addMaintain(row)" |
| | | > |
| | | 维修 |
| | | </el-button> |
| | | <el-button |
| | | type="danger" |
| | | text |
| | | icon="delete" |
| | | link |
| | | :disabled="row.status === 1" |
| | | @click="delRepairByIds(row.id)" |
| | | > |
| | | 删除 |
| | |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | <RepairModal ref="repairModalRef" @ok="getTableData" /> |
| | | <MaintainModal ref="maintainModalRef" @ok="getTableData" /> |
| | | <RepairModal ref="repairModalRef" @ok="getTableData"/> |
| | | <MaintainModal ref="maintainModalRef" @ok="getTableData"/> |
| | | |
| | | <el-dialog v-model="attachment.visible" title="附件" width="860px" @closed="onAttachmentClosed"> |
| | | <div v-loading="attachment.loading" class="attachment-wrap"> |
| | | <div class="attachment-actions"> |
| | | <el-upload |
| | | v-model:file-list="attachment.fileList" |
| | | :action="upload.url" |
| | | multiple |
| | | ref="attachmentFileUpload" |
| | | auto-upload |
| | | :headers="upload.headers" |
| | | :data="{ deviceRepairId: attachment.deviceRepairId }" |
| | | :before-upload="handleAttachmentBeforeUpload" |
| | | :on-error="handleAttachmentUploadError" |
| | | :on-success="handleAttachmentUploadSuccess" |
| | | :on-preview="handleAttachmentPreview" |
| | | :on-remove="handleAttachmentRemove" |
| | | list-type="picture-card" |
| | | :limit="9" |
| | | accept="image/png,image/jpeg,image/jpg" |
| | | > |
| | | + |
| | | </el-upload> |
| | | </div> |
| | | |
| | | <el-empty v-if="!attachment.loading && attachment.files.length === 0" description="暂无附件" /> |
| | | </div> |
| | | <template #footer> |
| | | <el-button @click="attachment.visible = false">关闭</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <el-dialog |
| | | v-model="attachment.previewVisible" |
| | | title="图片预览" |
| | | width="70%" |
| | | append-to-body |
| | | > |
| | | <div class="attachment-preview-wrap"> |
| | | <img |
| | | v-if="attachment.previewUrl" |
| | | :src="attachment.previewUrl" |
| | | alt="preview" |
| | | class="attachment-preview-img" |
| | | /> |
| | | </div> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { usePaginationApi } from "@/hooks/usePaginationApi"; |
| | | import { getRepairPage, delRepair } from "@/api/equipmentManagement/repair"; |
| | | import { onMounted } from "vue"; |
| | | import { onMounted, getCurrentInstance, computed } from "vue"; |
| | | import {usePaginationApi} from "@/hooks/usePaginationApi"; |
| | | import { |
| | | getRepairPage, |
| | | delRepair, |
| | | getRepairFileList, |
| | | deleteRepairFile, |
| | | } from "@/api/equipmentManagement/repair"; |
| | | import RepairModal from "./Modal/RepairModal.vue"; |
| | | import { ElMessageBox, ElMessage } from "element-plus"; |
| | | import {ElMessageBox, ElMessage} from "element-plus"; |
| | | import dayjs from "dayjs"; |
| | | import MaintainModal from "./Modal/MaintainModal.vue"; |
| | | import { getToken } from "@/utils/auth"; |
| | | |
| | | defineOptions({ |
| | | name: "设备报修", |
| | | }); |
| | | |
| | | const {proxy} = getCurrentInstance(); |
| | | const javaApi = proxy?.javaApi || ""; |
| | | |
| | | // 模态框实例 |
| | | const repairModalRef = ref(); |
| | |
| | | |
| | | // 表格多选框选中项 |
| | | const multipleList = ref([]); |
| | | |
| | | const attachment = reactive({ |
| | | visible: false, |
| | | loading: false, |
| | | deviceRepairId: undefined, |
| | | files: [], |
| | | fileList: [], |
| | | previewVisible: false, |
| | | previewUrl: "", |
| | | }); |
| | | |
| | | const getFileAccessUrl = (file = {}) => { |
| | | if (file?.link) { |
| | | if (String(file.link).startsWith('http')) return file.link; |
| | | return normalizeFileUrl(file.link); |
| | | } |
| | | return normalizeFileUrl(file?.url || ''); |
| | | }; |
| | | |
| | | const normalizeFileUrl = (rawUrl = '') => { |
| | | let fileUrl = rawUrl || ''; |
| | | |
| | | if (fileUrl && 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 attachmentUploadAction = "/device/repair/uploadFile"; |
| | | const upload = reactive({ |
| | | url: import.meta.env.VITE_APP_BASE_API + attachmentUploadAction, |
| | | headers: { |
| | | Authorization: "Bearer " + getToken(), |
| | | }, |
| | | }); |
| | | |
| | | // 表格钩子 |
| | | const { |
| | |
| | | resetFilters, |
| | | onCurrentChange, |
| | | } = usePaginationApi( |
| | | getRepairPage, |
| | | { |
| | | searchText: undefined, |
| | | }, |
| | | [ |
| | | getRepairPage, |
| | | { |
| | | label: "设备名称", |
| | | align: "center", |
| | | prop: "deviceName", |
| | | deviceName: undefined, |
| | | deviceModel: undefined, |
| | | remark: undefined, |
| | | maintenanceName: undefined, |
| | | repairTimeStr: undefined, |
| | | maintenanceTimeStr: undefined, |
| | | }, |
| | | { |
| | | label: "规格型号", |
| | | align: "center", |
| | | prop: "deviceModel", |
| | | }, |
| | | { |
| | | label: "报修日期", |
| | | align: "center", |
| | | prop: "repairTime", |
| | | formatData: (cell) => dayjs(cell).format("YYYY-MM-DD"), |
| | | }, |
| | | { |
| | | label: "报修人", |
| | | align: "center", |
| | | prop: "repairName", |
| | | }, |
| | | { |
| | | label: "故障现象", |
| | | align: "center", |
| | | prop: "remark", |
| | | }, |
| | | { |
| | | label: "维修人", |
| | | align: "center", |
| | | prop: "maintenanceName", |
| | | }, |
| | | { |
| | | label: "维修结果", |
| | | align: "center", |
| | | prop: "maintenanceResult", |
| | | }, |
| | | { |
| | | label: "维修日期", |
| | | align: "center", |
| | | prop: "maintenanceTime", |
| | | formatData: (cell) => (cell ? dayjs(cell).format("YYYY-MM-DD") : ""), |
| | | }, |
| | | { |
| | | label: "状态", |
| | | align: "center", |
| | | prop: "status", |
| | | dataType: "slot", |
| | | slot: "statusRef", |
| | | }, |
| | | { |
| | | fixed: "right", |
| | | label: "操作", |
| | | dataType: "slot", |
| | | slot: "operation", |
| | | align: "center", |
| | | width: "200px", |
| | | }, |
| | | ] |
| | | [ |
| | | { |
| | | label: "设备名称", |
| | | align: "center", |
| | | prop: "deviceName", |
| | | }, |
| | | { |
| | | label: "规格型号", |
| | | align: "center", |
| | | prop: "deviceModel", |
| | | }, |
| | | { |
| | | label: "报修日期", |
| | | align: "center", |
| | | prop: "repairTime", |
| | | formatData: (cell) => dayjs(cell).format("YYYY-MM-DD"), |
| | | }, |
| | | { |
| | | label: "报修人", |
| | | align: "center", |
| | | prop: "repairName", |
| | | }, |
| | | { |
| | | label: "故障现象", |
| | | align: "center", |
| | | prop: "remark", |
| | | }, |
| | | { |
| | | label: "维修人", |
| | | align: "center", |
| | | prop: "maintenanceName", |
| | | }, |
| | | { |
| | | label: "维修结果", |
| | | align: "center", |
| | | prop: "maintenanceResult", |
| | | }, |
| | | { |
| | | label: "维修日期", |
| | | align: "center", |
| | | prop: "maintenanceTime", |
| | | formatData: (cell) => (cell ? dayjs(cell).format("YYYY-MM-DD") : ""), |
| | | }, |
| | | { |
| | | label: "状态", |
| | | align: "center", |
| | | prop: "status", |
| | | dataType: "slot", |
| | | slot: "statusRef", |
| | | }, |
| | | { |
| | | fixed: "right", |
| | | label: "操作", |
| | | dataType: "slot", |
| | | slot: "operation", |
| | | align: "center", |
| | | width: "300px", |
| | | }, |
| | | ] |
| | | ); |
| | | |
| | | // type === 1 维修 2报修间 |
| | | const handleDateChange = (value,type) => { |
| | | const handleDateChange = (value, type) => { |
| | | filters.maintenanceTimeStr = null |
| | | filters.c = null |
| | | if(type === 1){ |
| | | if (type === 1) { |
| | | if (value) { |
| | | filters.maintenanceTimeStr = dayjs(value).format("YYYY-MM-DD"); |
| | | } |
| | | }else{ |
| | | } else { |
| | | if (value) { |
| | | filters.repairTimeStr = dayjs(value).format("YYYY-MM-DD"); |
| | | } |
| | |
| | | multipleList.value = selectionList; |
| | | }; |
| | | |
| | | // 检查选中的记录中是否有完结状态的 |
| | | const hasFinishedStatus = computed(() => { |
| | | return multipleList.value.some(item => item.status === 1) |
| | | }) |
| | | |
| | | // 新增报修 |
| | | const addRepair = () => { |
| | | repairModalRef.value.openAdd(); |
| | |
| | | }; |
| | | |
| | | // 新增维修 |
| | | const addMaintain = () => { |
| | | const row = multipleList.value[0]; |
| | | const addMaintain = (row) => { |
| | | maintainModalRef.value.open(row.id, row); |
| | | }; |
| | | |
| | | const changePage = ({ page, limit }) => { |
| | | pagination.currentPage = page; |
| | | pagination.pageSize = limit; |
| | | onCurrentChange(page); |
| | | const changePage = ({page, limit}) => { |
| | | pagination.currentPage = page; |
| | | pagination.pageSize = limit; |
| | | onCurrentChange(page); |
| | | }; |
| | | |
| | | // 单行删除 |
| | | const delRepairByIds = async (ids) => { |
| | | // 检查是否有完结状态的记录 |
| | | const idsArray = Array.isArray(ids) ? ids : [ids]; |
| | | const hasFinished = idsArray.some(id => { |
| | | const record = dataList.value.find(item => item.id === id); |
| | | return record && record.status === 1; |
| | | }); |
| | | |
| | | if (hasFinished) { |
| | | ElMessage.warning('不能删除状态为完结的记录'); |
| | | return; |
| | | } |
| | | |
| | | ElMessageBox.confirm("确认删除报修数据, 此操作不可逆?", "警告", { |
| | | confirmButtonText: "确定", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }).then(async () => { |
| | | const { code } = await delRepair(ids); |
| | | const {code} = await delRepair(ids); |
| | | if (code === 200) { |
| | | ElMessage.success("删除成功"); |
| | | getTableData(); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // 导出 |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/device/repair/export", {}, "设备报修.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.info("已取消"); |
| | | }); |
| | | }; |
| | | |
| | | const openAttachment = async (row) => { |
| | | attachment.fileList = []; |
| | | attachment.visible = true; |
| | | attachment.deviceRepairId = row?.id; |
| | | await refreshAttachmentList(); |
| | | }; |
| | | |
| | | const refreshAttachmentList = async () => { |
| | | if (!attachment.deviceRepairId) return; |
| | | attachment.loading = true; |
| | | try { |
| | | const res = await getRepairFileList(attachment.deviceRepairId); |
| | | attachment.files = Array.isArray(res?.data) ? res.data : []; |
| | | attachment.fileList = attachment.files.map((item) => ({ |
| | | id: item.id, |
| | | name: item.name, |
| | | url: getFileAccessUrl(item), |
| | | })); |
| | | } finally { |
| | | attachment.loading = false; |
| | | } |
| | | }; |
| | | |
| | | const onAttachmentClosed = () => { |
| | | attachment.loading = false; |
| | | attachment.deviceRepairId = undefined; |
| | | attachment.files = []; |
| | | attachment.fileList = []; |
| | | attachment.previewVisible = false; |
| | | attachment.previewUrl = ""; |
| | | }; |
| | | |
| | | const handleAttachmentBeforeUpload = (file) => { |
| | | const isImage = ["image/png", "image/jpeg", "image/jpg"].includes(file.type); |
| | | if (!isImage) { |
| | | ElMessage.error("只能上传 png/jpg/jpeg 图片"); |
| | | return false; |
| | | } |
| | | return true; |
| | | }; |
| | | |
| | | const handleAttachmentUploadSuccess = async (res) => { |
| | | if (res?.code === 200) { |
| | | ElMessage.success("上传成功"); |
| | | await refreshAttachmentList(); |
| | | } |
| | | }; |
| | | |
| | | const handleAttachmentUploadError = () => { |
| | | ElMessage.error("上传失败"); |
| | | }; |
| | | |
| | | const handleAttachmentPreview = (file) => { |
| | | const rawUrl = file?.url || file?.response?.data?.link || file?.response?.data?.url || ""; |
| | | if (!rawUrl) { |
| | | ElMessage.warning("图片地址无效,无法预览"); |
| | | return; |
| | | } |
| | | attachment.previewUrl = normalizeFileUrl(rawUrl); |
| | | attachment.previewVisible = true; |
| | | }; |
| | | |
| | | const handleAttachmentRemove = async (file) => { |
| | | // 仅移除前端未入库文件时,不调用删除接口 |
| | | const matched = attachment.files.find((item) => item.id === file?.id) |
| | | || attachment.files.find((item) => item.name === file?.name); |
| | | if (!matched) return; |
| | | try { |
| | | await confirmDeleteAttachment(matched); |
| | | } finally { |
| | | // 取消删除时,el-upload 已先移除,刷新一次保持与后端一致 |
| | | await refreshAttachmentList(); |
| | | } |
| | | }; |
| | | |
| | | const confirmDeleteAttachment = (fileRow) => { |
| | | return ElMessageBox.confirm("确认删除该附件?", "警告", { |
| | | confirmButtonText: "确定", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }).then(async () => { |
| | | const { code } = await deleteRepairFile(fileRow.id); |
| | | if (code === 200) { |
| | | ElMessage.success("删除成功"); |
| | | await refreshAttachmentList(); |
| | | } |
| | | }); |
| | | }; |
| | |
| | | .table_list { |
| | | margin-top: unset; |
| | | } |
| | | |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .attachment-wrap { |
| | | min-height: 240px; |
| | | } |
| | | |
| | | .attachment-actions { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .attachment-preview-wrap { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | min-height: 360px; |
| | | } |
| | | |
| | | .attachment-preview-img { |
| | | max-width: 100%; |
| | | max-height: 70vh; |
| | | object-fit: contain; |
| | | } |
| | | |
| | | </style> |