Pārlūkot izejas kodu

refactor(manholeCover): 重构工单处置页面提升用户体验

- 使用Tailwind CSS重新设计页面布局和样式
- 将模拟数据替换为真实API数据源
- 实现工单列表和详情的完整数据绑定
- 添加部门树和用户选择功能支持工单分配
- 优化工单状态管理和操作权限控制
- 集成分页、搜索和状态筛选功能
- 改进工单处理流程包括派单、维修、验收等环节
- 添加完整的工单日志显示和延期处理功能
nahida 1 nedēļu atpakaļ
vecāks
revīzija
0449f85d00
1 mainītis faili ar 579 papildinājumiem un 379 dzēšanām
  1. 579 379
      src/views/subSystem/manholeCover/caseHandling/orderDisposal.vue

+ 579 - 379
src/views/subSystem/manholeCover/caseHandling/orderDisposal.vue

@@ -1,180 +1,175 @@
 <!--工单处置管理-->
 <template>
-  <div class="workorder-management">
-    <!-- 页面头部统计 -->
-    <div class="page-header">
-      <div class="header-title">
+  <div class="min-h-screen bg-[#f0f2f6] p-5">
+    <div class="mb-5 flex flex-wrap items-center justify-between gap-4 rounded-[20px] bg-white px-6 py-4">
+      <div class="flex items-center gap-[14px] text-[20px] font-600">
         <el-icon><Tickets /></el-icon>
         <span>工单任务管理中心</span>
         <el-tag type="primary" effect="dark">待办工单 {{ pendingCount }}</el-tag>
       </div>
-      <div class="header-stats">
-        <div class="stat" @click="filterStatus('')">
-          <div class="num">{{ totalOrders }}</div>
-          <div>全部工单</div>
-        </div>
-        <div class="stat" @click="filterStatus('pending')">
-          <div class="num warning">{{ pendingCount }}</div>
-          <div>待处理</div>
-        </div>
-        <div class="stat" @click="filterStatus('processing')">
-          <div class="num">{{ processingCount }}</div>
-          <div>处理中</div>
-        </div>
-        <div class="stat" @click="filterStatus('feedback')">
-          <div class="num">{{ feedbackCount }}</div>
-          <div>待审批</div>
-        </div>
-        <div class="stat" @click="filterStatus('completed')">
-          <div class="num">{{ completedCount }}</div>
-          <div>已完成</div>
-        </div>
+      <div class="flex flex-wrap gap-3">
+        <el-input
+          v-model="searchKeyword"
+          placeholder="搜索工单编号/案件编号"
+          clearable
+          :prefix-icon="Search"
+          class="w-[220px]"
+          @keyup.enter="loadOrders"
+        />
+        <el-button type="primary" @click="loadOrders">刷新</el-button>
       </div>
     </div>
 
-    <div class="main-layout">
-      <!-- 左侧工单列表 -->
-      <div class="order-list-panel">
-        <div class="list-header">
+    <div class="mb-5 flex flex-wrap gap-6">
+      <div class="min-w-[120px] cursor-pointer rounded-2xl bg-white px-4 py-3 text-center" :class="{ 'bg-[#ecf5ff]': statusFilter === '' }" @click="filterStatus('')">
+        <div class="text-[26px] font-bold">{{ totalOrders }}</div>
+        <div>全部工单</div>
+      </div>
+      <div class="min-w-[120px] cursor-pointer rounded-2xl bg-white px-4 py-3 text-center" :class="{ 'bg-[#ecf5ff]': statusFilter === 'pending' }" @click="filterStatus('pending')">
+        <div class="text-[26px] font-bold text-[#e6a23c]">{{ pendingCount }}</div>
+        <div>待处理</div>
+      </div>
+      <div class="min-w-[120px] cursor-pointer rounded-2xl bg-white px-4 py-3 text-center" :class="{ 'bg-[#ecf5ff]': statusFilter === 'processing' }" @click="filterStatus('processing')">
+        <div class="text-[26px] font-bold">{{ processingCount }}</div>
+        <div>处理中</div>
+      </div>
+      <div class="min-w-[120px] cursor-pointer rounded-2xl bg-white px-4 py-3 text-center" :class="{ 'bg-[#ecf5ff]': statusFilter === 'verify' }" @click="filterStatus('verify')">
+        <div class="text-[26px] font-bold">{{ verifyCount }}</div>
+        <div>待验收</div>
+      </div>
+      <div class="min-w-[120px] cursor-pointer rounded-2xl bg-white px-4 py-3 text-center" :class="{ 'bg-[#ecf5ff]': statusFilter === 'completed' }" @click="filterStatus('completed')">
+        <div class="text-[26px] font-bold">{{ completedCount }}</div>
+        <div>已完成</div>
+      </div>
+    </div>
+
+    <div class="flex gap-5">
+      <div class="w-[380px] flex flex-col overflow-hidden rounded-[20px] bg-white" v-loading="loading">
+        <div class="flex justify-between border-b border-[#e9ecef] px-4 py-4 font-600">
           <span>工单列表</span>
-          <el-input v-model="searchKeyword" placeholder="搜索工单/案件编号" prefix-icon="Search" clearable size="small" style="width: 180px" />
         </div>
-        <div class="order-list">
+        <div class="flex-1 overflow-y-auto p-3">
           <div
-              v-for="order in paginatedOrders"
-              :key="order.id"
-              class="order-item"
-              :class="{ active: selectedOrder?.id === order.id, [order.status]: true }"
-              @click="selectOrder(order)"
+            v-for="order in paginatedOrders"
+            :key="order.orderId"
+            class="mb-2 cursor-pointer rounded-[14px] border border-transparent bg-[#fafbfc] p-3"
+            :class="{ 'border-[#409eff] bg-[#ecf5ff]': selectedOrder?.orderId === order.orderId }"
+            @click="selectOrder(order)"
           >
-            <div class="order-header">
-              <span class="order-code">{{ order.code }}</span>
-              <el-tag :type="getStatusType(order.status)" size="small">{{ getStatusText(order.status) }}</el-tag>
+            <div class="mb-1.5 flex justify-between">
+              <span class="font-600">{{ order.orderNo }}</span>
+              <el-tag :type="order.orderStatusTag" size="small">{{ order.orderStatusText }}</el-tag>
             </div>
-            <div class="order-case">关联案件: {{ order.caseCode }}</div>
-            <div class="order-location">{{ order.address }}</div>
-            <div class="order-time">创建: {{ order.createTime }}</div>
+            <div class="text-[12px] text-[#666]">关联案件: {{ order.caseNo || '—' }}</div>
+            <div class="my-1 text-[12px] text-[#666]">设备编号: {{ order.deviceCode || '—' }}</div>
+            <div class="text-[12px] text-[#666]">创建: {{ order.createTimeText }}</div>
           </div>
+          <el-empty v-if="!paginatedOrders.length" description="暂无工单数据" :image-size="80" />
         </div>
-        <div class="pagination-area">
+        <div class="border-t border-[#eee] p-3 text-center">
           <el-pagination small layout="prev, pager, next" :total="filteredOrders.length" :page-size="pageSize" v-model:current-page="currentPage" />
         </div>
       </div>
 
-      <!-- 右侧工单详情与操作区 -->
-      <div class="detail-panel" v-if="selectedOrder">
+      <div class="flex-1 overflow-y-auto rounded-[20px] bg-white p-5" v-if="selectedOrder" v-loading="detailLoading">
         <el-tabs v-model="activeTab">
-          <!-- 工单基本信息与案件信息 -->
           <el-tab-pane label="工单信息" name="info">
-            <div class="info-section">
-              <div class="section-title">
-                <span><el-icon><Document /></el-icon> 工单基本信息</span>
-                <div class="assign-area">
-                  <el-select v-model="selectedWorker" placeholder="选择维护人员" size="small" style="width: 160px" clearable>
-                    <el-option v-for="worker in workers" :key="worker.id" :label="worker.name" :value="worker.id" />
-                  </el-select>
-                  <el-button type="primary" size="small" @click="assignWorker">下派任务</el-button>
-                </div>
-              </div>
-              <el-descriptions :column="2" border>
-                <el-descriptions-item label="工单编号">{{ selectedOrder.code }}</el-descriptions-item>
-                <el-descriptions-item label="工单状态">{{ getStatusText(selectedOrder.status) }}</el-descriptions-item>
-                <el-descriptions-item label="关联案件编号">
-                  <el-link type="primary" @click="viewCase">{{ selectedOrder.caseCode }}</el-link>
-                </el-descriptions-item>
-                <el-descriptions-item label="报警级别">
-                  <el-tag :type="selectedOrder.alertLevel === 'critical' ? 'danger' : 'warning'" size="small">
-                    {{ selectedOrder.alertLevel === 'critical' ? '严重' : '一般' }}
-                  </el-tag>
-                </el-descriptions-item>
-                <el-descriptions-item label="报警类型">{{ selectedOrder.alertType }}</el-descriptions-item>
-                <el-descriptions-item label="权属单位">{{ selectedOrder.ownerUnit }}</el-descriptions-item>
-                <el-descriptions-item label="报警描述" :span="2">{{ selectedOrder.alertDesc }}</el-descriptions-item>
-                <el-descriptions-item label="详细地址" :span="2">{{ selectedOrder.address }}</el-descriptions-item>
-                <el-descriptions-item label="用途">{{ selectedOrder.purpose || '市政排水' }}</el-descriptions-item>
-                <el-descriptions-item label="指派人">{{ selectedOrder.assignee || '未指派' }}</el-descriptions-item>
-              </el-descriptions>
+            <div class="mb-3 mt-4 flex items-center gap-2 text-[16px] font-600">
+              <span><el-icon><Document /></el-icon> 工单基本信息</span>
             </div>
+            <el-descriptions :column="2" border>
+              <el-descriptions-item label="工单ID">{{ selectedOrder.orderId || '—' }}</el-descriptions-item>
+              <el-descriptions-item label="工单编号">{{ selectedOrder.orderNo }}</el-descriptions-item>
+              <el-descriptions-item label="报警ID">{{ selectedOrder.alarmId || '—' }}</el-descriptions-item>
+              <el-descriptions-item label="工单状态">
+                <el-tag :type="selectedOrder.orderStatusTag">{{ selectedOrder.orderStatusText }}</el-tag>
+              </el-descriptions-item>
+              <el-descriptions-item label="案件ID">{{ selectedOrder.caseId || '—' }}</el-descriptions-item>
+              <el-descriptions-item label="关联案件编号">{{ selectedOrder.caseNo || '—' }}</el-descriptions-item>
+              <el-descriptions-item label="设备ID">{{ selectedOrder.deviceId || '—' }}</el-descriptions-item>
+              <el-descriptions-item label="设备编号">{{ selectedOrder.deviceCode || '—' }}</el-descriptions-item>
+              <el-descriptions-item label="优先级">
+                <el-tag :type="selectedOrder.orderLevelTag">{{ selectedOrder.orderLevelText }}</el-tag>
+              </el-descriptions-item>
+              <el-descriptions-item label="工单类型">{{ selectedOrder.orderTypeText }}</el-descriptions-item>
+              <el-descriptions-item label="问题描述" :span="2">{{ selectedOrder.orderDesc || '—' }}</el-descriptions-item>
+              <el-descriptions-item label="负责部门ID">{{ selectedOrder.deptId || '—' }}</el-descriptions-item>
+              <el-descriptions-item label="设备类别">{{ selectedOrder.equipmentTypeName || '—' }}</el-descriptions-item>
+              <el-descriptions-item label="接单人ID">{{ selectedOrder.receiveUser || '—' }}</el-descriptions-item>
+              <el-descriptions-item label="派单人ID">{{ selectedOrder.dispatchUser || '—' }}</el-descriptions-item>
+              <el-descriptions-item label="派单时间">{{ selectedOrder.dispatchTimeText }}</el-descriptions-item>
+              <el-descriptions-item label="创建时间">{{ selectedOrder.createTimeText }}</el-descriptions-item>
+              <el-descriptions-item label="计划完成时间">{{ selectedOrder.planFinishTimeText }}</el-descriptions-item>
+              <el-descriptions-item label="延期原因">{{ selectedOrder.delayedReason || '—' }}</el-descriptions-item>
+              <el-descriptions-item label="延期截止时间">{{ selectedOrder.delayedDeadlineText }}</el-descriptions-item>
+              <el-descriptions-item label="验收人ID">{{ selectedOrder.verifyUser || '—' }}</el-descriptions-item>
+              <el-descriptions-item label="验收时间">{{ selectedOrder.verifyTimeText }}</el-descriptions-item>
+              <el-descriptions-item label="实际完成时间">{{ selectedOrder.realFinishTimeText }}</el-descriptions-item>
+              <el-descriptions-item label="运维状态">{{ selectedOrder.currentStatusText }}</el-descriptions-item>
+              <el-descriptions-item label="在线状态">{{ selectedOrder.onlineStatusText }}</el-descriptions-item>
+              <el-descriptions-item label="报警状态">{{ selectedOrder.alarmStatusText }}</el-descriptions-item>
+              <el-descriptions-item label="状态更新时间">{{ selectedOrder.statusUpdateTimeText }}</el-descriptions-item>
+              <el-descriptions-item label="处置结果" :span="2">{{ selectedOrder.repairResult || '—' }}</el-descriptions-item>
+              <el-descriptions-item label="更新时间">{{ selectedOrder.updateTimeText }}</el-descriptions-item>
+            </el-descriptions>
 
-            <!-- 延期/缓办操作 -->
-            <div class="delay-section">
-              <div class="section-title"><el-icon><Timer /></el-icon> 工单延期/缓办</div>
-              <div class="delay-buttons">
-                <el-button size="small" type="warning" @click="openDelayDialog('延期')">申请延期</el-button>
-                <el-button size="small" type="info" @click="openDelayDialog('缓办')">申请缓办</el-button>
+            <div class="mt-5 border-t border-[#e9ecef] pt-3">
+              <div class="mb-3 mt-4 flex items-center gap-2 text-[16px] font-600">
+                <span><el-icon><Timer /></el-icon> 工单操作</span>
               </div>
-              <div v-if="selectedOrder.delayRecords?.length" class="delay-history">
-                <div v-for="(rec, idx) in selectedOrder.delayRecords" :key="idx" class="delay-item">
-                  {{ rec.type }} 至 {{ rec.deadline }},理由: {{ rec.reason }}
-                </div>
-              </div>
-            </div>
-          </el-tab-pane>
-
-          <!-- 处置反馈与审批 -->
-          <el-tab-pane label="处置反馈与审批" name="feedback">
-            <div class="feedback-section">
-              <div class="section-title"><el-icon><Picture /></el-icon> 现场处置反馈(负责人上报)</div>
-              <div v-if="selectedOrder.feedback">
-                <el-descriptions :column="1" border>
-                  <el-descriptions-item label="处置人员">{{ selectedOrder.feedback.workerName }}</el-descriptions-item>
-                  <el-descriptions-item label="处置时间">{{ selectedOrder.feedback.feedbackTime }}</el-descriptions-item>
-                  <el-descriptions-item label="处置说明">{{ selectedOrder.feedback.description }}</el-descriptions-item>
-                  <el-descriptions-item label="现场照片">
-                    <div class="feedback-images">
-                      <el-image v-for="(img, idx) in selectedOrder.feedback.images" :key="idx" :src="img" fit="cover" class="feedback-img" preview-teleported />
-                    </div>
-                  </el-descriptions-item>
-                </el-descriptions>
-                <!-- 周边视频监控 -->
-                <div class="video-section" v-if="selectedOrder.surroundVideo">
-                  <div class="sub-title"><el-icon><VideoCamera /></el-icon> 井盖周边视频监控</div>
-                  <div class="video-preview" @click="playVideo">点击查看周边监控录像</div>
-                </div>
+              <div class="mb-3 flex items-center gap-3" v-if="canDispatch(selectedOrder)">
+                <el-input v-model="receiveUserDisplay" readonly placeholder="请选择接单人" class="w-[260px]" />
+                <el-button @click="openDispatchDialog">选择人员</el-button>
+                <el-button type="primary" @click="dispatchOrder">下派任务</el-button>
               </div>
-              <div v-else class="no-feedback">暂无处置反馈信息,等待负责人上报</div>
-
-              <!-- 审批操作栏 -->
-              <div class="approval-section" v-if="selectedOrder.status === 'feedback'">
-                <div class="section-title"><el-icon><Checked /></el-icon> 工单审批</div>
-                <div class="approval-buttons">
-                  <el-button type="success" @click="approveOrder(true)">审批通过</el-button>
-                  <el-button type="danger" @click="approveOrder(false)">驳回重办</el-button>
-                </div>
-                <el-input type="textarea" v-model="approvalRemark" placeholder="审批意见(可选)" rows="2" style="margin-top: 12px" />
-              </div>
-              <div v-else-if="selectedOrder.status === 'completed'" class="approved-info">
-                <el-alert title="工单已完成审批" type="success" :closable="false" show-icon />
+              <div class="flex flex-wrap gap-3">
+                <el-button v-if="canStartRepair(selectedOrder)" type="primary" @click="startRepair">开始维修</el-button>
+                <el-button v-if="canSubmitVerify(selectedOrder)" type="warning" @click="submitVerify">提交验收</el-button>
+                <el-button v-if="canApprove(selectedOrder)" type="success" @click="approveOrder(true)">验收通过</el-button>
+                <el-button v-if="canApprove(selectedOrder)" type="danger" @click="approveOrder(false)">驳回工单</el-button>
+                <el-button v-if="canApproveDelay(selectedOrder)" type="success" @click="approveDelay">延期审核通过</el-button>
+                <el-button v-if="canRejectDelay(selectedOrder)" type="danger" @click="rejectDelay">驳回延期</el-button>
+                <el-button v-if="canDelay(selectedOrder)" type="info" plain @click="openDelayDialog">申请延期</el-button>
               </div>
+              <el-empty v-if="!hasOperationButtons(selectedOrder)" description="当前状态暂无可操作项" :image-size="80" />
             </div>
           </el-tab-pane>
 
-          <!-- 工单处理历史 -->
           <el-tab-pane label="处理历史" name="history">
-            <el-timeline>
-              <el-timeline-item v-for="log in selectedOrder.processLogs" :key="log.id" :timestamp="log.time" :type="log.type">
-                {{ log.content }}
-                <span v-if="log.operator" class="operator">操作人: {{ log.operator }}</span>
+            <el-timeline v-if="selectedOrder.workOrderLogList?.length">
+              <el-timeline-item
+                v-for="log in selectedOrder.workOrderLogList"
+                :key="log.logId"
+                :timestamp="formatDateTime(log.operTime)"
+                :type="getLogType(log.operType)"
+              >
+                {{ log.operDesc }}
+              <span v-if="log.operUserName" class="ml-3 text-[12px] text-[#909399]">操作人: {{ log.operUserName }}</span>
               </el-timeline-item>
             </el-timeline>
+            <el-empty v-else description="暂无工单日志" :image-size="80" />
           </el-tab-pane>
         </el-tabs>
       </div>
 
-      <!-- 未选中状态 -->
-      <div v-else class="empty-panel">
+      <div v-else class="flex flex-1 items-center justify-center rounded-[20px] bg-white">
         <el-empty description="请从左侧选择工单进行处理" :image-size="140" />
       </div>
     </div>
 
-    <!-- 延期/缓办对话框 -->
-    <el-dialog v-model="delayDialogVisible" :title="delayType + '申请'" width="450px">
-      <el-form>
-        <el-form-item :label="delayType === '延期' ? '延期截止时间' : '缓办截止时间'">
-          <el-date-picker v-model="delayDeadline" type="datetime" placeholder="选择截止时间" format="YYYY-MM-DD HH:mm" />
+    <el-dialog v-model="delayDialogVisible" title="申请延期" width="450px">
+      <el-form label-width="88px">
+        <el-form-item label="截止时间">
+          <el-date-picker
+            v-model="delayDeadline"
+            type="datetime"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            placeholder="选择截止时间"
+            class="w-full"
+          />
         </el-form-item>
         <el-form-item label="申请理由">
-          <el-input type="textarea" v-model="delayReason" placeholder="请详细说明延期或缓办理由" rows="3" />
+          <el-input type="textarea" v-model="delayReason" placeholder="请详细说明延期理由" rows="3" />
         </el-form-item>
       </el-form>
       <template #footer>
@@ -182,286 +177,491 @@
         <el-button type="primary" @click="submitDelay">提交申请</el-button>
       </template>
     </el-dialog>
+
+    <el-dialog v-model="dispatchDialogVisible" title="下派任务" width="860px">
+      <div class="flex gap-4">
+        <div class="w-[260px] overflow-auto rounded-lg border border-[#ebeef5] p-3">
+          <div class="mb-3 font-600">部门树</div>
+          <el-input
+            v-model="deptKeyword"
+            placeholder="筛选部门"
+            clearable
+            size="small"
+            class="mb-3"
+          />
+          <el-tree
+            ref="deptTreeRef"
+            :data="deptOptions"
+            node-key="id"
+            :props="{ label: 'label', children: 'children' }"
+            default-expand-all
+            highlight-current
+            :filter-node-method="filterDeptNode"
+            @node-click="handleDeptClick"
+          />
+        </div>
+        <div class="flex-1 rounded-lg border border-[#ebeef5] p-3">
+          <div class="mb-3 font-600">用户列表</div>
+          <el-table
+            :data="dispatchUsers"
+            height="360"
+            border
+            highlight-current-row
+            v-loading="dispatchUserLoading"
+            @current-change="handleUserCurrentChange"
+            @row-click="handleUserCurrentChange"
+          >
+            <el-table-column prop="userId" label="ID" width="90" />
+            <el-table-column prop="userName" label="用户名" min-width="120" />
+            <el-table-column prop="nickName" label="昵称" min-width="120" />
+            <el-table-column prop="dept.deptName" label="部门" min-width="140" show-overflow-tooltip />
+          </el-table>
+          <div class="mt-3 text-[#606266]">
+            已选人员:{{ dispatchSelectedUser ? `${dispatchSelectedUser.userName} / ID: ${dispatchSelectedUser.userId}` : '未选择' }}
+          </div>
+        </div>
+      </div>
+      <template #footer>
+        <el-button @click="dispatchDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="confirmDispatchUser">确认选择</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
-import { ref, computed } from 'vue'
-import { Tickets, Search, Document, Timer, Picture, VideoCamera, Checked } from '@element-plus/icons-vue'
-import { ElMessage } from 'element-plus'
-
-// 模拟维护人员列表
-const workers = ref([
-  { id: 'w1', name: '张建国 - 维修一组' },
-  { id: 'w2', name: '李志强 - 维修二组' },
-  { id: 'w3', name: '王大明 - 应急抢修队' },
-  { id: 'w4', name: '陈辉 - 巡检专员' }
-])
-
-// 模拟工单数据
-const mockOrders = ref([
-  {
-    id: 1, code: 'WO-20250402-001', caseCode: 'CASE-20250402-001', alertLevel: 'critical', alertType: '倾斜超标',
-    alertDesc: '井盖倾斜角度14.2°,超过阈值8°,有跌落风险', ownerUnit: '市政工程管理处', address: '人民路与解放路口东50m',
-    purpose: '主干道排水', status: 'pending', createTime: '2025-04-02 14:25:00', assignee: null,
-    processLogs: [{ id: 1, time: '2025-04-02 14:25:00', content: '工单自动创建', type: 'info', operator: '系统' }],
-    delayRecords: [], feedback: null
-  },
-  {
-    id: 2, code: 'WO-20250402-002', caseCode: 'CASE-20250402-002', alertLevel: 'critical', alertType: '水位超限',
-    alertDesc: '井下水位62cm,超过阈值40cm,溢水风险', ownerUnit: '水务集团', address: '滨江路化工园区南门',
-    purpose: '化工区雨水排放', status: 'processing', createTime: '2025-04-02 14:00:00', assignee: '张建国',
-    processLogs: [
-      { id: 1, time: '2025-04-02 14:00:00', content: '工单自动创建', type: 'info', operator: '系统' },
-      { id: 2, time: '2025-04-02 14:30:00', content: '已指派张建国前往处置', type: 'primary', operator: '调度中心' }
-    ],
-    delayRecords: [],
-    feedback: null
-  },
-  {
-    id: 3, code: 'WO-20250402-003', caseCode: 'CASE-20250402-003', alertLevel: 'warning', alertType: '异常震动',
-    alertDesc: '异常震动112mg,超过阈值80mg,疑似外力冲击', ownerUnit: '市政设施管理处', address: '古城街北段树下',
-    purpose: '老城区排水', status: 'feedback', createTime: '2025-04-02 12:15:00', assignee: '李志强',
-    surroundVideo: true,
-    feedback: {
-      workerName: '李志强', feedbackTime: '2025-04-02 15:20:00', description: '现场井盖因车辆碾轧移位,已复位加固,设备恢复正常',
-      images: ['https://fuss10.elemecdn.com/1/34/19aa98b1fcb2781c4fba33d850549jpeg.jpeg']
-    },
-    processLogs: [
-      { id: 1, time: '2025-04-02 12:15:00', content: '工单自动创建', type: 'info', operator: '系统' },
-      { id: 2, time: '2025-04-02 13:00:00', content: '指派李志强现场处置', type: 'primary', operator: '调度中心' },
-      { id: 3, time: '2025-04-02 15:20:00', content: '负责人提交处置反馈,待审批', type: 'warning', operator: '李志强' }
-    ],
-    delayRecords: []
-  },
-  {
-    id: 4, code: 'WO-20250402-004', caseCode: 'CASE-20250402-004', alertLevel: 'warning', alertType: '井盖开启异常',
-    alertDesc: '井盖非正常开启', ownerUnit: '市政工程管理处', address: '南港路旧车站段',
-    purpose: '市政井盖', status: 'completed', createTime: '2025-04-02 11:50:00', assignee: '王大明',
-    feedback: {
-      workerName: '王大明', feedbackTime: '2025-04-02 14:10:00', description: '经核实为附近施工人员误开,已关闭并加锁',
-      images: ['https://fuss10.elemecdn.com/3/5e/4bbf4c1b1b4f1e1c0b4b4c1b1b4f1e1c.jpeg']
-    },
-    processLogs: [
-      { id: 1, time: '2025-04-02 11:50:00', content: '工单创建', type: 'info', operator: '系统' },
-      { id: 2, time: '2025-04-02 13:00:00', content: '指派王大明处置', type: 'primary', operator: '调度中心' },
-      { id: 3, time: '2025-04-02 14:10:00', content: '提交反馈并审批通过', type: 'success', operator: '审批人' }
-    ],
-    delayRecords: [],
-    approved: true
-  }
-])
+import {computed, nextTick, onMounted, ref, watch} from 'vue'
+import {Document, Search, Tickets, Timer} from '@element-plus/icons-vue'
+import {ElMessage} from 'element-plus'
+import {getManholeWorkOrderDetail, getManholeWorkOrderPage, operManholeWorkOrder} from '@/api/pipeNetwork/basic'
+import {deptTreeSelect, listUser} from '@/api/system/user'
+
+const DEFAULT_OPER_USER_ID = 1
 
+const orders = ref([])
+const loading = ref(false)
+const detailLoading = ref(false)
 const searchKeyword = ref('')
 const statusFilter = ref('')
 const currentPage = ref(1)
 const pageSize = 5
-const selectedOrder = ref(mockOrders.value[0])
-const selectedWorker = ref('')
+const selectedOrder = ref(null)
 const activeTab = ref('info')
+const receiveUserId = ref(null)
+const receiveUserDisplay = ref('')
 const delayDialogVisible = ref(false)
-const delayType = ref('延期')
 const delayDeadline = ref('')
 const delayReason = ref('')
-const approvalRemark = ref('')
+const dispatchDialogVisible = ref(false)
+const dispatchUserLoading = ref(false)
+const deptOptions = ref([])
+const deptKeyword = ref('')
+const selectedDeptId = ref(undefined)
+const dispatchUsers = ref([])
+const dispatchSelectedUser = ref(null)
+const deptTreeRef = ref(null)
 
-// 过滤与分页
-const filteredOrders = computed(() => {
-  let list = mockOrders.value
-  if (statusFilter.value) list = list.filter(o => o.status === statusFilter.value)
-  if (searchKeyword.value) {
-    const kw = searchKeyword.value.toLowerCase()
-    list = list.filter(o => o.code.toLowerCase().includes(kw) || o.caseCode.toLowerCase().includes(kw))
+function formatDateTime(value) {
+  if (!value) return '—'
+  return String(value).replace('T', ' ')
+}
+
+function getOrderLevelMeta(level) {
+  const map = {
+    1: { text: '紧急', tag: 'danger' },
+    2: { text: '一般', tag: 'warning' },
+    3: { text: '低', tag: 'info' }
   }
-  return list
+  return map[Number(level)] || { text: '—', tag: 'info' }
+}
+
+function getOrderTypeText(type) {
+  const map = {
+    1: '故障维修',
+    2: '日常巡检',
+    3: '设备保养'
+  }
+  return map[Number(type)] || '—'
+}
+
+function getOrderStatusMeta(status) {
+  const map = {
+    1: { key: 'pending', text: '待派单', tag: 'danger' },
+    2: { key: 'pending', text: '待接单', tag: 'danger' },
+    3: { key: 'processing', text: '已接单', tag: 'warning' },
+    4: { key: 'processing', text: '处理中', tag: 'warning' },
+    5: { key: 'verify', text: '待验收', tag: 'primary' },
+    6: { key: 'completed', text: '已结案', tag: 'success' },
+    7: { key: 'completed', text: '已驳回', tag: 'danger' },
+    8: { key: 'processing', text: '延期审核', tag: 'warning' },
+    9: { key: 'completed', text: '已延期', tag: 'warning' }
+  }
+  return map[Number(status)] || { key: '', text: '—', tag: 'info' }
+}
+
+function getCurrentStatusText(status) {
+  const map = {
+    1: '在用',
+    2: '闲置',
+    3: '维修',
+    4: '报废',
+    5: '待入库'
+  }
+  return map[Number(status)] || '—'
+}
+
+function getOnlineStatusText(status) {
+  const map = {
+    0: '离线',
+    1: '在线'
+  }
+  return map[Number(status)] || '—'
+}
+
+function getAlarmStatusText(status) {
+  const map = {
+    0: '正常',
+    1: '报警'
+  }
+  return map[Number(status)] || '—'
+}
+
+function normalizeOrder(item) {
+  const levelMeta = getOrderLevelMeta(item.orderLevel)
+  const statusMeta = getOrderStatusMeta(item.orderStatus)
+  return {
+    ...item,
+    orderLevelText: levelMeta.text,
+    orderLevelTag: levelMeta.tag,
+    orderTypeText: getOrderTypeText(item.orderType),
+    orderStatusKey: statusMeta.key,
+    orderStatusText: statusMeta.text,
+    orderStatusTag: statusMeta.tag,
+    createTimeText: formatDateTime(item.createTime),
+    dispatchTimeText: formatDateTime(item.dispatchTime),
+    planFinishTimeText: formatDateTime(item.planFinishTime),
+    delayedDeadlineText: formatDateTime(item.delayedDeadline),
+    realFinishTimeText: formatDateTime(item.realFinishTime),
+    verifyTimeText: formatDateTime(item.verifyTime),
+    updateTimeText: formatDateTime(item.updateTime),
+    currentStatusText: getCurrentStatusText(item.equipmentStatus?.currentStatus),
+    onlineStatusText: getOnlineStatusText(item.equipmentStatus?.onlineStatus),
+    alarmStatusText: getAlarmStatusText(item.equipmentStatus?.alarmStatus),
+    statusUpdateTimeText: formatDateTime(item.equipmentStatus?.statusUpdateTime)
+  }
+}
+
+const filteredOrders = computed(() => {
+  if (!statusFilter.value) return orders.value
+  return orders.value.filter(item => item.orderStatusKey === statusFilter.value)
 })
+
 const paginatedOrders = computed(() => {
   const start = (currentPage.value - 1) * pageSize
   return filteredOrders.value.slice(start, start + pageSize)
 })
 
-const totalOrders = computed(() => mockOrders.value.length)
-const pendingCount = computed(() => mockOrders.value.filter(o => o.status === 'pending').length)
-const processingCount = computed(() => mockOrders.value.filter(o => o.status === 'processing').length)
-const feedbackCount = computed(() => mockOrders.value.filter(o => o.status === 'feedback').length)
-const completedCount = computed(() => mockOrders.value.filter(o => o.status === 'completed').length)
-
-const filterStatus = (status) => { statusFilter.value = status; currentPage.value = 1 }
-const selectOrder = (order) => { selectedOrder.value = order; selectedWorker.value = order.assignee ? workers.value.find(w => w.name === order.assignee)?.id || '' : '' }
-
-const getStatusType = (status) => {
-  if (status === 'pending') return 'danger'
-  if (status === 'processing') return 'warning'
-  if (status === 'feedback') return 'info'
-  return 'success'
-}
-const getStatusText = (status) => {
-  const map = { pending: '待处理', processing: '处理中', feedback: '待审批', completed: '已完成' }
-  return map[status] || status
-}
-
-// 下派任务
-const assignWorker = () => {
-  if (!selectedWorker.value) { ElMessage.warning('请选择维护人员'); return }
-  const worker = workers.value.find(w => w.id === selectedWorker.value)
-  if (selectedOrder.value.status === 'pending') {
-    selectedOrder.value.status = 'processing'
-    selectedOrder.value.assignee = worker.name
-    selectedOrder.value.processLogs.push({
-      id: Date.now(),
-      time: new Date().toLocaleString(),
-      content: `已下派任务给 ${worker.name}`,
-      type: 'primary',
-      operator: '平台调度员'
+const totalOrders = computed(() => orders.value.length)
+const pendingCount = computed(() => orders.value.filter(item => item.orderStatusKey === 'pending').length)
+const processingCount = computed(() => orders.value.filter(item => item.orderStatusKey === 'processing').length)
+const verifyCount = computed(() => orders.value.filter(item => item.orderStatusKey === 'verify').length)
+const completedCount = computed(() => orders.value.filter(item => item.orderStatusKey === 'completed').length)
+
+async function loadOrders() {
+  loading.value = true
+  try {
+    const keyword = searchKeyword.value.trim()
+    const res = await getManholeWorkOrderPage({
+      pageNum: 1,
+      pageSize: 200,
+      orderNo: keyword || undefined,
+      caseNo: keyword || undefined
     })
-    ElMessage.success(`工单已下派至 ${worker.name}`)
-  } else {
-    ElMessage.warning('当前工单状态不可下派')
+    const records = Array.isArray(res?.data?.records) ? res.data.records : []
+    orders.value = records.map(normalizeOrder)
+    currentPage.value = 1
+    const first = filteredOrders.value[0]
+    if (!first) {
+      selectedOrder.value = null
+      return
+    }
+    if (selectedOrder.value) {
+      const matched = orders.value.find(item => item.orderId === selectedOrder.value.orderId)
+      if (matched) {
+        await selectOrder(matched)
+        return
+      }
+    }
+    await selectOrder(first)
+  } catch (error) {
+    orders.value = []
+    selectedOrder.value = null
+    ElMessage.error('工单列表加载失败')
+  } finally {
+    loading.value = false
   }
 }
 
-// 延期/缓办
-const openDelayDialog = (type) => {
-  delayType.value = type
-  delayDialogVisible.value = true
+async function selectOrder(order) {
+  detailLoading.value = true
+  receiveUserId.value = order.receiveUser || null
+  receiveUserDisplay.value = order.receiveUser ? `用户ID: ${order.receiveUser}` : ''
+  try {
+    const res = await getManholeWorkOrderDetail(order.orderId)
+    selectedOrder.value = normalizeOrder(res?.data || order)
+    receiveUserId.value = selectedOrder.value.receiveUser || null
+    receiveUserDisplay.value = selectedOrder.value.receiveUser ? `用户ID: ${selectedOrder.value.receiveUser}` : ''
+  } catch (error) {
+    selectedOrder.value = order
+    ElMessage.error('工单详情加载失败')
+  } finally {
+    detailLoading.value = false
+  }
+}
+
+function filterStatus(status) {
+  statusFilter.value = status
+  currentPage.value = 1
+  const first = filteredOrders.value[0]
+  if (first) {
+    selectOrder(first)
+  }
+}
+
+function canDispatch(order) {
+  return Number(order.orderStatus) === 1
+}
+
+function canStartRepair(order) {
+  return [2, 3].includes(Number(order.orderStatus))
+}
+
+function canSubmitVerify(order) {
+  return Number(order.orderStatus) === 4
+}
+
+function canApprove(order) {
+  return Number(order.orderStatus) === 5
+}
+
+function canApproveDelay(order) {
+  return Number(order.orderStatus) === 8
+}
+
+function canRejectDelay(order) {
+  return Number(order.orderStatus) === 8
+}
+
+function canDelay(order) {
+  return [2, 3, 4].includes(Number(order.orderStatus))
 }
-const submitDelay = () => {
-  if (!delayDeadline.value) { ElMessage.warning('请选择截止时间'); return }
-  if (!delayReason.value) { ElMessage.warning('请填写申请理由'); return }
-  const record = {
-    type: delayType.value,
-    deadline: new Date(delayDeadline.value).toLocaleString(),
-    reason: delayReason.value,
-    applyTime: new Date().toLocaleString()
+
+function hasOperationButtons(order) {
+  if (!order) return false
+  return (
+    canDispatch(order) ||
+    canStartRepair(order) ||
+    canSubmitVerify(order) ||
+    canApprove(order) ||
+    canApproveDelay(order) ||
+    canRejectDelay(order) ||
+    canDelay(order)
+  )
+}
+
+async function operateCurrent(operType, operDesc, extra = {}) {
+  if (!selectedOrder.value?.orderId) return
+  await operManholeWorkOrder({
+    orderId: selectedOrder.value.orderId,
+    operType,
+    operDesc,
+    operUserId: DEFAULT_OPER_USER_ID,
+    ...extra
+  })
+  await loadOrders()
+}
+
+async function dispatchOrder() {
+  if (!receiveUserId.value) {
+    ElMessage.warning('请先选择接单人')
+    return
   }
-  if (!selectedOrder.value.delayRecords) selectedOrder.value.delayRecords = []
-  selectedOrder.value.delayRecords.push(record)
-  selectedOrder.value.processLogs.push({
-    id: Date.now(),
-    time: new Date().toLocaleString(),
-    content: `申请${delayType.value},截止时间:${record.deadline},理由:${delayReason.value}`,
-    type: 'warning',
-    operator: '工单负责人'
+  try {
+    await operateCurrent(2, `下派工单至用户${receiveUserId.value}`, { receiveUser: Number(receiveUserId.value) })
+    ElMessage.success('工单下派成功')
+  } catch (error) {
+    ElMessage.error(error?.message || '工单下派失败')
+  }
+}
+
+function filterDeptNode(value, data) {
+  if (!value) return true
+  return data.label?.includes(value)
+}
+
+watch(deptKeyword, value => {
+  nextTick(() => {
+    deptTreeRef.value?.filter(value)
   })
-  ElMessage.success(`${delayType.value}申请已提交,待审批`)
-  delayDialogVisible.value = false
+})
+
+async function loadDeptTree() {
+  const res = await deptTreeSelect()
+  deptOptions.value = Array.isArray(res?.data) ? res.data : []
+}
+
+async function loadDispatchUsers(deptId) {
+  dispatchUserLoading.value = true
+  try {
+    const res = await listUser({
+      pageNum: 1,
+      pageSize: 100,
+      deptId,
+      status: '0'
+    })
+    dispatchUsers.value = Array.isArray(res?.rows) ? res.rows : []
+  } catch (error) {
+    dispatchUsers.value = []
+    ElMessage.error('用户列表加载失败')
+  } finally {
+    dispatchUserLoading.value = false
+  }
+}
+
+async function openDispatchDialog() {
+  dispatchDialogVisible.value = true
+  dispatchSelectedUser.value = null
+  dispatchUsers.value = []
+  deptKeyword.value = ''
+  if (!deptOptions.value.length) {
+    await loadDeptTree()
+  }
+  const firstDept = findFirstDept(deptOptions.value)
+  if (firstDept?.id) {
+    selectedDeptId.value = firstDept.id
+    await nextTick()
+    deptTreeRef.value?.setCurrentKey(firstDept.id)
+    await loadDispatchUsers(firstDept.id)
+  }
+}
+
+function findFirstDept(nodes = []) {
+  for (const node of nodes) {
+    if (node?.id) return node
+    if (node?.children?.length) {
+      const child = findFirstDept(node.children)
+      if (child) return child
+    }
+  }
+  return null
+}
+
+function handleDeptClick(data) {
+  selectedDeptId.value = data.id
+  loadDispatchUsers(data.id)
+}
+
+function handleUserCurrentChange(row) {
+  dispatchSelectedUser.value = row || null
+}
+
+function confirmDispatchUser() {
+  if (!dispatchSelectedUser.value?.userId) {
+    ElMessage.warning('请选择用户')
+    return
+  }
+  receiveUserId.value = dispatchSelectedUser.value.userId
+  receiveUserDisplay.value = `${dispatchSelectedUser.value.userName} / ID: ${dispatchSelectedUser.value.userId}`
+  dispatchDialogVisible.value = false
+}
+
+async function startRepair() {
+  try {
+    await operateCurrent(4, '开始维修')
+    ElMessage.success('已开始维修')
+  } catch (error) {
+    ElMessage.error(error?.message || '开始维修失败')
+  }
+}
+
+async function submitVerify() {
+  try {
+    await operateCurrent(5, '提交验收')
+    ElMessage.success('已提交验收')
+  } catch (error) {
+    ElMessage.error(error?.message || '提交验收失败')
+  }
+}
+
+async function approveOrder(isApproved) {
+  try {
+    await operateCurrent(isApproved ? 6 : 7, isApproved ? '验收通过' : '驳回工单')
+    ElMessage.success(isApproved ? '验收已通过' : '工单已驳回')
+  } catch (error) {
+    ElMessage.error(error?.message || '工单操作失败')
+  }
+}
+
+async function approveDelay() {
+  try {
+    await operateCurrent(9, '延期审核通过')
+    ElMessage.success('延期审核已通过')
+  } catch (error) {
+    ElMessage.error(error?.message || '延期审核通过失败')
+  }
+}
+
+async function rejectDelay() {
+  try {
+    await operateCurrent(7, '驳回延期')
+    ElMessage.success('延期申请已驳回')
+  } catch (error) {
+    ElMessage.error(error?.message || '驳回延期失败')
+  }
+}
+
+function openDelayDialog() {
   delayDeadline.value = ''
   delayReason.value = ''
+  delayDialogVisible.value = true
 }
 
-// 审批功能
-const approveOrder = (isApproved) => {
-  if (selectedOrder.value.status !== 'feedback') return
-  if (isApproved) {
-    selectedOrder.value.status = 'completed'
-    selectedOrder.value.processLogs.push({
-      id: Date.now(),
-      time: new Date().toLocaleString(),
-      content: `审批通过,工单办结。审批意见:${approvalRemark.value || '无'}`,
-      type: 'success',
-      operator: '审批人'
-    })
-    ElMessage.success('工单审批通过,已办结')
-  } else {
-    selectedOrder.value.status = 'processing'
-    selectedOrder.value.processLogs.push({
-      id: Date.now(),
-      time: new Date().toLocaleString(),
-      content: `审批驳回,需重新处置。驳回意见:${approvalRemark.value || '请重新反馈'}`,
-      type: 'danger',
-      operator: '审批人'
+async function submitDelay() {
+  if (!delayDeadline.value) {
+    ElMessage.warning('请选择截止时间')
+    return
+  }
+  if (!delayReason.value.trim()) {
+    ElMessage.warning('请填写延期理由')
+    return
+  }
+  try {
+    await operateCurrent(8, '申请延期', {
+      delayedDeadline: delayDeadline.value,
+      delayedReason: delayReason.value.trim()
     })
-    // 清除原反馈,等待重新上报
-    selectedOrder.value.feedback = null
-    ElMessage.warning('工单已驳回,请重新处置反馈')
+    delayDialogVisible.value = false
+    ElMessage.success('延期申请已提交')
+  } catch (error) {
+    ElMessage.error(error?.message || '延期申请失败')
   }
-  approvalRemark.value = ''
 }
 
-const viewCase = () => { ElMessage.info('跳转案件详情(演示)') }
-const playVideo = () => { ElMessage.info('模拟播放周边视频监控') }
+function getLogType(operType) {
+  const map = {
+    2: 'primary',
+    3: 'warning',
+    4: 'primary',
+    5: 'warning',
+    6: 'success',
+    7: 'danger',
+    8: 'warning',
+    9: 'success'
+  }
+  return map[Number(operType)] || 'info'
+}
 
+onMounted(() => {
+  loadOrders()
+})
 </script>
-
-<style scoped>
-.workorder-management {
-  background: #f0f2f6;
-  min-height: 100vh;
-  padding: 20px;
-}
-.page-header {
-  background: white;
-  border-radius: 20px;
-  padding: 16px 24px;
-  margin-bottom: 20px;
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-}
-.header-title { display: flex; align-items: center; gap: 14px; font-size: 20px; font-weight: 600; }
-.header-stats { display: flex; gap: 24px; }
-.stat { text-align: center; cursor: pointer; padding: 4px 16px; border-radius: 30px; }
-.stat:hover { background: #ecf5ff; }
-.num { font-size: 26px; font-weight: bold; }
-.num.warning { color: #e6a23c; }
-.main-layout { display: flex; gap: 20px; }
-.order-list-panel {
-  width: 380px;
-  background: white;
-  border-radius: 20px;
-  display: flex;
-  flex-direction: column;
-  overflow: hidden;
-}
-.list-header {
-  padding: 16px;
-  border-bottom: 1px solid #e9ecef;
-  display: flex;
-  justify-content: space-between;
-  font-weight: 600;
-}
-.order-list { flex: 1; overflow-y: auto; padding: 12px; }
-.order-item {
-  padding: 12px;
-  margin-bottom: 8px;
-  border-radius: 14px;
-  background: #fafbfc;
-  cursor: pointer;
-  border: 1px solid transparent;
-}
-.order-item.active { background: #ecf5ff; border-color: #409eff; }
-.order-item.pending { border-left: 3px solid #f56c6c; }
-.order-item.processing { border-left: 3px solid #e6a23c; }
-.order-item.feedback { border-left: 3px solid #909399; }
-.order-header { display: flex; justify-content: space-between; margin-bottom: 6px; }
-.order-code { font-weight: 600; }
-.order-location { font-size: 12px; color: #666; margin: 4px 0; }
-.order-time { font-size: 11px; color: #aaa; }
-.detail-panel {
-  flex: 1;
-  background: white;
-  border-radius: 20px;
-  padding: 20px;
-  overflow-y: auto;
-}
-.section-title {
-  font-weight: 600;
-  font-size: 16px;
-  margin: 16px 0 12px 0;
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-}
-.assign-area { display: flex; gap: 8px; }
-.delay-buttons { display: flex; gap: 12px; margin-bottom: 12px; }
-.delay-history { background: #fef7e0; padding: 8px 12px; border-radius: 8px; font-size: 13px; }
-.feedback-images { display: flex; gap: 12px; margin-top: 8px; }
-.feedback-img { width: 100px; height: 80px; border-radius: 8px; object-fit: cover; }
-.video-preview { background: #eef2fa; padding: 10px; border-radius: 12px; text-align: center; cursor: pointer; margin-top: 12px; }
-.approval-section { margin-top: 20px; border-top: 1px solid #e9ecef; padding-top: 16px; }
-.approval-buttons { display: flex; gap: 12px; margin: 12px 0; }
-.approved-info { margin-top: 16px; }
-.empty-panel { flex: 1; display: flex; align-items: center; justify-content: center; background: white; border-radius: 20px; }
-.operator { font-size: 12px; color: #909399; margin-left: 12px; }
-.pagination-area { padding: 12px; text-align: center; border-top: 1px solid #eee; }
-</style>