Explorar o código

fix:新增排水和燃气工单

null hai 1 mes
pai
achega
75c7621542

+ 14 - 0
src/api/pipeNetwork/basic.js

@@ -191,6 +191,20 @@ export function deleteSimEvent(id) { return request({ url: '/api/drill/sim/event
 export function saveSimRole(data) { return request({ url: '/api/drill/sim/role', method: 'post', data }) }
 export function saveSimTask(data) { return request({ url: '/api/drill/sim/task', method: 'post', data }) }
 export function getDrillStats() { return request({ url: '/api/drill/stats', method: 'get' }) }
+export function sendDrillAnalysis(data) { return request({ url: '/api/drill/practical/analysis', method: 'post', data }) }
+// 实战演练记录
+export function getDrillRecord(planId) { return request({ url: '/api/drill/practical/record/' + planId, method: 'get' }) }
+export function saveDrillRecord(data) { return request({ url: '/api/drill/practical/record', method: 'post', data }) }
+export function updateDrillRecord(data) { return request({ url: '/api/drill/practical/record', method: 'put', data }) }
+// 演练监测信息
+export function getDrillMonitoring(recordId) { return request({ url: '/api/drill/practical/monitoring/' + recordId, method: 'get' }) }
+export function saveDrillMonitoring(data) { return request({ url: '/api/drill/practical/monitoring', method: 'post', data }) }
+export function deleteDrillMonitoring(id) { return request({ url: '/api/drill/practical/monitoring/' + id, method: 'delete' }) }
+// 演练分析结果
+export function getDrillAnalysis(planId) { return request({ url: '/api/drill/practical/analysis/' + planId, method: 'get' }) }
+export function exportDrillReport(planId) { return request({ url: '/api/drill/practical/report/' + planId, method: 'get', responseType: 'blob' }) }
+// 更新演练计划状态
+export function updatePlanStatus(planId, status) { return request({ url: '/api/drill/practical/plan/status', method: 'put', params: { planId, status } }) }
 export function getRepairRecordById(id) {
   return request({ url: '/api/repair-record/getById/' + id, method: 'get' })
 }

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

@@ -0,0 +1,62 @@
+import request from '@/utils/request'
+
+/** 查询工单列表 */
+export function listWorkOrder(queryParams, pageNum = 1, pageSize = 10) {
+  return request({
+    url: '/api/work-order/list',
+    method: 'get',
+    params: { ...queryParams, pageNum, pageSize }
+  })
+}
+
+/** 查询工单详情(含操作日志) */
+export function getWorkOrderDetail(orderId) {
+  return request({
+    url: '/api/work-order/' + orderId,
+    method: 'get'
+  })
+}
+
+/** 派单操作 */
+export function dispatchWorkOrder(data) {
+  return request({
+    url: '/api/work-order/dispatch',
+    method: 'post',
+    data
+  })
+}
+
+/** 核查操作 */
+export function verifyWorkOrder(data) {
+  return request({
+    url: '/api/work-order/verify',
+    method: 'put',
+    data
+  })
+}
+
+/** 处理操作 */
+export function processWorkOrder(data) {
+  return request({
+    url: '/api/work-order/process',
+    method: 'put',
+    data
+  })
+}
+
+/** 完成/提交验收 */
+export function completeWorkOrder(orderId, result) {
+  return request({
+    url: '/api/work-order/complete',
+    method: 'put',
+    params: { orderId, result }
+  })
+}
+
+/** 取消工单 */
+export function cancelWorkOrder(orderId) {
+  return request({
+    url: '/api/work-order/' + orderId,
+    method: 'delete'
+  })
+}

+ 907 - 0
src/views/subSystem/basic/BusinessWorkOrder.vue

@@ -0,0 +1,907 @@
+<!-- 业务工单管理 -->
+<template>
+  <div class="business-work-order">
+    <!-- 顶部搜索区 -->
+    <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 label="故障维修" :value="1" />
+              <el-option label="日常巡检" :value="2" />
+              <el-option label="设备保养" :value="3" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="优先级">
+            <el-select v-model="queryParams.orderLevel" placeholder="全部优先级" clearable style="width: 120px">
+              <el-option label="紧急" :value="1" />
+              <el-option label="一般" :value="2" />
+              <el-option label="低" :value="3" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="状态">
+            <el-select v-model="queryParams.orderStatus" placeholder="全部状态" clearable style="width: 120px">
+              <el-option label="待派单" :value="1" />
+              <el-option label="待接单" :value="2" />
+              <el-option label="已接单" :value="3" />
+              <el-option label="处理中" :value="4" />
+              <el-option label="已提交验收" :value="5" />
+              <el-option label="已结案" :value="6" />
+              <el-option label="已驳回" :value="7" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="关键词">
+            <el-input 
+              v-model="queryParams.keyword" 
+              placeholder="工单编号/设备编码" 
+              clearable 
+              style="width: 200px"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" @click="handleSearch">查询</el-button>
+            <el-button @click="handleReset">重置</el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </el-card>
+
+    <!-- 主容器 -->
+    <el-row :gutter="16" style="height: calc(100vh - 200px)">
+      <!-- 左侧:工单列表 -->
+      <el-col :span="8" style="height: 100%">
+        <el-card class="box-card" shadow="always">
+          <template #header>
+            <div class="card-header">
+              <span>工单列表</span>
+            </div>
+          </template>
+          
+          <!-- 工单列表 -->
+          <el-scrollbar style="flex: 1">
+            <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 
+                    v-if="order.orderStatus === 1" 
+                    type="warning" 
+                    size="small"
+                    effect="light"
+                    class="status-badge"
+                  >待派单</el-tag>
+                  <el-tag 
+                    v-else-if="order.orderStatus === 2" 
+                    type="info" 
+                    size="small"
+                    effect="light"
+                    class="status-badge"
+                  >待接单</el-tag>
+                  <el-tag 
+                    v-else-if="order.orderStatus === 3" 
+                    type="success" 
+                    size="small"
+                    effect="light"
+                    class="status-badge"
+                  >已接单</el-tag>
+                  <el-tag 
+                    v-else-if="order.orderStatus === 4" 
+                    type="danger" 
+                    size="small"
+                    effect="light"
+                    class="status-badge"
+                  >处理中</el-tag>
+                  <el-tag 
+                    v-else-if="order.orderStatus === 5" 
+                    type="success" 
+                    size="small"
+                    effect="light"
+                    class="status-badge"
+                  >已提交验收</el-tag>
+                  <el-tag 
+                    v-else-if="order.orderStatus === 6" 
+                    type="info" 
+                    size="small"
+                    effect="light"
+                    class="status-badge"
+                  >已结案</el-tag>
+                  <el-tag 
+                    v-else-if="order.orderStatus === 7" 
+                    type="danger" 
+                    size="small"
+                    effect="light"
+                    class="status-badge"
+                  >已驳回</el-tag>
+                </div>
+                <div class="order-body">
+                  <div class="order-tags">
+                    <el-tag :type="getLevelTag(order.orderLevel)" size="small" effect="plain">{{ getLevelLabel(order.orderLevel) }}</el-tag>
+                    <el-tag :type="getTypeTag(order.orderType)" size="small" effect="plain">{{ getTypeLabel(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="create-time">创建:{{ formatDate(order.createTime) }}</div>
+                </div>
+              </div>
+              <el-empty v-if="tableData.length === 0" description="暂无数据" :image-size="100"></el-empty>
+            </div>
+          </el-scrollbar>
+          
+          <!-- 分页 -->
+          <div class="pagination-wrapper">
+            <el-pagination
+              v-model:current-page="pageNum" 
+              v-model:page-size="pageSize"
+              :total="total"
+              layout="total, prev, pager, next"
+              :page-sizes="[10, 20, 50, 100]"
+              @size-change="handleSizeChange"
+              @current-change="handlePageChange"
+              class="pagination"
+            />
+          </div>
+        </el-card>
+      </el-col>
+      
+      <!-- 右侧:工单详情 -->
+      <el-col :span="16" style="height: 100%">
+        <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">
+            <!-- 标签页 -->
+            <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 
+                      v-if="selectedOrder.orderStatus === 1" 
+                      type="warning"
+                      size="small"
+                    >待派单</el-tag>
+                    <el-tag 
+                      v-else-if="selectedOrder.orderStatus === 2" 
+                      type="info"
+                      size="small"
+                    >待接单</el-tag>
+                    <el-tag 
+                      v-else-if="selectedOrder.orderStatus === 3" 
+                      type="success"
+                      size="small"
+                    >已接单</el-tag>
+                    <el-tag 
+                      v-else-if="selectedOrder.orderStatus === 4" 
+                      type="danger"
+                      size="small"
+                    >处理中</el-tag>
+                    <el-tag 
+                      v-else-if="selectedOrder.orderStatus === 5" 
+                      type="success"
+                      size="small"
+                    >已提交验收</el-tag>
+                    <el-tag 
+                      v-else-if="selectedOrder.orderStatus === 6" 
+                      type="info"
+                      size="small"
+                    >已结案</el-tag>
+                    <el-tag 
+                      v-else-if="selectedOrder.orderStatus === 7" 
+                      type="danger"
+                      size="small"
+                    >已驳回</el-tag>
+                  </el-descriptions-item>
+                  <el-descriptions-item label="关联案件编号">{{ selectedOrder.caseNo || '-' }}</el-descriptions-item>
+                  <el-descriptions-item label="设备编号">{{ selectedOrder.deviceCode || '-' }}</el-descriptions-item>
+                  <el-descriptions-item label="优先级">
+                    <el-tag 
+                      v-if="selectedOrder.orderLevel === 1" 
+                      type="danger"
+                      size="small"
+                    >紧急</el-tag>
+                    <el-tag 
+                      v-else-if="selectedOrder.orderLevel === 2" 
+                      type="warning"
+                      size="small"
+                    >一般</el-tag>
+                    <el-tag 
+                      v-else 
+                      type="info"
+                      size="small"
+                    >低</el-tag>
+                  </el-descriptions-item>
+                  <el-descriptions-item label="工单类型">{{ getOrderTypeText(selectedOrder.orderType) }}</el-descriptions-item>
+                  <el-descriptions-item label="问题描述" :span="2">{{ selectedOrder.orderDesc }}</el-descriptions-item>
+                  <el-descriptions-item label="创建时间">{{ formatDate(selectedOrder.createTime) }}</el-descriptions-item>
+                  <el-descriptions-item label="更新时间">{{ formatDate(selectedOrder.updateTime) }}</el-descriptions-item>
+                  <el-descriptions-item label="运维状态">在用</el-descriptions-item>
+                  <el-descriptions-item label="在线状态">在线</el-descriptions-item>
+                  <el-descriptions-item label="报警状态">正常</el-descriptions-item>
+                  <el-descriptions-item label="处置结果">{{ selectedOrder.repairResult || '-' }}</el-descriptions-item>
+                </el-descriptions>
+                
+                <!-- 工单图片 -->
+                <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="action-section">
+                  <h4 class="section-title">工单操作</h4>
+                  <div class="action-buttons">
+                    <el-button 
+                      v-if="canDispatch()" 
+                      type="primary" 
+                      icon="Download"
+                      @click="handleDispatch"
+                    >下派任务</el-button>
+                    <el-button 
+                      v-if="canVerify()" 
+                      type="warning" 
+                      icon="Check"
+                      @click="handleVerify"
+                    >核查</el-button>
+                    <el-button 
+                      v-if="canProcess()" 
+                      type="success" 
+                      icon="Edit"
+                      @click="handleProcess"
+                    >处理</el-button>
+                    <el-button 
+                      v-if="canComplete()" 
+                      type="danger" 
+                      icon="Checked"
+                      @click="handleComplete"
+                    >提交验收</el-button>
+                    <el-button 
+                      v-if="canReject()" 
+                      type="info" 
+                      icon="Close"
+                      @click="handleReject"
+                    >驳回</el-button>
+                  </div>
+                </div>
+              </el-tab-pane>
+              
+              <!-- 处理历史 -->
+              <el-tab-pane label="处理历史" name="history">
+                <el-timeline v-if="currentOrderLogs && currentOrderLogs.length > 0">
+                  <el-timeline-item 
+                    v-for="log in currentOrderLogs" 
+                    :key="log.logId" 
+                    :timestamp="formatDate(log.operTime)"
+                    placement="top"
+                    :type="getLogType(log.operType)"
+                    :size="'large'"
+                  >
+                    <el-card shadow="hover">
+                      <div class="timeline-content">
+                        <div class="operator-info">
+                          <span class="operator-name">{{ log.operUserName }}</span>
+                          <span class="oper-type">· {{ getOperTypeLabel(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"></el-empty>
+              </el-tab-pane>
+            </el-tabs>
+          </div>
+          
+          <el-empty v-else description="请从左侧选择工单" :image-size="150"></el-empty>
+        </el-card>
+      </el-col>
+    </el-row>
+    
+    <!-- 派单弹窗 -->
+    <el-dialog v-model="dispatchDialogVisible" title="下派任务 - 选择接单人" width="750px">
+      <div style="display: flex; gap: 16px; min-height: 350px;">
+        <!-- 左侧:部门树 -->
+        <div style="width: 280px; border-right: 1px solid #e4e7ed; padding-right: 12px;">
+          <el-input 
+            v-model="deptFilterText" 
+            placeholder="筛选部门" 
+            clearable 
+            size="small" 
+            style="margin-bottom: 8px"
+          />
+          <el-tree
+            ref="deptTreeRef"
+            :data="deptTreeData"
+            :props="{ label: 'deptName', children: 'children' }"
+            :filter-node-method="filterDeptNode"
+            node-key="deptId"
+            highlight-current
+            @node-click="handleDeptClick"
+            style="font-size: 14px"
+          />
+        </div>
+        <!-- 右侧:用户列表 -->
+        <div style="flex: 1; min-width: 0;">
+          <el-table 
+            :data="userList" 
+            border 
+            size="small" 
+            max-height="300" 
+            ref="userTableRef"
+            row-key="userId"
+            @selection-change="handleSelectionChange"
+            @row-click="handleRowClick"
+          >
+            <el-table-column type="selection" width="40" align="center" :reserve-selection="true" />
+            <el-table-column prop="userName" label="用户名" width="100" />
+            <el-table-column prop="nickName" label="昵称" width="100" />
+            <el-table-column prop="dept.deptName" label="部门" show-overflow-tooltip />
+          </el-table>
+          <div v-if="userList.length === 0" style="text-align: center; color: #909399; padding: 40px 0;">请先在左侧选择部门</div>
+        </div>
+      </div>
+      <el-form :model="dispatchForm" label-width="80px" style="margin-top: 16px">
+        <el-form-item label="备注"><el-input v-model="dispatchForm.remarks" placeholder="请输入派单备注" /></el-form-item>
+      </el-form>
+      <template #footer>
+      <span style="float: left; line-height: 32px; color: #606266;">
+        已选人员:<span v-if="selectedUserNames.length > 0" style="color: #409eff; font-weight: bold;">{{ selectedUserNames.join(', ') }}</span>
+        <span v-else style="color: #909399">未选择</span>
+        (<span v-if="dispatchForm.handlerIds && dispatchForm.handlerIds.length > 0">{{ dispatchForm.handlerIds.length }}</span><span v-else>0</span>)
+      </span>
+      <el-button @click="dispatchDialogVisible = false">取消</el-button>
+      <el-button type="primary" @click="submitDispatch">确认</el-button>
+    </template>
+    </el-dialog>
+    
+    <!-- 核查/处理弹窗 -->
+    <el-dialog v-model="actionDialogVisible" :title="actionTitle" width="600px">
+      <el-input v-model="actionFormData.solution" type="textarea" :rows="6" placeholder="请输入说明文字" />
+      <template #footer>
+        <el-button @click="actionDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitAction">确定</el-button>
+      </template>
+    </el-dialog>
+    
+    <!-- 完成/验收弹窗 -->
+    <el-dialog v-model="completeDialogVisible" title="提交验收" width="500px">
+      <el-form label-width="80px">
+        <el-form-item label="验收意见">
+          <el-input v-model="completeForm.result" type="textarea" :rows="4" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="completeDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitComplete">提交</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="BusinessWorkOrder">
+import { ElMessage } from 'element-plus'
+import ImagePreview from '@/components/ImagePreview'
+import { listWorkOrder, getWorkOrderDetail, dispatchWorkOrder, verifyWorkOrder, processWorkOrder, completeWorkOrder } from '@/api/work/order'
+import { listDept } from '@/api/system/dept'
+import { listUser } from '@/api/system/user'
+import { handleTree } from '@/utils/ruoyi'
+
+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,
+  keyword: '',
+  // 燃气工单:只查询燃气类型设备工单
+  equipmentType: 'gas'
+})
+
+// 选中的工单
+const selectedOrderId = ref(null)
+const selectedOrder = ref({})
+const activeTab = ref('info')
+const currentOrderLogs = ref([])
+
+// 工单图片列表(多个用逗号分隔)
+const workImageList = computed(() => {
+  if (!selectedOrder.value.workImage) return []
+  return selectedOrder.value.workImage.split(',').filter(img => img && img.trim())
+})
+
+// 派单相关
+const dispatchDialogVisible = ref(false)
+const dispatchForm = reactive({ handlerIds: [], remarks: '' })
+const deptTreeRef = ref(null)
+const userTableRef = ref(null)
+const deptTreeData = ref([])
+const deptFilterText = ref('')
+const userList = ref([])
+const selectedUserList = ref([]) // 存储选中的用户行数据
+
+// 部门筛选
+watch(deptFilterText, (val) => { deptTreeRef.value && deptTreeRef.value.filter(val) })
+function filterDeptNode(value, data) {
+  if (!value) return true
+  return data.deptName.includes(value)
+}
+
+// 加载部门树
+async function loadDeptTree() {
+  if (deptTreeData.value.length > 0) return
+  try {
+    const res = await listDept()
+    deptTreeData.value = handleTree(res.data || [], 'deptId')
+  } catch (e) { console.error('加载部门失败', 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.error('加载用户失败', e) }
+}
+
+// 选择变化时触发
+function handleSelectionChange(selection) {
+  selectedUserList.value = selection
+  dispatchForm.handlerIds = selection.map(item => item.userId)
+}
+
+// 点击行切换选中状态
+function handleRowClick(row) {
+  userTableRef.value && userTableRef.value.toggleRowSelection(row)
+}
+
+// 获取已选人员昵称(用于显示)
+const selectedUserNames = computed(() => {
+  return selectedUserList.value.map(item => item.nickName || item.userName)
+})
+
+/** 查询工单列表 */
+async function handleSearch() {
+  listLoading.value = true
+  try {
+    const res = await listWorkOrder(queryParams, pageNum.value, pageSize.value)
+    if (res.code === 200) {
+      tableData.value = res.data.records || []
+      total.value = res.data.total || 0
+    }
+  } catch (e) {
+    ElMessage.error('查询失败')
+  } finally {
+    listLoading.value = false
+  }
+}
+
+/** 重置搜索 */
+function handleReset() {
+  queryFormRef.value && queryFormRef.value.resetFields()
+  queryParams.orderType = null
+  queryParams.orderLevel = null
+  queryParams.keyword = ''
+  pageNum.value = 1
+  selectedOrderId.value = null
+  selectedOrder.value = {}
+  handleSearch()
+}
+
+/** 选择工单 */
+async function handleSelectOrder(order) {
+  selectedOrderId.value = order.orderId
+  selectedOrder.value = { ...order } // 克隆对象确保更新
+  activeTab.value = 'info'
+  currentOrderLogs.value = []
+  
+  // 加载工单详情和处理历史
+  await loadOrderDetail(order.orderId)
+}
+
+/** 分页变化 */
+function handleSizeChange() {
+  pageNum.value = 1
+  selectedOrderId.value = null
+  selectedOrder.value = {}
+  handleSearch()
+}
+
+function handlePageChange() {
+  selectedOrderId.value = null
+  selectedOrder.value = {}
+  handleSearch()
+}
+
+/** 派单、核查、处理、完成、驳回的判断函数(示例) */
+function canDispatch() { return selectedOrder.value.orderStatus === 1 }
+function canVerify() { return [2, 3].includes(selectedOrder.value.orderStatus) }
+function canProcess() { return [3, 4].includes(selectedOrder.value.orderStatus) }
+function canComplete() { return selectedOrder.value.orderStatus === 4 }
+function canReject() { return [1, 2].includes(selectedOrder.value.orderStatus) }
+
+function handleDispatch() {
+  if (!canDispatch()) {
+    ElMessage.warning('仅可对待派单工单进行派单操作')
+    return
+  }
+  Object.assign(dispatchForm, { handlerIds: [], remarks: '' })
+  selectedUserList.value = []
+  userList.value = []
+  loadDeptTree()
+  dispatchDialogVisible.value = true
+}
+
+async function submitDispatch() {
+  if (!dispatchForm.handlerIds || dispatchForm.handlerIds.length === 0) {
+    ElMessage.warning('请选择至少一个接单人')
+    return
+  }
+  try {
+    await dispatchWorkOrder({
+      orderId: selectedOrder.value.orderId,
+      handlerIds: dispatchForm.handlerIds, // 传递数组
+      remarks: dispatchForm.remarks
+    })
+    ElMessage.success('派单成功!已向 ' + dispatchForm.handlerIds.length + ' 人下发任务')
+    dispatchDialogVisible.value = false
+    handleSearch()
+  } catch (e) {
+    ElMessage.error('派单失败')
+  }
+}
+
+/** 核查、处理相关 */
+const actionDialogVisible = ref(false)
+const actionTitle = ref('')
+const actionFormData = reactive({ solution: '' })
+
+function handleVerify() {
+  if (![2, 3].includes(selectedOrder.value.orderStatus)) {
+    ElMessage.warning('当前工单不允许核查')
+    return
+  }
+  actionTitle.value = '核查工单'
+  actionFormData.solution = ''
+  actionDialogVisible.value = true
+}
+
+function handleProcess() {
+  if (![3, 4].includes(selectedOrder.value.orderStatus)) {
+    ElMessage.warning('当前工单不允许处理')
+    return
+  }
+  actionTitle.value = '处理工单'
+  actionFormData.solution = ''
+  actionDialogVisible.value = true
+}
+
+async function submitAction() {
+  if (!actionFormData.solution.trim()) {
+    ElMessage.warning('请填写说明')
+    return
+  }
+  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()
+  } catch (e) {
+    ElMessage.error('操作失败')
+  }
+}
+
+/** 完成/验收相关 */
+const completeDialogVisible = ref(false)
+const completeForm = reactive({ result: '' })
+
+function handleComplete() {
+  if (![4, 5].includes(selectedOrder.value.orderStatus)) {
+    ElMessage.warning('当前工单不允许提交验收')
+    return
+  }
+  completeForm.result = ''
+  completeDialogVisible.value = true
+}
+
+async function submitComplete() {
+  if (!completeForm.result.trim()) {
+    ElMessage.warning('请填写验收意见')
+    return
+  }
+  try {
+    await completeWorkOrder(selectedOrder.value.orderId, completeForm.result)
+    ElMessage.success('提交验收成功')
+    completeDialogVisible.value = false
+    handleSearch()
+  } catch (e) {
+    ElMessage.error('提交失败')
+  }
+}
+
+/** 驳回相关 */
+function handleReject() {
+  if (![1, 2].includes(selectedOrder.value.orderStatus)) {
+    ElMessage.warning('当前工单不允许驳回')
+    return
+  }
+  ElMessage.info('驳回功能待实现')
+  // TODO: 实现驳回逻辑
+}
+
+/** 辅助函数 */
+function getOrderTypeText(type) {
+  const map = { 1: '故障维修', 2: '日常巡检', 3: '设备保养' }
+  return map[type] || '-'
+}
+
+function getOperTypeLabel(type) {
+  const map = {
+    1: '创建工单', 2: '派单', 3: '接单', 4: '开始维修',
+    5: '提交验收', 6: '验收通过', 7: '驳回工单', 8: '申请延期'
+  }
+  return map[type] || '-'
+}
+
+function getLogType(type) {
+  const map = {
+    1: 'primary', 2: 'success', 3: 'warning', 4: 'danger',
+    5: 'success', 6: 'success', 7: 'danger', 8: 'warning'
+  }
+  return map[type] || ''
+}
+
+function formatDate(dateStr) {
+  if (!dateStr) return '-'
+  return new Date(dateStr).toLocaleString('zh-CN')
+}
+
+/** 优先级标签 */
+function getLevelLabel(level) {
+  const map = { 1: '紧急', 2: '一般', 3: '低' }
+  return map[level] || '未知'
+}
+function getLevelTag(level) {
+  const map = { 1: 'danger', 2: 'warning', 3: 'info' }
+  return map[level] || 'info'
+}
+
+/** 工单类型标签 */
+function getTypeLabel(type) {
+  const map = { 1: '故障维修', 2: '日常巡检', 3: '设备保养' }
+  return map[type] || '未知'
+}
+function getTypeTag(type) {
+  const map = { 1: 'danger', 2: 'primary', 3: 'success' }
+  return map[type] || 'info'
+}
+
+/** 加载工单详情和处理历史 */
+async function loadOrderDetail(orderId) {
+  try {
+    const res = await getWorkOrderDetail(orderId)
+    if (res.code === 200) {
+      selectedOrder.value = { ...selectedOrder.value, ...res.data.order }
+      currentOrderLogs.value = res.data.logs || []
+    }
+  } catch (e) {
+    ElMessage.error('加载工单详情失败')
+  }
+}
+
+onMounted(() => {
+  handleSearch()
+})
+</script>
+
+<style scoped lang="scss">
+.business-work-order {
+  padding: 0 16px 16px;
+  
+  .search-card {
+    margin-bottom: 16px;
+    
+    :deep(.el-card__body) {
+      padding: 16px;
+    }
+  }
+  
+  .search-form {
+    .el-form {
+      display: flex;
+      flex-wrap: wrap;
+      align-items: flex-start;
+      gap: 12px;
+      
+      .el-form-item {
+        margin-bottom: 0;
+        min-width: 150px;
+        
+        &:last-child {
+          margin-left: auto;
+        }
+      }
+    }
+  }
+  
+  .order-list {
+    overflow-y: auto;
+  }
+  
+  .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;
+    }
+  }
+  
+  .box-card {
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    
+    :deep(.el-card__body) {
+      flex: 1;
+      display: flex;
+      flex-direction: column;
+      overflow: hidden;
+    }
+  }
+  
+  .detail-tabs {
+    margin-top: 16px;
+  }
+  
+  .image-section {
+    margin-top: 24px;
+    padding-top: 24px;
+    border-top: 1px solid #e4e7ed;
+    
+    .image-list {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 12px;
+    }
+  }
+  
+  .action-section {
+    margin-top: 24px;
+    padding-top: 24px;
+    border-top: 1px solid #e4e7ed;
+    
+    .section-title {
+      font-size: 16px;
+      font-weight: bold;
+      margin-bottom: 16px;
+    }
+    
+    .action-buttons {
+      display: flex;
+      gap: 12px;
+      flex-wrap: wrap;
+      
+      .el-button {
+        min-width: 100px;
+      }
+    }
+  }
+  
+  .timeline-content {
+    .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>

+ 1425 - 306
src/views/subSystem/dispatch/disExerciseMan/DisPracticalDrill.vue

@@ -1,60 +1,147 @@
-<!--实战演练管理-->
+<!--实战演练管理 - 基于业务场景的优化版-->
 <template>
-  <div>
-  <div class="app-container">
-    <div class="drill-layout">
+  <div class="practical-drill-container">
+    <!-- 顶部操作栏 -->
+    <div class="drill-top-bar">
+      <div class="top-left">
+        <h2 class="page-title"><el-icon><TrendCharts /></el-icon>实战演练管理</h2>
+        <el-tag type="warning" size="large">5.2.3.3.4 实战演练管理</el-tag>
+      </div>
+      <div class="top-right">
+        <el-button type="primary" size="large" @click="handleCreatePlan">
+          <el-icon><Plus /></el-icon>创建实战演练
+        </el-button>
+        <el-button size="large" @click="showAnalysisReport">
+          <el-icon><Document /></el-icon>分析报表
+        </el-button>
+      </div>
+    </div>
+
+    <div class="drill-main-layout">
       <!-- ===== 左列 ===== -->
-      <div class="col-left">
+      <div class="left-column">
         <!-- 演练计划模板 -->
-        <div class="panel">
+        <div class="panel template-panel">
           <div class="panel-header">
             <span><el-icon><Document /></el-icon> 演练计划模板</span>
-            <el-button type="primary" size="small" @click="handleAddTemplate">新增</el-button>
+            <el-button type="primary" size="small" @click="openTemplateDialog">
+              <el-icon><Plus /></el-icon>新增模板
+            </el-button>
           </div>
           <div class="panel-body">
-            <div class="template-item" v-for="tpl in templates" :key="tpl.id">
-              <div class="template-info">
-                <div class="template-name">{{ tpl.name }}</div>
-                <div class="template-date">{{ tpl.date }}</div>
-              </div>
-              <div class="template-actions">
-                <el-button link type="primary" @click="handleEditTemplate(tpl)"><el-icon><Edit /></el-icon></el-button>
-                <el-button link type="success" @click="handleUseTemplate(tpl)">使用</el-button>
-                <el-button link type="danger" @click="handleDeleteTemplate(tpl)"><el-icon><Delete /></el-icon></el-button>
+            <div class="template-list">
+              <el-empty v-if="templates.length === 0" description="暂无演练模板" :image-size="80" />
+              <div v-for="tpl in templates" :key="tpl.id" class="template-card">
+                <div class="template-card-header">
+                  <div class="template-name">{{ tpl.name }}</div>
+                  <el-tag :type="getCategoryTagType(tpl.category)" size="small">{{ getCategoryLabel(tpl.category) }}</el-tag>
+                </div>
+                <div class="template-meta">
+                  <span class="meta-item"><el-icon><Calendar /></el-icon>{{ tpl.createDate || '未知日期' }}</span>
+                  <span class="meta-item"><el-icon><User /></el-icon>{{ tpl.userCount || 0 }}人</span>
+                </div>
+                <div class="template-actions">
+                  <el-button link type="primary" size="small" @click.stop="openEditTemplate(tpl)">
+                    <el-icon><Edit /></el-icon>编辑
+                  </el-button>
+                  <el-button link type="success" size="small" @click.stop="applyTemplate(tpl)">
+                    <el-icon><SwitchButton /></el-icon>应用
+                  </el-button>
+                  <el-button link type="danger" size="small" @click.stop="confirmDeleteTemplate(tpl)">
+                    <el-icon><Delete /></el-icon>删除
+                  </el-button>
+                </div>
               </div>
             </div>
           </div>
         </div>
 
         <!-- 演练计划设置 -->
-        <div class="panel">
+        <div class="panel plan-panel">
           <div class="panel-header">
             <span><el-icon><Setting /></el-icon> 演练计划设置</span>
             <div class="header-actions">
-              <el-button type="primary" size="small" @click="handleAddPlan">新增计划</el-button>
+              <el-select v-model="planFilterStatus" placeholder="筛选状态" size="small" style="width: 90px; margin-right: 5px" clearable>
+                <el-option label="全部" value="" />
+                <el-option label="计划中" value="计划中" />
+                <el-option label="进行中" value="进行中" />
+                <el-option label="已完成" value="已完成" />
+              </el-select>
+              <el-button type="primary" size="small" @click="openPlanDialog">
+                <el-icon><Plus /></el-icon>新建计划
+              </el-button>
             </div>
           </div>
           <div class="panel-body">
-            <div class="search-bar">
-              <el-input v-model="planSearch" placeholder="按演练项目/目标查询" size="small" style="width: 160px" />
-              <el-button type="primary" size="small" @click="handleSearchPlan">查询</el-button>
+            <div class="search-filter">
+              <el-input v-model="planSearchText" placeholder="搜索演练名称/目标/项目" size="small" style="width: 200px" clearable>
+                <template #prefix><el-icon><Search /></el-icon></template>
+              </el-input>
+              <el-button type="primary" size="small" @click="filterPlans">
+                <el-icon><Search /></el-icon>查询
+              </el-button>
             </div>
-            <div class="plan-item" v-for="plan in plans" :key="plan.id">
-              <div class="plan-header">
-                <div class="plan-name">{{ plan.name }}</div>
-                <div class="plan-actions">
-                  <el-button link type="primary" @click="handleEditPlan(plan)"><el-icon><Edit /></el-icon></el-button>
-                  <el-button link type="danger" @click="handleDeletePlan(plan)"><el-icon><Delete /></el-icon></el-button>
-                </div>
-              </div>
-              <div class="plan-details">
-                <div class="plan-row"><span class="plan-label">目标:</span>{{ plan.target }}</div>
-                <div class="plan-row"><span class="plan-label">项目:</span>{{ plan.project }}</div>
-                <div class="plan-row"><span class="plan-label">事件:</span>{{ plan.event }}</div>
-                <div class="plan-row"><span class="plan-label">人员:</span>{{ plan.personnel }}</div>
-                <div class="plan-row">
-                  <span class="plan-label">状态:</span>
-                  <el-tag :type="plan.status === '进行中' ? 'warning' : 'success'" size="small">{{ plan.status }}</el-tag>
+            <div class="plan-list">
+              <el-empty v-if="filteredPlans.length === 0" description="暂无演练计划" :image-size="80" />
+              <div
+                v-for="plan in filteredPlans"
+                :key="plan.id"
+                :class="['plan-card', { active: selectedPlanId === plan.id }]"
+                @click="selectPlan(plan)"
+              >
+                <div class="plan-status-badge" :class="plan.status">{{ plan.status }}</div>
+                <div class="plan-card-content">
+                  <h4 class="plan-title">{{ plan.name }}</h4>
+                  <div class="plan-category">
+                    <el-tag :type="getCategoryTagType(plan.category)" size="small">{{ getCategoryLabel(plan.category) }}</el-tag>
+                    <span class="plan-date">{{ plan.endTime || plan.updateTime || '—' }}</span>
+                  </div>
+                  <div class="plan-info-list">
+                    <div class="info-item" v-if="plan.target">
+                      <el-icon><Flag /></el-icon>
+                      <span>目标:</span>
+                      <span class="info-text">{{ plan.target }}</span>
+                    </div>
+                    <div class="info-item" v-if="plan.project">
+                      <el-icon><List /></el-icon>
+                      <span>项目:</span>
+                      <span class="info-text">{{ plan.project }}</span>
+                    </div>
+                    <div class="info-item" v-if="plan.eventDesc">
+                      <el-icon><Bell /></el-icon>
+                      <span>事件:</span>
+                      <span class="info-text">{{ plan.eventDesc }}</span>
+                    </div>
+                    <div class="info-item" v-if="plan.personnel">
+                      <el-icon><User /></el-icon>
+                      <span>人员:</span>
+                      <span class="info-text">{{ plan.personnel }}</span>
+                    </div>
+                    <div class="info-item" v-if="plan.participantCount">
+                      <el-icon><UserFilled /></el-icon>
+                      <span>参与人数:</span>
+                      <span class="info-value">{{ plan.participantCount }}人</span>
+                    </div>
+                    <div class="info-item" v-if="plan.progress !== null && plan.progress !== undefined">
+                      <el-icon><CircleCheckFilled /></el-icon>
+                      <span>进度:</span>
+                      <el-progress :percentage="plan.progress" :status="plan.progress === 100 ? 'success' : ''" :stroke-width="6" />
+                    </div>
+                  </div>
+                  <div class="plan-actions">
+                    <el-button link type="primary" size="small" @click.stop="editPlan(plan)">
+                      <el-icon><Edit /></el-icon>编辑
+                    </el-button>
+                    <el-button link type="warning" size="small" @click.stop="startDrill(plan)">
+                      <el-icon><VideoPlay /></el-icon>开始演练
+                    </el-button>
+                    <el-button link type="success" size="small" @click.stop="viewDrillRecord(plan)">
+                      <el-icon><Document /></el-icon>查看记录
+                    </el-button>
+                    <el-button link type="danger" size="small" @click.stop="confirmDeletePlan(plan)">
+                      <el-icon><Delete /></el-icon>删除
+                    </el-button>
+                  </div>
                 </div>
               </div>
             </div>
@@ -62,420 +149,1452 @@
         </div>
       </div>
 
-      <!-- ===== 中列 + 右列合并区域 ===== -->
-      <div class="col-right">
-        <!-- 演练结果记录与分析评估 -->
-        <div class="panel">
+      <!-- ===== 右列 ===== -->
+      <div class="right-column">
+        <!-- 当前演练状态 -->
+        <div class="panel current-drill-panel" v-if="currentDrillInfo">
           <div class="panel-header">
-            <span><el-icon><DataAnalysis /></el-icon> 演练结果记录与分析评估</span>
-            <el-button type="warning" size="small" @click="sendToSubsystem">
-              发送至统计分析子系统 & 应急评估子系统
+            <span><el-icon><Monitor /></el-icon> 当前演练状态</span>
+            <el-button v-if="isDrilling" type="danger" size="small" @click="stopDrill">
+              <el-icon><VideoPause /></el-icon>结束演练
             </el-button>
           </div>
           <div class="panel-body">
-            <div class="info-grid">
-              <!-- 监测监控信息 -->
-              <div class="info-card">
-                <div class="info-title"><el-icon><Monitor /></el-icon> 监测监控信息</div>
-                <div class="info-content">
-                  <div class="info-line">烟感传感器报警:化工园区储罐区3号罐</div>
-                  <div class="info-line">热成像检测到火源:储罐区西侧</div>
-                  <div class="info-line">风速监测:12 m/s,风向东南</div>
-                </div>
-              </div>
-
-              <!-- 接警信息 -->
-              <div class="info-card">
-                <div class="info-title"><el-icon><Phone /></el-icon> 接警信息</div>
-                <div class="info-content">
-                  <div class="info-line">时间:2025-06-18 09:32:21</div>
-                  <div class="info-line">报警中心:安保值班室 张强</div>
-                  <div class="info-line">地点:化工园区储罐区</div>
-                </div>
+            <div class="drill-status-summary">
+              <div class="status-badge large" :class="currentDrillStatus">
+                <el-icon><CircleCheckFilled /></el-icon>
+                {{ currentDrillStatus }}
               </div>
-
-              <!-- 指挥调度信息 -->
-              <div class="info-card">
-                <div class="info-title"><el-icon><Guide /></el-icon> 指挥调度信息</div>
-                <div class="info-content">
-                  <div class="info-line">调派:3个消防中队 + 1个危化品处置小组</div>
-                  <div class="info-line">已设置现场指挥部</div>
-                  <div class="info-line">暂无人员伤亡报告</div>
-                </div>
-              </div>
-
-              <!-- 决策信息 -->
-              <div class="info-card">
-                <div class="info-title"><el-icon><Operation /></el-icon> 决策信息</div>
-                <div class="info-content">
-                  <div class="info-line">总指挥下达命令:启动一级应急响应</div>
-                  <div class="info-line">疏散周边500m范围内人员</div>
-                  <div class="info-line">请求消防增援</div>
-                </div>
+              <div class="drill-timeline">
+                <el-timeline>
+                  <el-timeline-item
+                    v-for="(action, index) in drillActions"
+                    :key="index"
+                    :timestamp="action.time"
+                    placement="top"
+                    :type="action.type"
+                  >
+                    {{ action.message }}
+                  </el-timeline-item>
+                </el-timeline>
               </div>
-
-              <!-- 对外发布信息 -->
-              <div class="info-card info-card-full">
-                <div class="info-title"><el-icon><ChatDotRound /></el-icon> 对外发布信息</div>
-                <div class="info-content">
-                  <div class="info-line">官方发布:"事故已控制,无人员伤亡,提醒周边居民关闭门窗。"</div>
-                </div>
+            </div>
+          </div>
+        </div>
+  
+        <!-- 演练结果记录 -->
+        <div class="panel record-panel">
+          <div class="panel-header">
+            <span><el-icon><DataAnalysis /></el-icon> 演练结果记录</span>
+            <el-button type="primary" size="small" @click="saveAllRecord">
+              <el-icon><CircleCheckFilled /></el-icon>保存记录
+            </el-button>
+          </div>
+          <div class="panel-body">
+            <el-descriptions :column="2" border v-if="currentDrillRecord">
+              <el-descriptions-item label="演练项目">{{ currentDrillRecord.drillProjectName }}</el-descriptions-item>
+              <el-descriptions-item label="记录时间">{{ currentDrillRecord.recordTime }}</el-descriptions-item>
+              <el-descriptions-item label="演练类型">{{ currentDrillRecord.drillType }}</el-descriptions-item>
+              <el-descriptions-item label="参演部门">{{ currentDrillRecord.participatingDept }}</el-descriptions-item>
+              <el-descriptions-item label="演练负责人" :span="2">{{ currentDrillRecord.responsiblePerson }}</el-descriptions-item>
+            </el-descriptions>
+  
+            <!-- 监测监控信息 -->
+            <div class="record-section">
+              <div class="section-title-row">
+                <h4 class="section-title"><el-icon><Monitor /></el-icon> 监测监控信息</h4>
+                <el-button type="primary" size="small" @click="openAddMonitoring">
+                  <el-icon><Plus /></el-icon>添加监测数据
+                </el-button>
               </div>
+              <el-table :data="monitoringInfo" stripe size="small" style="width: 100%" max-height="200">
+                <el-table-column prop="sensorType" label="传感器类型" min-width="120" />
+                <el-table-column prop="location" label="监测位置" min-width="140" show-overflow-tooltip />
+                <el-table-column prop="value" label="监测数值" width="100" />
+                <el-table-column prop="unit" label="单位" width="70" />
+                <el-table-column prop="alarmLevel" label="报警等级" width="90">
+                  <template #default="{ row }">
+                    <el-tag :type="getAlarmLevelType(row.alarmLevel)" size="small">{{ row.alarmLevel }}</el-tag>
+                  </template>
+                </el-table-column>
+                <el-table-column prop="time" label="监测时间" width="150" />
+                <el-table-column label="操作" width="120" fixed="right">
+                  <template #default="{ row }">
+                    <el-button link type="primary" size="small" @click="openEditMonitoring(row)">编辑</el-button>
+                    <el-button link type="danger" size="small" @click="confirmDeleteMonitoring(row)">删除</el-button>
+                  </template>
+                </el-table-column>
+              </el-table>
+              <el-empty v-if="monitoringInfo.length === 0" description="暂无监测数据" :image-size="60" />
+            </div>
+  
+            <!-- 接警信息 -->
+            <div class="record-section" v-if="currentDrillRecord">
+              <h4 class="section-title"><el-icon><Phone /></el-icon> 接警信息</h4>
+              <el-descriptions :column="2" border>
+                <el-descriptions-item label="接警时间">{{ currentDrillRecord.reportTime || '—' }}</el-descriptions-item>
+                <el-descriptions-item label="报警方式">{{ currentDrillRecord.reportMethod || '电话' }}</el-descriptions-item>
+                <el-descriptions-item label="报警人" :span="2">{{ currentDrillRecord.reporter || '—' }}</el-descriptions-item>
+                <el-descriptions-item label="联系电话" :span="2">{{ currentDrillRecord.contactPhone || '—' }}</el-descriptions-item>
+                <el-descriptions-item label="事件地点" :span="2">{{ currentDrillRecord.eventLocation || '—' }}</el-descriptions-item>
+                <el-descriptions-item label="事件描述" :span="2">
+                  <el-input v-model="currentDrillRecord.eventDescription" type="textarea" :rows="2" placeholder="请输入事件描述" size="small" />
+                </el-descriptions-item>
+              </el-descriptions>
+            </div>
+  
+            <!-- 指挥调度信息 -->
+            <div class="record-section">
+              <h4 class="section-title"><el-icon><Guide /></el-icon> 指挥调度信息</h4>
+              <el-form :model="dispatchForm" label-width="80px" size="small">
+                <el-form-item label="调度指令">
+                  <el-input v-model="dispatchForm.instruction" type="textarea" :rows="2" placeholder="请输入调度指令" />
+                </el-form-item>
+                <el-form-item label="调派力量">
+                  <el-select v-model="dispatchForm.forces" multiple placeholder="请选择调派力量" style="width: 100%">
+                    <el-option label="消防中队" value="消防中队" />
+                    <el-option label="危化品处置小组" value="危化品处置小组" />
+                    <el-option label="医疗救援队" value="医疗救援队" />
+                    <el-option label="应急抢险队" value="应急抢险队" />
+                    <el-option label="公安警力" value="公安警力" />
+                  </el-select>
+                </el-form-item>
+                <el-form-item label="现场指挥部">
+                  <el-switch v-model="dispatchForm.commandCenter" />
+                </el-form-item>
+                <el-form-item label="人员伤亡">
+                  <el-input v-model="dispatchForm.casualties" placeholder="暂无人员伤亡报告" />
+                </el-form-item>
+              </el-form>
+            </div>
+  
+            <!-- 决策信息 -->
+            <div class="record-section">
+              <h4 class="section-title"><el-icon><Operation /></el-icon> 决策信息</h4>
+              <el-form :model="decisionForm" label-width="80px" size="small">
+                <el-form-item label="响应级别">
+                  <el-select v-model="decisionForm.responseLevel" style="width: 100%">
+                    <el-option label="一级响应" value="一级响应" />
+                    <el-option label="二级响应" value="二级响应" />
+                    <el-option label="三级响应" value="三级响应" />
+                    <el-option label="四级响应" value="四级响应" />
+                  </el-select>
+                </el-form-item>
+                <el-form-item label="总指挥命令">
+                  <el-input v-model="decisionForm.command" type="textarea" :rows="2" placeholder="请输入总指挥命令" />
+                </el-form-item>
+                <el-form-item label="疏散范围">
+                  <el-input v-model="decisionForm.evacuationRange" placeholder="请输入疏散范围" />
+                </el-form-item>
+                <el-form-item label="增援请求">
+                  <el-switch v-model="decisionForm.reinforcement" />
+                </el-form-item>
+                <el-form-item label="备注说明">
+                  <el-input v-model="decisionForm.remark" type="textarea" :rows="2" placeholder="请输入备注说明" />
+                </el-form-item>
+              </el-form>
+            </div>
+  
+            <!-- 对外发布信息 -->
+            <div class="record-section">
+              <h4 class="section-title"><el-icon><ChatDotRound /></el-icon> 对外发布信息</h4>
+              <el-form label-width="80px" size="small">
+                <el-form-item label="发布渠道">
+                  <el-checkbox-group v-model="releaseChannels">
+                    <el-checkbox label="官方微博">官方微博</el-checkbox>
+                    <el-checkbox label="微信公众号">微信公众号</el-checkbox>
+                    <el-checkbox label="门户网站">门户网站</el-checkbox>
+                    <el-checkbox label="媒体发布">媒体发布</el-checkbox>
+                    <el-checkbox label="短信通知">短信通知</el-checkbox>
+                  </el-checkbox-group>
+                </el-form-item>
+                <el-form-item label="发布内容">
+                  <el-input v-model="releaseContent" type="textarea" :rows="4" placeholder="请输入对外发布信息内容" />
+                </el-form-item>
+                <el-form-item>
+                  <el-button type="primary" size="small" @click="saveReleaseInfo">保存并发布</el-button>
+                  <el-button size="small" @click="previewRelease">预览</el-button>
+                </el-form-item>
+              </el-form>
             </div>
           </div>
         </div>
-
-        <!-- 统计分析子系统 结果 -->
-        <div class="panel">
+  
+        <!-- 统计分析子系统结果 -->
+        <div class="panel analysis-panel">
           <div class="panel-header">
-            <span><el-icon><TrendCharts /></el-icon> 统计分析子系统 结果</span>
+            <span><el-icon><TrendCharts /></el-icon> 统计分析子系统结果</span>
+            <div class="header-actions">
+              <el-button type="warning" size="small" @click="sendToAnalysisSubsystems" :disabled="!canSendToSubsystems">
+                <el-icon><Share /></el-icon>发送至子系统和评估系统
+              </el-button>
+              <el-button type="success" size="small" @click="exportAnalysisReport">
+                <el-icon><Download /></el-icon>导出分析报告
+              </el-button>
+            </div>
           </div>
           <div class="panel-body">
-            <div class="analysis-result">
-              <div class="analysis-time">分析时间: 16:50:52</div>
-              <div class="analysis-detail">
-                <div class="detail-row"><span class="detail-label">技能:</span>灭火、堵漏</div>
-                <div class="detail-row"><span class="detail-label">装备:</span>防护服、灭火器</div>
+            <div class="analysis-summary" v-if="analysisResult">
+              <div class="analysis-time">
+                <el-icon><Clock /></el-icon>
+                分析时间:{{ analysisResult.analyzeTime || '--:--:--' }}
               </div>
-              <div class="analysis-eval">
-                <div class="eval-title">评估结果</div>
-                <div class="eval-grid">
-                  <div class="eval-item">
-                    <div class="eval-label">任务完成率</div>
-                    <div class="eval-value">67%</div>
+              <div class="analysis-basic">
+                <h4 class="basic-title">演练能力评估</h4>
+                <div class="capability-grid">
+                  <div class="capability-item">
+                    <div class="cap-label">技能掌握</div>
+                    <div class="cap-score">{{ analysisResult.skillScore }}/100</div>
+                    <el-progress :percentage="analysisResult.skillScore" :stroke-width="8" />
                   </div>
-                  <div class="eval-item">
-                    <div class="eval-label">响应时效</div>
-                    <div class="eval-value">达标</div>
+                  <div class="capability-item">
+                    <div class="cap-label">装备使用</div>
+                    <div class="cap-score">{{ analysisResult.equipmentScore }}/100</div>
+                    <el-progress :percentage="analysisResult.equipmentScore" :stroke-width="8" />
                   </div>
-                  <div class="eval-item">
-                    <div class="eval-label">协同评分</div>
-                    <div class="eval-value">良好</div>
+                  <div class="capability-item">
+                    <div class="cap-label">响应时效</div>
+                    <div class="cap-score">{{ analysisResult.responseScore }}/100</div>
+                    <el-progress :percentage="analysisResult.responseScore" :stroke-width="8" />
                   </div>
+                  <div class="capability-item">
+                    <div class="cap-label">协同配合</div>
+                    <div class="cap-score">{{ analysisResult.cooperationScore }}/100</div>
+                    <el-progress :percentage="analysisResult.cooperationScore" :stroke-width="8" />
+                  </div>
+                </div>
+              </div>
+              <div class="analysis-task">
+                <h4 class="task-title">任务执行情况</h4>
+                <div class="task-stats">
+                  <div class="task-stat-item">
+                    <div class="stat-label">任务总数</div>
+                    <div class="stat-value highlight-blue">{{ analysisResult.totalTasks }}</div>
+                  </div>
+                  <div class="task-stat-item">
+                    <div class="stat-label">完成任务</div>
+                    <div class="stat-value highlight-green">{{ analysisResult.completedTasks }}</div>
+                  </div>
+                  <div class="task-stat-item">
+                    <div class="stat-label">完成率</div>
+                    <div class="stat-value highlight-orange">{{ analysisResult.completionRate }}%</div>
+                  </div>
+                  <div class="task-stat-item">
+                    <div class="stat-label">超时任务</div>
+                    <div class="stat-value highlight-red">{{ analysisResult.overtimeTasks }}</div>
+                  </div>
+                </div>
+              </div>
+              <div class="analysis-comprehensive">
+                <h4 class="comprehensive-title">综合评估结果</h4>
+                <div class="eval-grid">
                   <div class="eval-item">
                     <div class="eval-label">综合等级</div>
-                    <div class="eval-value highlight">良好</div>
+                    <div class="eval-value" :class="{ success: analysisResult.overallLevel === '优秀', warning: analysisResult.overallLevel === '良好', info: analysisResult.overallLevel === '合格', danger: analysisResult.overallLevel === '待改进' }">
+                      {{ analysisResult.overallLevel }}
+                    </div>
                   </div>
                 </div>
+                <div class="eval-description">
+                  <strong>评估结论:</strong>
+                  <p>{{ analysisResult.evalConclusion || '无评估结论' }}</p>
+                </div>
+                <div class="suggestion-section">
+                  <strong>改进建议:</strong>
+                  <ul>
+                    <li v-for="(sug, idx) in analysisResult.improvementSuggestions" :key="idx">{{ sug }}</li>
+                  </ul>
+                </div>
               </div>
             </div>
+            <el-empty v-else description="暂无统计分析结果,请点击“分析报表”加载" :image-size="100" />
           </div>
         </div>
       </div>
     </div>
-  </div>
 
-    <!-- 模板弹窗 -->
-  <el-dialog v-model="tplDialogVisible" title="演练模板" width="500px" destroy-on-close>
-    <el-form :model="tplForm" label-width="80px">
-      <el-form-item label="模板名称"><el-input v-model="tplForm.name" /></el-form-item>
-      <el-form-item label="所属系统">
-        <el-select v-model="tplForm.category" style="width:100%"><el-option label="燃气" value="燃气" /><el-option label="排水" value="排水" /><el-option label="供水" value="供水" /></el-select>
-      </el-form-item>
-      <el-form-item label="模板内容"><el-input v-model="tplForm.content" type="textarea" :rows="4" /></el-form-item>
-    </el-form>
-    <template #footer><el-button @click="tplDialogVisible=false">取消</el-button><el-button type="primary" @click="handleTplSubmit">确定</el-button></template>
-  </el-dialog>
-  <!-- 计划表单弹窗 -->
-    <el-dialog v-model="planDialogVisible" :title="planFormTitle" width="550px" destroy-on-close>
-      <el-form :model="planForm" ref="planFormRef" label-width="90px">
-        <el-form-item label="演练名称"><el-input v-model="planForm.name" /></el-form-item>
+    <!-- 弹窗:模板管理 -->
+    <el-dialog 
+      v-model="templateDialogVisible" 
+      :title="templateDialogTitle" 
+      width="600px" 
+      destroy-on-close
+    >
+      <el-form :model="templateForm" label-width="90px">
+        <el-form-item label="模板名称">
+          <el-input v-model="templateForm.name" placeholder="请输入演练模板名称" />
+        </el-form-item>
         <el-form-item label="所属系统">
-          <el-select v-model="planForm.category" style="width:100%">
-            <el-option label="燃气" value="燃气" /><el-option label="排水" value="排水" /><el-option label="供水" value="供水" />
+          <el-select v-model="templateForm.category" style="width: 100%">
+            <el-option label="燃气管网" value="燃气" />
+            <el-option label="排水管网" value="排水" />
+            <el-option label="供水管网" value="供水" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="演练目标">
+          <el-input 
+            v-model="templateForm.target" 
+            type="textarea" 
+            :rows="2" 
+            placeholder="请输入演练目标"
+          />
+        </el-form-item>
+        <el-form-item label="演练项目">
+          <el-input 
+            v-model="templateForm.project" 
+            type="textarea" 
+            :rows="3" 
+            placeholder="请输入演练项目详情"
+          />
+        </el-form-item>
+        <el-form-item label="涉及事件">
+          <el-input 
+            v-model="templateForm.eventDesc" 
+            type="textarea" 
+            :rows="2" 
+            placeholder="请输入可能涉及的事件类型"
+          />
+        </el-form-item>
+        <el-form-item label="参与人员">
+          <el-input 
+            v-model="templateForm.personnel" 
+            type="textarea" 
+            :rows="2" 
+            placeholder="例如:指挥中心、消防队、医疗组等"
+          />
+        </el-form-item>
+        <el-form-item label="完成时限">
+          <el-date-picker
+            v-model="templateForm.dueDate"
+            type="date"
+            placeholder="选择截止日期"
+            format="YYYY-MM-DD"
+            date-format="YYYY-MM-DD"
+            style="width: 100%"
+          />
+        </el-form-item>
+        <el-form-item label="预案内容">
+          <el-input 
+            v-model="templateForm.content" 
+            type="textarea" 
+            :rows="4" 
+            placeholder="请输入详细的预案内容和操作流程"
+          />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="closeTemplateDialog">取消</el-button>
+        <el-button type="primary" @click="submitTemplateForm">确定</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 弹窗:计划管理 -->
+    <el-dialog 
+      v-model="planDialogVisible" 
+      :title="planDialogTitle" 
+      width="600px" 
+      destroy-on-close
+    >
+      <el-form :model="planForm" ref="planFormRef" label-width="90px">
+        <el-form-item label="演练名称" required>
+          <el-input v-model="planForm.name" placeholder="请输入演练名称" />
+        </el-form-item>
+        <el-form-item label="所属系统" required>
+          <el-select v-model="planForm.category" style="width: 100%">
+            <el-option label="燃气管网" value="燃气" />
+            <el-option label="排水管网" value="排水" />
+            <el-option label="供水管网" value="供水" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="演练目标" required>
+          <el-input 
+            v-model="planForm.target" 
+            type="textarea" 
+            :rows="2" 
+            placeholder="请输入演练目标"
+          />
+        </el-form-item>
+        <el-form-item label="演练项目" required>
+          <el-input 
+            v-model="planForm.project" 
+            type="textarea" 
+            :rows="3" 
+            placeholder="请输入演练项目详情"
+          />
+        </el-form-item>
+        <el-form-item label="事件描述">
+          <el-input 
+            v-model="planForm.eventDesc" 
+            type="textarea" 
+            :rows="2" 
+            placeholder="请输入模拟事件的描述"
+          />
+        </el-form-item>
+        <el-form-item label="参与人员" required>
+          <el-input 
+            v-model="planForm.personnel" 
+            type="textarea" 
+            :rows="2" 
+            placeholder="请输入参与演练的人员或部门"
+          />
+        </el-form-item>
+        <el-form-item label="完成时间">
+          <el-date-picker
+            v-model="planForm.endTime"
+            type="date"
+            placeholder="选择完成时间"
+            format="YYYY-MM-DD"
+            date-format="YYYY-MM-DD"
+            style="width: 100%"
+          />
+        </el-form-item>
+        <el-form-item label="参与人数">
+          <el-input-number 
+            v-model="planForm.participantCount" 
+            :min="1" 
+            :max="1000" 
+            style="width: 100%"
+          />
+        </el-form-item>
+        <el-form-item label="演练进度">
+          <el-slider v-model="planForm.progress" :min="0" :max="100" :step="10" />
+        </el-form-item>
+        <el-form-item label="状态" required>
+          <el-select v-model="planForm.status" style="width: 100%">
+            <el-option label="计划中" value="计划中" />
+            <el-option label="进行中" value="进行中" />
+            <el-option label="已完成" value="已完成" />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="planDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitPlanForm">确定</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 弹窗:监测数据 -->
+    <el-dialog v-model="monitoringDialogVisible" :title="monitoringDialogTitle" width="500px" destroy-on-close>
+      <el-form :model="monitoringForm" label-width="90px">
+        <el-form-item label="传感器类型" required>
+          <el-input v-model="monitoringForm.sensorType" placeholder="如:燃气浓度传感器" />
+        </el-form-item>
+        <el-form-item label="监测位置" required>
+          <el-input v-model="monitoringForm.location" placeholder="如:XX路XX号管点" />
+        </el-form-item>
+        <el-form-item label="监测数值">
+          <el-input v-model="monitoringForm.value" placeholder="如:50" />
+        </el-form-item>
+        <el-form-item label="单位">
+          <el-input v-model="monitoringForm.unit" placeholder="如:%LEL" />
+        </el-form-item>
+        <el-form-item label="报警等级">
+          <el-select v-model="monitoringForm.alarmLevel" style="width: 100%">
+            <el-option label="一般" value="一般" />
+            <el-option label="较大" value="较大" />
+            <el-option label="重大" value="重大" />
+            <el-option label="特别重大" value="特别重大" />
           </el-select>
         </el-form-item>
-        <el-form-item label="演练目标"><el-input v-model="planForm.target" type="textarea" :rows="2" /></el-form-item>
-        <el-form-item label="演练项目"><el-input v-model="planForm.project" type="textarea" :rows="2" /></el-form-item>
-        <el-form-item label="事件描述"><el-input v-model="planForm.eventDesc" /></el-form-item>
-        <el-form-item label="参与人员"><el-input v-model="planForm.personnel" /></el-form-item>
-        <el-form-item label="状态"><el-select v-model="planForm.status" style="width:100%"><el-option label="计划中" value="计划中" /><el-option label="进行中" value="进行中" /><el-option label="已完成" value="已完成" /></el-select></el-form-item>
+        <el-form-item label="监测时间">
+          <el-input v-model="monitoringForm.time" placeholder="如:2024-01-01 12:00:00" />
+        </el-form-item>
       </el-form>
-      <template #footer><el-button @click="planDialogVisible=false">取消</el-button><el-button type="primary" @click="handlePlanSubmit">确定</el-button></template>
+      <template #footer>
+        <el-button @click="monitoringDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitMonitoringForm">确定</el-button>
+      </template>
     </el-dialog>
   </div>
 </template>
 
 <script setup>
-import { ref, onMounted } from 'vue'
-import { ElMessage, ElMessageBox } from 'element-plus'
+import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
+import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'
 import {
   Document, Edit, Delete, Setting, DataAnalysis, Monitor,
-  Phone, Guide, Operation, ChatDotRound, TrendCharts
+  Phone, Guide, Operation, ChatDotRound, TrendCharts, Plus, Calendar,
+  User, SwitchButton, Search, Flag, Bell, List, Share, Download,
+  Clock, VideoPlay, VideoPause, UserFilled, CircleCheckFilled
 } from '@element-plus/icons-vue'
-import { getPracticalPlans, savePracticalPlan, updatePracticalPlan, deletePracticalPlan, getPracticalTemplates, savePracticalTemplate, updatePracticalTemplate, deletePracticalTemplate } from '@/api/pipeNetwork/basic'
+import {
+  getPracticalTemplates, savePracticalTemplate, updatePracticalTemplate, deletePracticalTemplate,
+  getPracticalPlans, savePracticalPlan, updatePracticalPlan, deletePracticalPlan,
+  sendDrillAnalysis,
+  getDrillRecord, saveDrillRecord, updateDrillRecord,
+  getDrillMonitoring, saveDrillMonitoring, deleteDrillMonitoring,
+  getDrillAnalysis, exportDrillReport, updatePlanStatus
+} from '@/api/pipeNetwork/basic'
+
+// ========== 卸载保护 ==========
+let isUnmounted = false
 
+// ========== 演练模板相关 ==========
 const templates = ref([])
-// 模板弹窗
-const tplDialogVisible = ref(false)
-const tplForm = ref({ id: null, name: '', category: '燃气', content: '' })
-const handleAddTemplate = () => { tplForm.value = { id: null, name: '', category: '燃气', content: '' }; tplDialogVisible.value = true }
-const handleEditTemplate = (tpl) => { tplForm.value = { ...tpl }; tplDialogVisible.value = true }
-const handleUseTemplate = (tpl) => { ElMessage.success(`已应用模板: ${tpl.name}`) }
-const handleTplSubmit = async () => {
-  const d = tplForm.value
+const templateDialogVisible = ref(false)
+const templateDialogTitle = ref('新增演练模板')
+const templateForm = ref({
+  id: null, name: '', category: '燃气', target: '', project: '', eventDesc: '',
+  personnel: '', dueDate: null, content: ''
+})
+
+const openTemplateDialog = () => {
+  templateForm.value = {
+    id: null, name: '', category: '燃气', target: '', project: '',
+    eventDesc: '', personnel: '', dueDate: null, content: ''
+  }
+  templateDialogTitle.value = '新增演练模板'
+  templateDialogVisible.value = true
+}
+
+const closeTemplateDialog = () => { templateDialogVisible.value = false }
+
+const openEditTemplate = (tpl) => {
+  templateForm.value = { ...tpl, dueDate: tpl.dueDate || null }
+  templateDialogTitle.value = '编辑演练模板'
+  templateDialogVisible.value = true
+}
+
+const submitTemplateForm = async () => {
+  if (!templateForm.value.name) {
+    ElMessage.warning('请输入模板名称')
+    return
+  }
   try {
-    d.id ? await updatePracticalTemplate(d) : await savePracticalTemplate(d)
-    ElMessage.success('保存成功'); tplDialogVisible.value = false; loadTemplates()
-  } catch (e) { ElMessage.error('保存失败') }
+    const d = templateForm.value
+    if (d.id) await updatePracticalTemplate(d)
+    else await savePracticalTemplate(d)
+    if (isUnmounted) return
+    ElMessage.success('保存成功')
+    templateDialogVisible.value = false
+    loadTemplates()
+  } catch (e) {
+    ElMessage.error('保存失败')
+  }
+}
+
+// 应用模板 → 打开计划弹窗并预填模板数据
+const applyTemplate = (tpl) => {
+  planForm.value = {
+    id: null, name: tpl.name, category: tpl.category || '燃气',
+    target: tpl.target || '', project: tpl.project || '',
+    eventDesc: tpl.eventDesc || '', personnel: tpl.personnel || '',
+    endTime: tpl.dueDate || null, participantCount: 1, progress: 0, status: '计划中'
+  }
+  planDialogTitle.value = '基于模板创建演练计划'
+  planDialogVisible.value = true
+  ElMessage.info(`已应用模板「${tpl.name}」,请完善计划信息`)
+}
+
+const confirmDeleteTemplate = (tpl) => {
+  ElMessageBox.confirm('确认删除此演练模板?', '提示', { type: 'warning' })
+    .then(async () => {
+      await deletePracticalTemplate(tpl.id)
+      ElMessage.success('删除成功')
+      loadTemplates()
+    })
+    .catch(() => {})
+}
+
+async function loadTemplates() {
+  try {
+    const res = await getPracticalTemplates()
+    if (isUnmounted) return
+    const rows = res.data || []
+    templates.value = rows.map(d => ({
+      id: d.id, name: d.name, category: d.category || '燃气',
+      target: d.target, project: d.project, eventDesc: d.eventDesc,
+      personnel: d.personnel, dueDate: d.dueDate,
+      content: d.content, createDate: d.createDate || d.updateTime,
+      userCount: d.userCount || 0
+    }))
+  } catch (e) {
+    console.error('加载模板失败', e)
+  }
+}
+
+const getCategoryTagType = (category) => {
+  const types = { 燃气: 'danger', 排水: 'warning', 供水: 'primary' }
+  return types[category] || 'info'
 }
-const handleDeleteTemplate = (tpl) => { ElMessageBox.confirm('确认删除?', '提示', { type: 'warning' }).then(async () => { await deletePracticalTemplate(tpl.id); ElMessage.success('删除成功'); loadTemplates() }).catch(() => {}) }
-async function loadTemplates() { try { const res = await getPracticalTemplates(); const rows = res.data || []; templates.value = rows.map(d => ({ id: d.id, name: d.name, content: d.content, date: d.createDate })) } catch (e) {} }
 
-const planSearch = ref('')
+const getCategoryLabel = (category) => {
+  const labels = { 燃气: '燃气管网', 排水: '排水管网', 供水: '供水管网' }
+  return labels[category] || category
+}
+
+// ========== 演练计划相关 ==========
+const planSearchText = ref('')
+const planFilterStatus = ref('')
 const plans = ref([])
+const filteredPlans = computed(() => {
+  let result = plans.value
+  if (planFilterStatus.value) {
+    result = result.filter(p => p.status === planFilterStatus.value)
+  }
+  if (planSearchText.value) {
+    const keyword = planSearchText.value.toLowerCase()
+    result = result.filter(p =>
+      p.name?.toLowerCase().includes(keyword) ||
+      p.target?.toLowerCase().includes(keyword) ||
+      p.project?.toLowerCase().includes(keyword)
+    )
+  }
+  return result
+})
+
+const selectedPlanId = ref(null)
+const currentPlan = ref(null)
+
+// 选中计划 → 加载关联的演练记录
+const selectPlan = async (plan) => {
+  selectedPlanId.value = plan.id
+  currentPlan.value = plan
+  await loadDrillRecord(plan.id)
+}
 
-// 计划弹窗
 const planDialogVisible = ref(false)
-const planFormTitle = ref('新增计划')
-const planForm = ref({ id: null, name: '', category: '燃气', target: '', project: '', eventDesc: '', personnel: '', status: '计划中' })
+const planDialogTitle = ref('新建演练计划')
+const planForm = ref({
+  id: null, name: '', category: '燃气', target: '', project: '',
+  eventDesc: '', personnel: '', endTime: null, participantCount: 1, progress: 0, status: '计划中'
+})
 const planFormRef = ref(null)
-const handleAddPlan = () => { planForm.value = { id: null, name: '', category: '燃气', target: '', project: '', eventDesc: '', personnel: '', status: '计划中' }; planFormTitle.value = '新增计划'; planDialogVisible.value = true }
-const handleEditPlan = (plan) => { planForm.value = { id: plan.id, name: plan.name, category: plan.category || '燃气', target: plan.target || '', project: plan.project || '', eventDesc: plan.eventDesc || plan.event || '', personnel: plan.personnel || '', status: plan.status || '计划中' }; planFormTitle.value = '编辑计划'; planDialogVisible.value = true }
-const handleDeletePlan = (plan) => { ElMessageBox.confirm('确认删除?', '提示', { type: 'warning' }).then(async () => { await deletePracticalPlan(plan.id); ElMessage.success('删除成功'); loadData() }).catch(() => {}) }
-const handlePlanSubmit = async () => {
-  const d = planForm.value
+
+const openPlanDialog = () => {
+  planForm.value = {
+    id: null, name: '', category: '燃气', target: '', project: '',
+    eventDesc: '', personnel: '', endTime: null, participantCount: 1, progress: 0, status: '计划中'
+  }
+  planDialogTitle.value = '新建演练计划'
+  planDialogVisible.value = true
+}
+
+const editPlan = (plan) => {
+  planForm.value = { ...plan, endTime: plan.endTime || null }
+  planDialogTitle.value = '编辑演练计划'
+  planDialogVisible.value = true
+}
+
+const submitPlanForm = async () => {
+  if (!planForm.value.name) {
+    ElMessage.warning('请输入演练名称')
+    return
+  }
+  try {
+    const d = planForm.value
+    if (d.id) await updatePracticalPlan(d)
+    else await savePracticalPlan(d)
+    if (isUnmounted) return
+    ElMessage.success('保存成功')
+    planDialogVisible.value = false
+    loadData()
+  } catch (e) {
+    ElMessage.error('保存失败')
+  }
+}
+
+const confirmDeletePlan = (plan) => {
+  ElMessageBox.confirm('确认删除此演练计划?', '提示', { type: 'warning' })
+    .then(async () => {
+      await deletePracticalPlan(plan.id)
+      ElMessage.success('删除成功')
+      if (selectedPlanId.value === plan.id) {
+        selectedPlanId.value = null
+        currentPlan.value = null
+        currentDrillRecord.value = null
+        currentDrillInfo.value = null
+      }
+      loadData()
+    })
+    .catch(() => {})
+}
+
+const handleCreatePlan = () => { openPlanDialog() }
+const filterPlans = () => { /* 过滤由 computed 自动处理 */ }
+
+async function loadData() {
+  try {
+    const res = await getPracticalPlans()
+    if (isUnmounted) return
+    const rows = res.data || []
+    plans.value = rows.map(d => ({
+      id: d.id, name: d.name, category: d.category || '燃气',
+      target: d.target, project: d.project, eventDesc: d.eventDesc || d.event,
+      personnel: d.personnel, status: d.status || '计划中',
+      endTime: d.endTime || d.updateTime, participantCount: d.participantCount || 0,
+      progress: d.progress !== undefined ? d.progress : 0
+    }))
+  } catch (e) {
+    console.error('加载计划失败', e)
+  }
+}
+
+// ========== 演练记录相关 ==========
+const currentDrillInfo = ref(null)
+const currentDrillRecord = ref(null)
+const isDrilling = ref(false)
+const currentDrillStatus = ref('未开始')
+const drillActions = ref([])
+const monitoringInfo = ref([])
+const dispatchForm = ref({ instruction: '', forces: [], commandCenter: false, casualties: '' })
+const decisionForm = ref({ responseLevel: '三级响应', command: '', evacuationRange: '', reinforcement: false, remark: '' })
+const releaseChannels = ref([])
+const releaseContent = ref('')
+
+// 加载演练记录
+async function loadDrillRecord(planId) {
+  try {
+    const res = await getDrillRecord(planId)
+    if (isUnmounted) return
+    const data = res.data
+    if (data) {
+      currentDrillRecord.value = data
+      currentDrillInfo.value = { planId, status: data.status || '已记录' }
+      currentDrillStatus.value = data.status || '已记录'
+      if (data.dispatchInfo) dispatchForm.value = { ...dispatchForm.value, ...data.dispatchInfo }
+      if (data.decisionInfo) decisionForm.value = { ...decisionForm.value, ...data.decisionInfo }
+      if (data.releaseChannels) releaseChannels.value = data.releaseChannels
+      if (data.releaseContent) releaseContent.value = data.releaseContent
+      if (data.id) await loadMonitoring(data.id)
+      if (data.actions) drillActions.value = data.actions
+    } else {
+      currentDrillRecord.value = null
+      currentDrillInfo.value = null
+      monitoringInfo.value = []
+      drillActions.value = []
+    }
+  } catch (e) {
+    currentDrillRecord.value = null
+    currentDrillInfo.value = null
+    monitoringInfo.value = []
+    drillActions.value = []
+  }
+}
+
+// 加载监测信息
+async function loadMonitoring(recordId) {
+  if (!recordId) {
+    monitoringInfo.value = []
+    return
+  }
+  try {
+    const res = await getDrillMonitoring(recordId)
+    if (isUnmounted) return
+    monitoringInfo.value = res.data || []
+  } catch (e) {
+    monitoringInfo.value = []
+  }
+}
+
+// 保存全部演练记录
+const saveAllRecord = async () => {
+  if (!currentDrillRecord.value) {
+    ElMessage.warning('请先开始演练或选择有记录的计划')
+    return
+  }
+  try {
+    const recordData = {
+      ...currentDrillRecord.value,
+      planId: selectedPlanId.value,
+      dispatchInfo: { ...dispatchForm.value },
+      decisionInfo: { ...decisionForm.value },
+      releaseChannels: releaseChannels.value,
+      releaseContent: releaseContent.value,
+      actions: drillActions.value
+    }
+    if (currentDrillRecord.value.id) {
+      await updateDrillRecord(recordData)
+    } else {
+      const res = await saveDrillRecord(recordData)
+      if (res.data) {
+        const saved = typeof res.data === 'object' ? res.data : { id: res.data }
+        currentDrillRecord.value.id = saved.id
+      }
+    }
+    if (isUnmounted) return
+    ElMessage.success('演练记录已保存')
+  } catch (e) {
+    ElMessage.error('保存失败')
+  }
+}
+
+// ========== 监测信息弹窗 ==========
+const monitoringDialogVisible = ref(false)
+const monitoringDialogTitle = ref('添加监测数据')
+const monitoringForm = ref({
+  id: null, sensorType: '', location: '', value: '', unit: '', alarmLevel: '一般', time: ''
+})
+
+const openAddMonitoring = () => {
+  if (!currentDrillRecord.value) {
+    ElMessage.warning('请先开始演练或加载演练记录')
+    return
+  }
+  monitoringForm.value = {
+    id: null, sensorType: '', location: '', value: '', unit: '', alarmLevel: '一般',
+    time: new Date().toLocaleString()
+  }
+  monitoringDialogTitle.value = '添加监测数据'
+  monitoringDialogVisible.value = true
+}
+
+const openEditMonitoring = (row) => {
+  monitoringForm.value = { ...row }
+  monitoringDialogTitle.value = '编辑监测数据'
+  monitoringDialogVisible.value = true
+}
+
+const submitMonitoringForm = async () => {
+  if (!monitoringForm.value.sensorType || !monitoringForm.value.location) {
+    ElMessage.warning('请填写传感器类型和监测位置')
+    return
+  }
+  try {
+    const data = { ...monitoringForm.value, recordId: currentDrillRecord.value.id }
+    await saveDrillMonitoring(data)
+    if (isUnmounted) return
+    ElMessage.success('保存成功')
+    monitoringDialogVisible.value = false
+    if (currentDrillRecord.value.id) await loadMonitoring(currentDrillRecord.value.id)
+  } catch (e) {
+    ElMessage.error('保存失败')
+  }
+}
+
+const confirmDeleteMonitoring = (row) => {
+  ElMessageBox.confirm('确认删除此监测数据?', '提示', { type: 'warning' })
+    .then(async () => {
+      await deleteDrillMonitoring(row.id)
+      ElMessage.success('删除成功')
+      if (currentDrillRecord.value?.id) await loadMonitoring(currentDrillRecord.value.id)
+    })
+    .catch(() => {})
+}
+
+const getAlarmLevelType = (level) => {
+  const types = { 一般: 'info', 较大: 'warning', 重大: 'danger', 特别重大: 'danger' }
+  return types[level] || 'info'
+}
+
+// ========== 对外发布 ==========
+const saveReleaseInfo = () => {
+  if (!releaseChannels.value.length && !releaseContent.value) {
+    ElMessage.warning('请填写发布渠道和内容')
+    return
+  }
+  saveAllRecord()
+}
+
+const previewRelease = () => {
+  if (!releaseContent.value) {
+    ElMessage.warning('请先输入发布内容')
+    return
+  }
+  ElMessageBox.alert(releaseContent.value, '发布内容预览', { confirmButtonText: '确定' })
+}
+
+// ========== 统计分析子系统 ==========
+const analysisResult = ref(null)
+const canSendToSubsystems = computed(() => !!currentDrillRecord.value)
+
+const showAnalysisReport = async () => {
+  if (!selectedPlanId.value) {
+    ElMessage.warning('请先选择演练计划')
+    return
+  }
   try {
-    d.id ? await updatePracticalPlan(d) : await savePracticalPlan(d)
-    ElMessage.success('保存成功'); planDialogVisible.value = false; loadData()
-  } catch (e) { ElMessage.error('保存失败') }
+    const res = await getDrillAnalysis(selectedPlanId.value)
+    if (isUnmounted) return
+    if (res.data) {
+      analysisResult.value = res.data
+      ElMessage.success('分析报表已加载')
+    } else {
+      ElMessage.info('暂无分析数据,请先完成演练并保存记录')
+    }
+  } catch (e) {
+    ElMessage.error('加载分析数据失败')
+  }
 }
-const handleSearchPlan = () => { loadData() }
 
-// ---------- 发送至子系统 ----------
-async function loadData() { try { const res = await getPracticalPlans(); const rows = res.data || []; if (rows.length) plans.value = rows.map(d => ({ id: d.id, name: d.name, category: d.category, target: d.target, project: d.project, eventDesc: d.eventDesc, personnel: d.personnel, status: d.status })) } catch (e) {} }
-onMounted(() => { loadData(); loadTemplates() })
+const exportAnalysisReport = async () => {
+  if (!selectedPlanId.value) {
+    ElMessage.warning('请先选择演练计划')
+    return
+  }
+  try {
+    const data = await exportDrillReport(selectedPlanId.value)
+    const blob = new Blob(['\ufeff' + data], { type: 'text/plain;charset=utf-8' })
+    const url = window.URL.createObjectURL(blob)
+    const a = document.createElement('a')
+    a.href = url
+    a.download = `演练分析报告_${currentPlan.value?.name || ''}.txt`
+    a.click()
+    window.URL.revokeObjectURL(url)
+    ElMessage.success('报告已导出')
+  } catch (e) {
+    ElMessage.error('导出失败')
+  }
+}
 
-const sendToSubsystem = () => {
-  ElMessage.success('已发送至统计分析子系统 & 应急评估子系统(伪)')
+const sendToAnalysisSubsystems = async () => {
+  if (!currentDrillRecord.value) {
+    ElMessage.warning('请先录入演练结果信息')
+    return
+  }
+  try {
+    const sendData = {
+      ...currentDrillRecord.value,
+      planId: selectedPlanId.value,
+      planName: currentPlan.value?.name,
+      dispatchInfo: { ...dispatchForm.value },
+      decisionInfo: { ...decisionForm.value },
+      releaseChannels: releaseChannels.value,
+      releaseContent: releaseContent.value,
+      monitoringInfo: monitoringInfo.value,
+      actions: drillActions.value
+    }
+    await sendDrillAnalysis(sendData)
+    if (isUnmounted) return
+    ElNotification({
+      title: '发送成功',
+      message: '演练分析数据已成功发送至统计分析和应急评估子系统',
+      type: 'success',
+      duration: 3000
+    })
+  } catch (e) {
+    ElMessage.error('发送失败')
+  }
+}
+
+// ========== 演练流程控制 ==========
+const startDrill = async (plan) => {
+  try {
+    await updatePlanStatus(plan.id, '进行中')
+    if (isUnmounted) return
+    plan.status = '进行中'
+    plan.progress = Math.max(plan.progress, 10)
+    selectedPlanId.value = plan.id
+    currentPlan.value = plan
+    isDrilling.value = true
+    currentDrillStatus.value = '进行中'
+    currentDrillInfo.value = { planId: plan.id, status: '进行中' }
+    drillActions.value = [
+      { time: new Date().toLocaleString(), type: 'primary', message: `开始演练:${plan.name}` }
+    ]
+    currentDrillRecord.value = {
+      drillProjectName: plan.name,
+      recordTime: new Date().toLocaleString(),
+      drillType: '实战演练',
+      participatingDept: plan.personnel || '—',
+      responsiblePerson: '—',
+      planId: plan.id,
+      status: '进行中',
+      reportTime: new Date().toLocaleString(),
+      reportMethod: '电话',
+      reporter: '',
+      contactPhone: '',
+      eventLocation: '',
+      eventDescription: plan.eventDesc || ''
+    }
+    dispatchForm.value = { instruction: '', forces: [], commandCenter: false, casualties: '' }
+    decisionForm.value = { responseLevel: '三级响应', command: '', evacuationRange: '', reinforcement: false, remark: '' }
+    releaseChannels.value = []
+    releaseContent.value = ''
+    monitoringInfo.value = []
+    ElMessage.success('演练已开始,请录入演练过程信息')
+  } catch (e) {
+    ElMessage.error('启动演练失败')
+  }
 }
+
+const stopDrill = async () => {
+  if (!currentDrillRecord.value) return
+  try {
+    await saveAllRecord()
+    if (selectedPlanId.value) {
+      await updatePlanStatus(selectedPlanId.value, '已完成')
+    }
+    if (isUnmounted) return
+    isDrilling.value = false
+    currentDrillStatus.value = '已完成'
+    currentDrillRecord.value.status = '已完成'
+    drillActions.value.push({ time: new Date().toLocaleString(), type: 'success', message: '演练结束,记录已保存' })
+    const plan = plans.value.find(p => p.id === selectedPlanId.value)
+    if (plan) {
+      plan.status = '已完成'
+      plan.progress = 100
+    }
+    ElMessage.success('演练已结束,记录已保存')
+  } catch (e) {
+    ElMessage.error('结束演练失败')
+  }
+}
+
+const viewDrillRecord = async (plan) => {
+  selectedPlanId.value = plan.id
+  currentPlan.value = plan
+  await loadDrillRecord(plan.id)
+  if (currentDrillRecord.value) {
+    currentDrillInfo.value = { planId: plan.id, status: currentDrillRecord.value.status || '已记录' }
+    currentDrillStatus.value = currentDrillRecord.value.status || '已记录'
+    if (!drillActions.value.length) {
+      drillActions.value = [
+        { time: currentDrillRecord.value.recordTime, type: 'info', message: `查看记录:${plan.name}` }
+      ]
+    }
+    ElMessage.success('已加载演练记录')
+  } else {
+    ElMessage.info('该计划暂无演练记录,请先开始演练')
+  }
+}
+
+// ========== 生命周期 ==========
+onMounted(async () => {
+  try {
+    await Promise.all([loadData(), loadTemplates()])
+  } catch (e) {
+    console.error(e)
+  }
+})
+
+onBeforeUnmount(() => {
+  isUnmounted = true
+})
 </script>
 
 <style scoped lang="scss">
-.drill-layout {
-  display: flex;
-  gap: 12px;
+/* ========== 实战演练管理容器 ==========
+.practical-drill-container {
+  padding: 20px;
   min-height: calc(100vh - 160px);
 }
 
-.col-left {
-  width: 25%;
+/* 顶部操作栏 */
+.drill-top-bar {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+  padding: 20px;
+  background: white;
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+
+  .top-left {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+
+    .page-title {
+      font-size: 24px;
+      font-weight: bold;
+      color: #303133;
+      margin: 0;
+    }
+  }
+}
+
+/* 主布局 */
+.drill-main-layout {
+  display: grid;
+  grid-template-columns: 350px 1fr;
+  gap: 20px;
+  min-height: calc(100vh - 220px);
+}
+
+.left-column {
   display: flex;
   flex-direction: column;
-  gap: 12px;
+  gap: 20px;
 }
 
-.col-right {
-  width: 75%;
+.right-column {
   display: flex;
   flex-direction: column;
-  gap: 12px;
+  gap: 20px;
 }
 
+/* 通用面板样式 */
 .panel {
-  border: 1px solid #e4e7ed;
-  border-radius: 6px;
-  background: #fff;
+  background: white;
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
   overflow: hidden;
 
   .panel-header {
     display: flex;
     justify-content: space-between;
     align-items: center;
-    padding: 10px 14px;
-    background: #f5f7fa;
-    border-bottom: 1px solid #e4e7ed;
+    padding: 14px 16px;
+    background: linear-gradient(135deg, #f5f7fa 0%, #e9ecef 100%);
+    border-bottom: 2px solid #e4e7ed;
     font-weight: bold;
-    font-size: 14px;
+    font-size: 15px;
     color: #303133;
-    flex-wrap: wrap;
     gap: 8px;
 
     span {
       display: flex;
       align-items: center;
-      gap: 6px;
+      gap: 8px;
     }
 
     .el-icon {
       color: #409eff;
+      font-size: 18px;
     }
   }
 
   .panel-body {
-    padding: 12px 14px;
+    padding: 16px;
+    max-height: 600px;
+    overflow-y: auto;
   }
 }
 
-// 模板列表
-.template-item {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  padding: 8px 0;
-  border-bottom: 1px dashed #ebeef5;
-
-  &:last-child { border-bottom: none; }
-
-  .template-info {
-    .template-name { font-size: 13px; color: #303133; font-weight: 500; }
-    .template-date { font-size: 12px; color: #909399; margin-top: 2px; }
-  }
-
-  .template-actions {
-    display: flex;
-    gap: 4px;
-    align-items: center;
+/* 模板卡片 */
+.template-panel, .plan-panel {
+  .panel-body {
+    max-height: none;
   }
 }
 
-// 计划搜索
-.search-bar {
-  display: flex;
-  gap: 8px;
-  align-items: center;
-  margin-bottom: 10px;
-}
+.template-list, .plan-list {
+  .template-card,
+  .plan-card {
+    border: 1px solid #ebeef5;
+    border-radius: 6px;
+    padding: 12px;
+    margin-bottom: 10px;
+    background: #fafafa;
+    transition: all 0.3s;
+    cursor: pointer;
+    position: relative;
+
+    &:hover {
+      transform: translateY(-2px);
+      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+      border-color: #409eff;
+    }
 
-// 计划卡片
-.plan-item {
-  border: 1px solid #ebeef5;
-  border-radius: 4px;
-  padding: 10px;
-  margin-bottom: 8px;
-  background: #fafafa;
+    &.active {
+      border-left: 4px solid #409eff;
+      background: #ecf5ff;
+    }
 
-  &:last-child { margin-bottom: 0; }
+    .template-card-header,
+    .plan-card-content {
+      margin-bottom: 10px;
 
-  .plan-header {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    margin-bottom: 6px;
+      h4 {
+        margin: 0 0 8px 0;
+        font-size: 16px;
+        font-weight: bold;
+        color: #303133;
+      }
+    }
 
-    .plan-name {
-      font-size: 13px;
+    .template-name,
+    .plan-title {
+      font-size: 15px;
       font-weight: bold;
       color: #303133;
+      margin-bottom: 6px;
     }
 
-    .plan-actions {
+    .template-meta,
+    .plan-category {
       display: flex;
-      gap: 2px;
+      gap: 12px;
+      margin-bottom: 8px;
+      font-size: 12px;
+      color: #909399;
+
+      .meta-item,
+      .plan-date {
+        display: flex;
+        align-items: center;
+        gap: 4px;
+      }
     }
-  }
 
-  .plan-details {
-    .plan-row {
-      font-size: 12px;
-      color: #606266;
-      line-height: 1.8;
+    .plan-info-list {
+      .info-item {
+        display: flex;
+        align-items: center;
+        gap: 6px;
+        font-size: 12px;
+        margin-bottom: 4px;
+
+        .info-text {
+          flex: 1;
+          color: #606266;
+        }
 
-      .plan-label {
-        color: #909399;
+        .info-value {
+          font-weight: bold;
+          color: #409eff;
+        }
+      }
+    }
+
+    .template-actions,
+    .plan-actions {
+      display: flex;
+      gap: 8px;
+      margin-top: 10px;
+      padding-top: 10px;
+      border-top: 1px dashed #ebeef5;
+
+      .el-button {
+        font-size: 12px;
       }
     }
   }
 }
 
-// 信息卡片网格
-.info-grid {
-  display: grid;
-  grid-template-columns: 1fr 1fr;
-  gap: 10px;
+.plan-status-badge {
+  position: absolute;
+  top: 10px;
+  right: 10px;
+  padding: 4px 10px;
+  border-radius: 12px;
+  font-size: 12px;
+  font-weight: bold;
+  
+  &.计划中 { background: #f0f0f0; color: #909399; }
+  &.进行中 { background: #fff4e6; color: #e6a23c; }
+  &.已完成 { background: #f0f9ff; color: #409eff; }
 }
 
-.info-card {
-  border: 1px solid #ebeef5;
-  border-radius: 4px;
-  padding: 12px;
-  background: #fafafa;
-
-  &.info-card-full {
-    grid-column: 1 / -1;
-  }
-
-  .info-title {
-    font-size: 13px;
-    font-weight: bold;
-    color: #303133;
-    margin-bottom: 8px;
-    display: flex;
-    align-items: center;
-    gap: 4px;
+.search-filter {
+  display: flex;
+  gap: 8px;
+  margin-bottom: 12px;
+}
 
-    .el-icon { color: #409eff; }
-  }
+/* 当前演练状态 */
+.current-drill-panel {
+  .drill-status-summary {
+    .status-badge.large {
+      display: inline-flex;
+      align-items: center;
+      gap: 8px;
+      padding: 12px 24px;
+      border-radius: 20px;
+      font-size: 16px;
+      font-weight: bold;
+      background: #f0f9ff;
+      color: #409eff;
+      margin-bottom: 16px;
+    }
 
-  .info-content {
-    .info-line {
-      font-size: 12px;
-      color: #606266;
-      line-height: 1.8;
+    .drill-timeline {
+      margin-top: 16px;
     }
   }
 }
 
-// 统计分析
-.analysis-result {
-  .analysis-time {
-    font-size: 12px;
-    color: #909399;
-    margin-bottom: 10px;
-  }
+/* 记录面板 */
+.record-panel {
+  .record-section {
+    margin-top: 20px;
+    padding-top: 16px;
+    border-top: 1px dashed #ebeef5;
 
-  .analysis-detail {
-    margin-bottom: 12px;
-    padding: 10px;
-    border: 1px solid #ebeef5;
-    border-radius: 4px;
-    background: #fafafa;
+    &:first-child {
+      margin-top: 0;
+      padding-top: 0;
+      border-top: none;
+    }
 
-    .detail-row {
-      font-size: 13px;
-      color: #606266;
-      line-height: 1.8;
+    .section-title-row {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 12px;
 
-      .detail-label {
-        color: #909399;
+      .section-title {
+        margin-bottom: 0;
+      }
+    }
+
+    .section-title {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      font-size: 14px;
+      font-weight: bold;
+      color: #303133;
+      margin-bottom: 12px;
+
+      .el-icon {
+        color: #409eff;
       }
     }
   }
+}
 
-  .analysis-eval {
-    .eval-title {
+/* 分析面板 */
+.analysis-panel {
+  .analysis-summary {
+    .analysis-time {
+      display: flex;
+      align-items: center;
+      gap: 8px;
       font-size: 13px;
+      color: #909399;
+      margin-bottom: 16px;
+    }
+
+    .analysis-basic, .analysis-task, .analysis-comprehensive {
+      margin-bottom: 20px;
+      padding: 16px;
+      border: 1px solid #ebeef5;
+      border-radius: 8px;
+      background: #fafafa;
+
+      &:last-child { margin-bottom: 0; }
+    }
+
+    .basic-title,
+    .task-title,
+    .comprehensive-title {
+      font-size: 15px;
       font-weight: bold;
       color: #303133;
-      margin-bottom: 8px;
+      margin-bottom: 12px;
+    }
+
+    /* 能力评估网格 */
+    .capability-grid {
+      display: grid;
+      grid-template-columns: repeat(2, 1fr);
+      gap: 16px;
+
+      .capability-item {
+        display: flex;
+        flex-direction: column;
+        gap: 6px;
+
+        .cap-label {
+          font-size: 12px;
+          color: #606266;
+        }
+
+        .cap-score {
+          font-size: 18px;
+          font-weight: bold;
+          color: #409eff;
+        }
+      }
+    }
+
+    /* 任务统计 */
+    .task-stats {
+      display: grid;
+      grid-template-columns: repeat(4, 1fr);
+      gap: 12px;
+
+      .task-stat-item {
+        display: flex;
+        flex-direction: column;
+        gap: 4px;
+        text-align: center;
+        padding: 12px;
+        background: white;
+        border-radius: 6px;
+
+        .stat-label {
+          font-size: 12px;
+          color: #909399;
+        }
+
+        .stat-value {
+          font-size: 20px;
+          font-weight: bold;
+
+          &.highlight-blue { color: #409eff; }
+          &.highlight-green { color: #67c23a; }
+          &.highlight-orange { color: #e6a23c; }
+          &.highlight-red { color: #f56c6c; }
+        }
+      }
     }
 
+    /* 综合评估 */
     .eval-grid {
       display: grid;
       grid-template-columns: repeat(4, 1fr);
-      gap: 10px;
+      gap: 12px;
+      margin-bottom: 16px;
+
+      .eval-item {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        padding: 16px;
+        background: white;
+        border-radius: 6px;
+
+        .eval-label {
+          font-size: 12px;
+          color: #909399;
+          margin-bottom: 8px;
+        }
+
+        .eval-value {
+          font-size: 18px;
+          font-weight: bold;
+
+          &.success { color: #67c23a; }
+          &.warning { color: #e6a23c; }
+          &.info { color: #409eff; }
+          &.danger { color: #f56c6c; }
+        }
+
+        &.level-high { border-left: 4px solid #67c23a; }
+        &.level-middle { border-left: 4px solid #e6a23c; }
+        &.level-medium { border-left: 4px solid #409eff; }
+        &.level-low { border-left: 4px solid #f56c6c; }
+      }
     }
 
-    .eval-item {
-      border: 1px solid #ebeef5;
-      border-radius: 4px;
-      padding: 10px;
-      text-align: center;
+    .eval-description,
+    .suggestion-section {
+      margin-top: 12px;
+      font-size: 13px;
+      line-height: 1.6;
+      color: #606266;
 
-      .eval-label {
-        font-size: 12px;
-        color: #909399;
+      strong {
+        color: #303133;
+        display: block;
         margin-bottom: 4px;
       }
 
-      .eval-value {
-        font-size: 16px;
-        font-weight: bold;
-        color: #303133;
+      p, ul {
+        margin: 4px 0;
+        padding-left: 20px;
+      }
 
-        &.highlight { color: #67c23a; }
+      li {
+        margin-bottom: 4px;
       }
     }
   }
 }
+
+/* 响应式布局 */
+@media (max-width: 1200px) {
+  .drill-main-layout {
+    grid-template-columns: 1fr;
+  }
+
+  .left-column,
+  .right-column {
+    min-height: auto;
+  }
+}
 </style>

+ 907 - 0
src/views/subSystem/drainage/DrainageWorkOrder.vue

@@ -0,0 +1,907 @@
+<!-- 排水管网工单管理 -->
+<template>
+  <div class="drainage-work-order">
+    <!-- 顶部搜索区 -->
+    <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 label="故障维修" :value="1" />
+              <el-option label="日常巡检" :value="2" />
+              <el-option label="设备保养" :value="3" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="优先级">
+            <el-select v-model="queryParams.orderLevel" placeholder="全部优先级" clearable style="width: 120px">
+              <el-option label="紧急" :value="1" />
+              <el-option label="一般" :value="2" />
+              <el-option label="低" :value="3" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="状态">
+            <el-select v-model="queryParams.orderStatus" placeholder="全部状态" clearable style="width: 120px">
+              <el-option label="待派单" :value="1" />
+              <el-option label="待接单" :value="2" />
+              <el-option label="已接单" :value="3" />
+              <el-option label="处理中" :value="4" />
+              <el-option label="已提交验收" :value="5" />
+              <el-option label="已结案" :value="6" />
+              <el-option label="已驳回" :value="7" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="关键词">
+            <el-input 
+              v-model="queryParams.keyword" 
+              placeholder="工单编号/设备编码" 
+              clearable 
+              style="width: 200px"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" @click="handleSearch">查询</el-button>
+            <el-button @click="handleReset">重置</el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </el-card>
+
+    <!-- 主容器 -->
+    <el-row :gutter="16" style="height: calc(100vh - 200px)">
+      <!-- 左侧:工单列表 -->
+      <el-col :span="8" style="height: 100%">
+        <el-card class="box-card" shadow="always">
+          <template #header>
+            <div class="card-header">
+              <span>工单列表</span>
+            </div>
+          </template>
+          
+          <!-- 工单列表 -->
+          <el-scrollbar style="flex: 1">
+            <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 
+                    v-if="order.orderStatus === 1" 
+                    type="warning" 
+                    size="small"
+                    effect="light"
+                    class="status-badge"
+                  >待派单</el-tag>
+                  <el-tag 
+                    v-else-if="order.orderStatus === 2" 
+                    type="info" 
+                    size="small"
+                    effect="light"
+                    class="status-badge"
+                  >待接单</el-tag>
+                  <el-tag 
+                    v-else-if="order.orderStatus === 3" 
+                    type="success" 
+                    size="small"
+                    effect="light"
+                    class="status-badge"
+                  >已接单</el-tag>
+                  <el-tag 
+                    v-else-if="order.orderStatus === 4" 
+                    type="danger" 
+                    size="small"
+                    effect="light"
+                    class="status-badge"
+                  >处理中</el-tag>
+                  <el-tag 
+                    v-else-if="order.orderStatus === 5" 
+                    type="success" 
+                    size="small"
+                    effect="light"
+                    class="status-badge"
+                  >已提交验收</el-tag>
+                  <el-tag 
+                    v-else-if="order.orderStatus === 6" 
+                    type="info" 
+                    size="small"
+                    effect="light"
+                    class="status-badge"
+                  >已结案</el-tag>
+                  <el-tag 
+                    v-else-if="order.orderStatus === 7" 
+                    type="danger" 
+                    size="small"
+                    effect="light"
+                    class="status-badge"
+                  >已驳回</el-tag>
+                </div>
+                <div class="order-body">
+                  <div class="order-tags">
+                    <el-tag :type="getLevelTag(order.orderLevel)" size="small" effect="plain">{{ getLevelLabel(order.orderLevel) }}</el-tag>
+                    <el-tag :type="getTypeTag(order.orderType)" size="small" effect="plain">{{ getTypeLabel(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="create-time">创建:{{ formatDate(order.createTime) }}</div>
+                </div>
+              </div>
+              <el-empty v-if="tableData.length === 0" description="暂无数据" :image-size="100"></el-empty>
+            </div>
+          </el-scrollbar>
+          
+          <!-- 分页 -->
+          <div class="pagination-wrapper">
+            <el-pagination
+              v-model:current-page="pageNum" 
+              v-model:page-size="pageSize"
+              :total="total"
+              layout="total, prev, pager, next"
+              :page-sizes="[10, 20, 50, 100]"
+              @size-change="handleSizeChange"
+              @current-change="handlePageChange"
+              class="pagination"
+            />
+          </div>
+        </el-card>
+      </el-col>
+      
+      <!-- 右侧:工单详情 -->
+      <el-col :span="16" style="height: 100%">
+        <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">
+            <!-- 标签页 -->
+            <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 
+                      v-if="selectedOrder.orderStatus === 1" 
+                      type="warning"
+                      size="small"
+                    >待派单</el-tag>
+                    <el-tag 
+                      v-else-if="selectedOrder.orderStatus === 2" 
+                      type="info"
+                      size="small"
+                    >待接单</el-tag>
+                    <el-tag 
+                      v-else-if="selectedOrder.orderStatus === 3" 
+                      type="success"
+                      size="small"
+                    >已接单</el-tag>
+                    <el-tag 
+                      v-else-if="selectedOrder.orderStatus === 4" 
+                      type="danger"
+                      size="small"
+                    >处理中</el-tag>
+                    <el-tag 
+                      v-else-if="selectedOrder.orderStatus === 5" 
+                      type="success"
+                      size="small"
+                    >已提交验收</el-tag>
+                    <el-tag 
+                      v-else-if="selectedOrder.orderStatus === 6" 
+                      type="info"
+                      size="small"
+                    >已结案</el-tag>
+                    <el-tag 
+                      v-else-if="selectedOrder.orderStatus === 7" 
+                      type="danger"
+                      size="small"
+                    >已驳回</el-tag>
+                  </el-descriptions-item>
+                  <el-descriptions-item label="关联案件编号">{{ selectedOrder.caseNo || '-' }}</el-descriptions-item>
+                  <el-descriptions-item label="设备编号">{{ selectedOrder.deviceCode || '-' }}</el-descriptions-item>
+                  <el-descriptions-item label="优先级">
+                    <el-tag 
+                      v-if="selectedOrder.orderLevel === 1" 
+                      type="danger"
+                      size="small"
+                    >紧急</el-tag>
+                    <el-tag 
+                      v-else-if="selectedOrder.orderLevel === 2" 
+                      type="warning"
+                      size="small"
+                    >一般</el-tag>
+                    <el-tag 
+                      v-else 
+                      type="info"
+                      size="small"
+                    >低</el-tag>
+                  </el-descriptions-item>
+                  <el-descriptions-item label="工单类型">{{ getOrderTypeText(selectedOrder.orderType) }}</el-descriptions-item>
+                  <el-descriptions-item label="问题描述" :span="2">{{ selectedOrder.orderDesc }}</el-descriptions-item>
+                  <el-descriptions-item label="创建时间">{{ formatDate(selectedOrder.createTime) }}</el-descriptions-item>
+                  <el-descriptions-item label="更新时间">{{ formatDate(selectedOrder.updateTime) }}</el-descriptions-item>
+                  <el-descriptions-item label="运维状态">在用</el-descriptions-item>
+                  <el-descriptions-item label="在线状态">在线</el-descriptions-item>
+                  <el-descriptions-item label="报警状态">正常</el-descriptions-item>
+                  <el-descriptions-item label="处置结果">{{ selectedOrder.repairResult || '-' }}</el-descriptions-item>
+                </el-descriptions>
+                
+                <!-- 工单图片 -->
+                <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="action-section">
+                  <h4 class="section-title">工单操作</h4>
+                  <div class="action-buttons">
+                    <el-button 
+                      v-if="canDispatch()" 
+                      type="primary" 
+                      icon="Download"
+                      @click="handleDispatch"
+                    >下派任务</el-button>
+                    <el-button 
+                      v-if="canVerify()" 
+                      type="warning" 
+                      icon="Check"
+                      @click="handleVerify"
+                    >核查</el-button>
+                    <el-button 
+                      v-if="canProcess()" 
+                      type="success" 
+                      icon="Edit"
+                      @click="handleProcess"
+                    >处理</el-button>
+                    <el-button 
+                      v-if="canComplete()" 
+                      type="danger" 
+                      icon="Checked"
+                      @click="handleComplete"
+                    >提交验收</el-button>
+                    <el-button 
+                      v-if="canReject()" 
+                      type="info" 
+                      icon="Close"
+                      @click="handleReject"
+                    >驳回</el-button>
+                  </div>
+                </div>
+              </el-tab-pane>
+              
+              <!-- 处理历史 -->
+              <el-tab-pane label="处理历史" name="history">
+                <el-timeline v-if="currentOrderLogs && currentOrderLogs.length > 0">
+                  <el-timeline-item 
+                    v-for="log in currentOrderLogs" 
+                    :key="log.logId" 
+                    :timestamp="formatDate(log.operTime)"
+                    placement="top"
+                    :type="getLogType(log.operType)"
+                    :size="'large'"
+                  >
+                    <el-card shadow="hover">
+                      <div class="timeline-content">
+                        <div class="operator-info">
+                          <span class="operator-name">{{ log.operUserName }}</span>
+                          <span class="oper-type">· {{ getOperTypeLabel(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"></el-empty>
+              </el-tab-pane>
+            </el-tabs>
+          </div>
+          
+          <el-empty v-else description="请从左侧选择工单" :image-size="150"></el-empty>
+        </el-card>
+      </el-col>
+    </el-row>
+    
+    <!-- 派单弹窗 -->
+    <el-dialog v-model="dispatchDialogVisible" title="下派任务 - 选择接单人" width="750px">
+      <div style="display: flex; gap: 16px; min-height: 350px;">
+        <!-- 左侧:部门树 -->
+        <div style="width: 280px; border-right: 1px solid #e4e7ed; padding-right: 12px;">
+          <el-input 
+            v-model="deptFilterText" 
+            placeholder="筛选部门" 
+            clearable 
+            size="small" 
+            style="margin-bottom: 8px"
+          />
+          <el-tree
+            ref="deptTreeRef"
+            :data="deptTreeData"
+            :props="{ label: 'deptName', children: 'children' }"
+            :filter-node-method="filterDeptNode"
+            node-key="deptId"
+            highlight-current
+            @node-click="handleDeptClick"
+            style="font-size: 14px"
+          />
+        </div>
+        <!-- 右侧:用户列表 -->
+        <div style="flex: 1; min-width: 0;">
+          <el-table 
+            :data="userList" 
+            border 
+            size="small" 
+            max-height="300" 
+            ref="userTableRef"
+            row-key="userId"
+            @selection-change="handleSelectionChange"
+            @row-click="handleRowClick"
+          >
+            <el-table-column type="selection" width="40" align="center" :reserve-selection="true" />
+            <el-table-column prop="userName" label="用户名" width="100" />
+            <el-table-column prop="nickName" label="昵称" width="100" />
+            <el-table-column prop="dept.deptName" label="部门" show-overflow-tooltip />
+          </el-table>
+          <div v-if="userList.length === 0" style="text-align: center; color: #909399; padding: 40px 0;">请先在左侧选择部门</div>
+        </div>
+      </div>
+      <el-form :model="dispatchForm" label-width="80px" style="margin-top: 16px">
+        <el-form-item label="备注"><el-input v-model="dispatchForm.remarks" placeholder="请输入派单备注" /></el-form-item>
+      </el-form>
+      <template #footer>
+      <span style="float: left; line-height: 32px; color: #606266;">
+        已选人员:<span v-if="selectedUserNames.length > 0" style="color: #409eff; font-weight: bold;">{{ selectedUserNames.join(', ') }}</span>
+        <span v-else style="color: #909399">未选择</span>
+        (<span v-if="dispatchForm.handlerIds && dispatchForm.handlerIds.length > 0">{{ dispatchForm.handlerIds.length }}</span><span v-else>0</span>)
+      </span>
+      <el-button @click="dispatchDialogVisible = false">取消</el-button>
+      <el-button type="primary" @click="submitDispatch">确认</el-button>
+    </template>
+    </el-dialog>
+    
+    <!-- 核查/处理弹窗 -->
+    <el-dialog v-model="actionDialogVisible" :title="actionTitle" width="600px">
+      <el-input v-model="actionFormData.solution" type="textarea" :rows="6" placeholder="请输入说明文字" />
+      <template #footer>
+        <el-button @click="actionDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitAction">确定</el-button>
+      </template>
+    </el-dialog>
+    
+    <!-- 完成/验收弹窗 -->
+    <el-dialog v-model="completeDialogVisible" title="提交验收" width="500px">
+      <el-form label-width="80px">
+        <el-form-item label="验收意见">
+          <el-input v-model="completeForm.result" type="textarea" :rows="4" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="completeDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="submitComplete">提交</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="DrainageWorkOrder">
+import { ElMessage } from 'element-plus'
+import ImagePreview from '@/components/ImagePreview/index.vue'
+import { listWorkOrder, getWorkOrderDetail, dispatchWorkOrder, verifyWorkOrder, processWorkOrder, completeWorkOrder } from '@/api/work/order.js'
+import { listDept } from '@/api/system/dept.js'
+import { listUser } from '@/api/system/user.js'
+import { handleTree } from '@/utils/ruoyi.js'
+
+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,
+  keyword: '',
+  // 排水管网工单:只查询排水类型设备工单
+  equipmentType: 'drainage'
+})
+
+// 选中的工单
+const selectedOrderId = ref(null)
+const selectedOrder = ref({})
+const activeTab = ref('info')
+const currentOrderLogs = ref([])
+
+// 工单图片列表(多个用逗号分隔)
+const workImageList = computed(() => {
+  if (!selectedOrder.value.workImage) return []
+  return selectedOrder.value.workImage.split(',').filter(img => img && img.trim())
+})
+
+// 派单相关
+const dispatchDialogVisible = ref(false)
+const dispatchForm = reactive({ handlerIds: [], remarks: '' })
+const deptTreeRef = ref(null)
+const userTableRef = ref(null)
+const deptTreeData = ref([])
+const deptFilterText = ref('')
+const userList = ref([])
+const selectedUserList = ref([]) // 存储选中的用户行数据
+
+// 部门筛选
+watch(deptFilterText, (val) => { deptTreeRef.value && deptTreeRef.value.filter(val) })
+function filterDeptNode(value, data) {
+  if (!value) return true
+  return data.deptName.includes(value)
+}
+
+// 加载部门树
+async function loadDeptTree() {
+  if (deptTreeData.value.length > 0) return
+  try {
+    const res = await listDept()
+    deptTreeData.value = handleTree(res.data || [], 'deptId')
+  } catch (e) { console.error('加载部门失败', 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.error('加载用户失败', e) }
+}
+
+// 选择变化时触发
+function handleSelectionChange(selection) {
+  selectedUserList.value = selection
+  dispatchForm.handlerIds = selection.map(item => item.userId)
+}
+
+// 点击行切换选中状态
+function handleRowClick(row) {
+  userTableRef.value && userTableRef.value.toggleRowSelection(row)
+}
+
+// 获取已选人员昵称(用于显示)
+const selectedUserNames = computed(() => {
+  return selectedUserList.value.map(item => item.nickName || item.userName)
+})
+
+/** 查询工单列表 */
+async function handleSearch() {
+  listLoading.value = true
+  try {
+    const res = await listWorkOrder(queryParams, pageNum.value, pageSize.value)
+    if (res.code === 200) {
+      tableData.value = res.data.records || []
+      total.value = res.data.total || 0
+    }
+  } catch (e) {
+    ElMessage.error('查询失败')
+  } finally {
+    listLoading.value = false
+  }
+}
+
+/** 重置搜索 */
+function handleReset() {
+  queryFormRef.value && queryFormRef.value.resetFields()
+  queryParams.orderType = null
+  queryParams.orderLevel = null
+  queryParams.keyword = ''
+  pageNum.value = 1
+  selectedOrderId.value = null
+  selectedOrder.value = {}
+  handleSearch()
+}
+
+/** 选择工单 */
+async function handleSelectOrder(order) {
+  selectedOrderId.value = order.orderId
+  selectedOrder.value = { ...order } // 克隆对象确保更新
+  activeTab.value = 'info'
+  currentOrderLogs.value = []
+  
+  // 加载工单详情和处理历史
+  await loadOrderDetail(order.orderId)
+}
+
+/** 分页变化 */
+function handleSizeChange() {
+  pageNum.value = 1
+  selectedOrderId.value = null
+  selectedOrder.value = {}
+  handleSearch()
+}
+
+function handlePageChange() {
+  selectedOrderId.value = null
+  selectedOrder.value = {}
+  handleSearch()
+}
+
+/** 派单、核查、处理、完成、驳回的判断函数(示例) */
+function canDispatch() { return selectedOrder.value.orderStatus === 1 }
+function canVerify() { return [2, 3].includes(selectedOrder.value.orderStatus) }
+function canProcess() { return [3, 4].includes(selectedOrder.value.orderStatus) }
+function canComplete() { return selectedOrder.value.orderStatus === 4 }
+function canReject() { return [1, 2].includes(selectedOrder.value.orderStatus) }
+
+function handleDispatch() {
+  if (!canDispatch()) {
+    ElMessage.warning('仅可对待派单工单进行派单操作')
+    return
+  }
+  Object.assign(dispatchForm, { handlerIds: [], remarks: '' })
+  selectedUserList.value = []
+  userList.value = []
+  loadDeptTree()
+  dispatchDialogVisible.value = true
+}
+
+async function submitDispatch() {
+  if (!dispatchForm.handlerIds || dispatchForm.handlerIds.length === 0) {
+    ElMessage.warning('请选择至少一个接单人')
+    return
+  }
+  try {
+    await dispatchWorkOrder({
+      orderId: selectedOrder.value.orderId,
+      handlerIds: dispatchForm.handlerIds, // 传递数组
+      remarks: dispatchForm.remarks
+    })
+    ElMessage.success('派单成功!已向 ' + dispatchForm.handlerIds.length + ' 人下发任务')
+    dispatchDialogVisible.value = false
+    handleSearch()
+  } catch (e) {
+    ElMessage.error('派单失败')
+  }
+}
+
+/** 核查、处理相关 */
+const actionDialogVisible = ref(false)
+const actionTitle = ref('')
+const actionFormData = reactive({ solution: '' })
+
+function handleVerify() {
+  if (![2, 3].includes(selectedOrder.value.orderStatus)) {
+    ElMessage.warning('当前工单不允许核查')
+    return
+  }
+  actionTitle.value = '核查工单'
+  actionFormData.solution = ''
+  actionDialogVisible.value = true
+}
+
+function handleProcess() {
+  if (![3, 4].includes(selectedOrder.value.orderStatus)) {
+    ElMessage.warning('当前工单不允许处理')
+    return
+  }
+  actionTitle.value = '处理工单'
+  actionFormData.solution = ''
+  actionDialogVisible.value = true
+}
+
+async function submitAction() {
+  if (!actionFormData.solution.trim()) {
+    ElMessage.warning('请填写说明')
+    return
+  }
+  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()
+  } catch (e) {
+    ElMessage.error('操作失败')
+  }
+}
+
+/** 完成/验收相关 */
+const completeDialogVisible = ref(false)
+const completeForm = reactive({ result: '' })
+
+function handleComplete() {
+  if (![4, 5].includes(selectedOrder.value.orderStatus)) {
+    ElMessage.warning('当前工单不允许提交验收')
+    return
+  }
+  completeForm.result = ''
+  completeDialogVisible.value = true
+}
+
+async function submitComplete() {
+  if (!completeForm.result.trim()) {
+    ElMessage.warning('请填写验收意见')
+    return
+  }
+  try {
+    await completeWorkOrder(selectedOrder.value.orderId, completeForm.result)
+    ElMessage.success('提交验收成功')
+    completeDialogVisible.value = false
+    handleSearch()
+  } catch (e) {
+    ElMessage.error('提交失败')
+  }
+}
+
+/** 驳回相关 */
+function handleReject() {
+  if (![1, 2].includes(selectedOrder.value.orderStatus)) {
+    ElMessage.warning('当前工单不允许驳回')
+    return
+  }
+  ElMessage.info('驳回功能待实现')
+  // TODO: 实现驳回逻辑
+}
+
+/** 辅助函数 */
+function getOrderTypeText(type) {
+  const map = { 1: '故障维修', 2: '日常巡检', 3: '设备保养' }
+  return map[type] || '-'
+}
+
+function getOperTypeLabel(type) {
+  const map = {
+    1: '创建工单', 2: '派单', 3: '接单', 4: '开始维修',
+    5: '提交验收', 6: '验收通过', 7: '驳回工单', 8: '申请延期'
+  }
+  return map[type] || '-'
+}
+
+function getLogType(type) {
+  const map = {
+    1: 'primary', 2: 'success', 3: 'warning', 4: 'danger',
+    5: 'success', 6: 'success', 7: 'danger', 8: 'warning'
+  }
+  return map[type] || ''
+}
+
+function formatDate(dateStr) {
+  if (!dateStr) return '-'
+  return new Date(dateStr).toLocaleString('zh-CN')
+}
+
+/** 优先级标签 */
+function getLevelLabel(level) {
+  const map = { 1: '紧急', 2: '一般', 3: '低' }
+  return map[level] || '未知'
+}
+function getLevelTag(level) {
+  const map = { 1: 'danger', 2: 'warning', 3: 'info' }
+  return map[level] || 'info'
+}
+
+/** 工单类型标签 */
+function getTypeLabel(type) {
+  const map = { 1: '故障维修', 2: '日常巡检', 3: '设备保养' }
+  return map[type] || '未知'
+}
+function getTypeTag(type) {
+  const map = { 1: 'danger', 2: 'primary', 3: 'success' }
+  return map[type] || 'info'
+}
+
+/** 加载工单详情和处理历史 */
+async function loadOrderDetail(orderId) {
+  try {
+    const res = await getWorkOrderDetail(orderId)
+    if (res.code === 200) {
+      selectedOrder.value = { ...selectedOrder.value, ...res.data.order }
+      currentOrderLogs.value = res.data.logs || []
+    }
+  } catch (e) {
+    ElMessage.error('加载工单详情失败')
+  }
+}
+
+onMounted(() => {
+  handleSearch()
+})
+</script>
+
+<style scoped lang="scss">
+.drainage-work-order {
+  padding: 0 16px 16px;
+  
+  .search-card {
+    margin-bottom: 16px;
+    
+    :deep(.el-card__body) {
+      padding: 16px;
+    }
+  }
+  
+  .search-form {
+    .el-form {
+      display: flex;
+      flex-wrap: wrap;
+      align-items: flex-start;
+      gap: 12px;
+      
+      .el-form-item {
+        margin-bottom: 0;
+        min-width: 150px;
+        
+        &:last-child {
+          margin-left: auto;
+        }
+      }
+    }
+  }
+  
+  .order-list {
+    overflow-y: auto;
+  }
+  
+  .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;
+    }
+  }
+  
+  .box-card {
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    
+    :deep(.el-card__body) {
+      flex: 1;
+      display: flex;
+      flex-direction: column;
+      overflow: hidden;
+    }
+  }
+  
+  .detail-tabs {
+    margin-top: 16px;
+  }
+  
+  .image-section {
+    margin-top: 24px;
+    padding-top: 24px;
+    border-top: 1px solid #e4e7ed;
+    
+    .image-list {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 12px;
+    }
+  }
+  
+  .action-section {
+    margin-top: 24px;
+    padding-top: 24px;
+    border-top: 1px solid #e4e7ed;
+    
+    .section-title {
+      font-size: 16px;
+      font-weight: bold;
+      margin-bottom: 16px;
+    }
+    
+    .action-buttons {
+      display: flex;
+      gap: 12px;
+      flex-wrap: wrap;
+      
+      .el-button {
+        min-width: 100px;
+      }
+    }
+  }
+  
+  .timeline-content {
+    .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>

+ 1 - 1
src/views/subSystem/drainage/jcsj/yjsdgl.vue

@@ -37,7 +37,7 @@
       <el-table-column label="易积水点数量" prop="waterloggingCount" min-width="100" />
       <el-table-column label="积水量" prop="waterVolume" min-width="100" />
       <el-table-column label="积水程度" min-width="120">
-        <template #default="{ row }">
+        <template #default="{ row }">D:\work1\city-life-line\src\views\subSystem\drainage\DrainageWorkOrder.vue
           <el-tag :type="getWaterloggingLevelType(row.waterloggingLevel)" size="small">
             {{ getWaterloggingLevelLabel(row.waterloggingLevel) }}
           </el-tag>

+ 45 - 5
src/views/subSystem/gas/gasjcyj/gxylyctx.vue

@@ -61,7 +61,7 @@
         <div class="alert-list realtime">
           <div
               v-for="alert in paginatedRealtime"
-              :key="alert.id"
+              :key="alert.id || alert.equipmentId"
               class="alert-item critical"
               @click="viewAlertDetail(alert)"
           >
@@ -88,8 +88,15 @@
             <el-empty description="暂无实时异常" :image-size="80" />
           </div>
         </div>
-        <div class="pagination-area" v-if="alarmStats.realtimeCount > pageSize">
-          <el-pagination small layout="prev, pager, next" :total="alarmStats.realtimeCount" :page-size="pageSize" v-model:current-page="realtimePage" />
+        <div class="pagination-area" v-if="alarmStats.realtimeCount > realtimePageSize">
+          <el-pagination
+            small
+            layout="prev, pager, next"
+            :total="alarmStats.realtimeCount"
+            :page-size="realtimePageSize"
+            v-model:current-page="realtimePage"
+            @update:current-page="handleRealtimePageChange"
+          />
         </div>
       </div>
 
@@ -139,6 +146,7 @@
               :total="filteredHistory.length"
               :page-size="historyPageSize"
               v-model:current-page="historyPage"
+              @update:current-page="handleHistoryPageChange"
           />
         </div>
       </div>
@@ -189,10 +197,12 @@ import * as echarts from 'echarts'
 import { ElMessage } from 'element-plus'
 import { getPressureAlarmEquipment, getPressureAlarmHistory, getPressureAlarmStats, resolvePressureAlarm, dispatchPressureAlarm } from '@/api/pipeNetwork/basic'
 
-// 从API加载
+// 从 API 加载
 const equipments = ref([])
 const selectedEquipment = ref(null)
 const historyAlerts = ref([])
+const realtimePage = ref(1)
+const realtimePageSize = 10
 const historyPage = ref(1)
 const historyPageSize = 8
 const historyTotal = ref(0)
@@ -223,6 +233,15 @@ async function loadHistory() {
     historyTotal.value = res.data?.total || 0
   } catch (e) { console.error(e) }
 }
+
+function handleRealtimePageChange(newPage) {
+  realtimePage.value = newPage
+}
+
+function handleHistoryPageChange(newPage) {
+  historyPage.value = newPage
+  loadHistory()
+}
 function selectEquipment(eq) {
   selectedEquipment.value = eq
   currentDeviceCode.value = eq.equipmentCode
@@ -237,7 +256,28 @@ const abnormalRate = computed(() => totalPoints.value > 0 ? ((alarmStats.value.r
 const avgResponseTime = computed(() => '--')
 const dateRange = ref([])
 const historySearch = ref('')
-const paginatedHistory = computed(() => historyAlerts.value)
+
+// 实时异常分页
+const paginatedRealtime = computed(() => {
+  const start = (realtimePage.value - 1) * realtimePageSize
+  return equipments.value.slice(start, start + realtimePageSize)
+})
+
+// 历史异常筛选和分页
+const filteredHistory = computed(() => {
+  if (!historySearch.value) return historyAlerts.value
+  const keyword = historySearch.value.toLowerCase()
+  return historyAlerts.value.filter(item => 
+    item.pointName?.toLowerCase().includes(keyword) || 
+    item.address?.toLowerCase().includes(keyword)
+  )
+})
+
+const paginatedHistory = computed(() => {
+  const start = (historyPage.value - 1) * historyPageSize
+  const filtered = filteredHistory.value
+  return filtered.slice(start, start + historyPageSize)
+})
 
 const currentAlert = ref(null)
 const detailDrawerVisible = ref(false)