Эх сурвалжийг харах

完善窨井-运维任务管理

zengy 10 цаг өмнө
parent
commit
078bae36c2

+ 219 - 785
src/views/subSystem/manholeCover/operation/operationTask.vue

@@ -1,843 +1,277 @@
-<!--运维任务管理 - 窨井设备异常派发中心-->
+<!--运维任务管理 - 窨井设备待派单工单(内联复用 workOrder/list.vue 的列表,固定查 order_status=0 + rootTypeId=4)-->
 <template>
-  <div class="terminal-dispatch">
-    <!-- 页面头部 -->
-    <div class="page-header">
-      <div class="header-left">
-        <el-icon><Warning /></el-icon>
-        <span class="title">窨井设备异常派发中心</span>
-        <el-tag type="danger" effect="dark">未处理异常 {{ abnormalCount }}</el-tag>
-      </div>
-      <div class="header-right">
-        <el-input
-          v-model="searchKeyword"
-          placeholder="搜索设备编号/名称/位置"
-          clearable
-          :prefix-icon="Search"
-          style="width: 240px"
-          @keyup.enter="currentPage = 1"
-          @clear="currentPage = 1"
-        />
-        <el-select v-model="deviceTypeFilter" placeholder="设备类型" clearable style="width: 130px" @change="currentPage = 1">
-          <el-option label="供水窨井" value="5" />
-          <el-option label="排水窨井" value="9" />
-          <el-option label="燃气窨井" value="10" />
-        </el-select>
-        <el-button type="primary" :icon="Refresh" @click="loadDevices" :loading="loading">刷新</el-button>
-      </div>
-    </div>
-
-    <!-- 统计卡片 -->
-    <div class="stats-cards">
-      <div class="stat-card critical">
-        <div class="stat-value">{{ criticalCount }}</div>
-        <div class="stat-label">严重异常</div>
-      </div>
-      <div class="stat-card warning">
-        <div class="stat-value">{{ warningCount }}</div>
-        <div class="stat-label">一般异常</div>
-      </div>
-      <div class="stat-card">
-        <div class="stat-value">{{ pendingCount }}</div>
-        <div class="stat-label">待派发</div>
-      </div>
-      <div class="stat-card">
-        <div class="stat-value">{{ dispatchedCount }}</div>
-        <div class="stat-label">已派发</div>
-      </div>
-    </div>
-
-    <!-- 批量操作栏 -->
-    <div class="batch-bar" v-if="selectedRows.length > 0">
-      <span>已选择 <strong>{{ selectedRows.length }}</strong> 个异常设备</span>
-      <el-button type="primary" @click="batchDispatch">批量提交工单</el-button>
-      <el-button @click="clearSelection">取消选择</el-button>
-    </div>
-
-    <!-- 主内容区:左侧异常设备列表 + 右侧详情 -->
-    <div class="main-layout">
-      <!-- 左侧异常设备列表 -->
-      <div class="terminal-list-panel">
-        <div class="list-header">
-          <span>异常设备列表({{ filteredTerminals.length }} 台)</span>
-          <el-checkbox v-model="selectAll" @change="handleSelectAll">全选</el-checkbox>
-        </div>
-        <div class="terminal-list" v-loading="loading">
-          <div
-            v-for="terminal in paginatedTerminals"
-            :key="terminal.id"
-            class="terminal-item"
-            :class="{
-              selected: selectedTerminal?.id === terminal.id,
-              critical: terminal.abnormalLevel === 'critical',
-              warning: terminal.abnormalLevel === 'warning'
-            }"
-            @click="selectTerminal(terminal)"
-          >
-            <el-checkbox
-              :model-value="selectedRows.includes(terminal.id)"
-              @change="(val) => toggleSelect(terminal.id, val)"
-              class="select-checkbox"
-              @click.stop
-            />
-            <div class="terminal-content">
-              <div class="terminal-header">
-                <span class="terminal-code">{{ terminal.code }}</span>
-                <el-tag :type="terminal.abnormalLevel === 'critical' ? 'danger' : 'warning'" size="small">
-                  {{ terminal.abnormalLevel === 'critical' ? '严重' : '一般' }}
-                </el-tag>
-              </div>
-              <div class="terminal-name">{{ terminal.name }}</div>
-              <div class="terminal-location">
-                <el-icon><Location /></el-icon> {{ terminal.locationShort }}
-              </div>
-              <div class="terminal-desc">{{ terminal.abnormalDesc }}</div>
-              <div class="terminal-time">异常时间: {{ terminal.abnormalTime || '—' }}</div>
-              <div class="terminal-footer">
-                <el-tag v-if="terminal.workOrderSubmitted" type="success" size="small" effect="plain">已派发</el-tag>
-                <el-tag v-else type="danger" size="small" effect="plain">待派发</el-tag>
-                <span class="type-tag">{{ terminal.deviceTypeName }}</span>
-              </div>
-            </div>
-          </div>
-          <el-empty v-if="!loading && filteredTerminals.length === 0" description="暂无异常设备" :image-size="80" />
-        </div>
-        <div class="pagination-area" v-if="filteredTerminals.length > pageSize">
-          <el-pagination
-            small
-            layout="prev, pager, next"
-            :total="filteredTerminals.length"
-            :page-size="pageSize"
-            v-model:current-page="currentPage"
+  <div class="operation-task">
+    <!-- 搜索栏 -->
+    <el-card shadow="never" class="search-card">
+      <el-form :model="queryParams" inline class="search-form" @submit.prevent>
+        <el-form-item label="关键字">
+          <el-input
+            v-model="queryParams.keyword"
+            placeholder="工单编号 / 设备编码 / 故障描述"
+            clearable
+            style="width: 240px"
+            @keyup.enter="handleQuery"
           />
-        </div>
-      </div>
-
-      <!-- 右侧详情面板 -->
-      <div class="detail-panel" v-if="selectedTerminal">
-        <el-tabs v-model="activeTab" @tab-change="onTabChange">
-          <!-- 设备信息 -->
-          <el-tab-pane label="设备信息" name="info">
-            <div class="info-section">
-              <div class="section-title">
-                <span><el-icon><Monitor /></el-icon> 设备基本信息</span>
-                <el-button
-                  type="primary"
-                  size="small"
-                  @click="openDispatchDialog(selectedTerminal)"
-                  :disabled="selectedTerminal.workOrderSubmitted"
-                >
-                  {{ selectedTerminal.workOrderSubmitted ? '已派发' : '提交工单' }}
-                </el-button>
-              </div>
-              <el-descriptions :column="2" border>
-                <el-descriptions-item label="设备编号">{{ selectedTerminal.code }}</el-descriptions-item>
-                <el-descriptions-item label="设备名称">{{ selectedTerminal.name }}</el-descriptions-item>
-                <el-descriptions-item label="设备类型">{{ selectedTerminal.deviceTypeName }}</el-descriptions-item>
-                <el-descriptions-item label="设备位置">{{ selectedTerminal.location }}</el-descriptions-item>
-                <el-descriptions-item label="异常级别">
-                  <el-tag :type="selectedTerminal.abnormalLevel === 'critical' ? 'danger' : 'warning'">
-                    {{ selectedTerminal.abnormalLevel === 'critical' ? '严重异常' : '一般异常' }}
-                  </el-tag>
-                </el-descriptions-item>
-                <el-descriptions-item label="派发状态">
-                  <el-tag :type="selectedTerminal.workOrderSubmitted ? 'success' : 'danger'">
-                    {{ selectedTerminal.workOrderSubmitted ? '已派发' : '待派发' }}
-                  </el-tag>
-                </el-descriptions-item>
-                <el-descriptions-item label="异常描述" :span="2">{{ selectedTerminal.abnormalDesc }}</el-descriptions-item>
-              </el-descriptions>
-            </div>
-
-            <!-- 实时监测数据 -->
-            <div class="info-section">
-              <div class="section-title"><el-icon><DataAnalysis /></el-icon> 实时监测数据</div>
-              <div class="monitor-grid">
-                <div class="monitor-item" :class="{ alert: selectedTerminal.status.tilt }">
-                  <div class="monitor-label">倾斜角度</div>
-                  <div class="monitor-value">{{ selectedTerminal.tiltAngle }}°</div>
-                  <div class="monitor-extra">阈值: {{ selectedTerminal.threshold }}°</div>
-                </div>
-                <div class="monitor-item" :class="{ alert: selectedTerminal.status.waterInfiltration }">
-                  <div class="monitor-label">水浸状态</div>
-                  <div class="monitor-value">{{ selectedTerminal.status.waterInfiltration ? '报警' : '正常' }}</div>
-                </div>
-                <div class="monitor-item" :class="{ alert: selectedTerminal.status.waterLevel }">
-                  <div class="monitor-label">水位状态</div>
-                  <div class="monitor-value">{{ selectedTerminal.status.waterLevel ? '超标' : '正常' }}</div>
-                </div>
-                <div class="monitor-item" :class="{ alert: selectedTerminal.status.lowBattery }">
-                  <div class="monitor-label">电量</div>
-                  <div class="monitor-value">{{ selectedTerminal.battery }}%</div>
-                </div>
-                <div class="monitor-item" :class="{ alert: selectedTerminal.status.lowSignal }">
-                  <div class="monitor-label">信号强度</div>
-                  <div class="monitor-value">{{ selectedTerminal.signal }}%</div>
-                </div>
-                <div class="monitor-item">
-                  <div class="monitor-label">温度</div>
-                  <div class="monitor-value">{{ selectedTerminal.temperature || '—' }}°C</div>
-                </div>
-              </div>
-              <div class="last-upload">最后上传时间: {{ selectedTerminal.lastTime || '—' }}</div>
-            </div>
-
-            <!-- 位置信息 -->
-            <div class="location-section">
-              <div class="section-title"><el-icon><MapLocation /></el-icon> 设备位置信息</div>
-              <div class="location-info">
-                <el-icon :size="20" color="#f56c6c"><Location /></el-icon>
-                <span>{{ selectedTerminal.location }}</span>
-                <span v-if="selectedTerminal.lng" class="coords">(经度: {{ selectedTerminal.lng }}, 纬度: {{ selectedTerminal.lat }})</span>
-              </div>
-            </div>
-          </el-tab-pane>
-
-          <!-- 处置记录 -->
-          <el-tab-pane label="处置记录" name="history">
-            <div class="history-section">
-              <div class="section-title"><el-icon><Document /></el-icon> 工单处置记录</div>
-              <div v-loading="loadingOrders">
-                <el-timeline v-if="workOrders.length">
-                  <el-timeline-item
-                    v-for="order in workOrders"
-                    :key="order.orderId"
-                    :timestamp="order.createTime || ''"
-                    :type="Number(order.orderStatus) >= 6 ? 'success' : 'primary'"
-                  >
-                    <div class="record-content">
-                      <div class="record-row"><strong>工单编号:</strong> {{ order.orderNo || '—' }}</div>
-                      <div class="record-row"><strong>优先级:</strong>
-                        <el-tag :type="orderLevelTagType(order.orderLevel)" size="small">{{ priorityText(order.orderLevel) }}</el-tag>
-                      </div>
-                      <div class="record-row"><strong>状态:</strong>
-                        <el-tag :type="orderStatusTagType(order.orderStatus)" size="small">{{ orderStatusText(order.orderStatus) }}</el-tag>
-                      </div>
-                      <div class="record-row" v-if="order.orderDesc"><strong>描述:</strong> {{ order.orderDesc }}</div>
-                    </div>
-                  </el-timeline-item>
-                </el-timeline>
-                <el-empty v-else description="暂无工单处置记录" :image-size="80" />
-              </div>
-            </div>
-          </el-tab-pane>
-        </el-tabs>
-      </div>
-
-      <!-- 未选中状态 -->
-      <div v-else class="empty-panel">
-        <el-empty description="请从左侧选择异常设备查看详情" :image-size="140" />
-      </div>
-    </div>
-
-    <!-- 提交工单对话框 -->
-    <el-dialog v-model="dispatchDialogVisible" title="提交工单" width="520px" @close="dispatchForm = { orderLevel: defaultOrderLevel(), orderDesc: '', owner: '' }">
-      <el-form :model="dispatchForm" label-width="100px">
-        <el-form-item label="设备名称">
-          <el-input :model-value="currentDispatchTerminal?.name" disabled />
-        </el-form-item>
-        <el-form-item label="异常描述">
-          <el-input type="textarea" :model-value="currentDispatchTerminal?.abnormalDesc" disabled :rows="2" />
         </el-form-item>
-
-        <el-form-item label="优先级" required>
-          <el-select v-model="dispatchForm.orderLevel" placeholder="选择优先级" style="width: 100%">
-            <el-option v-for="item in workOrderLevels" :key="item.value" :label="item.label" :value="Number(item.value)" />
-          </el-select>
-        </el-form-item>
-        <el-form-item label="问题描述" required>
-          <el-input type="textarea" v-model="dispatchForm.orderDesc" placeholder="填写问题描述、维修要求等" :rows="3" />
-        </el-form-item>
-        <el-form-item label="负责人">
-          <el-input v-model="dispatchForm.owner" placeholder="填写负责人姓名(选填)" />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <el-button @click="dispatchDialogVisible = false">取消</el-button>
-        <el-button type="primary" @click="confirmDispatch" :loading="submitting">确认提交</el-button>
-      </template>
-    </el-dialog>
-
-    <!-- 批量提交工单对话框 -->
-    <el-dialog v-model="batchDispatchDialogVisible" title="批量提交工单" width="450px">
-      <p>将向以下 {{ batchDispatchIds.length }} 个异常设备提交工单:</p>
-      <div class="batch-list">
-        <el-tag v-for="id in batchDispatchIds.slice(0, 5)" :key="id" size="small" style="margin: 4px">
-          {{ getTerminalCode(id) }}
-        </el-tag>
-        <span v-if="batchDispatchIds.length > 5">等{{ batchDispatchIds.length }}个设备</span>
-      </div>
-      <el-form style="margin-top: 16px" label-width="80px">
         <el-form-item label="优先级">
-          <el-select v-model="batchDispatchForm.orderLevel" style="width: 100%">
+          <el-select v-model="queryParams.orderLevel" placeholder="全部" clearable style="width: 120px">
             <el-option v-for="item in workOrderLevels" :key="item.value" :label="item.label" :value="Number(item.value)" />
           </el-select>
         </el-form-item>
-        <el-form-item label="问题描述">
-          <el-input type="textarea" v-model="batchDispatchForm.orderDesc" :rows="2" placeholder="批量工单描述" />
+        <el-form-item label="故障时间">
+          <el-date-picker
+            v-model="faultTimeRange"
+            type="datetimerange"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            start-placeholder="开始时间"
+            end-placeholder="结束时间"
+            style="width: 360px"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="Search" @click="handleQuery">查询</el-button>
+          <el-button icon="Refresh" @click="resetQuery">重置</el-button>
         </el-form-item>
       </el-form>
-      <template #footer>
-        <el-button @click="batchDispatchDialogVisible = false">取消</el-button>
-        <el-button type="primary" @click="confirmBatchDispatch" :loading="submitting">确认批量提交</el-button>
-      </template>
+    </el-card>
+
+    <el-card shadow="never" class="table-card">
+      <el-table v-loading="loading" :data="list" border stripe>
+        <el-table-column label="工单编号" prop="orderNo" min-width="180" show-overflow-tooltip />
+        <el-table-column label="故障描述" min-width="240">
+          <template #default="{ row }">
+            <el-tooltip
+              :content="row.orderDesc"
+              placement="top"
+              popper-class="order-desc-tooltip"
+              :show-after="200"
+              :disabled="!row.orderDesc"
+            >
+              <span class="desc-cell">{{ row.orderDesc || '-' }}</span>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+        <el-table-column label="优先级" width="100" align="center">
+          <template #default="{ row }">
+            <el-tag :type="orderLevelType(row.orderLevel)">{{ orderLevelText(row.orderLevel) }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="设备编码" prop="deviceCode" min-width="150" show-overflow-tooltip>
+          <template #default="{ row }">{{ row.deviceCode || '-' }}</template>
+        </el-table-column>
+        <el-table-column label="处置人" prop="receiveUserName" min-width="100" show-overflow-tooltip>
+          <template #default="{ row }">{{ row.receiveUserName || '-' }}</template>
+        </el-table-column>
+        <el-table-column label="故障时间" prop="faultTime" width="180" align="center">
+          <template #default="{ row }">{{ timeText(row.faultTime) }}</template>
+        </el-table-column>
+        <el-table-column label="计划完成时间" prop="planFinishTime" width="180" align="center">
+          <template #default="{ row }">{{ timeText(row.planFinishTime) }}</template>
+        </el-table-column>
+        <el-table-column label="状态" width="100" align="center">
+          <template #default="{ row }">
+            <el-tag :type="orderStatusType(row.orderStatus)">{{ orderStatusText(row.orderStatus) }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="160" align="center" fixed="right">
+          <template #default="{ row }">
+            <el-button link type="primary" @click="goDispatch(row)">派单</el-button>
+            <el-button link type="primary" @click="openHistory(row)">历史处置</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination
+        v-show="total > 0"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        :total="total"
+        @pagination="getList"
+      />
+    </el-card>
+
+    <!-- 历史处置记录对话框 -->
+    <el-dialog v-model="historyVisible" title="历史处置记录" width="760px" @close="historyList = []">
+      <el-table v-loading="historyLoading" :data="historyList" border stripe>
+        <el-table-column prop="orderNo" label="工单编号" min-width="180" show-overflow-tooltip />
+        <el-table-column label="故障描述" min-width="240">
+          <template #default="{ row }">
+            <span class="desc-cell">{{ row.orderDesc || '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="faultTime" label="故障时间" width="180" align="center">
+          <template #default="{ row }">{{ timeText(row.faultTime) }}</template>
+        </el-table-column>
+      </el-table>
+      <pagination
+        v-show="historyTotal > 0"
+        v-model:page="historyPageNum"
+        v-model:limit="historyPageSize"
+        :total="historyTotal"
+        @pagination="loadHistory"
+      />
     </el-dialog>
   </div>
 </template>
 
 <script setup>
-import { ref, computed, onMounted } from 'vue'
-import { Warning, Search, Refresh, Location, Monitor, MapLocation, Document, DataAnalysis } from '@element-plus/icons-vue'
-import { ElMessage } from 'element-plus'
-import { getManholeAlarmMonitorList, addManholeWorkOrder } from '@/api/pipeNetwork/basic'
-import { listWorkOrder } from '@/api/pipeNetwork/ops/workOrder'
+import { ref, reactive, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+import { listWorkOrder } from '@/api/pipeNetwork/ops/workOrder.js'
+import {
+  orderStatusText,
+  orderStatusType,
+  timeText
+} from '@/views/subSystem/pipeNetwork/ops/workOrder/js/workOrderDict.js'
 import { useWorkOrderLevelDict } from '@/views/subSystem/pipeNetwork/ops/workOrder/js/useWorkOrderLevelDict.js'
 
-const { workOrderLevels, defaultOrderLevel, orderLevelText: priorityText } = useWorkOrderLevelDict()
+defineOptions({ name: 'OperationTask' })
 
-// ==================== 常量 ====================
-const TYPE_MAP = { '5': '供水窨井', '9': '排水窨井', '10': '燃气窨井' }
-const LOW_BATTERY_THRESHOLD = 15
-const LOW_SIGNAL_THRESHOLD = 20
+const { workOrderLevels, orderLevelText, orderLevelType } = useWorkOrderLevelDict()
+const router = useRouter()
 
-// ==================== 状态 ====================
+// ==================== 列表(固定:待派单 order_status=0 + 窨井设备 rootTypeId=4)====================
 const loading = ref(false)
-const loadingOrders = ref(false)
-const submitting = ref(false)
-const devices = ref([])
-const searchKeyword = ref('')
-const deviceTypeFilter = ref('')
-const currentPage = ref(1)
-const pageSize = 8
-
-const selectedRows = ref([])
-const selectAll = ref(false)
-const selectedTerminal = ref(null)
-const activeTab = ref('info')
-
-const workOrders = ref([])
-
-const dispatchDialogVisible = ref(false)
-const currentDispatchTerminal = ref(null)
-const dispatchForm = ref({ orderLevel: defaultOrderLevel(), orderDesc: '', owner: '' })
-
-const batchDispatchDialogVisible = ref(false)
-const batchDispatchIds = ref([])
-const batchDispatchForm = ref({ orderLevel: defaultOrderLevel(), orderDesc: '' })
+const list = ref([])
+const total = ref(0)
+const faultTimeRange = ref([])
+
+const queryParams = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  keyword: '',
+  orderLevel: undefined,
+  faultTimeStart: '',
+  faultTimeEnd: ''
+})
 
-// ==================== 数据加载 ====================
-async function loadDevices() {
-  loading.value = true
-  try {
-    const keyword = searchKeyword.value?.trim()
-    const res = await getManholeAlarmMonitorList({ keyword: keyword || undefined })
-    const result = res.data || {}
-    const allDevices = (result.devices || []).map(item => normalizeDevice(item))
-    // 只保留有异常的设备
-    devices.value = allDevices.filter(d => d.hasAlert)
-    // 默认选中第一个
-    if (devices.value.length && !selectedTerminal.value) {
-      selectedTerminal.value = devices.value[0]
-    } else if (devices.value.length === 0) {
-      selectedTerminal.value = null
-    }
-  } catch (error) {
-    devices.value = []
-    const msg = error?.message || error?.msg || '未知错误'
-    ElMessage.error('设备数据加载失败: ' + msg)
-  } finally {
-    loading.value = false
+/** 统一构造查询入参:固定查待派单 + 窨井设备,不随查询条件透出 */
+function buildQueryParams() {
+  return {
+    ...queryParams,
+    orderStatus: '0',
+    rootTypeId: '4'
   }
 }
 
-async function loadWorkOrders(deviceId) {
-  if (!deviceId) return
-  loadingOrders.value = true
+async function getList() {
+  loading.value = true
   try {
-    const { data } = await listWorkOrder({ deviceId, pageNum: 1, pageSize: 20 })
-    workOrders.value = data?.records || data?.rows || []
-  } catch (error) {
-    workOrders.value = []
-    console.error('[operationTask] 工单记录加载失败:', error)
+    const res = await listWorkOrder(buildQueryParams())
+    const data = res.data || {}
+    list.value = data.records || []
+    total.value = data.total || 0
   } finally {
-    loadingOrders.value = false
-  }
-}
-
-// ==================== 设备数据规范化 ====================
-function normalizeDevice(raw) {
-  const md = raw?.manholeData || {}
-  const st = raw?.equipmentStatus || {}
-  const typeId = raw?.equipmentTypeId || ''
-
-  // 数值解析
-  const tiltAngle = parseFloat(md.tiltAngle) || 0
-  const angleThreshold = parseFloat(md.angleAlarmThreshold) || 15
-  const battery = parseFloat(md.batteryLevel) || 0
-  const signal = parseFloat(md.signalStrength) || 0
-  const temperature = md.temperatureValue ? parseFloat(md.temperatureValue).toFixed(1) : null
-  const isOnline = Number(st.onlineStatus) === 1
-
-  // 异常判定
-  const tilt = isOnline && tiltAngle > angleThreshold
-  const waterInfiltration = isOnline && `${md.waterInfiltrationAlarmStatus}` === '1'
-  const waterLevel = isOnline && `${md.waterLevelAlarmStatus}` === '1'
-  const lowBattery = battery > 0 && battery <= LOW_BATTERY_THRESHOLD
-  const lowSignal = signal > 0 && signal <= LOW_SIGNAL_THRESHOLD
-
-  // 告警等级
-  const isCritical = tilt || waterInfiltration || waterLevel
-  const isWarning = !isCritical && (lowBattery || lowSignal)
-  const hasAlert = isCritical || isWarning
-  const alertLevel = isCritical ? 'critical' : (isWarning ? 'warning' : null)
-
-  // 异常描述
-  const parts = []
-  if (tilt) parts.push(`倾斜角度${tiltAngle}°超过阈值${angleThreshold}°`)
-  if (waterInfiltration) parts.push('水浸报警')
-  if (waterLevel) parts.push('水位超标报警')
-  if (lowBattery) parts.push(`电量仅剩${battery}%`)
-  if (lowSignal) parts.push(`信号强度${signal}%`)
-
-  return {
-    id: raw?.equipmentId || raw?.equipmentCode || '',
-    code: raw?.equipmentCode || raw?.equipmentId || '',
-    equipmentId: raw?.equipmentId || '',
-    name: raw?.equipmentName || raw?.equipmentCode || '未命名设备',
-    typeId,
-    deviceTypeName: TYPE_MAP[typeId] || raw?.equipmentTypeName || '未知类型',
-    location: raw?.equipmentLocation || '暂无位置信息',
-    locationShort: (raw?.equipmentLocation || '暂无位置').slice(0, 20),
-    lng: raw?.longitude ?? null,
-    lat: raw?.latitude ?? null,
-    battery,
-    signal,
-    temperature,
-    tiltAngle,
-    threshold: angleThreshold,
-    status: { tilt, waterInfiltration, waterLevel, lowBattery, lowSignal },
-    hasAlert,
-    alertLevel,
-    abnormalLevel: alertLevel,
-    abnormalDesc: parts.join(';') || '设备状态正常',
-    abnormalTime: md.uploadTime || md.createTime || '',
-    lastTime: md.uploadTime || md.createTime || '',
-    workOrderSubmitted: !!raw?.workOrderSubmitted
-  }
-}
-
-// ==================== 计算属性 ====================
-const filteredTerminals = computed(() => {
-  let list = devices.value
-  if (searchKeyword.value) {
-    const kw = searchKeyword.value.toLowerCase()
-    list = list.filter(t =>
-      t.code.toLowerCase().includes(kw) ||
-      t.name.toLowerCase().includes(kw) ||
-      t.location.toLowerCase().includes(kw)
-    )
-  }
-  if (deviceTypeFilter.value) {
-    list = list.filter(t => t.typeId === deviceTypeFilter.value)
-  }
-  return list
-})
-
-const paginatedTerminals = computed(() => {
-  const start = (currentPage.value - 1) * pageSize
-  return filteredTerminals.value.slice(start, start + pageSize)
-})
-
-const abnormalCount = computed(() => devices.value.length)
-const criticalCount = computed(() => devices.value.filter(t => t.abnormalLevel === 'critical').length)
-const warningCount = computed(() => devices.value.filter(t => t.abnormalLevel === 'warning').length)
-const pendingCount = computed(() => devices.value.filter(t => !t.workOrderSubmitted).length)
-const dispatchedCount = computed(() => devices.value.filter(t => t.workOrderSubmitted).length)
-
-// ==================== 辅助函数 ====================
-const orderStatusText = (s) => ({
-  0: '待派单', 1: '待接单', 2: '已接单', 3: '处理中',
-  4: '待验收', 5: '已结案', 6: '延期中', 7: '已取消'
-}[s] || '—')
-const orderLevelTagType = (l) => ({ 0: 'danger', 1: 'warning', 2: 'info' }[l] || 'info')
-const orderStatusTagType = (s) => {
-  const n = Number(s)
-  if (n >= 6) return 'success'
-  if (n >= 3) return 'primary'
-  if (n >= 1) return 'warning'
-  return 'info'
-}
-
-// ==================== 选择操作 ====================
-const handleSelectAll = (val) => {
-  if (val) {
-    selectedRows.value = paginatedTerminals.value.map(t => t.id)
-  } else {
-    selectedRows.value = []
+    loading.value = false
   }
 }
 
-const toggleSelect = (id, val) => {
-  if (val) {
-    if (!selectedRows.value.includes(id)) selectedRows.value.push(id)
+function handleQuery() {
+  queryParams.pageNum = 1
+  if (faultTimeRange.value && faultTimeRange.value.length === 2) {
+    queryParams.faultTimeStart = faultTimeRange.value[0]
+    queryParams.faultTimeEnd = faultTimeRange.value[1]
   } else {
-    selectedRows.value = selectedRows.value.filter(i => i !== id)
-  }
-  selectAll.value = paginatedTerminals.value.length > 0 &&
-    paginatedTerminals.value.every(t => selectedRows.value.includes(t.id))
-}
-
-const clearSelection = () => {
-  selectedRows.value = []
-  selectAll.value = false
-}
-
-const selectTerminal = (terminal) => {
-  selectedTerminal.value = terminal
-  if (activeTab.value === 'history') {
-    loadWorkOrders(terminal.equipmentId)
-  }
-}
-
-const onTabChange = (tabName) => {
-  if (tabName === 'history' && selectedTerminal.value) {
-    loadWorkOrders(selectedTerminal.value.equipmentId)
+    queryParams.faultTimeStart = ''
+    queryParams.faultTimeEnd = ''
   }
+  getList()
 }
 
-const getTerminalCode = (id) => {
-  const terminal = devices.value.find(t => t.id === id)
-  return terminal?.code || id
+function resetQuery() {
+  queryParams.keyword = ''
+  queryParams.orderLevel = undefined
+  faultTimeRange.value = []
+  queryParams.faultTimeStart = ''
+  queryParams.faultTimeEnd = ''
+  handleQuery()
 }
 
-// ==================== 提交工单 ====================
-const openDispatchDialog = (terminal) => {
-  currentDispatchTerminal.value = terminal
-  dispatchForm.value = {
-    orderLevel: terminal.abnormalLevel === 'critical' && workOrderLevels.value.some(item => Number(item.value) === 0) ? 0 : defaultOrderLevel(),
-    orderDesc: terminal.abnormalDesc || '',
-    owner: ''
-  }
-  dispatchDialogVisible.value = true
-}
+onMounted(() => {
+  getList()
+})
 
-const confirmDispatch = async () => {
-  if (!dispatchForm.value.orderDesc?.trim()) {
-    ElMessage.warning('请填写问题描述')
-    return
-  }
-  if (!currentDispatchTerminal.value?.equipmentId) {
-    ElMessage.warning('设备信息缺失,无法提交工单')
-    return
-  }
-  submitting.value = true
+// ==================== 历史处置 ====================
+const historyVisible = ref(false)
+const historyLoading = ref(false)
+const historyList = ref([])
+const historyTotal = ref(0)
+const historyPageNum = ref(1)
+const historyPageSize = ref(20)
+const currentDeviceId = ref('')
+
+async function openHistory(row) {
+  currentDeviceId.value = row.deviceId
+  historyPageNum.value = 1
+  historyVisible.value = true
+  await loadHistory()
+}
+
+async function loadHistory() {
+  if (!currentDeviceId.value) return
+  historyLoading.value = true
   try {
-    const terminal = currentDispatchTerminal.value
-    const desc = dispatchForm.value.owner
-      ? `${dispatchForm.value.orderDesc}(负责人:${dispatchForm.value.owner})`
-      : dispatchForm.value.orderDesc
-    await addManholeWorkOrder({
-      deviceId: terminal.equipmentId,
-      orderLevel: dispatchForm.value.orderLevel,
-      orderDesc: desc
+    const res = await listWorkOrder({
+      deviceId: currentDeviceId.value,
+      pageNum: historyPageNum.value,
+      pageSize: historyPageSize.value
     })
-    // 标记已派发
-    terminal.workOrderSubmitted = true
-    ElMessage.success('工单已提交,相关部门将即刻处理')
-    dispatchDialogVisible.value = false
-    // 如果在处置记录tab,刷新工单列表
-    if (activeTab.value === 'history') {
-      loadWorkOrders(terminal.equipmentId)
-    }
-  } catch (error) {
-    const msg = error?.message || error?.msg || '未知错误'
-    ElMessage.error('工单提交失败: ' + msg)
+    const data = res.data || {}
+    historyList.value = data.records || []
+    historyTotal.value = data.total || 0
   } finally {
-    submitting.value = false
-  }
-}
-
-// ==================== 批量提交工单 ====================
-const batchDispatch = () => {
-  if (selectedRows.value.length === 0) {
-    ElMessage.warning('请先选择异常设备')
-    return
+    historyLoading.value = false
   }
-  batchDispatchIds.value = [...selectedRows.value]
-  batchDispatchForm.value = { orderLevel: defaultOrderLevel(), orderDesc: '' }
-  batchDispatchDialogVisible.value = true
 }
 
-const confirmBatchDispatch = async () => {
-  if (!batchDispatchForm.value.orderDesc?.trim()) {
-    ElMessage.warning('请填写问题描述')
-    return
-  }
-  submitting.value = true
-  try {
-    let successCount = 0
-    let failCount = 0
-    for (const id of batchDispatchIds.value) {
-      const terminal = devices.value.find(t => t.id === id)
-      if (!terminal || terminal.workOrderSubmitted) continue
-      try {
-        await addManholeWorkOrder({
-          deviceId: terminal.equipmentId,
-          orderLevel: batchDispatchForm.value.orderLevel,
-          orderDesc: batchDispatchForm.value.orderDesc
-        })
-        terminal.workOrderSubmitted = true
-        successCount++
-      } catch {
-        failCount++
-      }
-    }
-    if (successCount > 0) {
-      ElMessage.success(`成功提交 ${successCount} 个工单${failCount > 0 ? `,${failCount}个失败` : ''}`)
-    } else {
-      ElMessage.error('批量提交工单全部失败')
-    }
-    batchDispatchDialogVisible.value = false
-    clearSelection()
-  } catch (error) {
-    const msg = error?.message || error?.msg || '未知错误'
-    ElMessage.error('批量提交失败: ' + msg)
-  } finally {
-    submitting.value = false
-  }
+// ==================== 派单:按工单号跳转到本子系统(manholeCover)的工单处置页 ====================
+function goDispatch(row) {
+  router.push({
+    path: '/subSystem/manholeCover/caseHandling/workOrderDispose',
+    query: { keyword: row.orderNo || '', rootTypeId: '4' }
+  })
 }
-
-// ==================== 生命周期 ====================
-onMounted(() => {
-  loadDevices()
-})
 </script>
 
-<style scoped>
-.terminal-dispatch {
-  background: #f0f2f6;
-  min-height: 100vh;
-  padding: 20px;
-}
-
-.page-header {
-  background: white;
-  border-radius: 16px;
-  padding: 16px 24px;
-  margin-bottom: 20px;
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  flex-wrap: wrap;
-  gap: 12px;
-  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
-}
-
-.header-left {
-  display: flex;
-  align-items: center;
-  gap: 12px;
+<style scoped lang="scss">
+.operation-task {
+  padding: 16px;
+  background: #f5f7fa;
+  min-height: calc(100vh - 84px);
 }
 
-.header-left .title {
-  font-size: 20px;
-  font-weight: 600;
-  color: #1f2d3d;
+.search-card {
+  margin-bottom: 12px;
 }
 
-.header-right {
+/* 查询栏:flex + row-gap 控制换行行间距,清零 el-form-item 默认 margin-bottom */
+.search-form {
   display: flex;
-  gap: 12px;
-  align-items: center;
   flex-wrap: wrap;
-}
-
-.stats-cards {
-  display: flex;
-  gap: 16px;
-  margin-bottom: 20px;
-  flex-wrap: wrap;
-}
-
-.stat-card {
-  background: white;
-  border-radius: 12px;
-  padding: 12px 24px;
-  text-align: center;
-  min-width: 110px;
-  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
-}
+  row-gap: 4px;
 
-.stat-card.critical .stat-value { color: #f56c6c; }
-.stat-card.warning .stat-value { color: #e6a23c; }
-.stat-value { font-size: 28px; font-weight: bold; }
-.stat-label { font-size: 13px; color: #606266; margin-top: 4px; }
-
-.batch-bar {
-  background: #ecf5ff;
-  border-radius: 12px;
-  padding: 12px 20px;
-  margin-bottom: 20px;
-  display: flex;
-  align-items: center;
-  gap: 16px;
-}
-
-.main-layout {
-  display: flex;
-  gap: 20px;
+  :deep(.el-form-item) {
+    margin-bottom: 0;
+  }
 }
 
-.terminal-list-panel {
-  width: 420px;
-  background: white;
-  border-radius: 16px;
-  display: flex;
-  flex-direction: column;
+/* 单元格内省略:交给自定义 tooltip 展示全文 */
+.desc-cell {
+  display: inline-block;
+  max-width: 100%;
   overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  vertical-align: bottom;
 }
+</style>
 
-.list-header {
-  padding: 16px;
-  border-bottom: 1px solid #e9ecef;
-  display: flex;
-  justify-content: space-between;
-  font-weight: 600;
-}
-
-.terminal-list {
-  flex: 1;
-  overflow-y: auto;
-  padding: 12px;
-  max-height: calc(100vh - 280px);
-}
-
-.terminal-item {
-  display: flex;
-  align-items: flex-start;
-  gap: 10px;
-  padding: 12px;
-  margin-bottom: 10px;
-  border-radius: 12px;
-  background: #fafbfc;
-  border: 1px solid #e9ecef;
-  cursor: pointer;
-  transition: all 0.2s;
-}
-
-.terminal-item.selected {
-  background: #ecf5ff;
-  border-color: #409eff;
-}
-
-.terminal-item.critical { border-left: 3px solid #f56c6c; }
-.terminal-item.warning { border-left: 3px solid #e6a23c; }
-
-.select-checkbox { margin-top: 2px; }
-.terminal-content { flex: 1; }
-
-.terminal-header {
-  display: flex;
-  justify-content: space-between;
-  margin-bottom: 4px;
-}
-
-.terminal-code { font-weight: 600; font-size: 14px; }
-.terminal-name { font-size: 13px; color: #303133; margin-bottom: 4px; }
-.terminal-location { font-size: 12px; color: #6c757d; display: flex; align-items: center; gap: 4px; margin-bottom: 4px; }
-.terminal-desc { font-size: 12px; color: #f56c6c; margin-bottom: 4px; }
-.terminal-time { font-size: 11px; color: #aaa; }
-
-.terminal-footer {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-top: 6px;
-}
-
-.type-tag {
-  font-size: 11px;
-  color: #909399;
-}
-
-.detail-panel {
-  flex: 1;
-  background: white;
-  border-radius: 16px;
-  padding: 20px;
-  overflow-y: auto;
-  max-height: calc(100vh - 160px);
-}
-
-.empty-panel {
-  flex: 1;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  background: white;
-  border-radius: 16px;
-}
-
-.section-title {
-  font-weight: 600;
-  font-size: 16px;
-  margin: 16px 0 12px 0;
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-}
-
-.info-section { margin-bottom: 20px; }
-
-.monitor-grid {
-  display: grid;
-  grid-template-columns: repeat(3, 1fr);
-  gap: 12px;
-}
-
-.monitor-item {
-  background: #f5f7fa;
-  border-radius: 8px;
-  padding: 12px;
-  text-align: center;
-  border: 1px solid #e9ecef;
-}
-
-.monitor-item.alert {
-  background: #fef0f0;
-  border-color: #f56c6c;
-}
-
-.monitor-label { font-size: 12px; color: #909399; margin-bottom: 4px; }
-.monitor-value { font-size: 18px; font-weight: 600; color: #303133; }
-.monitor-item.alert .monitor-value { color: #f56c6c; }
-.monitor-extra { font-size: 11px; color: #aaa; margin-top: 2px; }
-
-.last-upload {
-  margin-top: 10px;
-  font-size: 12px;
-  color: #909399;
-}
-
-.location-section { margin-bottom: 20px; }
-
-.location-info {
-  display: flex;
-  align-items: center;
-  gap: 8px;
-  background: #f5f7fa;
-  border-radius: 8px;
-  padding: 16px;
-  font-size: 14px;
-  color: #303133;
+<style lang="scss">
+/* 故障描述悬浮提示:popper 挂在 body 下不受 scoped 限制,单独用全局样式块 */
+.el-popper.order-desc-tooltip {
+  max-width: 420px;
+  line-height: 1.6;
+  white-space: normal;
+  word-break: break-all;
 }
-
-.location-info .coords { color: #909399; font-size: 12px; }
-
-.record-content { font-size: 13px; }
-.record-row { margin-bottom: 4px; }
-
-.pagination-area { padding: 12px; text-align: center; border-top: 1px solid #eee; }
-
-.batch-list { max-height: 150px; overflow-y: auto; }
 </style>