DrainageWorkOrder.vue 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907
  1. <!-- 排水管网工单管理 -->
  2. <template>
  3. <div class="drainage-work-order">
  4. <!-- 顶部搜索区 -->
  5. <el-card class="search-card" shadow="never">
  6. <div class="search-form">
  7. <el-form :model="queryParams" ref="queryFormRef" :inline>
  8. <el-form-item label="类型">
  9. <el-select v-model="queryParams.orderType" placeholder="全部类型" clearable style="width: 150px">
  10. <el-option label="故障维修" :value="1" />
  11. <el-option label="日常巡检" :value="2" />
  12. <el-option label="设备保养" :value="3" />
  13. </el-select>
  14. </el-form-item>
  15. <el-form-item label="优先级">
  16. <el-select v-model="queryParams.orderLevel" placeholder="全部优先级" clearable style="width: 120px">
  17. <el-option label="紧急" :value="1" />
  18. <el-option label="一般" :value="2" />
  19. <el-option label="低" :value="3" />
  20. </el-select>
  21. </el-form-item>
  22. <el-form-item label="状态">
  23. <el-select v-model="queryParams.orderStatus" placeholder="全部状态" clearable style="width: 120px">
  24. <el-option label="待派单" :value="1" />
  25. <el-option label="待接单" :value="2" />
  26. <el-option label="已接单" :value="3" />
  27. <el-option label="处理中" :value="4" />
  28. <el-option label="已提交验收" :value="5" />
  29. <el-option label="已结案" :value="6" />
  30. <el-option label="已驳回" :value="7" />
  31. </el-select>
  32. </el-form-item>
  33. <el-form-item label="关键词">
  34. <el-input
  35. v-model="queryParams.keyword"
  36. placeholder="工单编号/设备编码"
  37. clearable
  38. style="width: 200px"
  39. />
  40. </el-form-item>
  41. <el-form-item>
  42. <el-button type="primary" @click="handleSearch">查询</el-button>
  43. <el-button @click="handleReset">重置</el-button>
  44. </el-form-item>
  45. </el-form>
  46. </div>
  47. </el-card>
  48. <!-- 主容器 -->
  49. <el-row :gutter="16" style="height: calc(100vh - 200px)">
  50. <!-- 左侧:工单列表 -->
  51. <el-col :span="8" style="height: 100%">
  52. <el-card class="box-card" shadow="always">
  53. <template #header>
  54. <div class="card-header">
  55. <span>工单列表</span>
  56. </div>
  57. </template>
  58. <!-- 工单列表 -->
  59. <el-scrollbar style="flex: 1">
  60. <div class="order-list">
  61. <div
  62. v-for="order in tableData"
  63. :key="order.orderId"
  64. class="order-item"
  65. :class="{ 'active': selectedOrderId === order.orderId }"
  66. @click="handleSelectOrder(order)"
  67. >
  68. <div class="order-header">
  69. <span class="order-no">{{ order.orderNo }}</span>
  70. <el-tag
  71. v-if="order.orderStatus === 1"
  72. type="warning"
  73. size="small"
  74. effect="light"
  75. class="status-badge"
  76. >待派单</el-tag>
  77. <el-tag
  78. v-else-if="order.orderStatus === 2"
  79. type="info"
  80. size="small"
  81. effect="light"
  82. class="status-badge"
  83. >待接单</el-tag>
  84. <el-tag
  85. v-else-if="order.orderStatus === 3"
  86. type="success"
  87. size="small"
  88. effect="light"
  89. class="status-badge"
  90. >已接单</el-tag>
  91. <el-tag
  92. v-else-if="order.orderStatus === 4"
  93. type="danger"
  94. size="small"
  95. effect="light"
  96. class="status-badge"
  97. >处理中</el-tag>
  98. <el-tag
  99. v-else-if="order.orderStatus === 5"
  100. type="success"
  101. size="small"
  102. effect="light"
  103. class="status-badge"
  104. >已提交验收</el-tag>
  105. <el-tag
  106. v-else-if="order.orderStatus === 6"
  107. type="info"
  108. size="small"
  109. effect="light"
  110. class="status-badge"
  111. >已结案</el-tag>
  112. <el-tag
  113. v-else-if="order.orderStatus === 7"
  114. type="danger"
  115. size="small"
  116. effect="light"
  117. class="status-badge"
  118. >已驳回</el-tag>
  119. </div>
  120. <div class="order-body">
  121. <div class="order-tags">
  122. <el-tag :type="getLevelTag(order.orderLevel)" size="small" effect="plain">{{ getLevelLabel(order.orderLevel) }}</el-tag>
  123. <el-tag :type="getTypeTag(order.orderType)" size="small" effect="plain">{{ getTypeLabel(order.orderType) }}</el-tag>
  124. </div>
  125. <div class="order-desc" v-if="order.orderDesc">{{ order.orderDesc }}</div>
  126. <div class="device-code">设备编号:{{ order.deviceCode || '-' }}</div>
  127. <div class="create-time">创建:{{ formatDate(order.createTime) }}</div>
  128. </div>
  129. </div>
  130. <el-empty v-if="tableData.length === 0" description="暂无数据" :image-size="100"></el-empty>
  131. </div>
  132. </el-scrollbar>
  133. <!-- 分页 -->
  134. <div class="pagination-wrapper">
  135. <el-pagination
  136. v-model:current-page="pageNum"
  137. v-model:page-size="pageSize"
  138. :total="total"
  139. layout="total, prev, pager, next"
  140. :page-sizes="[10, 20, 50, 100]"
  141. @size-change="handleSizeChange"
  142. @current-change="handlePageChange"
  143. class="pagination"
  144. />
  145. </div>
  146. </el-card>
  147. </el-col>
  148. <!-- 右侧:工单详情 -->
  149. <el-col :span="16" style="height: 100%">
  150. <el-card v-loading="detailLoading" class="box-card" shadow="always">
  151. <template #header>
  152. <div class="card-header">
  153. <span v-if="selectedOrder && selectedOrder.orderId">工单详情 - {{ selectedOrder.orderNo }}</span>
  154. <span v-else>请选择工单查看详情</span>
  155. </div>
  156. </template>
  157. <div v-if="selectedOrder && selectedOrder.orderId">
  158. <!-- 标签页 -->
  159. <el-tabs v-model="activeTab" class="detail-tabs">
  160. <!-- 工单信息 -->
  161. <el-tab-pane label="工单信息" name="info">
  162. <el-descriptions :column="2" border size="small">
  163. <el-descriptions-item label="工单编号">{{ selectedOrder.orderNo }}</el-descriptions-item>
  164. <el-descriptions-item label="工单状态">
  165. <el-tag
  166. v-if="selectedOrder.orderStatus === 1"
  167. type="warning"
  168. size="small"
  169. >待派单</el-tag>
  170. <el-tag
  171. v-else-if="selectedOrder.orderStatus === 2"
  172. type="info"
  173. size="small"
  174. >待接单</el-tag>
  175. <el-tag
  176. v-else-if="selectedOrder.orderStatus === 3"
  177. type="success"
  178. size="small"
  179. >已接单</el-tag>
  180. <el-tag
  181. v-else-if="selectedOrder.orderStatus === 4"
  182. type="danger"
  183. size="small"
  184. >处理中</el-tag>
  185. <el-tag
  186. v-else-if="selectedOrder.orderStatus === 5"
  187. type="success"
  188. size="small"
  189. >已提交验收</el-tag>
  190. <el-tag
  191. v-else-if="selectedOrder.orderStatus === 6"
  192. type="info"
  193. size="small"
  194. >已结案</el-tag>
  195. <el-tag
  196. v-else-if="selectedOrder.orderStatus === 7"
  197. type="danger"
  198. size="small"
  199. >已驳回</el-tag>
  200. </el-descriptions-item>
  201. <el-descriptions-item label="关联案件编号">{{ selectedOrder.caseNo || '-' }}</el-descriptions-item>
  202. <el-descriptions-item label="设备编号">{{ selectedOrder.deviceCode || '-' }}</el-descriptions-item>
  203. <el-descriptions-item label="优先级">
  204. <el-tag
  205. v-if="selectedOrder.orderLevel === 1"
  206. type="danger"
  207. size="small"
  208. >紧急</el-tag>
  209. <el-tag
  210. v-else-if="selectedOrder.orderLevel === 2"
  211. type="warning"
  212. size="small"
  213. >一般</el-tag>
  214. <el-tag
  215. v-else
  216. type="info"
  217. size="small"
  218. >低</el-tag>
  219. </el-descriptions-item>
  220. <el-descriptions-item label="工单类型">{{ getOrderTypeText(selectedOrder.orderType) }}</el-descriptions-item>
  221. <el-descriptions-item label="问题描述" :span="2">{{ selectedOrder.orderDesc }}</el-descriptions-item>
  222. <el-descriptions-item label="创建时间">{{ formatDate(selectedOrder.createTime) }}</el-descriptions-item>
  223. <el-descriptions-item label="更新时间">{{ formatDate(selectedOrder.updateTime) }}</el-descriptions-item>
  224. <el-descriptions-item label="运维状态">在用</el-descriptions-item>
  225. <el-descriptions-item label="在线状态">在线</el-descriptions-item>
  226. <el-descriptions-item label="报警状态">正常</el-descriptions-item>
  227. <el-descriptions-item label="处置结果">{{ selectedOrder.repairResult || '-' }}</el-descriptions-item>
  228. </el-descriptions>
  229. <!-- 工单图片 -->
  230. <div v-if="workImageList.length > 0" class="image-section">
  231. <h4 class="section-title">工单图片</h4>
  232. <div class="image-list">
  233. <image-preview
  234. v-for="(img, index) in workImageList"
  235. :key="index"
  236. :src="img"
  237. :width="100"
  238. :height="100"
  239. />
  240. </div>
  241. </div>
  242. <!-- 工单操作 -->
  243. <div class="action-section">
  244. <h4 class="section-title">工单操作</h4>
  245. <div class="action-buttons">
  246. <el-button
  247. v-if="canDispatch()"
  248. type="primary"
  249. icon="Download"
  250. @click="handleDispatch"
  251. >下派任务</el-button>
  252. <el-button
  253. v-if="canVerify()"
  254. type="warning"
  255. icon="Check"
  256. @click="handleVerify"
  257. >核查</el-button>
  258. <el-button
  259. v-if="canProcess()"
  260. type="success"
  261. icon="Edit"
  262. @click="handleProcess"
  263. >处理</el-button>
  264. <el-button
  265. v-if="canComplete()"
  266. type="danger"
  267. icon="Checked"
  268. @click="handleComplete"
  269. >提交验收</el-button>
  270. <el-button
  271. v-if="canReject()"
  272. type="info"
  273. icon="Close"
  274. @click="handleReject"
  275. >驳回</el-button>
  276. </div>
  277. </div>
  278. </el-tab-pane>
  279. <!-- 处理历史 -->
  280. <el-tab-pane label="处理历史" name="history">
  281. <el-timeline v-if="currentOrderLogs && currentOrderLogs.length > 0">
  282. <el-timeline-item
  283. v-for="log in currentOrderLogs"
  284. :key="log.logId"
  285. :timestamp="formatDate(log.operTime)"
  286. placement="top"
  287. :type="getLogType(log.operType)"
  288. :size="'large'"
  289. >
  290. <el-card shadow="hover">
  291. <div class="timeline-content">
  292. <div class="operator-info">
  293. <span class="operator-name">{{ log.operUserName }}</span>
  294. <span class="oper-type">· {{ getOperTypeLabel(log.operType) }}</span>
  295. </div>
  296. <div class="oper-desc">{{ log.operDesc }}</div>
  297. </div>
  298. </el-card>
  299. </el-timeline-item>
  300. </el-timeline>
  301. <el-empty v-else description="暂无处理记录" :image-size="100"></el-empty>
  302. </el-tab-pane>
  303. </el-tabs>
  304. </div>
  305. <el-empty v-else description="请从左侧选择工单" :image-size="150"></el-empty>
  306. </el-card>
  307. </el-col>
  308. </el-row>
  309. <!-- 派单弹窗 -->
  310. <el-dialog v-model="dispatchDialogVisible" title="下派任务 - 选择接单人" width="750px">
  311. <div style="display: flex; gap: 16px; min-height: 350px;">
  312. <!-- 左侧:部门树 -->
  313. <div style="width: 280px; border-right: 1px solid #e4e7ed; padding-right: 12px;">
  314. <el-input
  315. v-model="deptFilterText"
  316. placeholder="筛选部门"
  317. clearable
  318. size="small"
  319. style="margin-bottom: 8px"
  320. />
  321. <el-tree
  322. ref="deptTreeRef"
  323. :data="deptTreeData"
  324. :props="{ label: 'deptName', children: 'children' }"
  325. :filter-node-method="filterDeptNode"
  326. node-key="deptId"
  327. highlight-current
  328. @node-click="handleDeptClick"
  329. style="font-size: 14px"
  330. />
  331. </div>
  332. <!-- 右侧:用户列表 -->
  333. <div style="flex: 1; min-width: 0;">
  334. <el-table
  335. :data="userList"
  336. border
  337. size="small"
  338. max-height="300"
  339. ref="userTableRef"
  340. row-key="userId"
  341. @selection-change="handleSelectionChange"
  342. @row-click="handleRowClick"
  343. >
  344. <el-table-column type="selection" width="40" align="center" :reserve-selection="true" />
  345. <el-table-column prop="userName" label="用户名" width="100" />
  346. <el-table-column prop="nickName" label="昵称" width="100" />
  347. <el-table-column prop="dept.deptName" label="部门" show-overflow-tooltip />
  348. </el-table>
  349. <div v-if="userList.length === 0" style="text-align: center; color: #909399; padding: 40px 0;">请先在左侧选择部门</div>
  350. </div>
  351. </div>
  352. <el-form :model="dispatchForm" label-width="80px" style="margin-top: 16px">
  353. <el-form-item label="备注"><el-input v-model="dispatchForm.remarks" placeholder="请输入派单备注" /></el-form-item>
  354. </el-form>
  355. <template #footer>
  356. <span style="float: left; line-height: 32px; color: #606266;">
  357. 已选人员:<span v-if="selectedUserNames.length > 0" style="color: #409eff; font-weight: bold;">{{ selectedUserNames.join(', ') }}</span>
  358. <span v-else style="color: #909399">未选择</span>
  359. (<span v-if="dispatchForm.handlerIds && dispatchForm.handlerIds.length > 0">{{ dispatchForm.handlerIds.length }}</span><span v-else>0</span>)
  360. </span>
  361. <el-button @click="dispatchDialogVisible = false">取消</el-button>
  362. <el-button type="primary" @click="submitDispatch">确认</el-button>
  363. </template>
  364. </el-dialog>
  365. <!-- 核查/处理弹窗 -->
  366. <el-dialog v-model="actionDialogVisible" :title="actionTitle" width="600px">
  367. <el-input v-model="actionFormData.solution" type="textarea" :rows="6" placeholder="请输入说明文字" />
  368. <template #footer>
  369. <el-button @click="actionDialogVisible = false">取消</el-button>
  370. <el-button type="primary" @click="submitAction">确定</el-button>
  371. </template>
  372. </el-dialog>
  373. <!-- 完成/验收弹窗 -->
  374. <el-dialog v-model="completeDialogVisible" title="提交验收" width="500px">
  375. <el-form label-width="80px">
  376. <el-form-item label="验收意见">
  377. <el-input v-model="completeForm.result" type="textarea" :rows="4" />
  378. </el-form-item>
  379. </el-form>
  380. <template #footer>
  381. <el-button @click="completeDialogVisible = false">取消</el-button>
  382. <el-button type="primary" @click="submitComplete">提交</el-button>
  383. </template>
  384. </el-dialog>
  385. </div>
  386. </template>
  387. <script setup name="DrainageWorkOrder">
  388. import { ElMessage } from 'element-plus'
  389. import ImagePreview from '@/components/ImagePreview/index.vue'
  390. import { listWorkOrder, getWorkOrderDetail, dispatchWorkOrder, verifyWorkOrder, processWorkOrder, completeWorkOrder } from '@/api/work/order.js'
  391. import { listDept } from '@/api/system/dept.js'
  392. import { listUser } from '@/api/system/user.js'
  393. import { handleTree } from '@/utils/ruoyi.js'
  394. const listLoading = ref(false)
  395. const tableData = ref([])
  396. const pageNum = ref(1)
  397. const pageSize = ref(10)
  398. const total = ref(0)
  399. const queryFormRef = ref(null)
  400. const queryParams = reactive({
  401. orderType: null,
  402. orderLevel: null,
  403. keyword: '',
  404. // 排水管网工单:只查询排水类型设备工单
  405. equipmentType: 'drainage'
  406. })
  407. // 选中的工单
  408. const selectedOrderId = ref(null)
  409. const selectedOrder = ref({})
  410. const activeTab = ref('info')
  411. const currentOrderLogs = ref([])
  412. // 工单图片列表(多个用逗号分隔)
  413. const workImageList = computed(() => {
  414. if (!selectedOrder.value.workImage) return []
  415. return selectedOrder.value.workImage.split(',').filter(img => img && img.trim())
  416. })
  417. // 派单相关
  418. const dispatchDialogVisible = ref(false)
  419. const dispatchForm = reactive({ handlerIds: [], remarks: '' })
  420. const deptTreeRef = ref(null)
  421. const userTableRef = ref(null)
  422. const deptTreeData = ref([])
  423. const deptFilterText = ref('')
  424. const userList = ref([])
  425. const selectedUserList = ref([]) // 存储选中的用户行数据
  426. // 部门筛选
  427. watch(deptFilterText, (val) => { deptTreeRef.value && deptTreeRef.value.filter(val) })
  428. function filterDeptNode(value, data) {
  429. if (!value) return true
  430. return data.deptName.includes(value)
  431. }
  432. // 加载部门树
  433. async function loadDeptTree() {
  434. if (deptTreeData.value.length > 0) return
  435. try {
  436. const res = await listDept()
  437. deptTreeData.value = handleTree(res.data || [], 'deptId')
  438. } catch (e) { console.error('加载部门失败', e) }
  439. }
  440. // 点击部门加载用户
  441. async function handleDeptClick(node) {
  442. try {
  443. // 清除当前选择
  444. userTableRef.value && userTableRef.value.clearSelection()
  445. selectedUserList.value = []
  446. dispatchForm.handlerIds = []
  447. const res = await listUser({ deptId: node.deptId, pageNum: 1, pageSize: 100 })
  448. userList.value = res.rows || []
  449. } catch (e) { console.error('加载用户失败', e) }
  450. }
  451. // 选择变化时触发
  452. function handleSelectionChange(selection) {
  453. selectedUserList.value = selection
  454. dispatchForm.handlerIds = selection.map(item => item.userId)
  455. }
  456. // 点击行切换选中状态
  457. function handleRowClick(row) {
  458. userTableRef.value && userTableRef.value.toggleRowSelection(row)
  459. }
  460. // 获取已选人员昵称(用于显示)
  461. const selectedUserNames = computed(() => {
  462. return selectedUserList.value.map(item => item.nickName || item.userName)
  463. })
  464. /** 查询工单列表 */
  465. async function handleSearch() {
  466. listLoading.value = true
  467. try {
  468. const res = await listWorkOrder(queryParams, pageNum.value, pageSize.value)
  469. if (res.code === 200) {
  470. tableData.value = res.data.records || []
  471. total.value = res.data.total || 0
  472. }
  473. } catch (e) {
  474. ElMessage.error('查询失败')
  475. } finally {
  476. listLoading.value = false
  477. }
  478. }
  479. /** 重置搜索 */
  480. function handleReset() {
  481. queryFormRef.value && queryFormRef.value.resetFields()
  482. queryParams.orderType = null
  483. queryParams.orderLevel = null
  484. queryParams.keyword = ''
  485. pageNum.value = 1
  486. selectedOrderId.value = null
  487. selectedOrder.value = {}
  488. handleSearch()
  489. }
  490. /** 选择工单 */
  491. async function handleSelectOrder(order) {
  492. selectedOrderId.value = order.orderId
  493. selectedOrder.value = { ...order } // 克隆对象确保更新
  494. activeTab.value = 'info'
  495. currentOrderLogs.value = []
  496. // 加载工单详情和处理历史
  497. await loadOrderDetail(order.orderId)
  498. }
  499. /** 分页变化 */
  500. function handleSizeChange() {
  501. pageNum.value = 1
  502. selectedOrderId.value = null
  503. selectedOrder.value = {}
  504. handleSearch()
  505. }
  506. function handlePageChange() {
  507. selectedOrderId.value = null
  508. selectedOrder.value = {}
  509. handleSearch()
  510. }
  511. /** 派单、核查、处理、完成、驳回的判断函数(示例) */
  512. function canDispatch() { return selectedOrder.value.orderStatus === 1 }
  513. function canVerify() { return [2, 3].includes(selectedOrder.value.orderStatus) }
  514. function canProcess() { return [3, 4].includes(selectedOrder.value.orderStatus) }
  515. function canComplete() { return selectedOrder.value.orderStatus === 4 }
  516. function canReject() { return [1, 2].includes(selectedOrder.value.orderStatus) }
  517. function handleDispatch() {
  518. if (!canDispatch()) {
  519. ElMessage.warning('仅可对待派单工单进行派单操作')
  520. return
  521. }
  522. Object.assign(dispatchForm, { handlerIds: [], remarks: '' })
  523. selectedUserList.value = []
  524. userList.value = []
  525. loadDeptTree()
  526. dispatchDialogVisible.value = true
  527. }
  528. async function submitDispatch() {
  529. if (!dispatchForm.handlerIds || dispatchForm.handlerIds.length === 0) {
  530. ElMessage.warning('请选择至少一个接单人')
  531. return
  532. }
  533. try {
  534. await dispatchWorkOrder({
  535. orderId: selectedOrder.value.orderId,
  536. handlerIds: dispatchForm.handlerIds, // 传递数组
  537. remarks: dispatchForm.remarks
  538. })
  539. ElMessage.success('派单成功!已向 ' + dispatchForm.handlerIds.length + ' 人下发任务')
  540. dispatchDialogVisible.value = false
  541. handleSearch()
  542. } catch (e) {
  543. ElMessage.error('派单失败')
  544. }
  545. }
  546. /** 核查、处理相关 */
  547. const actionDialogVisible = ref(false)
  548. const actionTitle = ref('')
  549. const actionFormData = reactive({ solution: '' })
  550. function handleVerify() {
  551. if (![2, 3].includes(selectedOrder.value.orderStatus)) {
  552. ElMessage.warning('当前工单不允许核查')
  553. return
  554. }
  555. actionTitle.value = '核查工单'
  556. actionFormData.solution = ''
  557. actionDialogVisible.value = true
  558. }
  559. function handleProcess() {
  560. if (![3, 4].includes(selectedOrder.value.orderStatus)) {
  561. ElMessage.warning('当前工单不允许处理')
  562. return
  563. }
  564. actionTitle.value = '处理工单'
  565. actionFormData.solution = ''
  566. actionDialogVisible.value = true
  567. }
  568. async function submitAction() {
  569. if (!actionFormData.solution.trim()) {
  570. ElMessage.warning('请填写说明')
  571. return
  572. }
  573. try {
  574. const data = {
  575. orderId: selectedOrder.value.orderId,
  576. handleResult: 'solved',
  577. solution: actionFormData.solution
  578. }
  579. if (actionTitle.value === '核查工单') {
  580. await verifyWorkOrder(data)
  581. } else {
  582. await processWorkOrder(data)
  583. }
  584. ElMessage.success(actionTitle.value + '成功')
  585. actionDialogVisible.value = false
  586. handleSearch()
  587. } catch (e) {
  588. ElMessage.error('操作失败')
  589. }
  590. }
  591. /** 完成/验收相关 */
  592. const completeDialogVisible = ref(false)
  593. const completeForm = reactive({ result: '' })
  594. function handleComplete() {
  595. if (![4, 5].includes(selectedOrder.value.orderStatus)) {
  596. ElMessage.warning('当前工单不允许提交验收')
  597. return
  598. }
  599. completeForm.result = ''
  600. completeDialogVisible.value = true
  601. }
  602. async function submitComplete() {
  603. if (!completeForm.result.trim()) {
  604. ElMessage.warning('请填写验收意见')
  605. return
  606. }
  607. try {
  608. await completeWorkOrder(selectedOrder.value.orderId, completeForm.result)
  609. ElMessage.success('提交验收成功')
  610. completeDialogVisible.value = false
  611. handleSearch()
  612. } catch (e) {
  613. ElMessage.error('提交失败')
  614. }
  615. }
  616. /** 驳回相关 */
  617. function handleReject() {
  618. if (![1, 2].includes(selectedOrder.value.orderStatus)) {
  619. ElMessage.warning('当前工单不允许驳回')
  620. return
  621. }
  622. ElMessage.info('驳回功能待实现')
  623. // TODO: 实现驳回逻辑
  624. }
  625. /** 辅助函数 */
  626. function getOrderTypeText(type) {
  627. const map = { 1: '故障维修', 2: '日常巡检', 3: '设备保养' }
  628. return map[type] || '-'
  629. }
  630. function getOperTypeLabel(type) {
  631. const map = {
  632. 1: '创建工单', 2: '派单', 3: '接单', 4: '开始维修',
  633. 5: '提交验收', 6: '验收通过', 7: '驳回工单', 8: '申请延期'
  634. }
  635. return map[type] || '-'
  636. }
  637. function getLogType(type) {
  638. const map = {
  639. 1: 'primary', 2: 'success', 3: 'warning', 4: 'danger',
  640. 5: 'success', 6: 'success', 7: 'danger', 8: 'warning'
  641. }
  642. return map[type] || ''
  643. }
  644. function formatDate(dateStr) {
  645. if (!dateStr) return '-'
  646. return new Date(dateStr).toLocaleString('zh-CN')
  647. }
  648. /** 优先级标签 */
  649. function getLevelLabel(level) {
  650. const map = { 1: '紧急', 2: '一般', 3: '低' }
  651. return map[level] || '未知'
  652. }
  653. function getLevelTag(level) {
  654. const map = { 1: 'danger', 2: 'warning', 3: 'info' }
  655. return map[level] || 'info'
  656. }
  657. /** 工单类型标签 */
  658. function getTypeLabel(type) {
  659. const map = { 1: '故障维修', 2: '日常巡检', 3: '设备保养' }
  660. return map[type] || '未知'
  661. }
  662. function getTypeTag(type) {
  663. const map = { 1: 'danger', 2: 'primary', 3: 'success' }
  664. return map[type] || 'info'
  665. }
  666. /** 加载工单详情和处理历史 */
  667. async function loadOrderDetail(orderId) {
  668. try {
  669. const res = await getWorkOrderDetail(orderId)
  670. if (res.code === 200) {
  671. selectedOrder.value = { ...selectedOrder.value, ...res.data.order }
  672. currentOrderLogs.value = res.data.logs || []
  673. }
  674. } catch (e) {
  675. ElMessage.error('加载工单详情失败')
  676. }
  677. }
  678. onMounted(() => {
  679. handleSearch()
  680. })
  681. </script>
  682. <style scoped lang="scss">
  683. .drainage-work-order {
  684. padding: 0 16px 16px;
  685. .search-card {
  686. margin-bottom: 16px;
  687. :deep(.el-card__body) {
  688. padding: 16px;
  689. }
  690. }
  691. .search-form {
  692. .el-form {
  693. display: flex;
  694. flex-wrap: wrap;
  695. align-items: flex-start;
  696. gap: 12px;
  697. .el-form-item {
  698. margin-bottom: 0;
  699. min-width: 150px;
  700. &:last-child {
  701. margin-left: auto;
  702. }
  703. }
  704. }
  705. }
  706. .order-list {
  707. overflow-y: auto;
  708. }
  709. .order-item {
  710. padding: 16px;
  711. margin-bottom: 8px;
  712. border-radius: 8px;
  713. cursor: pointer;
  714. transition: all 0.2s;
  715. border: 1px solid #e4e7ed;
  716. &:hover {
  717. background-color: #f5f7fa;
  718. }
  719. &.active {
  720. background-color: #ecf5ff;
  721. border-color: #409eff;
  722. }
  723. .order-header {
  724. display: flex;
  725. justify-content: space-between;
  726. align-items: center;
  727. margin-bottom: 8px;
  728. .order-no {
  729. font-weight: bold;
  730. font-size: 14px;
  731. }
  732. .status-badge {
  733. min-width: 60px;
  734. }
  735. }
  736. .order-body {
  737. font-size: 12px;
  738. color: #606266;
  739. .order-tags {
  740. display: flex;
  741. gap: 6px;
  742. margin-bottom: 6px;
  743. }
  744. .order-desc {
  745. margin-top: 4px;
  746. color: #303133;
  747. font-size: 13px;
  748. overflow: hidden;
  749. text-overflow: ellipsis;
  750. white-space: nowrap;
  751. }
  752. .device-code, .create-time {
  753. margin-top: 4px;
  754. }
  755. }
  756. }
  757. .pagination-wrapper {
  758. padding: 16px 0;
  759. border-top: 1px solid #e4e7ed;
  760. margin-top: 8px;
  761. .pagination {
  762. display: flex;
  763. justify-content: flex-end;
  764. }
  765. }
  766. .box-card {
  767. height: 100%;
  768. display: flex;
  769. flex-direction: column;
  770. :deep(.el-card__body) {
  771. flex: 1;
  772. display: flex;
  773. flex-direction: column;
  774. overflow: hidden;
  775. }
  776. }
  777. .detail-tabs {
  778. margin-top: 16px;
  779. }
  780. .image-section {
  781. margin-top: 24px;
  782. padding-top: 24px;
  783. border-top: 1px solid #e4e7ed;
  784. .image-list {
  785. display: flex;
  786. flex-wrap: wrap;
  787. gap: 12px;
  788. }
  789. }
  790. .action-section {
  791. margin-top: 24px;
  792. padding-top: 24px;
  793. border-top: 1px solid #e4e7ed;
  794. .section-title {
  795. font-size: 16px;
  796. font-weight: bold;
  797. margin-bottom: 16px;
  798. }
  799. .action-buttons {
  800. display: flex;
  801. gap: 12px;
  802. flex-wrap: wrap;
  803. .el-button {
  804. min-width: 100px;
  805. }
  806. }
  807. }
  808. .timeline-content {
  809. .operator-info {
  810. font-size: 14px;
  811. margin-bottom: 8px;
  812. .operator-name {
  813. font-weight: bold;
  814. color: #303133;
  815. }
  816. .oper-type {
  817. color: #909399;
  818. }
  819. }
  820. .oper-desc {
  821. font-size: 14px;
  822. color: #606266;
  823. }
  824. }
  825. }
  826. </style>