|
|
@@ -1,871 +0,0 @@
|
|
|
-<!--日常巡检工单(orderType=2,参考排水/燃气工单:搜索卡 + 左列表 + 右详情 tabs + 按钮操作 + 部门树派单)-->
|
|
|
-<!--数据源:/api/work-order/*(orderType=2 过滤;equipmentType 为空 = 所有设备)-->
|
|
|
-<template>
|
|
|
- <div class="order-container">
|
|
|
- <!-- 顶部搜索区(参考排水/燃气工单) -->
|
|
|
- <el-card class="search-card" shadow="never">
|
|
|
- <div class="search-form">
|
|
|
- <el-form :model="queryParams" ref="queryFormRef" :inline>
|
|
|
- <el-form-item label="优先级">
|
|
|
- <el-select v-model="queryParams.orderLevel" placeholder="全部优先级" clearable style="width: 120px">
|
|
|
- <el-option v-for="(v, k) in ORDER_LEVEL_MAP" :key="k" :label="v.text" :value="Number(k)"/>
|
|
|
- </el-select>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="状态">
|
|
|
- <el-select v-model="queryParams.orderStatus" placeholder="全部状态" clearable style="width: 120px">
|
|
|
- <el-option v-for="(v, k) in ORDER_STATUS_MAP" :key="k" :label="v.text" :value="Number(k)"/>
|
|
|
- </el-select>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="关键词">
|
|
|
- <el-input v-model="queryParams.keyword" placeholder="巡检内容/工单编号" clearable style="width: 200px" @keyup.enter="handleSearch"/>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item>
|
|
|
- <el-button type="primary" @click="handleSearch">查询</el-button>
|
|
|
- <el-button @click="handleReset">重置</el-button>
|
|
|
- <el-button type="primary" plain @click="openAddDialog">➕ 新增巡检</el-button>
|
|
|
- </el-form-item>
|
|
|
- </el-form>
|
|
|
- </div>
|
|
|
- </el-card>
|
|
|
-
|
|
|
- <!-- 主容器:左列表 + 右详情(参考排水/燃气工单) -->
|
|
|
- <el-row :gutter="16" class="main-row">
|
|
|
- <!-- 左侧:巡检工单列表 -->
|
|
|
- <el-col :span="8" class="col-fill">
|
|
|
- <el-card class="box-card" shadow="always">
|
|
|
- <template #header>
|
|
|
- <div class="card-header">
|
|
|
- <span>巡检工单列表</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- <el-scrollbar class="list-scroll">
|
|
|
- <div class="order-list">
|
|
|
- <div
|
|
|
- v-for="order in tableData"
|
|
|
- :key="order.orderId"
|
|
|
- class="order-item"
|
|
|
- :class="{ 'active': selectedOrderId === order.orderId }"
|
|
|
- @click="handleSelectOrder(order)"
|
|
|
- >
|
|
|
- <div class="order-header">
|
|
|
- <span class="order-no">{{ order.orderNo }}</span>
|
|
|
- <el-tag :type="orderStatusType(order.orderStatus)" size="small" effect="light" class="status-badge">{{ orderStatusText(order.orderStatus) }}</el-tag>
|
|
|
- </div>
|
|
|
- <div class="order-body">
|
|
|
- <div class="order-tags">
|
|
|
- <el-tag :type="orderLevelType(order.orderLevel)" size="small" effect="plain">{{ orderLevelText(order.orderLevel) }}</el-tag>
|
|
|
- <el-tag v-if="order.hazardNo" size="small" effect="plain">隐患 {{ order.hazardNo }}</el-tag>
|
|
|
- </div>
|
|
|
- <div class="order-desc" v-if="order.orderDesc">{{ order.orderDesc }}</div>
|
|
|
- <div class="device-code">巡检人员:{{ userNames(order.receiveUser) || '-' }}</div>
|
|
|
- <div class="create-time">计划巡检:{{ timeText(order.planFinishTime) }}</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <el-empty v-if="tableData.length === 0" description="暂无数据" :image-size="100"/>
|
|
|
- </div>
|
|
|
- </el-scrollbar>
|
|
|
- <div class="pagination-wrapper">
|
|
|
- <el-pagination
|
|
|
- v-model:current-page="pageNum"
|
|
|
- v-model:page-size="pageSize"
|
|
|
- :total="total"
|
|
|
- layout="total, prev, pager, next"
|
|
|
- :page-sizes="[10, 20, 50, 100]"
|
|
|
- @size-change="handleSizeChange"
|
|
|
- @current-change="handlePageChange"
|
|
|
- class="pagination"
|
|
|
- />
|
|
|
- </div>
|
|
|
- </el-card>
|
|
|
- </el-col>
|
|
|
-
|
|
|
- <!-- 右侧:巡检工单详情 -->
|
|
|
- <el-col :span="16" class="col-fill">
|
|
|
- <el-card v-loading="detailLoading" class="box-card" shadow="always">
|
|
|
- <template #header>
|
|
|
- <div class="card-header">
|
|
|
- <span v-if="selectedOrder && selectedOrder.orderId">巡检详情 - {{ selectedOrder.orderNo }}</span>
|
|
|
- <span v-else>请选择巡检工单查看详情</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
-
|
|
|
- <div v-if="selectedOrder && selectedOrder.orderId" class="detail-body">
|
|
|
- <el-tabs v-model="activeTab" class="detail-tabs">
|
|
|
- <!-- 工单信息 -->
|
|
|
- <el-tab-pane label="工单信息" name="info">
|
|
|
- <el-descriptions :column="2" border size="small">
|
|
|
- <el-descriptions-item label="工单编号">{{ selectedOrder.orderNo }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="工单状态">
|
|
|
- <el-tag :type="orderStatusType(selectedOrder.orderStatus)" size="small">{{ orderStatusText(selectedOrder.orderStatus) }}</el-tag>
|
|
|
- </el-descriptions-item>
|
|
|
- <el-descriptions-item label="关联隐患编号">{{ selectedOrder.hazardNo || '-' }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="设备编号">{{ selectedOrder.deviceCode || '-' }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="优先级">
|
|
|
- <el-tag :type="orderLevelType(selectedOrder.orderLevel)" size="small">{{ orderLevelText(selectedOrder.orderLevel) }}</el-tag>
|
|
|
- </el-descriptions-item>
|
|
|
- <el-descriptions-item label="巡检人员">{{ userNames(selectedOrder.receiveUser) || '-' }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="巡检内容" :span="2">{{ selectedOrder.orderDesc || '-' }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="计划巡检时间">{{ timeText(selectedOrder.planFinishTime) }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="派单时间">{{ timeText(selectedOrder.dispatchTime) }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="实际完成时间">{{ timeText(selectedOrder.realFinishTime) }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="验收时间">{{ timeText(selectedOrder.verifyTime) }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="创建时间">{{ timeText(selectedOrder.createTime) }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="更新时间">{{ timeText(selectedOrder.updateTime) }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="处置结果" :span="2">{{ selectedOrder.repairResult || '-' }}</el-descriptions-item>
|
|
|
- </el-descriptions>
|
|
|
-
|
|
|
- <!-- 关联设备信息 -->
|
|
|
- <div v-if="detailDevice" class="device-section">
|
|
|
- <h4 class="section-title">🔗 关联设备</h4>
|
|
|
- <el-descriptions :column="2" border size="small">
|
|
|
- <el-descriptions-item label="设备类型">{{ detailDevice.equipmentTypeName || '-' }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="设备编码">{{ detailDevice.equipmentCode || '-' }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="安装位置">{{ detailDevice.equipmentLocation || '-' }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="管点地址">{{ detailDevice.pointAddress || '-' }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="设备厂家">{{ detailDevice.manufacturer || '-' }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="所属管线">{{ detailDevice.networkName || '-' }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="维护人员">{{ detailDevice.maintainer || '-' }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="联系方式">{{ detailDevice.maintainerPhone || '-' }}</el-descriptions-item>
|
|
|
- </el-descriptions>
|
|
|
- </div>
|
|
|
-
|
|
|
- <RepairImagePanel :before-images="selectedOrder.maintenanceBeforeImg" :after-images="selectedOrder.maintenanceAfterImg" />
|
|
|
-
|
|
|
- <!-- 工单操作(参考排水/燃气工单按钮组) -->
|
|
|
- <div class="action-section">
|
|
|
- <h4 class="section-title">工单操作</h4>
|
|
|
- <div class="action-buttons">
|
|
|
- <el-button v-if="canOperate.dispatch(selectedOrder)" type="primary" @click="handleDispatch">下派任务</el-button>
|
|
|
- <el-button v-if="canOperate.verify(selectedOrder)" type="warning" @click="handleVerify">核查</el-button>
|
|
|
- <el-button v-if="canOperate.process(selectedOrder)" type="success" plain @click="handleProcess">
|
|
|
- 📷 处理(可传处理前照片)
|
|
|
- </el-button>
|
|
|
- <el-button v-if="canOperate.complete(selectedOrder)" type="danger" plain @click="handleComplete">
|
|
|
- ✅ 提交验收(可传维修后照片)
|
|
|
- </el-button>
|
|
|
- <el-button v-if="canOperate.close(selectedOrder)" type="success" @click="handleClose">验收通过</el-button>
|
|
|
- <el-button v-if="canOperate.cancel(selectedOrder)" type="info" plain @click="handleCancel">取消工单</el-button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </el-tab-pane>
|
|
|
-
|
|
|
- <!-- 处理历史 -->
|
|
|
- <el-tab-pane label="处理历史" name="history">
|
|
|
- <div class="timeline-scroll">
|
|
|
- <el-timeline v-if="currentOrderLogs && currentOrderLogs.length > 0">
|
|
|
- <el-timeline-item
|
|
|
- v-for="log in currentOrderLogs"
|
|
|
- :key="log.logId || log.id"
|
|
|
- :timestamp="timeText(log.operTime)"
|
|
|
- placement="top"
|
|
|
- :type="orderLogType(log.operType)"
|
|
|
- size="large"
|
|
|
- >
|
|
|
- <el-card shadow="hover">
|
|
|
- <div class="timeline-content">
|
|
|
- <div class="timestamp">{{ timeText(log.operTime) }}</div>
|
|
|
- <div class="operator-info">
|
|
|
- <span class="operator-name">{{ log.operUserName || '-' }}</span>
|
|
|
- <span class="oper-type">· {{ orderLogText(log.operType) }}</span>
|
|
|
- </div>
|
|
|
- <div class="oper-desc">{{ log.operDesc || '-' }}</div>
|
|
|
- </div>
|
|
|
- </el-card>
|
|
|
- </el-timeline-item>
|
|
|
- </el-timeline>
|
|
|
- <el-empty v-else description="暂无处理记录" :image-size="100"/>
|
|
|
- </div>
|
|
|
- </el-tab-pane>
|
|
|
- </el-tabs>
|
|
|
- </div>
|
|
|
-
|
|
|
- <el-empty v-else description="请从左侧选择巡检工单" :image-size="150"/>
|
|
|
- </el-card>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
-
|
|
|
- <!-- 新增巡检弹窗(提交后自动派单) -->
|
|
|
- <el-dialog v-model="addVisible" title="➕ 新增日常巡检" width="560px" append-to-body destroy-on-close>
|
|
|
- <el-form ref="addFormRef" :model="addForm" :rules="addRules" label-width="110px">
|
|
|
- <el-form-item label="巡检内容" prop="orderDesc">
|
|
|
- <el-input v-model="addForm.orderDesc" type="textarea" :rows="3" placeholder="如:泵房设备检查、仪表读数记录、管道巡查" maxlength="300" show-word-limit/>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="计划巡检时间" prop="planFinishTime">
|
|
|
- <el-date-picker v-model="addForm.planFinishTime" type="datetime" placeholder="计划巡检时间" value-format="YYYY-MM-DD HH:mm:ss" style="width:100%"/>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="巡检人员" prop="receiveUserId">
|
|
|
- <el-select v-model="addForm.receiveUserId" filterable placeholder="选择巡检人员(提交后自动派单)" style="width:100%">
|
|
|
- <el-option v-for="u in userOptions" :key="u.userId" :label="u.nickName + '(' + u.userName + ')'" :value="u.userId"/>
|
|
|
- </el-select>
|
|
|
- </el-form-item>
|
|
|
- </el-form>
|
|
|
- <template #footer>
|
|
|
- <el-button @click="addVisible=false">取消</el-button>
|
|
|
- <el-button type="primary" :loading="submitting" @click="submitAdd">提交并派单</el-button>
|
|
|
- </template>
|
|
|
- </el-dialog>
|
|
|
-
|
|
|
- <!-- 派单弹窗(部门树 + 用户多选,参考排水/燃气工单) -->
|
|
|
- <el-dialog v-model="dispatchDialogVisible" title="下派任务 - 选择接单人" width="750px" append-to-body>
|
|
|
- <div style="display: flex; gap: 16px; min-height: 350px;">
|
|
|
- <div style="width: 280px; border-right: 1px solid #e4e7ed; padding-right: 12px;">
|
|
|
- <el-input v-model="deptFilterText" placeholder="筛选部门" clearable size="small" style="margin-bottom: 8px"/>
|
|
|
- <el-tree
|
|
|
- ref="deptTreeRef"
|
|
|
- :data="deptTreeData"
|
|
|
- :props="{ label: 'deptName', children: 'children' }"
|
|
|
- :filter-node-method="filterDeptNode"
|
|
|
- node-key="deptId"
|
|
|
- highlight-current
|
|
|
- @node-click="handleDeptClick"
|
|
|
- style="font-size: 14px"
|
|
|
- />
|
|
|
- </div>
|
|
|
- <div style="flex: 1; min-width: 0;">
|
|
|
- <el-table
|
|
|
- ref="userTableRef"
|
|
|
- :data="userList"
|
|
|
- border size="small"
|
|
|
- max-height="300"
|
|
|
- row-key="userId"
|
|
|
- @selection-change="handleSelectionChange"
|
|
|
- @row-click="handleRowClick"
|
|
|
- >
|
|
|
- <el-table-column type="selection" width="40" align="center" :reserve-selection="true"/>
|
|
|
- <el-table-column prop="userName" label="用户名" width="100"/>
|
|
|
- <el-table-column prop="nickName" label="昵称" width="100"/>
|
|
|
- <el-table-column prop="dept.deptName" label="部门" show-overflow-tooltip/>
|
|
|
- </el-table>
|
|
|
- <div v-if="userList.length === 0" style="text-align: center; color: #909399; padding: 40px 0;">请先在左侧选择部门</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <el-form label-width="80px" style="margin-top: 16px">
|
|
|
- <el-form-item label="备注"><el-input v-model="dispatchForm.remarks" placeholder="请输入派单备注"/></el-form-item>
|
|
|
- </el-form>
|
|
|
- <template #footer>
|
|
|
- <span style="float: left; line-height: 32px; color: #606266;">
|
|
|
- 已选人员:
|
|
|
- <span v-if="selectedUserNames.length > 0" style="color: #409eff; font-weight: bold;">{{ selectedUserNames.join('、') }}</span>
|
|
|
- <span v-else style="color: #909399">未选择</span>
|
|
|
- ({{ dispatchForm.handlerIds.length }})
|
|
|
- </span>
|
|
|
- <el-button @click="dispatchDialogVisible = false">取消</el-button>
|
|
|
- <el-button type="primary" :loading="submitting" @click="submitDispatch">确认</el-button>
|
|
|
- </template>
|
|
|
- </el-dialog>
|
|
|
-
|
|
|
- <!-- 核查/处理弹窗(处理过程写入 solution,后端存入 repairResult 展示) -->
|
|
|
- <el-dialog v-model="actionDialogVisible" :title="actionTitle" width="600px" append-to-body>
|
|
|
- <el-form label-width="90px">
|
|
|
- <el-form-item label="结果说明" required>
|
|
|
- <el-input v-model="actionFormData.solution" type="textarea" :rows="5" :placeholder="actionTitle === '核查工单' ? '请输入核查结论(属实/不属实、现场情况)' : '请输入巡检问题及处理过程、处理结果'" maxlength="500" show-word-limit/>
|
|
|
- </el-form-item>
|
|
|
- </el-form>
|
|
|
- <template #footer>
|
|
|
- <el-button @click="actionDialogVisible = false">取消</el-button>
|
|
|
- <el-button type="primary" :loading="submitting" @click="submitAction">确定</el-button>
|
|
|
- </template>
|
|
|
- </el-dialog>
|
|
|
-
|
|
|
- <!-- 提交验收弹窗 -->
|
|
|
- <el-dialog v-model="completeDialogVisible" title="提交验收" width="500px" append-to-body>
|
|
|
- <el-form label-width="80px">
|
|
|
- <el-form-item label="验收意见">
|
|
|
- <el-input v-model="completeForm.result" type="textarea" :rows="4" placeholder="请填写巡检结果确认说明" maxlength="500" show-word-limit/>
|
|
|
- </el-form-item>
|
|
|
- </el-form>
|
|
|
- <template #footer>
|
|
|
- <el-button @click="completeDialogVisible = false">取消</el-button>
|
|
|
- <el-button type="primary" :loading="submitting" @click="submitComplete">提交</el-button>
|
|
|
- </template>
|
|
|
- </el-dialog>
|
|
|
-
|
|
|
- <!-- 验收通过/结案弹窗 -->
|
|
|
- <el-dialog v-model="closeDialogVisible" title="验收通过" width="500px" append-to-body>
|
|
|
- <el-form label-width="80px">
|
|
|
- <el-form-item label="验收结果">
|
|
|
- <el-input v-model="closeForm.result" type="textarea" :rows="4" placeholder="请填写巡检验收结果说明" maxlength="500" show-word-limit/>
|
|
|
- </el-form-item>
|
|
|
- </el-form>
|
|
|
- <template #footer>
|
|
|
- <el-button @click="closeDialogVisible = false">取消</el-button>
|
|
|
- <el-button type="success" :loading="submitting" @click="submitClose">确认验收</el-button>
|
|
|
- </template>
|
|
|
- </el-dialog>
|
|
|
- </div>
|
|
|
-</template>
|
|
|
-
|
|
|
-<script setup>
|
|
|
-import { ref, reactive, computed, watch, onMounted } from 'vue'
|
|
|
-import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
-import RepairImagePanel from '@/components/RepairImagePanel'
|
|
|
-import { listWorkOrder, getWorkOrderDetail, addWorkOrder, dispatchWorkOrder, verifyWorkOrder, processWorkOrder, completeWorkOrder, closeWorkOrder, cancelWorkOrder } from '@/api/work/order'
|
|
|
-import { getEquipmentFullPage } from '@/api/pipeNetwork/basic'
|
|
|
-import { listDept } from '@/api/system/dept'
|
|
|
-import { enabledAll, listUser } from '@/api/system/user/'
|
|
|
-import { handleTree } from '@/utils/ruoyi'
|
|
|
-import { ORDER_STATUS_MAP, ORDER_LEVEL_MAP, orderStatusText, orderStatusType, orderLevelText, orderLevelType, orderLogText, orderLogType, canOperate, timeText } from './workOrderDict'
|
|
|
-
|
|
|
-// ========== 查询 & 列表(orderType=2 日常巡检) ==========
|
|
|
-const listLoading = ref(false)
|
|
|
-const tableData = ref([])
|
|
|
-const pageNum = ref(1)
|
|
|
-const pageSize = ref(10)
|
|
|
-const total = ref(0)
|
|
|
-const queryFormRef = ref(null)
|
|
|
-const queryParams = reactive({ orderLevel: null, orderStatus: null, keyword: '' })
|
|
|
-
|
|
|
-async function handleSearch() {
|
|
|
- listLoading.value = true
|
|
|
- try {
|
|
|
- const params = { orderType: 2 }
|
|
|
- if (queryParams.orderLevel) params.orderLevel = queryParams.orderLevel
|
|
|
- if (queryParams.orderStatus) params.orderStatus = queryParams.orderStatus
|
|
|
- if (queryParams.keyword) params.keyword = queryParams.keyword
|
|
|
- const res = await listWorkOrder(params, pageNum.value, pageSize.value)
|
|
|
- if (res.data) {
|
|
|
- tableData.value = res.data.records || []
|
|
|
- total.value = Number(res.data.total || 0)
|
|
|
- }
|
|
|
- } catch(e) { ElMessage.error('查询失败'); console.error(e) }
|
|
|
- finally { listLoading.value = false }
|
|
|
-}
|
|
|
-
|
|
|
-function handleReset() {
|
|
|
- queryFormRef.value && queryFormRef.value.resetFields()
|
|
|
- queryParams.orderLevel = null
|
|
|
- queryParams.orderStatus = null
|
|
|
- queryParams.keyword = ''
|
|
|
- pageNum.value = 1
|
|
|
- selectedOrderId.value = null
|
|
|
- selectedOrder.value = {}
|
|
|
- handleSearch()
|
|
|
-}
|
|
|
-
|
|
|
-// ========== 选中工单 & 详情(合并 res.data.order + logs) ==========
|
|
|
-const selectedOrderId = ref(null)
|
|
|
-const selectedOrder = ref({})
|
|
|
-const activeTab = ref('info')
|
|
|
-const currentOrderLogs = ref([])
|
|
|
-const detailLoading = ref(false)
|
|
|
-const detailDevice = ref(null)
|
|
|
-
|
|
|
-async function handleSelectOrder(order) {
|
|
|
- selectedOrderId.value = order.orderId
|
|
|
- selectedOrder.value = { ...order }
|
|
|
- activeTab.value = 'info'
|
|
|
- currentOrderLogs.value = []
|
|
|
- detailDevice.value = null
|
|
|
- await loadOrderDetail(order.orderId)
|
|
|
-}
|
|
|
-
|
|
|
-async function loadOrderDetail(orderId) {
|
|
|
- detailLoading.value = true
|
|
|
- try {
|
|
|
- const res = await getWorkOrderDetail(orderId)
|
|
|
- if (res.code === 200 && res.data) {
|
|
|
- // 后端返回 { order, logs } 两层结构,必须合并 order 层级
|
|
|
- selectedOrder.value = { ...selectedOrder.value, ...res.data.order }
|
|
|
- currentOrderLogs.value = res.data.logs || []
|
|
|
- if (res.data.order?.deviceCode) {
|
|
|
- loadDeviceInfo(res.data.order.deviceCode)
|
|
|
- }
|
|
|
- }
|
|
|
- } catch(e) { ElMessage.error('加载巡检详情失败'); console.error(e) }
|
|
|
- finally { detailLoading.value = false }
|
|
|
-}
|
|
|
-
|
|
|
-async function loadDeviceInfo(codeOrId) {
|
|
|
- try {
|
|
|
- const res = await getEquipmentFullPage(1, 1, { equipmentCode: codeOrId })
|
|
|
- const rows = res.data?.records || res.data?.rows || []
|
|
|
- if (rows.length) { detailDevice.value = rows[0]; return }
|
|
|
- } catch(e) { console.warn('加载设备信息失败', e) }
|
|
|
-}
|
|
|
-
|
|
|
-function handleSizeChange() {
|
|
|
- pageNum.value = 1
|
|
|
- selectedOrderId.value = null
|
|
|
- selectedOrder.value = {}
|
|
|
- handleSearch()
|
|
|
-}
|
|
|
-
|
|
|
-function handlePageChange() {
|
|
|
- selectedOrderId.value = null
|
|
|
- selectedOrder.value = {}
|
|
|
- handleSearch()
|
|
|
-}
|
|
|
-
|
|
|
-// ========== 用户映射(巡检人员 ID → 姓名) ==========
|
|
|
-const userOptions = ref([])
|
|
|
-const userMap = ref({})
|
|
|
-function userNames(ids) {
|
|
|
- if (!ids) return ''
|
|
|
- return String(ids).split(',').map(id => userMap.value[id] || id).join('、')
|
|
|
-}
|
|
|
-async function loadUsers() {
|
|
|
- try {
|
|
|
- const res = await enabledAll()
|
|
|
- // 不分页接口:兼容 data / rows / 直接返回数组三种形态
|
|
|
- const list = res?.data || res?.rows || (Array.isArray(res) ? res : [])
|
|
|
- userOptions.value = list
|
|
|
- userMap.value = Object.fromEntries(list.map(u => [u.userId, u.nickName || u.userName]))
|
|
|
- } catch(e) { console.warn('加载用户列表失败', e) }
|
|
|
-}
|
|
|
-
|
|
|
-// ========== 新增巡检(提交后自动派单) ==========
|
|
|
-const addVisible = ref(false)
|
|
|
-const addFormRef = ref(null)
|
|
|
-const addForm = reactive({ orderDesc: '', planFinishTime: '', receiveUserId: null })
|
|
|
-const addRules = {
|
|
|
- orderDesc: [{ required: true, message: '请填写巡检内容', trigger: 'blur' }],
|
|
|
- planFinishTime: [{ required: true, message: '请选择巡检时间', trigger: 'change' }],
|
|
|
- receiveUserId: [{ required: true, message: '请选择巡检人员', trigger: 'change' }]
|
|
|
-}
|
|
|
-const submitting = ref(false)
|
|
|
-
|
|
|
-function openAddDialog() {
|
|
|
- addForm.orderDesc = ''
|
|
|
- addForm.planFinishTime = ''
|
|
|
- addForm.receiveUserId = null
|
|
|
- addVisible.value = true
|
|
|
-}
|
|
|
-
|
|
|
-async function submitAdd() {
|
|
|
- await addFormRef.value.validate().catch(() => { throw new Error('校验失败') })
|
|
|
- submitting.value = true
|
|
|
- try {
|
|
|
- const res = await addWorkOrder({ orderType: 2, orderDesc: addForm.orderDesc, planFinishTime: addForm.planFinishTime })
|
|
|
- const orderId = res.data
|
|
|
- if (!orderId) throw new Error('未返回工单ID')
|
|
|
- await dispatchWorkOrder({ orderId: Number(orderId), handlerIds: [addForm.receiveUserId], remarks: '日常巡检自动派单' })
|
|
|
- ElMessage.success('巡检工单已创建并派单')
|
|
|
- addVisible.value = false
|
|
|
- handleSearch()
|
|
|
- } catch(e) { ElMessage.error('创建失败:' + (e.msg || e.message || '请重试')); console.error(e) }
|
|
|
- finally { submitting.value = false }
|
|
|
-}
|
|
|
-
|
|
|
-// ========== 派单(部门树 + 用户多选,参考排水/燃气工单) ==========
|
|
|
-const dispatchDialogVisible = ref(false)
|
|
|
-const dispatchForm = reactive({ handlerIds: [], remarks: '' })
|
|
|
-const deptTreeRef = ref(null)
|
|
|
-const userTableRef = ref(null)
|
|
|
-const deptTreeData = ref([])
|
|
|
-const deptFilterText = ref('')
|
|
|
-const userList = ref([])
|
|
|
-const selectedUserList = ref([])
|
|
|
-
|
|
|
-watch(deptFilterText, (val) => { deptTreeRef.value && deptTreeRef.value.filter(val) })
|
|
|
-function filterDeptNode(value, data) {
|
|
|
- if (!value) return true
|
|
|
- return data.deptName.includes(value)
|
|
|
-}
|
|
|
-
|
|
|
-async function loadDeptTree() {
|
|
|
- if (deptTreeData.value.length > 0) return
|
|
|
- try {
|
|
|
- const res = await listDept()
|
|
|
- deptTreeData.value = handleTree(res.data || [], 'deptId')
|
|
|
- } catch(e) { console.warn('加载部门失败', e) }
|
|
|
-}
|
|
|
-
|
|
|
-async function handleDeptClick(node) {
|
|
|
- try {
|
|
|
- userTableRef.value && userTableRef.value.clearSelection()
|
|
|
- selectedUserList.value = []
|
|
|
- dispatchForm.handlerIds = []
|
|
|
- const res = await listUser({ deptId: node.deptId, pageNum: 1, pageSize: 100 })
|
|
|
- userList.value = res.rows || []
|
|
|
- } catch(e) { console.warn('加载部门用户失败', e) }
|
|
|
-}
|
|
|
-
|
|
|
-function handleSelectionChange(selection) {
|
|
|
- selectedUserList.value = selection
|
|
|
- dispatchForm.handlerIds = selection.map(item => item.userId)
|
|
|
-}
|
|
|
-
|
|
|
-function handleRowClick(row) {
|
|
|
- userTableRef.value && userTableRef.value.toggleRowSelection(row)
|
|
|
-}
|
|
|
-
|
|
|
-const selectedUserNames = computed(() => selectedUserList.value.map(item => item.nickName || item.userName))
|
|
|
-
|
|
|
-function handleDispatch() {
|
|
|
- if (!canOperate.dispatch(selectedOrder.value)) {
|
|
|
- ElMessage.warning('仅可对待派单工单进行派单操作')
|
|
|
- return
|
|
|
- }
|
|
|
- Object.assign(dispatchForm, { handlerIds: [], remarks: '' })
|
|
|
- selectedUserList.value = []
|
|
|
- userList.value = []
|
|
|
- loadDeptTree()
|
|
|
- dispatchDialogVisible.value = true
|
|
|
-}
|
|
|
-
|
|
|
-async function submitDispatch() {
|
|
|
- if (!dispatchForm.handlerIds || dispatchForm.handlerIds.length === 0) {
|
|
|
- ElMessage.warning('请选择至少一个接单人')
|
|
|
- return
|
|
|
- }
|
|
|
- submitting.value = true
|
|
|
- try {
|
|
|
- await dispatchWorkOrder({
|
|
|
- orderId: selectedOrder.value.orderId,
|
|
|
- handlerIds: dispatchForm.handlerIds,
|
|
|
- remarks: dispatchForm.remarks
|
|
|
- })
|
|
|
- ElMessage.success('派单成功!已向 ' + dispatchForm.handlerIds.length + ' 人下发任务')
|
|
|
- dispatchDialogVisible.value = false
|
|
|
- refreshAfterAction()
|
|
|
- } catch(e) { ElMessage.error('派单失败:' + (e.msg || e.message || '请重试')); console.error(e) }
|
|
|
- finally { submitting.value = false }
|
|
|
-}
|
|
|
-
|
|
|
-// ========== 核查/处理(处理过程写入 solution,后端存入 repairResult) ==========
|
|
|
-const actionDialogVisible = ref(false)
|
|
|
-const actionTitle = ref('')
|
|
|
-const actionFormData = reactive({ solution: '' })
|
|
|
-
|
|
|
-function handleVerify() {
|
|
|
- if (!canOperate.verify(selectedOrder.value)) {
|
|
|
- ElMessage.warning('当前工单不允许核查')
|
|
|
- return
|
|
|
- }
|
|
|
- actionTitle.value = '核查工单'
|
|
|
- actionFormData.solution = ''
|
|
|
- actionDialogVisible.value = true
|
|
|
-}
|
|
|
-
|
|
|
-function handleProcess() {
|
|
|
- if (!canOperate.process(selectedOrder.value)) {
|
|
|
- ElMessage.warning('当前工单不允许处理')
|
|
|
- return
|
|
|
- }
|
|
|
- actionTitle.value = '处理工单'
|
|
|
- actionFormData.solution = ''
|
|
|
- actionDialogVisible.value = true
|
|
|
-}
|
|
|
-
|
|
|
-async function submitAction() {
|
|
|
- if (!actionFormData.solution.trim()) {
|
|
|
- ElMessage.warning('请填写说明')
|
|
|
- return
|
|
|
- }
|
|
|
- submitting.value = true
|
|
|
- try {
|
|
|
- const data = {
|
|
|
- orderId: selectedOrder.value.orderId,
|
|
|
- handleResult: 'solved',
|
|
|
- solution: actionFormData.solution
|
|
|
- }
|
|
|
- if (actionTitle.value === '核查工单') {
|
|
|
- await verifyWorkOrder(data)
|
|
|
- } else {
|
|
|
- await processWorkOrder(data)
|
|
|
- }
|
|
|
- ElMessage.success(actionTitle.value + '成功')
|
|
|
- actionDialogVisible.value = false
|
|
|
- refreshAfterAction()
|
|
|
- } catch(e) { ElMessage.error('操作失败:' + (e.msg || e.message || '请重试')); console.error(e) }
|
|
|
- finally { submitting.value = false }
|
|
|
-}
|
|
|
-
|
|
|
-// ========== 提交验收(后端 completeOrder 要求状态 4) ==========
|
|
|
-const completeDialogVisible = ref(false)
|
|
|
-const completeForm = reactive({ result: '' })
|
|
|
-
|
|
|
-function handleComplete() {
|
|
|
- if (!canOperate.complete(selectedOrder.value)) {
|
|
|
- ElMessage.warning('当前工单不允许提交验收')
|
|
|
- return
|
|
|
- }
|
|
|
- completeForm.result = ''
|
|
|
- completeDialogVisible.value = true
|
|
|
-}
|
|
|
-
|
|
|
-async function submitComplete() {
|
|
|
- if (!completeForm.result.trim()) {
|
|
|
- ElMessage.warning('请填写验收意见')
|
|
|
- return
|
|
|
- }
|
|
|
- submitting.value = true
|
|
|
- try {
|
|
|
- await completeWorkOrder({ orderId: selectedOrder.value.orderId, result: completeForm.result })
|
|
|
- ElMessage.success('提交验收成功')
|
|
|
- completeDialogVisible.value = false
|
|
|
- refreshAfterAction()
|
|
|
- } catch(e) { ElMessage.error('提交失败:' + (e.msg || e.message || '请重试')); console.error(e) }
|
|
|
- finally { submitting.value = false }
|
|
|
-}
|
|
|
-
|
|
|
-// ========== 验收通过/结案 ==========
|
|
|
-const closeDialogVisible = ref(false)
|
|
|
-const closeForm = reactive({ result: '' })
|
|
|
-
|
|
|
-function handleClose() {
|
|
|
- if (!canOperate.close(selectedOrder.value)) {
|
|
|
- ElMessage.warning('当前工单不允许验收通过')
|
|
|
- return
|
|
|
- }
|
|
|
- closeForm.result = ''
|
|
|
- closeDialogVisible.value = true
|
|
|
-}
|
|
|
-
|
|
|
-async function submitClose() {
|
|
|
- if (!closeForm.result.trim()) {
|
|
|
- ElMessage.warning('请填写验收结果')
|
|
|
- return
|
|
|
- }
|
|
|
- submitting.value = true
|
|
|
- try {
|
|
|
- await closeWorkOrder({
|
|
|
- orderId: selectedOrder.value.orderId,
|
|
|
- result: closeForm.result
|
|
|
- })
|
|
|
- ElMessage.success('验收通过,巡检工单已结案')
|
|
|
- closeDialogVisible.value = false
|
|
|
- refreshAfterAction()
|
|
|
- } catch(e) { ElMessage.error('验收失败:' + (e.msg || e.message || '请重试')); console.error(e) }
|
|
|
- finally { submitting.value = false }
|
|
|
-}
|
|
|
-
|
|
|
-// ========== 取消工单 ==========
|
|
|
-function handleCancel() {
|
|
|
- ElMessageBox.confirm(`确定取消巡检工单「${selectedOrder.value.orderNo}」吗?`, '取消工单', { type: 'warning' })
|
|
|
- .then(async () => {
|
|
|
- try {
|
|
|
- await cancelWorkOrder(selectedOrder.value.orderId)
|
|
|
- ElMessage.success('工单已取消')
|
|
|
- selectedOrderId.value = null
|
|
|
- selectedOrder.value = {}
|
|
|
- handleSearch()
|
|
|
- } catch(e) { ElMessage.error('取消失败:' + (e.msg || e.message || '请重试')); console.error(e) }
|
|
|
- }).catch(() => {})
|
|
|
-}
|
|
|
-
|
|
|
-/** 操作完成后刷新列表并重新加载当前选中工单 */
|
|
|
-async function refreshAfterAction() {
|
|
|
- await handleSearch()
|
|
|
- const fresh = tableData.value.find(o => o.orderId === selectedOrderId.value)
|
|
|
- if (fresh) {
|
|
|
- await handleSelectOrder(fresh)
|
|
|
- } else {
|
|
|
- selectedOrderId.value = null
|
|
|
- selectedOrder.value = {}
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-onMounted(() => {
|
|
|
- loadUsers()
|
|
|
- handleSearch()
|
|
|
-})
|
|
|
-</script>
|
|
|
-
|
|
|
-<style scoped lang="scss">
|
|
|
-.order-container {
|
|
|
- padding: 0 16px 16px;
|
|
|
-
|
|
|
- .search-card {
|
|
|
- margin-bottom: 16px;
|
|
|
-
|
|
|
- :deep(.el-card__body) {
|
|
|
- padding: 16px;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .search-form {
|
|
|
- .el-form {
|
|
|
- display: flex;
|
|
|
- flex-wrap: wrap;
|
|
|
- align-items: flex-start;
|
|
|
- gap: 12px;
|
|
|
-
|
|
|
- .el-form-item {
|
|
|
- margin-bottom: 0;
|
|
|
- min-width: 150px;
|
|
|
-
|
|
|
- &:last-child {
|
|
|
- margin-left: auto;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .main-row {
|
|
|
- height: calc(100vh - 200px);
|
|
|
-
|
|
|
- .col-fill {
|
|
|
- height: 100%;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .box-card {
|
|
|
- height: 100%;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
-
|
|
|
- :deep(.el-card__body) {
|
|
|
- flex: 1;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- overflow: hidden;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .card-header {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- }
|
|
|
-
|
|
|
- .list-scroll {
|
|
|
- flex: 1;
|
|
|
- overflow: hidden;
|
|
|
- }
|
|
|
-
|
|
|
- .order-list {
|
|
|
- padding-right: 4px;
|
|
|
- }
|
|
|
-
|
|
|
- .order-item {
|
|
|
- padding: 16px;
|
|
|
- margin-bottom: 8px;
|
|
|
- border-radius: 8px;
|
|
|
- cursor: pointer;
|
|
|
- transition: all 0.2s;
|
|
|
- border: 1px solid #e4e7ed;
|
|
|
-
|
|
|
- &:hover {
|
|
|
- background-color: #f5f7fa;
|
|
|
- }
|
|
|
-
|
|
|
- &.active {
|
|
|
- background-color: #ecf5ff;
|
|
|
- border-color: #409eff;
|
|
|
- }
|
|
|
-
|
|
|
- .order-header {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- margin-bottom: 8px;
|
|
|
-
|
|
|
- .order-no {
|
|
|
- font-weight: bold;
|
|
|
- font-size: 14px;
|
|
|
- }
|
|
|
-
|
|
|
- .status-badge {
|
|
|
- min-width: 60px;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .order-body {
|
|
|
- font-size: 12px;
|
|
|
- color: #606266;
|
|
|
-
|
|
|
- .order-tags {
|
|
|
- display: flex;
|
|
|
- gap: 6px;
|
|
|
- margin-bottom: 6px;
|
|
|
- }
|
|
|
-
|
|
|
- .order-desc {
|
|
|
- margin-top: 4px;
|
|
|
- color: #303133;
|
|
|
- font-size: 13px;
|
|
|
- overflow: hidden;
|
|
|
- text-overflow: ellipsis;
|
|
|
- white-space: nowrap;
|
|
|
- }
|
|
|
-
|
|
|
- .device-code, .create-time {
|
|
|
- margin-top: 4px;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .pagination-wrapper {
|
|
|
- padding: 16px 0;
|
|
|
- border-top: 1px solid #e4e7ed;
|
|
|
- margin-top: 8px;
|
|
|
-
|
|
|
- .pagination {
|
|
|
- display: flex;
|
|
|
- justify-content: flex-end;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .detail-body {
|
|
|
- flex: 1;
|
|
|
- overflow-y: auto;
|
|
|
- }
|
|
|
-
|
|
|
- .detail-tabs {
|
|
|
- margin-top: 0;
|
|
|
- }
|
|
|
-
|
|
|
- .timeline-scroll {
|
|
|
- max-height: calc(100vh - 340px);
|
|
|
- overflow-y: auto;
|
|
|
- padding-right: 8px;
|
|
|
- }
|
|
|
-
|
|
|
- .device-section, .image-section, .action-section {
|
|
|
- margin-top: 24px;
|
|
|
- padding-top: 24px;
|
|
|
- border-top: 1px solid #e4e7ed;
|
|
|
-
|
|
|
- .section-title {
|
|
|
- font-size: 15px;
|
|
|
- font-weight: bold;
|
|
|
- margin-bottom: 16px;
|
|
|
- color: #303133;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .image-list {
|
|
|
- display: flex;
|
|
|
- flex-wrap: wrap;
|
|
|
- gap: 12px;
|
|
|
- }
|
|
|
-
|
|
|
- .action-buttons {
|
|
|
- display: flex;
|
|
|
- gap: 12px;
|
|
|
- flex-wrap: wrap;
|
|
|
-
|
|
|
- .el-button {
|
|
|
- min-width: 100px;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .timeline-content {
|
|
|
- .timestamp {
|
|
|
- font-size: 12px;
|
|
|
- color: #909399;
|
|
|
- margin-bottom: 6px;
|
|
|
- }
|
|
|
-
|
|
|
- .operator-info {
|
|
|
- font-size: 14px;
|
|
|
- margin-bottom: 8px;
|
|
|
-
|
|
|
- .operator-name {
|
|
|
- font-weight: bold;
|
|
|
- color: #303133;
|
|
|
- }
|
|
|
-
|
|
|
- .oper-type {
|
|
|
- color: #909399;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .oper-desc {
|
|
|
- font-size: 14px;
|
|
|
- color: #606266;
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-</style>
|