Bladeren bron

fix:bug修改

null 1 week geleden
bovenliggende
commit
391c1ee7c0

+ 165 - 0
src/api/pipeNetwork/earlyWarning.js

@@ -0,0 +1,165 @@
+import request from '@/utils/request'
+
+/**
+ * 设备预警管理 API
+ * 数据源:early_warning 预警体系(/warning/*)
+ * 处置人分配复用工单接口:addManholeWorkOrder / operManholeWorkOrder(basic.js)
+ */
+
+// 预警综合搜索(名称/编号/类型/级别/状态/位置/发布人/权属单位/时间范围)
+export function getWarningSearchData(query) {
+  return request({
+    url: '/warning/search',
+    method: 'get',
+    params: query
+  })
+}
+
+// 待办预警列表(当前用户)
+export function getWarningTodoListData(query) {
+  return request({
+    url: '/warning/todo/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 预警完整详情(基本信息+GIS+处置历史+附件+归档+关联报警)
+export function getWarningFullDetailData(warningId) {
+  return request({
+    url: `/warning/detail/full/${warningId}`,
+    method: 'get'
+  })
+}
+
+// 发布预警(确认异常)
+export function publishWarningData(warningId) {
+  return request({
+    url: `/warning/publish/${warningId}`,
+    method: 'post'
+  })
+}
+
+// 解除预警(误报/工单完成)
+export function resolveWarningData(data) {
+  return request({
+    url: '/warning/resolve',
+    method: 'post',
+    params: data
+  })
+}
+
+// 预警升级
+export function upgradeWarningData(data) {
+  return request({
+    url: '/warning/upgrade',
+    method: 'post',
+    params: data
+  })
+}
+
+// 预警退回重办
+export function returnWarningData(data) {
+  return request({
+    url: '/warning/return',
+    method: 'post',
+    params: data
+  })
+}
+
+// 预警督办
+export function supervisionWarningData(data) {
+  return request({
+    url: '/warning/supervision',
+    method: 'post',
+    params: data
+  })
+}
+
+// 领导批示
+export function instructionWarningData(data) {
+  return request({
+    url: '/warning/instruction',
+    method: 'post',
+    params: data
+  })
+}
+
+// 处置历史记录
+export function getWarningDisposalHistoryData(warningId) {
+  return request({
+    url: `/warning/disposal/history/${warningId}`,
+    method: 'get'
+  })
+}
+
+// 附件列表
+export function getWarningAttachmentsData(warningId) {
+  return request({
+    url: `/warning/attachments/${warningId}`,
+    method: 'get'
+  })
+}
+
+// 流程可视化数据(路线图节点+边)
+export function getWarningDiagramData(warningId) {
+  return request({
+    url: `/warning/diagram/${warningId}`,
+    method: 'get'
+  })
+}
+
+// 仪表盘统计(总数/类型分布/级别分布/近7天趋势)
+export function getWarningDashboardStatisticsData() {
+  return request({
+    url: '/warning/dashboard/statistics',
+    method: 'get'
+  })
+}
+
+// 处置中预警列表
+export function getWarningProcessingListData(query) {
+  return request({
+    url: '/warning/processing/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 已完成预警列表
+export function getWarningCompletedListData(query) {
+  return request({
+    url: '/warning/completed/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 预警归档信息
+export function getWarningArchiveData(warningId) {
+  return request({
+    url: `/warning/archive/${warningId}`,
+    method: 'get'
+  })
+}
+
+// 修改预警(处理反馈时上传附件:warning JSON + files)
+export function updateWarningData(data) {
+  const formData = new FormData()
+  const warning = data.warning || {}
+  if (warning.warningId) {
+    formData.append('warning', JSON.stringify(warning))
+  } else {
+    const { warningId, ...rest } = warning
+    formData.append('warning', JSON.stringify(rest))
+  }
+  formData.append('alarmIds', JSON.stringify(data.alarmIds || []))
+  ;(data.files || []).forEach((file) => {
+    formData.append('files', file)
+  })
+  return request({
+    url: '/warning/update',
+    method: 'put',
+    data: formData
+  })
+}

+ 9 - 0
src/api/work/order.js

@@ -53,6 +53,15 @@ export function completeWorkOrder(orderId, result) {
   })
 }
 
+/** 新增工单(手动建单) */
+export function addWorkOrder(data) {
+  return request({
+    url: '/api/work-order/add',
+    method: 'post',
+    data
+  })
+}
+
 /** 取消工单 */
 export function cancelWorkOrder(orderId) {
   return request({

+ 523 - 576
src/views/subSystem/pipeNetwork/pAlarmMonitor/PEqWarning.vue

@@ -1,636 +1,583 @@
-<!--设备预警-->
-<template>
-  <div class="prototype-container">
-    <!-- 头部 -->
-    <div class="header">
-      <div class="logo">
-        <h2>⚠️ 设备预警与处理系统</h2>
-        <p>普通预警 | 特殊预警 | 预警确认 | 预警处理 | 物联感知一张图</p>
-      </div>
-      <div class="header-stats">
-        <div class="stat-card">
-          <span class="stat-value">{{ unconfirmedCount }}</span>
-          <span class="stat-label">待确认</span>
-        </div>
-        <div class="stat-card warning">
-          <span class="stat-value">{{ processingCount }}</span>
-          <span class="stat-label">处理中</span>
-        </div>
-        <div class="stat-card success">
-          <span class="stat-value">{{ todayCount }}</span>
-          <span class="stat-label">今日新增</span>
-        </div>
-      </div>
-    </div>
-
-    <div class="main-layout">
-      <!-- 左侧:物联感知一张图(特殊预警地图展示) -->
-      <div class="left-panel">
-        <div class="card">
-          <div class="card-header">
-            🗺️ 物联感知一张图
-            <span class="live-badge">● 实时预警</span>
-          </div>
-          <div class="card-body">
-            <div class="iot-map" id="iotMap">
-              <div class="map-container">
-                <!-- 模拟管网地图 -->
-                <svg class="map-svg" viewBox="0 0 600 400">
-                  <!-- 管网线路 -->
-                  <path d="M50,200 L150,200 L250,150 L350,150 L450,200 L550,200" stroke="#7f8c8d" stroke-width="3" fill="none" stroke-dasharray="6,3"/>
-                  <path d="M150,200 L150,300 L250,350 L350,350 L450,300 L450,200" stroke="#7f8c8d" stroke-width="3" fill="none" stroke-dasharray="6,3"/>
-                  <path d="M250,150 L250,80 L350,80 L350,150" stroke="#7f8c8d" stroke-width="3" fill="none" stroke-dasharray="6,3"/>
-
-                  <!-- 设备节点 -->
-                  <circle v-for="point in mapPoints" :key="point.id" :cx="point.x" :cy="point.y" r="12"
-                          :fill="getMapPointColor(point.status)" stroke="white" stroke-width="2"
-                          @click="showDeviceOnMap(point)" style="cursor:pointer;">
-                    <title>{{ point.name }}</title>
-                  </circle>
-                  <text v-for="point in mapPoints" :key="'t'+point.id" :x="point.x-10" :y="point.y-12" fill="white" font-size="8">{{ point.name }}</text>
-
-                  <!-- 特殊预警闪烁点 -->
-                  <circle v-for="alert in specialAlertsOnMap" :key="'a'+alert.id" :cx="alert.x" :cy="alert.y" r="18"
-                          fill="none" stroke="#e74c3c" stroke-width="2" class="pulse-ring">
-                    <animate attributeName="r" values="12;22;12" dur="1.5s" repeatCount="indefinite"/>
-                    <animate attributeName="opacity" values="1;0;1" dur="1.5s" repeatCount="indefinite"/>
-                  </circle>
-                </svg>
-
-                <!-- 图例 -->
-                <div class="map-legend">
-                  <div><span class="legend-dot normal"></span> 正常设备</div>
-                  <div><span class="legend-dot offline"></span> 离线/普通预警</div>
-                  <div><span class="legend-dot special"></span> 特殊预警</div>
-                  <div><span class="legend-dot processing"></span> 处理中</div>
-                </div>
-              </div>
-            </div>
-          </div>
-        </div>
-
-        <!-- 预警统计图表 -->
-        <div class="card">
-          <div class="card-header">📊 预警趋势统计</div>
-          <div class="card-body">
-            <div class="trend-chart">
-              <div v-for="(count, idx) in weeklyStats" :key="idx" class="trend-bar">
-                <div class="bar" :style="{ height: (count / maxWeeklyCount) * 60 + 'px' }"></div>
-                <div class="bar-label">{{ ['一', '二', '三', '四', '五', '六', '日'][idx] }}</div>
-              </div>
-            </div>
-          </div>
-        </div>
-      </div>
-
-      <!-- 右侧:预警列表与管理 -->
-      <div class="right-panel">
-        <!-- 查询筛选栏 -->
-        <div class="card">
-          <div class="card-header">
-            🔍 预警查询
-            <div class="filter-group">
-              <input type="date" v-model="queryParams.startDate" class="filter-input" placeholder="开始日期">
-              <span class="filter-sep">至</span>
-              <input type="date" v-model="queryParams.endDate" class="filter-input" placeholder="结束日期">
-              <select v-model="queryParams.status" class="filter-select">
-                <option value="">全部状态</option>
-                <option value="unconfirmed">待确认</option>
-                <option value="confirmed">已确认</option>
-                <option value="processing">处理中</option>
-                <option value="resolved">已清除</option>
-                <option value="misreport">误报</option>
-              </select>
-              <select v-model="queryParams.type" class="filter-select">
-                <option value="">全部类型</option>
-                <option value="normal">普通预警</option>
-                <option value="special">特殊预警</option>
-              </select>
-              <button @click="searchAlerts" class="btn-primary btn-sm">查询</button>
-              <button @click="resetQuery" class="btn-outline btn-sm">重置</button>
-            </div>
-          </div>
-        </div>
-
-        <!-- 预警列表(普通+特殊) -->
-        <div class="card">
-          <div class="card-header">
-            📋 设备预警列表
-            <span class="list-count">共 {{ filteredAlerts.length }} 条</span>
-          </div>
-          <div class="card-body">
-            <div class="alert-table">
-              <table class="data-table">
-                <thead>
-                <tr><th>预警时间</th><th>设备名称</th><th>设备位置</th><th>预警类型</th><th>预警内容</th><th>状态</th><th>操作</th>
-                </tr>
-                </thead>
-                <tbody>
-                <tr v-for="alert in paginatedAlerts" :key="alert.id">
-                  <td class="time-cell">{{ alert.time }}</td>
-                  <td>{{ alert.deviceName }}</td>
-                  <td>{{ alert.location }}</td>
-                  <td>
-                      <span class="alert-type" :class="alert.type === 'normal' ? 'type-normal' : 'type-special'">
-                        {{ alert.type === 'normal' ? '普通预警' : '特殊预警' }}
-                      </span>
-                  </td>
-                  <td class="content-cell">{{ alert.content }}</td>
-                  <td>
-                      <span class="status-badge" :class="getStatusClass(alert.status)">
-                        {{ getStatusText(alert.status) }}
-                      </span>
-                  </td>
-                  <td class="action-cell">
-                    <button v-if="alert.status === 'unconfirmed'" @click="confirmAlert(alert)" class="btn-confirm">确认</button>
-                    <button v-if="alert.status === 'unconfirmed'" @click="markMisreport(alert)" class="btn-misreport">误报</button>
-                    <button v-if="alert.status === 'confirmed'" @click="assignHandler(alert)" class="btn-assign">分配处理</button>
-                    <button v-if="alert.status === 'processing'" @click="processAlert(alert)" class="btn-process">处理反馈</button>
-                    <button v-if="alert.status === 'processing'" @click="viewDetail(alert)" class="btn-view">查看</button>
-                    <span v-if="alert.status === 'resolved' || alert.status === 'misreport'" class="completed-badge">✓ 已完成</span>
-                  </td>
-                </tr>
-                <tr v-if="paginatedAlerts.length === 0">
-                  <td colspan="7" class="empty-row">暂无预警数据</td>
-                </tr>
-                </tbody>
-              </table>
-            </div>
+<!--预警管理页面(RuoYi Element Plus 标准)-->
+<!--数据源:early_warning 体系 /warning/*接口 -->
+<!--确认异常后只建工单不派单,由工单系统处理后续流程-->
 
-            <!-- 分页 -->
-            <div class="pagination" v-if="totalPages > 1">
-              <button v-for="page in totalPages" :key="page" class="page-btn" :class="{ active: currentPage === page }"
-                      @click="currentPage = page">{{ page }}</button>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-
-    <!-- 预警确认模态框 -->
-    <div v-if="confirmModalVisible" class="modal-overlay" @click.self="closeConfirmModal">
-      <div class="modal-content">
-        <h3>⚠️ 预警信息确认</h3>
-        <div class="confirm-info">
-          <div class="info-row"><span class="label">设备名称:</span>{{ currentAlert?.deviceName }}</div>
-          <div class="info-row"><span class="label">预警时间:</span>{{ currentAlert?.time }}</div>
-          <div class="info-row"><span class="label">预警内容:</span>{{ currentAlert?.content }}</div>
-        </div>
-        <div class="form-group">
-          <label class="form-label">确认类型</label>
-          <select v-model="confirmType" class="form-select">
-            <option value="device">设备异常预警(需处理)</option>
-            <option value="misreport">误报(网络等因素)</option>
-          </select>
-        </div>
-        <div class="form-group" v-if="confirmType === 'device'">
-          <label class="form-label">分配处理人员</label>
-          <select v-model="assignedPerson" class="form-select">
-            <option value="张工">张工(设备技术组)</option>
-            <option value="李工">李工(运维组)</option>
-            <option value="王工">王工(抢修组)</option>
-          </select>
+<template>
+  <div class="warning-container">
+    <!-- ========= 统计卡片区域 ========== -->
+    <el-row :gutter="16" class="stat-row">
+      <el-col :span="4">
+        <div class="stat-item">
+          <div class="stat-title">总预警</div>
+          <div class="stat-value" :class="stats.total > 0 ? 'primary' : ''">{{ stats.total }}</div>
         </div>
-        <div class="form-actions">
-          <button @click="closeConfirmModal" class="btn-outline">取消</button>
-          <button @click="submitConfirm" class="btn-primary">确认提交</button>
+      </el-col>
+      <el-col :span="4">
+        <div class="stat-item">
+          <div class="stat-title">待确认</div>
+          <div class="stat-value warning" :class="stats.unconfirmed > 0 ? 'highlight' : ''">{{ stats.unconfirmed }}</div>
         </div>
-      </div>
-    </div>
-
-    <!-- 预警处理模态框 -->
-    <div v-if="processModalVisible" class="modal-overlay" @click.self="closeProcessModal">
-      <div class="modal-content" style="width:500px;">
-        <h3>🔧 预警处理反馈</h3>
-        <div class="process-info">
-          <div class="info-row"><span class="label">设备:</span>{{ currentAlert?.deviceName }}</div>
-          <div class="info-row"><span class="label">预警内容:</span>{{ currentAlert?.content }}</div>
+      </el-col>
+      <el-col :span="4">
+        <div class="stat-item">
+          <div class="stat-title">处理中</div>
+          <div class="stat-value info" :class="stats.processing > 0 ? 'highlight' : ''">{{ stats.processing }}</div>
         </div>
-        <div class="form-group">
-          <label class="form-label">处理结果</label>
-          <textarea v-model="processResult" class="form-textarea" rows="3" placeholder="请描述处理过程及结果..."></textarea>
+      </el-col>
+      <el-col :span="4">
+        <div class="stat-item">
+          <div class="stat-title">已处置</div>
+          <div class="stat-value success" :class="stats.handled > 0 ? 'highlight' : ''">{{ stats.handled }}</div>
         </div>
-        <div class="form-group">
-          <label class="form-label">附件上传</label>
-          <div class="upload-area">
-            <label class="upload-btn">
-              📎 选择文件
-              <input type="file" @change="handleFileUpload" accept="image/*,.pdf" hidden multiple>
-            </label>
-            <div class="file-list">
-              <span v-for="(file, idx) in uploadedFiles" :key="idx" class="file-tag">
-                {{ file.name }}
-                <button @click="removeFile(idx)" class="remove-file">✖</button>
-              </span>
-            </div>
-          </div>
+      </el-col>
+      <el-col :span="4">
+        <div class="stat-item">
+          <div class="stat-title">已解除</div>
+          <div class="stat-value done" :class="stats.closed > 0 ? 'highlight' : ''">{{ stats.closed }}</div>
         </div>
-        <div class="form-actions">
-          <button @click="closeProcessModal" class="btn-outline">取消</button>
-          <button @click="submitProcess" class="btn-success">提交处理</button>
+      </el-col>
+      <el-col :span="4">
+        <div class="stat-item">
+          <div class="stat-title">今日新增</div>
+          <div class="stat-value today" :class="stats.today > 0 ? 'highlight' : ''">{{ stats.today }}</div>
         </div>
-      </div>
-    </div>
-
-    <!-- 查看详情模态框 -->
-    <div v-if="detailModalVisible" class="modal-overlay" @click.self="closeDetailModal">
-      <div class="modal-content">
-        <h3>📋 预警处理详情</h3>
-        <div class="detail-info">
-          <div class="info-row"><span class="label">设备名称:</span>{{ currentAlert?.deviceName }}</div>
-          <div class="info-row"><span class="label">预警时间:</span>{{ currentAlert?.time }}</div>
-          <div class="info-row"><span class="label">确认时间:</span>{{ currentAlert?.confirmTime || '-' }}</div>
-          <div class="info-row"><span class="label">处理人员:</span>{{ currentAlert?.handler || '-' }}</div>
-          <div class="info-row"><span class="label">处理结果:</span>{{ currentAlert?.processResult || '-' }}</div>
-          <div class="info-row" v-if="currentAlert?.attachments?.length">
-            <span class="label">附件:</span>
-            <div class="attachment-list">
-              <span v-for="(att, idx) in currentAlert.attachments" :key="idx" class="attachment">{{ att.name }}</span>
+      </el-col>
+    </el-row>
+
+    <!-- ========== 图表区域 ========== -->
+    <el-row :gutter="16" class="chart-row">
+      <el-col :span="12"><el-card><template #header><span>近 7 天趋势</span></template><div id="trendChart" style="height:240px"/></el-card></el-col>
+      <el-col :span="6"><el-card><template #header><span>级别分布</span></template><div id="levelChart" style="height:240px"/></el-card></el-col>
+      <el-col :span="6"><el-card><template #header><span>类型占比</span></template><div id="typeChart" style="height:240px"/></el-card></el-col>
+    </el-row>
+
+    <!-- ========= 查询表单 ========== -->
+    <el-card class="search-card">
+      <el-form :inline="true" :model="queryParams">
+        <el-form-item label="预警名称">
+          <el-input v-model="queryParams.warningName" placeholder="请输入预警名称" clearable style="width:200px"/>
+        </el-form-item>
+        <el-form-item label="状态">
+          <el-select v-model="queryParams.status" placeholder="全部状态" clearable style="width:150px">
+            <el-option label="待确认" value="RELEASED"/>
+            <el-option label="待办" value="PENDING"/>
+            <el-option label="处理中" value="PROCESSING"/>
+            <el-option label="已处置" value="HANDLED"/>
+            <el-option label="已解除" value="CLOSED"/>
+            <el-option label="草稿" value="DRAFT"/>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="级别">
+          <el-select v-model="queryParams.warningLevel" placeholder="全部级别" clearable style="width:120px">
+            <el-option label="1 级 (蓝)" value="1"/>
+            <el-option label="2 级 (黄)" value="2"/>
+            <el-option label="3 级 (橙)" value="3"/>
+            <el-option label="4 级 (红)" value="4"/>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="类别">
+          <el-select v-model="queryParams.category" placeholder="全部类别" clearable style="width:120px">
+            <el-option label="普通预警" value="normal"/>
+            <el-option label="特殊预警" value="special"/>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="日期范围">
+          <el-date-picker v-model="queryParams.startDate" type="date" placeholder="开始日期" clearable value-format="YYYY-MM-DD" style="width:160px"/>
+          <span style="margin:0 8px;color:#909399;">至</span>
+          <el-date-picker v-model="queryParams.endDate" type="date" placeholder="结束日期" clearable value-format="YYYY-MM-DD" style="width:160px"/>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleSearch"><el-icon><Search/></el-icon> 查询</el-button>
+          <el-button @click="handleReset"><el-icon><Refresh/></el-icon> 重置</el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
+
+    <!-- ========== 预警列表 ========== -->
+    <el-card class="table-card">
+      <el-table :data="tableData" border stripe v-loading="loading" max-height="600">
+        <el-table-column prop="warningNo" label="预警编号" width="140" />
+        <el-table-column prop="warningName" label="预警名称" min-width="160" show-overflow-tooltip />
+        <el-table-column label="关联设备" width="160">
+          <template #default="{ row }">
+            <div v-if="row.deviceCode" class="device-cell">
+              <el-tag :type="row.warningLevel === '4' ? 'danger' : 'info'" size="small" effect="plain">{{ row.deviceCode }}</el-tag>
             </div>
-          </div>
+            <span v-else class="no-device">未关联</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="location" label="设备位置" min-width="140" show-overflow-tooltip />
+        <el-table-column label="类别" width="100">
+          <template #default="{ row }">
+            <el-tag :type="row.category === 'normal' ? '' : 'warning'" size="small">
+              {{ row.category === 'normal' ? '普通预警' : '特殊预警' }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="级别" width="100">
+          <template #default="{ row }">
+            <el-tag :type="getLevelType(row.warningLevel)" size="small">{{ getLevelText(row.warningLevel) }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="状态" width="100">
+          <template #default="{ row }">
+            <el-tag :type="getStatusType(row.status)" size="small">{{ getStatusText(row.status) }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="publishTime" label="发布时间" width="170" sortable />
+        <el-table-column label="操作" width="180" fixed="right">
+          <template #default="{ row }">
+            <el-button v-if="isUnconfirmed(row.status)" type="primary" size="small" @click="openConfirm(row)">确认异常</el-button>
+            <el-button v-if="isUnconfirmed(row.status)" type="info" size="small" @click="confirmMisreport(row)">误报</el-button>
+            <el-button type="info" size="small" @click="openDetail(row.warningId)">详情</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <el-pagination v-model:current-page="pageNum" v-model:page-size="pageSize" :total="total" layout="total,prev,pager,next,sizes" :page-sizes="[10,20,30]"
+                     @change="loadList" style="margin-top:16px;justify-content:flex-end" />
+    </el-card>
+
+    <!-- ========== 确认异常弹窗 ========== -->
+    <el-dialog v-model="confirmVisible" title="⚠️ 确认预警异常" width="600px" append-to-body destroy-on-close>
+      <el-descriptions :column="1" border>
+        <el-descriptions-item label="预警编号">{{ current.warningNo }}</el-descriptions-item>
+        <el-descriptions-item label="预警名称">{{ current.warningName }}</el-descriptions-item>
+        <el-descriptions-item label="预警内容">{{ current.warningContent || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="设备位置">{{ current.location || '-' }}</el-descriptions-item>
+      </el-descriptions>
+      <el-form label-width="100px" style="margin-top:16px">
+        <el-form-item label="关联设备">
+          <el-select v-model="deviceSelected" filterable remote clearable placeholder="请输入设备编码搜索" :remote-method="searchDevice" :loading="deviceLoading" style="width:100%">
+            <el-option v-for="dev in deviceOptions" :key="dev.equipmentId" :label="`${dev.equipmentName}(${dev.equipmentCode})`" :value="dev.equipmentId"/>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="备注信息">
+          <el-input v-model="confirmRemark" type="textarea" :rows="3" placeholder="填写异常情况描述(将写入工单描述)" maxlength="300" show-word-limit/>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="confirmVisible=false">取消</el-button>
+        <el-button type="primary" :loading="submitting" @click="submitConfirm">确认提交</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- ========== 详情页抽屉 ========== -->
+    <el-drawer v-model="detailVisible" title="📋 预警处理详情" size="560px">
+      <template v-if="detail">
+        <el-descriptions :column="1" border size="small">
+          <el-descriptions-item label="预警编号">{{ detail.basicInfo.warning_no }}</el-descriptions-item>
+          <el-descriptions-item label="预警名称">{{ detail.basicInfo.warning_name }}</el-descriptions-item>
+          <el-descriptions-item label="预警类型">{{ detail.basicInfo.warning_type || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="预警级别">{{ getLevelText(detail.basicInfo.warning_level) }}</el-descriptions-item>
+          <el-descriptions-item label="发布人">{{ detail.basicInfo.publisher || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="发布时间">{{ detail.basicInfo.publish_time || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="状态">{{ getStatusText(detail.basicInfo.status) }}</el-descriptions-item>
+          <el-descriptions-item label="预警内容">{{ detail.basicInfo.warning_content }}</el-descriptions-item>
+        </el-descriptions>
+
+        <div class="detail-section-title">🔗 关联设备</div>
+        <el-descriptions :column="1" border size="small">
+          <el-descriptions-item label="设备编码">{{ detail.basicInfo.remark?.startsWith('AUTO|') ? detail.basicInfo.remark.split('|')[1] : '(手工录入,无设备关联)' }}</el-descriptions-item>
+          <el-descriptions-item label="设备位置">{{ detail.basicInfo.location || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="权属单位">{{ detail.basicInfo.ownership_unit || '-' }}</el-descriptions-item>
+        </el-descriptions>
+
+        <div class="detail-section-title">🕐 处置历史</div>
+        <el-timeline v-if="detail.disposalHistory?.length" style="padding:0 0 20px">
+          <el-timeline-item v-for="(item,idx) in detail.disposalHistory" :key="idx" :timestamp="item.create_time" :type="disposalTimelineType(item.disposal_type)">
+            <b>{{ disposalTypeText(item.disposal_type) }}</b>
+            <div>{{ item.disposal_content || '-' }}</div>
+            <div class="detail-sub">处置人:{{ item.disposal_user || '-' }}</div>
+          </el-timeline-item>
+        </el-timeline>
+        <div v-else class="detail-empty">暂无处置记录</div>
+
+        <div class="detail-section-title">📎 附件</div>
+        <el-upload v-if="false" action="#" list-type="text"/>
+        <div v-if="detail.attachmentList?.length" class="attachment-list">
+          <a v-for="(att,idx) in detail.attachmentList" :key="idx" class="attachment-link" :href="att.attachmentUrl" target="_blank">{{ att.attachmentName }}</a>
         </div>
-        <button @click="closeDetailModal" class="btn-primary" style="margin-top:16px;">关闭</button>
-      </div>
-    </div>
+        <div v-else class="detail-empty">暂无附件</div>
+
+        <div class="detail-section-title">📦 电子归档</div>
+        <el-descriptions v-if="detail.electronicArchive" :column="2" border size="small">
+          <el-descriptions-item label="归档编号">{{ detail.electronicArchive.archiveNo }}</el-descriptions-item>
+          <el-descriptions-item label="归档时间">{{ detail.electronicArchive.createTime }}</el-descriptions-item>
+        </el-descriptions>
+        <div v-else class="detail-empty">暂无归档(解除预警后自动生成)</div>
+      </template>
+    </el-drawer>
   </div>
 </template>
 
 <script setup>
-import { ref, computed } from 'vue'
-
-// ==================== 预警数据模拟 ====================
-const alerts = ref([
-  { id: 1, deviceName: "压力传感器-PS001", location: "城东泵站", type: "normal", content: "设备离线,超过30分钟未回传数据", time: "2025-05-08 14:23:15", status: "unconfirmed", confirmTime: null, handler: null, processResult: null, attachments: [] },
-  { id: 2, deviceName: "流量监测仪-FL002", location: "城南枢纽", type: "normal", content: "设备在线异常,数据波动较大", time: "2025-05-08 13:45:22", status: "unconfirmed", confirmTime: null, handler: null, processResult: null, attachments: [] },
-  { id: 3, deviceName: "泄漏检测仪-LD005", location: "化工园区", type: "special", content: "可燃气浓度超阈值报警,当前值35%LEL", time: "2025-05-08 13:30:12", status: "processing", confirmTime: "2025-05-08 13:35:00", handler: "张工", processResult: null, attachments: [] },
-  { id: 4, deviceName: "温度传感器-TP003", location: "城西换热站", type: "normal", content: "设备离线", time: "2025-05-08 12:15:08", status: "confirmed", confirmTime: "2025-05-08 12:20:00", handler: null, processResult: null, attachments: [] },
-  { id: 5, deviceName: "水位监测仪-WL006", location: "东区排水口", type: "special", content: "水位超警戒线,当前水位3.2m", time: "2025-05-08 11:50:33", status: "resolved", confirmTime: "2025-05-08 11:55:00", handler: "李工", processResult: "已启动排水泵,水位降至正常", attachments: [{ name: "处理现场.jpg", url: "" }] },
-  { id: 6, deviceName: "压力传感器-PS008", location: "北环加压站", type: "normal", content: "设备离线(网络波动)", time: "2025-05-08 10:30:45", status: "misreport", confirmTime: "2025-05-08 10:35:00", handler: null, processResult: "网络恢复后设备正常,确认误报", attachments: [] },
-  { id: 7, deviceName: "燃气探测器-GD004", location: "城南调压站", type: "special", content: "燃气浓度异常波动", time: "2025-05-08 09:20:18", status: "confirmed", confirmTime: "2025-05-08 09:25:00", handler: null, processResult: null, attachments: [] }
-])
-
-// 地图点位数据(模拟)
-const mapPoints = ref([
-  { id: 1, name: "PS001", x: 150, y: 200, status: "offline" },
-  { id: 2, name: "FL002", x: 250, y: 150, status: "normal" },
-  { id: 3, name: "LD005", x: 350, y: 150, status: "special" },
-  { id: 4, name: "TP003", x: 450, y: 200, status: "normal" },
-  { id: 5, name: "WL006", x: 350, y: 300, status: "processing" },
-  { id: 6, name: "PS008", x: 250, y: 300, status: "normal" },
-  { id: 7, name: "GD004", x: 150, y: 300, status: "special" }
-])
-
-// 特殊预警地图标记
-const specialAlertsOnMap = computed(() => {
-  const specialAlerts = alerts.value.filter(a => a.type === 'special' && (a.status === 'unconfirmed' || a.status === 'confirmed' || a.status === 'processing'))
-  return specialAlerts.map((alert, idx) => {
-    const point = mapPoints.value.find(p => alert.deviceName.includes(p.name))
-    return { id: alert.id, x: point?.x || 200 + idx * 50, y: point?.y || 250, name: alert.deviceName }
-  })
-})
-
-// ==================== 查询参数 ====================
-const queryParams = ref({
-  startDate: '',
-  endDate: '',
-  status: '',
-  type: ''
-})
-
-const currentPage = ref(1)
-const pageSize = 8
-
-// 过滤后的预警列表
-const filteredAlerts = computed(() => {
-  let result = [...alerts.value]
-
-  if (queryParams.value.startDate) {
-    result = result.filter(a => a.time >= queryParams.value.startDate)
-  }
-  if (queryParams.value.endDate) {
-    result = result.filter(a => a.time <= queryParams.value.endDate + ' 23:59:59')
-  }
-  if (queryParams.value.status) {
-    result = result.filter(a => a.status === queryParams.value.status)
-  }
-  if (queryParams.value.type) {
-    result = result.filter(a => a.type === queryParams.value.type)
-  }
-
-  // 时间倒序
-  return result.sort((a, b) => new Date(b.time) - new Date(a.time))
-})
-
-// 分页数据
-const paginatedAlerts = computed(() => {
-  const start = (currentPage.value - 1) * pageSize
-  return filteredAlerts.value.slice(start, start + pageSize)
-})
-
-const totalPages = computed(() => Math.ceil(filteredAlerts.value.length / pageSize))
-
-// 统计数据
-const unconfirmedCount = computed(() => alerts.value.filter(a => a.status === 'unconfirmed').length)
-const processingCount = computed(() => alerts.value.filter(a => a.status === 'processing').length)
-const todayCount = computed(() => {
-  const today = new Date().toISOString().slice(0, 10)
-  return alerts.value.filter(a => a.time.startsWith(today)).length
-})
-
-// 周统计
-const weeklyStats = computed(() => {
-  const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
-  return days.map(() => Math.floor(Math.random() * 8) + 2)
-})
-const maxWeeklyCount = computed(() => Math.max(...weeklyStats.value, 10))
-
-// ==================== 辅助函数 ====================
-const getStatusText = (status) => {
-  const map = {
-    unconfirmed: '待确认',
-    confirmed: '已确认',
-    processing: '处理中',
-    resolved: '已清除',
-    misreport: '误报'
-  }
-  return map[status] || status
+import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
+import * as echarts from 'echarts'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { Search, Refresh } from '@element-plus/icons-vue'
+import { getWarningDashboardStatisticsData, getWarningSearchData, getWarningFullDetailData, publishWarningData, resolveWarningData } from '@/api/pipeNetwork/earlyWarning'
+import { addManholeWorkOrder, getEquipmentFullPage } from '@/api/pipeNetwork/basic'
+
+// ========== 统计数据 ==========
+const stats = ref({ total:0, unconfirmed:0, processing:0, handled:0, closed:0, today:0 })
+
+// ========== 图表数据 ==========
+const chartData = ref({ trend:[], levels:[], types:[] })
+
+// ========== 查询参数 & 列表 ==========
+const queryParams = ref({ warningName:'', status:'', warningLevel:'', category:'', startDate:'', endDate:'' })
+const tableData = ref([])
+const loading = ref(false)
+const pageNum = ref(1)
+const pageSize = ref(10)
+const total = ref(0)
+
+async function loadStats() {
+  try {
+    const res = await getWarningDashboardStatisticsData()
+    if (!res.data) return
+    const b = res.data.basicStats
+    const t = res.data.trendStats || []
+    // releasedCount + pendingCount = 待确认
+    stats.value = {
+      total: Number(b.total || 0),
+      unconfirmed: Number(b.releasedCount || 0) + Number(b.pendingCount || 0),
+      processing: Number(b.processingCount || 0),
+      handled: Number(b.handledCount || 0),
+      closed: Number(b.closedCount || 0),
+      today: t.length ? Number(t[t.length-1].count || 0) : 0
+    }
+  } catch(e) { console.warn('加载统计数据失败', e) }
 }
 
-const getStatusClass = (status) => {
-  const map = {
-    unconfirmed: 'status-unconfirmed',
-    confirmed: 'status-confirmed',
-    processing: 'status-processing',
-    resolved: 'status-resolved',
-    misreport: 'status-misreport'
-  }
-  return map[status] || ''
+async function loadList() {
+  loading.value = true
+  try {
+    const params = { pageNum: pageNum.value, pageSize: pageSize.value }
+    if (queryParams.value.warningName) params.warningName = queryParams.value.warningName
+    if (queryParams.value.status) params.status = queryParams.value.status
+    if (queryParams.value.warningLevel) params.warningLevel = queryParams.value.warningLevel
+    if (queryParams.value.category === 'normal') params.publisher = '系统自动'
+    if (queryParams.value.category === 'special') params.excludePublisher = '系统自动'
+    if (queryParams.value.startDate) params.startTime = queryParams.value.startDate + ' 00:00:00'
+    if (queryParams.value.endDate) params.endTime = queryParams.value.endDate + ' 23:59:59'
+    
+    const res = await getWarningSearchData(params)
+    if (!res.data) return
+    tableData.value = (res.data.records || []).map(row => normalizeWarning(row))
+    total.value = Number(res.data.total || 0)
+  } catch(e) { ElMessage.error('加载预警列表失败'); console.error(e) }
+  finally { loading.value = false }
 }
 
-const getMapPointColor = (status) => {
-  const map = {
-    normal: '#27ae60',
-    offline: '#e74c3c',
-    special: '#e74c3c',
-    processing: '#f39c12'
-  }
-  return map[status] || '#95a5a6'
+// 图表数据填充(模拟)
+const fillChartData = () => {
+  if (chartData.value.trend?.length) return
+  const allStats = stats.value
+  const dates = [], counts = []
+  for(let i=6;i>=0;i--) { const d=new Date(); d.setDate(d.getDate()-i); dates.push((d.getMonth()+1)+'-'+d.getDate()) }
+  for(let i=6;i>=0;i--) { counts.push(Math.max(0, Math.floor(allStats.today * (0.5 + Math.random()*0.5)))) }
+  chartData.value.trend = dates.map((date,idx)=>({date,count:counts[idx]}))
+  chartData.value.levels = [1,2,3,4].map(lv=>({levelCode:String(lv), count: Math.max(1, Math.floor(Math.random()*allStats.total/5)+1)}))
+  chartData.value.types = [{typeCode:'drainage',typeName:'排水'}, {typeCode:'gas',typeName:'燃气管网'}, {typeCode:'fire',typeName:'消防水压'}]
+    .map(t=>({...t, count: Math.max(1, Math.floor(Math.random()*allStats.total/3)+2)}))
 }
 
-const showDeviceOnMap = (point) => {
-  const alert = alerts.value.find(a => a.deviceName.includes(point.name))
-  if (alert) {
-    alert(`设备: ${point.name}\n状态: ${alert.status}\n预警内容: ${alert.content}`)
-  }
-}
+// 图表渲染时的空数据状态
+const emptyChartData = { trend:[], levels:[], types:[] }
 
-// 查询相关
-const searchAlerts = () => {
-  currentPage.value = 1
-}
+// ========== 图表管理 ==========
+let trendChart, levelChart, typeChart
 
-const resetQuery = () => {
-  queryParams.value = { startDate: '', endDate: '', status: '', type: '' }
-  currentPage.value = 1
+const initCharts = () => {
+  // 如果数据为空,填充模拟数据
+  if (chartData.value.trend?.length === 0) fillChartData()
+  try { renderTrendChart() } catch(e){ console.warn('趋势图初始化失败:', e) }
+  try { renderLevelChart() } catch(e){ console.warn('级别图初始化失败:', e) }
+  try { renderTypeChart() } catch(e){ console.warn('类型图初始化失败:', e) }
 }
 
-// ==================== 预警确认 ====================
-const confirmModalVisible = ref(false)
-const currentAlert = ref(null)
-const confirmType = ref('device')
-const assignedPerson = ref('张工')
-
-const confirmAlert = (alert) => {
-  currentAlert.value = alert
-  confirmModalVisible.value = true
+// 下划线转驼峰 + 判断类别
+function normalizeWarning(row) {
+  const n = {}
+  Object.keys(row).forEach(key => {
+    const camel = key.replace(/_([a-z])/g, (_, c) => c.toUpperCase())
+    n[camel] = row[key]
+  })
+  n.category = n.publisher === '系统自动' ? 'normal' : 'special'
+  // 从 remark 字段提取设备编码 (AUTO|deviceCode|warningCode)
+  n.deviceCode = extractDeviceCodeStr(row)
+  return n
 }
 
-const markMisreport = (alert) => {
-  if (confirm('确认标记为误报吗?')) {
-    alert.status = 'misreport'
-    alert.confirmTime = new Date().toLocaleString()
-    alert.processResult = '网络/环境因素导致误报'
-    addSystemLog(alert, '误报确认')
+// 从预警行中提取设备编码
+function extractDeviceCodeStr(row) {
+  if (row.remark && typeof row.remark === 'string' && row.remark.startsWith('AUTO|')) {
+    const parts = row.remark.split('|')
+    if (parts.length >= 2) return parts[1]
   }
+  const m = (row.warning_content || row.warningContent || '').match(/设备\s*\[([^\]]+)\]/)
+  if (m) return m[1]
+  return ''
 }
 
-const closeConfirmModal = () => {
-  confirmModalVisible.value = false
-  currentAlert.value = null
+// ========== ECharts ==========
+const trendChartOpt = {
+  tooltip:{ trigger:'axis' },
+  legend:{ data:['新增预警'], top:0 },
+  grid:{ left:40,right:20,top:44,bottom:32 },
+  xAxis:{ type:'category', axisLabel:{ color:'#9098a6' }, axisLine:{ lineStyle:{ color:'#d0d5dd' } } },
+  yAxis:{ type:'value', name:'数量', axisLabel:{ color:'#9098a6' }, splitLine:{ lineStyle:{ color:'#f0f2f6',type:'dashed' } } },
+  series:[{ type:'line', smooth:true, data:[], lineStyle:{ color:'#e74c3c',width:2 }, itemStyle:{ color:'#e74c3c' } }]
 }
 
-const submitConfirm = () => {
-  if (currentAlert.value) {
-    if (confirmType.value === 'misreport') {
-      currentAlert.value.status = 'misreport'
-      currentAlert.value.processResult = '确认误报'
-    } else {
-      currentAlert.value.status = 'confirmed'
-      currentAlert.value.handler = assignedPerson.value
+const levelChartOpt = {
+  tooltip:{ trigger:'axis' },
+  grid:{ left:40,right:20,top:44,bottom:32 },
+  xAxis:{ type:'category', data: ['1 级','2 级','3 级','4 级'], axisLabel:{ color:'#9098a6' }, axisLine:{ lineStyle:{ color:'#d0d5dd' } } },
+  yAxis:{ type:'value', name:'数量', axisLabel:{ color:'#9098a6' }, splitLine:{ lineStyle:{ color:'#f0f2f6',type:'dashed' } } },
+  series:[{
+    type:'bar', barWidth:30,
+    data:[],
+    itemStyle:{
+      color:function(params){
+        const colors={1:'#e3f2fd',2:'#fff8e1',3:'#fff3e0',4:'#ffebee'};
+        return colors[params.dataIndex+1]||'#e3f2fd';
+      }
     }
-    currentAlert.value.confirmTime = new Date().toLocaleString()
-    addSystemLog(currentAlert.value, confirmType.value === 'device' ? '确认设备异常' : '误报确认')
-    closeConfirmModal()
-  }
+  }]
 }
 
-// ==================== 分配处理 ====================
-const assignHandler = (alert) => {
-  const handler = prompt('请指定处理人员(设备技术员)', '张工')
-  if (handler) {
-    alert.status = 'processing'
-    alert.handler = handler
-    addSystemLog(alert, `分配处理人员: ${handler}`)
-  }
+const typeChartOpt = {
+  tooltip:{ trigger:'item' },
+  legend:{ top:'10%', orient:'vertical', right:0 },
+  series:[{
+    type:'pie', radius:['40%','70%'], avoidLabelOverlap:false,
+    itemStyle:{ borderRadius:4, borderColor:'#fff', borderWidth:1 },
+    label:{ show:false },
+    data:[]
+  }]
 }
 
-// ==================== 预警处理 ====================
-const processModalVisible = ref(false)
-const processResult = ref('')
-const uploadedFiles = ref([])
+const resizeCharts = () => { trendChart?.resize(); levelChart?.resize(); typeChart?.resize() }
 
-const processAlert = (alert) => {
-  currentAlert.value = alert
-  processResult.value = alert.processResult || ''
-  uploadedFiles.value = alert.attachments || []
-  processModalVisible.value = true
+// ========== 图表渲染函数 ==========
+function renderTrendChart() {
+  const dom = document.getElementById('trendChart')
+  if (!dom || !trendChart) return
+  trendChart.dispose()
+  trendChart = echarts.init(dom)
+  const data = chartData.value.trend.length ? chartData.value.trend : emptyChartData.trend
+  trendChart.setOption({ ...trendChartOpt, xAxis:{ ...trendChartOpt.xAxis, data: data.map(i=>i.date) }, series:[{ ...trendChartOpt.series[0], data: data.map(i=>i.count || 0) }]
+  })
 }
 
-const handleFileUpload = (event) => {
-  const files = Array.from(event.target.files)
-  files.forEach(file => {
-    uploadedFiles.value.push({ name: file.name, size: file.size, url: URL.createObjectURL(file) })
+function renderLevelChart() {
+  const dom = document.getElementById('levelChart')
+  if (!dom || !levelChart) return
+  levelChart.dispose()
+  levelChart = echarts.init(dom)
+  const data = chartData.value.levels.length ? chartData.value.levels : emptyChartData.levels
+  levelChart.setOption({ ...levelChartOpt, series:[{ ...levelChartOpt.series[0], data: data.map(l=>l.count || 0) }]
   })
 }
 
-const removeFile = (idx) => {
-  uploadedFiles.value.splice(idx, 1)
+function renderTypeChart() {
+  const dom = document.getElementById('typeChart')
+  if (!dom || !typeChart) return
+  typeChart.dispose()
+  typeChart = echarts.init(dom)
+  const data = chartData.value.types.length ? chartData.value.types : emptyChartData.types
+  typeChart.setOption({ ...typeChartOpt, series:[{ ...typeChartOpt.series[0], data: data.map(t=>({ value:t.count || 0, name:t.typeName || t.typeCode })) }]
+  })
 }
 
-const closeProcessModal = () => {
-  processModalVisible.value = false
-  currentAlert.value = null
-  processResult.value = ''
-  uploadedFiles.value = []
+onMounted(async () => {
+  await loadStats()
+  await loadList()
+  await nextTick()
+  // 填充图表数据
+  const allStats = stats.value
+  const dates = [], counts = []
+  for(let i=6;i>=0;i--) { const d = new Date(); d.setDate(d.getDate()-i); dates.push((d.getMonth()+1)+'-'+d.getDate()) }
+  for(let i=6;i>=0;i--) { counts.push(Math.max(0, allStats.today - Math.floor(Math.random()*allStats.total*0.3))) }
+  chartData.value.trend = dates.map((date,idx)=>({date,count:counts[idx]}))
+  
+  // 级别图
+  chartData.value.levels = [1,2,3,4].map(lv=>({levelCode:String(lv), count: Math.floor(Math.random()*allStats.total/5)+1}))
+  // 类型图(无 API 时随机)
+  chartData.value.types = [{typeCode:'drainage',typeName:'排水'}, {typeCode:'gas',typeName:'燃气管网'}, {typeCode:'fire',typeName:'消防水压'}]
+    .map(t=>({...t, count: Math.floor(Math.random()*allStats.total/3)+2}))
+
+  initCharts()
+  window.addEventListener('resize', resizeCharts)
+})
+
+onBeforeUnmount(() => {
+  window.removeEventListener('resize', resizeCharts)
+  trendChart?.dispose()
+  levelChart?.dispose()
+  typeChart?.dispose()
+})
+
+// ========== 辅助函数 ==========
+const isUnconfirmed = (status) => ['RELEASED','PENDING'].includes(status)
+const getStatusText = (s) => ({ DRAFT:'草稿', PENDING:'待办', PROCESSING:'处理中', RELEASED:'待确认', HANDLED:'已处置', CLOSED:'已解除' }[s]||s)
+const getStatusType = (s) => ({ DRAFT:'info', PENDING:'danger', PROCESSING:'warning', RELEASED:'warning', HANDLED:'primary', CLOSED:'success' }[s]||'')
+const getLevelText = (lv) => ({ '1':'1 级 (蓝)','2':'2 级 (黄)','3':'3 级 (橙)','4':'4 级 (红)' }[lv]||'-' )
+const getLevelType = (lv) => ({ '1':'','2':'warning','3':'warning','4':'danger' }[lv]||'')
+const disposalTypeText = (t) => ({ RELEASE:'发布预警', UPGRADE:'升级预警', RESOLVE:'解除预警', RETURN:'退回重办', SUPERVISION:'督办', INSTRUCTION:'批示' }[t]||t)
+const disposalTimelineType = (t) => ({ RESOLVE:'success', UPGRADE:'warning', RETURN:'danger' }[t]||'primary')
+
+// ========== 操作处理 ==========
+function handleSearch() { pageNum.value=1; loadList() }
+function handleReset() { queryParams.value={warningName:'',status:'',warningLevel:'',category:'',startDate:'',endDate:''}; pageNum.value=1; loadList() }
+
+// ========== 确认异常 ==========
+const confirmVisible = ref(false)
+const current = ref({})
+const deviceSelected = ref(null)
+const deviceOptions = ref([])
+const deviceLoading = ref(false)
+const confirmingRemark = ref('')
+const submitting = ref(false)
+
+function openConfirm(row) {
+  current.value = row
+  deviceSelected.value = null
+  deviceOptions.value = []
+  confirmingRemark.value = ''
+  extractDeviceCode(row)
+  confirmVisible.value = true
 }
 
-const submitProcess = () => {
-  if (currentAlert.value) {
-    currentAlert.value.status = 'resolved'
-    currentAlert.value.processResult = processResult.value
-    currentAlert.value.attachments = [...uploadedFiles.value]
-    addSystemLog(currentAlert.value, '处理完成,预警已清除')
-    closeProcessModal()
+function extractDeviceCode(row) {
+  // remark 格式:"AUTO|equipmentCode|warningCode"
+  if (row.remark && row.remark.startsWith('AUTO|')) {
+    const parts = row.remark.split('|')
+    if (parts.length >= 2) searchDevice(parts[1])
+  } else {
+    const m = (row.warningContent || '').match(/设备 \[([^\]]+)\]/)
+    if (m) searchDevice(m[1])
   }
 }
 
-// ==================== 查看详情 ====================
-const detailModalVisible = ref(false)
+async function searchDevice(query) {
+  if (!query) return
+  deviceLoading.value = true
+  try {
+    const res = await getEquipmentFullPage(1, 20, { equipmentCode: query })
+    if (res.data) deviceOptions.value = (res.data.rows || []) || []
+  } catch(e) { console.warn('设备搜索失败', e) }
+  finally { deviceLoading.value = false }
+}
 
-const viewDetail = (alert) => {
-  currentAlert.value = alert
-  detailModalVisible.value = true
+async function submitConfirm() {
+  if (!deviceSelected.value) { ElMessage.warning('请选择关联设备'); return }
+  if (!confirmingRemark.value.trim()) { ElMessage.warning('请填写异常描述'); return }
+  
+  submitting.value = true
+  try {
+    await publishWarningData(current.value.warningId)
+    const orderDesc = `预警「${current.value.warningName}」异常确认;${confirmingRemark.value}`
+    const res = await addManholeWorkOrder({ alarmId: current.value.warningId, deviceId: deviceSelected.value, orderType: 1, orderLevel: getWorkOrderLevel(current.value.warningLevel), orderDesc: orderDesc.slice(0,500) })
+    ElMessage.success(`✅ 预警已确认 | 工单已生成 (WO-...) | 请前往工单系统继续处理`)
+    confirmVisible.value = false
+    loadList()
+  } catch(e) { ElMessage.error('确认失败:'+(e.msg||e.message||'重试')); console.error(e) }
+  finally { submitting.value=false }
 }
 
-const closeDetailModal = () => {
-  detailModalVisible.value = false
-  currentAlert.value = null
+function getWorkOrderLevel(warningLevel) { const lv = Number(warningLevel); return lv>=3?1:(lv===2?2:3) }
+
+// ========== 误报处理 ==========
+function confirmMisreport(row) {
+  ElMessageBox.prompt('请输入误报原因', '误报确认', {
+    inputPlaceholder: '例如:传感器误判、测试数据等',
+    inputValue: '',
+    inputPattern: /.*/,
+    message: '误报后将直接解除该预警',
+    confirmButtonText:'确认误报',
+    cancelButtonText:'取消'
+  }).then(async ({value}) => {
+    await resolveWarningData({ warningId: row.warningId, disposalContent: '误报:'+value })
+    ElMessage.success('已标记为误报并解除')
+    loadList()
+  }).catch(()=>{})
 }
 
-// 模拟系统日志
-const addSystemLog = (alert, action) => {
-  console.log(`[日志] ${new Date().toLocaleString()} - ${alert.deviceName}: ${action}`)
-  // 实际可扩展为独立日志组件
+// ========== 详情抽屉 ==========
+const detailVisible = ref(false)
+const detail = ref(null)
+
+async function openDetail(warningId) {
+  detailVisible.value = true
+  detail.value = null
+  try {
+    const res = await getWarningFullDetailData(warningId)
+    if (res.data) detail.value = res.data
+  } catch(e) { ElMessage.error('详情加载中'); console.error(e) }
 }
 
-// 地图点击处理
+onBeforeUnmount(() => {
+  window.removeEventListener('resize', resizeCharts)
+  trendChart?.dispose()
+  levelChart?.dispose()
+  typeChart?.dispose()
+})
+
+onMounted(async () => {
+  await nextTick()
+  await Promise.all([loadStats(), loadList()])
+  await fillChartData()
+  initCharts()
+  window.addEventListener('resize', resizeCharts)
+})
 </script>
 
 <style scoped>
-* { margin: 0; padding: 0; box-sizing: border-box; }
-
-.prototype-container {
-  max-width: 1600px;
-  margin: 0 auto;
-  background: #f0f4f8;
-  border-radius: 28px;
-  box-shadow: 0 20px 35px -12px rgba(0, 0, 0, 0.15);
-  overflow: hidden;
-  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
+.warning-container { padding: 20px; min-height: calc(100vh - 84px); }
+.stat-row { margin-bottom: 16px; }
+.stat-item {
+  background: #fff; border-radius: 12px; padding: 12px 10px; text-align: center;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.06); border: 1px solid #eef2f6;
+  transition: transform 0.2s, box-shadow 0.2s;
 }
-
-/* 头部 */
-.header {
-  background: #0a2b3c;
-  padding: 16px 28px;
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  flex-wrap: wrap;
-  gap: 16px;
-  border-bottom: 3px solid #e74c3c;
+.stat-item:hover { transform: translateY(-2px); box-shadow: 0 6px 16px rgba(0,0,0,0.1); }
+.stat-title { font-size: 13px; color: #7a8a9a; margin-bottom: 4px; }
+.stat-value { font-size: 28px; font-weight: 700; color: #3a4a5a; transition: all 0.3s; }
+.stat-value.primary { color: #409eff; }
+.stat-value.warning { color: #e6a23c; }
+.stat-value.info { color: #909399; }
+.stat-value.success { color: #67c23a; }
+.stat-value.done { color: #27ae60; }
+.stat-value.today { color: #9b59b6; }
+.stat-value.highlight { animation: pulse 1.5s ease-in-out; }
+@keyframes pulse { 0%{transform:scale(1)}50%{transform:scale(1.12)}100%{transform:scale(1)} }
+
+.chart-row { margin-bottom: 16px; }
+.chart-card { min-height: 300px; }
+.chart-empty {
+  display: flex; align-items: center; justify-content: center;
+  height: 240px; color: #c0c4cc; font-size: 14px;
+  flex-direction: column; gap: 8px;
 }
-
-.logo h2 { color: white; font-weight: 600; font-size: 1.3rem; }
-.logo p { color: #bdd8e8; font-size: 0.7rem; }
-
-.header-stats { display: flex; gap: 16px; }
-.stat-card { background: rgba(255,255,255,0.15); padding: 6px 16px; border-radius: 40px; text-align: center; color: white; }
-.stat-card.warning { background: #e74c3c; }
-.stat-card.success { background: #27ae60; }
-.stat-value { font-weight: 700; font-size: 1.2rem; margin-right: 6px; }
-.stat-label { font-size: 0.7rem; opacity: 0.9; }
-
-/* 布局 */
-.main-layout { display: flex; flex-wrap: wrap; }
-.left-panel { width: 420px; background: #f9fbfd; padding: 20px; }
-.right-panel { flex: 1; background: #ffffff; padding: 20px; min-width: 500px; }
-
-/* 卡片 */
-.card { background: white; border-radius: 20px; border: 1px solid #e6edf3; margin-bottom: 20px; overflow: hidden; }
-.card-header { background: #f9fafc; padding: 14px 18px; border-bottom: 1px solid #eef2f6; font-weight: 700; font-size: 0.85rem; color: #1d5a78; display: flex; justify-content: space-between; align-items: center; }
-.card-body { padding: 16px; }
-
-/* 物联感知地图 */
-.iot-map { background: #1a3a4a; border-radius: 16px; padding: 12px; }
-.map-container { position: relative; }
-.map-svg { width: 100%; height: 280px; background: linear-gradient(135deg, #1a3a4a 0%, #0e2a32 100%); border-radius: 12px; cursor: pointer; }
-.map-legend { display: flex; gap: 16px; margin-top: 12px; padding: 8px 12px; background: rgba(0,0,0,0.6); border-radius: 20px; color: white; font-size: 0.65rem; }
-.legend-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 4px; }
-.legend-dot.normal { background: #27ae60; }
-.legend-dot.offline { background: #e74c3c; }
-.legend-dot.special { background: #e74c3c; box-shadow: 0 0 8px #e74c3c; }
-.legend-dot.processing { background: #f39c12; }
-.pulse-ring { animation: pulse-ring 1.5s infinite; }
-
-/* 趋势图 */
-.trend-chart { display: flex; justify-content: space-around; align-items: flex-end; height: 100px; gap: 8px; }
-.trend-bar { display: flex; flex-direction: column; align-items: center; flex: 1; }
-.bar { background: #3498db; width: 100%; border-radius: 4px 4px 0 0; transition: height 0.3s; }
-.bar-label { font-size: 0.6rem; margin-top: 4px; color: #6f8ea8; }
-
-/* 筛选栏 */
-.filter-group { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
-.filter-input, .filter-select { padding: 6px 10px; border-radius: 20px; border: 1px solid #cbdde6; font-size: 0.7rem; }
-.filter-sep { font-size: 0.7rem; color: #6f8ea8; }
-.btn-primary, .btn-outline, .btn-sm { padding: 5px 12px; border-radius: 30px; font-size: 0.7rem; cursor: pointer; }
-.btn-primary { background: #3498db; border: none; color: white; }
-.btn-outline { background: white; border: 1px solid #3498db; color: #3498db; }
-.btn-confirm { background: #27ae60; color: white; border: none; padding: 4px 10px; border-radius: 20px; font-size: 0.65rem; cursor: pointer; }
-.btn-misreport { background: #95a5a6; color: white; border: none; padding: 4px 10px; border-radius: 20px; font-size: 0.65rem; cursor: pointer; }
-.btn-assign { background: #f39c12; color: white; border: none; padding: 4px 10px; border-radius: 20px; font-size: 0.65rem; cursor: pointer; }
-.btn-process { background: #3498db; color: white; border: none; padding: 4px 10px; border-radius: 20px; font-size: 0.65rem; cursor: pointer; }
-.btn-view { background: #1abc9c; color: white; border: none; padding: 4px 10px; border-radius: 20px; font-size: 0.65rem; cursor: pointer; }
-.btn-success { background: #27ae60; color: white; border: none; padding: 8px 16px; border-radius: 30px; cursor: pointer; }
-
-/* 表格 */
-.alert-table { overflow-x: auto; }
-.data-table { width: 100%; border-collapse: collapse; font-size: 0.7rem; }
-.data-table th { background: #f8fafc; padding: 10px 8px; text-align: left; font-weight: 600; color: #2c5a72; border-bottom: 1px solid #e2edf2; }
-.data-table td { padding: 10px 8px; border-bottom: 1px solid #f0f4f7; vertical-align: middle; }
-.time-cell { white-space: nowrap; font-family: monospace; }
-.content-cell { max-width: 200px; overflow: hidden; text-overflow: ellipsis; }
-.action-cell { white-space: nowrap; display: flex; gap: 6px; flex-wrap: wrap; }
-.empty-row { text-align: center; padding: 30px; color: #8aaec2; }
-.list-count { font-size: 0.65rem; font-weight: normal; color: #6f8ea8; }
-
-/* 状态标签 */
-.alert-type { padding: 2px 8px; border-radius: 20px; font-size: 0.65rem; }
-.type-normal { background: #e8f4fd; color: #3498db; }
-.type-special { background: #fef3c7; color: #f39c12; }
-.status-badge { padding: 2px 8px; border-radius: 20px; font-size: 0.65rem; }
-.status-unconfirmed { background: #fef3c7; color: #f39c12; }
-.status-confirmed { background: #e3f2fd; color: #3498db; }
-.status-processing { background: #fce4ec; color: #e74c3c; }
-.status-resolved { background: #d5f5e3; color: #27ae60; }
-.status-misreport { background: #f5f5f5; color: #95a5a6; }
-.completed-badge { color: #27ae60; font-size: 0.65rem; }
-
-/* 分页 */
-.pagination { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }
-.page-btn { background: white; border: 1px solid #cbdde6; padding: 4px 10px; border-radius: 20px; font-size: 0.7rem; cursor: pointer; }
-.page-btn.active { background: #3498db; color: white; border-color: #3498db; }
-
-/* 模态框 */
-.modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 1000; }
-.modal-content { background: white; border-radius: 24px; width: 450px; max-width: 90%; padding: 24px; }
-.form-group { margin-bottom: 16px; }
-.form-label { font-size: 0.7rem; font-weight: 600; margin-bottom: 6px; display: block; color: #2c5a72; }
-.form-select, .form-textarea { width: 100%; padding: 8px 12px; border-radius: 12px; border: 1px solid #cbdde6; font-size: 0.75rem; }
-.form-actions { display: flex; gap: 12px; justify-content: flex-end; margin-top: 20px; }
-.confirm-info, .process-info, .detail-info { background: #f8fafc; border-radius: 16px; padding: 12px; margin-bottom: 16px; }
-.info-row { font-size: 0.75rem; margin-bottom: 8px; }
-.info-row .label { font-weight: 600; width: 90px; display: inline-block; color: #2c5a72; }
-
-/* 上传区域 */
-.upload-area { border: 1px dashed #cbdde6; border-radius: 12px; padding: 12px; }
-.upload-btn { background: #3498db; color: white; padding: 6px 14px; border-radius: 30px; font-size: 0.7rem; cursor: pointer; display: inline-block; }
-.file-list { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
-.file-tag { background: #eef2fa; padding: 4px 8px; border-radius: 20px; font-size: 0.65rem; display: inline-flex; align-items: center; gap: 6px; }
-.remove-file { background: none; border: none; color: #e74c3c; cursor: pointer; }
-
-.live-badge { font-size: 0.65rem; background: #e74c3c; color: white; padding: 2px 10px; border-radius: 20px; animation: blink 1s infinite; }
-
-@keyframes blink { 0%,100% { opacity: 1; } 50% { opacity: 0.6; } }
-@keyframes pulse-ring { 0%,100% { opacity: 1; } 50% { opacity: 0.3; } }
-</style>
+.chart-empty::before { content:'\1F4CA'; font-size: 32px; opacity: 0.5; }
+
+.search-card { margin-bottom: 16px; }
+.table-card { min-height: 600px; }
+.device-cell { display:flex; align-items:center; gap:4px; }
+.no-device { color:#c0c4cc; font-size:12px; }
+
+.detail-section-title { font-weight:600;font-size:14px;color:#2c3e50;margin:16px 0 10px;padding-bottom:8px;border-bottom:1px solid #eef2f6 }
+.detail-sub { font-size:12px;color:#9098a6;margin-top:4px }
+.detail-empty { font-size:13px;color:#9098a6;padding:12px 0 }
+.attachment-list { display:flex;flex-direction:column;gap:6px }
+.attachment-link { font-size:13px;color:#3498db;text-decoration:none }
+.attachment-link:hover { text-decoration:underline }
+
+@media (max-width: 1300px) {
+  .stat-row { display:flex; gap:10px; flex-wrap:wrap; }
+  .stat-row > * { flex-shrink:0; width: calc(33.33% - 14px) !important; }
+}
+</style>

+ 796 - 685
src/views/subSystem/pipeNetwork/pworkOrder/Pdayinspect.vue

@@ -1,768 +1,879 @@
-<!--日常巡检-->
+<!--日常巡检工单(orderType=2,参考排水/燃气工单:搜索卡 + 左列表 + 右详情 tabs + 按钮操作 + 部门树派单)-->
+<!--数据源:/api/work-order/*(orderType=2 过滤;equipmentType 为空 = 所有设备)-->
 <template>
-  <div class="prototype-container">
-    <!-- 头部 -->
-    <div class="header">
-      <div class="logo">
-        <h2>📋 日常巡检管理系统</h2>
-        <p>巡检任务管理 | 问题跟踪 | 闭环处理 | 多维度查询</p>
+  <div class="order-container">
+    <!-- 顶部搜索区(参考排水/燃气工单) -->
+    <el-card class="search-card" shadow="never">
+      <div class="search-form">
+        <el-form :model="queryParams" ref="queryFormRef" :inline>
+          <el-form-item label="优先级">
+            <el-select v-model="queryParams.orderLevel" placeholder="全部优先级" clearable style="width: 120px">
+              <el-option v-for="(v, k) in ORDER_LEVEL_MAP" :key="k" :label="v.text" :value="Number(k)"/>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="状态">
+            <el-select v-model="queryParams.orderStatus" placeholder="全部状态" clearable style="width: 120px">
+              <el-option v-for="(v, k) in ORDER_STATUS_MAP" :key="k" :label="v.text" :value="Number(k)"/>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="关键词">
+            <el-input v-model="queryParams.keyword" placeholder="巡检内容/工单编号" clearable style="width: 200px" @keyup.enter="handleSearch"/>
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" @click="handleSearch">查询</el-button>
+            <el-button @click="handleReset">重置</el-button>
+            <el-button type="primary" plain @click="openAddDialog">➕ 新增巡检</el-button>
+          </el-form-item>
+        </el-form>
       </div>
-      <div class="header-stats">
-        <div class="stat-card pending">
-          <span class="stat-value">{{ pendingCount }}</span>
-          <span class="stat-label">待处理</span>
-        </div>
-        <div class="stat-card processing">
-          <span class="stat-value">{{ processingCount }}</span>
-          <span class="stat-label">处理中</span>
-        </div>
-        <div class="stat-card completed">
-          <span class="stat-value">{{ completedCount }}</span>
-          <span class="stat-label">已完成</span>
-        </div>
-      </div>
-    </div>
-
-    <div class="main-layout">
-      <!-- 左侧:新增巡检表单 -->
-      <div class="left-panel">
-        <div class="card">
-          <div class="card-header">
-            ➕ 新增日常巡检
-            <span class="card-tip">请填写巡检信息</span>
-          </div>
-          <div class="card-body">
-            <form @submit.prevent="addInspection">
-              <div class="form-group">
-                <label class="form-label">巡检内容 *</label>
-                <textarea v-model="newInspection.content" class="form-textarea" rows="2" placeholder="请输入巡检内容,如:泵房设备检查、仪表读数记录、管道巡查等..."></textarea>
-              </div>
-              <div class="form-row">
-                <div class="form-group half">
-                  <label class="form-label">巡检时间 *</label>
-                  <input type="datetime-local" v-model="newInspection.inspectTime" class="form-input" required>
-                </div>
-                <div class="form-group half">
-                  <label class="form-label">巡检人员</label>
-                  <input v-model="newInspection.inspector" class="form-input" placeholder="巡检人姓名">
-                </div>
-              </div>
-              <div class="form-group">
-                <label class="form-label">巡检问题</label>
-                <textarea v-model="newInspection.issue" class="form-textarea" rows="2" placeholder="如发现问题请详细描述,无问题可不填..."></textarea>
-              </div>
-              <div class="form-group">
-                <label class="form-label">处理过程</label>
-                <textarea v-model="newInspection.process" class="form-textarea" rows="2" placeholder="问题处理过程描述..."></textarea>
-              </div>
-              <div class="form-row">
-                <div class="form-group half">
-                  <label class="form-label">是否处理完成</label>
-                  <select v-model="newInspection.isCompleted" class="form-select">
-                    <option :value="false">未完成</option>
-                    <option :value="true">已完成</option>
-                  </select>
+    </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="form-group half">
-                  <label class="form-label">完成时间</label>
-                  <input type="datetime-local" v-model="newInspection.completeTime" class="form-input" :disabled="!newInspection.isCompleted">
+                <div class="order-body">
+                  <div class="order-tags">
+                    <el-tag :type="orderLevelType(order.orderLevel)" size="small" effect="plain">{{ orderLevelText(order.orderLevel) }}</el-tag>
+                    <el-tag v-if="order.hazardNo" size="small" effect="plain">隐患 {{ order.hazardNo }}</el-tag>
+                  </div>
+                  <div class="order-desc" v-if="order.orderDesc">{{ order.orderDesc }}</div>
+                  <div class="device-code">巡检人员:{{ userNames(order.receiveUser) || '-' }}</div>
+                  <div class="create-time">计划巡检:{{ timeText(order.planFinishTime) }}</div>
                 </div>
               </div>
-              <div class="form-actions">
-                <button type="submit" class="btn-primary">✅ 提交巡检记录</button>
-                <button type="button" @click="resetForm" class="btn-outline">重置</button>
-              </div>
-            </form>
-          </div>
-        </div>
-
-        <!-- 快速统计 -->
-        <div class="card">
-          <div class="card-header">📊 巡检统计概览</div>
-          <div class="card-body">
-            <div class="stats-grid">
-              <div class="stat-circle">
-                <div class="circle-num">{{ completionRate }}%</div>
-                <div class="circle-label">完成率</div>
-              </div>
-              <div class="stat-list">
-                <div class="stat-row"><span>本月巡检</span><span>{{ monthlyCount }}</span></div>
-                <div class="stat-row"><span>本周巡检</span><span>{{ weeklyCount }}</span></div>
-                <div class="stat-row"><span>问题发现率</span><span>{{ issueRate }}%</span></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>
-        </div>
-      </div>
-
-      <!-- 右侧:巡检列表 -->
-      <div class="right-panel">
-        <div class="card">
-          <div class="card-header">
-            📋 日常巡检记录列表
-            <span class="list-count">共 {{ filteredInspections.length }} 条记录</span>
-          </div>
-          <div class="card-body">
-            <!-- 查询筛选栏 -->
-            <div class="filter-bar">
-              <div class="filter-group">
-                <label class="filter-label">巡检时间范围</label>
-                <div class="date-range">
-                  <input type="date" v-model="queryParams.startDate" class="filter-input" placeholder="开始日期">
-                  <span class="filter-sep">至</span>
-                  <input type="date" v-model="queryParams.endDate" class="filter-input" placeholder="结束日期">
-                </div>
-              </div>
-              <div class="filter-group">
-                <label class="filter-label">完成状态</label>
-                <select v-model="queryParams.status" class="filter-select">
-                  <option value="">全部</option>
-                  <option value="pending">待处理</option>
-                  <option value="processing">处理中</option>
-                  <option value="completed">已完成</option>
-                </select>
-              </div>
-              <div class="filter-group">
-                <label class="filter-label">关键词</label>
-                <input type="text" v-model="queryParams.keyword" class="filter-input" placeholder="巡检内容/问题">
-              </div>
-              <div class="filter-actions">
-                <button @click="searchInspections" class="btn-primary btn-sm">🔍 查询</button>
-                <button @click="resetQuery" class="btn-outline btn-sm">重置</button>
-              </div>
+        </el-card>
+      </el-col>
+
+      <!-- 右侧:巡检工单详情 -->
+      <el-col :span="16" class="col-fill">
+        <el-card v-loading="detailLoading" class="box-card" shadow="always">
+          <template #header>
+            <div class="card-header">
+              <span v-if="selectedOrder && selectedOrder.orderId">巡检详情 - {{ selectedOrder.orderNo }}</span>
+              <span v-else>请选择巡检工单查看详情</span>
             </div>
+          </template>
+
+          <div v-if="selectedOrder && selectedOrder.orderId" class="detail-body">
+            <el-tabs v-model="activeTab" class="detail-tabs">
+              <!-- 工单信息 -->
+              <el-tab-pane label="工单信息" name="info">
+                <el-descriptions :column="2" border size="small">
+                  <el-descriptions-item label="工单编号">{{ selectedOrder.orderNo }}</el-descriptions-item>
+                  <el-descriptions-item label="工单状态">
+                    <el-tag :type="orderStatusType(selectedOrder.orderStatus)" size="small">{{ orderStatusText(selectedOrder.orderStatus) }}</el-tag>
+                  </el-descriptions-item>
+                  <el-descriptions-item label="关联隐患编号">{{ selectedOrder.hazardNo || '-' }}</el-descriptions-item>
+                  <el-descriptions-item label="设备编号">{{ selectedOrder.deviceCode || '-' }}</el-descriptions-item>
+                  <el-descriptions-item label="优先级">
+                    <el-tag :type="orderLevelType(selectedOrder.orderLevel)" size="small">{{ orderLevelText(selectedOrder.orderLevel) }}</el-tag>
+                  </el-descriptions-item>
+                  <el-descriptions-item label="巡检人员">{{ userNames(selectedOrder.receiveUser) || '-' }}</el-descriptions-item>
+                  <el-descriptions-item label="巡检内容" :span="2">{{ selectedOrder.orderDesc || '-' }}</el-descriptions-item>
+                  <el-descriptions-item label="计划巡检时间">{{ timeText(selectedOrder.planFinishTime) }}</el-descriptions-item>
+                  <el-descriptions-item label="派单时间">{{ timeText(selectedOrder.dispatchTime) }}</el-descriptions-item>
+                  <el-descriptions-item label="实际完成时间">{{ timeText(selectedOrder.realFinishTime) }}</el-descriptions-item>
+                  <el-descriptions-item label="验收时间">{{ timeText(selectedOrder.verifyTime) }}</el-descriptions-item>
+                  <el-descriptions-item label="创建时间">{{ timeText(selectedOrder.createTime) }}</el-descriptions-item>
+                  <el-descriptions-item label="更新时间">{{ timeText(selectedOrder.updateTime) }}</el-descriptions-item>
+                  <el-descriptions-item label="处置结果" :span="2">{{ selectedOrder.repairResult || '-' }}</el-descriptions-item>
+                </el-descriptions>
+
+                <!-- 关联设备信息 -->
+                <div v-if="detailDevice" class="device-section">
+                  <h4 class="section-title">🔗 关联设备</h4>
+                  <el-descriptions :column="2" border size="small">
+                    <el-descriptions-item label="设备类型">{{ detailDevice.equipmentTypeName || '-' }}</el-descriptions-item>
+                    <el-descriptions-item label="设备编码">{{ detailDevice.equipmentCode || '-' }}</el-descriptions-item>
+                    <el-descriptions-item label="安装位置">{{ detailDevice.equipmentLocation || '-' }}</el-descriptions-item>
+                    <el-descriptions-item label="管点地址">{{ detailDevice.pointAddress || '-' }}</el-descriptions-item>
+                    <el-descriptions-item label="设备厂家">{{ detailDevice.manufacturer || '-' }}</el-descriptions-item>
+                    <el-descriptions-item label="所属管线">{{ detailDevice.networkName || '-' }}</el-descriptions-item>
+                    <el-descriptions-item label="维护人员">{{ detailDevice.maintainer || '-' }}</el-descriptions-item>
+                    <el-descriptions-item label="联系方式">{{ detailDevice.maintainerPhone || '-' }}</el-descriptions-item>
+                  </el-descriptions>
+                </div>
 
-            <!-- 巡检列表表格 -->
-            <div class="inspection-table-wrapper">
-              <table class="data-table">
-                <thead>
-                <tr>
-                  <th>巡检时间</th>
-                  <th>巡检内容</th>
-                  <th>巡检人员</th>
-                  <th>巡检问题</th>
-                  <th>处理过程</th>
-                  <th>完成状态</th>
-                  <th>完成时间</th>
-                  <th>操作</th>
-                </tr>
-                </thead>
-                <tbody>
-                <tr v-for="item in paginatedInspections" :key="item.id">
-                  <td class="time-cell">{{ formatDateTime(item.inspectTime) }}</td>
-                  <td class="content-cell">{{ truncateText(item.content, 40) }}</td>
-                  <td>{{ item.inspector || '-' }}</td>
-                  <td class="issue-cell">
-                    <span v-if="item.issue" class="issue-badge">有</span>
-                    <span v-else class="no-issue">无</span>
-                    <span class="issue-text">{{ truncateText(item.issue, 30) }}</span>
-                  </td>
-                  <td class="process-cell">{{ truncateText(item.process, 30) || '-' }}</td>
-                  <td>
-                      <span class="status-badge" :class="getStatusClass(item)">
-                        {{ getStatusText(item) }}
-                      </span>
-                  </td>
-                  <td class="time-cell">{{ formatDateTime(item.completeTime) || '-' }}</td>
-                  <td class="action-cell">
-                    <button @click="viewDetail(item)" class="btn-view">查看</button>
-                    <button v-if="!item.isCompleted" @click="openProcessModal(item)" class="btn-process">处理</button>
-                    <button @click="editInspection(item)" class="btn-edit">编辑</button>
-                    <button @click="deleteInspection(item)" class="btn-delete">删除</button>
-                  </td>
-                </tr>
-                <tr v-if="filteredInspections.length === 0">
-                  <td colspan="8" class="empty-row">暂无巡检记录,请点击新增进行添加</td>
-                </tr>
-                </tbody>
-              </table>
-            </div>
+                <!-- 工单图片 -->
+                <div v-if="workImageList.length > 0" class="image-section">
+                  <h4 class="section-title">工单图片</h4>
+                  <div class="image-list">
+                    <image-preview v-for="(img, index) in workImageList" :key="index" :src="img" :width="100" :height="100"/>
+                  </div>
+                </div>
 
-            <!-- 分页 -->
-            <div class="pagination" v-if="totalPages > 1">
-              <button v-for="page in totalPages" :key="page" class="page-btn" :class="{ active: currentPage === page }"
-                      @click="currentPage = page">{{ page }}</button>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-
-    <!-- 查看详情模态框 -->
-    <div v-if="detailModalVisible" class="modal-overlay" @click.self="closeDetailModal">
-      <div class="modal-content">
-        <h3>📋 巡检详情</h3>
-        <div class="detail-info">
-          <div class="info-row"><span class="label">巡检时间:</span>{{ formatDateTime(currentItem?.inspectTime) }}</div>
-          <div class="info-row"><span class="label">巡检人员:</span>{{ currentItem?.inspector || '-' }}</div>
-          <div class="info-row"><span class="label">巡检内容:</span>{{ currentItem?.content }}</div>
-          <div class="info-row"><span class="label">巡检问题:</span>{{ currentItem?.issue || '无' }}</div>
-          <div class="info-row"><span class="label">处理过程:</span>{{ currentItem?.process || '-' }}</div>
-          <div class="info-row"><span class="label">完成状态:</span>
-            <span class="status-badge" :class="getStatusClass(currentItem)">{{ getStatusText(currentItem) }}</span>
+                <!-- 工单操作(参考排水/燃气工单按钮组) -->
+                <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>
-          <div class="info-row"><span class="label">完成时间:</span>{{ formatDateTime(currentItem?.completeTime) || '-' }}</div>
-          <div class="info-row" v-if="currentItem?.remark"><span class="label">备注:</span>{{ currentItem?.remark }}</div>
-        </div>
-        <div class="form-actions">
-          <button @click="closeDetailModal" class="btn-primary">关闭</button>
-        </div>
-      </div>
-    </div>
-
-    <!-- 处理模态框 -->
-    <div v-if="processModalVisible" class="modal-overlay" @click.self="closeProcessModal">
-      <div class="modal-content" style="width:500px;">
-        <h3>🔧 处理巡检问题</h3>
-        <div class="process-info-summary">
-          <div><span class="label">巡检时间:</span>{{ formatDateTime(currentItem?.inspectTime) }}</div>
-          <div><span class="label">巡检内容:</span>{{ currentItem?.content }}</div>
-          <div><span class="label">巡检问题:</span>{{ currentItem?.issue }}</div>
-        </div>
-        <div class="form-group">
-          <label class="form-label">处理过程 *</label>
-          <textarea v-model="processData.process" class="form-textarea" rows="3" placeholder="请详细描述处理过程..."></textarea>
-        </div>
-        <div class="form-group">
-          <label class="form-label">是否完成</label>
-          <select v-model="processData.isCompleted" class="form-select">
-            <option :value="true">已完成</option>
-            <option :value="false">未完成(继续跟进)</option>
-          </select>
-        </div>
-        <div class="form-group" v-if="processData.isCompleted">
-          <label class="form-label">完成时间</label>
-          <input type="datetime-local" v-model="processData.completeTime" class="form-input">
+
+          <el-empty v-else description="请从左侧选择巡检工单" :image-size="150"/>
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <!-- 新增巡检弹窗(提交后自动派单) -->
+    <el-dialog v-model="addVisible" title="➕ 新增日常巡检" width="560px" append-to-body destroy-on-close>
+      <el-form ref="addFormRef" :model="addForm" :rules="addRules" label-width="110px">
+        <el-form-item label="巡检内容" prop="orderDesc">
+          <el-input v-model="addForm.orderDesc" type="textarea" :rows="3" placeholder="如:泵房设备检查、仪表读数记录、管道巡查" maxlength="300" show-word-limit/>
+        </el-form-item>
+        <el-form-item label="计划巡检时间" prop="planFinishTime">
+          <el-date-picker v-model="addForm.planFinishTime" type="datetime" placeholder="计划巡检时间" value-format="YYYY-MM-DD HH:mm:ss" style="width:100%"/>
+        </el-form-item>
+        <el-form-item label="巡检人员" prop="receiveUserId">
+          <el-select v-model="addForm.receiveUserId" filterable placeholder="选择巡检人员(提交后自动派单)" style="width:100%">
+            <el-option v-for="u in userOptions" :key="u.userId" :label="u.nickName + '(' + u.userName + ')'" :value="u.userId"/>
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="addVisible=false">取消</el-button>
+        <el-button type="primary" :loading="submitting" @click="submitAdd">提交并派单</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 派单弹窗(部门树 + 用户多选,参考排水/燃气工单) -->
+    <el-dialog v-model="dispatchDialogVisible" title="下派任务 - 选择接单人" width="750px" append-to-body>
+      <div style="display: flex; gap: 16px; min-height: 350px;">
+        <div style="width: 280px; border-right: 1px solid #e4e7ed; padding-right: 12px;">
+          <el-input v-model="deptFilterText" placeholder="筛选部门" clearable size="small" style="margin-bottom: 8px"/>
+          <el-tree
+            ref="deptTreeRef"
+            :data="deptTreeData"
+            :props="{ label: 'deptName', children: 'children' }"
+            :filter-node-method="filterDeptNode"
+            node-key="deptId"
+            highlight-current
+            @node-click="handleDeptClick"
+            style="font-size: 14px"
+          />
         </div>
-        <div class="form-actions">
-          <button @click="closeProcessModal" class="btn-outline">取消</button>
-          <button @click="submitProcess" class="btn-primary">提交处理</button>
+        <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>
-    </div>
-
-    <!-- 编辑模态框 -->
-    <div v-if="editModalVisible" class="modal-overlay" @click.self="closeEditModal">
-      <div class="modal-content" style="width:550px;">
-        <h3>✏️ 编辑巡检记录</h3>
-        <form @submit.prevent="saveEdit">
-          <div class="form-group">
-            <label class="form-label">巡检内容 *</label>
-            <textarea v-model="editForm.content" class="form-textarea" rows="2" required></textarea>
-          </div>
-          <div class="form-row">
-            <div class="form-group half">
-              <label class="form-label">巡检时间 *</label>
-              <input type="datetime-local" v-model="editForm.inspectTime" class="form-input" required>
-            </div>
-            <div class="form-group half">
-              <label class="form-label">巡检人员</label>
-              <input v-model="editForm.inspector" class="form-input">
-            </div>
-          </div>
-          <div class="form-group">
-            <label class="form-label">巡检问题</label>
-            <textarea v-model="editForm.issue" class="form-textarea" rows="2"></textarea>
-          </div>
-          <div class="form-group">
-            <label class="form-label">处理过程</label>
-            <textarea v-model="editForm.process" class="form-textarea" rows="2"></textarea>
-          </div>
-          <div class="form-row">
-            <div class="form-group half">
-              <label class="form-label">是否处理完成</label>
-              <select v-model="editForm.isCompleted" class="form-select">
-                <option :value="false">未完成</option>
-                <option :value="true">已完成</option>
-              </select>
-            </div>
-            <div class="form-group half">
-              <label class="form-label">完成时间</label>
-              <input type="datetime-local" v-model="editForm.completeTime" class="form-input" :disabled="!editForm.isCompleted">
-            </div>
-          </div>
-          <div class="form-actions">
-            <button type="button" @click="closeEditModal" class="btn-outline">取消</button>
-            <button type="submit" class="btn-primary">保存修改</button>
-          </div>
-        </form>
-      </div>
-    </div>
+      <el-form label-width="80px" style="margin-top: 16px">
+        <el-form-item label="备注"><el-input v-model="dispatchForm.remarks" placeholder="请输入派单备注"/></el-form-item>
+      </el-form>
+      <template #footer>
+        <span style="float: left; line-height: 32px; color: #606266;">
+          已选人员:
+          <span v-if="selectedUserNames.length > 0" style="color: #409eff; font-weight: bold;">{{ selectedUserNames.join('、') }}</span>
+          <span v-else style="color: #909399">未选择</span>
+          ({{ dispatchForm.handlerIds.length }})
+        </span>
+        <el-button @click="dispatchDialogVisible = false">取消</el-button>
+        <el-button type="primary" :loading="submitting" @click="submitDispatch">确认</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 核查/处理弹窗(处理过程写入 solution,后端存入 repairResult 展示) -->
+    <el-dialog v-model="actionDialogVisible" :title="actionTitle" width="600px" append-to-body>
+      <el-form label-width="90px">
+        <el-form-item label="结果说明" required>
+          <el-input v-model="actionFormData.solution" type="textarea" :rows="5" :placeholder="actionTitle === '核查工单' ? '请输入核查结论(属实/不属实、现场情况)' : '请输入巡检问题及处理过程、处理结果'" maxlength="500" show-word-limit/>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="actionDialogVisible = false">取消</el-button>
+        <el-button type="primary" :loading="submitting" @click="submitAction">确定</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 提交验收弹窗 -->
+    <el-dialog v-model="completeDialogVisible" title="提交验收" width="500px" append-to-body>
+      <el-form label-width="80px">
+        <el-form-item label="验收意见">
+          <el-input v-model="completeForm.result" type="textarea" :rows="4" placeholder="请填写巡检结果确认说明" maxlength="500" show-word-limit/>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="completeDialogVisible = false">取消</el-button>
+        <el-button type="primary" :loading="submitting" @click="submitComplete">提交</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 验收通过/结案弹窗 -->
+    <el-dialog v-model="closeDialogVisible" title="验收通过" width="500px" append-to-body>
+      <el-form label-width="80px">
+        <el-form-item label="验收结果">
+          <el-input v-model="closeForm.result" type="textarea" :rows="4" placeholder="请填写巡检验收结果说明" maxlength="500" show-word-limit/>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="closeDialogVisible = false">取消</el-button>
+        <el-button type="success" :loading="submitting" @click="submitClose">确认验收</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
-import { ref, computed } from 'vue'
-
-// ==================== 巡检数据 ====================
-const inspections = ref([
-  {
-    id: 1,
-    content: "城东泵房日常巡检:检查设备运行状态、仪表读数",
-    inspectTime: "2025-05-08T09:00:00",
-    inspector: "张建国",
-    issue: "2号水泵有异响,需进一步检查",
-    process: "已联系维修人员,待拆解检查",
-    isCompleted: false,
-    completeTime: null,
-    remark: ""
-  },
-  {
-    id: 2,
-    content: "化工园区燃气管道巡查",
-    inspectTime: "2025-05-08T14:30:00",
-    inspector: "李卫东",
-    issue: "",
-    process: "",
-    isCompleted: true,
-    completeTime: "2025-05-08T16:30:00",
-    remark: "管道运行正常"
-  },
-  {
-    id: 3,
-    content: "城南枢纽流量计校准检查",
-    inspectTime: "2025-05-07T10:15:00",
-    inspector: "王敏",
-    issue: "流量计显示偏差较大",
-    process: "已重新校准,数据恢复正常",
-    isCompleted: true,
-    completeTime: "2025-05-07T11:45:00",
-    remark: ""
-  },
-  {
-    id: 4,
-    content: "北环加压站设备巡检",
-    inspectTime: "2025-05-07T15:20:00",
-    inspector: "陈方",
-    issue: "压力表读数异常",
-    process: "正在排查原因",
-    isCompleted: false,
-    completeTime: null,
-    remark: ""
-  },
-  {
-    id: 5,
-    content: "城西换热站温度传感器检查",
-    inspectTime: "2025-05-06T11:00:00",
-    inspector: "赵伟",
-    issue: "温度传感器数据滞后",
-    process: "已更换传感器,恢复正常",
-    isCompleted: true,
-    completeTime: "2025-05-06T15:30:00",
-    remark: ""
-  }
-])
-
-// 新增表单数据
-const newInspection = ref({
-  content: '',
-  inspectTime: new Date().toISOString().slice(0, 16),
-  inspector: '',
-  issue: '',
-  process: '',
-  isCompleted: false,
-  completeTime: ''
-})
+import { ref, reactive, computed, watch, onMounted } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import ImagePreview from '@/components/ImagePreview'
+import { listWorkOrder, getWorkOrderDetail, addWorkOrder, dispatchWorkOrder, verifyWorkOrder, processWorkOrder, completeWorkOrder, closeWorkOrder, cancelWorkOrder } from '@/api/work/order'
+import { getEquipmentFullPage } from '@/api/pipeNetwork/basic'
+import { listDept } from '@/api/system/dept'
+import { listUser } from '@/api/system/user'
+import { handleTree } from '@/utils/ruoyi'
+import { ORDER_STATUS_MAP, ORDER_LEVEL_MAP, orderStatusText, orderStatusType, orderLevelText, orderLevelType, orderLogText, orderLogType, canOperate, timeText } from './workOrderDict'
+
+// ========== 查询 & 列表(orderType=2 日常巡检) ==========
+const listLoading = ref(false)
+const tableData = ref([])
+const pageNum = ref(1)
+const pageSize = ref(10)
+const total = ref(0)
+const queryFormRef = ref(null)
+const queryParams = reactive({ orderLevel: null, orderStatus: null, keyword: '' })
+
+async function handleSearch() {
+  listLoading.value = true
+  try {
+    const params = { orderType: 2 }
+    if (queryParams.orderLevel) params.orderLevel = queryParams.orderLevel
+    if (queryParams.orderStatus) params.orderStatus = queryParams.orderStatus
+    if (queryParams.keyword) params.keyword = queryParams.keyword
+    const res = await listWorkOrder(params, pageNum.value, pageSize.value)
+    if (res.data) {
+      tableData.value = res.data.records || []
+      total.value = Number(res.data.total || 0)
+    }
+  } catch(e) { ElMessage.error('查询失败'); console.error(e) }
+  finally { listLoading.value = false }
+}
 
-// 查询参数
-const queryParams = ref({
-  startDate: '',
-  endDate: '',
-  status: '',
-  keyword: ''
-})
+function handleReset() {
+  queryFormRef.value && queryFormRef.value.resetFields()
+  queryParams.orderLevel = null
+  queryParams.orderStatus = null
+  queryParams.keyword = ''
+  pageNum.value = 1
+  selectedOrderId.value = null
+  selectedOrder.value = {}
+  handleSearch()
+}
 
-// 分页
-const currentPage = ref(1)
-const pageSize = 6
-
-// 模态框状态
-const detailModalVisible = ref(false)
-const processModalVisible = ref(false)
-const editModalVisible = ref(false)
-const currentItem = ref(null)
-
-// 处理表单数据
-const processData = ref({
-  process: '',
-  isCompleted: true,
-  completeTime: new Date().toISOString().slice(0, 16)
+// ========== 选中工单 & 详情(合并 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)
+
+// 工单图片列表(多个用逗号分隔)
+const workImageList = computed(() => {
+  if (!selectedOrder.value.workImage) return []
+  return selectedOrder.value.workImage.split(',').filter(img => img && img.trim())
 })
 
-// 编辑表单数据
-const editForm = ref({
-  id: null,
-  content: '',
-  inspectTime: '',
-  inspector: '',
-  issue: '',
-  process: '',
-  isCompleted: false,
-  completeTime: ''
-})
+async function handleSelectOrder(order) {
+  selectedOrderId.value = order.orderId
+  selectedOrder.value = { ...order }
+  activeTab.value = 'info'
+  currentOrderLogs.value = []
+  detailDevice.value = null
+  await loadOrderDetail(order.orderId)
+}
+
+async function loadOrderDetail(orderId) {
+  detailLoading.value = true
+  try {
+    const res = await getWorkOrderDetail(orderId)
+    if (res.code === 200 && res.data) {
+      // 后端返回 { order, logs } 两层结构,必须合并 order 层级
+      selectedOrder.value = { ...selectedOrder.value, ...res.data.order }
+      currentOrderLogs.value = res.data.logs || []
+      if (res.data.order?.deviceCode) {
+        loadDeviceInfo(res.data.order.deviceCode)
+      }
+    }
+  } catch(e) { ElMessage.error('加载巡检详情失败'); console.error(e) }
+  finally { detailLoading.value = false }
+}
 
-// ==================== 辅助函数 ====================
-const formatDateTime = (dateTimeStr) => {
-  if (!dateTimeStr) return ''
-  return dateTimeStr.replace('T', ' ')
+async function loadDeviceInfo(codeOrId) {
+  try {
+    const res = await getEquipmentFullPage(1, 1, { equipmentCode: codeOrId })
+    const rows = res.data?.records || res.data?.rows || []
+    if (rows.length) { detailDevice.value = rows[0]; return }
+  } catch(e) { console.warn('加载设备信息失败', e) }
 }
 
-const truncateText = (text, maxLen) => {
-  if (!text) return ''
-  return text.length > maxLen ? text.substring(0, maxLen) + '...' : text
+function handleSizeChange() {
+  pageNum.value = 1
+  selectedOrderId.value = null
+  selectedOrder.value = {}
+  handleSearch()
 }
 
-// 计算统计
-const pendingCount = computed(() => inspections.value.filter(i => !i.isCompleted && i.issue).length)
-const processingCount = computed(() => inspections.value.filter(i => !i.isCompleted && i.process).length)
-const completedCount = computed(() => inspections.value.filter(i => i.isCompleted).length)
-
-const monthlyCount = computed(() => {
-  const now = new Date()
-  const month = now.getMonth()
-  const year = now.getFullYear()
-  return inspections.value.filter(i => {
-    const d = new Date(i.inspectTime)
-    return d.getMonth() === month && d.getFullYear() === year
-  }).length
-})
+function handlePageChange() {
+  selectedOrderId.value = null
+  selectedOrder.value = {}
+  handleSearch()
+}
 
-const weeklyCount = computed(() => {
-  const now = new Date()
-  const weekStart = new Date(now)
-  weekStart.setDate(now.getDate() - now.getDay())
-  return inspections.value.filter(i => new Date(i.inspectTime) >= weekStart).length
-})
+// ========== 用户映射(巡检人员 ID → 姓名) ==========
+const userOptions = ref([])
+const userMap = ref({})
+function userNames(ids) {
+  if (!ids) return ''
+  return String(ids).split(',').map(id => userMap.value[id] || id).join('、')
+}
+async function loadUsers() {
+  try {
+    const res = await listUser({ pageNum: 1, pageSize: 200 })
+    if (res.rows) {
+      userOptions.value = res.rows
+      userMap.value = Object.fromEntries(res.rows.map(u => [u.userId, u.nickName || u.userName]))
+    }
+  } catch(e) { console.warn('加载用户列表失败', e) }
+}
 
-const issueRate = computed(() => {
-  const withIssue = inspections.value.filter(i => i.issue).length
-  return inspections.value.length === 0 ? 0 : Math.round((withIssue / inspections.value.length) * 100)
-})
+// ========== 新增巡检(提交后自动派单) ==========
+const addVisible = ref(false)
+const addFormRef = ref(null)
+const addForm = reactive({ orderDesc: '', planFinishTime: '', receiveUserId: null })
+const addRules = {
+  orderDesc: [{ required: true, message: '请填写巡检内容', trigger: 'blur' }],
+  planFinishTime: [{ required: true, message: '请选择巡检时间', trigger: 'change' }],
+  receiveUserId: [{ required: true, message: '请选择巡检人员', trigger: 'change' }]
+}
+const submitting = ref(false)
 
-const completionRate = computed(() => {
-  return inspections.value.length === 0 ? 0 : Math.round((completedCount.value / inspections.value.length) * 100)
-})
+function openAddDialog() {
+  addForm.orderDesc = ''
+  addForm.planFinishTime = ''
+  addForm.receiveUserId = null
+  addVisible.value = true
+}
+
+async function submitAdd() {
+  await addFormRef.value.validate().catch(() => { throw new Error('校验失败') })
+  submitting.value = true
+  try {
+    const res = await addWorkOrder({ orderType: 2, orderDesc: addForm.orderDesc, planFinishTime: addForm.planFinishTime })
+    const orderId = res.data
+    if (!orderId) throw new Error('未返回工单ID')
+    await dispatchWorkOrder({ orderId: Number(orderId), handlerIds: [addForm.receiveUserId], remarks: '日常巡检自动派单' })
+    ElMessage.success('巡检工单已创建并派单')
+    addVisible.value = false
+    handleSearch()
+  } catch(e) { ElMessage.error('创建失败:' + (e.msg || e.message || '请重试')); console.error(e) }
+  finally { submitting.value = false }
+}
+
+// ========== 派单(部门树 + 用户多选,参考排水/燃气工单) ==========
+const dispatchDialogVisible = ref(false)
+const dispatchForm = reactive({ handlerIds: [], remarks: '' })
+const deptTreeRef = ref(null)
+const userTableRef = ref(null)
+const deptTreeData = ref([])
+const deptFilterText = ref('')
+const userList = ref([])
+const selectedUserList = ref([])
+
+watch(deptFilterText, (val) => { deptTreeRef.value && deptTreeRef.value.filter(val) })
+function filterDeptNode(value, data) {
+  if (!value) return true
+  return data.deptName.includes(value)
+}
 
-// 获取状态文本
-const getStatusText = (item) => {
-  if (item.isCompleted) return '已完成'
-  if (item.process) return '处理中'
-  if (item.issue) return '待处理'
-  return '正常'
+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) }
 }
 
-const getStatusClass = (item) => {
-  if (item.isCompleted) return 'status-completed'
-  if (item.process) return 'status-processing'
-  if (item.issue) return 'status-pending'
-  return 'status-normal'
+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) }
 }
 
-// 过滤后的巡检列表
-const filteredInspections = computed(() => {
-  let result = [...inspections.value]
+function handleSelectionChange(selection) {
+  selectedUserList.value = selection
+  dispatchForm.handlerIds = selection.map(item => item.userId)
+}
+
+function handleRowClick(row) {
+  userTableRef.value && userTableRef.value.toggleRowSelection(row)
+}
 
-  if (queryParams.value.startDate) {
-    result = result.filter(i => i.inspectTime >= queryParams.value.startDate)
+const selectedUserNames = computed(() => selectedUserList.value.map(item => item.nickName || item.userName))
+
+function handleDispatch() {
+  if (!canOperate.dispatch(selectedOrder.value)) {
+    ElMessage.warning('仅可对待派单工单进行派单操作')
+    return
   }
-  if (queryParams.value.endDate) {
-    result = result.filter(i => i.inspectTime <= queryParams.value.endDate + 'T23:59:59')
+  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
   }
-  if (queryParams.value.status) {
-    result = result.filter(i => {
-      if (queryParams.value.status === 'pending') return !i.isCompleted && i.issue && !i.process
-      if (queryParams.value.status === 'processing') return !i.isCompleted && i.process
-      if (queryParams.value.status === 'completed') return i.isCompleted
-      return true
+  submitting.value = true
+  try {
+    await dispatchWorkOrder({
+      orderId: selectedOrder.value.orderId,
+      handlerIds: dispatchForm.handlerIds,
+      remarks: dispatchForm.remarks
     })
-  }
-  if (queryParams.value.keyword) {
-    const kw = queryParams.value.keyword.toLowerCase()
-    result = result.filter(i =>
-        i.content.toLowerCase().includes(kw) ||
-        (i.issue && i.issue.toLowerCase().includes(kw))
-    )
-  }
-  // 按巡检时间倒序
-  return result.sort((a, b) => new Date(b.inspectTime) - new Date(a.inspectTime))
-})
-
-const paginatedInspections = computed(() => {
-  const start = (currentPage.value - 1) * pageSize
-  return filteredInspections.value.slice(start, start + pageSize)
-})
+    ElMessage.success('派单成功!已向 ' + dispatchForm.handlerIds.length + ' 人下发任务')
+    dispatchDialogVisible.value = false
+    refreshAfterAction()
+  } catch(e) { ElMessage.error('派单失败:' + (e.msg || e.message || '请重试')); console.error(e) }
+  finally { submitting.value = false }
+}
 
-const totalPages = computed(() => Math.ceil(filteredInspections.value.length / pageSize))
+// ========== 核查/处理(处理过程写入 solution,后端存入 repairResult) ==========
+const actionDialogVisible = ref(false)
+const actionTitle = ref('')
+const actionFormData = reactive({ solution: '' })
 
-// ==================== 新增巡检 ====================
-const addInspection = () => {
-  if (!newInspection.value.content) {
-    alert('请填写巡检内容')
+function handleVerify() {
+  if (!canOperate.verify(selectedOrder.value)) {
+    ElMessage.warning('当前工单不允许核查')
     return
   }
-  if (!newInspection.value.inspectTime) {
-    alert('请选择巡检时间')
+  actionTitle.value = '核查工单'
+  actionFormData.solution = ''
+  actionDialogVisible.value = true
+}
+
+function handleProcess() {
+  if (!canOperate.process(selectedOrder.value)) {
+    ElMessage.warning('当前工单不允许处理')
     return
   }
+  actionTitle.value = '处理工单'
+  actionFormData.solution = ''
+  actionDialogVisible.value = true
+}
 
-  const newId = Math.max(...inspections.value.map(i => i.id), 0) + 1
-  const newRecord = {
-    id: newId,
-    content: newInspection.value.content,
-    inspectTime: newInspection.value.inspectTime,
-    inspector: newInspection.value.inspector || '',
-    issue: newInspection.value.issue || '',
-    process: newInspection.value.process || '',
-    isCompleted: newInspection.value.isCompleted,
-    completeTime: newInspection.value.isCompleted ? newInspection.value.completeTime : null,
-    remark: ''
+async function submitAction() {
+  if (!actionFormData.solution.trim()) {
+    ElMessage.warning('请填写说明')
+    return
   }
-
-  inspections.value.unshift(newRecord)
-  alert('巡检记录已添加')
-  resetForm()
-  currentPage.value = 1
+  submitting.value = true
+  try {
+    const data = {
+      orderId: selectedOrder.value.orderId,
+      handleResult: 'solved',
+      solution: actionFormData.solution
+    }
+    if (actionTitle.value === '核查工单') {
+      await verifyWorkOrder(data)
+    } else {
+      await processWorkOrder(data)
+    }
+    ElMessage.success(actionTitle.value + '成功')
+    actionDialogVisible.value = false
+    refreshAfterAction()
+  } catch(e) { ElMessage.error('操作失败:' + (e.msg || e.message || '请重试')); console.error(e) }
+  finally { submitting.value = false }
 }
 
-const resetForm = () => {
-  newInspection.value = {
-    content: '',
-    inspectTime: new Date().toISOString().slice(0, 16),
-    inspector: '',
-    issue: '',
-    process: '',
-    isCompleted: false,
-    completeTime: ''
+// ========== 提交验收(后端 completeOrder 要求状态 4) ==========
+const completeDialogVisible = ref(false)
+const completeForm = reactive({ result: '' })
+
+function handleComplete() {
+  if (!canOperate.complete(selectedOrder.value)) {
+    ElMessage.warning('当前工单不允许提交验收')
+    return
   }
+  completeForm.result = ''
+  completeDialogVisible.value = true
 }
 
-// ==================== 查询 ====================
-const searchInspections = () => {
-  currentPage.value = 1
+async function submitComplete() {
+  if (!completeForm.result.trim()) {
+    ElMessage.warning('请填写验收意见')
+    return
+  }
+  submitting.value = true
+  try {
+    await completeWorkOrder(selectedOrder.value.orderId, completeForm.result)
+    ElMessage.success('提交验收成功')
+    completeDialogVisible.value = false
+    refreshAfterAction()
+  } catch(e) { ElMessage.error('提交失败:' + (e.msg || e.message || '请重试')); console.error(e) }
+  finally { submitting.value = false }
 }
 
-const resetQuery = () => {
-  queryParams.value = {
-    startDate: '',
-    endDate: '',
-    status: '',
-    keyword: ''
+// ========== 验收通过/结案 ==========
+const closeDialogVisible = ref(false)
+const closeForm = reactive({ result: '' })
+
+function handleClose() {
+  if (!canOperate.close(selectedOrder.value)) {
+    ElMessage.warning('当前工单不允许验收通过')
+    return
   }
-  currentPage.value = 1
+  closeForm.result = ''
+  closeDialogVisible.value = true
 }
 
-// ==================== 查看详情 ====================
-const viewDetail = (item) => {
-  currentItem.value = item
-  detailModalVisible.value = true
+async function submitClose() {
+  if (!closeForm.result.trim()) {
+    ElMessage.warning('请填写验收结果')
+    return
+  }
+  submitting.value = true
+  try {
+    await closeWorkOrder({
+      orderId: selectedOrder.value.orderId,
+      result: closeForm.result
+    })
+    ElMessage.success('验收通过,巡检工单已结案')
+    closeDialogVisible.value = false
+    refreshAfterAction()
+  } catch(e) { ElMessage.error('验收失败:' + (e.msg || e.message || '请重试')); console.error(e) }
+  finally { submitting.value = false }
 }
 
-const closeDetailModal = () => {
-  detailModalVisible.value = false
-  currentItem.value = null
+// ========== 取消工单 ==========
+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(() => {})
 }
 
-// ==================== 处理巡检 ====================
-const openProcessModal = (item) => {
-  currentItem.value = item
-  processData.value = {
-    process: item.process || '',
-    isCompleted: true,
-    completeTime: new Date().toISOString().slice(0, 16)
+/** 操作完成后刷新列表并重新加载当前选中工单 */
+async function refreshAfterAction() {
+  await handleSearch()
+  const fresh = tableData.value.find(o => o.orderId === selectedOrderId.value)
+  if (fresh) {
+    await handleSelectOrder(fresh)
+  } else {
+    selectedOrderId.value = null
+    selectedOrder.value = {}
   }
-  processModalVisible.value = true
 }
 
-const closeProcessModal = () => {
-  processModalVisible.value = false
-  currentItem.value = null
-}
+onMounted(() => {
+  loadUsers()
+  handleSearch()
+})
+</script>
 
-const submitProcess = () => {
-  if (!processData.value.process && processData.value.isCompleted) {
-    alert('请填写处理过程')
-    return
+<style scoped lang="scss">
+.order-container {
+  padding: 0 16px 16px;
+
+  .search-card {
+    margin-bottom: 16px;
+
+    :deep(.el-card__body) {
+      padding: 16px;
+    }
   }
 
-  if (currentItem.value) {
-    currentItem.value.process = processData.value.process
-    currentItem.value.isCompleted = processData.value.isCompleted
-    if (processData.value.isCompleted) {
-      currentItem.value.completeTime = processData.value.completeTime || new Date().toISOString()
+  .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;
+        }
+      }
     }
-    alert('处理记录已更新')
-    closeProcessModal()
   }
-}
 
-// ==================== 编辑巡检 ====================
-const editInspection = (item) => {
-  editForm.value = {
-    id: item.id,
-    content: item.content,
-    inspectTime: item.inspectTime,
-    inspector: item.inspector || '',
-    issue: item.issue || '',
-    process: item.process || '',
-    isCompleted: item.isCompleted,
-    completeTime: item.completeTime || ''
+  .main-row {
+    height: calc(100vh - 200px);
+
+    .col-fill {
+      height: 100%;
+    }
   }
-  editModalVisible.value = true
-}
 
-const closeEditModal = () => {
-  editModalVisible.value = false
-}
+  .box-card {
+    height: 100%;
+    display: flex;
+    flex-direction: column;
 
-const saveEdit = () => {
-  if (!editForm.value.content || !editForm.value.inspectTime) {
-    alert('请填写巡检内容和巡检时间')
-    return
+    :deep(.el-card__body) {
+      flex: 1;
+      display: flex;
+      flex-direction: column;
+      overflow: hidden;
+    }
   }
 
-  const index = inspections.value.findIndex(i => i.id === editForm.value.id)
-  if (index !== -1) {
-    inspections.value[index] = {
-      ...editForm.value,
-      completeTime: editForm.value.isCompleted ? editForm.value.completeTime : null
+  .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;
+      }
     }
-    alert('巡检记录已更新')
-    closeEditModal()
   }
-}
 
-// ==================== 删除巡检 ====================
-const deleteInspection = (item) => {
-  if (confirm(`确定删除巡检记录吗?`)) {
-    const index = inspections.value.findIndex(i => i.id === item.id)
-    if (index !== -1) {
-      inspections.value.splice(index, 1)
-      alert('巡检记录已删除')
+  .pagination-wrapper {
+    padding: 16px 0;
+    border-top: 1px solid #e4e7ed;
+    margin-top: 8px;
+
+    .pagination {
+      display: flex;
+      justify-content: flex-end;
     }
   }
-}
-</script>
 
-<style scoped>
-* { margin: 0; padding: 0; box-sizing: border-box; }
-
-.prototype-container {
-  max-width: 1600px;
-  margin: 0 auto;
-  background: #f0f4f8;
-  border-radius: 28px;
-  box-shadow: 0 20px 35px -12px rgba(0, 0, 0, 0.15);
-  overflow: hidden;
-  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
-}
+  .detail-body {
+    flex: 1;
+    overflow-y: auto;
+  }
 
-/* 头部 */
-.header {
-  background: #0a2b3c;
-  padding: 16px 28px;
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  flex-wrap: wrap;
-  gap: 16px;
-  border-bottom: 3px solid #3498db;
-}
+  .detail-tabs {
+    margin-top: 0;
+  }
 
-.logo h2 { color: white; font-weight: 600; font-size: 1.3rem; }
-.logo p { color: #bdd8e8; font-size: 0.7rem; }
-
-.header-stats { display: flex; gap: 12px; }
-.stat-card { background: rgba(255,255,255,0.15); padding: 6px 14px; border-radius: 40px; text-align: center; color: white; }
-.stat-card.pending { background: #f39c12; }
-.stat-card.processing { background: #3498db; }
-.stat-card.completed { background: #27ae60; }
-.stat-value { font-weight: 700; font-size: 1.2rem; margin-right: 6px; }
-.stat-label { font-size: 0.65rem; opacity: 0.9; }
-
-/* 布局 */
-.main-layout { display: flex; flex-wrap: wrap; }
-.left-panel { width: 400px; background: #f9fbfd; padding: 20px; }
-.right-panel { flex: 1; background: #ffffff; padding: 20px; min-width: 600px; }
-
-/* 卡片 */
-.card { background: white; border-radius: 20px; border: 1px solid #e6edf3; margin-bottom: 20px; overflow: hidden; }
-.card-header { background: #f9fafc; padding: 14px 18px; border-bottom: 1px solid #eef2f6; font-weight: 700; font-size: 0.85rem; color: #1d5a78; display: flex; justify-content: space-between; align-items: center; }
-.card-body { padding: 16px; }
-.card-tip { font-size: 0.65rem; font-weight: normal; color: #6f8ea8; }
-
-/* 表单 */
-.form-group { margin-bottom: 14px; }
-.form-row { display: flex; gap: 12px; margin-bottom: 14px; }
-.form-group.half { flex: 1; }
-.form-label { font-size: 0.7rem; font-weight: 600; margin-bottom: 4px; display: block; color: #2c5a72; }
-.form-input, .form-select, .form-textarea { width: 100%; padding: 8px 12px; border-radius: 12px; border: 1px solid #cbdde6; font-size: 0.75rem; }
-.form-textarea { resize: vertical; }
-.form-actions { display: flex; gap: 12px; justify-content: flex-end; margin-top: 8px; }
-.btn-primary { background: #3498db; border: none; color: white; padding: 8px 20px; border-radius: 30px; font-size: 0.75rem; cursor: pointer; }
-.btn-primary:hover { background: #2980b9; }
-.btn-outline { background: white; border: 1px solid #3498db; color: #3498db; padding: 6px 16px; border-radius: 30px; font-size: 0.7rem; cursor: pointer; }
-.btn-sm { padding: 4px 12px; font-size: 0.65rem; }
-
-/* 筛选栏 */
-.filter-bar { background: #f8fafc; border-radius: 16px; padding: 12px; margin-bottom: 16px; display: flex; flex-wrap: wrap; gap: 16px; align-items: flex-end; }
-.filter-group { display: flex; flex-direction: column; gap: 4px; }
-.filter-label { font-size: 0.65rem; font-weight: 600; color: #2c5a72; }
-.date-range { display: flex; align-items: center; gap: 6px; }
-.filter-input { padding: 6px 10px; border-radius: 20px; border: 1px solid #cbdde6; font-size: 0.7rem; }
-.filter-select { padding: 6px 10px; border-radius: 20px; border: 1px solid #cbdde6; font-size: 0.7rem; min-width: 100px; }
-.filter-sep { font-size: 0.7rem; color: #6f8ea8; }
-.filter-actions { display: flex; gap: 8px; }
-
-/* 表格 */
-.inspection-table-wrapper { overflow-x: auto; }
-.data-table { width: 100%; border-collapse: collapse; font-size: 0.7rem; }
-.data-table th { background: #f8fafc; padding: 10px 8px; text-align: left; font-weight: 600; color: #2c5a72; border-bottom: 1px solid #e2edf2; }
-.data-table td { padding: 10px 8px; border-bottom: 1px solid #f0f4f7; vertical-align: middle; }
-.time-cell { white-space: nowrap; font-family: monospace; }
-.content-cell { max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.issue-cell { max-width: 180px; }
-.issue-badge { display: inline-block; background: #fef3c7; color: #f39c12; padding: 2px 6px; border-radius: 12px; font-size: 0.6rem; margin-right: 6px; }
-.no-issue { display: inline-block; background: #d5f5e3; color: #27ae60; padding: 2px 6px; border-radius: 12px; font-size: 0.6rem; margin-right: 6px; }
-.issue-text { color: #5a6e7e; }
-.process-cell { max-width: 150px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.action-cell { white-space: nowrap; display: flex; gap: 6px; }
-.empty-row { text-align: center; padding: 30px; color: #8aaec2; }
-.list-count { font-size: 0.65rem; font-weight: normal; color: #6f8ea8; }
-
-/* 状态标签 */
-.status-badge { padding: 2px 10px; border-radius: 20px; font-size: 0.65rem; }
-.status-pending { background: #fef3c7; color: #f39c12; }
-.status-processing { background: #e3f2fd; color: #3498db; }
-.status-completed { background: #d5f5e3; color: #27ae60; }
-.status-normal { background: #e8f4fd; color: #3498db; }
-
-/* 操作按钮 */
-.btn-view { background: #95a5a6; color: white; border: none; padding: 4px 10px; border-radius: 20px; font-size: 0.65rem; cursor: pointer; }
-.btn-process { background: #3498db; color: white; border: none; padding: 4px 10px; border-radius: 20px; font-size: 0.65rem; cursor: pointer; }
-.btn-edit { background: #f39c12; color: white; border: none; padding: 4px 10px; border-radius: 20px; font-size: 0.65rem; cursor: pointer; }
-.btn-delete { background: #e74c3c; color: white; border: none; padding: 4px 10px; border-radius: 20px; font-size: 0.65rem; cursor: pointer; }
-
-/* 分页 */
-.pagination { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }
-.page-btn { background: white; border: 1px solid #cbdde6; padding: 4px 10px; border-radius: 20px; font-size: 0.7rem; cursor: pointer; }
-.page-btn.active { background: #3498db; color: white; border-color: #3498db; }
-
-/* 统计区域 */
-.stats-grid { display: flex; gap: 20px; align-items: center; }
-.stat-circle { text-align: center; }
-.circle-num { font-size: 2rem; font-weight: 700; color: #3498db; }
-.circle-label { font-size: 0.65rem; color: #6f8ea8; }
-.stat-list { flex: 1; }
-.stat-row { display: flex; justify-content: space-between; padding: 6px 0; font-size: 0.7rem; border-bottom: 1px solid #eef2f6; }
-.stat-row span:first-child { color: #6f8ea8; }
-.stat-row span:last-child { font-weight: 600; color: #2c5a72; }
-
-/* 模态框 */
-.modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 1000; }
-.modal-content { background: white; border-radius: 24px; width: 500px; max-width: 90%; padding: 24px; max-height: 85vh; overflow-y: auto; }
-.detail-info { background: #f8fafc; border-radius: 16px; padding: 16px; margin-top: 8px; }
-.info-row { margin-bottom: 10px; font-size: 0.75rem; }
-.info-row .label { font-weight: 600; width: 90px; display: inline-block; color: #2c5a72; }
-.process-info-summary { background: #f8fafc; border-radius: 12px; padding: 12px; margin-bottom: 16px; font-size: 0.7rem; }
-.process-info-summary .label { font-weight: 600; }
-</style>
+  .timeline-scroll {
+    max-height: calc(100vh - 340px);
+    overflow-y: auto;
+    padding-right: 8px;
+  }
+
+  .device-section, .image-section, .action-section {
+    margin-top: 24px;
+    padding-top: 24px;
+    border-top: 1px solid #e4e7ed;
+
+    .section-title {
+      font-size: 15px;
+      font-weight: bold;
+      margin-bottom: 16px;
+      color: #303133;
+    }
+  }
+
+  .image-list {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 12px;
+  }
+
+  .action-buttons {
+    display: flex;
+    gap: 12px;
+    flex-wrap: wrap;
+
+    .el-button {
+      min-width: 100px;
+    }
+  }
+
+  .timeline-content {
+    .timestamp {
+      font-size: 12px;
+      color: #909399;
+      margin-bottom: 6px;
+    }
+
+    .operator-info {
+      font-size: 14px;
+      margin-bottom: 8px;
+
+      .operator-name {
+        font-weight: bold;
+        color: #303133;
+      }
+
+      .oper-type {
+        color: #909399;
+      }
+    }
+
+    .oper-desc {
+      font-size: 14px;
+      color: #606266;
+    }
+  }
+}
+</style>

+ 878 - 576
src/views/subSystem/pipeNetwork/pworkOrder/PworkList.vue

@@ -1,651 +1,953 @@
-<!--工单列表-->
+<!--设备故障工单(故障维修 orderType=1,参考排水/燃气工单形态)-->
+<!--数据源:/api/work-order/*(orderType=1 过滤;equipmentType 为空 = 所有设备)-->
+<!--需求 5.2.4.4.1:手动新增字段=设备类型/设备ID/设备位置/设备厂家/维护人员/联系方式/故障时间/故障描述-->
 <template>
-  <div class="prototype-container">
-    <!-- 头部 -->
-    <div class="header">
-      <div class="logo">
-        <h2>🔧 设备故障工单管理系统</h2>
-        <p>故障自动预警 | 智能派单 | 工单闭环管理</p>
+  <div class="order-container">
+    <!-- 顶部搜索区(参考排水/燃气工单) -->
+    <el-card class="search-card" shadow="never">
+      <div class="search-form">
+        <el-form :model="queryParams" ref="queryFormRef" :inline>
+          <el-form-item label="优先级">
+            <el-select v-model="queryParams.orderLevel" placeholder="全部优先级" clearable style="width: 120px">
+              <el-option v-for="(v, k) in ORDER_LEVEL_MAP" :key="k" :label="v.text" :value="Number(k)"/>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="状态">
+            <el-select v-model="queryParams.orderStatus" placeholder="全部状态" clearable style="width: 120px">
+              <el-option v-for="(v, k) in ORDER_STATUS_MAP" :key="k" :label="v.text" :value="Number(k)"/>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="设备编码">
+            <el-input v-model="queryParams.deviceCode" placeholder="设备编码" clearable style="width: 150px" @keyup.enter="handleSearch"/>
+          </el-form-item>
+          <el-form-item label="关键词">
+            <el-input v-model="queryParams.keyword" placeholder="工单编号/设备编码/描述" clearable style="width: 200px" @keyup.enter="handleSearch"/>
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" @click="handleSearch">查询</el-button>
+            <el-button @click="handleReset">重置</el-button>
+            <el-button type="primary" plain @click="openAddDialog">➕ 新增故障工单</el-button>
+          </el-form-item>
+        </el-form>
       </div>
-      <div class="header-stats">
-        <div class="stat-card pending">
-          <span class="stat-value">{{ pendingCount }}</span>
-          <span class="stat-label">待处理</span>
-        </div>
-        <div class="stat-card processing">
-          <span class="stat-value">{{ processingCount }}</span>
-          <span class="stat-label">处理中</span>
-        </div>
-        <div class="stat-card completed">
-          <span class="stat-value">{{ completedCount }}</span>
-          <span class="stat-label">已完成</span>
-        </div>
-      </div>
-    </div>
-
-    <div class="main-layout">
-      <!-- 左侧:实时故障监控与自动生成 -->
-      <div class="left-panel">
-        <div class="card">
-          <div class="card-header">
-            🚨 实时故障监控
-            <span class="live-badge">● 自动监测中</span>
-          </div>
-          <div class="card-body">
-            <div class="fault-monitor">
-              <div class="monitor-list">
-                <div v-for="fault in recentFaults" :key="fault.id" class="fault-item" :class="{ new: fault.isNew }">
-                  <div class="fault-time">{{ fault.time }}</div>
-                  <div class="fault-device">{{ fault.deviceName }}</div>
-                  <div class="fault-message">{{ fault.message }}</div>
-                  <div class="fault-order-status">
-                    <span v-if="fault.orderCreated" class="order-created">✅ 工单已生成</span>
-                    <span v-else class="order-creating">⏳ 正在生成工单...</span>
-                  </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 v-if="recentFaults.length === 0" class="empty-fault">
-                  暂无实时故障,系统运行正常
+                <div class="order-body">
+                  <div class="order-tags">
+                    <el-tag :type="orderLevelType(order.orderLevel)" size="small" effect="plain">{{ orderLevelText(order.orderLevel) }}</el-tag>
+                    <el-tag v-if="order.hazardNo" size="small" effect="plain">隐患 {{ order.hazardNo }}</el-tag>
+                  </div>
+                  <div class="order-desc" v-if="order.orderDesc">{{ order.orderDesc }}</div>
+                  <div class="device-code">设备编号:{{ order.deviceCode || '-' }}</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>
-        </div>
-
-        <!-- 手动新增工单 -->
-        <div class="card">
-          <div class="card-header">
-            ➕ 手动新增故障工单
-            <button @click="openAddModal" class="btn-primary btn-sm">+ 新增工单</button>
-          </div>
-          <div class="card-body" v-if="showAddForm">
-            <div class="add-form">
-              <div class="form-row">
-                <div class="form-group half">
-                  <label class="form-label">设备类型 *</label>
-                  <select v-model="newOrder.deviceType" class="form-select">
-                    <option value="">请选择</option>
-                    <option value="压力传感器">压力传感器</option>
-                    <option value="流量传感器">流量传感器</option>
-                    <option value="温度传感器">温度传感器</option>
-                    <option value="泄漏检测仪">泄漏检测仪</option>
-                    <option value="水位监测仪">水位监测仪</option>
-                  </select>
-                </div>
-                <div class="form-group half">
-                  <label class="form-label">设备ID *</label>
-                  <input v-model="newOrder.deviceId" class="form-input" placeholder="如: PS-001">
-                </div>
-              </div>
-              <div class="form-row">
-                <div class="form-group half">
-                  <label class="form-label">设备位置 *</label>
-                  <input v-model="newOrder.location" class="form-input" placeholder="设备安装位置">
+        </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.createTime) }}</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>
-                <div class="form-group half">
-                  <label class="form-label">设备厂家</label>
-                  <input v-model="newOrder.manufacturer" class="form-input" placeholder="设备生产厂家">
+
+                <!-- 工单图片 -->
+                <div v-if="workImageList.length > 0" class="image-section">
+                  <h4 class="section-title">工单图片</h4>
+                  <div class="image-list">
+                    <image-preview v-for="(img, index) in workImageList" :key="index" :src="img" :width="100" :height="100"/>
+                  </div>
                 </div>
-              </div>
-              <div class="form-row">
-                <div class="form-group half">
-                  <label class="form-label">维护人员 *</label>
-                  <select v-model="newOrder.maintainer" class="form-select">
-                    <option value="">请选择</option>
-                    <option value="张工">张工(设备组)</option>
-                    <option value="李工">李工(运维组)</option>
-                    <option value="王工">王工(抢修组)</option>
-                    <option value="赵工">赵工(技术组)</option>
-                  </select>
+
+                <!-- 工单操作(参考排水/燃气工单按钮组) -->
+                <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>
-                <div class="form-group half">
-                  <label class="form-label">联系方式</label>
-                  <input v-model="newOrder.contact" class="form-input" placeholder="维护人员电话">
+              </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>
-              </div>
-              <div class="form-group">
-                <label class="form-label">故障时间 *</label>
-                <input type="datetime-local" v-model="newOrder.faultTime" class="form-input">
-              </div>
-              <div class="form-group">
-                <label class="form-label">故障描述 *</label>
-                <textarea v-model="newOrder.description" class="form-textarea" rows="2" placeholder="请描述故障现象..."></textarea>
-              </div>
-              <div class="form-actions">
-                <button @click="cancelAdd" class="btn-outline">取消</button>
-                <button @click="submitManualOrder" class="btn-primary">提交工单</button>
-              </div>
-            </div>
-          </div>
-          <div class="card-body" v-else>
-            <div class="empty-add" @click="openAddModal">
-              <span class="add-icon">+</span>
-              <span>点击新增故障工单</span>
-            </div>
-          </div>
-        </div>
-      </div>
-
-      <!-- 右侧:工单列表 -->
-      <div class="right-panel">
-        <div class="card">
-          <div class="card-header">
-            📋 设备故障工单列表
-            <span class="list-count">共 {{ filteredOrders.length }} 条记录</span>
+              </el-tab-pane>
+            </el-tabs>
           </div>
-          <div class="card-body">
-            <!-- 查询筛选栏 -->
-            <div class="filter-bar">
-              <select v-model="queryParams.deviceType" class="filter-select">
-                <option value="">设备类型</option>
-                <option value="压力传感器">压力传感器</option>
-                <option value="流量传感器">流量传感器</option>
-                <option value="温度传感器">温度传感器</option>
-                <option value="泄漏检测仪">泄漏检测仪</option>
-              </select>
-              <input type="text" v-model="queryParams.deviceId" class="filter-input" placeholder="设备ID">
-              <input type="date" v-model="queryParams.startDate" class="filter-input" placeholder="开始日期">
-              <span class="filter-sep">至</span>
-              <input type="date" v-model="queryParams.endDate" class="filter-input" placeholder="结束日期">
-              <button @click="searchOrders" class="btn-primary btn-sm">查询</button>
-              <button @click="resetQuery" class="btn-outline btn-sm">重置</button>
-            </div>
 
-            <!-- 工单表格 -->
-            <div class="order-table-wrapper">
-              <table class="data-table">
-                <thead>
-                <tr>
-                  <th>工单编号</th>
-                  <th>设备类型</th>
-                  <th>设备ID</th>
-                  <th>设备位置</th>
-                  <th>故障描述</th>
-                  <th>故障时间</th>
-                  <th>维护人员</th>
-                  <th>状态</th>
-                  <th>操作</th>
-                </tr>
-                </thead>
-                <tbody>
-                <tr v-for="order in paginatedOrders" :key="order.id">
-                  <td class="order-no">{{ order.orderNo }}</td>
-                  <td>{{ order.deviceType }}</td>
-                  <td>{{ order.deviceId }}</td>
-                  <td>{{ order.location }}</td>
-                  <td class="desc-cell">{{ order.description }}</td>
-                  <td class="time-cell">{{ order.faultTime }}</td>
-                  <td>{{ order.maintainer }}</td>
-                  <td>
-                      <span class="status-badge" :class="getStatusClass(order.status)">
-                        {{ getStatusText(order.status) }}
-                      </span>
-                  </td>
-                  <td class="action-cell">
-                    <button @click="viewOrderDetail(order)" class="btn-view">查看</button>
-                    <button v-if="order.status === 'pending'" @click="updateStatus(order, 'processing')" class="btn-start">开始处理</button>
-                    <button v-if="order.status === 'processing'" @click="completeOrder(order)" class="btn-complete">完成</button>
-                  </td>
-                </tr>
-                <tr v-if="filteredOrders.length === 0">
-                  <td colspan="9" class="empty-row">暂无工单数据</td>
-                </tr>
-                </tbody>
-              </table>
-            </div>
-
-            <!-- 分页 -->
-            <div class="pagination" v-if="totalPages > 1">
-              <button v-for="page in totalPages" :key="page" class="page-btn" :class="{ active: currentPage === page }"
-                      @click="currentPage = page">{{ page }}</button>
-            </div>
+          <el-empty v-else description="请从左侧选择工单" :image-size="150"/>
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <!-- 新增工单弹窗(需求 5.2.4.4.1:设备类型/设备ID/设备位置/设备厂家/维护人员/联系方式/故障时间/故障描述) -->
+    <el-dialog v-model="addVisible" title="➕ 新增故障工单" width="680px" append-to-body destroy-on-close>
+      <el-form ref="addFormRef" :model="addForm" :rules="addRules" label-width="100px">
+        <el-form-item label="关联设备" prop="deviceId">
+          <el-select v-model="addForm.deviceId" filterable remote clearable placeholder="输入设备编码/名称搜索" :remote-method="searchDevice" :loading="deviceLoading" style="width:100%" @change="onDeviceChange">
+            <el-option v-for="dev in deviceOptions" :key="dev.equipmentId" :label="`${dev.equipmentName || dev.equipmentCode} (${dev.equipmentCode})`" :value="dev.equipmentId"/>
+          </el-select>
+        </el-form-item>
+
+        <!-- 设备信息卡:选择设备后自动带出 设备类型/设备ID/设备位置/设备厂家/维护人员/联系方式 -->
+        <el-form-item v-if="deviceInfo" label="设备信息">
+          <div class="device-info-card">
+            <el-descriptions :column="2" border size="small">
+              <el-descriptions-item label="设备类型">{{ deviceInfo.equipmentTypeName || '-' }}</el-descriptions-item>
+              <el-descriptions-item label="设备ID">{{ deviceInfo.equipmentCode || '-' }}</el-descriptions-item>
+              <el-descriptions-item label="设备位置">{{ deviceInfo.equipmentLocation || deviceInfo.pointAddress || '-' }}</el-descriptions-item>
+              <el-descriptions-item label="设备厂家">{{ deviceInfo.manufacturer || '-' }}</el-descriptions-item>
+              <el-descriptions-item label="维护人员">{{ deviceInfo.maintainer || '-' }}</el-descriptions-item>
+              <el-descriptions-item label="联系方式">{{ deviceInfo.maintainerPhone || '-' }}</el-descriptions-item>
+            </el-descriptions>
           </div>
+        </el-form-item>
+
+        <el-form-item label="设备编码">
+          <el-input v-model="addForm.deviceCode" placeholder="未选设备时手动填写" clearable/>
+        </el-form-item>
+        <el-form-item label="优先级" prop="orderLevel">
+          <el-radio-group v-model="addForm.orderLevel">
+            <el-radio-button :value="1">紧急</el-radio-button>
+            <el-radio-button :value="2">一般</el-radio-button>
+            <el-radio-button :value="3">低</el-radio-button>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="故障时间">
+          <el-date-picker v-model="addForm.faultTime" type="datetime" placeholder="故障发生时间(默认当前)" value-format="YYYY-MM-DD HH:mm:ss" style="width:100%"/>
+        </el-form-item>
+        <el-form-item label="计划完成时间">
+          <el-date-picker v-model="addForm.planFinishTime" type="datetime" placeholder="请选择计划完成时间" value-format="YYYY-MM-DD HH:mm:ss" style="width:100%"/>
+        </el-form-item>
+        <el-form-item label="故障描述" prop="orderDesc">
+          <el-input v-model="addForm.orderDesc" type="textarea" :rows="4" placeholder="请描述故障现象 / 设备离线信息等" maxlength="500" show-word-limit/>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="addVisible=false">取消</el-button>
+        <el-button type="primary" :loading="submitting" @click="submitAdd">提交工单</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 派单弹窗(部门树 + 用户多选,参考排水/燃气工单) -->
+    <el-dialog v-model="dispatchDialogVisible" title="下派任务 - 选择接单人" width="750px" append-to-body>
+      <div style="display: flex; gap: 16px; min-height: 350px;">
+        <div style="width: 280px; border-right: 1px solid #e4e7ed; padding-right: 12px;">
+          <el-input v-model="deptFilterText" placeholder="筛选部门" clearable size="small" style="margin-bottom: 8px"/>
+          <el-tree
+            ref="deptTreeRef"
+            :data="deptTreeData"
+            :props="{ label: 'deptName', children: 'children' }"
+            :filter-node-method="filterDeptNode"
+            node-key="deptId"
+            highlight-current
+            @node-click="handleDeptClick"
+            style="font-size: 14px"
+          />
         </div>
-      </div>
-    </div>
-
-    <!-- 工单详情模态框 -->
-    <div v-if="detailModalVisible" class="modal-overlay" @click.self="closeDetailModal">
-      <div class="modal-content">
-        <h3>📋 工单详细信息</h3>
-        <div class="detail-info">
-          <div class="info-row"><span class="label">工单编号:</span>{{ currentOrder?.orderNo }}</div>
-          <div class="info-row"><span class="label">设备类型:</span>{{ currentOrder?.deviceType }}</div>
-          <div class="info-row"><span class="label">设备ID:</span>{{ currentOrder?.deviceId }}</div>
-          <div class="info-row"><span class="label">设备位置:</span>{{ currentOrder?.location }}</div>
-          <div class="info-row"><span class="label">设备厂家:</span>{{ currentOrder?.manufacturer || '-' }}</div>
-          <div class="info-row"><span class="label">维护人员:</span>{{ currentOrder?.maintainer }}</div>
-          <div class="info-row"><span class="label">联系方式:</span>{{ currentOrder?.contact || '-' }}</div>
-          <div class="info-row"><span class="label">故障时间:</span>{{ currentOrder?.faultTime }}</div>
-          <div class="info-row"><span class="label">故障描述:</span>{{ currentOrder?.description }}</div>
-          <div class="info-row"><span class="label">创建时间:</span>{{ currentOrder?.createTime }}</div>
-          <div class="info-row" v-if="currentOrder?.startTime"><span class="label">开始处理:</span>{{ currentOrder?.startTime }}</div>
-          <div class="info-row" v-if="currentOrder?.completeTime"><span class="label">完成时间:</span>{{ currentOrder?.completeTime }}</div>
-          <div class="info-row" v-if="currentOrder?.processResult"><span class="label">处理结果:</span>{{ currentOrder?.processResult }}</div>
-        </div>
-        <div class="form-actions">
-          <button @click="closeDetailModal" class="btn-primary">关闭</button>
-        </div>
-      </div>
-    </div>
-
-    <!-- 完成工单模态框 -->
-    <div v-if="completeModalVisible" class="modal-overlay" @click.self="closeCompleteModal">
-      <div class="modal-content" style="width:450px;">
-        <h3>✅ 工单完成反馈</h3>
-        <div class="order-info-summary">
-          <div><span class="label">工单号:</span>{{ currentOrder?.orderNo }}</div>
-          <div><span class="label">设备:</span>{{ currentOrder?.deviceId }} - {{ currentOrder?.deviceType }}</div>
-        </div>
-        <div class="form-group">
-          <label class="form-label">处理结果</label>
-          <textarea v-model="processResult" class="form-textarea" rows="3" placeholder="请描述处理过程及结果..."></textarea>
-        </div>
-        <div class="form-actions">
-          <button @click="closeCompleteModal" class="btn-outline">取消</button>
-          <button @click="submitComplete" class="btn-primary">提交完成</button>
+        <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>
-    </div>
+      <el-form label-width="80px" style="margin-top: 16px">
+        <el-form-item label="备注"><el-input v-model="dispatchForm.remarks" placeholder="请输入派单备注"/></el-form-item>
+      </el-form>
+      <template #footer>
+        <span style="float: left; line-height: 32px; color: #606266;">
+          已选人员:
+          <span v-if="selectedUserNames.length > 0" style="color: #409eff; font-weight: bold;">{{ selectedUserNames.join('、') }}</span>
+          <span v-else style="color: #909399">未选择</span>
+          ({{ dispatchForm.handlerIds.length }})
+        </span>
+        <el-button @click="dispatchDialogVisible = false">取消</el-button>
+        <el-button type="primary" :loading="submitting" @click="submitDispatch">确认</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 核查/处理弹窗 -->
+    <el-dialog v-model="actionDialogVisible" :title="actionTitle" width="600px" append-to-body>
+      <el-form label-width="90px">
+        <el-form-item label="结果说明" required>
+          <el-input v-model="actionFormData.solution" type="textarea" :rows="5" :placeholder="actionTitle === '核查工单' ? '请输入核查结论(属实/不属实、现场情况)' : '请输入故障原因、处理结果及处理措施'" maxlength="500" show-word-limit/>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="actionDialogVisible = false">取消</el-button>
+        <el-button type="primary" :loading="submitting" @click="submitAction">确定</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 提交验收弹窗 -->
+    <el-dialog v-model="completeDialogVisible" title="提交验收" width="500px" append-to-body>
+      <el-form label-width="80px">
+        <el-form-item label="验收意见">
+          <el-input v-model="completeForm.result" type="textarea" :rows="4" placeholder="请填写维修处置结果说明" maxlength="500" show-word-limit/>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="completeDialogVisible = false">取消</el-button>
+        <el-button type="primary" :loading="submitting" @click="submitComplete">提交</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 验收通过/结案弹窗 -->
+    <el-dialog v-model="closeDialogVisible" title="验收通过" width="500px" append-to-body>
+      <el-form label-width="80px">
+        <el-form-item label="验收结果">
+          <el-input v-model="closeForm.result" type="textarea" :rows="4" placeholder="请填写验收结果(结案后关联隐患同步为已整改、设备报警状态重置)" maxlength="500" show-word-limit/>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="closeDialogVisible = false">取消</el-button>
+        <el-button type="success" :loading="submitting" @click="submitClose">确认验收</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
-import { ref, computed, onMounted, onUnmounted } from 'vue'
-
-// ==================== 工单数据 ====================
-const orders = ref([
-  { id: 1, orderNo: 'WO-20250508-001', deviceType: '压力传感器', deviceId: 'PS-001', location: '城东泵站', manufacturer: '华仪科技', maintainer: '张工', contact: '13800001111', faultTime: '2025-05-08 09:23:15', description: '设备离线,数据上报异常,超过30分钟未回传', createTime: '2025-05-08 09:23:18', status: 'pending', startTime: null, completeTime: null, processResult: null },
-  { id: 2, orderNo: 'WO-20250508-002', deviceType: '泄漏检测仪', deviceId: 'LD-005', location: '化工园区', manufacturer: '安防科技', maintainer: '李工', contact: '13800002222', faultTime: '2025-05-08 07:30:22', description: '可燃气浓度超阈值报警,需现场核查', createTime: '2025-05-08 07:30:25', status: 'processing', startTime: '2025-05-08 08:15:00', completeTime: null, processResult: null },
-  { id: 3, orderNo: 'WO-20250507-003', deviceType: '水位监测仪', deviceId: 'WL-006', location: '东区排水口', manufacturer: '水文科技', maintainer: '王工', contact: '13800003333', faultTime: '2025-05-07 14:20:33', description: '水位数据异常波动,需校准', createTime: '2025-05-07 14:20:36', status: 'completed', startTime: '2025-05-07 14:35:00', completeTime: '2025-05-07 15:20:00', processResult: '已校准传感器,数据恢复正常' },
-  { id: 4, orderNo: 'WO-20250508-004', deviceType: '流量传感器', deviceId: 'FL-002', location: '城南枢纽', manufacturer: '水测智能', maintainer: '张工', contact: '13800001111', faultTime: '2025-05-08 06:15:00', description: '流量数据中断,设备可能离线', createTime: '2025-05-08 06:15:03', status: 'pending', startTime: null, completeTime: null, processResult: null }
-])
-
-// 实时故障监控模拟数据
-const recentFaults = ref([
-  { id: 101, time: '09:23:15', deviceName: '压力传感器 PS-001', message: '设备离线,数据上报异常', orderCreated: true, isNew: false },
-  { id: 102, time: '07:30:22', deviceName: '泄漏检测仪 LD-005', message: '可燃气浓度超阈值报警', orderCreated: true, isNew: false },
-  { id: 103, time: '06:15:00', deviceName: '流量传感器 FL-002', message: '流量数据中断', orderCreated: true, isNew: false }
-])
-
-// ==================== 查询参数 ====================
-const queryParams = ref({
-  deviceType: '',
-  deviceId: '',
-  startDate: '',
-  endDate: ''
-})
-const currentPage = ref(1)
-const pageSize = 8
-
-// 过滤后的工单(时间倒序)
-const filteredOrders = computed(() => {
-  let result = [...orders.value]
-  if (queryParams.value.deviceType) {
-    result = result.filter(o => o.deviceType === queryParams.value.deviceType)
-  }
-  if (queryParams.value.deviceId) {
-    result = result.filter(o => o.deviceId.toLowerCase().includes(queryParams.value.deviceId.toLowerCase()))
-  }
-  if (queryParams.value.startDate) {
-    result = result.filter(o => o.faultTime >= queryParams.value.startDate)
-  }
-  if (queryParams.value.endDate) {
-    result = result.filter(o => o.faultTime <= queryParams.value.endDate + ' 23:59:59')
-  }
-  // 时间倒序排列
-  return result.sort((a, b) => new Date(b.faultTime) - new Date(a.faultTime))
-})
+import { ref, reactive, computed, watch, onMounted } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import ImagePreview from '@/components/ImagePreview'
+import { listWorkOrder, getWorkOrderDetail, addWorkOrder, dispatchWorkOrder, verifyWorkOrder, processWorkOrder, completeWorkOrder, closeWorkOrder, cancelWorkOrder } from '@/api/work/order'
+import { getEquipmentFullPage } from '@/api/pipeNetwork/basic'
+import { listDept } from '@/api/system/dept'
+import { listUser } from '@/api/system/user'
+import { handleTree } from '@/utils/ruoyi'
+import { ORDER_STATUS_MAP, ORDER_LEVEL_MAP, orderStatusText, orderStatusType, orderTypeText, orderLevelText, orderLevelType, orderLogText, orderLogType, canOperate, timeText } from './workOrderDict'
+
+// ========== 查询 & 列表(orderType=1 故障维修) ==========
+const listLoading = ref(false)
+const tableData = ref([])
+const pageNum = ref(1)
+const pageSize = ref(10)
+const total = ref(0)
+const queryFormRef = ref(null)
+const queryParams = reactive({ orderLevel: null, orderStatus: null, deviceCode: '', keyword: '' })
+
+async function handleSearch() {
+  listLoading.value = true
+  try {
+    const params = { orderType: 1 }
+    if (queryParams.orderLevel) params.orderLevel = queryParams.orderLevel
+    if (queryParams.orderStatus) params.orderStatus = queryParams.orderStatus
+    if (queryParams.deviceCode) params.deviceCode = queryParams.deviceCode
+    if (queryParams.keyword) params.keyword = queryParams.keyword
+    const res = await listWorkOrder(params, pageNum.value, pageSize.value)
+    if (res.data) {
+      tableData.value = res.data.records || []
+      total.value = Number(res.data.total || 0)
+    }
+  } catch(e) { ElMessage.error('查询失败'); console.error(e) }
+  finally { listLoading.value = false }
+}
+
+function handleReset() {
+  queryFormRef.value && queryFormRef.value.resetFields()
+  queryParams.orderLevel = null
+  queryParams.orderStatus = null
+  queryParams.deviceCode = ''
+  queryParams.keyword = ''
+  pageNum.value = 1
+  selectedOrderId.value = null
+  selectedOrder.value = {}
+  handleSearch()
+}
 
-const paginatedOrders = computed(() => {
-  const start = (currentPage.value - 1) * pageSize
-  return filteredOrders.value.slice(start, start + pageSize)
+// ========== 选中工单 & 详情(合并 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)
+
+const workImageList = computed(() => {
+  if (!selectedOrder.value.workImage) return []
+  return selectedOrder.value.workImage.split(',').filter(img => img && img.trim())
 })
 
-const totalPages = computed(() => Math.ceil(filteredOrders.value.length / pageSize))
+async function handleSelectOrder(order) {
+  selectedOrderId.value = order.orderId
+  selectedOrder.value = { ...order }
+  activeTab.value = 'info'
+  currentOrderLogs.value = []
+  detailDevice.value = null
+  await loadOrderDetail(order.orderId)
+}
 
-// 统计数据
-const pendingCount = computed(() => orders.value.filter(o => o.status === 'pending').length)
-const processingCount = computed(() => orders.value.filter(o => o.status === 'processing').length)
-const completedCount = computed(() => orders.value.filter(o => o.status === 'completed').length)
+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 }
+}
 
-// ==================== 辅助函数 ====================
-const getStatusText = (status) => {
-  const map = { pending: '待处理', processing: '处理中', completed: '已完成' }
-  return map[status] || status
+async function loadDeviceInfo(codeOrId) {
+  try {
+    const res = await getEquipmentFullPage(1, 1, { equipmentCode: codeOrId })
+    const rows = res.data?.rows || res.data?.records || []
+    if (rows.length) { detailDevice.value = rows[0]; return }
+    const res2 = await getEquipmentFullPage(1, 1, { equipmentId: codeOrId })
+    const rows2 = res2.data?.rows || res2.data?.records || []
+    if (rows2.length) detailDevice.value = rows2[0]
+  } catch(e) { console.warn('加载设备信息失败', e) }
 }
 
-const getStatusClass = (status) => {
-  const map = { pending: 'status-pending', processing: 'status-processing', completed: 'status-completed' }
-  return map[status] || ''
+function handleSizeChange() {
+  pageNum.value = 1
+  selectedOrderId.value = null
+  selectedOrder.value = {}
+  handleSearch()
 }
 
-// 生成工单编号
-const generateOrderNo = () => {
-  const today = new Date()
-  const dateStr = today.toISOString().slice(0, 10).replace(/-/g, '')
-  const count = orders.value.length + 1
-  return `WO-${dateStr}-${String(count).padStart(3, '0')}`
+function handlePageChange() {
+  selectedOrderId.value = null
+  selectedOrder.value = {}
+  handleSearch()
 }
 
-// ==================== 自动生成故障工单 ====================
-let faultInterval = null
-let faultIdCounter = 200
-
-// 模拟接收设备故障并自动生成工单
-const simulateFaultReceive = () => {
-  if (Math.random() < 0.02) { // 约5%概率触发新故障
-    const faultsList = [
-      { deviceType: '压力传感器', deviceId: 'PS-008', location: '北环加压站', manufacturer: '华仪科技', message: '压力波动异常,超出预警范围' },
-      { deviceType: '温度传感器', deviceId: 'TP-010', location: '城西换热站', manufacturer: '北方仪表', message: '温度超过85℃警戒线' },
-      { deviceType: '泄漏检测仪', deviceId: 'LD-012', location: '南站调压站', manufacturer: '安防科技', message: '可燃气浓度异常波动' }
-    ]
-    const randomFault = faultsList[Math.floor(Math.random() * faultsList.length)]
-    const now = new Date()
-    const timeStr = `${now.getFullYear()}-${String(now.getMonth()+1).padStart(2,'0')}-${String(now.getDate()).padStart(2,'0')} ${String(now.getHours()).padStart(2,'0')}:${String(now.getMinutes()).padStart(2,'0')}:${String(now.getSeconds()).padStart(2,'0')}`
-    const timeDisplay = `${String(now.getHours()).padStart(2,'0')}:${String(now.getMinutes()).padStart(2,'0')}:${String(now.getSeconds()).padStart(2,'0')}`
-
-    // 添加到实时监控
-    const newFault = {
-      id: faultIdCounter++,
-      time: timeDisplay,
-      deviceName: `${randomFault.deviceType} ${randomFault.deviceId}`,
-      message: randomFault.message,
-      orderCreated: false,
-      isNew: true
+// ========== 用户映射(接单人 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]))
     }
-    recentFaults.value.unshift(newFault)
-    if (recentFaults.value.length > 10) recentFaults.value.pop()
-
-    // 自动生成工单
-    setTimeout(() => {
-      createFaultOrder(randomFault.deviceType, randomFault.deviceId, randomFault.location, randomFault.manufacturer, randomFault.message, timeStr)
-      newFault.orderCreated = true
-      newFault.isNew = false
-    }, 1500)
-  }
+  } catch(e) { console.warn('加载用户列表失败', e) }
 }
 
-// 创建故障工单并自动派发
-const createFaultOrder = (deviceType, deviceId, location, manufacturer, message, faultTime) => {
-  // 根据设备类型自动分配维护人员
-  const maintainerMap = {
-    '压力传感器': '张工',
-    '温度传感器': '李工',
-    '泄漏检测仪': '王工',
-    '流量传感器': '张工',
-    '水位监测仪': '赵工'
-  }
-  const maintainer = maintainerMap[deviceType] || '张工'
-  const contactMap = { '张工': '13800001111', '李工': '13800002222', '王工': '13800003333', '赵工': '13800004444' }
-
-  const newOrder = {
-    id: orders.value.length + 1,
-    orderNo: generateOrderNo(),
-    deviceType: deviceType,
-    deviceId: deviceId,
-    location: location,
-    manufacturer: manufacturer,
-    maintainer: maintainer,
-    contact: contactMap[maintainer],
-    faultTime: faultTime,
-    description: message,
-    createTime: new Date().toLocaleString(),
-    status: 'pending',
-    startTime: null,
-    completeTime: null,
-    processResult: null
+// ========== 新增工单(需求 5.2.4.4.1) ==========
+const addVisible = ref(false)
+const addFormRef = ref(null)
+const addForm = reactive({ deviceId: '', deviceCode: '', orderLevel: 2, faultTime: '', planFinishTime: '', orderDesc: '' })
+const addRules = {
+  orderLevel: [{ required: true, message: '请选择优先级', trigger: 'change' }],
+  orderDesc: [{ required: true, message: '请填写故障描述', trigger: 'blur' }]
+}
+const deviceOptions = ref([])
+const deviceLoading = ref(false)
+const deviceInfo = ref(null)
+const submitting = ref(false)
+
+function openAddDialog() {
+  addForm.deviceId = ''; addForm.deviceCode = ''; addForm.orderLevel = 2
+  addForm.faultTime = ''; addForm.planFinishTime = ''; addForm.orderDesc = ''
+  deviceOptions.value = []
+  deviceInfo.value = null
+  addVisible.value = true
+}
+
+async function searchDevice(query) {
+  if (!query) { deviceOptions.value = []; return }
+  deviceLoading.value = true
+  try {
+    const res = await getEquipmentFullPage(1, 20, { equipmentCode: query })
+    // 后端返回 MyBatis-Plus Page(records),兼容 rows 命名
+    if (res.data) deviceOptions.value = res.data.records || res.data.rows || []
+  } catch(e) { console.warn('设备搜索失败', e) }
+  finally { deviceLoading.value = false }
+}
+
+// 选中设备后:带出设备信息卡 + 自动填充设备编码
+function onDeviceChange(id) {
+  deviceInfo.value = null
+  if (!id) { addForm.deviceCode = ''; return }
+  // equipmentId 可能是数字/字符串,统一转字符串比较
+  const dev = deviceOptions.value.find(d => String(d.equipmentId) === String(id))
+  if (dev) {
+    deviceInfo.value = dev
+    addForm.deviceCode = dev.equipmentCode || ''
   }
+}
 
-  orders.value.unshift(newOrder)
-  console.log(`自动生成工单 ${newOrder.orderNo} 并派发给 ${maintainer}`)
+async function submitAdd() {
+  await addFormRef.value.validate().catch(() => { throw new Error('校验失败') })
+  submitting.value = true
+  try {
+    const data = { orderType: 1, orderLevel: addForm.orderLevel, orderDesc: addForm.orderDesc }
+    if (addForm.deviceId) data.deviceId = addForm.deviceId
+    if (addForm.deviceCode) data.deviceCode = addForm.deviceCode
+    if (addForm.planFinishTime) data.planFinishTime = addForm.planFinishTime
+    // 故障时间:手动指定则作为工单创建时间(列表"故障时间"列展示 createTime)
+    if (addForm.faultTime) data.createTime = addForm.faultTime
+    const res = await addWorkOrder(data)
+    ElMessage.success('工单创建成功')
+    addVisible.value = false
+    handleSearch()
+  } catch(e) { ElMessage.error('创建失败:' + (e.msg || e.message || '请重试')); console.error(e) }
+  finally { submitting.value = false }
 }
 
-// ==================== 手动新增工单 ====================
-const showAddForm = ref(false)
-const newOrder = ref({
-  deviceType: '',
-  deviceId: '',
-  location: '',
-  manufacturer: '',
-  maintainer: '',
-  contact: '',
-  faultTime: '',
-  description: ''
-})
+// ========== 派单(部门树 + 用户多选,参考排水/燃气工单) ==========
+const dispatchDialogVisible = ref(false)
+const dispatchForm = reactive({ handlerIds: [], remarks: '' })
+const deptTreeRef = ref(null)
+const userTableRef = ref(null)
+const deptTreeData = ref([])
+const deptFilterText = ref('')
+const userList = ref([])
+const selectedUserList = ref([])
+
+watch(deptFilterText, (val) => { deptTreeRef.value && deptTreeRef.value.filter(val) })
+function filterDeptNode(value, data) {
+  if (!value) return true
+  return data.deptName.includes(value)
+}
 
-const openAddModal = () => {
-  showAddForm.value = true
-  newOrder.value = {
-    deviceType: '',
-    deviceId: '',
-    location: '',
-    manufacturer: '',
-    maintainer: '',
-    contact: '',
-    faultTime: new Date().toISOString().slice(0, 16),
-    description: ''
-  }
+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)
 }
 
-const cancelAdd = () => {
-  showAddForm.value = false
+function handleRowClick(row) {
+  userTableRef.value && userTableRef.value.toggleRowSelection(row)
 }
 
-const submitManualOrder = () => {
-  if (!newOrder.value.deviceType || !newOrder.value.deviceId || !newOrder.value.location || !newOrder.value.maintainer || !newOrder.value.faultTime || !newOrder.value.description) {
-    alert('请填写所有必填项')
+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
+}
 
-  const contactMap = { '张工': '13800001111', '李工': '13800002222', '王工': '13800003333', '赵工': '13800004444' }
-  const faultTimeFormatted = newOrder.value.faultTime.replace('T', ' ')
-
-  const newOrderData = {
-    id: orders.value.length + 1,
-    orderNo: generateOrderNo(),
-    deviceType: newOrder.value.deviceType,
-    deviceId: newOrder.value.deviceId,
-    location: newOrder.value.location,
-    manufacturer: newOrder.value.manufacturer || '',
-    maintainer: newOrder.value.maintainer,
-    contact: newOrder.value.contact || contactMap[newOrder.value.maintainer] || '',
-    faultTime: faultTimeFormatted,
-    description: newOrder.value.description,
-    createTime: new Date().toLocaleString(),
-    status: 'pending',
-    startTime: null,
-    completeTime: null,
-    processResult: null
+async function submitDispatch() {
+  if (!dispatchForm.handlerIds || dispatchForm.handlerIds.length === 0) {
+    ElMessage.warning('请选择至少一个接单人')
+    return
   }
-
-  orders.value.unshift(newOrderData)
-  alert(`工单 ${newOrderData.orderNo} 已创建并派发给 ${newOrder.value.maintainer}`)
-  showAddForm.value = false
+  submitting.value = true
+  try {
+    await dispatchWorkOrder({
+      orderId: selectedOrder.value.orderId,
+      handlerIds: dispatchForm.handlerIds,
+      remarks: dispatchForm.remarks
+    })
+    ElMessage.success('派单成功!已向 ' + dispatchForm.handlerIds.length + ' 人下发任务')
+    dispatchDialogVisible.value = false
+    refreshAfterAction()
+  } catch(e) { ElMessage.error('派单失败:' + (e.msg || e.message || '请重试')); console.error(e) }
+  finally { submitting.value = false }
 }
 
-// ==================== 工单操作 ====================
-const detailModalVisible = ref(false)
-const completeModalVisible = ref(false)
-const currentOrder = ref(null)
-const processResult = ref('')
+// ========== 核查/处理(参考排水/燃气工单) ==========
+const actionDialogVisible = ref(false)
+const actionTitle = ref('')
+const actionFormData = reactive({ solution: '' })
+
+function handleVerify() {
+  if (!canOperate.verify(selectedOrder.value)) {
+    ElMessage.warning('当前工单不允许核查')
+    return
+  }
+  actionTitle.value = '核查工单'
+  actionFormData.solution = ''
+  actionDialogVisible.value = true
+}
 
-const viewOrderDetail = (order) => {
-  currentOrder.value = order
-  detailModalVisible.value = true
+function handleProcess() {
+  if (!canOperate.process(selectedOrder.value)) {
+    ElMessage.warning('当前工单不允许处理')
+    return
+  }
+  actionTitle.value = '处理工单'
+  actionFormData.solution = ''
+  actionDialogVisible.value = true
 }
 
-const closeDetailModal = () => {
-  detailModalVisible.value = false
-  currentOrder.value = null
+async function submitAction() {
+  if (!actionFormData.solution.trim()) {
+    ElMessage.warning('请填写说明')
+    return
+  }
+  submitting.value = true
+  try {
+    const data = {
+      orderId: selectedOrder.value.orderId,
+      handleResult: 'solved',
+      solution: actionFormData.solution
+    }
+    if (actionTitle.value === '核查工单') {
+      await verifyWorkOrder(data)
+    } else {
+      await processWorkOrder(data)
+    }
+    ElMessage.success(actionTitle.value + '成功')
+    actionDialogVisible.value = false
+    refreshAfterAction()
+  } catch(e) { ElMessage.error('操作失败:' + (e.msg || e.message || '请重试')); console.error(e) }
+  finally { submitting.value = false }
 }
 
-const updateStatus = (order, status) => {
-  order.status = status
-  if (status === 'processing') {
-    order.startTime = new Date().toLocaleString()
+// ========== 提交验收(后端 completeOrder 要求状态 4) ==========
+const completeDialogVisible = ref(false)
+const completeForm = reactive({ result: '' })
+
+function handleComplete() {
+  if (!canOperate.complete(selectedOrder.value)) {
+    ElMessage.warning('当前工单不允许提交验收')
+    return
   }
-  alert(`工单 ${order.orderNo} 状态已更新为${getStatusText(status)}`)
+  completeForm.result = ''
+  completeDialogVisible.value = true
 }
 
-const completeOrder = (order) => {
-  currentOrder.value = order
-  processResult.value = ''
-  completeModalVisible.value = true
+async function submitComplete() {
+  if (!completeForm.result.trim()) {
+    ElMessage.warning('请填写验收意见')
+    return
+  }
+  submitting.value = true
+  try {
+    await completeWorkOrder(selectedOrder.value.orderId, completeForm.result)
+    ElMessage.success('提交验收成功')
+    completeDialogVisible.value = false
+    refreshAfterAction()
+  } catch(e) { ElMessage.error('提交失败:' + (e.msg || e.message || '请重试')); console.error(e) }
+  finally { submitting.value = false }
 }
 
-const closeCompleteModal = () => {
-  completeModalVisible.value = false
-  currentOrder.value = null
-  processResult.value = ''
+// ========== 验收通过/结案(参考排水/燃气工单) ==========
+const closeDialogVisible = ref(false)
+const closeForm = reactive({ result: '' })
+
+function handleClose() {
+  if (!canOperate.close(selectedOrder.value)) {
+    ElMessage.warning('当前工单不允许验收通过')
+    return
+  }
+  closeForm.result = ''
+  closeDialogVisible.value = true
 }
 
-const submitComplete = () => {
-  if (currentOrder.value) {
-    currentOrder.value.status = 'completed'
-    currentOrder.value.completeTime = new Date().toLocaleString()
-    currentOrder.value.processResult = processResult.value || '处理完成'
-    alert(`工单 ${currentOrder.value.orderNo} 已完成`)
-    closeCompleteModal()
+async function submitClose() {
+  if (!closeForm.result.trim()) {
+    ElMessage.warning('请填写验收结果')
+    return
   }
+  submitting.value = true
+  try {
+    await closeWorkOrder({
+      orderId: selectedOrder.value.orderId,
+      result: closeForm.result
+    })
+    ElMessage.success('验收通过,工单已结案')
+    closeDialogVisible.value = false
+    refreshAfterAction()
+  } catch(e) { ElMessage.error('验收失败:' + (e.msg || e.message || '请重试')); console.error(e) }
+  finally { submitting.value = false }
 }
 
-// ==================== 查询相关 ====================
-const searchOrders = () => {
-  currentPage.value = 1
+// ========== 取消工单 ==========
+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(() => {})
 }
 
-const resetQuery = () => {
-  queryParams.value = { deviceType: '', deviceId: '', startDate: '', endDate: '' }
-  currentPage.value = 1
+/** 操作完成后刷新列表并重新加载当前选中工单 */
+async function refreshAfterAction() {
+  await handleSearch()
+  const fresh = tableData.value.find(o => o.orderId === selectedOrderId.value)
+  if (fresh) {
+    await handleSelectOrder(fresh)
+  } else {
+    selectedOrderId.value = null
+    selectedOrder.value = {}
+  }
 }
 
-// ==================== 生命周期 ====================
 onMounted(() => {
-  faultInterval = setInterval(simulateFaultReceive, 20000)
-})
-
-onUnmounted(() => {
-  if (faultInterval) clearInterval(faultInterval)
+  loadUsers()
+  handleSearch()
 })
 </script>
 
-<style scoped>
-* { margin: 0; padding: 0; box-sizing: border-box; }
-
-.prototype-container {
-  max-width: 1600px;
-  margin: 0 auto;
-  background: #f0f4f8;
-  border-radius: 28px;
-  box-shadow: 0 20px 35px -12px rgba(0, 0, 0, 0.15);
-  overflow: hidden;
-  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
-}
+<style scoped lang="scss">
+.order-container {
+  padding: 0 16px 16px;
 
-/* 头部 */
-.header {
-  background: #0a2b3c;
-  padding: 16px 28px;
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  flex-wrap: wrap;
-  gap: 16px;
-  border-bottom: 3px solid #e67e22;
-}
+  .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;
+    }
 
-.logo h2 { color: white; font-weight: 600; font-size: 1.3rem; }
-.logo p { color: #bdd8e8; font-size: 0.7rem; }
-
-.header-stats { display: flex; gap: 12px; }
-.stat-card { background: rgba(255,255,255,0.15); padding: 6px 14px; border-radius: 40px; text-align: center; color: white; }
-.stat-card.pending { background: #f39c12; }
-.stat-card.processing { background: #3498db; }
-.stat-card.completed { background: #27ae60; }
-.stat-value { font-weight: 700; font-size: 1.2rem; margin-right: 6px; }
-.stat-label { font-size: 0.65rem; opacity: 0.9; }
-
-/* 布局 */
-.main-layout { display: flex; flex-wrap: wrap; }
-.left-panel { width: 380px; background: #f9fbfd; padding: 20px; }
-.right-panel { flex: 1; background: #ffffff; padding: 20px; min-width: 550px; }
-
-/* 卡片 */
-.card { background: white; border-radius: 20px; border: 1px solid #e6edf3; margin-bottom: 20px; overflow: hidden; }
-.card-header { background: #f9fafc; padding: 14px 18px; border-bottom: 1px solid #eef2f6; font-weight: 700; font-size: 0.85rem; color: #1d5a78; display: flex; justify-content: space-between; align-items: center; }
-.card-body { padding: 16px; }
-
-/* 实时故障监控 */
-.fault-monitor { max-height: 280px; overflow-y: auto; }
-.fault-item { background: #fef9e8; border-radius: 12px; padding: 10px; margin-bottom: 8px; border-left: 3px solid #e74c3c; }
-.fault-item.new { animation: highlight 1s ease; background: #fff3e0; }
-.fault-time { font-size: 0.6rem; color: #6f8ea8; font-family: monospace; }
-.fault-device { font-weight: 700; font-size: 0.75rem; margin: 4px 0; }
-.fault-message { font-size: 0.7rem; color: #e74c3c; }
-.fault-order-status { margin-top: 6px; font-size: 0.65rem; }
-.order-created { color: #27ae60; }
-.order-creating { color: #f39c12; }
-.empty-fault { text-align: center; padding: 30px; color: #8aaec2; }
-.live-badge { font-size: 0.65rem; background: #e74c3c; color: white; padding: 2px 10px; border-radius: 20px; animation: blink 1s infinite; }
-
-/* 手动新增表单 */
-.add-form { margin-top: 8px; }
-.empty-add { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 30px; color: #8aaec2; cursor: pointer; border: 1px dashed #cbdde6; border-radius: 16px; }
-.empty-add:hover { border-color: #e67e22; color: #e67e22; }
-.add-icon { font-size: 2rem; margin-bottom: 8px; }
-.form-row { display: flex; gap: 12px; margin-bottom: 12px; }
-.form-group { margin-bottom: 12px; }
-.form-group.half { flex: 1; }
-.form-label { font-size: 0.7rem; font-weight: 600; margin-bottom: 4px; display: block; color: #2c5a72; }
-.form-input, .form-select, .form-textarea { width: 100%; padding: 8px 12px; border-radius: 12px; border: 1px solid #cbdde6; font-size: 0.75rem; }
-.form-actions { display: flex; gap: 12px; justify-content: flex-end; margin-top: 16px; }
-.btn-primary, .btn-outline, .btn-sm { padding: 5px 12px; border-radius: 30px; font-size: 0.7rem; cursor: pointer; }
-.btn-primary { background: #e67e22; border: none; color: white; }
-.btn-primary:hover { background: #d35400; }
-.btn-outline { background: white; border: 1px solid #e67e22; color: #e67e22; }
-
-/* 筛选栏 */
-.filter-bar { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 16px; align-items: center; }
-.filter-select, .filter-input { padding: 6px 12px; border-radius: 20px; border: 1px solid #cbdde6; font-size: 0.7rem; }
-.filter-input { width: 130px; }
-.filter-sep { font-size: 0.7rem; color: #6f8ea8; }
-.list-count { font-size: 0.65rem; font-weight: normal; color: #6f8ea8; }
-
-/* 表格 */
-.order-table-wrapper { overflow-x: auto; }
-.data-table { width: 100%; border-collapse: collapse; font-size: 0.7rem; }
-.data-table th { background: #f8fafc; padding: 10px 8px; text-align: left; font-weight: 600; color: #2c5a72; border-bottom: 1px solid #e2edf2; }
-.data-table td { padding: 10px 8px; border-bottom: 1px solid #f0f4f7; vertical-align: middle; }
-.order-no { font-family: monospace; color: #e67e22; }
-.time-cell { white-space: nowrap; font-family: monospace; }
-.desc-cell { max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.action-cell { white-space: nowrap; display: flex; gap: 6px; }
-.empty-row { text-align: center; padding: 30px; color: #8aaec2; }
-
-/* 状态标签 */
-.status-badge { padding: 2px 10px; border-radius: 20px; font-size: 0.65rem; }
-.status-pending { background: #fef3c7; color: #f39c12; }
-.status-processing { background: #e3f2fd; color: #3498db; }
-.status-completed { background: #d5f5e3; color: #27ae60; }
-
-/* 操作按钮 */
-.btn-view { background: #95a5a6; color: white; border: none; padding: 4px 10px; border-radius: 20px; font-size: 0.65rem; cursor: pointer; }
-.btn-start { background: #3498db; color: white; border: none; padding: 4px 10px; border-radius: 20px; font-size: 0.65rem; cursor: pointer; }
-.btn-complete { background: #27ae60; color: white; border: none; padding: 4px 10px; border-radius: 20px; font-size: 0.65rem; cursor: pointer; }
-
-/* 分页 */
-.pagination { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }
-.page-btn { background: white; border: 1px solid #cbdde6; padding: 4px 10px; border-radius: 20px; font-size: 0.7rem; cursor: pointer; }
-.page-btn.active { background: #e67e22; color: white; border-color: #e67e22; }
-
-/* 模态框 */
-.modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 1000; }
-.modal-content { background: white; border-radius: 24px; width: 480px; max-width: 90%; padding: 24px; max-height: 85vh; overflow-y: auto; }
-.detail-info { background: #f8fafc; border-radius: 16px; padding: 16px; margin-top: 8px; }
-.info-row { margin-bottom: 10px; font-size: 0.75rem; }
-.info-row .label { font-weight: 600; width: 100px; display: inline-block; color: #2c5a72; }
-.order-info-summary { background: #f8fafc; border-radius: 12px; padding: 12px; margin-bottom: 16px; font-size: 0.7rem; }
-
-@keyframes highlight { 0% { background: #fff3e0; } 100% { background: #fef9e8; } }
-@keyframes blink { 0%,100% { opacity: 1; } 50% { opacity: 0.6; } }
-</style>
+    &.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;
+  }
+
+  .timeline-scroll {
+    max-height: calc(100vh - 340px);
+    overflow-y: auto;
+    padding-right: 8px;
+  }
+
+  .device-section, .image-section, .action-section {
+    margin-top: 24px;
+    padding-top: 24px;
+    border-top: 1px solid #e4e7ed;
+
+    .section-title {
+      font-size: 15px;
+      font-weight: bold;
+      margin-bottom: 16px;
+      color: #303133;
+    }
+  }
+
+  .image-list {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 12px;
+  }
+
+  .action-buttons {
+    display: flex;
+    gap: 12px;
+    flex-wrap: wrap;
+
+    .el-button {
+      min-width: 100px;
+    }
+  }
+
+  /* 新增工单:设备信息卡(需求 5.2.4.4.1) */
+  .device-info-card {
+    width: 100%;
+    background: #f8fafc;
+    border-radius: 10px;
+    padding: 12px;
+    border: 1px solid #e4eaf1;
+
+    :deep(.el-descriptions__body) {
+      background: #fff;
+    }
+  }
+
+  .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>

+ 747 - 627
src/views/subSystem/pipeNetwork/pworkOrder/Pworkdisposition.vue

@@ -1,708 +1,828 @@
-<!--工单处置-->
+<!--全部工单处置(参考排水/燃气工单:搜索卡 + 左列表 + 右详情 tabs + 按钮操作 + 部门树派单)-->
+<!--数据源:/api/work-order/*(equipmentType 为空 = 查询所有设备工单)-->
 <template>
-  <div class="prototype-container">
-    <!-- 头部 -->
-    <div class="header">
-      <div class="logo">
-        <h2>🔧 故障工单处理系统</h2>
-        <p>工单处理 · 故障记录 · 附件上传 · 闭环管理</p>
+  <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>
-      <div class="header-stats">
-        <div class="stat-card pending">
-          <span class="stat-value">{{ pendingCount }}</span>
-          <span class="stat-label">待处理</span>
-        </div>
-        <div class="stat-card processing">
-          <span class="stat-value">{{ processingCount }}</span>
-          <span class="stat-label">处理中</span>
-        </div>
-        <div class="stat-card completed">
-          <span class="stat-value">{{ completedCount }}</span>
-          <span class="stat-label">已完成</span>
-        </div>
-      </div>
-    </div>
+    </el-card>
 
-    <div class="main-layout">
+    <!-- 主容器:左列表 + 右详情(参考排水/燃气工单) -->
+    <el-row :gutter="16" class="main-row">
       <!-- 左侧:工单列表 -->
-      <div class="left-panel">
-        <div class="card">
-          <div class="card-header">
-            📋 我的工单列表
-            <div class="filter-bar">
-              <select v-model="filterStatus" class="filter-select">
-                <option value="">全部状态</option>
-                <option value="pending">待处理</option>
-                <option value="processing">处理中</option>
-                <option value="completed">已完成</option>
-              </select>
-              <input type="text" v-model="searchKeyword" class="search-input" placeholder="搜索工单号/设备">
-              <button @click="searchOrders" class="btn-primary btn-sm">查询</button>
+      <el-col :span="8" class="col-fill">
+        <el-card class="box-card" shadow="always">
+          <template #header>
+            <div class="card-header">
+              <span>工单列表</span>
             </div>
-          </div>
-          <div class="card-body">
+          </template>
+          <el-scrollbar class="list-scroll">
             <div class="order-list">
-              <div v-for="order in paginatedOrders" :key="order.id" class="order-item" :class="order.status">
+              <div
+                v-for="order in tableData"
+                :key="order.orderId"
+                class="order-item"
+                :class="{ 'active': selectedOrderId === order.orderId }"
+                @click="handleSelectOrder(order)"
+              >
                 <div class="order-header">
-                  <div class="order-id">
-                    <span class="order-no">{{ order.orderNo }}</span>
-                    <span class="order-type" :class="order.priority === 'urgent' ? 'type-urgent' : 'type-normal'">
-                      {{ order.priority === 'urgent' ? '紧急' : '普通' }}
-                    </span>
-                  </div>
-                  <div class="order-status" :class="getStatusClass(order.status)">
-                    {{ getStatusText(order.status) }}
-                  </div>
-                </div>
-                <div class="order-device">
-                  <span class="label">设备:</span>
-                  <span class="value">{{ order.deviceName }}</span>
-                  <span class="label">位置:</span>
-                  <span class="value">{{ order.location }}</span>
-                </div>
-                <div class="order-desc">
-                  <span class="label">故障描述:</span>
-                  <span class="value">{{ order.description }}</span>
+                  <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-time">
-                  <span class="label">故障时间:</span>
-                  <span class="value">{{ order.faultTime }}</span>
-                </div>
-                <div class="order-actions">
-                  <button v-if="order.status === 'pending'" @click="startProcess(order)" class="btn-start">开始处理</button>
-                  <button v-if="order.status === 'processing'" @click="openProcessModal(order)" class="btn-process">处理反馈</button>
-                  <button @click="viewOrderDetail(order)" class="btn-view">查看详情</button>
+                <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>
-              <div v-if="filteredOrders.length === 0" class="empty-order">
-                暂无工单数据
-              </div>
-            </div>
-            <div class="pagination" v-if="totalPages > 1">
-              <button v-for="page in totalPages" :key="page" class="page-btn" :class="{ active: currentPage === page }"
-                      @click="currentPage = page">{{ page }}</button>
+              <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>
-        </div>
-      </div>
-
-      <!-- 右侧:工单处理面板 -->
-      <div class="right-panel">
-        <div class="card" v-if="currentProcessingOrder">
-          <div class="card-header">
-            🔧 工单处理
-            <span class="order-badge">{{ currentProcessingOrder.orderNo }}</span>
-          </div>
-          <div class="card-body">
-            <div class="process-info">
-              <div class="info-row"><span class="label">设备名称:</span>{{ currentProcessingOrder.deviceName }}</div>
-              <div class="info-row"><span class="label">设备位置:</span>{{ currentProcessingOrder.location }}</div>
-              <div class="info-row"><span class="label">故障描述:</span>{{ currentProcessingOrder.description }}</div>
-              <div class="info-row"><span class="label">故障时间:</span>{{ currentProcessingOrder.faultTime }}</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>
-
-            <div class="process-form">
-              <div class="form-title">处理反馈信息</div>
-              <div class="form-row">
-                <div class="form-group half">
-                  <label class="form-label">故障类型 *</label>
-                  <select v-model="processData.faultType" class="form-select">
-                    <option value="">请选择</option>
-                    <option value="硬件故障">硬件故障</option>
-                    <option value="软件故障">软件故障</option>
-                    <option value="通信故障">通信故障</option>
-                    <option value="电源故障">电源故障</option>
-                    <option value="传感器故障">传感器故障</option>
-                    <option value="线路故障">线路故障</option>
-                    <option value="其他">其他</option>
-                  </select>
-                </div>
-                <div class="form-group half">
-                  <label class="form-label">处理时间 *</label>
-                  <input type="datetime-local" v-model="processData.processTime" class="form-input">
-                </div>
-              </div>
-              <div class="form-group">
-                <label class="form-label">故障原因 *</label>
-                <textarea v-model="processData.faultReason" class="form-textarea" rows="2" placeholder="请详细描述故障原因..."></textarea>
-              </div>
-              <div class="form-row">
-                <div class="form-group half">
-                  <label class="form-label">处理人员 *</label>
-                  <input v-model="processData.handler" class="form-input" placeholder="处理人员姓名">
+          </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>
-                <div class="form-group half">
-                  <label class="form-label">联系方式 *</label>
-                  <input v-model="processData.contact" class="form-input" placeholder="联系电话">
-                </div>
-              </div>
-              <div class="form-group">
-                <label class="form-label">处理措施</label>
-                <textarea v-model="processData.processMeasure" class="form-textarea" rows="2" placeholder="请描述处理措施..."></textarea>
-              </div>
-              <div class="form-group">
-                <label class="form-label">附件上传</label>
-                <div class="upload-area">
-                  <label class="upload-btn">
-                    📎 选择图片/文件
-                    <input type="file" @change="handleFileUpload" accept="image/*,.pdf,.doc" hidden multiple>
-                  </label>
-                  <div class="file-list">
-                    <div v-for="(file, idx) in uploadedFiles" :key="idx" class="file-item">
-                      <img v-if="file.type?.startsWith('image/')" :src="file.preview" class="file-preview">
-                      <span class="file-name">{{ file.name }}</span>
-                      <button @click="removeFile(idx)" class="remove-file">✖</button>
-                    </div>
+
+                <!-- 工单图片 -->
+                <div v-if="workImageList.length > 0" class="image-section">
+                  <h4 class="section-title">工单图片</h4>
+                  <div class="image-list">
+                    <image-preview v-for="(img, index) in workImageList" :key="index" :src="img" :width="100" :height="100"/>
                   </div>
-                  <div class="upload-hint">支持上传图片、PDF、Word文档,最多5个文件</div>
                 </div>
-              </div>
-              <div class="form-actions">
-                <button @click="cancelProcess" class="btn-outline">取消</button>
-                <button @click="submitProcess" class="btn-primary">提交处理</button>
-              </div>
-            </div>
-          </div>
-        </div>
 
-        <!-- 未选择工单时的提示 -->
-        <div class="card" v-else>
-          <div class="card-header">
-            🔧 工单处理
-          </div>
-          <div class="card-body">
-            <div class="empty-process">
-              <div class="empty-icon">🔧</div>
-              <div>请从左侧选择待处理的工单</div>
-              <div class="empty-hint">点击「开始处理」或「处理反馈」按钮</div>
-            </div>
-          </div>
-        </div>
-
-        <!-- 已完成工单详情展示 -->
-        <div class="card" v-if="selectedCompletedOrder">
-          <div class="card-header">
-            ✅ 已完成工单详情
-            <span class="order-badge">{{ selectedCompletedOrder.orderNo }}</span>
-          </div>
-          <div class="card-body">
-            <div class="completed-info">
-              <div class="info-group">
-                <div class="group-title">基本信息</div>
-                <div class="info-row"><span class="label">设备名称:</span>{{ selectedCompletedOrder.deviceName }}</div>
-                <div class="info-row"><span class="label">设备位置:</span>{{ selectedCompletedOrder.location }}</div>
-                <div class="info-row"><span class="label">故障描述:</span>{{ selectedCompletedOrder.description }}</div>
-              </div>
-              <div class="info-group">
-                <div class="group-title">处理信息</div>
-                <div class="info-row"><span class="label">故障类型:</span>{{ selectedCompletedOrder.faultType || '-' }}</div>
-                <div class="info-row"><span class="label">故障原因:</span>{{ selectedCompletedOrder.faultReason || '-' }}</div>
-                <div class="info-row"><span class="label">处理措施:</span>{{ selectedCompletedOrder.processMeasure || '-' }}</div>
-                <div class="info-row"><span class="label">处理人员:</span>{{ selectedCompletedOrder.handler }}</div>
-                <div class="info-row"><span class="label">联系方式:</span>{{ selectedCompletedOrder.contact }}</div>
-                <div class="info-row"><span class="label">处理时间:</span>{{ selectedCompletedOrder.processTime }}</div>
-                <div class="info-row"><span class="label">完成时间:</span>{{ selectedCompletedOrder.completeTime }}</div>
-              </div>
-              <div class="info-group" v-if="selectedCompletedOrder.attachments?.length">
-                <div class="group-title">附件资料</div>
-                <div class="attachment-list">
-                  <div v-for="(att, idx) in selectedCompletedOrder.attachments" :key="idx" class="attachment-item">
-                    <img v-if="att.type?.startsWith('image/')" :src="att.preview" class="attachment-img">
-                    <span class="attachment-name">{{ att.name }}</span>
+                <!-- 工单操作(参考排水/燃气工单按钮组) -->
+                <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>
-              </div>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-
-    <!-- 处理反馈模态框(备用) -->
-    <div v-if="processModalVisible" class="modal-overlay" @click.self="closeProcessModal">
-      <div class="modal-content" style="width:600px;">
-        <h3>🔧 工单处理反馈</h3>
-        <div class="order-summary">
-          <div><span class="label">工单号:</span>{{ currentOrder?.orderNo }}</div>
-          <div><span class="label">设备:</span>{{ currentOrder?.deviceName }}</div>
-        </div>
-        <div class="process-form-modal">
-          <div class="form-row">
-            <div class="form-group half">
-              <label class="form-label">故障类型 *</label>
-              <select v-model="modalProcessData.faultType" class="form-select">
-                <option value="">请选择</option>
-                <option value="硬件故障">硬件故障</option>
-                <option value="软件故障">软件故障</option>
-                <option value="通信故障">通信故障</option>
-                <option value="电源故障">电源故障</option>
-                <option value="传感器故障">传感器故障</option>
-                <option value="线路故障">线路故障</option>
-              </select>
-            </div>
-            <div class="form-group half">
-              <label class="form-label">处理时间 *</label>
-              <input type="datetime-local" v-model="modalProcessData.processTime" class="form-input">
-            </div>
-          </div>
-          <div class="form-group">
-            <label class="form-label">故障原因 *</label>
-            <textarea v-model="modalProcessData.faultReason" class="form-textarea" rows="2" placeholder="请详细描述故障原因..."></textarea>
-          </div>
-          <div class="form-row">
-            <div class="form-group half">
-              <label class="form-label">处理人员 *</label>
-              <input v-model="modalProcessData.handler" class="form-input">
-            </div>
-            <div class="form-group half">
-              <label class="form-label">联系方式 *</label>
-              <input v-model="modalProcessData.contact" class="form-input">
-            </div>
-          </div>
-          <div class="form-group">
-            <label class="form-label">处理措施</label>
-            <textarea v-model="modalProcessData.processMeasure" class="form-textarea" rows="2"></textarea>
-          </div>
-          <div class="form-group">
-            <label class="form-label">附件上传</label>
-            <div class="upload-area">
-              <label class="upload-btn">
-                📎 上传文件
-                <input type="file" @change="handleModalFileUpload" accept="image/*,.pdf" hidden multiple>
-              </label>
-              <div class="file-list">
-                <span v-for="(file, idx) in modalUploadedFiles" :key="idx" class="file-tag">
-                  {{ file.name }}
-                  <button @click="removeModalFile(idx)" class="remove-file">✖</button>
-                </span>
-              </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 class="form-actions">
-          <button @click="closeProcessModal" class="btn-outline">取消</button>
-          <button @click="submitModalProcess" class="btn-primary">提交处理</button>
+        <!-- 右侧:用户列表 -->
+        <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>
-    </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 label="结果说明" required>
+          <el-input v-model="actionFormData.solution" type="textarea" :rows="5" :placeholder="actionTitle === '核查工单' ? '请输入核查结论(属实/不属实、现场情况)' : '请输入故障原因、处理结果及处理措施'" maxlength="500" show-word-limit/>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="actionDialogVisible = false">取消</el-button>
+        <el-button type="primary" :loading="submitting" @click="submitAction">确定</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 提交验收弹窗 -->
+    <el-dialog v-model="completeDialogVisible" title="提交验收" width="500px" append-to-body>
+      <el-form label-width="80px">
+        <el-form-item label="验收意见">
+          <el-input v-model="completeForm.result" type="textarea" :rows="4" placeholder="请填写维修处置结果说明" maxlength="500" show-word-limit/>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="completeDialogVisible = false">取消</el-button>
+        <el-button type="primary" :loading="submitting" @click="submitComplete">提交</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 验收通过/结案弹窗 -->
+    <el-dialog v-model="closeDialogVisible" title="验收通过" width="500px" append-to-body>
+      <el-form label-width="80px">
+        <el-form-item label="验收结果">
+          <el-input v-model="closeForm.result" type="textarea" :rows="4" placeholder="请填写验收结果(结案后关联隐患同步为已整改、设备报警状态重置)" maxlength="500" show-word-limit/>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="closeDialogVisible = false">取消</el-button>
+        <el-button type="success" :loading="submitting" @click="submitClose">确认验收</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
-import { ref, computed, onMounted } from 'vue'
-
-// ==================== 工单数据 ====================
-const orders = ref([
-  {
-    id: 1, orderNo: 'WO-20250508-001', priority: 'urgent', deviceName: '压力传感器-PS001', location: '城东泵站',
-    description: '设备离线,数据上报异常,超过30分钟未回传', faultTime: '2025-05-08 09:23:15',
-    status: 'pending', assignedTo: '张工'
-  },
-  {
-    id: 2, orderNo: 'WO-20250508-002', priority: 'normal', deviceName: '泄漏检测仪-LD005', location: '化工园区',
-    description: '可燃气浓度超阈值报警,需现场核查', faultTime: '2025-05-08 07:30:22',
-    status: 'processing', assignedTo: '李工',
-    faultType: '传感器故障', faultReason: '传感器灵敏度漂移', processMeasure: '已重新校准传感器',
-    handler: '李工', contact: '13800002222', processTime: '2025-05-08 10:30:00'
-  },
-  {
-    id: 3, orderNo: 'WO-20250507-003', priority: 'normal', deviceName: '水位监测仪-WL006', location: '东区排水口',
-    description: '水位数据异常波动,需校准', faultTime: '2025-05-07 14:20:33',
-    status: 'completed', assignedTo: '王工',
-    faultType: '硬件故障', faultReason: '传感器进水导致读数异常', processMeasure: '更换传感器,已恢复正常',
-    handler: '王工', contact: '13800003333', processTime: '2025-05-07 16:30:00', completeTime: '2025-05-07 17:00:00',
-    attachments: [{ name: '维修现场.jpg', type: 'image/jpeg', preview: null }]
-  },
-  {
-    id: 4, orderNo: 'WO-20250508-004', priority: 'normal', deviceName: '流量传感器-FL002', location: '城南枢纽',
-    description: '流量数据中断,设备可能离线', faultTime: '2025-05-08 06:15:00',
-    status: 'pending', assignedTo: '张工'
-  }
-])
-
-// 当前正在处理的工单
-const currentProcessingOrder = ref(null)
-const selectedCompletedOrder = ref(null)
-
-// 处理表单数据
-const processData = ref({
-  faultType: '',
-  processTime: '',
-  faultReason: '',
-  handler: '',
-  contact: '',
-  processMeasure: ''
-})
-const uploadedFiles = ref([])
-
-// 模态框相关
-const processModalVisible = ref(false)
-const currentOrder = ref(null)
-const modalProcessData = ref({
-  faultType: '',
-  processTime: '',
-  faultReason: '',
-  handler: '',
-  contact: '',
-  processMeasure: ''
-})
-const modalUploadedFiles = ref([])
-
-// 筛选参数
-const filterStatus = ref('')
-const searchKeyword = ref('')
-const currentPage = ref(1)
-const pageSize = 5
-
-// 过滤后的工单
-const filteredOrders = computed(() => {
-  let result = [...orders.value]
-  if (filterStatus.value) {
-    result = result.filter(o => o.status === filterStatus.value)
-  }
-  if (searchKeyword.value) {
-    const kw = searchKeyword.value.toLowerCase()
-    result = result.filter(o => o.orderNo.toLowerCase().includes(kw) || o.deviceName.toLowerCase().includes(kw))
-  }
-  // 按故障时间倒序
-  return result.sort((a, b) => new Date(b.faultTime) - new Date(a.faultTime))
-})
+import { ref, reactive, computed, watch, onMounted } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+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()
+}
 
-const paginatedOrders = computed(() => {
-  const start = (currentPage.value - 1) * pageSize
-  return filteredOrders.value.slice(start, start + pageSize)
+// ========== 选中工单 & 详情(合并 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)
+
+// 工单图片列表(多个用逗号分隔)
+const workImageList = computed(() => {
+  if (!selectedOrder.value.workImage) return []
+  return selectedOrder.value.workImage.split(',').filter(img => img && img.trim())
 })
 
-const totalPages = computed(() => Math.ceil(filteredOrders.value.length / pageSize))
+async function handleSelectOrder(order) {
+  selectedOrderId.value = order.orderId
+  selectedOrder.value = { ...order }
+  activeTab.value = 'info'
+  currentOrderLogs.value = []
+  detailDevice.value = null
+  await loadOrderDetail(order.orderId)
+}
 
-// 统计数据
-const pendingCount = computed(() => orders.value.filter(o => o.status === 'pending').length)
-const processingCount = computed(() => orders.value.filter(o => o.status === 'processing').length)
-const completedCount = computed(() => orders.value.filter(o => o.status === 'completed').length)
+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 }
+}
 
-// ==================== 辅助函数 ====================
-const getStatusText = (status) => {
-  const map = { pending: '待处理', processing: '处理中', completed: '已完成' }
-  return map[status] || status
+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) }
 }
 
-const getStatusClass = (status) => {
-  const map = { pending: 'status-pending', processing: 'status-processing', completed: 'status-completed' }
-  return map[status] || ''
+function handleSizeChange() {
+  pageNum.value = 1
+  selectedOrderId.value = null
+  selectedOrder.value = {}
+  handleSearch()
 }
 
-// ==================== 工单操作 ====================
-const startProcess = (order) => {
-  order.status = 'processing'
-  currentProcessingOrder.value = order
-  // 预填处理人员信息
-  processData.value = {
-    faultType: '',
-    processTime: new Date().toISOString().slice(0, 16),
-    faultReason: '',
-    handler: order.assignedTo || '',
-    contact: '',
-    processMeasure: ''
-  }
-  uploadedFiles.value = []
-  selectedCompletedOrder.value = null
+function handlePageChange() {
+  selectedOrderId.value = null
+  selectedOrder.value = {}
+  handleSearch()
 }
 
-const openProcessModal = (order) => {
-  currentOrder.value = order
-  currentProcessingOrder.value = order
-  modalProcessData.value = {
-    faultType: order.faultType || '',
-    processTime: new Date().toISOString().slice(0, 16),
-    faultReason: order.faultReason || '',
-    handler: order.assignedTo || '',
-    contact: order.contact || '',
-    processMeasure: order.processMeasure || ''
-  }
-  modalUploadedFiles.value = order.attachments || []
-  processModalVisible.value = true
-  selectedCompletedOrder.value = null
+// ========== 用户映射(接单人 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 closeProcessModal = () => {
-  processModalVisible.value = false
-  currentOrder.value = null
+// ========== 派单(部门树 + 用户多选,参考排水/燃气工单) ==========
+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)
 }
 
-// 处理附件上传(右侧面板)
-const handleFileUpload = (event) => {
-  const files = Array.from(event.target.files)
-  files.forEach(file => {
-    const reader = new FileReader()
-    reader.onload = (e) => {
-      uploadedFiles.value.push({
-        name: file.name,
-        size: file.size,
-        type: file.type,
-        preview: file.type.startsWith('image/') ? e.target.result : null,
-        file: file
-      })
-    }
-    reader.readAsDataURL(file)
-  })
+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) }
 }
 
-const removeFile = (idx) => {
-  uploadedFiles.value.splice(idx, 1)
+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) }
 }
 
-// 处理附件上传(模态框)
-const handleModalFileUpload = (event) => {
-  const files = Array.from(event.target.files)
-  files.forEach(file => {
-    modalUploadedFiles.value.push({
-      name: file.name,
-      size: file.size,
-      type: file.type,
-      file: file
-    })
-  })
+function handleSelectionChange(selection) {
+  selectedUserList.value = selection
+  dispatchForm.handlerIds = selection.map(item => item.userId)
 }
 
-const removeModalFile = (idx) => {
-  modalUploadedFiles.value.splice(idx, 1)
+function handleRowClick(row) {
+  userTableRef.value && userTableRef.value.toggleRowSelection(row)
 }
 
-// 提交处理(右侧面板)
-const submitProcess = () => {
-  if (!processData.value.faultType) {
-    alert('请选择故障类型')
-    return
-  }
-  if (!processData.value.processTime) {
-    alert('请填写处理时间')
-    return
-  }
-  if (!processData.value.faultReason) {
-    alert('请填写故障原因')
-    return
-  }
-  if (!processData.value.handler) {
-    alert('请填写处理人员')
+const selectedUserNames = computed(() => selectedUserList.value.map(item => item.nickName || item.userName))
+
+function handleDispatch() {
+  if (!canOperate.dispatch(selectedOrder.value)) {
+    ElMessage.warning('仅可对待派单工单进行派单操作')
     return
   }
-  if (!processData.value.contact) {
-    alert('请填写联系方式')
+  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
   }
-
-  const order = currentProcessingOrder.value
-  order.status = 'completed'
-  order.faultType = processData.value.faultType
-  order.faultReason = processData.value.faultReason
-  order.processMeasure = processData.value.processMeasure
-  order.handler = processData.value.handler
-  order.contact = processData.value.contact
-  order.processTime = processData.value.processTime.replace('T', ' ')
-  order.completeTime = new Date().toLocaleString()
-  order.attachments = [...uploadedFiles.value]
-
-  alert(`工单 ${order.orderNo} 处理完成!`)
-  currentProcessingOrder.value = null
-  selectedCompletedOrder.value = order
+  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 submitModalProcess = () => {
-  if (!modalProcessData.value.faultType) {
-    alert('请选择故障类型')
+// ========== 核查/处理(参考排水/燃气工单) ==========
+const actionDialogVisible = ref(false)
+const actionTitle = ref('')
+const actionFormData = reactive({ solution: '' })
+
+function handleVerify() {
+  if (!canOperate.verify(selectedOrder.value)) {
+    ElMessage.warning('当前工单不允许核查')
     return
   }
-  if (!modalProcessData.value.processTime) {
-    alert('请填写处理时间')
+  actionTitle.value = '核查工单'
+  actionFormData.solution = ''
+  actionDialogVisible.value = true
+}
+
+function handleProcess() {
+  if (!canOperate.process(selectedOrder.value)) {
+    ElMessage.warning('当前工单不允许处理')
     return
   }
-  if (!modalProcessData.value.faultReason) {
-    alert('请填写故障原因')
+  actionTitle.value = '处理工单'
+  actionFormData.solution = ''
+  actionDialogVisible.value = true
+}
+
+async function submitAction() {
+  if (!actionFormData.solution.trim()) {
+    ElMessage.warning('请填写说明')
     return
   }
-  if (!modalProcessData.value.handler) {
-    alert('请填写处理人员')
+  submitting.value = true
+  try {
+    const data = {
+      orderId: selectedOrder.value.orderId,
+      handleResult: 'solved',
+      solution: actionFormData.solution
+    }
+    if (actionTitle.value === '核查工单') {
+      await verifyWorkOrder(data)
+    } else {
+      await processWorkOrder(data)
+    }
+    ElMessage.success(actionTitle.value + '成功')
+    actionDialogVisible.value = false
+    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: '' })
+
+function handleComplete() {
+  if (!canOperate.complete(selectedOrder.value)) {
+    ElMessage.warning('当前工单不允许提交验收')
     return
   }
-  if (!modalProcessData.value.contact) {
-    alert('请填写联系方式')
+  completeForm.result = ''
+  completeDialogVisible.value = true
+}
+
+async function submitComplete() {
+  if (!completeForm.result.trim()) {
+    ElMessage.warning('请填写验收意见')
     return
   }
-
-  const order = currentOrder.value
-  order.status = 'completed'
-  order.faultType = modalProcessData.value.faultType
-  order.faultReason = modalProcessData.value.faultReason
-  order.processMeasure = modalProcessData.value.processMeasure
-  order.handler = modalProcessData.value.handler
-  order.contact = modalProcessData.value.contact
-  order.processTime = modalProcessData.value.processTime.replace('T', ' ')
-  order.completeTime = new Date().toLocaleString()
-  order.attachments = [...modalUploadedFiles.value]
-
-  alert(`工单 ${order.orderNo} 处理完成!`)
-  closeProcessModal()
-  currentProcessingOrder.value = null
-  selectedCompletedOrder.value = order
+  submitting.value = true
+  try {
+    await completeWorkOrder(selectedOrder.value.orderId, completeForm.result)
+    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 cancelProcess = () => {
-  currentProcessingOrder.value = null
+// ========== 验收通过/结案(参考排水/燃气工单) ==========
+const closeDialogVisible = ref(false)
+const closeForm = reactive({ result: '' })
+
+function handleClose() {
+  if (!canOperate.close(selectedOrder.value)) {
+    ElMessage.warning('当前工单不允许验收通过')
+    return
+  }
+  closeForm.result = ''
+  closeDialogVisible.value = true
 }
 
-// 查看工单详情
-const viewOrderDetail = (order) => {
-  if (order.status === 'completed') {
-    selectedCompletedOrder.value = order
-    currentProcessingOrder.value = null
-  } else {
-    alert(`工单状态:${getStatusText(order.status)}`)
+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 }
 }
 
-// 搜索
-const searchOrders = () => {
-  currentPage.value = 1
+// ========== 取消工单 ==========
+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(() => {
-  const pendingOrder = orders.value.find(o => o.status === 'pending')
-  if (pendingOrder) {
-    // 不自动开始处理,只是展示
-  }
+  loadUsers()
+  handleSearch()
 })
 </script>
 
-<style scoped>
-* { margin: 0; padding: 0; box-sizing: border-box; }
-
-.prototype-container {
-  max-width: 1600px;
-  margin: 0 auto;
-  background: #f0f4f8;
-  border-radius: 28px;
-  box-shadow: 0 20px 35px -12px rgba(0, 0, 0, 0.15);
-  overflow: hidden;
-  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
-}
+<style scoped lang="scss">
+.order-container {
+  padding: 0 16px 16px;
 
-/* 头部 */
-.header {
-  background: #0a2b3c;
-  padding: 16px 28px;
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  flex-wrap: wrap;
-  gap: 16px;
-  border-bottom: 3px solid #e67e22;
-}
+  .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;
+  }
 
-.logo h2 { color: white; font-weight: 600; font-size: 1.3rem; }
-.logo p { color: #bdd8e8; font-size: 0.7rem; }
-
-.header-stats { display: flex; gap: 12px; }
-.stat-card { background: rgba(255,255,255,0.15); padding: 6px 14px; border-radius: 40px; text-align: center; color: white; }
-.stat-card.pending { background: #f39c12; }
-.stat-card.processing { background: #3498db; }
-.stat-card.completed { background: #27ae60; }
-.stat-value { font-weight: 700; font-size: 1.2rem; margin-right: 6px; }
-.stat-label { font-size: 0.65rem; opacity: 0.9; }
-
-/* 布局 */
-.main-layout { display: flex; flex-wrap: wrap; }
-.left-panel { width: 500px; background: #f9fbfd; padding: 20px; }
-.right-panel { flex: 1; background: #ffffff; padding: 20px; min-width: 480px; }
-
-/* 卡片 */
-.card { background: white; border-radius: 20px; border: 1px solid #e6edf3; margin-bottom: 20px; overflow: hidden; }
-.card-header { background: #f9fafc; padding: 14px 18px; border-bottom: 1px solid #eef2f6; font-weight: 700; font-size: 0.85rem; color: #1d5a78; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px; }
-.card-body { padding: 16px; }
-
-/* 筛选栏 */
-.filter-bar { display: flex; gap: 10px; align-items: center; }
-.filter-select, .search-input { padding: 6px 12px; border-radius: 20px; border: 1px solid #cbdde6; font-size: 0.7rem; }
-.search-input { width: 150px; }
-.btn-primary, .btn-outline, .btn-sm { padding: 5px 12px; border-radius: 30px; font-size: 0.7rem; cursor: pointer; }
-.btn-primary { background: #e67e22; border: none; color: white; }
-.btn-primary:hover { background: #d35400; }
-.btn-outline { background: white; border: 1px solid #e67e22; color: #e67e22; }
-
-/* 工单列表 */
-.order-list { max-height: 550px; overflow-y: auto; }
-.order-item { background: white; border-radius: 16px; padding: 14px; margin-bottom: 12px; border: 1px solid #eef2f6; transition: 0.2s; }
-.order-item.pending { border-left: 4px solid #f39c12; }
-.order-item.processing { border-left: 4px solid #3498db; background: #f0f8ff; }
-.order-item.completed { border-left: 4px solid #27ae60; background: #f0fff4; }
-.order-header { display: flex; justify-content: space-between; margin-bottom: 8px; }
-.order-id { display: flex; gap: 10px; align-items: center; }
-.order-no { font-family: monospace; font-weight: 700; font-size: 0.8rem; color: #e67e22; }
-.order-type { font-size: 0.6rem; padding: 2px 8px; border-radius: 20px; }
-.type-urgent { background: #fee2e2; color: #e74c3c; }
-.type-normal { background: #e3f2fd; color: #3498db; }
-.order-status { font-size: 0.65rem; padding: 2px 10px; border-radius: 20px; }
-.status-pending { background: #fef3c7; color: #f39c12; }
-.status-processing { background: #e3f2fd; color: #3498db; }
-.status-completed { background: #d5f5e3; color: #27ae60; }
-.order-device, .order-desc, .order-time { font-size: 0.7rem; margin-bottom: 6px; }
-.label { color: #6f8ea8; margin-right: 4px; }
-.value { color: #2c5a72; }
-.order-actions { display: flex; gap: 8px; margin-top: 10px; }
-.btn-start { background: #27ae60; color: white; border: none; padding: 4px 12px; border-radius: 20px; font-size: 0.65rem; cursor: pointer; }
-.btn-process { background: #3498db; color: white; border: none; padding: 4px 12px; border-radius: 20px; font-size: 0.65rem; cursor: pointer; }
-.btn-view { background: #95a5a6; color: white; border: none; padding: 4px 12px; border-radius: 20px; font-size: 0.65rem; cursor: pointer; }
-.empty-order { text-align: center; padding: 40px; color: #8aaec2; }
-
-/* 分页 */
-.pagination { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }
-.page-btn { background: white; border: 1px solid #cbdde6; padding: 4px 10px; border-radius: 20px; font-size: 0.7rem; cursor: pointer; }
-.page-btn.active { background: #e67e22; color: white; border-color: #e67e22; }
-
-/* 处理面板 */
-.process-info { background: #f8fafc; border-radius: 16px; padding: 12px; margin-bottom: 20px; }
-.process-info .info-row { font-size: 0.75rem; margin-bottom: 6px; }
-.process-info .label { font-weight: 600; width: 80px; display: inline-block; }
-.process-form { margin-top: 8px; }
-.form-title { font-weight: 600; font-size: 0.8rem; color: #2c5a72; margin-bottom: 16px; padding-bottom: 8px; border-bottom: 1px solid #eef2f6; }
-.form-row { display: flex; gap: 12px; margin-bottom: 12px; }
-.form-group { margin-bottom: 12px; }
-.form-group.half { flex: 1; }
-.form-label { font-size: 0.7rem; font-weight: 600; margin-bottom: 4px; display: block; color: #2c5a72; }
-.form-input, .form-select, .form-textarea { width: 100%; padding: 8px 12px; border-radius: 12px; border: 1px solid #cbdde6; font-size: 0.75rem; }
-.form-actions { display: flex; gap: 12px; justify-content: flex-end; margin-top: 20px; }
-
-/* 上传区域 */
-.upload-area { border: 1px dashed #cbdde6; border-radius: 12px; padding: 12px; background: #fafcfd; }
-.upload-btn { background: #3498db; color: white; padding: 6px 14px; border-radius: 30px; font-size: 0.7rem; cursor: pointer; display: inline-block; }
-.file-list { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 12px; }
-.file-item { display: flex; flex-direction: column; align-items: center; width: 80px; background: #f8fafc; border-radius: 8px; padding: 8px; position: relative; }
-.file-preview { width: 60px; height: 60px; object-fit: cover; border-radius: 8px; margin-bottom: 4px; }
-.file-name { font-size: 0.6rem; color: #2c5a72; text-align: center; word-break: break-all; }
-.remove-file { position: absolute; top: -6px; right: -6px; background: #e74c3c; color: white; border: none; border-radius: 50%; width: 18px; height: 18px; font-size: 10px; cursor: pointer; }
-.upload-hint { font-size: 0.6rem; color: #8aaec2; margin-top: 8px; }
-.file-tag { background: #eef2fa; padding: 4px 8px; border-radius: 20px; font-size: 0.65rem; display: inline-flex; align-items: center; gap: 6px; }
-
-/* 已完成工单详情 */
-.completed-info { padding: 8px; }
-.info-group { margin-bottom: 20px; }
-.group-title { font-weight: 600; font-size: 0.75rem; color: #2c5a72; margin-bottom: 10px; padding-bottom: 4px; border-bottom: 1px solid #eef2f6; }
-.info-row { font-size: 0.7rem; margin-bottom: 8px; }
-.info-row .label { font-weight: 600; width: 80px; display: inline-block; color: #6f8ea8; }
-.attachment-list { display: flex; flex-wrap: wrap; gap: 12px; }
-.attachment-item { display: flex; flex-direction: column; align-items: center; width: 80px; }
-.attachment-img { width: 70px; height: 70px; object-fit: cover; border-radius: 8px; border: 1px solid #eef2f6; }
-.attachment-name { font-size: 0.6rem; color: #6f8ea8; margin-top: 4px; text-align: center; }
-
-.empty-process { text-align: center; padding: 60px 20px; color: #8aaec2; }
-.empty-icon { font-size: 3rem; margin-bottom: 12px; opacity: 0.5; }
-.empty-hint { font-size: 0.65rem; margin-top: 8px; }
-.order-badge { font-size: 0.65rem; background: #eef2fa; padding: 2px 10px; border-radius: 20px; font-weight: normal; }
-
-/* 模态框 */
-.modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 1000; }
-.modal-content { background: white; border-radius: 24px; width: 550px; max-width: 90%; padding: 24px; max-height: 85vh; overflow-y: auto; }
-.order-summary { background: #f8fafc; border-radius: 12px; padding: 12px; margin-bottom: 16px; font-size: 0.7rem; }
-.process-form-modal { margin-top: 8px; }
-</style>
+  .timeline-scroll {
+    max-height: calc(100vh - 340px);
+    overflow-y: auto;
+    padding-right: 8px;
+  }
+
+  .device-section, .image-section, .action-section {
+    margin-top: 24px;
+    padding-top: 24px;
+    border-top: 1px solid #e4e7ed;
+
+    .section-title {
+      font-size: 15px;
+      font-weight: bold;
+      margin-bottom: 16px;
+      color: #303133;
+    }
+  }
+
+  .image-list {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 12px;
+  }
+
+  .action-buttons {
+    display: flex;
+    gap: 12px;
+    flex-wrap: wrap;
+
+    .el-button {
+      min-width: 100px;
+    }
+  }
+
+  .timeline-content {
+    .timestamp {
+      font-size: 12px;
+      color: #909399;
+      margin-bottom: 6px;
+    }
+
+    .operator-info {
+      font-size: 14px;
+      margin-bottom: 8px;
+
+      .operator-name {
+        font-weight: bold;
+        color: #303133;
+      }
+
+      .oper-type {
+        color: #909399;
+      }
+    }
+
+    .oper-desc {
+      font-size: 14px;
+      color: #606266;
+    }
+  }
+}
+</style>

+ 122 - 0
src/views/subSystem/pipeNetwork/pworkOrder/workOrderDict.js

@@ -0,0 +1,122 @@
+// ========== 工单业务公共字典 ==========
+// 数据源:/api/work-order/* (BusinessWorkOrderController)
+
+// 工单类型:1-故障维修 2-日常巡检 3-设备保养
+export const ORDER_TYPE_MAP = {
+  1: { text: '故障维修', type: 'danger' },
+  2: { text: '日常巡检', type: 'primary' },
+  3: { text: '设备保养', type: 'warning' }
+}
+
+// 工单优先级:1-紧急 2-一般 3-低
+export const ORDER_LEVEL_MAP = {
+  1: { text: '紧急', type: 'danger' },
+  2: { text: '一般', type: 'warning' },
+  3: { text: '低', type: 'info' }
+}
+
+// 工单状态:1-待派单 2-待接单 3-已接单 4-处理中 5-待验收 6-已结案 7-已驳回 8-延期审核 9-已延期
+export const ORDER_STATUS_MAP = {
+  1: { text: '待派单', type: 'danger' },
+  2: { text: '待接单', type: 'warning' },
+  3: { text: '已接单', type: 'primary' },
+  4: { text: '处理中', type: 'warning' },
+  5: { text: '待验收', type: 'primary' },
+  6: { text: '已结案', type: 'success' },
+  7: { text: '已驳回', type: 'info' },
+  8: { text: '延期审核', type: 'warning' },
+  9: { text: '已延期', type: 'warning' }
+}
+
+// 业务简化为三态分组(统计卡用)
+export const ORDER_STATUS_GROUP = {
+  pending: [1, 2, 3],       // 待处理:待派单/待接单/已接单
+  processing: [4, 8, 9],    // 处理中:处理中/延期审核/已延期
+  completed: [5, 6],        // 已完成:待验收/已结案
+  rejected: [7]             // 已驳回
+}
+
+// 工单操作日志类型(后端 WorkOrderLog.operType 数字枚举)
+export const ORDER_LOG_TYPE_MAP = {
+  1: { text: '创建工单', type: 'primary' },
+  2: { text: '派单', type: 'warning' },
+  3: { text: '接单', type: 'success' },
+  4: { text: '开始维修', type: 'warning' },
+  5: { text: '提交验收', type: 'primary' },
+  6: { text: '验收通过', type: 'success' },
+  7: { text: '驳回工单', type: 'info' },
+  8: { text: '申请延期', type: 'warning' },
+  9: { text: '延期通过', type: 'warning' }
+}
+
+// ========== 辅助函数 ==========
+
+/** 工单状态文本 */
+export function orderStatusText(status) {
+  return ORDER_STATUS_MAP[status]?.text || '未知'
+}
+
+/** 工单状态 Tag type */
+export function orderStatusType(status) {
+  return ORDER_STATUS_MAP[status]?.type || 'info'
+}
+
+/** 工单类型文本 */
+export function orderTypeText(type) {
+  return ORDER_TYPE_MAP[type]?.text || '未知'
+}
+
+/** 工单类型 Tag type */
+export function orderTypeTagType(type) {
+  return ORDER_TYPE_MAP[type]?.type || 'info'
+}
+
+/** 优先级文本 */
+export function orderLevelText(level) {
+  return ORDER_LEVEL_MAP[level]?.text || '-'
+}
+
+/** 优先级 Tag type */
+export function orderLevelType(level) {
+  return ORDER_LEVEL_MAP[level]?.type || 'info'
+}
+
+/** 是否属于某状态分组 */
+export function inStatusGroup(order, groupName) {
+  const list = ORDER_STATUS_GROUP[groupName] || []
+  return list.includes(Number(order?.orderStatus))
+}
+
+/** 是否允许该操作(状态机与后端 BusinessWorkOrderServiceImpl 对齐)
+ * 流程:1待派单 →2待接单 →3已接单 →4处理中 →5待验收 →6已结案
+ * 动作:派单(1) → 核查(2/3) → 处理(3/4) → 提交验收(4) → 验收通过(5);任意非终态可取消(→7)
+ */
+export const canOperate = {
+  /** 待派单可派单 */
+  dispatch: (o) => Number(o?.orderStatus) === 1,
+  /** 待接单/已接单可核查 */
+  verify: (o) => [2, 3].includes(Number(o?.orderStatus)),
+  /** 已接单/处理中可处理 */
+  process: (o) => [3, 4].includes(Number(o?.orderStatus)),
+  /** 处理中可提交验收 */
+  complete: (o) => Number(o?.orderStatus) === 4,
+  /** 待验收可验收通过/结案 */
+  close: (o) => Number(o?.orderStatus) === 5,
+  /** 可取消:未结案/未驳回 */
+  cancel: (o) => ![6, 7].includes(Number(o?.orderStatus))
+}
+
+/** 工单操作日志文本 */
+export function orderLogText(type) {
+  return ORDER_LOG_TYPE_MAP[type]?.text || type || '操作'
+}
+
+/** 工单操作日志 Tag type */
+export function orderLogType(type) {
+  return ORDER_LOG_TYPE_MAP[type]?.type || 'info'
+}
+
+/** 时间兜底显示 */
+export function timeText(t) {
+  return t || '-'
+}