| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864 |
- <!--全部工单处置(参考排水/燃气工单:搜索卡 + 左列表 + 右详情 tabs + 按钮操作 + 部门树派单)-->
- <!--数据源:/api/work-order/*(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.orderType" placeholder="全部类型" clearable style="width: 150px">
- <el-option v-for="(v, k) in ORDER_TYPE_MAP" :key="k" :label="v.text" :value="Number(k)"/>
- </el-select>
- </el-form-item>
- <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-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 :type="orderTypeTagType(order.orderType)" size="small" effect="plain">{{ orderTypeText(order.orderType) }}</el-tag>
- </div>
- <div class="order-desc" v-if="order.orderDesc">{{ order.orderDesc }}</div>
- <div class="device-code">设备编号:{{ order.deviceCode || '-' }}</div>
- <div class="device-code" v-if="order.hazardNo">关联隐患:{{ order.hazardNo }}</div>
- <div class="create-time">创建:{{ timeText(order.createTime) }}</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.caseNo || '-' }}</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="工单类型">{{ orderTypeText(selectedOrder.orderType) }}</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.dispatchTime) }}</el-descriptions-item>
- <el-descriptions-item label="计划完成">{{ timeText(selectedOrder.planFinishTime) }}</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>
- <!-- 关联设备信息(需求 5.2.4.4.1 设备维度:类型/位置/厂家/维护人员/联系方式) -->
- <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" @click="handleProcess">处理</el-button>
- <el-button v-if="canOperate.complete(selectedOrder)" type="danger" @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="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 :model="dispatchForm" 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>
- <!-- 核查/处理弹窗(参考排水/燃气工单) -->
- <el-dialog v-model="actionDialogVisible" :title="actionTitle" width="600px" append-to-body>
- <el-form label-width="90px">
- <el-form-item v-if="actionTitle === '处理工单'" label="处理前照片">
- <ImageUpload v-model="processBeforeImages" :limit="9" :isShowTip="true" :upload-url="imageUploadUrl" />
- </el-form-item>
- <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="600px" append-to-body>
- <el-form label-width="80px">
- <el-form-item label="维修后照片">
- <ImageUpload v-model="maintenanceAfterImages" :limit="9" :isShowTip="true" :upload-url="imageUploadUrl" />
- </el-form-item>
- <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 ImagePreview from '@/components/ImagePreview'
- import { listWorkOrder, getWorkOrderDetail, dispatchWorkOrder, verifyWorkOrder, processWorkOrder, completeWorkOrder, closeWorkOrder, cancelWorkOrder } from '@/api/work/order'
- import { getEquipmentFullPage } from '@/api/pipeNetwork/basic'
- import { listDept } from '@/api/system/dept'
- import { listUser } from '@/api/system/user'
- import { handleTree } from '@/utils/ruoyi'
- import { ORDER_STATUS_MAP, ORDER_TYPE_MAP, ORDER_LEVEL_MAP, orderStatusText, orderStatusType, orderTypeText, orderTypeTagType, orderLevelText, orderLevelType, orderLogText, orderLogType, canOperate, timeText } from './workOrderDict'
- // ========== 查询 & 列表(equipmentType 为空 = 查询所有设备工单) ==========
- 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({ orderType: null, orderLevel: null, orderStatus: null, keyword: '' })
- async function handleSearch() {
- listLoading.value = true
- try {
- const params = {}
- if (queryParams.orderType) params.orderType = queryParams.orderType
- 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.orderType = null
- 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) {
- 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]
- } 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 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 listUser({ pageNum: 1, pageSize: 200 })
- if (res.rows) {
- userMap.value = Object.fromEntries(res.rows.map(u => [u.userId, u.nickName || u.userName]))
- }
- } catch(e) { console.warn('加载用户列表失败', e) }
- }
- // ========== 派单(部门树 + 用户多选,参考排水/燃气工单) ==========
- 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([])
- const submitting = ref(false)
- 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
- handleSearch()
- if (selectedOrderId.value) handleSelectOrder(tableData.value.find(o => o.orderId === selectedOrderId.value) || {})
- } catch(e) { ElMessage.error('派单失败:' + (e.msg || e.message || '请重试')); console.error(e) }
- finally { submitting.value = false }
- }
- // ========== 核查/处理(参考排水/燃气工单) ==========
- const actionDialogVisible = ref(false)
- const actionTitle = ref('')
- const actionFormData = reactive({ solution: '' })
- const processBeforeImages = ref('')
- const imageUploadUrl = import.meta.env.VITE_APP_BASE_API + '/minio/upload'
- function handleVerify() {
- if (!canOperate.verify(selectedOrder.value)) {
- ElMessage.warning('当前工单不允许核查')
- return
- }
- actionTitle.value = '核查工单'
- actionFormData.solution = ''
- processBeforeImages.value = ''
- actionDialogVisible.value = true
- }
- function handleProcess() {
- if (!canOperate.process(selectedOrder.value)) {
- ElMessage.warning('当前工单不允许处理')
- return
- }
- ElMessageBox.alert(
- '<div style="padding: 10px; color: #666;">' +
- '<strong>维修操作指引:</strong><br/><br/>' +
- '• 在下方表格中可以上传处理前的照片(最多9张)<br/>' +
- '• 请选择本地图片或拍照上传<br/>' +
- '• 填写处理说明后点击"确定"提交<br/>' +
- '</div>',
- '开始维修',
- {
- dangerouslyUseHTMLString: true,
- confirmButtonText: '我已了解',
- center: false
- }
- ).then(() => {
- actionTitle.value = '处理工单'
- actionFormData.solution = ''
- processBeforeImages.value = ''
- actionDialogVisible.value = true
- }).catch(() => {})
- }
- 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 === '处理工单' && processBeforeImages.value) {
- data.maintenanceBeforeImg = processBeforeImages.value
- }
- if (actionTitle.value === '核查工单') {
- await verifyWorkOrder(data)
- } else {
- await processWorkOrder(data)
- }
- ElMessage.success(actionTitle.value + '成功')
- actionDialogVisible.value = false
- handleSearch()
- if (selectedOrderId.value) handleSelectOrder(tableData.value.find(o => o.orderId === selectedOrderId.value) || {})
- } 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: '' })
- const maintenanceAfterImages = ref('')
- function handleComplete() {
- if (!canOperate.complete(selectedOrder.value)) {
- ElMessage.warning('当前工单不允许提交验收')
- return
- }
- completeForm.result = ''
- maintenanceAfterImages.value = ''
- 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,
- maintenanceAfterImg: maintenanceAfterImages.value
- })
- ElMessage.success('提交验收成功')
- completeDialogVisible.value = false
- handleSearch()
- if (selectedOrderId.value) handleSelectOrder(tableData.value.find(o => o.orderId === selectedOrderId.value) || {})
- } 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
- handleSearch()
- if (selectedOrderId.value) handleSelectOrder(tableData.value.find(o => o.orderId === selectedOrderId.value) || {})
- } 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(() => {})
- }
- 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;
- }
- .image-group + .image-group {
- margin-top: 16px;
- }
- .image-label {
- display: block;
- margin-bottom: 8px;
- color: #606266;
- font-size: 13px;
- }
- .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>
|