| | |
| | | :label="tab.label" |
| | | :value="tab.name" /> |
| | | </el-radio-group> |
| | | <!-- 操作按钮区 --> |
| | | <el-space v-if="activeRadio !== 'task'"> |
| | | <el-button type="primary" |
| | | :icon="Plus" |
| | |
| | | <el-button @click="handleOut">导出</el-button> |
| | | </el-space> |
| | | </div> |
| | | <div> |
| | | |
| | | <!-- 巡检任务 --> |
| | | <div v-show="activeRadio === 'taskManage'"> |
| | | <PIMTable :table-loading="tableLoading" |
| | | :table-data="tableData" |
| | | :column="tableColumns" |
| | |
| | | total: total, |
| | | layout: 'total, sizes, prev, pager, next, jumper' |
| | | }" |
| | | :table-style="{ width: '100%', height: 'calc(100vh - 23em)' }"> |
| | | height="calc(100vh - 23em)" |
| | | :table-style="{ width: '100%' }"> |
| | | <template #inspector="{ row }"> |
| | | <div class="person-tags"> |
| | | <!-- 调试信息,上线时删除 --> |
| | | <!-- {{ console.log('inspector data:', row.inspector) }} --> |
| | | <template v-if="row.inspector && row.inspector.length > 0"> |
| | | <el-tag v-for="(person, index) in row.inspector" |
| | | :key="index" |
| | |
| | | {{ person }} |
| | | </el-tag> |
| | | </template> |
| | | <span v-else |
| | | class="no-data">--</span> |
| | | <span v-else class="no-data">--</span> |
| | | </div> |
| | | </template> |
| | | <template #isEnabled="{ row }"> |
| | | <el-tag :type="row.isEnabled === 1 ? 'success' : 'danger'" |
| | | size="small"> |
| | | {{ row.isEnabled == 1 ? '是' : '否' }} |
| | | </el-tag> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | |
| | | <!-- 巡检记录 - 日期分组折叠 --> |
| | | <div v-show="activeRadio === 'task'"> |
| | | <el-table |
| | | :data="groupedInspectionList" |
| | | border |
| | | v-loading="tableLoading" |
| | | :expand-row-keys="inspectionExpandedRowKeys" |
| | | :row-key="(row) => row.date" |
| | | @expand-change="onInspectionExpandChange" |
| | | height="calc(100vh - 23em)" |
| | | > |
| | | <el-table-column type="expand"> |
| | | <template #default="props"> |
| | | <el-table :data="props.row.children" border row-key="id"> |
| | | <el-table-column label="巡检任务名称" prop="taskName" align="center" min-width="180" /> |
| | | <el-table-column label="巡检项目" prop="inspectionProject" align="center" min-width="160" /> |
| | | <el-table-column label="巡检人" align="center" min-width="150"> |
| | | <template #default="{ row }"> |
| | | <template v-if="row.inspector && row.inspector.length > 0"> |
| | | <el-tag v-for="(p, i) in row.inspector" :key="i" size="small" type="primary" class="person-tag">{{ p }}</el-tag> |
| | | </template> |
| | | <span v-else>--</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="巡检状态" align="center" width="100"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="row.status === 1 ? 'success' : 'warning'" size="small"> |
| | | {{ row.status === 1 ? '已完成' : '待巡检' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="巡检结果" prop="inspectionResult" align="center" min-width="120" /> |
| | | <el-table-column label="备注" prop="remarks" align="center" min-width="150" /> |
| | | <el-table-column label="异常描述" prop="abnormalDescription" align="center" min-width="150" /> |
| | | <el-table-column label="操作" align="center" width="200"> |
| | | <template #default="{ row }"> |
| | | <el-button type="primary" link @click="openUploadDialog(row)">上传</el-button> |
| | | <el-button type="success" link @click="viewFile(row)">查看附件</el-button> |
| | | <el-button type="warning" link v-if="row.status === 0" @click="handleSingleComplete(row)">巡检完成</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="巡检日期" prop="date" align="center" /> |
| | | <el-table-column label="巡检记录数" prop="count" align="center" /> |
| | | <el-table-column label="待巡检数" align="center" width="120"> |
| | | <template #default="{ row }"> |
| | | {{ row.children.filter(c => c.status === 0).length }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" align="center" width="200"> |
| | | <template #default="{ row }"> |
| | | <el-button |
| | | type="primary" |
| | | size="small" |
| | | :disabled="!row.children.some(c => c.status === 0)" |
| | | @click="handleBatchInspection(row.date)" |
| | | > |
| | | 一键巡检完成 |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <Pagination |
| | | v-show="total > 0" |
| | | :total="total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="pageNum" |
| | | :limit="pageSize" |
| | | @pagination="handlePagination" |
| | | /> |
| | | </div> |
| | | </el-card> |
| | | <form-dia ref="formDia" |
| | | @closeDia="handleQuery"></form-dia> |
| | | <view-files ref="viewFiles"></view-files> |
| | | <upload-files ref="uploadFiles" |
| | | @success="handleQuery" |
| | | @closeDia="handleQuery"></upload-files> |
| | | |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Delete, Plus } from "@element-plus/icons-vue"; |
| | | import { onMounted, ref, reactive, getCurrentInstance, nextTick } from "vue"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { onMounted, ref, reactive, getCurrentInstance, nextTick, computed } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | // 组件引入 |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | import FormDia from "@/views/equipmentManagement/inspectionManagement/components/formDia.vue"; |
| | | import UploadFiles from "@/views/equipmentManagement/inspectionManagement/components/uploadFiles.vue"; |
| | | import ViewFiles from "@/views/equipmentManagement/inspectionManagement/components/viewFiles.vue"; |
| | | |
| | | // 接口引入 |
| | | import { |
| | | delTimingTask, |
| | | inspectionTaskList, |
| | | timingTaskList, |
| | | completeInspection, |
| | | batchCompleteInspection, |
| | | } from "@/api/inspectionManagement/index.js"; |
| | | import Pagination from "@/components/PIMTable/Pagination.vue"; |
| | | |
| | | // 全局变量 |
| | | const { proxy } = getCurrentInstance(); |
| | | const formDia = ref(); |
| | | const viewFiles = ref(); |
| | | const uploadFiles = ref(); |
| | | |
| | | // 查询参数 |
| | | const queryParams = reactive({ |
| | | taskName: "", |
| | | }); |
| | | const queryParams = reactive({ taskName: "" }); |
| | | |
| | | // 单选框配置 |
| | | const activeRadio = ref("taskManage"); |
| | | const radios = reactive([ |
| | | { name: "taskManage", label: "定时任务管理" }, |
| | | { name: "task", label: "定时任务记录" }, |
| | | { name: "taskManage", label: "巡检任务" }, |
| | | { name: "task", label: "巡检记录" }, |
| | | ]); |
| | | |
| | | // 表格数据 |
| | | const selectedRows = ref([]); |
| | | const tableData = ref([]); |
| | | const operationsArr = ref([]); |
| | |
| | | const tableLoading = ref(false); |
| | | const total = ref(0); |
| | | const pageNum = ref(1); |
| | | const pageSize = ref(10); |
| | | const pageSize = ref(100); |
| | | |
| | | // 列配置 |
| | | const columns = ref([ |
| | | { prop: "taskName", label: "巡检任务名称", minWidth: 160 }, |
| | | { prop: "remarks", label: "备注", minWidth: 150 }, |
| | | { prop: "inspector", label: "执行巡检人", minWidth: 150, slot: "inspector" }, |
| | | { prop: "taskName", label: "巡检任务名称", minWidth: 200 }, |
| | | { prop: "inspectionProject", label: "巡检项目", minWidth: 180 }, |
| | | { prop: "remarks", label: "备注", minWidth: 180 }, |
| | | { prop: "inspector", label: "执行巡检人", minWidth: 180, slot: "inspector" }, |
| | | { prop: "isEnabled", label: "是否启用", minWidth: 100, dataType: "slot", slot: "isEnabled" }, |
| | | { |
| | | prop: "frequencyType", |
| | | label: "频次", |
| | | minWidth: 150, |
| | | // formatter: (_, __, val) => ({ |
| | | // DAILY: "每日", |
| | | // WEEKLY: "每周", |
| | | // MONTHLY: "每月", |
| | | // QUARTERLY: "季度" |
| | | // }[val] || "") |
| | | formatData: params => { |
| | | return params === "DAILY" |
| | | ? "每日" |
| | | : params === "WEEKLY" |
| | | ? "每周" |
| | | : params === "MONTHLY" |
| | | ? "每月" |
| | | : params === "QUARTERLY" |
| | | ? "季度" |
| | | : ""; |
| | | }, |
| | | prop: "frequencyType", label: "频次", minWidth: 120, |
| | | formatData: params => ({ DAILY: "每日", WEEKLY: "每周", MONTHLY: "每月", QUARTERLY: "季度" }[params] || ""), |
| | | }, |
| | | { |
| | | prop: "frequencyDetail", |
| | | label: "开始日期与时间", |
| | | minWidth: 150, |
| | | prop: "frequencyDetail", label: "开始日期与时间", minWidth: 200, |
| | | formatter: (row, column, cellValue) => { |
| | | // 先判断是否是字符串 |
| | | if (typeof cellValue !== "string") return ""; |
| | | let val = cellValue; |
| | | const replacements = { |
| | | MON: "周一", |
| | | TUE: "周二", |
| | | WED: "周三", |
| | | THU: "周四", |
| | | FRI: "周五", |
| | | SAT: "周六", |
| | | SUN: "周日", |
| | | }; |
| | | // 使用正则一次性替换所有匹配项 |
| | | return val.replace( |
| | | /MON|TUE|WED|THU|FRI|SAT|SUN/g, |
| | | match => replacements[match] |
| | | ); |
| | | return cellValue.replace(/MON|TUE|WED|THU|FRI|SAT|SUN/g, match => ({ MON: "周一", TUE: "周二", WED: "周三", THU: "周四", FRI: "周五", SAT: "周六", SUN: "周日" }[match])); |
| | | }, |
| | | }, |
| | | { prop: "registrant", label: "登记人", minWidth: 100 }, |
| | | { prop: "createTime", label: "登记日期", minWidth: 100 }, |
| | | { prop: "registrant", label: "登记人", minWidth: 120 }, |
| | | { |
| | | prop: "createTime", label: "登记日期", minWidth: 180, |
| | | formatData: cell => { if (!cell) return "-"; try { return dayjs(cell).format("YYYY-MM-DD HH:mm:ss"); } catch { return cell; } }, |
| | | }, |
| | | { prop: "abnormalDescription", label: "异常描述", minWidth: 150 }, |
| | | ]); |
| | | |
| | | // 操作列配置 |
| | | const getOperationColumn = operations => { |
| | | if (!operations || operations.length === 0) return null; |
| | | |
| | | const operationConfig = { |
| | | label: "操作", |
| | | width: 130, |
| | | fixed: "right", |
| | | dataType: "action", |
| | | operation: operations |
| | | .map(op => { |
| | | switch (op) { |
| | | case "edit": |
| | | return { |
| | | name: "编辑", |
| | | clickFun: handleAdd, |
| | | color: "#409EFF", |
| | | }; |
| | | case "viewFile": |
| | | return { |
| | | name: "查看附件", |
| | | clickFun: viewFile, |
| | | color: "#67C23A", |
| | | }; |
| | | default: |
| | | return null; |
| | | } |
| | | }) |
| | | .filter(Boolean), |
| | | return { |
| | | label: "操作", width: operations.length > 1 ? 180 : 130, fixed: "right", align: "center", dataType: "action", |
| | | operation: operations.map(op => { |
| | | switch (op) { |
| | | case "edit": return { name: "编辑", clickFun: handleAdd, color: "#409EFF" }; |
| | | case "upload": return { name: "上传", clickFun: openUploadDialog, color: "#409EFF" }; |
| | | case "viewFile": return { name: "查看附件", clickFun: viewFile, color: "#67C23A" }; |
| | | default: return null; |
| | | } |
| | | }).filter(Boolean), |
| | | }; |
| | | |
| | | return operationConfig; |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | radioChange("taskManage"); |
| | | }); |
| | | // 巡检记录 - 日期分组 |
| | | const inspectionExpandedRowKeys = ref([]); |
| | | |
| | | // 单选变化 |
| | | const groupInspectionByDate = (list) => { |
| | | const map = {}; |
| | | list.forEach(item => { |
| | | const date = item.dateStr || (item.createTime ? dayjs(item.createTime).format('YYYY-MM-DD') : '未知日期'); |
| | | if (!map[date]) map[date] = []; |
| | | map[date].push(item); |
| | | }); |
| | | return Object.entries(map).map(([date, children]) => ({ date, children, count: children.length })); |
| | | }; |
| | | |
| | | const groupedInspectionList = computed(() => groupInspectionByDate(tableData.value)); |
| | | |
| | | const onInspectionExpandChange = (row, expandedRows) => { |
| | | inspectionExpandedRowKeys.value = expandedRows.map(r => r.date); |
| | | }; |
| | | |
| | | // 单条巡检完成 |
| | | const handleSingleComplete = async (row) => { |
| | | try { |
| | | await ElMessageBox.confirm('确认将该巡检记录标记为完成?', '提示', { type: 'warning' }); |
| | | const res = await completeInspection(row.id); |
| | | if (res.code === 200) { |
| | | ElMessage.success('巡检完成'); |
| | | getList(); |
| | | } else { |
| | | ElMessage.error(res.msg || '操作失败'); |
| | | } |
| | | } catch { /* 取消 */ } |
| | | }; |
| | | |
| | | // 一键巡检完成 |
| | | const handleBatchInspection = async (dateStr) => { |
| | | try { |
| | | await ElMessageBox.confirm(`确认将 ${dateStr} 的所有待巡检记录标记为完成?`, '提示', { type: 'warning' }); |
| | | const res = await batchCompleteInspection(dateStr); |
| | | if (res.code === 200) { |
| | | ElMessage.success(res.msg || '批量完成成功'); |
| | | getList(); |
| | | } else { |
| | | ElMessage.error(res.msg || '操作失败'); |
| | | } |
| | | } catch { /* 取消 */ } |
| | | }; |
| | | |
| | | onMounted(() => { radioChange("taskManage"); }); |
| | | |
| | | const radioChange = value => { |
| | | inspectionExpandedRowKeys.value = []; |
| | | if (value === "taskManage") { |
| | | const operationColumn = getOperationColumn(["edit"]); |
| | | tableColumns.value = [ |
| | | ...columns.value, |
| | | ...(operationColumn ? [operationColumn] : []), |
| | | ]; |
| | | tableColumns.value = [...columns.value, ...(operationColumn ? [operationColumn] : [])]; |
| | | operationsArr.value = ["edit"]; |
| | | } else if (value === "task") { |
| | | const operationColumn = getOperationColumn(["viewFile"]); |
| | | tableColumns.value = [ |
| | | ...columns.value, |
| | | ...(operationColumn ? [operationColumn] : []), |
| | | ]; |
| | | operationsArr.value = ["viewFile"]; |
| | | const operationColumn = getOperationColumn(["upload", "viewFile"]); |
| | | const taskColumns = columns.value.filter(col => col.prop !== "isEnabled"); |
| | | tableColumns.value = [...taskColumns, ...(operationColumn ? [operationColumn] : [])]; |
| | | operationsArr.value = ["upload", "viewFile"]; |
| | | } |
| | | pageNum.value = 1; |
| | | pageSize.value = 10; |
| | | getList(); |
| | | }; |
| | | |
| | | // 查询操作 |
| | | const handleQuery = () => { |
| | | pageNum.value = 1; |
| | | pageSize.value = 10; |
| | | getList(); |
| | | }; |
| | | // 分页处理 |
| | | const handlePagination = val => { |
| | | pageNum.value = val.page; |
| | | pageSize.value = val.limit; |
| | | getList(); |
| | | }; |
| | | // 获取列表数据 |
| | | const handleQuery = () => { pageNum.value = 1; getList(); }; |
| | | |
| | | const handlePagination = val => { pageNum.value = val.page; pageSize.value = val.limit; getList(); }; |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | |
| | | const params = { |
| | | ...queryParams, |
| | | size: pageSize.value, |
| | | current: pageNum.value, |
| | | }; |
| | | |
| | | let apiCall; |
| | | if (activeRadio.value === "task") { |
| | | apiCall = inspectionTaskList(params); |
| | | } else { |
| | | apiCall = timingTaskList(params); |
| | | } |
| | | |
| | | apiCall |
| | | .then(res => { |
| | | const rawData = res.data.records || []; |
| | | // 处理 inspector 字段,将字符串转换为数组(适用于所有情况) |
| | | tableData.value = rawData.map(item => { |
| | | const processedItem = { ...item }; |
| | | |
| | | // 处理 inspector 字段 |
| | | if (processedItem.inspector) { |
| | | if (typeof processedItem.inspector === "string") { |
| | | // 字符串按逗号分割 |
| | | processedItem.inspector = processedItem.inspector |
| | | .split(",") |
| | | .map(s => s.trim()) |
| | | .filter(s => s); |
| | | } else if (!Array.isArray(processedItem.inspector)) { |
| | | // 非数组转为数组 |
| | | processedItem.inspector = [processedItem.inspector]; |
| | | } |
| | | } else { |
| | | // 空值设为空数组 |
| | | processedItem.inspector = []; |
| | | const params = { ...queryParams, size: pageSize.value, current: pageNum.value }; |
| | | const apiCall = activeRadio.value === "task" ? inspectionTaskList(params) : timingTaskList(params); |
| | | apiCall.then(res => { |
| | | const rawData = res.data.records || []; |
| | | tableData.value = rawData.map(item => { |
| | | const processedItem = { ...item }; |
| | | if (processedItem.inspector) { |
| | | if (typeof processedItem.inspector === "string") { |
| | | processedItem.inspector = processedItem.inspector.split(",").map(s => s.trim()).filter(s => s); |
| | | } else if (!Array.isArray(processedItem.inspector)) { |
| | | processedItem.inspector = [processedItem.inspector]; |
| | | } |
| | | |
| | | return processedItem; |
| | | }); |
| | | total.value = res.data.total || 0; |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | // 重置查询 |
| | | const resetQuery = () => { |
| | | for (const key in queryParams) { |
| | | if (!["pageNum", "pageSize"].includes(key)) { |
| | | queryParams[key] = ""; |
| | | } |
| | | } |
| | | handleQuery(); |
| | | }; |
| | | |
| | | // 新增 / 编辑 |
| | | const handleAdd = row => { |
| | | const type = row ? "edit" : "add"; |
| | | nextTick(() => { |
| | | formDia.value?.openDialog(type, row); |
| | | }); |
| | | }; |
| | | |
| | | // 查看附件 |
| | | const viewFile = row => { |
| | | nextTick(() => { |
| | | viewFiles.value?.openDialog(row); |
| | | }); |
| | | }; |
| | | |
| | | // 删除操作 |
| | | const handleDelete = () => { |
| | | if (!selectedRows.value.length) { |
| | | proxy.$modal.msgWarning("请选择要删除的数据"); |
| | | return; |
| | | } |
| | | |
| | | const deleteIds = selectedRows.value.map(item => item.id); |
| | | |
| | | proxy.$modal |
| | | .confirm("是否确认删除所选数据项?") |
| | | .then(() => { |
| | | return delTimingTask(deleteIds); |
| | | }) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("删除成功"); |
| | | handleQuery(); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | // 多选变更 |
| | | const handleSelectionChange = selection => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | // 导出 |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | // 根据当前选中的标签页调用不同的导出接口 |
| | | if (activeRadio.value === "taskManage") { |
| | | // 定时任务管理 |
| | | proxy.download("/timingTask/export", {}, "定时任务管理.xlsx"); |
| | | } else if (activeRadio.value === "task") { |
| | | // 定时任务记录 |
| | | proxy.download("/inspectionTask/export", {}, "定时任务记录.xlsx"); |
| | | } else { |
| | | processedItem.inspector = []; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已取消"); |
| | | return processedItem; |
| | | }); |
| | | total.value = res.data.total || 0; |
| | | }).finally(() => { tableLoading.value = false; }); |
| | | }; |
| | | |
| | | const resetQuery = () => { queryParams.taskName = ""; handleQuery(); }; |
| | | |
| | | const handleAdd = row => { nextTick(() => { formDia.value?.openDialog(row ? "edit" : "add", row); }); }; |
| | | const viewFile = row => { nextTick(() => { viewFiles.value?.openDialog(row); }); }; |
| | | const openUploadDialog = row => { nextTick(() => { uploadFiles.value?.openDialog(row); }); }; |
| | | |
| | | const handleDelete = () => { |
| | | if (!selectedRows.value.length) { proxy.$modal.msgWarning("请选择要删除的数据"); return; } |
| | | proxy.$modal.confirm("是否确认删除所选数据项?").then(() => delTimingTask(selectedRows.value.map(item => item.id))) |
| | | .then(() => { proxy.$modal.msgSuccess("删除成功"); handleQuery(); }).catch(() => {}); |
| | | }; |
| | | |
| | | const handleSelectionChange = selection => { selectedRows.value = selection; }; |
| | | |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", { confirmButtonText: "确认", cancelButtonText: "取消", type: "warning" }) |
| | | .then(() => { |
| | | if (activeRadio.value === "taskManage") proxy.download("/timingTask/export", {}, "巡检任务.xlsx"); |
| | | else proxy.download("/inspectionTask/export", {}, "巡检记录.xlsx"); |
| | | }).catch(() => { proxy.$modal.msg("已取消"); }); |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .person-tags { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .person-tag { |
| | | margin-right: 4px; |
| | | margin-bottom: 2px; |
| | | } |
| | | |
| | | .no-data { |
| | | color: #909399; |
| | | font-size: 14px; |
| | | } |
| | | </style> |
| | | .person-tags { display: flex; flex-wrap: wrap; gap: 4px; } |
| | | .person-tag { margin-right: 4px; margin-bottom: 2px; } |
| | | .no-data { color: #909399; font-size: 14px; } |
| | | </style> |