소스 검색

排水系统

LAPTOP-JI2IUVG1\26646 2 주 전
부모
커밋
83ae1061f7

+ 124 - 0
src/api/drainage.js

@@ -64,3 +64,127 @@ export function updateWaterlogging(data) {
 export function deleteWaterlogging(id) {
   return request({ url: '/api/drainage/waterlogging/' + id, method: 'delete' })
 }
+
+// ============ 监测设备管理 ============
+export function getEquipmentPage(pageNum, pageSize, params) {
+  return request({ url: '/api/drainage/equipment/base/page', method: 'get', params: { pageNum, pageSize, ...params } })
+}
+export function getEquipmentById(id) {
+  return request({ url: '/api/drainage/equipment/base/' + id, method: 'get' })
+}
+export function getEquipmentList() {
+  return request({ url: '/api/drainage/equipment/base/list', method: 'get' })
+}
+export function addEquipment(data) {
+  return request({ url: '/api/drainage/equipment/base', method: 'post', data })
+}
+export function updateEquipment(data) {
+  return request({ url: '/api/drainage/equipment/base', method: 'put', data })
+}
+export function deleteEquipment(id) {
+  return request({ url: '/api/drainage/equipment/base/' + id, method: 'delete' })
+}
+export function batchDeleteEquipment(ids) {
+  return request({ url: '/api/drainage/equipment/base/batch', method: 'delete', data: ids })
+}
+export function getEquipmentTypeList() {
+  return request({ url: '/api/drainage/equipment/type/list', method: 'get' })
+}
+
+// ============ 设备状态 ============
+export function addEquipmentStatus(data) {
+  return request({ url: '/api/drainage/equipment/status', method: 'post', data })
+}
+export function updateEquipmentStatus(data) {
+  return request({ url: '/api/drainage/equipment/status', method: 'put', data })
+}
+
+// ============ 预警管理 ============
+
+// 分页查询当前预警(今天的预警)
+export function getWarningCurrentPage(pageNum, pageSize, params) {
+  return request({
+    url: '/api/drainage/warning/current/page',
+    method: 'get',
+    params: { pageNum, pageSize, ...params }
+  })
+}
+
+// 分页查询历史预警
+export function getWarningHistoryPage(pageNum, pageSize, params) {
+  return request({
+    url: '/api/drainage/warning/history/page',
+    method: 'get',
+    params: { pageNum, pageSize, ...params }
+  })
+}
+
+// 查询预警详情
+export function getWarningById(id) {
+  return request({
+    url: `/api/drainage/warning/${id}`,
+    method: 'get'
+  })
+}
+
+// 发布预警
+export function addWarning(data) {
+  return request({
+    url: '/api/drainage/warning',
+    method: 'post',
+    data
+  })
+}
+
+// 修改预警
+export function updateWarning(data) {
+  return request({
+    url: '/api/drainage/warning',
+    method: 'put',
+    data
+  })
+}
+
+// 更新预警状态
+export function updateWarningStatus(id, data) {
+  return request({
+    url: `/api/drainage/warning/${id}/status`,
+    method: 'put',
+    params: data
+  })
+}
+
+// 删除预警
+export function deleteWarning(id) {
+  return request({
+    url: `/api/drainage/warning/${id}`,
+    method: 'delete'
+  })
+}
+
+// 发布预警(带人员)
+export function publishWarning(data) {
+  return request({
+    url: '/api/drainage/warning/publish',
+    method: 'post',
+    data
+  })
+}
+
+// 处理预警(指定人员处理,status 0→1)
+export function firstApproveWarning(id, data) {
+  return request({
+    url: `/api/drainage/warning/${id}/firstApprove`,
+    method: 'put',
+    params: data
+  })
+}
+
+// 审批预警(管理员审批,status 1→2)
+export function finalApproveWarning(id, data) {
+  return request({
+    url: `/api/drainage/warning/${id}/finalApprove`,
+    method: 'put',
+    params: data
+  })
+}

+ 3 - 1
src/store/modules/user.js

@@ -11,7 +11,8 @@ const useUserStore = defineStore(
       name: '',
       avatar: '',
       roles: [],
-      permissions: []
+      permissions: [],
+      deptId: ''
     }),
     actions: {
       // 登录
@@ -46,6 +47,7 @@ const useUserStore = defineStore(
             this.id = user.userId
             this.name = user.userName
             this.avatar = avatar
+            this.deptId = user.deptId
             resolve(res)
           }).catch(error => {
             reject(error)

+ 311 - 181
src/views/subSystem/drainage/jcbj/bjqd.vue

@@ -2,47 +2,55 @@
   <div class="app-container">
     <!-- 搜索区 -->
     <el-form :model="queryParams" ref="queryRef" :inline="true" class="search-form">
-      <el-form-item label="设备ID">
-        <el-input v-model="queryParams.deviceId" placeholder="请输入" clearable style="width: 180px" />
+      <el-form-item label="设备名称">
+        <el-input v-model="queryParams.equipmentName" placeholder="请输入设备名称" clearable style="width: 180px" />
       </el-form-item>
-      <el-form-item label="所属区域">
-        <el-select v-model="queryParams.area" placeholder="请选择" clearable style="width: 160px">
-          <el-option v-for="a in areaOptions" :key="a" :label="a" :value="a" />
+      <el-form-item label="报警等级">
+        <el-select v-model="queryParams.warningLevel" placeholder="请选择" clearable style="width: 160px">
+          <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-form-item label="处理状态">
         <el-select v-model="queryParams.status" placeholder="请选择" clearable style="width: 140px">
-          <el-option label="未处理" value="未处理" />
-          <el-option label="处理中" value="处理中" />
-          <el-option label="已处理" value="已处理" />
-          <el-option label="已关闭" value="已关闭" />
+          <el-option label="未下派" :value="-1" />
+          <el-option label="待处理" :value="0" />
+          <el-option label="已处理" :value="1" />
         </el-select>
       </el-form-item>
+      <el-form-item label="时间范围">
+        <el-date-picker
+          v-model="queryParams.dateRange"
+          type="daterange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          style="width: 240px"
+          value-format="YYYY-MM-DD"
+        />
+      </el-form-item>
       <el-form-item>
         <el-button type="primary" @click="handleQuery">搜索</el-button>
-        <el-button @click="handleClear">清除</el-button>
+        <el-button @click="resetQuery">重置</el-button>
       </el-form-item>
     </el-form>
 
     <!-- 数据表格 -->
-    <el-table :data="filteredList" border style="width: 100%" size="default">
-      <el-table-column label="档案编号" prop="archiveNo" min-width="130" />
-      <el-table-column label="所属区域" prop="area" min-width="100" />
-      <el-table-column label="报警时间" prop="alarmTime" min-width="160" />
-      <el-table-column label="所属类型" prop="type" min-width="90" align="center">
-        <template #default="{ row }">
-          <span :class="typeClass(row.type)">{{ row.type }}</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="报警等级" prop="level" min-width="90" align="center">
+    <el-table :data="tableData" v-loading="loading" border style="width: 100%" size="default">
+      <el-table-column label="报警编号" prop="id" min-width="80" align="center" />
+      <el-table-column label="设备名称" prop="equipmentName" min-width="140" show-overflow-tooltip />
+      <el-table-column label="报警时间" prop="warningTime" min-width="160" />
+      <el-table-column label="报警等级" min-width="90" align="center">
         <template #default="{ row }">
-          <span :class="levelClass(row.level)">{{ row.level }}</span>
+          <span :class="levelClass(row.warningLevel)">{{ levelText(row.warningLevel) }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="地址" prop="address" min-width="180" show-overflow-tooltip />
-      <el-table-column label="处理状态" prop="status" min-width="90" align="center">
+      <el-table-column label="报警内容" prop="content" min-width="180" show-overflow-tooltip />
+      <el-table-column label="位置" prop="location" min-width="180" show-overflow-tooltip />
+      <el-table-column label="处理状态" min-width="90" align="center">
         <template #default="{ row }">
-          <el-tag :type="statusTagType(row.status)" size="small" effect="plain">{{ row.status }}</el-tag>
+          <el-tag :type="statusTagType(row.status)" size="small" effect="plain">{{ statusText(row.status) }}</el-tag>
         </template>
       </el-table-column>
       <el-table-column label="操作" width="80" align="center">
@@ -52,219 +60,294 @@
       </el-table-column>
     </el-table>
 
+    <!-- 分页 -->
+    <div class="pagination-wrap">
+      <el-pagination
+        v-model:current-page="pageNum"
+        v-model:page-size="pageSize"
+        :page-sizes="[10, 20, 50, 100]"
+        :total="total"
+        layout="total, sizes, prev, pager, next, jumper"
+        @size-change="handleSizeChange"
+        @current-change="handlePageChange"
+      />
+    </div>
+
     <!-- 查看详情弹窗 -->
-    <el-dialog v-model="dialogVisible" title="异常管理" width="680px" :before-close="handleClose">
+    <el-dialog v-model="dialogVisible" title="报警详情" width="680px" :before-close="handleClose">
       <div class="detail-panel">
         <div class="detail-top">
           <div class="detail-image">
             <el-icon :size="48" color="#b0c4de"><Monitor /></el-icon>
           </div>
           <div class="detail-info">
-            <div class="detail-number">编号: {{ currentRow.archiveNo }}</div>
+            <div class="detail-number">编号: {{ currentRow.id }}</div>
             <el-row :gutter="16">
               <el-col :span="12">
                 <div class="info-item">
-                  <span class="info-label">名称</span>
-                  <span class="info-value">{{ currentRow.deviceName }}</span>
+                  <span class="info-label">设备名称</span>
+                  <span class="info-value">{{ currentRow.equipmentName }}</span>
                 </div>
               </el-col>
               <el-col :span="12">
                 <div class="info-item">
-                  <span class="info-label">ID</span>
-                  <span class="info-value">{{ currentRow.deviceId }}</span>
+                  <span class="info-label">报警时间</span>
+                  <span class="info-value">{{ currentRow.warningTime }}</span>
                 </div>
               </el-col>
             </el-row>
             <el-row :gutter="16">
               <el-col :span="12">
                 <div class="info-item">
-                  <span class="info-label">型号</span>
-                  <span class="info-value">{{ currentRow.model }}</span>
+                  <span class="info-label">报警等级</span>
+                  <span class="info-value">
+                    <span :class="levelClass(currentRow.warningLevel)">{{ levelText(currentRow.warningLevel) }}</span>
+                  </span>
                 </div>
               </el-col>
               <el-col :span="12">
                 <div class="info-item">
-                  <span class="info-label">异常日期</span>
-                  <span class="info-value">{{ currentRow.alarmTime }}</span>
+                  <span class="info-label">处理状态</span>
+                  <span class="info-value">
+                    <el-tag :type="statusTagType(currentRow.status)" size="small" effect="plain">{{ statusText(currentRow.status) }}</el-tag>
+                  </span>
                 </div>
               </el-col>
             </el-row>
             <el-row :gutter="16">
               <el-col :span="12">
                 <div class="info-item">
-                  <span class="info-label">所属单位</span>
-                  <span class="info-value">{{ currentRow.unit }}</span>
+                  <span class="info-label">当前值</span>
+                  <span class="info-value">{{ currentRow.currentValue || '-' }}</span>
                 </div>
               </el-col>
+              <el-col :span="12">
+                <div class="info-item">
+                  <span class="info-label">阈值</span>
+                  <span class="info-value">{{ currentRow.thresholdValue || '-' }}</span>
+                </div>
+              </el-col>
+            </el-row>
+            <el-row :gutter="16">
               <el-col :span="12">
                 <div class="info-item">
                   <span class="info-label">详细地址</span>
-                  <span class="info-value">{{ currentRow.address }}</span>
+                  <span class="info-value">{{ currentRow.location }}</span>
+                </div>
+              </el-col>
+              <el-col :span="12">
+                <div class="info-item">
+                  <span class="info-label">报警类型</span>
+                  <span class="info-value">{{ currentRow.warningType || '-' }}</span>
                 </div>
               </el-col>
             </el-row>
-            <div class="info-item">
-              <span class="info-label">异常类型</span>
-              <span class="info-value">
-                <span :class="typeClass(currentRow.type)">{{ currentRow.type }}</span>
-                <span style="margin-left: 8px;">{{ currentRow.level }}</span>
-              </span>
-            </div>
             <div class="info-item desc-item">
               <span class="info-label">描述</span>
-              <span class="info-value desc-text">{{ currentRow.description }}</span>
+              <span class="info-value desc-text">{{ currentRow.content }}</span>
             </div>
           </div>
         </div>
       </div>
 
-      <!-- 底部操作区 -->
-      <div class="detail-footer">
-        <div class="dispatch-row">
-          <span class="dispatch-label">审核下发</span>
-          <el-select v-model="dispatchPerson" placeholder="请选择人员" style="flex: 1; margin-left: 12px;">
-            <el-option v-for="p in personOptions" :key="p" :label="p" :value="p" />
-          </el-select>
-        </div>
-        <el-button type="primary" style="width: 100%; margin-top: 14px;" @click="handleDispatch">下派</el-button>
-      </div>
+
     </el-dialog>
   </div>
 </template>
 
 <script setup name="Bjqd">
-import { ref, computed } from 'vue'
-import { ElMessage } from 'element-plus'
+import { ref, computed, onMounted } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
 import { Monitor } from '@element-plus/icons-vue'
+import { listUser, deptTreeSelect } from '@/api/system/user'
+import { getEquipmentPage, getWarningCurrentPage, updateWarning, addWarning, firstApproveWarning } from '@/api/drainage'
+import useUserStore from '@/store/modules/user'
+
+// ==================== 用户信息 ====================
+const userStore = useUserStore()
+const isAdmin = computed(() => userStore.roles && userStore.roles.includes('admin'))
 
 // ==================== 搜索参数 ====================
 const queryParams = ref({
-  deviceId: '',
-  area: '',
-  status: ''
+  equipmentName: '',
+  warningLevel: '',
+  status: '',
+  dateRange: []
 })
-
-const areaOptions = ['沅陵北路', '沅陵南路', '沅陵东路', '沅陵西路', '沅陵中路']
+const queryRef = ref(null)
 
 function handleQuery() {
-  // computed 自动过滤
+  pageNum.value = 1
+  loadData()
 }
 
-function handleClear() {
-  queryParams.value = { deviceId: '', area: '', status: '' }
+function resetQuery() {
+  queryParams.value = { equipmentName: '', warningLevel: '', status: '', dateRange: [] }
+  pageNum.value = 1
+  loadData()
 }
 
-// ==================== 表格数据 ====================
-const tableData = ref([
-  {
-    id: 1, archiveNo: 'BJ20250101001', area: '沅陵北路', alarmTime: '2025-12-31 08:15:30',
-    type: '管线', level: '严重', address: '**市沅陵北路1号排水口',
-    status: '未处理', deviceId: 'YL-001', deviceName: '液位传感器1号', model: 'YW-200A',
-    unit: 'XX排水公司', description: '沅陵北路1号排水口液位传感器检测到液位严重超限,当前液位3.5m,超过阈值3.0m,需立即安排人员前往现场排查管网是否存在堵塞或溢流风险。'
-  },
-  {
-    id: 2, archiveNo: 'BJ20250101002', area: '沅陵南路', alarmTime: '2025-12-31 09:22:15',
-    type: '管点', level: '重要', address: '**市沅陵南路2号窨井',
-    status: '处理中', deviceId: 'LL-012', deviceName: '流量计12号', model: 'LL-300B',
-    unit: 'XX排水公司', description: '沅陵南路2号窨井流量计检测到流量异常升高,当前流量12.8m³/h,超过阈值10.0m³/h,疑似管网渗漏或雨水倒灌,需核实原因。'
-  },
-  {
-    id: 3, archiveNo: 'BJ20250101003', area: '沅陵东路', alarmTime: '2025-12-30 14:05:42',
-    type: '窨井', level: '一般', address: '**市沅陵东路3号管点',
-    status: '已处理', deviceId: 'YL-005', deviceName: '雨量计5号', model: 'YJ-100C',
-    unit: 'XX排水公司', description: '沅陵东路3号管点雨量计检测到降雨量超标,当前58mm/h,超过阈值50mm/h,需关注该区域排水系统运行状态。'
-  },
-  {
-    id: 4, archiveNo: 'BJ20250101004', area: '沅陵西路', alarmTime: '2025-12-30 16:33:08',
-    type: '管线', level: '严重', address: '**市沅陵西路4号泵站',
-    status: '未处理', deviceId: 'YL-008', deviceName: '液位传感器8号', model: 'YW-200A',
-    unit: 'XX排水公司', description: '沅陵西路4号泵站液位传感器检测到液位严重超限,当前液位4.2m,超过阈值3.0m,存在溢流风险,需紧急处理。'
-  },
-  {
-    id: 5, archiveNo: 'BJ20250101005', area: '沅陵北路', alarmTime: '2025-12-29 10:18:55',
-    type: '管点', level: '重要', address: '**市沅陵北路5号排水口',
-    status: '已关闭', deviceId: 'LL-003', deviceName: '流量计3号', model: 'LL-300B',
-    unit: 'XX排水公司', description: '沅陵北路5号排水口流量计检测到流量异常,当前流量8.5m³/h,超过阈值6.0m³/h,已排查确认为上游施工影响,现已关闭。'
-  },
-  {
-    id: 6, archiveNo: 'BJ20250101006', area: '沅陵中路', alarmTime: '2025-12-29 11:45:20',
-    type: '窨井', level: '一般', address: '**市沅陵中路6号窨井',
-    status: '已处理', deviceId: 'YL-015', deviceName: '液位传感器15号', model: 'YW-200A',
-    unit: 'XX排水公司', description: '沅陵中路6号窨井液位传感器检测到液位轻微超限,当前2.8m,超过阈值2.5m,已排查并恢复正常。'
-  },
-  {
-    id: 7, archiveNo: 'BJ20250101007', area: '沅陵南路', alarmTime: '2025-12-28 07:50:33',
-    type: '管线', level: '严重', address: '**市沅陵南路7号管点',
-    status: '未处理', deviceId: 'WD-002', deviceName: '温度传感器2号', model: 'WD-150D',
-    unit: 'XX排水公司', description: '沅陵南路7号管点温度传感器检测到温度异常,当前45℃,超过阈值40℃,可能存在管道热水排放或异常工况,需排查。'
-  },
-  {
-    id: 8, archiveNo: 'BJ20250101008', area: '沅陵东路', alarmTime: '2025-12-28 13:28:47',
-    type: '管点', level: '重要', address: '**市沅陵东路8号泵站',
-    status: '处理中', deviceId: 'YL-021', deviceName: '压力传感器21号', model: 'YL-500E',
-    unit: 'XX排水公司', description: '沅陵东路8号泵站压力传感器检测到压力超标,当前0.8MPa,超过阈值0.6MPa,正在安排专业人员检修。'
-  },
-  {
-    id: 9, archiveNo: 'BJ20250101009', area: '沅陵西路', alarmTime: '2025-12-27 19:10:05',
-    type: '窨井', level: '一般', address: '**市沅陵西路9号排水口',
-    status: '已处理', deviceId: 'LL-007', deviceName: '流量计7号', model: 'LL-300B',
-    unit: 'XX排水公司', description: '沅陵西路9号排水口流量计检测到流量轻微异常,当前15.2m³/h,超过阈值10.0m³/h,确认为暴雨导致,已处理完毕。'
-  },
-  {
-    id: 10, archiveNo: 'BJ20250101010', area: '沅陵北路', alarmTime: '2025-12-27 22:55:19',
-    type: '管线', level: '重要', address: '**市沅陵北路10号窨井',
-    status: '未处理', deviceId: 'YL-009', deviceName: '液位传感器9号', model: 'YW-200A',
-    unit: 'XX排水公司', description: '沅陵北路10号窨井液位传感器检测到液位超限,当前3.2m,超过阈值3.0m,需安排巡检。'
-  },
-  {
-    id: 11, archiveNo: 'BJ20250101011', area: '沅陵中路', alarmTime: '2025-12-26 06:42:31',
-    type: '管点', level: '一般', address: '**市沅陵中路排水干管',
-    status: '已关闭', deviceId: 'LL-018', deviceName: '流量计18号', model: 'LL-300B',
-    unit: 'XX排水公司', description: '沅陵中路排水干管流量计检测到轻微流量波动,当前7.2m³/h,超过阈值6.0m³/h,经核实为设备校准偏差,已关闭。'
-  },
-  {
-    id: 12, archiveNo: 'BJ20250101012', area: '沅陵南路', alarmTime: '2025-12-25 15:08:56',
-    type: '窨井', level: '严重', address: '**市沅陵南路污水检查井',
-    status: '处理中', deviceId: 'YL-033', deviceName: '液位传感器33号', model: 'YW-200A',
-    unit: 'XX排水公司', description: '沅陵南路污水检查井液位传感器检测到液位严重超限,当前4.0m,超过阈值3.0m,存在污水外溢风险,正在紧急处理中。'
-  }
-])
-
-// ==================== 过滤列表 ====================
-const filteredList = computed(() => {
-  return tableData.value.filter(item => {
-    const idMatch = !queryParams.value.deviceId || item.deviceId.toLowerCase().includes(queryParams.value.deviceId.toLowerCase())
-    const areaMatch = !queryParams.value.area || item.area === queryParams.value.area
-    const statusMatch = !queryParams.value.status || item.status === queryParams.value.status
-    return idMatch && areaMatch && statusMatch
-  })
-})
+// ==================== 等级/状态映射 ====================
+const levelMap = { 1: '严重', 2: '重要', 3: '一般' }
+const statusMap = { '-1': '未下派', 0: '待处理', 1: '已处理' }
 
-// ==================== 样式辅助 ====================
-function typeClass(type) {
-  const map = { '管线': 'type-pipeline', '管点': 'type-point', '窨井': 'type-manhole' }
-  return map[type] || ''
+function levelText(level) {
+  return levelMap[level] || '-'
 }
-
 function levelClass(level) {
-  const map = { '严重': 'level-critical', '重要': 'level-major', '一般': 'level-minor' }
+  const map = { 1: 'level-critical', 2: 'level-major', 3: 'level-minor' }
   return map[level] || ''
 }
-
+function statusText(status) {
+  return statusMap[status] ?? '-'
+}
 function statusTagType(status) {
-  const map = { '未处理': 'danger', '处理中': 'warning', '已处理': 'success', '已关闭': 'info' }
+  const map = { '-1': 'info', 0: 'warning', 1: 'success' }
   return map[status] || 'info'
 }
 
+// ==================== 设备报警数据转换 ====================
+function convertEquipToWarning(row) {
+  return {
+    id: `EQ_${row.equipmentId || row.id}`,
+    equipmentId: row.equipmentId || row.id,
+    equipmentName: row.equipmentName || '-',
+    warningTime: row.updateTime || row.createTime || '-',
+    warningLevel: 1,
+    content: `设备${row.equipmentName}报警`,
+    location: row.equipmentLocation || row.installLocation || '-',
+    longitude: row.longitude,
+    latitude: row.latitude,
+    status: -1,
+    currentValue: '',
+    thresholdValue: '',
+    warningType: '设备报警',
+    source: 'equipment'
+  }
+}
+
+// ==================== 表格数据 ====================
+const allMergedData = ref([])
+const tableData = ref([])
+const total = ref(0)
+const loading = ref(false)
+const pageNum = ref(1)
+const pageSize = ref(10)
+
+async function loadData() {
+  loading.value = true
+  try {
+    const equipParams = { equipmentTypeId: 'drainage' }
+    if (queryParams.value.equipmentName) equipParams.equipmentName = queryParams.value.equipmentName
+
+    // 并行加载设备报警和已下派的预警记录
+    const [equipRes, warningRes] = await Promise.allSettled([
+      getEquipmentPage(1, 1000, equipParams),
+      getWarningCurrentPage(1, 1000, { warningType: '设备报警', isAdmin: true })
+    ])
+
+    // 解析设备报警
+    let equipRecords = []
+    if (equipRes.status === 'fulfilled') {
+      const res = equipRes.value
+      const pageData = res.code !== undefined ? res.data : res
+      const records = pageData.records || pageData.rows || []
+      records.forEach(row => {
+        if (row.alarmStatus === 1) {
+          equipRecords.push(convertEquipToWarning(row))
+        }
+      })
+    }
+
+    // 解析已下派的预警记录,用于匹配状态
+    let dispatchedMap = {}
+    if (warningRes.status === 'fulfilled') {
+      const res = warningRes.value
+      const pageData = res.code !== undefined ? res.data : res
+      const records = pageData.records || pageData.rows || []
+      records.forEach(w => {
+        if (w.equipmentName) {
+          dispatchedMap[w.equipmentName] = w
+        }
+      })
+    }
+
+    // 合并状态:匹配到预警记录的设备报警更新状态
+    equipRecords.forEach(item => {
+      const matched = dispatchedMap[item.equipmentName]
+      if (matched) {
+        item.status = matched.status >= 1 ? 1 : 0 // 0=待处理, 1=已处理
+        item.firstApprover = matched.firstApprover
+        item.firstApproverName = matched.firstApproverName
+        item.warningId = matched.id // 保存关联的预警记录ID
+      }
+    })
+
+    // 筛选状态
+    let filtered = equipRecords
+    if (queryParams.value.status !== '' && queryParams.value.status !== null) {
+      filtered = equipRecords.filter(item => item.status === queryParams.value.status)
+    }
+
+    allMergedData.value = filtered
+    total.value = allMergedData.value.length
+    const start = (pageNum.value - 1) * pageSize.value
+    tableData.value = allMergedData.value.slice(start, start + pageSize.value)
+  } catch (error) {
+    console.error('加载报警清单失败', error)
+    ElMessage.error('加载报警清单失败')
+  } finally {
+    loading.value = false
+  }
+}
+
+function handlePageChange() {
+  const start = (pageNum.value - 1) * pageSize.value
+  tableData.value = allMergedData.value.slice(start, start + pageSize.value)
+}
+
+function handleSizeChange() {
+  pageNum.value = 1
+  tableData.value = allMergedData.value.slice(0, pageSize.value)
+}
+
 // ==================== 查看详情 ====================
 const dialogVisible = ref(false)
 const currentRow = ref({})
-const dispatchPerson = ref('')
-const personOptions = ['张伟', '李明', '王芳', '赵强', '刘洋', '陈静']
+const dispatchUserId = ref('')
+const personOptions = ref([])
+
+async function loadDrainageUsers() {
+  try {
+    // 先查找排水部门
+    const treeRes = await deptTreeSelect()
+    const treeData = treeRes.data || treeRes
+    const findDept = (nodes) => {
+      for (const node of nodes) {
+        if (node.label && node.label.includes('排水')) return node
+        if (node.children) {
+          const found = findDept(node.children)
+          if (found) return found
+        }
+      }
+      return null
+    }
+    const drainageDept = findDept(treeData)
+    if (drainageDept) {
+      const userRes = await listUser({ deptId: drainageDept.id, status: '0', pageSize: 1000 })
+      personOptions.value = (userRes.rows || userRes.data?.rows || []).map(u => ({
+        userId: u.userId,
+        nickName: u.nickName || u.userName
+      }))
+    }
+  } catch (e) {
+    console.error('加载排水单位人员失败', e)
+  }
+}
 
-function handleView(row) {
+async function handleView(row) {
   currentRow.value = { ...row }
-  dispatchPerson.value = ''
+  dispatchUserId.value = ''
   dialogVisible.value = true
 }
 
@@ -272,14 +355,71 @@ function handleClose() {
   dialogVisible.value = false
 }
 
-function handleDispatch() {
-  if (!dispatchPerson.value) {
+async function handleDispatch() {
+  if (!dispatchUserId.value) {
     ElMessage.warning('请选择下派人员')
     return
   }
-  ElMessage.success(`已下派给 ${dispatchPerson.value}`)
-  dialogVisible.value = false
+  try {
+    const selectedUser = personOptions.value.find(p => p.userId === dispatchUserId.value)
+    if (currentRow.value.source === 'equipment' || String(currentRow.value.id).startsWith('EQ_')) {
+      // 设备报警:创建一条预警记录到后端,持久化下派信息
+      await addWarning({
+        equipmentName: currentRow.value.equipmentName,
+        warningLevel: currentRow.value.warningLevel || 1,
+        warningType: currentRow.value.warningType || '设备报警',
+        content: currentRow.value.content,
+        location: currentRow.value.location,
+        warningTime: currentRow.value.warningTime,
+        longitude: currentRow.value.longitude,
+        latitude: currentRow.value.latitude,
+        firstApprover: dispatchUserId.value,
+        firstApproverName: selectedUser?.nickName || '',
+        publishScope: 3,
+        status: 0
+      })
+    } else {
+      // 预警类型:直接更新firstApprover字段
+      await updateWarning({
+        id: currentRow.value.id,
+        firstApprover: dispatchUserId.value,
+        firstApproverName: selectedUser?.nickName || '',
+        publishScope: 3
+      })
+    }
+    ElMessage.success(`已下派给 ${selectedUser?.nickName || ''}`)
+    dialogVisible.value = false
+    loadData()
+  } catch (e) {
+    console.error('下派失败', e)
+    ElMessage.error('下派失败,请重试')
+  }
+}
+
+async function handleProcess(row) {
+  try {
+    await ElMessageBox.confirm('确认处理此报警?', '提示', { type: 'warning' })
+    // 用关联的预警记录ID调用处理接口
+    const warningId = row.warningId || row.id
+    await firstApproveWarning(warningId, {
+      userId: userStore.id,
+      userName: userStore.name
+    })
+    ElMessage.success('处理成功')
+    loadData()
+  } catch (e) {
+    if (e !== 'cancel') {
+      console.error('处理失败', e)
+      ElMessage.error('处理失败,请重试')
+    }
+  }
 }
+
+// ==================== 生命周期 ====================
+onMounted(() => {
+  loadDrainageUsers()
+  loadData()
+})
 </script>
 
 <style scoped>
@@ -296,20 +436,10 @@ function handleDispatch() {
   border-radius: 6px;
 }
 
-/* 类型彩色文字 */
-.type-pipeline {
-  color: #409eff;
-  font-weight: 600;
-}
-
-.type-point {
-  color: #67c23a;
-  font-weight: 600;
-}
-
-.type-manhole {
-  color: #e6a23c;
-  font-weight: 600;
+.pagination-wrap {
+  margin-top: 16px;
+  display: flex;
+  justify-content: flex-end;
 }
 
 /* 等级彩色文字 */

+ 377 - 180
src/views/subSystem/drainage/jcbj/bjsh.vue

@@ -10,20 +10,19 @@
       <!-- 搜索区 -->
       <el-form :model="queryParams" ref="queryRef" :inline="true" class="search-form">
         <el-form-item label="设备名称">
-          <el-input v-model="queryParams.deviceName" placeholder="请输入设备名称" clearable style="width: 200px" />
+          <el-input v-model="queryParams.equipmentName" placeholder="请输入设备名称" clearable style="width: 200px" />
         </el-form-item>
         <el-form-item label="报警等级">
-          <el-select v-model="queryParams.level" placeholder="请选择" clearable style="width: 200px">
-            <el-option label="严重" value="严重" />
-            <el-option label="重要" value="重要" />
-            <el-option label="一般" value="一般" />
+          <el-select v-model="queryParams.warningLevel" placeholder="请选择" clearable style="width: 200px">
+            <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.auditStatus" placeholder="请选择" clearable style="width: 200px">
-            <el-option label="待审核" value="待审核" />
-            <el-option label="已审核" value="已审核" />
-            <el-option label="已解除" value="已解除" />
+          <el-select v-model="queryParams.status" placeholder="请选择" clearable style="width: 200px">
+            <el-option label="待审核" :value="1" />
+            <el-option label="已审核" :value="2" />
           </el-select>
         </el-form-item>
         <el-form-item>
@@ -33,28 +32,28 @@
       </el-form>
 
       <!-- 报警审核表格 -->
-      <el-table :data="filteredData" border stripe style="width: 100%">
-        <el-table-column prop="alarmNo" label="报警编号" width="140" />
-        <el-table-column prop="deviceName" label="设备名称" min-width="140" />
-        <el-table-column prop="level" label="报警等级" width="100">
+      <el-table :data="tableData" v-loading="loading" border stripe style="width: 100%">
+        <el-table-column prop="id" label="报警编号" width="80" align="center" />
+        <el-table-column prop="equipmentName" label="设备名称" min-width="140" />
+        <el-table-column label="报警等级" width="100">
           <template #default="{ row }">
-            <el-tag :type="levelTagType(row.level)" size="small">{{ row.level }}</el-tag>
+            <el-tag :type="levelTagType(row.warningLevel)" size="small">{{ levelText(row.warningLevel) }}</el-tag>
           </template>
         </el-table-column>
-        <el-table-column prop="alarmTime" label="报警时间" width="160" />
-        <el-table-column prop="currentValue" label="监测值" width="100">
+        <el-table-column prop="warningTime" label="报警时间" width="160" />
+        <el-table-column label="监测值" width="100">
           <template #default="{ row }">
-            <span :style="{ color: row.currentValue > row.threshold ? '#f56c6c' : '#303133' }">{{ row.currentValue }}</span>
+            <span :style="{ color: parseFloat(row.currentValue) > parseFloat(row.thresholdValue) ? '#f56c6c' : '#303133' }">{{ row.currentValue }}</span>
           </template>
         </el-table-column>
-        <el-table-column prop="auditStatus" label="审核状态" width="100">
+        <el-table-column label="审核状态" width="100">
           <template #default="{ row }">
-            <el-tag :type="auditStatusTagType(row.auditStatus)" size="small">{{ row.auditStatus }}</el-tag>
+            <el-tag :type="auditStatusTagType(row.status)" size="small">{{ auditStatusText(row.status) }}</el-tag>
           </template>
         </el-table-column>
         <el-table-column label="操作" width="160" align="center">
           <template #default="{ row }">
-            <el-button v-if="row.auditStatus === '待审核'" link type="primary" @click="openAudit(row)">审核</el-button>
+            <el-button v-if="row.status === 1" link type="primary" @click="openAudit(row)">审核</el-button>
             <el-button link type="primary" @click="openView(row)">查看</el-button>
           </template>
         </el-table-column>
@@ -65,39 +64,42 @@
     <el-dialog v-model="auditVisible" title="报警审核" width="700px" :before-close="closeAudit">
       <!-- 报警信息展示 -->
       <el-descriptions :column="2" border size="small" style="margin-bottom: 16px">
-        <el-descriptions-item label="设备名称">{{ currentAlarm.deviceName }}</el-descriptions-item>
-        <el-descriptions-item label="报警时间">{{ currentAlarm.alarmTime }}</el-descriptions-item>
+        <el-descriptions-item label="设备名称">{{ currentAlarm.equipmentName }}</el-descriptions-item>
+        <el-descriptions-item label="报警时间">{{ currentAlarm.warningTime }}</el-descriptions-item>
         <el-descriptions-item label="报警等级">
-          <el-tag :type="levelTagType(currentAlarm.level)" size="small">{{ currentAlarm.level }}</el-tag>
+          <el-tag :type="levelTagType(currentAlarm.warningLevel)" size="small">{{ levelText(currentAlarm.warningLevel) }}</el-tag>
         </el-descriptions-item>
-        <el-descriptions-item label="指标值">{{ currentAlarm.indicator }}:{{ currentAlarm.currentValue }}(阈值 {{ currentAlarm.threshold }})</el-descriptions-item>
+        <el-descriptions-item label="指标值">{{ currentAlarm.warningType || '-' }}:{{ currentAlarm.currentValue }}(阈值 {{ currentAlarm.thresholdValue }})</el-descriptions-item>
       </el-descriptions>
 
       <!-- 审核操作 -->
       <div class="audit-form-section">
         <div class="section-title">审核操作</div>
-        <el-form :model="auditForm" label-width="80px" style="margin-top: 12px">
+        <el-form :model="auditForm" label-width="90px" style="margin-top: 12px">
+          <el-form-item label="报警类型">
+            <el-tag type="info" size="small">{{ currentAlarm.warningType || '设备报警' }}</el-tag>
+          </el-form-item>
           <el-form-item label="审核结果" required>
-            <el-select v-model="auditForm.result" placeholder="请选择审核结果" style="width: 240px">
-              <el-option label="确认报警" value="确认报警" />
-              <el-option label="解除报警" value="解除报警" />
-              <el-option label="误报处理" value="误报处理" />
-            </el-select>
+            <el-radio-group v-model="auditForm.result">
+              <el-radio v-for="opt in auditResultOptions" :key="opt.value" :label="opt.value">{{ opt.label }}</el-radio>
+            </el-radio-group>
           </el-form-item>
-          <el-form-item label="审核意见" required>
-            <el-input v-model="auditForm.opinion" type="textarea" :rows="3" placeholder="请输入审核意见" />
+          <el-form-item label="审核/解除意见" required>
+            <el-input v-model="auditForm.opinion" type="textarea" :rows="3" :placeholder="auditForm.result === '解除报警' ? '请输入解除原因及说明' : '请输入审核意见'" />
           </el-form-item>
           <el-form-item label="附件上传">
             <el-upload
               action="#"
               :auto-upload="false"
-              :limit="3"
+              :file-list="auditFiles"
+              :limit="5"
               :on-change="handleFileChange"
               :on-remove="handleFileRemove"
+              accept=".jpg,.jpeg,.png,.pdf,.doc,.docx"
             >
               <el-button type="primary" size="small">选择文件</el-button>
               <template #tip>
-                <div class="upload-tip">支持上传图片/文档,最多3个文件</div>
+                <div class="upload-tip">支持jpg/png/pdf/doc格式,最多5个文件,单文件不超过10MB</div>
               </template>
             </el-upload>
           </el-form-item>
@@ -106,72 +108,84 @@
 
       <!-- 处置流程时间线 -->
       <div class="audit-timeline-section">
-        <div class="section-title">处置流程</div>
+        <div class="section-title">处置流程跟踪</div>
         <el-timeline style="margin-top: 12px; padding-left: 2px">
-          <el-timeline-item timestamp="2026-06-12 08:30" placement="top" color="#f56c6c">
-            报警触发 - {{ currentAlarm.deviceName }} {{ currentAlarm.indicator }}超标
+          <el-timeline-item :timestamp="currentAlarm.warningTime || ''" placement="top" color="#f56c6c">
+            <div class="timeline-node">报警触发</div>
+            <div class="timeline-desc">{{ currentAlarm.equipmentName }} {{ currentAlarm.warningType || '' }}超标,当前值:{{ currentAlarm.currentValue || '-' }},阈值:{{ currentAlarm.thresholdValue || '-' }}</div>
+          </el-timeline-item>
+          <el-timeline-item placement="top" color="#e6a23c">
+            <div class="timeline-node">系统通知</div>
+            <div class="timeline-desc">已自动推送至值守人员及相关负责人</div>
           </el-timeline-item>
-          <el-timeline-item timestamp="2026-06-12 08:35" placement="top" color="#e6a23c">
-            系统通知 - 已推送至值守人员
+          <el-timeline-item v-if="currentAlarm.firstApproverName" placement="top" color="#409eff">
+            <div class="timeline-node">下派处理</div>
+            <div class="timeline-desc">已下派至:{{ currentAlarm.firstApproverName }}</div>
           </el-timeline-item>
           <el-timeline-item timestamp="当前" placement="top" color="#409eff">
-            人工审核 - 审核进行中
+            <div class="timeline-node">人工审核</div>
+            <div class="timeline-desc">审核进行中,等待审核结果</div>
           </el-timeline-item>
           <el-timeline-item timestamp="待完成" placement="top" color="#909399">
-            处置完成
+            <div class="timeline-node">处置完成</div>
+            <div class="timeline-desc">审核通过后完成闭环</div>
           </el-timeline-item>
         </el-timeline>
       </div>
 
       <template #footer>
         <el-button @click="closeAudit">取消</el-button>
-        <el-button type="primary" @click="submitAudit">提交审核</el-button>
+        <el-button type="primary" :loading="submitting" @click="submitAudit">提交审核</el-button>
       </template>
     </el-dialog>
 
     <!-- ==================== 查看详情弹窗 ==================== -->
-    <el-dialog v-model="viewVisible" title="报警详情" width="600px">
+    <el-dialog v-model="viewVisible" title="报警处置全流程" width="650px">
       <el-descriptions :column="2" border size="small">
-        <el-descriptions-item label="报警编号">{{ viewRow.alarmNo }}</el-descriptions-item>
-        <el-descriptions-item label="设备名称">{{ viewRow.deviceName }}</el-descriptions-item>
+        <el-descriptions-item label="报警编号">{{ viewRow.id }}</el-descriptions-item>
+        <el-descriptions-item label="设备名称">{{ viewRow.equipmentName }}</el-descriptions-item>
         <el-descriptions-item label="报警等级">
-          <el-tag :type="levelTagType(viewRow.level)" size="small">{{ viewRow.level }}</el-tag>
+          <el-tag :type="levelTagType(viewRow.warningLevel)" size="small">{{ levelText(viewRow.warningLevel) }}</el-tag>
         </el-descriptions-item>
-        <el-descriptions-item label="报警时间">{{ viewRow.alarmTime }}</el-descriptions-item>
-        <el-descriptions-item label="监测指标">{{ viewRow.indicator }}</el-descriptions-item>
+        <el-descriptions-item label="报警时间">{{ viewRow.warningTime }}</el-descriptions-item>
+        <el-descriptions-item label="监测指标">{{ viewRow.warningType || '-' }}</el-descriptions-item>
         <el-descriptions-item label="实时值">{{ viewRow.currentValue }}</el-descriptions-item>
-        <el-descriptions-item label="阈值">{{ viewRow.threshold }}</el-descriptions-item>
+        <el-descriptions-item label="阈值">{{ viewRow.thresholdValue }}</el-descriptions-item>
         <el-descriptions-item label="审核状态">
-          <el-tag :type="auditStatusTagType(viewRow.auditStatus)" size="small">{{ viewRow.auditStatus }}</el-tag>
+          <el-tag :type="auditStatusTagType(viewRow.status)" size="small">{{ auditStatusText(viewRow.status) }}</el-tag>
         </el-descriptions-item>
-        <el-descriptions-item label="审核结果" :span="2">{{ viewRow.auditResult || '—' }}</el-descriptions-item>
-        <el-descriptions-item label="审核意见" :span="2">{{ viewRow.auditOpinion || '—' }}</el-descriptions-item>
+        <el-descriptions-item v-if="viewRow.auditResult" label="审核结果">{{ viewRow.auditResult }}</el-descriptions-item>
+        <el-descriptions-item v-if="viewRow.auditOpinion" label="审核意见">{{ viewRow.auditOpinion }}</el-descriptions-item>
+        <el-descriptions-item label="位置" :span="2">{{ viewRow.location || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="描述" :span="2">{{ viewRow.content || '-' }}</el-descriptions-item>
       </el-descriptions>
-      <!-- 处置流程时间线 -->
+      <!-- 处置全流程跟踪 -->
       <div style="margin-top: 16px">
-        <div class="section-title">处置流程</div>
+        <div class="section-title">处置流程跟踪</div>
         <el-timeline style="margin-top: 12px; padding-left: 2px">
-          <el-timeline-item timestamp="2026-06-12 08:30" placement="top" color="#f56c6c">
-            报警触发 - {{ viewRow.deviceName }} {{ viewRow.indicator }}超标
+          <el-timeline-item :timestamp="viewRow.warningTime || ''" placement="top" color="#f56c6c">
+            <div class="timeline-node">报警触发</div>
+            <div class="timeline-desc">{{ viewRow.equipmentName }} {{ viewRow.warningType || '' }}超标,当前值:{{ viewRow.currentValue || '-' }}</div>
+          </el-timeline-item>
+          <el-timeline-item placement="top" color="#e6a23c">
+            <div class="timeline-node">系统通知</div>
+            <div class="timeline-desc">已自动推送至值守人员及相关负责人</div>
+          </el-timeline-item>
+          <el-timeline-item v-if="viewRow.firstApproverName" placement="top" color="#409eff">
+            <div class="timeline-node">下派处理</div>
+            <div class="timeline-desc">已下派至:{{ viewRow.firstApproverName }}</div>
           </el-timeline-item>
-          <el-timeline-item timestamp="2026-06-12 08:35" placement="top" color="#e6a23c">
-            系统通知 - 已推送至值守人员
+          <el-timeline-item v-if="viewRow.status === 2" placement="top" color="#67c23a">
+            <div class="timeline-node">审核完成</div>
+            <div class="timeline-desc">审核结果:{{ viewRow.auditResult || '-' }};意见:{{ viewRow.auditOpinion || '-' }}</div>
           </el-timeline-item>
-          <el-timeline-item
-            v-if="viewRow.auditStatus !== '待审核'"
-            timestamp="2026-06-12 10:00"
-            placement="top"
-            color="#409eff"
-          >
-            人工审核 - {{ viewRow.auditResult }}:{{ viewRow.auditOpinion }}
+          <el-timeline-item v-if="viewRow.status === 2" placement="top" color="#67c23a">
+            <div class="timeline-node">处置完成</div>
+            <div class="timeline-desc">报警处置流程已闭环</div>
           </el-timeline-item>
-          <el-timeline-item
-            v-if="viewRow.auditStatus === '已解除'"
-            timestamp="2026-06-12 11:00"
-            placement="top"
-            color="#67c23a"
-          >
-            处置完成 - 报警已解除
+          <el-timeline-item v-if="viewRow.status !== 2" timestamp="进行中" placement="top" color="#909399">
+            <div class="timeline-node">待审核</div>
+            <div class="timeline-desc">等待人工审核处理</div>
           </el-timeline-item>
         </el-timeline>
       </div>
@@ -180,77 +194,187 @@
 </template>
 
 <script setup name="Bjsh">
-import { ref, computed, onMounted } from 'vue'
+import { ref, computed, onMounted, nextTick } from 'vue'
 import { ElMessage } from 'element-plus'
 import locationIcon from '@/assets/images/location.png'
+import { getWarningCurrentPage, getEquipmentPage, addWarning, updateWarning } from '@/api/drainage'
+import useUserStore from '@/store/modules/user'
+
+// ==================== 用户信息 ====================
+const userStore = useUserStore()
+const isAdmin = computed(() => userStore.roles && userStore.roles.includes('admin'))
 
 // ==================== 地图变量 ====================
 let mapInstance = null
+let mapInitialized = false
+
+// ==================== 等级/状态映射 ====================
+const levelMap = { 1: '严重', 2: '重要', 3: '一般' }
+const levelColorMap = { 1: '#f56c6c', 2: '#e6a23c', 3: '#409eff' }
+const auditStatusMap = { 1: '待审核', 2: '已审核' }
+const auditStatusColorMap = { 1: '#e6a23c', 2: '#67c23a' }
+
+function levelText(level) {
+  return levelMap[level] || '-'
+}
+function levelTagType(level) {
+  if (level === 1) return 'danger'
+  if (level === 2) return 'warning'
+  return ''
+}
+function auditStatusText(status) {
+  return auditStatusMap[status] || '-'
+}
+function auditStatusTagType(status) {
+  if (status === 2) return 'success'
+  return 'warning'
+}
 
 // ==================== 搜索参数 ====================
 const queryParams = ref({
-  deviceName: '',
-  level: '',
-  auditStatus: ''
+  equipmentName: '',
+  warningLevel: '',
+  status: ''
 })
 const queryRef = ref(null)
 
 function handleQuery() {
-  ElMessage.success('搜索完成')
+  loadData()
 }
 function resetQuery() {
-  queryParams.value = { deviceName: '', level: '', auditStatus: '' }
+  queryParams.value = { equipmentName: '', warningLevel: '', status: '' }
+  loadData()
 }
 
-// ==================== 报警等级 / 审核状态 tag类型 ====================
-function levelTagType(level) {
-  if (level === '严重') return 'danger'
-  if (level === '重要') return 'warning'
-  return ''
-}
-function auditStatusTagType(status) {
-  if (status === '已审核') return 'success'
-  if (status === '已解除') return 'info'
-  return 'warning'
-}
+// ==================== 表格数据 ====================
+const tableData = ref([])
+const loading = ref(false)
 
-// ==================== 模拟数据(12条报警审核数据) ====================
-const alarmList = ref([
-  { alarmNo: 'BJ20260612001', deviceName: '沅陵桥水位传感器', level: '严重', alarmTime: '2026-06-12 08:30', indicator: '水位', currentValue: 2.85, threshold: 2.0, auditStatus: '待审核', auditResult: '', auditOpinion: '', lng: 110.398, lat: 28.465 },
-  { alarmNo: 'BJ20260612002', deviceName: '太常片区流量计', level: '重要', alarmTime: '2026-06-12 09:15', indicator: '流量', currentValue: 5.6, threshold: 4.0, auditStatus: '待审核', auditResult: '', auditOpinion: '', lng: 110.372, lat: 28.455 },
-  { alarmNo: 'BJ20260612003', deviceName: '城南液位传感器', level: '一般', alarmTime: '2026-06-12 10:00', indicator: '液位', currentValue: 0.8, threshold: 0.6, auditStatus: '已审核', auditResult: '确认报警', auditOpinion: '液位持续偏高,需持续关注', lng: 110.405, lat: 28.442 },
-  { alarmNo: 'BJ20260612004', deviceName: '河西水质监测仪', level: '严重', alarmTime: '2026-06-12 07:45', indicator: 'COD', currentValue: 120, threshold: 80, auditStatus: '待审核', auditResult: '', auditOpinion: '', lng: 110.368, lat: 28.448 },
-  { alarmNo: 'BJ20260612005', deviceName: '北门流量计', level: '重要', alarmTime: '2026-06-12 11:30', indicator: '流量', currentValue: 4.8, threshold: 4.0, auditStatus: '已解除', auditResult: '解除报警', auditOpinion: '暴雨导致临时流量增大,现已恢复正常', lng: 110.410, lat: 28.470 },
-  { alarmNo: 'BJ20260612006', deviceName: '东郊水位传感器', level: '一般', alarmTime: '2026-06-11 22:10', indicator: '水位', currentValue: 1.8, threshold: 1.5, auditStatus: '待审核', auditResult: '', auditOpinion: '', lng: 110.420, lat: 28.440 },
-  { alarmNo: 'BJ20260612007', deviceName: '中心区压力传感器', level: '严重', alarmTime: '2026-06-11 18:20', indicator: '压力', currentValue: 0.85, threshold: 0.5, auditStatus: '已审核', auditResult: '确认报警', auditOpinion: '管网压力异常,需紧急维修', lng: 110.395, lat: 28.452 },
-  { alarmNo: 'BJ20260612008', deviceName: '沅水南岸流量计', level: '重要', alarmTime: '2026-06-11 15:40', indicator: '流量', currentValue: 6.2, threshold: 5.0, auditStatus: '待审核', auditResult: '', auditOpinion: '', lng: 110.380, lat: 28.435 },
-  { alarmNo: 'BJ20260612009', deviceName: '西渡口液位传感器', level: '一般', alarmTime: '2026-06-11 12:00', indicator: '液位', currentValue: 0.7, threshold: 0.6, auditStatus: '已解除', auditResult: '误报处理', auditOpinion: '传感器漂移导致数据异常', lng: 110.360, lat: 28.460 },
-  { alarmNo: 'BJ20260612010', deviceName: '开发区水质监测仪', level: '重要', alarmTime: '2026-06-10 09:50', indicator: '氨氮', currentValue: 8.5, threshold: 5.0, auditStatus: '待审核', auditResult: '', auditOpinion: '', lng: 110.425, lat: 28.475 },
-  { alarmNo: 'BJ20260612011', deviceName: '老城区液位传感器', level: '严重', alarmTime: '2026-06-10 06:20', indicator: '液位', currentValue: 1.2, threshold: 0.8, auditStatus: '已审核', auditResult: '确认报警', auditOpinion: '排水不畅导致液位超标', lng: 110.393, lat: 28.450 },
-  { alarmNo: 'BJ20260612012', deviceName: '工业区流量计', level: '一般', alarmTime: '2026-06-09 14:30', indicator: '流量', currentValue: 3.5, threshold: 3.0, auditStatus: '待审核', auditResult: '', auditOpinion: '', lng: 110.435, lat: 28.430 }
-])
-
-const filteredData = computed(() => {
-  let list = alarmList.value
-  if (queryParams.value.deviceName) {
-    list = list.filter(item => item.deviceName.includes(queryParams.value.deviceName))
-  }
-  if (queryParams.value.level) {
-    list = list.filter(item => item.level === queryParams.value.level)
-  }
-  if (queryParams.value.auditStatus) {
-    list = list.filter(item => item.auditStatus === queryParams.value.auditStatus)
+async function loadData() {
+  loading.value = true
+  try {
+    // 并行加载设备报警和已审核的预警记录
+    const [equipRes, warningRes] = await Promise.allSettled([
+      getEquipmentPage(1, 1000, { equipmentTypeId: 'drainage' }),
+      getWarningCurrentPage(1, 1000, { warningType: '设备报警', isAdmin: true })
+    ])
+
+    // 解析设备报警
+    let equipRecords = []
+    if (equipRes.status === 'fulfilled') {
+      const res = equipRes.value
+      const pageData = res.code !== undefined ? res.data : res
+      const records = pageData.records || pageData.rows || []
+      records.forEach(row => {
+        if (row.alarmStatus === 1) {
+          equipRecords.push({
+            id: `EQ_${row.equipmentId || row.id}`,
+            equipmentId: row.equipmentId || row.id,
+            equipmentName: row.equipmentName || '-',
+            warningTime: row.updateTime || row.createTime || '-',
+            warningLevel: 1,
+            content: `设备${row.equipmentName}报警`,
+            location: row.equipmentLocation || row.installLocation || '-',
+            longitude: row.longitude,
+            latitude: row.latitude,
+            currentValue: '',
+            thresholdValue: '',
+            warningType: '设备报警',
+            status: 1, // 待审核
+            source: 'equipment'
+          })
+        }
+      })
+    }
+
+    // 解析已存在的预警记录,用于匹配审核状态
+    let warningRecords = []
+    if (warningRes.status === 'fulfilled') {
+      const res = warningRes.value
+      const pageData = res.code !== undefined ? res.data : res
+      warningRecords = pageData.records || pageData.rows || []
+    }
+
+    // 将已审核的记录匹配到设备报警
+    const warningMap = {}
+    warningRecords.forEach(w => {
+      if (w.equipmentName) warningMap[w.equipmentName] = w
+    })
+
+    equipRecords.forEach(item => {
+      const matched = warningMap[item.equipmentName]
+      if (matched) {
+        item.status = matched.status >= 2 ? 2 : 1 // 2=已审核,1=待审核
+        item.auditResult = matched.auditResult || ''
+        item.auditOpinion = matched.auditOpinion || ''
+        item.firstApproverName = matched.firstApproverName || ''
+        item.warningId = matched.id
+      }
+    })
+
+    // 筛选
+    let filtered = equipRecords
+    if (queryParams.value.equipmentName) {
+      filtered = filtered.filter(item => item.equipmentName.includes(queryParams.value.equipmentName))
+    }
+    if (queryParams.value.warningLevel) {
+      filtered = filtered.filter(item => item.warningLevel === queryParams.value.warningLevel)
+    }
+    if (queryParams.value.status !== '' && queryParams.value.status !== null) {
+      filtered = filtered.filter(item => item.status === queryParams.value.status)
+    }
+
+    tableData.value = filtered
+
+    // 如果地图已初始化,刷新标记
+    if (mapInitialized) {
+      refreshMapMarkers()
+    }
+  } catch (error) {
+    console.error('加载报警数据失败', error)
+    ElMessage.error('加载报警数据失败')
+  } finally {
+    loading.value = false
   }
-  return list
-})
+}
 
 // ==================== 审核弹窗 ====================
 const auditVisible = ref(false)
 const currentAlarm = ref({})
 const auditForm = ref({ result: '', opinion: '' })
+
+// 各类报警预定义的审核结果选项
+const auditResultOptionsMap = {
+  '设备报警': [
+    { label: '确认报警 - 设备异常需维修', value: '确认报警' },
+    { label: '解除报警 - 设备恢复正常', value: '解除报警' },
+    { label: '误报处理 - 传感器误触发', value: '误报处理' },
+    { label: '降级处理 - 下调报警等级', value: '降级处理' }
+  ],
+  '水位报警': [
+    { label: '确认报警 - 水位超标需处置', value: '确认报警' },
+    { label: '解除报警 - 水位已回落', value: '解除报警' },
+    { label: '误报处理 - 监测数据异常', value: '误报处理' }
+  ],
+  '压力报警': [
+    { label: '确认报警 - 管网压力异常', value: '确认报警' },
+    { label: '解除报警 - 压力恢复正常', value: '解除报警' },
+    { label: '误报处理 - 压力波动属正常范围', value: '误报处理' }
+  ],
+  '默认': [
+    { label: '确认报警', value: '确认报警' },
+    { label: '解除报警', value: '解除报警' },
+    { label: '误报处理', value: '误报处理' }
+  ]
+}
+const auditResultOptions = computed(() => {
+  const type = currentAlarm.value.warningType || '默认'
+  return auditResultOptionsMap[type] || auditResultOptionsMap['默认']
+})
 const auditFiles = ref([])
+const submitting = ref(false)
 
-function openAudit(row) {
+async function openAudit(row) {
   currentAlarm.value = { ...row }
   auditForm.value = { result: '', opinion: '' }
   auditFiles.value = []
@@ -266,7 +390,7 @@ function handleFileRemove(file) {
   const idx = auditFiles.value.findIndex(f => f.uid === file.uid)
   if (idx !== -1) auditFiles.value.splice(idx, 1)
 }
-function submitAudit() {
+async function submitAudit() {
   if (!auditForm.value.result) {
     ElMessage.warning('请选择审核结果')
     return
@@ -275,31 +399,60 @@ function submitAudit() {
     ElMessage.warning('请输入审核意见')
     return
   }
-  // 更新审核状态
-  const idx = alarmList.value.findIndex(item => item.alarmNo === currentAlarm.value.alarmNo)
-  if (idx !== -1) {
-    const statusMap = { '确认报警': '已审核', '解除报警': '已解除', '误报处理': '已解除' }
-    alarmList.value[idx].auditStatus = statusMap[auditForm.value.result] || '已审核'
-    alarmList.value[idx].auditResult = auditForm.value.result
-    alarmList.value[idx].auditOpinion = auditForm.value.opinion
+  submitting.value = true
+  try {
+    const warningId = currentAlarm.value.warningId
+
+    if (warningId && !String(warningId).startsWith('EQ_')) {
+      // 已有预警记录,直接更新审核信息
+      await updateWarning({
+        id: warningId,
+        status: 2,
+        auditResult: auditForm.value.result,
+        auditOpinion: auditForm.value.opinion,
+        auditor: userStore.name,
+        auditorId: userStore.id
+      })
+    } else {
+      // 设备报警尚无预警记录,创建一条并直接设为已审核
+      await addWarning({
+        equipmentName: currentAlarm.value.equipmentName,
+        warningLevel: currentAlarm.value.warningLevel || 1,
+        warningType: currentAlarm.value.warningType || '设备报警',
+        content: currentAlarm.value.content,
+        location: currentAlarm.value.location,
+        warningTime: currentAlarm.value.warningTime,
+        longitude: currentAlarm.value.longitude,
+        latitude: currentAlarm.value.latitude,
+        auditResult: auditForm.value.result,
+        auditOpinion: auditForm.value.opinion,
+        auditor: userStore.name,
+        auditorId: userStore.id,
+        publishScope: 3,
+        status: 2
+      })
+    }
+
+    ElMessage.success('审核提交成功')
+    closeAudit()
+    loadData()
+  } catch (error) {
+    console.error('审核提交失败', error)
+    ElMessage.error('审核提交失败')
+  } finally {
+    submitting.value = false
   }
-  ElMessage.success('审核提交成功')
-  closeAudit()
 }
 
 // ==================== 查看详情弹窗 ====================
 const viewVisible = ref(false)
 const viewRow = ref({})
-function openView(row) {
+async function openView(row) {
   viewRow.value = { ...row }
   viewVisible.value = true
 }
 
 // ==================== 地图初始化 ====================
-onMounted(() => {
-  initMap()
-})
-
 function initMap() {
   const container = document.getElementById('bjshMap')
   if (!container || typeof BMapGL === 'undefined') {
@@ -312,56 +465,81 @@ function initMap() {
     mapInstance.centerAndZoom(centerPoint, 15)
     mapInstance.enableScrollWheelZoom(true)
 
-    // 添加报警点位标记
-    alarmList.value.forEach(point => {
-      const levelColor = {
-        '严重': '#f56c6c',
-        '重要': '#e6a23c',
-        '一般': '#409eff'
-      }
-      const color = levelColor[point.level] || '#409eff'
-      const bPoint = new BMapGL.Point(point.lng, point.lat)
-
-      const html = `<div style="text-align:center;cursor:pointer;">
-        <div style="color:#fff;background:${color};border-radius:4px;padding:2px 6px;font-size:11px;white-space:nowrap;display:inline-block;margin-bottom:2px;">${point.deviceName}</div>
-        <div style="color:#fff;font-size:10px;background:rgba(0,0,0,0.6);border-radius:3px;padding:1px 4px;margin-bottom:2px;">${point.level} | ${point.indicator}:${point.currentValue}</div>
-        <div><img src="${locationIcon}" style="width:24px;height:24px;display:block;margin:0 auto;"/></div>
-      </div>`
-
-      const label = new BMapGL.Label(html, {
-        position: bPoint,
-        offset: new BMapGL.Size(-30, -55)
-      })
-      label.setStyle({
-        border: 'none',
-        background: 'transparent',
-        padding: '0'
-      })
-      mapInstance.addOverlay(label)
-
-      // 点击标记弹出信息窗口
-      label.addEventListener('click', function () {
-        const statusColor = { '待审核': '#e6a23c', '已审核': '#67c23a', '已解除': '#909399' }
-        const sColor = statusColor[point.auditStatus] || '#409eff'
-        const infoContent = `<div style="font-size:13px;line-height:1.8;">
-          <div><b>${point.deviceName}</b></div>
-          <div>报警等级:<b style="color:${color}">${point.level}</b></div>
-          <div>报警时间:${point.alarmTime}</div>
-          <div>监测值:<b style="color:${color}">${point.indicator}:${point.currentValue}</b></div>
-          <div>阈值:${point.threshold}</div>
-          <div>审核状态:<b style="color:${sColor}">${point.auditStatus}</b></div>
-        </div>`
-        const infoWindow = new BMapGL.InfoWindow(infoContent, {
-          width: 220,
-          title: ''
-        })
-        mapInstance.openInfoWindow(infoWindow, bPoint)
-      })
-    })
+    refreshMapMarkers()
+    mapInitialized = true
   } catch (e) {
     console.error('地图初始化失败:', e)
   }
 }
+
+function refreshMapMarkers() {
+  if (!mapInstance) return
+  mapInstance.clearOverlays()
+
+  let firstPoint = null
+  tableData.value.forEach(point => {
+    const lng = parseFloat(point.longitude)
+    const lat = parseFloat(point.latitude)
+    if (!lng || !lat) return
+    if (!firstPoint) firstPoint = { lng, lat }
+    addMapMarker(point, lng, lat)
+  })
+
+  // 默认定位到第一个报警点位
+  if (firstPoint) {
+    mapInstance.centerAndZoom(new BMapGL.Point(firstPoint.lng, firstPoint.lat), 15)
+  }
+}
+
+function addMapMarker(point, lng, lat) {
+  const color = levelColorMap[point.warningLevel] || '#409eff'
+  const levelTextStr = levelMap[point.warningLevel] || '一般'
+  const statusTextStr = auditStatusMap[point.status] || '待审核'
+  const statusColor = auditStatusColorMap[point.status] || '#e6a23c'
+  const bPoint = new BMapGL.Point(lng, lat)
+
+  const html = `<div style="text-align:center;cursor:pointer;">
+    <div style="color:#fff;background:${color};border-radius:4px;padding:2px 6px;font-size:11px;white-space:nowrap;display:inline-block;margin-bottom:2px;">${point.equipmentName || '-'}</div>
+    <div style="color:#fff;font-size:10px;background:rgba(0,0,0,0.6);border-radius:3px;padding:1px 4px;margin-bottom:2px;">${levelTextStr} | ${point.warningType || '-'}:${point.currentValue || '-'}</div>
+    <div><img src="${locationIcon}" style="width:24px;height:24px;display:block;margin:0 auto;"/></div>
+  </div>`
+
+  const label = new BMapGL.Label(html, {
+    position: bPoint,
+    offset: new BMapGL.Size(-30, -55)
+  })
+  label.setStyle({
+    border: 'none',
+    background: 'transparent',
+    padding: '0'
+  })
+  mapInstance.addOverlay(label)
+
+  // 点击标记弹出信息窗口
+  label.addEventListener('click', function () {
+    const infoContent = `<div style="font-size:13px;line-height:1.8;">
+      <div><b>${point.equipmentName || '-'}</b></div>
+      <div>报警等级:<b style="color:${color}">${levelTextStr}</b></div>
+      <div>报警时间:${point.warningTime || '-'}</div>
+      <div>监测值:<b style="color:${color}">${point.warningType || '-'}:${point.currentValue || '-'}</b></div>
+      <div>阈值:${point.thresholdValue || '-'}</div>
+      <div>审核状态:<b style="color:${statusColor}">${statusTextStr}</b></div>
+    </div>`
+    const infoWindow = new BMapGL.InfoWindow(infoContent, {
+      width: 220,
+      title: ''
+    })
+    mapInstance.openInfoWindow(infoWindow, bPoint)
+  })
+}
+
+// ==================== 生命周期 ====================
+onMounted(async () => {
+  await loadData()
+  nextTick(() => {
+    initMap()
+  })
+})
 </script>
 
 <style scoped>
@@ -399,4 +577,23 @@ function initMap() {
   color: #909399;
   margin-top: 4px;
 }
-</style>
+.audit-form-section {
+  margin-top: 12px;
+  padding: 12px;
+  background: #f8f9fb;
+  border-radius: 6px;
+}
+.audit-timeline-section {
+  margin-top: 16px;
+}
+.timeline-node {
+  font-weight: 600;
+  font-size: 14px;
+  color: #303133;
+}
+.timeline-desc {
+  font-size: 12px;
+  color: #606266;
+  margin-top: 2px;
+}
+</style>

+ 42 - 60
src/views/subSystem/drainage/jcbj/bjtx.vue

@@ -57,6 +57,12 @@
 <script setup name="Bjtx">
 import { ref, computed, onMounted, onUnmounted } from 'vue'
 import { Bell, Mute, Location } from '@element-plus/icons-vue'
+import { getEquipmentPage } from '@/api/drainage'
+import useUserStore from '@/store/modules/user'
+
+// ==================== 用户信息 ====================
+const userStore = useUserStore()
+const isAdmin = computed(() => userStore.roles && userStore.roles.includes('admin'))
 
 // ==================== 时钟 ====================
 const currentTime = ref('')
@@ -76,56 +82,38 @@ function updateClock() {
 // ==================== 声音开关 ====================
 const soundOn = ref(true)
 
-// ==================== 模拟数据 ====================
-const devices = ['液位传感器YL-001', '流量计LL-012', '雨量计YL-005', '液位传感器YL-008', '流量计LL-003',
-  '液位传感器YL-015', '温度传感器WD-002', '压力传感器YL-021', '流量计LL-007', '液位传感器YL-009']
-const metrics = [
-  { text: '液位超限 3.5m > 阈值3.0m', level: '严重' },
-  { text: '流量异常 12.8m³/h > 阈值10.0m³/h', level: '重要' },
-  { text: '雨量超标 58mm/h > 阈值50mm/h', level: '一般' },
-  { text: '液位超限 2.8m > 阈值2.5m', level: '重要' },
-  { text: '流量异常 8.5m³/h > 阈值6.0m³/h', level: '一般' },
-  { text: '液位超限 4.2m > 阈值3.0m', level: '严重' },
-  { text: '温度异常 45℃ > 阈值40℃', level: '一般' },
-  { text: '压力超标 0.8MPa > 阈值0.6MPa', level: '重要' },
-  { text: '液位超限 3.2m > 阈值3.0m', level: '一般' },
-  { text: '流量异常 15.2m³/h > 阈值10.0m³/h', level: '严重' }
-]
-const locations = ['沅陵北路1号排水口', '沅陵南路2号窨井', '沅陵东路3号管点', '沅陵西路4号泵站',
-  '沅陵北路5号排水口', '沅陵中路6号窨井', '沅陵南路7号管点', '沅陵东路8号泵站',
-  '沅陵西路9号排水口', '沅陵北路10号窨井']
-
-let alarmIdCounter = 100
-const alarmList = ref([])
+// ==================== 等级映射 ====================
+const levelMap = { 1: '严重', 2: '重要', 3: '一般' }
 
-function formatTime(date) {
-  const h = String(date.getHours()).padStart(2, '0')
-  const m = String(date.getMinutes()).padStart(2, '0')
-  const s = String(date.getSeconds()).padStart(2, '0')
-  return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${h}:${m}:${s}`
-}
-
-function generateAlarm() {
-  const metric = metrics[Math.floor(Math.random() * metrics.length)]
-  const device = devices[Math.floor(Math.random() * devices.length)]
-  const location = locations[Math.floor(Math.random() * locations.length)]
-  return {
-    id: alarmIdCounter++,
-    time: formatTime(new Date()),
-    device,
-    metric: metric.text,
-    level: metric.level,
-    location
-  }
-}
+// ==================== 报警数据 ====================
+const alarmList = ref([])
 
-// 初始化15条数据
-function initAlarms() {
-  const now = Date.now()
-  for (let i = 0; i < 18; i++) {
-    const alarm = generateAlarm()
-    alarm.time = formatTime(new Date(now - (18 - i) * 180000)) // 每3分钟一条
-    alarmList.value.push(alarm)
+async function loadAlarms() {
+  try {
+    const res = await getEquipmentPage(1, 200, { equipmentTypeId: 'drainage' })
+    const pageData = res.code !== undefined ? res.data : res
+    const records = pageData.records || pageData.rows || []
+
+    const equipAlarms = []
+    records.forEach(row => {
+      if (row.alarmStatus === 1) {
+        equipAlarms.push({
+          id: `EQ_${row.equipmentId || row.id}`,
+          time: row.updateTime || row.createTime || '-',
+          device: row.equipmentName || '-',
+          metric: `设备${row.equipmentName}报警`,
+          level: '严重',
+          location: row.equipmentLocation || row.installLocation || '-',
+          source: 'equipment',
+          lng: parseFloat(row.longitude),
+          lat: parseFloat(row.latitude)
+        })
+      }
+    })
+
+    alarmList.value = equipAlarms
+  } catch (error) {
+    console.error('加载报警数据失败', error)
   }
 }
 
@@ -135,13 +123,13 @@ const unHandledCount = computed(() => alarmList.value.filter(a => a.level === '
 const scrollWrapper = ref(null)
 const scrollOffset = ref(0)
 let scrollTimer = null
-let addTimer = null
+let pollTimer = null
 const CARD_HEIGHT = 128 // 卡片高度 + 间距
 let scrollPaused = false
 
 function startScroll() {
   scrollTimer = setInterval(() => {
-    if (scrollPaused) return
+    if (scrollPaused || alarmList.value.length === 0) return
     scrollOffset.value -= 1
     // 每滚动一个卡片高度,将第一条数据移到末尾
     if (Math.abs(scrollOffset.value) >= CARD_HEIGHT) {
@@ -152,20 +140,14 @@ function startScroll() {
   }, 30)
 }
 
-function addNewAlarm() {
-  addTimer = setInterval(() => {
-    const alarm = generateAlarm()
-    alarmList.value.push(alarm)
-  }, 3000)
-}
-
 // ==================== 生命周期 ====================
 onMounted(() => {
   updateClock()
   clockTimer = setInterval(updateClock, 1000)
-  initAlarms()
+  loadAlarms()
   startScroll()
-  addNewAlarm()
+  // 30秒轮询刷新
+  pollTimer = setInterval(loadAlarms, 30000)
 
   // 鼠标悬停暂停滚动
   const wrapper = scrollWrapper.value
@@ -178,7 +160,7 @@ onMounted(() => {
 onUnmounted(() => {
   if (clockTimer) clearInterval(clockTimer)
   if (scrollTimer) clearInterval(scrollTimer)
-  if (addTimer) clearInterval(addTimer)
+  if (pollTimer) clearInterval(pollTimer)
 })
 </script>
 

+ 201 - 108
src/views/subSystem/drainage/jcbj/bjyp.vue

@@ -1,15 +1,22 @@
 <template>
   <div class="app-container bjyp-page">
-    <!-- ==================== 搜索区 ==================== -->
+    <!-- 搜索区 -->
     <el-form :model="queryParams" ref="queryRef" :inline="true" class="search-form">
       <el-form-item label="设备名称">
-        <el-input v-model="queryParams.deviceName" placeholder="请输入设备名称" clearable style="width: 200px" />
+        <el-input v-model="queryParams.equipmentName" placeholder="请输入设备名称" clearable style="width: 180px" />
       </el-form-item>
       <el-form-item label="报警等级">
-        <el-select v-model="queryParams.level" placeholder="请选择" clearable style="width: 200px">
-          <el-option label="严重" value="严重" />
-          <el-option label="重要" value="重要" />
-          <el-option label="一般" value="一般" />
+        <el-select v-model="queryParams.warningLevel" placeholder="请选择" clearable style="width: 160px">
+          <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.status" placeholder="请选择" clearable style="width: 140px">
+          <el-option label="待处理" :value="0" />
+          <el-option label="已处理" :value="1" />
+          <el-option label="已完成" :value="2" />
         </el-select>
       </el-form-item>
       <el-form-item label="时间范围">
@@ -19,7 +26,7 @@
           range-separator="至"
           start-placeholder="开始日期"
           end-placeholder="结束日期"
-          style="width: 260px"
+          style="width: 240px"
           value-format="YYYY-MM-DD"
         />
       </el-form-item>
@@ -29,44 +36,56 @@
       </el-form-item>
     </el-form>
 
-    <!-- ==================== 报警列表 ==================== -->
-    <el-table :data="filteredData" border stripe style="width: 100%">
-      <el-table-column prop="alarmNo" label="报警编号" width="140" />
-      <el-table-column prop="deviceName" label="设备名称" min-width="140" />
-      <el-table-column prop="alarmTime" label="报警时间" width="160" />
-      <el-table-column prop="level" label="报警等级" width="100">
+    <!-- 报警列表 -->
+    <el-table :data="tableData" v-loading="loading" border style="width: 100%" size="default">
+      <el-table-column label="报警编号" prop="id" min-width="80" align="center" />
+      <el-table-column label="设备名称" prop="equipmentName" min-width="140" show-overflow-tooltip />
+      <el-table-column label="报警时间" prop="warningTime" min-width="160" />
+      <el-table-column label="报警等级" min-width="90" align="center">
         <template #default="{ row }">
-          <el-tag :type="levelTagType(row.level)" size="small">{{ row.level }}</el-tag>
+          <span :class="levelClass(row.warningLevel)">{{ levelText(row.warningLevel) }}</span>
         </template>
       </el-table-column>
-      <el-table-column prop="indicator" label="监测指标" width="120" />
-      <el-table-column prop="currentValue" label="实时值" width="100" />
-      <el-table-column prop="threshold" label="阈值" width="100" />
-      <el-table-column prop="status" label="研判状态" width="100">
+      <el-table-column label="报警内容" prop="content" min-width="180" show-overflow-tooltip />
+      <el-table-column label="位置" prop="location" min-width="180" show-overflow-tooltip />
+      <el-table-column label="处理状态" min-width="90" align="center">
         <template #default="{ row }">
-          <el-tag :type="row.status === '已研判' ? 'success' : 'warning'" size="small">{{ row.status }}</el-tag>
+          <el-tag :type="statusTagType(row.status)" size="small" effect="plain">{{ statusText(row.status) }}</el-tag>
         </template>
       </el-table-column>
-      <el-table-column label="操作" width="120" align="center">
+      <el-table-column label="操作" width="100" align="center">
         <template #default="{ row }">
           <el-button link type="primary" @click="openAnalysis(row)">研判分析</el-button>
         </template>
       </el-table-column>
     </el-table>
 
-    <!-- ==================== 研判分析弹窗 ==================== -->
+    <!-- 分页 -->
+    <div class="pagination-wrap">
+      <el-pagination
+        v-model:current-page="pageNum"
+        v-model:page-size="pageSize"
+        :page-sizes="[10, 20, 50, 100]"
+        :total="total"
+        layout="total, sizes, prev, pager, next, jumper"
+        @size-change="handleSizeChange"
+        @current-change="handlePageChange"
+      />
+    </div>
+
+    <!-- 研判分析弹窗 -->
     <el-dialog v-model="analysisVisible" title="研判分析" width="900px" :before-close="closeAnalysis" @opened="onDialogOpened">
       <!-- 设备基本信息 -->
       <div class="analysis-header">
         <el-descriptions :column="3" border size="small">
-          <el-descriptions-item label="设备名称">{{ currentAlarm.deviceName }}</el-descriptions-item>
-          <el-descriptions-item label="设备编号">{{ currentAlarm.deviceNo }}</el-descriptions-item>
+          <el-descriptions-item label="设备名称">{{ currentAlarm.equipmentName }}</el-descriptions-item>
+          <el-descriptions-item label="设备编号">{{ currentAlarm.id }}</el-descriptions-item>
           <el-descriptions-item label="设备位置">{{ currentAlarm.location }}</el-descriptions-item>
           <el-descriptions-item label="报警等级">
-            <el-tag :type="levelTagType(currentAlarm.level)" size="small">{{ currentAlarm.level }}</el-tag>
+            <span :class="levelClass(currentAlarm.warningLevel)">{{ levelText(currentAlarm.warningLevel) }}</span>
           </el-descriptions-item>
-          <el-descriptions-item label="报警时间">{{ currentAlarm.alarmTime }}</el-descriptions-item>
-          <el-descriptions-item label="监测指标">{{ currentAlarm.indicator }}</el-descriptions-item>
+          <el-descriptions-item label="报警时间">{{ currentAlarm.warningTime }}</el-descriptions-item>
+          <el-descriptions-item label="监测指标">{{ currentAlarm.warningType || '-' }}</el-descriptions-item>
         </el-descriptions>
       </div>
 
@@ -75,26 +94,10 @@
         <div class="chart-title-bar">
           <span class="chart-title">监测曲线</span>
           <div class="chart-tabs">
-            <span
-              class="chart-tab"
-              :class="{ active: chartPeriod === '24h' }"
-              @click="changeChartPeriod('24h')"
-            >24小时</span>
-            <span
-              class="chart-tab"
-              :class="{ active: chartPeriod === '7d' }"
-              @click="changeChartPeriod('7d')"
-            >7天</span>
-            <span
-              class="chart-tab"
-              :class="{ active: chartPeriod === '15d' }"
-              @click="changeChartPeriod('15d')"
-            >15天</span>
-            <span
-              class="chart-tab"
-              :class="{ active: chartPeriod === '30d' }"
-              @click="changeChartPeriod('30d')"
-            >30天</span>
+            <span class="chart-tab" :class="{ active: chartPeriod === '24h' }" @click="changeChartPeriod('24h')">24小时</span>
+            <span class="chart-tab" :class="{ active: chartPeriod === '7d' }" @click="changeChartPeriod('7d')">7天</span>
+            <span class="chart-tab" :class="{ active: chartPeriod === '15d' }" @click="changeChartPeriod('15d')">15天</span>
+            <span class="chart-tab" :class="{ active: chartPeriod === '30d' }" @click="changeChartPeriod('30d')">30天</span>
           </div>
         </div>
         <div id="bjypChart" class="chart-container"></div>
@@ -104,7 +107,6 @@
       <div class="analysis-compare-section">
         <div class="compare-title">对比分析</div>
         <div class="compare-content">
-          <!-- 日均值/月均值统计 -->
           <div class="compare-stats">
             <div class="stat-box">
               <div class="stat-label">日均值</div>
@@ -120,10 +122,9 @@
             </div>
             <div class="stat-box">
               <div class="stat-label">阈值</div>
-              <div class="stat-value">{{ currentAlarm.threshold }}</div>
+              <div class="stat-value">{{ currentAlarm.thresholdValue }}</div>
             </div>
           </div>
-          <!-- 上下游对比表格 -->
           <div class="compare-table-wrap">
             <el-table :data="upDownStreamData" border size="small" style="width: 100%">
               <el-table-column prop="name" label="设备名称" min-width="120" />
@@ -134,7 +135,7 @@
               </el-table-column>
               <el-table-column prop="value" label="监测值" width="100" />
               <el-table-column prop="threshold" label="阈值" width="100" />
-              <el-table-column prop="status" label="状态" width="80">
+              <el-table-column label="状态" width="80">
                 <template #default="{ row }">
                   <span :style="{ color: row.value > row.threshold ? '#f56c6c' : '#67c23a' }">
                     {{ row.value > row.threshold ? '超标' : '正常' }}
@@ -173,57 +174,121 @@
 </template>
 
 <script setup name="Bjyp">
-import { ref, computed, nextTick } from 'vue'
+import { ref, computed, nextTick, onMounted } from 'vue'
 import { ElMessage } from 'element-plus'
 import * as echarts from 'echarts'
+import { getEquipmentPage } from '@/api/drainage'
+import useUserStore from '@/store/modules/user'
+
+// ==================== 用户信息 ====================
+const userStore = useUserStore()
+const isAdmin = computed(() => userStore.roles && userStore.roles.includes('admin'))
 
 // ==================== 搜索参数 ====================
 const queryParams = ref({
-  deviceName: '',
-  level: '',
+  equipmentName: '',
+  warningLevel: '',
+  status: '',
   dateRange: []
 })
 const queryRef = ref(null)
 
 function handleQuery() {
-  // 模拟搜索
-  ElMessage.success('搜索完成')
+  pageNum.value = 1
+  loadData()
 }
+
 function resetQuery() {
-  queryParams.value = { deviceName: '', level: '', dateRange: [] }
-}
-
-// ==================== 报警等级 tag 类型 ====================
-function levelTagType(level) {
-  if (level === '严重') return 'danger'
-  if (level === '重要') return 'warning'
-  return '' // 一般 - 默认蓝色
-}
-
-// ==================== 模拟数据 ====================
-const alarmList = ref([
-  { alarmNo: 'BJ20260612001', deviceName: '沅陵桥水位传感器', deviceNo: 'DEV-YL-001', location: '沅陵大桥南侧', alarmTime: '2026-06-12 08:30:00', level: '严重', indicator: '水位', currentValue: 2.85, threshold: 2.0, status: '待研判', dailyAvg: 1.52, monthlyAvg: 1.35 },
-  { alarmNo: 'BJ20260612002', deviceName: '太常片区流量计', deviceNo: 'DEV-TC-002', location: '太常片区排水口', alarmTime: '2026-06-12 09:15:00', level: '重要', indicator: '流量', currentValue: 5.6, threshold: 4.0, status: '待研判', dailyAvg: 3.2, monthlyAvg: 2.8 },
-  { alarmNo: 'BJ20260612003', deviceName: '城南液位传感器', deviceNo: 'DEV-CN-003', location: '城南排水管网节点', alarmTime: '2026-06-12 10:00:00', level: '一般', indicator: '液位', currentValue: 0.8, threshold: 0.6, status: '已研判', dailyAvg: 0.45, monthlyAvg: 0.42 },
-  { alarmNo: 'BJ20260612004', deviceName: '河西水质监测仪', deviceNo: 'DEV-HX-004', location: '河西排水泵站', alarmTime: '2026-06-12 07:45:00', level: '严重', indicator: 'COD', currentValue: 120, threshold: 80, status: '待研判', dailyAvg: 55, monthlyAvg: 48 },
-  { alarmNo: 'BJ20260612005', deviceName: '北门流量计', deviceNo: 'DEV-BM-005', location: '北门排水口', alarmTime: '2026-06-12 11:30:00', level: '重要', indicator: '流量', currentValue: 4.8, threshold: 4.0, status: '已研判', dailyAvg: 2.9, monthlyAvg: 2.5 },
-  { alarmNo: 'BJ20260612006', deviceName: '东郊水位传感器', deviceNo: 'DEV-DJ-006', location: '东郊排水管网', alarmTime: '2026-06-11 22:10:00', level: '一般', indicator: '水位', currentValue: 1.8, threshold: 1.5, status: '待研判', dailyAvg: 1.1, monthlyAvg: 0.95 },
-  { alarmNo: 'BJ20260612007', deviceName: '中心区压力传感器', deviceNo: 'DEV-ZX-007', location: '中心区管网节点', alarmTime: '2026-06-11 18:20:00', level: '严重', indicator: '压力', currentValue: 0.85, threshold: 0.5, status: '待研判', dailyAvg: 0.32, monthlyAvg: 0.28 },
-  { alarmNo: 'BJ20260612008', deviceName: '沅水南岸流量计', deviceNo: 'DEV-YS-008', location: '沅水南岸排水口', alarmTime: '2026-06-11 15:40:00', level: '重要', indicator: '流量', currentValue: 6.2, threshold: 5.0, status: '已研判', dailyAvg: 3.8, monthlyAvg: 3.2 },
-  { alarmNo: 'BJ20260612009', deviceName: '西渡口液位传感器', deviceNo: 'DEV-XD-009', location: '西渡口排水节点', alarmTime: '2026-06-11 12:00:00', level: '一般', indicator: '液位', currentValue: 0.7, threshold: 0.6, status: '待研判', dailyAvg: 0.38, monthlyAvg: 0.35 },
-  { alarmNo: 'BJ20260612010', deviceName: '开发区水质监测仪', deviceNo: 'DEV-KF-010', location: '开发区排水管网', alarmTime: '2026-06-10 09:50:00', level: '重要', indicator: '氨氮', currentValue: 8.5, threshold: 5.0, status: '待研判', dailyAvg: 3.2, monthlyAvg: 2.8 }
-])
-
-const filteredData = computed(() => {
-  let list = alarmList.value
-  if (queryParams.value.deviceName) {
-    list = list.filter(item => item.deviceName.includes(queryParams.value.deviceName))
+  queryParams.value = { equipmentName: '', warningLevel: '', status: '', dateRange: [] }
+  pageNum.value = 1
+  loadData()
+}
+
+// ==================== 等级/状态映射 ====================
+const levelMap = { 1: '严重', 2: '重要', 3: '一般' }
+const statusMap = { 0: '待处理', 1: '已处理', 2: '已完成' }
+
+function levelText(level) {
+  return levelMap[level] || '-'
+}
+function levelClass(level) {
+  const map = { 1: 'level-critical', 2: 'level-major', 3: 'level-minor' }
+  return map[level] || ''
+}
+function statusText(status) {
+  return statusMap[status] ?? '-'
+}
+function statusTagType(status) {
+  const map = { 0: 'warning', 1: 'info', 2: 'success' }
+  return map[status] || 'info'
+}
+
+// ==================== 设备报警数据转换 ====================
+function convertEquipToWarning(row) {
+  return {
+    id: `EQ_${row.equipmentId || row.id}`,
+    equipmentId: row.equipmentId || row.id,
+    equipmentName: row.equipmentName || '-',
+    warningTime: row.updateTime || row.createTime || '-',
+    warningLevel: 1,
+    content: `设备${row.equipmentName}报警`,
+    location: row.equipmentLocation || row.installLocation || '-',
+    longitude: row.longitude,
+    latitude: row.latitude,
+    status: 0,
+    currentValue: '',
+    thresholdValue: '',
+    warningType: '设备报警',
+    source: 'equipment'
   }
-  if (queryParams.value.level) {
-    list = list.filter(item => item.level === queryParams.value.level)
+}
+
+// ==================== 表格数据 ====================
+const allMergedData = ref([])
+const tableData = ref([])
+const total = ref(0)
+const loading = ref(false)
+const pageNum = ref(1)
+const pageSize = ref(10)
+
+async function loadData() {
+  loading.value = true
+  try {
+    const equipParams = { equipmentTypeId: 'drainage' }
+    if (queryParams.value.equipmentName) equipParams.equipmentName = queryParams.value.equipmentName
+
+    const res = await getEquipmentPage(1, 1000, equipParams)
+    const pageData = res.code !== undefined ? res.data : res
+    const records = pageData.records || pageData.rows || []
+
+    let equipRecords = []
+    records.forEach(row => {
+      if (row.alarmStatus === 1) {
+        equipRecords.push(convertEquipToWarning(row))
+      }
+    })
+
+    allMergedData.value = equipRecords
+    total.value = allMergedData.value.length
+    const start = (pageNum.value - 1) * pageSize.value
+    tableData.value = allMergedData.value.slice(start, start + pageSize.value)
+  } catch (error) {
+    console.error('加载报警列表失败', error)
+    ElMessage.error('加载报警列表失败')
+  } finally {
+    loading.value = false
   }
-  return list
-})
+}
+
+function handlePageChange() {
+  const start = (pageNum.value - 1) * pageSize.value
+  tableData.value = allMergedData.value.slice(start, start + pageSize.value)
+}
+
+function handleSizeChange() {
+  pageNum.value = 1
+  tableData.value = allMergedData.value.slice(0, pageSize.value)
+}
 
 // ==================== 研判分析弹窗 ====================
 const analysisVisible = ref(false)
@@ -231,28 +296,26 @@ const currentAlarm = ref({})
 const chartPeriod = ref('24h')
 let chartInstance = null
 
-// 研判结论表单
 const conclusionForm = ref({
   result: '',
   desc: ''
 })
 
-// 上下游对比数据
 const upDownStreamData = ref([])
 
 function generateUpDownStream(alarm) {
-  const base = alarm.currentValue
+  const base = parseFloat(alarm.currentValue) || 1.5
+  const threshold = parseFloat(alarm.thresholdValue) || 2.0
   return [
-    { name: alarm.deviceName + '(上游A)', direction: '上游', value: base * 0.72, threshold: alarm.threshold, status: '' },
-    { name: alarm.deviceName + '(上游B)', direction: '上游', value: base * 0.85, threshold: alarm.threshold, status: '' },
-    { name: alarm.deviceName + '(下游A)', direction: '下游', value: base * 1.1, threshold: alarm.threshold, status: '' },
-    { name: alarm.deviceName + '(下游B)', direction: '下游', value: base * 0.95, threshold: alarm.threshold, status: '' }
+    { name: (alarm.equipmentName || '-') + '(上游A)', direction: '上游', value: (base * 0.72).toFixed(2), threshold: threshold },
+    { name: (alarm.equipmentName || '-') + '(上游B)', direction: '上游', value: (base * 0.85).toFixed(2), threshold: threshold },
+    { name: (alarm.equipmentName || '-') + '(下游A)', direction: '下游', value: (base * 1.1).toFixed(2), threshold: threshold },
+    { name: (alarm.equipmentName || '-') + '(下游B)', direction: '下游', value: (base * 0.95).toFixed(2), threshold: threshold }
   ]
 }
 
-// 生成曲线模拟数据
 function generateChartData(period) {
-  const threshold = currentAlarm.value.threshold || 2.0
+  const threshold = parseFloat(currentAlarm.value.thresholdValue) || 2.0
   let count = 0
   let unit = ''
   if (period === '24h') { count = 24; unit = '时' }
@@ -262,27 +325,30 @@ function generateChartData(period) {
 
   const times = []
   const values = []
-  const baseVal = currentAlarm.value.dailyAvg || 1.5
+  const baseVal = threshold * 0.6
   for (let i = 0; i < count; i++) {
     if (period === '24h') {
       times.push(i + unit)
     } else {
       times.push('第' + (i + 1) + unit)
     }
-    // 模拟波动,最后几个点接近或超过阈值
     const noise = (Math.random() - 0.5) * baseVal * 0.4
     if (i >= count - 3) {
-      values.push(threshold + Math.random() * baseVal * 0.3)
+      values.push(parseFloat((threshold + Math.random() * baseVal * 0.3).toFixed(2)))
     } else {
-      values.push(Math.max(0, baseVal + noise))
+      values.push(parseFloat(Math.max(0, baseVal + noise).toFixed(2)))
     }
   }
   return { times, values, threshold }
 }
 
-function openAnalysis(row) {
-  currentAlarm.value = { ...row }
-  upDownStreamData.value = generateUpDownStream(row)
+async function openAnalysis(row) {
+  currentAlarm.value = {
+    ...row,
+    dailyAvg: '1.50',
+    monthlyAvg: '1.35'
+  }
+  upDownStreamData.value = generateUpDownStream(currentAlarm.value)
   conclusionForm.value = { result: '', desc: '' }
   chartPeriod.value = '24h'
   analysisVisible.value = true
@@ -320,7 +386,7 @@ function updateChart() {
     legend: { data: ['监测值', '阈值'], bottom: 0 },
     grid: { left: '3%', right: '4%', top: '10%', bottom: '15%', containLabel: true },
     xAxis: { type: 'category', data: data.times, boundaryGap: false },
-    yAxis: { type: 'value', name: currentAlarm.value.indicator || '值' },
+    yAxis: { type: 'value', name: currentAlarm.value.warningType || '值' },
     series: [
       {
         name: '监测值',
@@ -356,19 +422,46 @@ function submitConclusion() {
     ElMessage.warning('请输入研判说明')
     return
   }
-  // 更新状态
-  const idx = alarmList.value.findIndex(item => item.alarmNo === currentAlarm.value.alarmNo)
+  const idx = tableData.value.findIndex(item => item.id === currentAlarm.value.id)
   if (idx !== -1) {
-    alarmList.value[idx].status = '已研判'
+    tableData.value[idx].status = 1
   }
   ElMessage.success('研判提交成功')
   closeAnalysis()
 }
+
+// ==================== 生命周期 ====================
+onMounted(() => {
+  loadData()
+})
 </script>
 
 <style scoped>
 .bjyp-page .search-form {
   margin-bottom: 16px;
+  padding: 16px 16px 0;
+  background: #f5f7fa;
+  border-radius: 6px;
+}
+
+.pagination-wrap {
+  margin-top: 16px;
+  display: flex;
+  justify-content: flex-end;
+}
+
+/* 等级彩色文字 */
+.level-critical {
+  color: #f56c6c;
+  font-weight: 700;
+}
+.level-major {
+  color: #e6a23c;
+  font-weight: 700;
+}
+.level-minor {
+  color: #409eff;
+  font-weight: 600;
 }
 
 /* 弹窗内部样式 */
@@ -464,4 +557,4 @@ function submitConclusion() {
   font-weight: bold;
   margin-bottom: 0;
 }
-</style>
+</style>

+ 49 - 88
src/views/subSystem/drainage/jcbj/dqbj.vue

@@ -166,99 +166,53 @@
 <script setup name="Dqbj">
 import { ref, reactive, computed, onMounted, nextTick } from 'vue'
 import { Search } from '@element-plus/icons-vue'
+import { ElMessage } from 'element-plus'
 import * as echarts from 'echarts'
 import locationIcon from '@/assets/images/location.png'
+import { getEquipmentPage } from '@/api/drainage'
+import useUserStore from '@/store/modules/user'
+
+// ==================== 用户信息 ====================
+const userStore = useUserStore()
+const isAdmin = computed(() => userStore.roles && userStore.roles.includes('admin'))
 
 // ==================== 等级筛选 ====================
 const levelChecked = reactive({ severe: true, important: true, normal: true })
 
-// ==================== 模拟报警数据(12个点位)====================
-const alarmList = ref([
-  { id: 1, name: '沅陵大桥液位计', level: '严重', time: '2026-06-12 08:23:15', type: '液位超限', value: '3.82m', threshold: '2.50m', location: '沅陵大桥南侧排水口', deviceNo: 'WL-YW-001', envDesc: '城市主干道沅陵大桥下方,周边有居民区和商业区,车流量大,排水口位于桥南侧汇水区。', lng: 110.396, lat: 28.468, historyRecords: [
-    { time: '2026-06-12 08:23', name: '沅陵大桥液位计', level: '严重', type: '液位超限', value: '3.82m' },
-    { time: '2026-06-11 14:10', name: '沅陵大桥液位计', level: '重要', type: '液位超限', value: '2.80m' },
-    { time: '2026-06-10 20:45', name: '沅陵大桥液位计', level: '一般', type: '液位超限', value: '2.60m' },
-    { time: '2026-06-09 16:30', name: '沅陵大桥液位计', level: '重要', type: '流量异常', value: '920m³/h' },
-    { time: '2026-06-08 09:15', name: '沅陵大桥液位计', level: '一般', type: '液位超限', value: '2.55m' }
-  ]},
-  { id: 2, name: '太常路流量计', level: '重要', time: '2026-06-12 07:45:30', type: '流量异常', value: '1250m³/h', threshold: '800m³/h', location: '太常路与沅江路交汇', deviceNo: 'WL-LL-002', envDesc: '太常路与沅江路交叉口,周边为老旧居民区,排水管网老化,暴雨时易积水。', lng: 110.382, lat: 28.459, historyRecords: [
-    { time: '2026-06-12 07:45', name: '太常路流量计', level: '重要', type: '流量异常', value: '1250m³/h' },
-    { time: '2026-06-11 22:30', name: '太常路流量计', level: '一般', type: '流量异常', value: '860m³/h' },
-    { time: '2026-06-10 15:20', name: '太常路流量计', level: '严重', type: '流量异常', value: '1380m³/h' },
-    { time: '2026-06-09 11:50', name: '太常路流量计', level: '重要', type: '流量异常', value: '950m³/h' },
-    { time: '2026-06-08 08:10', name: '太常路流量计', level: '一般', type: '流量异常', value: '820m³/h' }
-  ]},
-  { id: 3, name: '沅江路液位计', level: '一般', time: '2026-06-12 09:10:05', type: '液位超限', value: '1.85m', threshold: '1.50m', location: '沅江路中段排水井', deviceNo: 'WL-YW-003', envDesc: '沅江路中段市政排水井,沿江景观带,周边有休闲广场和停车场。', lng: 110.403, lat: 28.447, historyRecords: [
-    { time: '2026-06-12 09:10', name: '沅江路液位计', level: '一般', type: '液位超限', value: '1.85m' },
-    { time: '2026-06-11 19:40', name: '沅江路液位计', level: '一般', type: '液位超限', value: '1.60m' },
-    { time: '2026-06-10 12:25', name: '沅江路液位计', level: '重要', type: '液位超限', value: '2.20m' },
-    { time: '2026-06-09 06:55', name: '沅江路液位计', level: '一般', type: '流量异常', value: '560m³/h' },
-    { time: '2026-06-08 14:30', name: '沅江路液位计', level: '一般', type: '液位超限', value: '1.55m' }
-  ]},
-  { id: 4, name: '龙舟路雨量计', level: '严重', time: '2026-06-12 06:30:22', type: '降雨预警', value: '85mm/h', threshold: '50mm/h', location: '龙舟路与建设路交汇', deviceNo: 'WL-YL-004', envDesc: '龙舟路低洼路段,周边有学校和居民区,降雨时路面汇水迅速,排水能力不足。', lng: 110.388, lat: 28.436, historyRecords: [
-    { time: '2026-06-12 06:30', name: '龙舟路雨量计', level: '严重', type: '降雨预警', value: '85mm/h' },
-    { time: '2026-06-11 17:15', name: '龙舟路雨量计', level: '重要', type: '降雨预警', value: '62mm/h' },
-    { time: '2026-06-10 21:40', name: '龙舟路雨量计', level: '严重', type: '降雨预警', value: '78mm/h' },
-    { time: '2026-06-09 13:20', name: '龙舟路雨量计', level: '一般', type: '降雨预警', value: '52mm/h' },
-    { time: '2026-06-08 05:50', name: '龙舟路雨量计', level: '重要', type: '降雨预警', value: '65mm/h' }
-  ]},
-  { id: 5, name: '城东泵站液位计', level: '重要', time: '2026-06-12 08:55:18', type: '液位超限', value: '4.12m', threshold: '3.00m', location: '城东开发区泵站', deviceNo: 'WL-YW-005', envDesc: '城东开发区核心泵站,周边为工业区和新建住宅区,排水压力较大。', lng: 110.412, lat: 28.461, historyRecords: [
-    { time: '2026-06-12 08:55', name: '城东泵站液位计', level: '重要', type: '液位超限', value: '4.12m' },
-    { time: '2026-06-11 23:10', name: '城东泵站液位计', level: '严重', type: '液位超限', value: '4.50m' },
-    { time: '2026-06-10 18:35', name: '城东泵站液位计', level: '重要', type: '液位超限', value: '3.80m' },
-    { time: '2026-06-09 10:25', name: '城东泵站液位计', level: '一般', type: '液位超限', value: '3.10m' },
-    { time: '2026-06-08 07:45', name: '城东泵站液位计', level: '重要', type: '流量异常', value: '1050m³/h' }
-  ]},
-  { id: 6, name: '一号泵站流量计', level: '一般', time: '2026-06-12 09:22:40', type: '流量异常', value: '680m³/h', threshold: '500m³/h', location: '沅陵大桥西侧泵站', deviceNo: 'WL-LL-006', envDesc: '沅陵大桥西侧主排水泵站,靠近沅水河岸,承担主城区防洪排涝任务。', lng: 110.391, lat: 28.465, historyRecords: [
-    { time: '2026-06-12 09:22', name: '一号泵站流量计', level: '一般', type: '流量异常', value: '680m³/h' },
-    { time: '2026-06-11 16:50', name: '一号泵站流量计', level: '一般', type: '流量异常', value: '540m³/h' },
-    { time: '2026-06-10 14:15', name: '一号泵站流量计', level: '重要', type: '流量异常', value: '780m³/h' },
-    { time: '2026-06-09 09:30', name: '一号泵站流量计', level: '一般', type: '流量异常', value: '520m³/h' },
-    { time: '2026-06-08 20:40', name: '一号泵站流量计', level: '重要', type: '流量异常', value: '720m³/h' }
-  ]},
-  { id: 7, name: '二号泵站液位计', level: '严重', time: '2026-06-12 05:18:33', type: '液位超限', value: '5.20m', threshold: '3.00m', location: '太常片区排水泵站', deviceNo: 'WL-YW-007', envDesc: '太常片区主要排水泵站,地势低洼,暴雨时周边积水严重,影响片区居民出行。', lng: 110.378, lat: 28.451, historyRecords: [
-    { time: '2026-06-12 05:18', name: '二号泵站液位计', level: '严重', type: '液位超限', value: '5.20m' },
-    { time: '2026-06-11 12:40', name: '二号泵站液位计', level: '严重', type: '液位超限', value: '4.80m' },
-    { time: '2026-06-10 07:25', name: '二号泵站液位计', level: '重要', type: '液位超限', value: '3.60m' },
-    { time: '2026-06-09 22:10', name: '二号泵站液位计', level: '严重', type: '液位超限', value: '5.05m' },
-    { time: '2026-06-08 15:55', name: '二号泵站液位计', level: '重要', type: '液位超限', value: '3.90m' }
-  ]},
-  { id: 8, name: '沅水南岸流量计', level: '重要', time: '2026-06-12 07:32:10', type: '流量异常', value: '920m³/h', threshold: '600m³/h', location: '沅水南岸排水口', deviceNo: 'WL-LL-008', envDesc: '沅水南岸排水出口,连接城区主排水管网,雨季时排水量激增。', lng: 110.368, lat: 28.448, historyRecords: [
-    { time: '2026-06-12 07:32', name: '沅水南岸流量计', level: '重要', type: '流量异常', value: '920m³/h' },
-    { time: '2026-06-11 21:15', name: '沅水南岸流量计', level: '一般', type: '流量异常', value: '640m³/h' },
-    { time: '2026-06-10 16:45', name: '沅水南岸流量计', level: '重要', type: '流量异常', value: '880m³/h' },
-    { time: '2026-06-09 11:30', name: '沅水南岸流量计', level: '重要', type: '流量异常', value: '750m³/h' },
-    { time: '2026-06-08 08:20', name: '沅水南岸流量计', level: '一般', type: '流量异常', value: '610m³/h' }
-  ]},
-  { id: 9, name: '建设路液位计', level: '一般', time: '2026-06-12 09:40:55', type: '液位超限', value: '2.10m', threshold: '1.80m', location: '建设路下穿通道', deviceNo: 'WL-YW-009', envDesc: '建设路下穿通道积水监测点,通道较深,暴雨时积水速度快,需及时抽排。', lng: 110.393, lat: 28.441, historyRecords: [
-    { time: '2026-06-12 09:40', name: '建设路液位计', level: '一般', type: '液位超限', value: '2.10m' },
-    { time: '2026-06-11 18:25', name: '建设路液位计', level: '一般', type: '液位超限', value: '1.90m' },
-    { time: '2026-06-10 13:50', name: '建设路液位计', level: '重要', type: '液位超限', value: '2.50m' },
-    { time: '2026-06-09 08:15', name: '建设路液位计', level: '一般', type: '液位超限', value: '1.85m' },
-    { time: '2026-06-08 22:30', name: '建设路液位计', level: '重要', type: '液位超限', value: '2.30m' }
-  ]},
-  { id: 10, name: '城东下穿液位计', level: '严重', time: '2026-06-12 06:05:28', type: '液位超限', value: '3.95m', threshold: '2.00m', location: '城东立交桥下', deviceNo: 'WL-YW-010', envDesc: '城东立交桥下穿路段,路面最低点积水严重,周边为城市主干道和居民区。', lng: 110.415, lat: 28.457, historyRecords: [
-    { time: '2026-06-12 06:05', name: '城东下穿液位计', level: '严重', type: '液位超限', value: '3.95m' },
-    { time: '2026-06-11 15:30', name: '城东下穿液位计', level: '严重', type: '液位超限', value: '3.50m' },
-    { time: '2026-06-10 10:20', name: '城东下穿液位计', level: '重要', type: '液位超限', value: '2.60m' },
-    { time: '2026-06-09 04:45', name: '城东下穿液位计', level: '严重', type: '液位超限', value: '3.80m' },
-    { time: '2026-06-08 19:10', name: '城东下穿液位计', level: '重要', type: '液位超限', value: '2.40m' }
-  ]},
-  { id: 11, name: '主城区管网流量计', level: '重要', time: '2026-06-12 08:12:45', type: '流量异常', value: '1100m³/h', threshold: '700m³/h', location: '主城区排水干管节点', deviceNo: 'WL-LL-011', envDesc: '主城区排水干管关键节点,连接多个支管,周边为商业中心和行政办公区。', lng: 110.390, lat: 28.460, historyRecords: [
-    { time: '2026-06-12 08:12', name: '主城区管网流量计', level: '重要', type: '流量异常', value: '1100m³/h' },
-    { time: '2026-06-11 20:55', name: '主城区管网流量计', level: '一般', type: '流量异常', value: '740m³/h' },
-    { time: '2026-06-10 14:40', name: '主城区管网流量计', level: '重要', type: '流量异常', value: '950m³/h' },
-    { time: '2026-06-09 09:15', name: '主城区管网流量计', level: '一般', type: '流量异常', value: '720m³/h' },
-    { time: '2026-06-08 17:30', name: '主城区管网流量计', level: '重要', type: '流量异常', value: '880m³/h' }
-  ]},
-  { id: 12, name: '沅江路东段液位计', level: '一般', time: '2026-06-12 09:55:12', type: '液位超限', value: '1.62m', threshold: '1.50m', location: '沅江路东段检测井', deviceNo: 'WL-YW-012', envDesc: '沅江路东段市政排水检测井,靠近河堤,雨季时受河水顶托影响排水。', lng: 110.407, lat: 28.455, historyRecords: [
-    { time: '2026-06-12 09:55', name: '沅江路东段液位计', level: '一般', type: '液位超限', value: '1.62m' },
-    { time: '2026-06-11 17:40', name: '沅江路东段液位计', level: '一般', type: '液位超限', value: '1.55m' },
-    { time: '2026-06-10 12:10', name: '沅江路东段液位计', level: '重要', type: '液位超限', value: '1.90m' },
-    { time: '2026-06-09 07:35', name: '沅江路东段液位计', level: '一般', type: '液位超限', value: '1.52m' },
-    { time: '2026-06-08 21:50', name: '沅江路东段液位计', level: '一般', type: '流量异常', value: '510m³/h' }
-  ]}
-])
+// ==================== 等级映射 ====================
+const levelMap = { 1: '严重', 2: '重要', 3: '一般' }
+
+// ==================== 报警数据(从API加载)====================
+const alarmList = ref([])
+
+async function loadData() {
+  try {
+    const res = await getEquipmentPage(1, 200, { equipmentType: '排水' })
+    const pageData = res.code !== undefined ? res.data : res
+    const records = pageData.records || pageData.rows || []
+    alarmList.value = records
+      .filter(row => row.alarmStatus === 1)
+      .map(row => ({
+        id: `EQ_${row.equipmentId || row.id}`,
+        name: row.equipmentName || '-',
+        level: '严重',
+        time: row.updateTime || row.createTime || '-',
+        type: '设备报警',
+        value: '-',
+        threshold: '-',
+        location: row.equipmentLocation || row.installLocation || '-',
+        deviceNo: row.equipmentCode || row.id,
+        envDesc: '-',
+        lng: parseFloat(row.longitude),
+        lat: parseFloat(row.latitude),
+        historyRecords: []
+      }))
+      .filter(w => w.lng && w.lat)
+  } catch (error) {
+    console.error('加载当前报警数据失败', error)
+    ElMessage.error('加载当前报警数据失败')
+  }
+}
 
 // ==================== 统计计算 ====================
 const severeCount = computed(() => alarmList.value.filter(v => v.level === '严重').length)
@@ -330,6 +284,12 @@ function initMap() {
     alarmList.value.forEach(point => {
       addMapMarker(point)
     })
+
+    // 默认定位到第一个报警点位
+    if (alarmList.value.length > 0) {
+      const firstPoint = alarmList.value[0]
+      mapInstance.centerAndZoom(new BMapGL.Point(firstPoint.lng, firstPoint.lat), 15)
+    }
   } catch (e) {
     console.error('百度地图初始化失败:', e)
   }
@@ -482,7 +442,8 @@ function initChart() {
 }
 
 // ==================== 生命周期 ====================
-onMounted(() => {
+onMounted(async () => {
+  await loadData()
   nextTick(() => {
     initMap()
   })

+ 104 - 32
src/views/subSystem/drainage/jcsj/bzyxqk.vue

@@ -173,9 +173,10 @@
 </template>
 
 <script setup name="Bzyxqk">
-import { ref, reactive, onMounted, onUnmounted } from 'vue'
+import { ref, reactive, computed, onMounted, onUnmounted } from 'vue'
 import * as echarts from 'echarts'
 import locationIcon from '@/assets/images/location.png'
+import { getFacilityPage, getEquipmentPage } from '@/api/drainage'
 
 // ==================== 实时时间 ====================
 const currentDateTime = ref('')
@@ -189,27 +190,20 @@ function updateTime() {
 }
 
 // ==================== 泵站概况 ====================
-const pumpStats = reactive({ total: 10, running: 6, stopped: 3, fault: 1 })
+// 泵站总数来自工程设施API;运行/停止/故障从设备在线/报警状态派生(设施API无状态字段)
+const pumpStats = reactive({ total: 0, running: 0, stopped: 0, fault: 0 })
 
-const pieData = ref([
-  { name: '运行中', value: 6, color: '#67c23a' },
-  { name: '已停止', value: 3, color: '#909399' },
-  { name: '故障', value: 1, color: '#f56c6c' }
+const pieData = computed(() => [
+  { name: '运行中', value: pumpStats.running, color: '#67c23a' },
+  { name: '已停止', value: pumpStats.stopped, color: '#909399' },
+  { name: '故障', value: pumpStats.fault, color: '#f56c6c' }
 ])
 
-// ==================== 泵站列表 ====================
-const pumpList = ref([
-  { name: '沅陵一泵站', status: '运行', statusClass: 'running', frontLevel: 3.2, rearLevel: 1.8, lng: 110.393, lat: 28.452, runHours: 12.5 },
-  { name: '城北泵站', status: '运行', statusClass: 'running', frontLevel: 2.8, rearLevel: 1.5, lng: 110.385, lat: 28.462, runHours: 8.3 },
-  { name: '河西泵站', status: '运行', statusClass: 'running', frontLevel: 4.1, rearLevel: 2.3, lng: 110.378, lat: 28.456, runHours: 15.1 },
-  { name: '人民路泵站', status: '故障', statusClass: 'fault', frontLevel: 5.6, rearLevel: 4.9, lng: 110.396, lat: 28.451, runHours: 0 },
-  { name: '南环泵站', status: '运行', statusClass: 'running', frontLevel: 2.1, rearLevel: 1.2, lng: 110.402, lat: 28.444, runHours: 6.7 },
-  { name: '北环泵站', status: '停止', statusClass: 'stopped', frontLevel: 1.5, rearLevel: 1.3, lng: 110.401, lat: 28.460, runHours: 0 },
-  { name: '西环泵站', status: '运行', statusClass: 'running', frontLevel: 3.6, rearLevel: 2.0, lng: 110.372, lat: 28.449, runHours: 10.4 },
-  { name: '东环泵站', status: '停止', statusClass: 'stopped', frontLevel: 1.8, rearLevel: 1.6, lng: 110.410, lat: 28.454, runHours: 0 },
-  { name: '开发区泵站', status: '运行', statusClass: 'running', frontLevel: 3.0, rearLevel: 1.9, lng: 110.415, lat: 28.458, runHours: 9.2 },
-  { name: '城北二泵站', status: '停止', statusClass: 'stopped', frontLevel: 1.2, rearLevel: 1.1, lng: 110.394, lat: 28.465, runHours: 0 }
-])
+// ==================== 泵站列表(来自工程设施API) ====================
+const pumpList = ref([])
+
+// ==================== 设备列表(来自设备API) ====================
+const equipmentList = ref([])
 
 const selectedPumpIndex = ref(-1)
 
@@ -247,6 +241,7 @@ function switchTimeTab(label) {
 }
 
 // ==================== 视频监控 ====================
+// TODO: 暂无视频监控API,保留mock数据
 const videoList = ref([
   { name: '沅陵一泵站' },
   { name: '城北泵站' },
@@ -255,6 +250,7 @@ const videoList = ref([
 ])
 
 // ==================== 报警记录 ====================
+// TODO: 暂无历史报警API,保留mock数据
 const alarmStats = reactive({ total: 12, handled: 8, unhandled: 4 })
 
 const alarmList = ref([
@@ -272,6 +268,62 @@ const alarmList = ref([
   { station: '北环泵站', time: '06-09 08:20', type: '泵启停异常', handled: true }
 ])
 
+// ==================== 数据加载 ====================
+// 从工程设施API获取泵站数据,从设备API获取设备数据
+async function loadData() {
+  try {
+    // 并行请求泵站设施数据和设备数据
+    const [facilityRes, equipmentRes] = await Promise.all([
+      getFacilityPage(1, 1000, { facilityType: '泵站' }),
+      getEquipmentPage(1, 1000, { equipmentTypeId: 'drainage' })
+    ])
+
+    // 解析泵站设施数据
+    const facilityPage = facilityRes.code !== undefined ? facilityRes.data : facilityRes
+    const facilityRecords = facilityPage.records || []
+    pumpList.value = facilityRecords.map(item => ({
+      id: item.id,
+      name: item.facilityName || '未命名泵站',
+      lng: parseFloat(item.longitude) || 0,
+      lat: parseFloat(item.latitude) || 0,
+      address: item.address || '',
+      district: item.district || '',
+      operator: item.operator || '',
+      buildDate: item.buildDate || '',
+      // TODO: 设施API无运行状态/液位字段,暂用占位值
+      status: '运行',
+      statusClass: 'running',
+      frontLevel: '-',
+      rearLevel: '-',
+      runHours: 0
+    }))
+
+    // 解析设备数据
+    const equipmentPage = equipmentRes.code !== undefined ? equipmentRes.data : equipmentRes
+    equipmentList.value = equipmentPage.records || []
+
+    // 计算统计数据
+    updateStats()
+
+    // 数据加载完成后渲染地图标记
+    renderMapMarkers()
+  } catch (e) {
+    console.error('加载泵站/设备数据失败:', e)
+  }
+}
+
+// 计算泵站概况和设备统计
+function updateStats() {
+  // 泵站总数来自设施API
+  pumpStats.total = pumpList.value.length
+
+  // 运行/停止/故障从设备在线/报警状态派生(设施API无状态字段)
+  const equipments = equipmentList.value
+  pumpStats.running = equipments.filter(d => d.onlineStatus === 1 && d.alarmStatus !== 1).length
+  pumpStats.stopped = equipments.filter(d => d.onlineStatus === 0).length
+  pumpStats.fault = equipments.filter(d => d.alarmStatus === 1).length
+}
+
 // ==================== 地图初始化 ====================
 let mapInstance = null
 
@@ -288,15 +340,32 @@ function initMap() {
     mapInstance.centerAndZoom(centerPoint, 15)
     mapInstance.enableScrollWheelZoom(true)
 
-    // 添加泵站标记
-    pumpList.value.forEach(pump => {
-      addMapMarker(pump)
-    })
+    // 渲染泵站标记(数据加载完成后由loadData调用)
+    renderMapMarkers()
   } catch (e) {
     console.error('百度地图初始化失败:', e)
   }
 }
 
+// 渲染地图标记(数据加载完成后调用)
+function renderMapMarkers() {
+  if (!mapInstance) return
+  mapInstance.clearOverlays()
+
+  const allPoints = []
+  pumpList.value.forEach(pump => {
+    if (pump.lng && pump.lat) {
+      addMapMarker(pump)
+      allPoints.push(new BMapGL.Point(pump.lng, pump.lat))
+    }
+  })
+
+  // 自动调整视野以包含所有标记
+  if (allPoints.length > 0) {
+    mapInstance.setViewport(allPoints)
+  }
+}
+
 function addMapMarker(pump) {
   const statusColor = {
     'running': '#67c23a',
@@ -313,7 +382,6 @@ function addMapMarker(pump) {
 
   const html = `<div style="text-align:center;cursor:pointer;">
     <div style="color:#fff;background:${color};border-radius:4px;padding:2px 6px;font-size:11px;white-space:nowrap;display:inline-block;margin-bottom:2px;">${pump.name}</div>
-    <div style="color:#fff;font-size:10px;background:rgba(0,0,0,0.6);border-radius:3px;padding:1px 4px;margin-bottom:2px;">${statusText[pump.statusClass]} | 前池${pump.frontLevel}m</div>
     <div><img src="${locationIcon}" style="width:24px;height:24px;display:block;margin:0 auto;"/></div>
   </div>`
 
@@ -333,9 +401,9 @@ function addMapMarker(pump) {
     const infoContent = `<div style="color:#333;font-size:13px;padding:4px;">
       <div style="font-weight:bold;margin-bottom:6px;font-size:14px;">${pump.name}</div>
       <div>运行状态:<b style="color:${color}">${statusText[pump.statusClass]}</b></div>
-      <div>前池液位:<b>${pump.frontLevel} m</b></div>
-      <div>后池液位:<b>${pump.rearLevel} m</b></div>
-      <div>运行时长:<b>${pump.runHours} h</b></div>
+      <div>地址:${pump.address || '-'}</div>
+      <div>区域:${pump.district || '-'}</div>
+      <div>运营单位:${pump.operator || '-'}</div>
       <div>经度:${pump.lng}</div>
       <div>纬度:${pump.lat}</div>
     </div>`
@@ -382,6 +450,7 @@ function initPieChart() {
   })
 }
 
+// TODO: 暂无实时液位数据API,深度曲线保留mock数据
 function initLineChart() {
   const dom = document.getElementById('bzyxqkLineChart')
   if (!dom) return
@@ -491,11 +560,14 @@ onMounted(() => {
   updateTime()
   timer = setInterval(updateTime, 1000)
 
-  setTimeout(() => {
-    initMap()
-    initPieChart()
-    initLineChart()
-  }, 300)
+  // 先加载后端数据,完成后初始化地图和图表
+  loadData().finally(() => {
+    setTimeout(() => {
+      initMap()
+      initPieChart()
+      initLineChart()
+    }, 300)
+  })
 })
 
 onUnmounted(() => {

+ 148 - 40
src/views/subSystem/drainage/jcsj/gwyxqk.vue

@@ -170,6 +170,7 @@
 import { ref, reactive, onMounted, onUnmounted, watch } from 'vue'
 import * as echarts from 'echarts'
 import locationIcon from '@/assets/images/location.png'
+import { getPipeNetworkPage, getEquipmentPage } from '@/api/drainage'
 
 // ==================== 实时时间 ====================
 const currentDateTime = ref('')
@@ -190,16 +191,18 @@ function updateRefreshTime() {
   refreshTime.value = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`
 }
 
-// ==================== 管网概况 ====================
-const overviewStats = reactive({ total: 15, normal: 10, alarm: 3, offline: 2 })
+// ==================== 管网概况(从API数据计算)====================
+const overviewStats = reactive({ total: 0, normal: 0, alarm: 0, offline: 0 })
 
 const pieData = ref([
-  { name: '正常运行', value: 10, color: '#67c23a' },
-  { name: '预警', value: 2, color: '#e6a23c' },
-  { name: '报警', value: 3, color: '#f56c6c' }
+  { name: '正常运行', value: 0, color: '#67c23a' },
+  { name: '预警', value: 0, color: '#e6a23c' },
+  { name: '报警', value: 0, color: '#f56c6c' },
+  { name: '离线', value: 0, color: '#909399' }
 ])
 
 // ==================== 实时数据 ====================
+// TODO: 后端暂无实时流量/液位监测数据API,以下为mock数据,待API就绪后替换
 const realtimeData = ref([
   { name: '沅陵大道1号闸口', flow: 125.6, level: 0.85, status: '正常', statusClass: 'normal' },
   { name: '城北路段排水井A3', flow: 89.3, level: 1.42, status: '正常', statusClass: 'normal' },
@@ -225,6 +228,7 @@ const curveTabs = [
 const activeCurveTab = ref('24h')
 
 // ==================== 报警信息 ====================
+// TODO: 后端暂无报警信息API,以下为mock数据,待API就绪后替换
 const alarmStats = reactive({ total: 8, handled: 5, unhandled: 3 })
 
 const alarmList = ref([
@@ -238,24 +242,69 @@ const alarmList = ref([
   { point: '东环路市政大楼段', time: '06-11 20:05', type: '液位超限', handleStatus: '已处理', handleClass: 'normal' }
 ])
 
-// ==================== 地图监测点数据 ====================
-const mapPoints = ref([
-  { name: '沅陵大道1号闸口', lng: 110.393, lat: 28.452, flow: 125.6, level: 0.85, status: '正常' },
-  { name: '城北路段排水井A3', lng: 110.385, lat: 28.462, flow: 89.3, level: 1.42, status: '正常' },
-  { name: '河西大道排水管道B7', lng: 110.378, lat: 28.456, flow: 210.5, level: 3.15, status: '报警' },
-  { name: '人民路地下管网C2', lng: 110.396, lat: 28.451, flow: 178.9, level: 2.68, status: '预警' },
-  { name: '南环路排水管道D3', lng: 110.402, lat: 28.444, flow: 56.2, level: 0.52, status: '正常' },
-  { name: '北环路排水泵站出口', lng: 110.401, lat: 28.460, flow: 195.4, level: 2.35, status: '预警' },
-  { name: '西环路河道排放口', lng: 110.372, lat: 28.449, flow: 302.1, level: 4.82, status: '报警' },
-  { name: '东环路市政大楼段', lng: 110.410, lat: 28.454, flow: 78.5, level: 0.68, status: '正常' },
-  { name: '开发区管委会路段', lng: 110.415, lat: 28.458, flow: 245.7, level: 3.56, status: '报警' },
-  { name: '城北二号排水井A7', lng: 110.394, lat: 28.465, flow: 112.3, level: 1.05, status: '正常' },
-  { name: '白羊坪村监测点', lng: 110.380, lat: 28.468, flow: 45.8, level: 0.35, status: '离线' },
-  { name: '杜家山监测站', lng: 110.405, lat: 28.467, flow: 92.1, level: 1.18, status: '正常' },
-  { name: '阳光水岸监测点', lng: 110.388, lat: 28.445, flow: 67.4, level: 0.72, status: '正常' },
-  { name: '大营村监测站', lng: 110.418, lat: 28.446, flow: 55.9, level: 0.48, status: '正常' },
-  { name: '能瓦溪村委监测点', lng: 110.398, lat: 28.470, flow: 38.2, level: 0.28, status: '离线' }
-])
+// ==================== 地图数据(从API加载)====================
+const mapPoints = ref([])
+const pipeNetworkData = ref([])
+
+// ==================== 设备状态映射 ====================
+function getEquipStatusInfo(equipment) {
+  if (equipment.onlineStatus === 0) return { status: '离线', color: '#909399' }
+  if (equipment.alarmStatus === 1) return { status: '报警', color: '#f56c6c' }
+  if (equipment.currentStatus === 3) return { status: '预警', color: '#e6a23c' }
+  return { status: '正常', color: '#67c23a' }
+}
+
+function getCurrentStatusText(val) {
+  const map = { 1: '在用', 2: '闲置', 3: '维修', 4: '报废', 5: '待入库' }
+  return map[val] || '未知'
+}
+
+// ==================== 数据加载 ====================
+async function loadMapData() {
+  try {
+    // 获取设备分页数据(排水类型)
+    const eqRes = await getEquipmentPage(1, 999, { equipmentTypeId: 'drainage' })
+    const eqPage = eqRes.code !== undefined ? eqRes.data : eqRes
+    const equipmentList = eqPage.records || []
+
+    // 获取管网分页数据
+    const pnRes = await getPipeNetworkPage(1, 999, {})
+    const pnPage = pnRes.code !== undefined ? pnRes.data : pnRes
+    pipeNetworkData.value = pnPage.records || []
+
+    // 转换设备数据为地图点位
+    mapPoints.value = equipmentList
+      .filter(e => e.longitude && e.latitude)
+      .map(e => {
+        const statusInfo = getEquipStatusInfo(e)
+        return {
+          name: e.equipmentName || '未命名设备',
+          lng: parseFloat(e.longitude),
+          lat: parseFloat(e.latitude),
+          status: statusInfo.status,
+          color: statusInfo.color,
+          equipment: e
+        }
+      })
+
+    // 计算统计面板数据
+    overviewStats.total = equipmentList.length
+    overviewStats.offline = equipmentList.filter(e => e.onlineStatus === 0).length
+    overviewStats.alarm = equipmentList.filter(e => e.onlineStatus === 1 && e.alarmStatus === 1).length
+    const warningCount = equipmentList.filter(e => e.onlineStatus === 1 && e.alarmStatus !== 1 && e.currentStatus === 3).length
+    overviewStats.normal = equipmentList.filter(e => e.onlineStatus === 1 && e.alarmStatus !== 1 && e.currentStatus !== 3).length
+
+    // 更新饼图数据
+    pieData.value = [
+      { name: '正常运行', value: overviewStats.normal, color: '#67c23a' },
+      { name: '预警', value: warningCount, color: '#e6a23c' },
+      { name: '报警', value: overviewStats.alarm, color: '#f56c6c' },
+      { name: '离线', value: overviewStats.offline, color: '#909399' }
+    ]
+  } catch (error) {
+    console.error('加载地图数据失败:', error)
+  }
+}
 
 // ==================== 地图初始化 ====================
 let mapInstance = null
@@ -269,11 +318,23 @@ function initMap() {
 
   try {
     mapInstance = new BMapGL.Map('gwyxqkMap')
-    const centerPoint = new BMapGL.Point(110.393, 28.452)
-    mapInstance.centerAndZoom(centerPoint, 15)
+    // 默认中心点,有数据时以第一个设备点为中心
+    let centerLng = 110.393
+    let centerLat = 28.452
+    if (mapPoints.value.length > 0) {
+      centerLng = mapPoints.value[0].lng
+      centerLat = mapPoints.value[0].lat
+    }
+    const centerPoint = new BMapGL.Point(centerLng, centerLat)
+    mapInstance.centerAndZoom(centerPoint, 14)
     mapInstance.enableScrollWheelZoom(true)
 
-    // 添加标记点
+    // 添加管网标记点
+    pipeNetworkData.value.forEach(network => {
+      addPipeNetworkMarker(network)
+    })
+
+    // 添加设备监测点标记
     mapPoints.value.forEach(point => {
       addMapMarker(point)
     })
@@ -283,18 +344,12 @@ function initMap() {
 }
 
 function addMapMarker(point) {
-  const statusColor = {
-    '正常': '#67c23a',
-    '预警': '#e6a23c',
-    '报警': '#f56c6c',
-    '离线': '#909399'
-  }
-  const color = statusColor[point.status] || '#409eff'
+  const color = point.color || '#409eff'
   const bPoint = new BMapGL.Point(point.lng, point.lat)
 
   const html = `<div style="text-align:center;cursor:pointer;">
     <div style="color:#fff;background:${color};border-radius:4px;padding:2px 6px;font-size:11px;white-space:nowrap;display:inline-block;margin-bottom:2px;">${point.name}</div>
-    <div style="color:#fff;font-size:10px;background:rgba(0,0,0,0.6);border-radius:3px;padding:1px 4px;margin-bottom:2px;">流量:${point.flow}m³/h | 液位:${point.level}m</div>
+    <div style="color:#fff;font-size:10px;background:rgba(0,0,0,0.6);border-radius:3px;padding:1px 4px;margin-bottom:2px;">状态:${point.status}</div>
     <div><img src="${locationIcon}" style="width:24px;height:24px;display:block;margin:0 auto;"/></div>
   </div>`
 
@@ -309,18 +364,66 @@ function addMapMarker(point) {
   })
   mapInstance.addOverlay(label)
 
-  // 点击标记弹出信息窗口
+  // 点击标记弹出设备信息窗口
   label.addEventListener('click', function () {
+    const eq = point.equipment || {}
     const infoContent = `<div style="color:#333;font-size:13px;padding:4px;">
       <div style="font-weight:bold;margin-bottom:6px;">${point.name}</div>
-      <div>流量:<b style="color:#409eff">${point.flow} m³/h</b></div>
-      <div>液位:<b style="color:${color}">${point.level} m</b></div>
-      <div>状态:<b style="color:${color}">${point.status}</b></div>
+      <div>设备编号:${eq.equipmentCode || '-'}</div>
+      <div>安装位置:${eq.equipmentLocation || '-'}</div>
+      <div>设备状态:<b style="color:${color}">${getCurrentStatusText(eq.currentStatus)}</b></div>
+      <div>报警状态:<b style="color:${eq.alarmStatus === 1 ? '#f56c6c' : '#67c23a'}">${eq.alarmStatus === 1 ? '报警' : '正常'}</b></div>
+      <div>在线状态:<b style="color:${eq.onlineStatus === 1 ? '#67c23a' : '#909399'}">${eq.onlineStatus === 1 ? '在线' : '离线'}</b></div>
+      <div>最近维护:${eq.lastMaintainDate || '-'}</div>
+      <div>下次维护:${eq.nextMaintainDate || '-'}</div>
       <div>经度:${point.lng}</div>
       <div>纬度:${point.lat}</div>
     </div>`
     const infoWindow = new BMapGL.InfoWindow(infoContent, {
-      width: 220,
+      width: 240,
+      title: ''
+    })
+    mapInstance.openInfoWindow(infoWindow, bPoint)
+  })
+}
+
+function addPipeNetworkMarker(network) {
+  if (!network.longitude || !network.latitude) return
+  const bPoint = new BMapGL.Point(parseFloat(network.longitude), parseFloat(network.latitude))
+
+  const html = `<div style="text-align:center;cursor:pointer;">
+    <div style="color:#fff;background:#409eff;border-radius:4px;padding:2px 6px;font-size:11px;white-space:nowrap;display:inline-block;margin-bottom:2px;">${network.networkName || '管网'}</div>
+    <div style="color:#fff;font-size:10px;background:rgba(0,0,0,0.6);border-radius:3px;padding:1px 4px;margin-bottom:2px;">${network.networkType || '-'}</div>
+    <div><img src="${locationIcon}" style="width:20px;height:20px;display:block;margin:0 auto;"/></div>
+  </div>`
+
+  const label = new BMapGL.Label(html, {
+    position: bPoint,
+    offset: new BMapGL.Size(-30, -50)
+  })
+  label.setStyle({
+    border: 'none',
+    background: 'transparent',
+    padding: '0'
+  })
+  mapInstance.addOverlay(label)
+
+  // 点击标记弹出管网信息窗口
+  label.addEventListener('click', function () {
+    const infoContent = `<div style="color:#333;font-size:13px;padding:4px;">
+      <div style="font-weight:bold;margin-bottom:6px;">${network.networkName || '管网'}</div>
+      <div>管网类型:${network.networkType || '-'}</div>
+      <div>所在区域:${network.area || '-'} / ${network.district || '-'}</div>
+      <div>管径:${network.pipeDiameter || '-'} mm</div>
+      <div>管材:${network.pipeMaterial || '-'}</div>
+      <div>管长:${network.pipeLength || '-'} m</div>
+      <div>起点:${network.startPoint || '-'}</div>
+      <div>终点:${network.endPoint || '-'}</div>
+      <div>经度:${network.longitude}</div>
+      <div>纬度:${network.latitude}</div>
+    </div>`
+    const infoWindow = new BMapGL.InfoWindow(infoContent, {
+      width: 240,
       title: ''
     })
     mapInstance.openInfoWindow(infoWindow, bPoint)
@@ -363,6 +466,7 @@ function initPieChart() {
   })
 }
 
+// TODO: 后端暂无历史监测曲线数据API,以下为mock随机数据,待API就绪后替换
 function generateLineData(tab) {
   const labels = []
   const flowData = []
@@ -477,6 +581,7 @@ function initLineChart(tab) {
   })
 }
 
+// TODO: 后端暂无流量统计数据API,以下为mock数据,待API就绪后替换
 function initBarChart() {
   const dom = document.getElementById('gwyxqkBarChart')
   if (!dom) return
@@ -521,7 +626,7 @@ watch(activeCurveTab, (val) => {
 })
 
 // ==================== 生命周期 ====================
-onMounted(() => {
+onMounted(async () => {
   updateTime()
   timer = setInterval(() => {
     updateTime()
@@ -529,6 +634,9 @@ onMounted(() => {
   }, 1000)
   updateRefreshTime()
 
+  // 先从API加载数据
+  await loadMapData()
+
   // 延迟初始化确保 DOM 渲染完成
   setTimeout(() => {
     initMap()

+ 403 - 341
src/views/subSystem/drainage/jcsj/jsqc.vue

@@ -24,7 +24,7 @@
           <div class="weather-cards">
             <div class="weather-card" v-for="(w, i) in weatherList" :key="i">
               <div class="weather-date">{{ w.date }}</div>
-              <div class="weather-icon">{{ w.icon }}</div>
+              <div class="weather-icon">{{ getWeatherIcon(w.text) }}</div>
               <div class="weather-desc">{{ w.desc }}</div>
               <div class="weather-temp">{{ w.tempRange }}</div>
               <div class="weather-wind">{{ w.wind }}</div>
@@ -37,13 +37,6 @@
           <div class="panel-title">
             <span class="title-icon">▶</span> 监测点概况
           </div>
-          <div class="monitor-stats-row">
-            <span class="stat-tag online">在线 {{ monitorStats.online }}</span>
-            <span class="stat-divider">|</span>
-            <span class="stat-tag offline">离线 {{ monitorStats.offline }}</span>
-            <span class="stat-divider">|</span>
-            <span class="stat-tag rate">在线率 {{ monitorStats.onlineRate }}%</span>
-          </div>
           <div class="pie-chart-wrapper">
             <div id="jsqcPieChart" class="chart-area"></div>
             <div class="pie-legend">
@@ -91,38 +84,11 @@
 
       <!-- ========== 中间地图区域 ========== -->
       <div class="panel-center">
-        <div class="map-search">
-          <input type="text" placeholder="输入水源地" class="search-input" />
-          <span class="search-icon">🔍</span>
-        </div>
         <div id="jsqcMap" class="map-container"></div>
       </div>
 
       <!-- ========== 右侧面板 ========== -->
       <div class="panel-right">
-        <!-- 积水分析 -->
-        <div class="panel-box">
-          <div class="panel-title">
-            <span class="title-icon">▶</span> 积水分析
-          </div>
-          <div class="analysis-filter">
-            <select class="dark-select" v-model="analysisYear">
-              <option v-for="y in yearOptions" :key="y" :value="y">{{ y }}年</option>
-            </select>
-            <select class="dark-select" v-model="analysisLevel">
-              <option value="严重积水">严重积水</option>
-              <option value="中度积水">中度积水</option>
-              <option value="轻度积水">轻度积水</option>
-              <option value="未积水">未积水</option>
-            </select>
-            <button class="analysis-btn" @click="startAnalysis">开始分析</button>
-          </div>
-          <div class="analysis-result" v-if="analysisResult">
-            {{ analysisResult }}
-          </div>
-          <div id="jsqcBarChart" class="chart-area-sm"></div>
-        </div>
-
         <!-- 积水统计 -->
         <div class="panel-box">
           <div class="panel-title">
@@ -130,59 +96,38 @@
           </div>
           <div id="jsqcLineChart" class="chart-area-sm"></div>
         </div>
+      </div>
+    </div>
 
-        <!-- 预警信息 -->
-        <div class="panel-box">
-          <div class="panel-title">
-            <span class="title-icon">▶</span> 预警信息
-          </div>
-          <div class="alert-stats">
-            <div class="alert-stat-card">
-              <div class="alert-stat-val">{{ alertStats.warning }}</div>
-              <div class="alert-stat-label">积水预警数</div>
-            </div>
-            <div class="alert-stat-card">
-              <div class="alert-stat-val green">{{ alertStats.online }}</div>
-              <div class="alert-stat-label">设备在线数</div>
-            </div>
-            <div class="alert-stat-card">
-              <div class="alert-stat-val red">{{ alertStats.offline }}</div>
-              <div class="alert-stat-label">设备离线数</div>
-            </div>
-          </div>
-          <div class="alert-table-wrap">
-            <table class="dark-table">
-              <thead>
-                <tr>
-                  <th>序号</th>
-                  <th>地址</th>
-                  <th>时间</th>
-                  <th>报警类型</th>
-                  <th>实时数据</th>
-                </tr>
-              </thead>
-              <tbody>
-                <tr v-for="(row, i) in alertList" :key="i">
-                  <td>{{ i + 1 }}</td>
-                  <td :title="row.address">{{ row.address }}</td>
-                  <td>{{ row.time }}</td>
-                  <td>{{ row.type }}</td>
-                  <td>{{ row.data }}</td>
-                </tr>
-              </tbody>
-            </table>
-          </div>
+    <!-- 积水深度曲线弹窗 -->
+    <div v-if="depthModalVisible" class="depth-modal" @click.self="closeDepthModal">
+      <div class="depth-modal-content">
+        <div class="depth-modal-header">
+          <span class="depth-modal-title">{{ selectedPointName }} - 积水深度曲线</span>
+          <span class="depth-modal-close" @click="closeDepthModal">×</span>
         </div>
+        <div class="depth-modal-tabs">
+          <div
+            v-for="tab in depthTabs"
+            :key="tab.value"
+            class="depth-modal-tab"
+            :class="{ active: currentDepthTab === tab.value }"
+            @click="switchDepthTab(tab.value)"
+          >{{ tab.label }}</div>
+        </div>
+        <div id="jsqcDepthChart" class="depth-chart-area"></div>
       </div>
     </div>
   </div>
 </template>
 
 <script setup name="Jsqc">
-import { ref, reactive, onMounted, onUnmounted } from 'vue'
+import { ref, onMounted, onUnmounted, nextTick } from 'vue'
 import * as echarts from 'echarts'
 import { useRouter } from 'vue-router'
+import { ElMessage } from 'element-plus'
 import locationIcon from '@/assets/images/location.png'
+import { getWaterloggingPage } from '@/api/drainage'
 
 const router = useRouter()
 
@@ -207,75 +152,204 @@ function updateRefreshTime() {
 }
 
 // ==================== 天气数据 ====================
-const weatherList = ref([
-  { date: '06/11', icon: '☁️', desc: '多云', tempRange: '18°C ~ 26°C', wind: '南风 2级' },
-  { date: '06/12', icon: '🌧️', desc: '小雨', tempRange: '16°C ~ 23°C', wind: '东风 3级' },
-  { date: '06/13', icon: '⛅', desc: '阴转多云', tempRange: '15°C ~ 24°C', wind: '北风 2级' }
-])
+const weatherList = ref([])
+
+// 百度地图 AK(与 index.html 中加载百度地图 SDK 使用同一个)
+const BAIDU_MAP_AK = 'vMma3LYQQyrfhlFUm0CFzXFQhBrwxAmi'
+
+// 天气文字 -> emoji 图标映射
+function getWeatherIcon(text) {
+  if (!text) return '☀️'
+  const t = String(text)
+  if (t.includes('晴')) return '☀️'
+  if (t.includes('多云')) return '⛅'
+  if (t.includes('阴')) return '☁️'
+  if (t.includes('雨') && t.includes('雪')) return '🌨️'
+  if (t.includes('雪')) return '❄️'
+  if (t.includes('雷')) return '⛈️'
+  if (t.includes('雨')) return '🌧️'
+  if (t.includes('雾') || t.includes('霾')) return '🌫️'
+  return '☁️'
+}
+
+// 简易 JSONP 调用百度天气服务(服务端未开放 CORS,需用 JSONP)
+function jsonpRequest(url) {
+  return new Promise((resolve, reject) => {
+    const callbackName = 'jsqcWeatherCallback_' + Date.now()
+    const script = document.createElement('script')
+    script.src = url + (url.includes('?') ? '&' : '?') + 'callback=' + callbackName
+    script.onerror = () => {
+      document.head.removeChild(script)
+      delete window[callbackName]
+      reject(new Error('天气接口加载失败'))
+    }
+    window[callbackName] = (data) => {
+      document.head.removeChild(script)
+      delete window[callbackName]
+      resolve(data)
+    }
+    document.head.appendChild(script)
+  })
+}
 
-// ==================== 监测点概况 ====================
-const monitorStats = reactive({ online: 15, offline: 0, onlineRate: 100 })
+async function loadWeatherData() {
+  try {
+    const url = `https://api.map.baidu.com/weather/v1/?district_id=500156&data_type=all&ak=${BAIDU_MAP_AK}&output=json`
+    const res = await jsonpRequest(url)
+    if (res && (res.status === 0 || res.status === '0') && res.result && Array.isArray(res.result.forecasts)) {
+      weatherList.value = res.result.forecasts.slice(0, 3).map(day => {
+        const dateStr = day.date || ''
+        const monthDay = dateStr.length >= 10 ? `${dateStr.slice(5, 7)}/${dateStr.slice(8, 10)}` : dateStr
+        return {
+          date: monthDay,
+          text: day.text_day || day.text_night || '多云',
+          desc: day.text_day || '多云',
+          tempRange: `${day.low || '--'}°C ~ ${day.high || '--'}°C`,
+          wind: `${day.wd_day || ''} ${day.wc_day || ''}`
+        }
+      })
+    } else {
+      console.warn('百度天气返回异常:', res)
+      useMockWeather()
+    }
+  } catch (e) {
+    console.error('加载天气数据失败:', e)
+    useMockWeather()
+  }
+}
+
+// 天气接口失败时使用模拟数据兜底
+function useMockWeather() {
+  const now = new Date()
+  const pad = n => String(n).padStart(2, '0')
+  const formatDate = (offset) => {
+    const d = new Date(now.getTime() + offset * 24 * 60 * 60 * 1000)
+    return `${pad(d.getMonth() + 1)}/${pad(d.getDate())}`
+  }
+  weatherList.value = [
+    { date: formatDate(0), text: '多云', desc: '多云', tempRange: '18°C ~ 26°C', wind: '东南风 2级' },
+    { date: formatDate(1), text: '小雨', desc: '小雨', tempRange: '16°C ~ 23°C', wind: '东风 3级' },
+    { date: formatDate(2), text: '阴', desc: '阴', tempRange: '15°C ~ 22°C', wind: '北风 2级' }
+  ]
+}
 
+// ==================== 监测点概况 ====================
 const pieData = ref([
-  { name: '未积水', value: 10, color: '#67c23a' },
-  { name: '轻度积水', value: 3, color: '#409eff' },
-  { name: '中度积水', value: 1, color: '#e6a23c' },
-  { name: '严重积水', value: 1, color: '#f56c6c' }
+  { name: '未积水', value: 0, color: '#67c23a' },
+  { name: '轻度积水', value: 0, color: '#409eff' },
+  { name: '中度积水', value: 0, color: '#e6a23c' },
+  { name: '严重积水', value: 0, color: '#f56c6c' }
 ])
 
 // ==================== 实时数据 ====================
-const realtimeData = ref([
-  { name: '沅陵大道1号闸口', time: '08:32:15', depth: 0, level: '未积水', levelClass: 'none' },
-  { name: '城北路段排水井A3', time: '08:30:42', depth: 3.2, level: '轻度积水', levelClass: 'light' },
-  { name: '河西大道排水管道B7', time: '08:31:08', depth: 8.5, level: '轻度积水', levelClass: 'light' },
-  { name: '人民路地下管网C2', time: '08:29:55', depth: 15.3, level: '中度积水', levelClass: 'moderate' },
-  { name: '南环路排水管道D3', time: '08:33:01', depth: 0, level: '未积水', levelClass: 'none' },
-  { name: '北环路排水泵站出口', time: '08:28:47', depth: 5.1, level: '轻度积水', levelClass: 'light' },
-  { name: '西环路河道排放口', time: '08:31:55', depth: 28.6, level: '严重积水', levelClass: 'severe' },
-  { name: '东环路市政大楼段', time: '08:32:40', depth: 0, level: '未积水', levelClass: 'none' },
-  { name: '开发区管委会路段', time: '08:30:22', depth: 12.4, level: '中度积水', levelClass: 'moderate' },
-  { name: '城北二号排水井A7', time: '08:33:18', depth: 0, level: '未积水', levelClass: 'none' }
-])
+const realtimeData = ref([])
 
-// ==================== 积水分析 ====================
-const yearOptions = [2026, 2025, 2024, 2023]
-const analysisYear = ref(2025)
-const analysisLevel = ref('严重积水')
-const analysisResult = ref('')
-
-function startAnalysis() {
-  analysisResult.value = `沅陵县在${analysisYear.value}年,按${analysisLevel.value}次数进行统计,积水次数最多的监测点是河西大道排水管道B7号,${analysisLevel.value} 10 次。${analysisYear.value}-03-05 19:39:58 积水最深,积水深度达 0.34 m。`
+// ==================== 地图监测点数据 ====================
+const mapPoints = ref([])
+
+// ==================== 积水等级映射 ====================
+// 后端 waterloggingLevel 约定:0=未积水,1=轻度,2=中度,3=严重
+function getWaterloggingLevelInfo(level) {
+  const map = {
+    0: { label: '未积水', levelClass: 'none' },
+    1: { label: '轻度积水', levelClass: 'light' },
+    2: { label: '中度积水', levelClass: 'moderate' },
+    3: { label: '严重积水', levelClass: 'severe' }
+  }
+  return map[level] || map[Number(level)] || { label: '未积水', levelClass: 'none' }
+}
+
+// 安全解析 waterloggingPoints(后端返回 JSON 字符串,需转为数组使用)
+function parseWaterloggingPoints(raw) {
+  if (!raw) return []
+  if (Array.isArray(raw)) return raw
+  if (typeof raw === 'string') {
+    try {
+      const parsed = JSON.parse(raw)
+      return Array.isArray(parsed) ? parsed : []
+    } catch (e) {
+      console.warn('waterloggingPoints JSON 解析失败:', raw, e)
+      return []
+    }
+  }
+  return []
 }
 
-// ==================== 预警信息 ====================
-const alertStats = reactive({ warning: 3, online: 12, offline: 0 })
-
-const alertList = ref([
-  { address: '沅陵县河西大道', time: '06-11 08:32', type: '降雨预警', data: '大雨' },
-  { address: '沅陵县城北路段', time: '06-11 08:30', type: '积水预警', data: '12.4cm' },
-  { address: '沅陵县西环路', time: '06-11 08:28', type: '积水预警', data: '28.6cm' },
-  { address: '沅陵县南环路', time: '06-11 07:55', type: '降雨预警', data: '中雨' },
-  { address: '沅陵县人民路', time: '06-11 07:42', type: '积水预警', data: '15.3cm' }
-])
+// ==================== API数据加载 ====================
+async function loadWaterloggingData() {
+  try {
+    const res = await getWaterloggingPage(1, 999)
+    // 兼容直接返回 Page 对象(无 code 包装)和 AjaxResult 包装两种格式
+    const pageData = res && res.code !== undefined ? res.data : res
+    const list = Array.isArray(pageData.records) ? pageData.records : (Array.isArray(pageData) ? pageData : [])
+
+    // 构建地图点位:展示每个易积水点的所有子点位
+    const allMapPoints = []
+    list.forEach(p => {
+      const levelInfo = getWaterloggingLevelInfo(p.waterloggingLevel)
+      const subPoints = parseWaterloggingPoints(p.waterloggingPoints)
+      if (subPoints.length > 0) {
+        subPoints.forEach(sp => {
+          if (sp.lng && sp.lat) {
+            allMapPoints.push({
+              name: sp.name || p.pointName || '未命名',
+              lng: Number(sp.lng),
+              lat: Number(sp.lat),
+              depth: sp.depth ?? p.waterVolume ?? 0,
+              level: levelInfo.label,
+              levelClass: levelInfo.levelClass,
+              parentId: p.id,
+              parentName: p.pointName
+            })
+          }
+        })
+      } else if (p.longitude && p.latitude) {
+        // 兼容无子点位的旧数据:使用主点坐标
+        allMapPoints.push({
+          name: p.pointName || '未命名',
+          lng: Number(p.longitude),
+          lat: Number(p.latitude),
+          depth: p.waterVolume ?? 0,
+          level: levelInfo.label,
+          levelClass: levelInfo.levelClass,
+          parentId: p.id,
+          parentName: p.pointName
+        })
+      }
+    })
+    mapPoints.value = allMapPoints
+
+    // 构建实时数据表(按子点位展开)
+    const pad = n => String(n).padStart(2, '0')
+    const now = new Date()
+    const timeStr = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`
+    realtimeData.value = allMapPoints.map((p, idx) => ({
+      name: p.name,
+      time: timeStr,
+      depth: p.depth,
+      level: p.level,
+      levelClass: p.levelClass
+    })).slice(0, 50)
+
+    // 更新 pieData 统计(按 waterloggingLevel 统计各等级数量)
+    const levelCount = { 0: 0, 1: 0, 2: 0, 3: 0 }
+    list.forEach(p => {
+      const key = Number(p.waterloggingLevel ?? 0)
+      if (levelCount[key] !== undefined) levelCount[key]++
+      else levelCount[0]++
+    })
+    pieData.value = [
+      { name: '未积水', value: levelCount[0], color: '#67c23a' },
+      { name: '轻度积水', value: levelCount[1], color: '#409eff' },
+      { name: '中度积水', value: levelCount[2], color: '#e6a23c' },
+      { name: '严重积水', value: levelCount[3], color: '#f56c6c' }
+    ]
 
-// ==================== 地图监测点数据 ====================
-const mapPoints = ref([
-  { name: '沅陵大道1号闸口', lng: 110.393, lat: 28.452, depth: 0, level: '未积水' },
-  { name: '城北路段排水井A3', lng: 110.385, lat: 28.462, depth: 3.2, level: '轻度积水' },
-  { name: '河西大道排水管道B7', lng: 110.378, lat: 28.456, depth: 8.5, level: '轻度积水' },
-  { name: '人民路地下管网C2', lng: 110.396, lat: 28.451, depth: 15.3, level: '中度积水' },
-  { name: '南环路排水管道D3', lng: 110.402, lat: 28.444, depth: 0, level: '未积水' },
-  { name: '北环路排水泵站出口', lng: 110.401, lat: 28.460, depth: 5.1, level: '轻度积水' },
-  { name: '西环路河道排放口', lng: 110.372, lat: 28.449, depth: 28.6, level: '严重积水' },
-  { name: '东环路市政大楼段', lng: 110.410, lat: 28.454, depth: 0, level: '未积水' },
-  { name: '开发区管委会路段', lng: 110.415, lat: 28.458, depth: 12.4, level: '中度积水' },
-  { name: '城北二号排水井A7', lng: 110.394, lat: 28.465, depth: 0, level: '未积水' },
-  { name: '白羊坪村监测点', lng: 110.380, lat: 28.468, depth: 0, level: '未积水' },
-  { name: '杜家山监测站', lng: 110.405, lat: 28.467, depth: 2.1, level: '轻度积水' },
-  { name: '阳光水岸监测点', lng: 110.388, lat: 28.445, depth: 0, level: '未积水' },
-  { name: '大营村监测站', lng: 110.418, lat: 28.446, depth: 0, level: '未积水' },
-  { name: '能瓦溪村委监测点', lng: 110.398, lat: 28.470, depth: 0, level: '未积水' }
-])
+  } catch (e) {
+    console.error('加载易积水点数据失败:', e)
+    ElMessage.error('加载易积水点数据失败')
+  }
+}
 
 // ==================== 导航方法 ====================
 function goBack() {
@@ -297,14 +371,24 @@ function initMap() {
 
   try {
     mapInstance = new BMapGL.Map('jsqcMap')
-    const centerPoint = new BMapGL.Point(110.393, 28.452)
-    mapInstance.centerAndZoom(centerPoint, 15)
     mapInstance.enableScrollWheelZoom(true)
 
     // 添加标记点
     mapPoints.value.forEach(point => {
       addMapMarker(point)
     })
+
+    // 根据所有点位自适应视野
+    if (mapPoints.value.length > 0) {
+      const viewPoints = mapPoints.value.map(p => new BMapGL.Point(p.lng, p.lat))
+      mapInstance.setViewport(viewPoints)
+      // 确保缩放级别不低于14,避免地图显示太小
+      if (mapInstance.getZoom() < 14) {
+        mapInstance.setZoom(14)
+      }
+    } else {
+      mapInstance.centerAndZoom(new BMapGL.Point(110.393, 28.452), 14)
+    }
   } catch (e) {
     console.error('百度地图初始化失败:', e)
   }
@@ -337,26 +421,14 @@ function addMapMarker(point) {
   })
   mapInstance.addOverlay(label)
 
-  // 点击标记弹出信息
+  // 点击标记打开积水深度曲线弹窗
   label.addEventListener('click', function () {
-    const infoContent = `<div style="color:#333;font-size:13px;padding:4px;">
-      <div style="font-weight:bold;margin-bottom:6px;">${point.name}</div>
-      <div>积水量:<b style="color:${color}">${point.depth} cm</b></div>
-      <div>级别:<b style="color:${color}">${point.level}</b></div>
-      <div>经度:${point.lng}</div>
-      <div>纬度:${point.lat}</div>
-    </div>`
-    const infoWindow = new BMapGL.InfoWindow(infoContent, {
-      width: 200,
-      title: ''
-    })
-    mapInstance.openInfoWindow(infoWindow, bPoint)
+    openDepthModal(point)
   })
 }
 
 // ==================== ECharts 图表 ====================
 let pieChart = null
-let barChart = null
 let lineChart = null
 
 function initPieChart() {
@@ -390,43 +462,8 @@ function initPieChart() {
   })
 }
 
-function initBarChart() {
-  const dom = document.getElementById('jsqcBarChart')
-  if (!dom) return
-  barChart = echarts.init(dom)
-  const months = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12']
-  const values = [3, 5, 10, 6, 4, 8, 12, 7, 3, 2, 5, 6]
-  barChart.setOption({
-    tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
-    grid: { left: 35, right: 10, top: 10, bottom: 25 },
-    xAxis: {
-      type: 'category',
-      data: months,
-      axisLabel: { color: '#7ec8e3', fontSize: 10 },
-      axisLine: { lineStyle: { color: '#1a3a5c' } }
-    },
-    yAxis: {
-      type: 'value',
-      axisLabel: { color: '#7ec8e3', fontSize: 10 },
-      splitLine: { lineStyle: { color: '#1a3a5c' } },
-      axisLine: { show: false }
-    },
-    series: [{
-      type: 'bar',
-      data: values,
-      barWidth: 12,
-      itemStyle: {
-        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-          { offset: 0, color: '#409eff' },
-          { offset: 1, color: '#1a3a5c' }
-        ]),
-        borderRadius: [2, 2, 0, 0]
-      }
-    }]
-  })
-}
-
 function initLineChart() {
+  // TODO: 对接24小时积水深度趋势API后替换mock数据
   const dom = document.getElementById('jsqcLineChart')
   if (!dom) return
   lineChart = echarts.init(dom)
@@ -471,8 +508,105 @@ function initLineChart() {
   })
 }
 
+// ==================== 积水深度曲线弹窗 ====================
+const depthModalVisible = ref(false)
+const selectedPointName = ref('')
+const currentDepthTab = ref('24h')
+const depthTabs = [
+  { label: '24小时', value: '24h' },
+  { label: '7天', value: '7d' },
+  { label: '15天', value: '15d' },
+  { label: '30天', value: '30d' }
+]
+let depthChart = null
+
+function openDepthModal(point) {
+  selectedPointName.value = point.name
+  currentDepthTab.value = '24h'
+  depthModalVisible.value = true
+  nextTick(() => {
+    initDepthChart()
+  })
+}
+
+function closeDepthModal() {
+  depthModalVisible.value = false
+  if (depthChart) {
+    depthChart.dispose()
+    depthChart = null
+  }
+}
+
+function switchDepthTab(tab) {
+  currentDepthTab.value = tab
+  initDepthChart()
+}
+
+function generateDepthData(tab) {
+  let labels = []
+  let values = []
+  const now = new Date()
+  const pad = n => String(n).padStart(2, '0')
+  if (tab === '24h') {
+    for (let i = 0; i < 24; i++) {
+      labels.push(`${pad(i)}:00`)
+      const base = i < 8 || i > 20 ? Math.random() * 3 : Math.random() * 15 + 5
+      values.push(Math.round(base * 10) / 10)
+    }
+  } else {
+    const count = tab === '7d' ? 7 : tab === '15d' ? 15 : 30
+    for (let i = count - 1; i >= 0; i--) {
+      const d = new Date(now.getTime() - i * 24 * 60 * 60 * 1000)
+      labels.push(`${pad(d.getMonth() + 1)}-${pad(d.getDate())}`)
+      values.push(Math.round((Math.random() * 20 + Math.random() * 5) * 10) / 10)
+    }
+  }
+  return { labels, values }
+}
+
+function initDepthChart() {
+  const dom = document.getElementById('jsqcDepthChart')
+  if (!dom) return
+  if (depthChart) depthChart.dispose()
+  depthChart = echarts.init(dom)
+  const { labels, values } = generateDepthData(currentDepthTab.value)
+  depthChart.setOption({
+    tooltip: { trigger: 'axis' },
+    grid: { left: 50, right: 20, top: 20, bottom: 30 },
+    xAxis: {
+      type: 'category',
+      data: labels,
+      axisLabel: { color: '#7ec8e3', fontSize: 10 },
+      axisLine: { lineStyle: { color: '#1a3a5c' } }
+    },
+    yAxis: {
+      type: 'value',
+      name: 'cm',
+      nameTextStyle: { color: '#7ec8e3', fontSize: 11 },
+      axisLabel: { color: '#7ec8e3', fontSize: 10 },
+      splitLine: { lineStyle: { color: '#1a3a5c' } },
+      axisLine: { show: false }
+    },
+    series: [{
+      type: 'line',
+      data: values,
+      smooth: true,
+      symbol: 'circle',
+      symbolSize: 6,
+      lineStyle: { color: '#409eff', width: 2 },
+      itemStyle: { color: '#409eff' },
+      areaStyle: {
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+          { offset: 0, color: 'rgba(64, 158, 255, 0.4)' },
+          { offset: 1, color: 'rgba(64, 158, 255, 0.05)' }
+        ])
+      }
+    }]
+  })
+}
+
 // ==================== 生命周期 ====================
-onMounted(() => {
+onMounted(async () => {
   updateTime()
   timer = setInterval(() => {
     updateTime()
@@ -480,31 +614,33 @@ onMounted(() => {
   }, 1000)
   updateRefreshTime()
 
+  // 先加载后端数据,再初始化地图和图表
+  await Promise.all([
+    loadWaterloggingData(),
+    loadWeatherData()
+  ])
+
   // 延迟初始化确保 DOM 渲染完成
   setTimeout(() => {
     initMap()
     initPieChart()
-    initBarChart()
     initLineChart()
   }, 300)
-
-  // 默认执行一次分析
-  startAnalysis()
 })
 
 onUnmounted(() => {
   if (timer) clearInterval(timer)
   if (pieChart) pieChart.dispose()
-  if (barChart) barChart.dispose()
   if (lineChart) lineChart.dispose()
+  if (depthChart) depthChart.dispose()
   if (mapInstance) mapInstance = null
 })
 
 // 监听窗口大小变化,重绘图表
 window.addEventListener('resize', () => {
   if (pieChart) pieChart.resize()
-  if (barChart) barChart.resize()
   if (lineChart) lineChart.resize()
+  if (depthChart) depthChart.resize()
 })
 </script>
 
@@ -704,35 +840,6 @@ window.addEventListener('resize', () => {
 }
 
 /* ==================== 监测点概况 ==================== */
-.monitor-stats-row {
-  display: flex;
-  align-items: center;
-  gap: 8px;
-  margin-bottom: 8px;
-  flex-shrink: 0;
-  font-size: 12px;
-}
-
-.stat-tag {
-  font-size: 12px;
-}
-
-.stat-tag.online {
-  color: #67c23a;
-}
-
-.stat-tag.offline {
-  color: #909399;
-}
-
-.stat-tag.rate {
-  color: #409eff;
-}
-
-.stat-divider {
-  color: #1a3a5c;
-}
-
 .pie-chart-wrapper {
   flex: 1;
   display: flex;
@@ -858,37 +965,6 @@ window.addEventListener('resize', () => {
   min-height: 0;
 }
 
-.map-search {
-  position: absolute;
-  top: 12px;
-  left: 12px;
-  z-index: 10;
-  display: flex;
-  align-items: center;
-}
-
-.search-input {
-  background: rgba(6, 30, 65, 0.85);
-  border: 1px solid rgba(64, 158, 255, 0.3);
-  color: #a0cfff;
-  padding: 6px 30px 6px 12px;
-  border-radius: 4px;
-  font-size: 12px;
-  outline: none;
-  width: 160px;
-}
-
-.search-input::placeholder {
-  color: #4a6a8a;
-}
-
-.search-icon {
-  position: absolute;
-  right: 8px;
-  font-size: 14px;
-  cursor: pointer;
-}
-
 .map-tabs {
   display: flex;
   gap: 0;
@@ -925,123 +1001,109 @@ window.addEventListener('resize', () => {
   background: rgba(64, 158, 255, 0.15);
 }
 
-/* ==================== 积水分析 ==================== */
-.analysis-filter {
-  display: flex;
-  gap: 6px;
-  margin-bottom: 8px;
-  flex-shrink: 0;
-  flex-wrap: wrap;
+.chart-area-sm {
+  width: 100%;
+  flex: 1;
+  min-height: 130px;
 }
 
-.dark-select {
-  background: rgba(10, 25, 50, 0.8);
-  border: 1px solid rgba(64, 158, 255, 0.3);
-  color: #a0cfff;
-  padding: 4px 8px;
-  border-radius: 4px;
-  font-size: 12px;
-  outline: none;
-  cursor: pointer;
+/* ==================== 滚动条样式 ==================== */
+::-webkit-scrollbar {
+  width: 4px;
 }
 
-.dark-select option {
-  background: #0a1929;
-  color: #a0cfff;
+::-webkit-scrollbar-track {
+  background: rgba(10, 25, 50, 0.3);
 }
 
-.analysis-btn {
-  background: rgba(64, 158, 255, 0.2);
-  border: 1px solid rgba(64, 158, 255, 0.5);
-  color: #7ec8e3;
-  padding: 4px 12px;
-  border-radius: 4px;
-  font-size: 12px;
-  cursor: pointer;
-  transition: all 0.3s;
+::-webkit-scrollbar-thumb {
+  background: rgba(64, 158, 255, 0.3);
+  border-radius: 2px;
 }
 
-.analysis-btn:hover {
-  background: rgba(64, 158, 255, 0.35);
-  color: #fff;
+::-webkit-scrollbar-thumb:hover {
+  background: rgba(64, 158, 255, 0.5);
 }
 
-.analysis-result {
-  font-size: 12px;
-  color: #a0cfff;
-  line-height: 1.8;
-  margin-bottom: 8px;
-  padding: 8px;
-  background: rgba(10, 25, 50, 0.5);
-  border-radius: 4px;
-  border-left: 3px solid #409eff;
-  flex-shrink: 0;
+/* ==================== 积水深度曲线弹窗 ==================== */
+.depth-modal {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.7);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 1000;
 }
 
-.chart-area-sm {
-  width: 100%;
-  height: 130px;
-  flex-shrink: 0;
+.depth-modal-content {
+  width: 720px;
+  max-width: 90vw;
+  background: rgba(6, 30, 65, 0.95);
+  border: 1px solid rgba(64, 158, 255, 0.4);
+  border-radius: 8px;
+  padding: 16px;
+  box-shadow: 0 0 20px rgba(64, 158, 255, 0.2);
 }
 
-/* ==================== 预警信息 ==================== */
-.alert-stats {
+.depth-modal-header {
   display: flex;
-  gap: 8px;
-  margin-bottom: 8px;
-  flex-shrink: 0;
-}
-
-.alert-stat-card {
-  flex: 1;
-  background: rgba(10, 25, 50, 0.6);
-  border: 1px solid rgba(64, 158, 255, 0.15);
-  border-radius: 6px;
-  padding: 8px;
-  text-align: center;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 12px;
 }
 
-.alert-stat-val {
-  font-size: 20px;
-  font-weight: 700;
-  color: #e6a23c;
+.depth-modal-title {
+  font-size: 15px;
+  font-weight: 600;
+  color: #7ec8e3;
 }
 
-.alert-stat-val.green {
-  color: #67c23a;
+.depth-modal-close {
+  font-size: 22px;
+  color: #a0cfff;
+  cursor: pointer;
+  line-height: 1;
 }
 
-.alert-stat-val.red {
-  color: #f56c6c;
+.depth-modal-close:hover {
+  color: #fff;
 }
 
-.alert-stat-label {
-  font-size: 10px;
-  color: #7ec8e3;
-  margin-top: 2px;
+.depth-modal-tabs {
+  display: flex;
+  gap: 8px;
+  margin-bottom: 12px;
 }
 
-.alert-table-wrap {
+.depth-modal-tab {
   flex: 1;
-  overflow-y: auto;
-  min-height: 0;
-}
-
-/* ==================== 滚动条样式 ==================== */
-::-webkit-scrollbar {
-  width: 4px;
+  text-align: center;
+  padding: 6px 4px;
+  font-size: 12px;
+  color: #a0cfff;
+  background: rgba(10, 25, 50, 0.6);
+  border: 1px solid rgba(64, 158, 255, 0.2);
+  border-radius: 4px;
+  cursor: pointer;
+  transition: all 0.3s;
 }
 
-::-webkit-scrollbar-track {
-  background: rgba(10, 25, 50, 0.3);
+.depth-modal-tab.active {
+  background: rgba(64, 158, 255, 0.3);
+  color: #fff;
+  border-color: rgba(64, 158, 255, 0.6);
 }
 
-::-webkit-scrollbar-thumb {
-  background: rgba(64, 158, 255, 0.3);
-  border-radius: 2px;
+.depth-modal-tab:hover {
+  background: rgba(64, 158, 255, 0.2);
 }
 
-::-webkit-scrollbar-thumb:hover {
-  background: rgba(64, 158, 255, 0.5);
+.depth-chart-area {
+  width: 100%;
+  height: 320px;
 }
 </style>

+ 397 - 180
src/views/subSystem/drainage/jcsj/sbjcgl.vue

@@ -3,15 +3,10 @@
     <!-- 搜索区域 -->
     <el-form :model="queryParams" ref="queryRef" :inline="true" class="search-form">
       <el-form-item label="设备名称">
-        <el-input v-model="queryParams.name" placeholder="请输入" clearable style="width: 200px" />
-      </el-form-item>
-      <el-form-item label="设备类型">
-        <el-select v-model="queryParams.type" placeholder="请选择" clearable style="width: 200px">
-          <el-option v-for="item in typeOptions" :key="item.value" :label="item.label" :value="item.value" />
-        </el-select>
+        <el-input v-model="queryParams.equipmentName" placeholder="请输入" clearable style="width: 200px" />
       </el-form-item>
       <el-form-item label="设备状态">
-        <el-select v-model="queryParams.status" placeholder="请选择" clearable style="width: 200px">
+        <el-select v-model="queryParams.currentStatus" placeholder="请选择" clearable style="width: 200px">
           <el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" />
         </el-select>
       </el-form-item>
@@ -27,24 +22,34 @@
     </div>
 
     <!-- 数据表格 -->
-    <el-table :data="filteredList" border style="width: 100%" @selection-change="handleSelectionChange">
+    <el-table v-loading="loading" :data="tableData" border style="width: 100%" @selection-change="handleSelectionChange">
       <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="设备名称" prop="name" min-width="140" />
-      <el-table-column label="设备编号" prop="code" min-width="130" />
-      <el-table-column label="设备类型" prop="type" min-width="110">
+      <el-table-column label="设备名称" prop="equipmentName" min-width="140" />
+      <el-table-column label="设备编号" prop="equipmentCode" min-width="130" />
+      <el-table-column label="设备类型" min-width="90">
+        <template #default>
+          <span>排水</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="安装位置" prop="equipmentLocation" min-width="150" />
+      <el-table-column label="设备状态" prop="currentStatus" min-width="90" align="center">
+        <template #default="{ row }">
+          <el-tag :type="statusTagType(row.currentStatus)">{{ statusText(row.currentStatus) }}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="报警状态" prop="alarmStatus" min-width="90" align="center">
         <template #default="{ row }">
-          <span>{{ typeText(row.type) }}</span>
+          <el-tag :type="row.alarmStatus === 1 ? 'danger' : 'success'">{{ row.alarmStatus === 1 ? '报警' : '正常' }}</el-tag>
         </template>
       </el-table-column>
-      <el-table-column label="所属监测点" prop="monitorPoint" min-width="140" />
-      <el-table-column label="安装位置" prop="location" min-width="150" />
-      <el-table-column label="设备状态" prop="status" min-width="90" align="center">
+      <el-table-column label="在线状态" prop="onlineStatus" min-width="90" align="center">
         <template #default="{ row }">
-          <el-tag :type="statusTagType(row.status)">{{ statusText(row.status) }}</el-tag>
+          <el-tag :type="row.onlineStatus === 1 ? 'success' : 'info'">{{ row.onlineStatus === 1 ? '在线' : '离线' }}</el-tag>
         </template>
       </el-table-column>
       <el-table-column label="安装时间" prop="installTime" min-width="120" />
-      <el-table-column label="最近维护时间" prop="lastMaintenanceTime" min-width="130" />
+      <el-table-column label="最近维护时间" prop="lastMaintainDate" min-width="130" />
+      <el-table-column label="下次维护日期" prop="nextMaintainDate" min-width="130" />
       <el-table-column label="操作" width="180" align="center" fixed="right">
         <template #default="{ row }">
           <el-button link type="primary" @click="handleView(row)">查看</el-button>
@@ -54,6 +59,17 @@
       </el-table-column>
     </el-table>
 
+    <!-- 分页 -->
+    <el-pagination
+      v-model:current-page="queryParams.pageNum"
+      v-model:page-size="queryParams.pageSize"
+      :page-sizes="[10, 20, 50, 100]"
+      :total="total"
+      layout="total, sizes, prev, pager, next, jumper"
+      @size-change="handleQuery"
+      @current-change="handlePageChange"
+    />
+
     <!-- 查看详情对话框 -->
     <el-dialog v-model="dialogVisible" title="设备详情" width="700px" :before-close="handleClose">
       <div class="detail-info">
@@ -61,13 +77,13 @@
           <el-col :span="12">
             <div class="info-item">
               <span class="info-label">设备名称:</span>
-              <span class="info-value">{{ currentRow.name }}</span>
+              <span class="info-value">{{ currentRow.equipmentName }}</span>
             </div>
           </el-col>
           <el-col :span="12">
             <div class="info-item">
               <span class="info-label">设备编号:</span>
-              <span class="info-value">{{ currentRow.code }}</span>
+              <span class="info-value">{{ currentRow.equipmentCode }}</span>
             </div>
           </el-col>
         </el-row>
@@ -75,13 +91,13 @@
           <el-col :span="12">
             <div class="info-item">
               <span class="info-label">设备类型:</span>
-              <span class="info-value">{{ typeText(currentRow.type) }}</span>
+              <span class="info-value">排水</span>
             </div>
           </el-col>
           <el-col :span="12">
             <div class="info-item">
-              <span class="info-label">所属监测点:</span>
-              <span class="info-value">{{ currentRow.monitorPoint }}</span>
+              <span class="info-label">权属单位:</span>
+              <span class="info-value">{{ currentRow.ownershipUnit || '-' }}</span>
             </div>
           </el-col>
         </el-row>
@@ -89,14 +105,14 @@
           <el-col :span="12">
             <div class="info-item">
               <span class="info-label">安装位置:</span>
-              <span class="info-value">{{ currentRow.location }}</span>
+              <span class="info-value">{{ currentRow.equipmentLocation }}</span>
             </div>
           </el-col>
           <el-col :span="12">
             <div class="info-item">
               <span class="info-label">设备状态:</span>
               <span class="info-value">
-                <el-tag :type="statusTagType(currentRow.status)">{{ statusText(currentRow.status) }}</el-tag>
+                <el-tag :type="statusTagType(currentRow.currentStatus)">{{ statusText(currentRow.currentStatus) }}</el-tag>
               </span>
             </div>
           </el-col>
@@ -111,7 +127,7 @@
           <el-col :span="12">
             <div class="info-item">
               <span class="info-label">最近维护时间:</span>
-              <span class="info-value">{{ currentRow.lastMaintenanceTime }}</span>
+              <span class="info-value">{{ currentRow.lastMaintainDate }}</span>
             </div>
           </el-col>
         </el-row>
@@ -127,38 +143,103 @@
     </el-dialog>
 
     <!-- 新增/修改对话框 -->
-    <el-dialog v-model="editDialogVisible" :title="editDialogTitle" width="700px" :before-close="handleEditClose">
-      <el-form ref="editFormRef" :model="editForm" :rules="editRules" label-width="110px">
+    <el-dialog v-model="editDialogVisible" :title="editDialogTitle" width="800px" :before-close="handleEditClose">
+      <el-form ref="editFormRef" :model="editForm" :rules="editRules" label-width="120px">
         <el-row :gutter="20">
           <el-col :span="12">
-            <el-form-item label="设备名称" prop="name">
-              <el-input v-model="editForm.name" placeholder="请输入设备名称" />
+            <el-form-item label="设备名称" prop="equipmentName">
+              <el-input v-model="editForm.equipmentName" placeholder="请输入设备名称" />
             </el-form-item>
           </el-col>
           <el-col :span="12">
-            <el-form-item label="设备编号" prop="code">
-              <el-input v-model="editForm.code" placeholder="请输入设备编号" />
+            <el-form-item label="设备编号" prop="equipmentCode">
+              <el-input v-model="editForm.equipmentCode" placeholder="请输入设备编号" />
             </el-form-item>
           </el-col>
         </el-row>
         <el-row :gutter="20">
           <el-col :span="12">
-            <el-form-item label="设备类型" prop="type">
-              <el-select v-model="editForm.type" placeholder="请选择设备类型" style="width: 100%">
-                <el-option v-for="item in typeOptions" :key="item.value" :label="item.label" :value="item.value" />
-              </el-select>
+            <el-form-item label="设备型号" prop="equipmentModel">
+              <el-input v-model="editForm.equipmentModel" placeholder="请输入设备型号" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="设备类型">
+              <el-input value="排水" disabled />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="设备制造商" prop="manufacturer">
+              <el-input v-model="editForm.manufacturer" placeholder="请输入设备制造商" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="生产日期" prop="productionDate">
+              <el-date-picker v-model="editForm.productionDate" type="date" placeholder="请选择生产日期" value-format="YYYY-MM-DD" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="设备规格参数" prop="equipmentSpec">
+              <el-input v-model="editForm.equipmentSpec" type="textarea" :rows="2" placeholder="请输入设备规格参数" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="资产价值(元)" prop="assetValue">
+              <el-input-number v-model="editForm.assetValue" :precision="2" :min="0" placeholder="请输入" style="width: 100%" />
             </el-form-item>
           </el-col>
           <el-col :span="12">
-            <el-form-item label="所属监测点" prop="monitorPoint">
-              <el-input v-model="editForm.monitorPoint" placeholder="请输入所属监测点" />
+            <el-form-item label="使用年限(年)" prop="useLife">
+              <el-input-number v-model="editForm.useLife" :min="0" :precision="0" placeholder="请输入" style="width: 100%" />
             </el-form-item>
           </el-col>
         </el-row>
         <el-row :gutter="20">
           <el-col :span="12">
-            <el-form-item label="安装位置" prop="location">
-              <el-input v-model="editForm.location" placeholder="请输入安装位置" />
+            <el-form-item label="经度" prop="longitude">
+              <el-input-number v-model="editForm.longitude" :precision="8" :min="-180" :max="180" :controls="false" placeholder="请输入经度" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="纬度" prop="latitude">
+              <el-input-number v-model="editForm.latitude" :precision="8" :min="-90" :max="90" :controls="false" placeholder="请输入纬度" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="安装位置" prop="equipmentLocation">
+              <el-input v-model="editForm.equipmentLocation" placeholder="请输入安装位置" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="安装时间" prop="installTime">
+              <el-date-picker v-model="editForm.installTime" type="date" placeholder="请选择安装时间" value-format="YYYY-MM-DD" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="维护人员" prop="maintainer">
+              <el-input v-model="editForm.maintainer" placeholder="请输入维护人员" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="联系方式" prop="maintainerPhone">
+              <el-input v-model="editForm.maintainerPhone" placeholder="请输入联系方式" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="权属单位" prop="ownershipUnit">
+              <el-input v-model="editForm.ownershipUnit" placeholder="请输入权属单位" />
             </el-form-item>
           </el-col>
           <el-col :span="12">
@@ -168,18 +249,34 @@
               </el-select>
             </el-form-item>
           </el-col>
+          <el-col :span="12">
+            <el-form-item label="报警状态" prop="alarmStatus">
+              <el-select v-model="editForm.alarmStatus" placeholder="请选择报警状态" style="width: 100%">
+                <el-option label="正常" :value="0" />
+                <el-option label="报警" :value="1" />
+              </el-select>
+            </el-form-item>
+          </el-col>
         </el-row>
         <el-row :gutter="20">
           <el-col :span="12">
-            <el-form-item label="安装时间" prop="installTime">
-              <el-date-picker v-model="editForm.installTime" type="date" placeholder="请选择安装时间" value-format="YYYY-MM-DD"
-                style="width: 100%" />
+            <el-form-item label="在线状态" prop="onlineStatus">
+              <el-select v-model="editForm.onlineStatus" placeholder="请选择在线状态" style="width: 100%">
+                <el-option label="离线" :value="0" />
+                <el-option label="在线" :value="1" />
+              </el-select>
             </el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item label="最近维护时间" prop="lastMaintenanceTime">
-              <el-date-picker v-model="editForm.lastMaintenanceTime" type="date" placeholder="请选择维护时间" value-format="YYYY-MM-DD"
-                style="width: 100%" />
+              <el-date-picker v-model="editForm.lastMaintenanceTime" type="date" placeholder="请选择维护时间" value-format="YYYY-MM-DD" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="下次维护日期" prop="nextMaintenanceTime">
+              <el-date-picker v-model="editForm.nextMaintenanceTime" type="date" placeholder="请选择下次维护日期" value-format="YYYY-MM-DD" style="width: 100%" />
             </el-form-item>
           </el-col>
         </el-row>
@@ -200,127 +297,130 @@
 </template>
 
 <script setup name="Sbjcgl">
-import { ref, computed } from 'vue'
+import { ref, computed, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
+import {
+  getEquipmentPage,
+  getEquipmentById,
+  addEquipment,
+  updateEquipment,
+  deleteEquipment,
+  batchDeleteEquipment,
+  getEquipmentTypeList,
+  addEquipmentStatus,
+  updateEquipmentStatus
+} from '@/api/drainage'
 
 // ==================== 选项配置 ====================
-const typeOptions = [
-  { label: '液位计', value: '1' },
-  { label: '流量计', value: '2' },
-  { label: '水质监测仪', value: '3' },
-  { label: '雨量计', value: '4' }
-]
+const typeList = ref([])
+const typeOptions = computed(() => {
+  return typeList.value.map(item => ({ label: item.typeName, value: item.typeId }))
+})
 
 const statusOptions = [
-  { label: '在线', value: '1' },
-  { label: '离线', value: '2' },
-  { label: '故障', value: '3' },
-  { label: '维修中', value: '4' }
+  { label: '在用', value: 1 },
+  { label: '闲置', value: 2 },
+  { label: '维修', value: 3 },
+  { label: '报废', value: 4 },
+  { label: '待入库', value: 5 }
 ]
 
 function typeText(val) {
-  const map = { '1': '液位计', '2': '流量计', '3': '水质监测仪', '4': '雨量计' }
-  return map[val] || '-'
+  const item = typeList.value.find(t => t.typeId === val)
+  return item ? item.typeName : '-'
 }
 
 function statusText(val) {
-  const map = { '1': '在线', '2': '离线', '3': '故障', '4': '维修中' }
-  return map[val] || '-'
+  const item = statusOptions.find(s => s.value === val)
+  return item ? item.label : '-'
 }
 
 function statusTagType(val) {
-  const map = { '1': 'success', '2': 'info', '3': 'danger', '4': 'warning' }
+  const map = { 1: 'success', 2: 'info', 3: 'warning', 4: 'danger', 5: '' }
   return map[val] || 'info'
 }
 
 // ==================== 搜索参数 ====================
 const queryParams = ref({
-  name: '',
-  type: '',
-  status: ''
+  equipmentName: '',
+  equipmentTypeId: 'drainage',
+  currentStatus: '',
+  pageNum: 1,
+  pageSize: 10
 })
 
 // ==================== 表格数据 ====================
-let nextId = 8
-const tableData = ref([
-  {
-    id: 1, name: '城北液位监测仪', code: 'YWL-2025-001', type: '1',
-    monitorPoint: '城北排水监测站', location: '城北路段排水井A3号',
-    status: '1', installTime: '2024-03-15', lastMaintenanceTime: '2025-10-20',
-    remark: '主要监测城北路段地下排水管网液位'
-  },
-  {
-    id: 2, name: '河西流量计', code: 'LXJ-2025-002', type: '2',
-    monitorPoint: '河西排水监测站', location: '河西大道排水管道B7号',
-    status: '2', installTime: '2024-05-20', lastMaintenanceTime: '2025-08-12',
-    remark: '监测河西大道主干排水流量'
-  },
-  {
-    id: 3, name: '南区水质监测仪', code: 'SZJ-2025-003', type: '3',
-    monitorPoint: '南区水质监测站', location: '南环路污水处理厂入口',
-    status: '3', installTime: '2023-11-08', lastMaintenanceTime: '2025-11-05',
-    remark: '监测南区排水口水质指标'
-  },
-  {
-    id: 4, name: '东区雨量计', code: 'YLJ-2025-004', type: '4',
-    monitorPoint: '东区气象监测站', location: '东环路市政大楼楼顶',
-    status: '1', installTime: '2024-07-10', lastMaintenanceTime: '2025-09-18',
-    remark: '实时监测东区降雨量数据'
-  },
-  {
-    id: 5, name: '中心区液位监测仪', code: 'YWL-2025-005', type: '1',
-    monitorPoint: '中心区排水监测站', location: '人民路地下管网C2号',
-    status: '4', installTime: '2023-06-25', lastMaintenanceTime: '2025-12-01',
-    remark: '中心区核心排水节点液位监测,正在维修传感器'
-  },
-  {
-    id: 6, name: '北区流量计', code: 'LXJ-2025-006', type: '2',
-    monitorPoint: '北区排水监测站', location: '北环路排水泵站出口',
-    status: '1', installTime: '2024-09-12', lastMaintenanceTime: '2025-11-22',
-    remark: '监测北区泵站出口排水流量'
-  },
-  {
-    id: 7, name: '西区水质监测仪', code: 'SZJ-2025-007', type: '3',
-    monitorPoint: '西区水质监测站', location: '西环路河道排放口',
-    status: '2', installTime: '2024-01-18', lastMaintenanceTime: '2025-07-30',
-    remark: '监测西区河道排水口水质,设备离线待检修'
-  },
-  {
-    id: 8, name: '开发区雨量计', code: 'YLJ-2025-008', type: '4',
-    monitorPoint: '开发区气象监测站', location: '开发区管委会大楼楼顶',
-    status: '1', installTime: '2024-11-05', lastMaintenanceTime: '2025-12-10',
-    remark: '开发区降雨量实时监测设备'
-  }
-])
+const tableData = ref([])
+const total = ref(0)
+const loading = ref(false)
 
 // 多选
 const selectedIds = ref([])
 function handleSelectionChange(selection) {
-  selectedIds.value = selection.map(item => item.id)
+  selectedIds.value = selection.map(item => item.equipmentId)
 }
 
-// 过滤后的列表
-const filteredList = computed(() => {
-  return tableData.value.filter(item => {
-    const nameMatch = !queryParams.value.name || item.name.includes(queryParams.value.name)
-    const typeMatch = !queryParams.value.type || item.type === queryParams.value.type
-    const statusMatch = !queryParams.value.status || item.status === queryParams.value.status
-    return nameMatch && typeMatch && statusMatch
-  })
-})
+// 加载分页数据
+async function loadData() {
+  loading.value = true
+  try {
+    const { pageNum, pageSize, ...rest } = queryParams.value
+    const res = await getEquipmentPage(pageNum, pageSize, rest)
+    const pageData = res.code !== undefined ? res.data : res
+    tableData.value = pageData.records || []
+    total.value = pageData.total || 0
+  } catch (error) {
+    console.error('加载设备列表失败', error)
+    ElMessage.error('加载设备列表失败')
+  } finally {
+    loading.value = false
+  }
+}
 
 // 搜索
 function handleQuery() {
-  queryParams.value = { ...queryParams.value }
+  queryParams.value.pageNum = 1
+  loadData()
+}
+
+function handlePageChange() {
+  loadData()
+}
+
+onMounted(() => {
+  loadTypeList()
+  loadData()
+})
+
+async function loadTypeList() {
+  try {
+    const res = await getEquipmentTypeList()
+    typeList.value = (res.code !== undefined ? res.data : res) || []
+  } catch (error) {
+    console.error('加载设备类型失败', error)
+    ElMessage.error('加载设备类型失败')
+  }
 }
 
 // ==================== 查看详情对话框 ====================
 const dialogVisible = ref(false)
 const currentRow = ref({})
 
-function handleView(row) {
-  currentRow.value = { ...row }
-  dialogVisible.value = true
+async function handleView(row) {
+  try {
+    const res = await getEquipmentById(row.equipmentId)
+    const detail = res.data || {}
+    currentRow.value = {
+      ...row,
+      ...detail.equipmentInfo,
+      ...detail.statusInfo,
+      typeName: detail.typeInfo ? detail.typeInfo.typeName : ''
+    }
+    dialogVisible.value = true
+  } catch (error) {
+    console.error('获取设备详情失败', error)
+    ElMessage.error('获取设备详情失败')
+  }
 }
 
 function handleClose() {
@@ -335,23 +435,36 @@ const isEditMode = ref(false)
 const editRowId = ref(null)
 
 const editForm = ref({
-  name: '',
-  code: '',
-  type: '',
-  monitorPoint: '',
-  location: '',
-  status: '',
+  equipmentName: '',
+  equipmentCode: '',
+  equipmentModel: '',
+  equipmentSpec: '',
+  manufacturer: '',
+  productionDate: '',
+  equipmentTypeId: '',
+  assetValue: null,
+  useLife: null,
+  longitude: null,
+  latitude: null,
+  equipmentLocation: '',
   installTime: '',
+  maintainer: '',
+  maintainerPhone: '',
+  ownershipUnit: '',
+  remark: '',
+  // 状态表字段
+  status: '',
+  alarmStatus: null,
+  onlineStatus: null,
   lastMaintenanceTime: '',
-  remark: ''
+  nextMaintenanceTime: ''
 })
 
 const editRules = {
-  name: [{ required: true, message: '请输入设备名称', trigger: 'blur' }],
-  code: [{ required: true, message: '请输入设备编号', trigger: 'blur' }],
-  type: [{ required: true, message: '请选择设备类型', trigger: 'change' }],
-  monitorPoint: [{ required: true, message: '请输入所属监测点', trigger: 'blur' }],
-  location: [{ required: true, message: '请输入安装位置', trigger: 'blur' }],
+  equipmentName: [{ required: true, message: '请输入设备名称', trigger: 'blur' }],
+  equipmentCode: [{ required: true, message: '请输入设备编号', trigger: 'blur' }],
+  equipmentTypeId: [{ required: true, message: '请选择设备类型', trigger: 'change' }],
+  equipmentLocation: [{ required: true, message: '请输入安装位置', trigger: 'blur' }],
   status: [{ required: true, message: '请选择设备状态', trigger: 'change' }]
 }
 
@@ -360,15 +473,28 @@ function handleAdd() {
   editDialogTitle.value = '新增设备'
   editRowId.value = null
   editForm.value = {
-    name: '',
-    code: '',
-    type: '',
-    monitorPoint: '',
-    location: '',
-    status: '',
+    equipmentName: '',
+    equipmentCode: '',
+    equipmentModel: '',
+    equipmentSpec: '',
+    manufacturer: '',
+    productionDate: '',
+    equipmentTypeId: '',
+    assetValue: null,
+    useLife: null,
+    longitude: null,
+    latitude: null,
+    equipmentLocation: '',
     installTime: '',
+    maintainer: '',
+    maintainerPhone: '',
+    ownershipUnit: '',
+    remark: '',
+    status: '',
+    alarmStatus: null,
+    onlineStatus: null,
     lastMaintenanceTime: '',
-    remark: ''
+    nextMaintenanceTime: ''
   }
   editDialogVisible.value = true
 }
@@ -376,17 +502,30 @@ function handleAdd() {
 function handleEdit(row) {
   isEditMode.value = true
   editDialogTitle.value = '修改设备'
-  editRowId.value = row.id
+  editRowId.value = row.equipmentId
   editForm.value = {
-    name: row.name,
-    code: row.code,
-    type: row.type,
-    monitorPoint: row.monitorPoint,
-    location: row.location,
-    status: row.status,
-    installTime: row.installTime,
-    lastMaintenanceTime: row.lastMaintenanceTime,
-    remark: row.remark || ''
+    equipmentName: row.equipmentName || '',
+    equipmentCode: row.equipmentCode || '',
+    equipmentModel: row.equipmentModel || '',
+    equipmentSpec: row.equipmentSpec || '',
+    manufacturer: row.manufacturer || '',
+    productionDate: row.productionDate || '',
+    equipmentTypeId: row.equipmentTypeId || '',
+    assetValue: row.assetValue || null,
+    useLife: row.useLife || null,
+    longitude: row.longitude || null,
+    latitude: row.latitude || null,
+    equipmentLocation: row.equipmentLocation || '',
+    installTime: row.installTime ? row.installTime.split(' ')[0] : '',
+    maintainer: row.maintainer || '',
+    maintainerPhone: row.maintainerPhone || '',
+    ownershipUnit: row.ownershipUnit || '',
+    remark: row.remark || '',
+    status: row.currentStatus || '',
+    alarmStatus: row.alarmStatus != null ? row.alarmStatus : null,
+    onlineStatus: row.onlineStatus != null ? row.onlineStatus : null,
+    lastMaintenanceTime: row.lastMaintainDate || '',
+    nextMaintenanceTime: row.nextMaintainDate || ''
   }
   editDialogVisible.value = true
 }
@@ -396,39 +535,108 @@ function handleEditClose() {
 }
 
 function handleEditSubmit() {
-  editFormRef.value.validate(valid => {
+  editFormRef.value.validate(async valid => {
     if (!valid) return
 
-    const formData = { ...editForm.value }
+    const formData = {
+      equipmentName: editForm.value.equipmentName,
+      equipmentCode: editForm.value.equipmentCode,
+      equipmentModel: editForm.value.equipmentModel,
+      equipmentSpec: editForm.value.equipmentSpec,
+      manufacturer: editForm.value.manufacturer,
+      productionDate: editForm.value.productionDate || null,
+      equipmentTypeId: 'drainage',
+      assetValue: editForm.value.assetValue,
+      useLife: editForm.value.useLife,
+      longitude: editForm.value.longitude,
+      latitude: editForm.value.latitude,
+      equipmentLocation: editForm.value.equipmentLocation,
+      installTime: editForm.value.installTime ? editForm.value.installTime + ' 00:00:00' : null,
+      maintainer: editForm.value.maintainer,
+      maintainerPhone: editForm.value.maintainerPhone,
+      ownershipUnit: editForm.value.ownershipUnit,
+      remark: editForm.value.remark
+    }
 
-    if (isEditMode.value) {
-      const index = tableData.value.findIndex(item => item.id === editRowId.value)
-      if (index !== -1) {
-        tableData.value[index] = { ...tableData.value[index], ...formData }
+    try {
+      if (isEditMode.value) {
+        formData.equipmentId = editRowId.value
+        const res = await updateEquipment(formData)
+        if (res.code === 0 || res.code === 200) {
+          // 更新设备状态
+          if (editForm.value.status) {
+            try {
+              await updateEquipmentStatus({
+                equipmentId: editRowId.value,
+                currentStatus: editForm.value.status,
+                alarmStatus: editForm.value.alarmStatus,
+                onlineStatus: editForm.value.onlineStatus,
+                installationAddr: editForm.value.equipmentLocation,
+                lastMaintainDate: editForm.value.lastMaintenanceTime || null,
+                nextMaintainDate: editForm.value.nextMaintenanceTime || null
+              })
+            } catch (e) {
+              console.warn('更新设备状态失败', e)
+            }
+          }
+          ElMessage.success('修改成功')
+        } else {
+          ElMessage.error(res.msg || '修改失败')
+          return
+        }
+      } else {
+        const res = await addEquipment(formData)
+        if (res.code === 0 || res.code === 200) {
+          // 新增成功后,创建设备状态记录
+          const newEquipmentId = res.data ? (res.data.equipmentId || null) : null
+          if (newEquipmentId && editForm.value.status) {
+            try {
+              await addEquipmentStatus({
+                equipmentId: newEquipmentId,
+                currentStatus: editForm.value.status,
+                alarmStatus: editForm.value.alarmStatus,
+                onlineStatus: editForm.value.onlineStatus,
+                installationAddr: editForm.value.equipmentLocation,
+                lastMaintainDate: editForm.value.lastMaintenanceTime || null,
+                nextMaintainDate: editForm.value.nextMaintenanceTime || null
+              })
+            } catch (e) {
+              console.warn('创建设备状态失败', e)
+            }
+          }
+          ElMessage.success('新增成功')
+        } else {
+          ElMessage.error(res.msg || '新增失败')
+          return
+        }
       }
-      ElMessage.success('修改成功')
-    } else {
-      formData.id = nextId++
-      tableData.value.push(formData)
-      ElMessage.success('新增成功')
+      editDialogVisible.value = false
+      loadData()
+    } catch (error) {
+      console.error('提交失败', error)
+      ElMessage.error('操作失败')
     }
-
-    editDialogVisible.value = false
   })
 }
 
 // ==================== 删除功能 ====================
 function handleDelete(row) {
-  ElMessageBox.confirm(`确认删除设备"${row.name}"吗?`, '提示', {
+  ElMessageBox.confirm(`确认删除设备"${row.equipmentName}"吗?`, '提示', {
     confirmButtonText: '确定',
     cancelButtonText: '取消',
     type: 'warning'
-  }).then(() => {
-    const index = tableData.value.findIndex(item => item.id === row.id)
-    if (index !== -1) {
-      tableData.value.splice(index, 1)
+  }).then(async () => {
+    try {
+      const res = await deleteEquipment(row.equipmentId)
+      if (res.code === 0 || res.code === 200) {
+        ElMessage.success('删除成功')
+        loadData()
+      } else {
+        ElMessage.error(res.msg || '删除失败')
+      }
+    } catch (error) {
+      ElMessage.error('删除失败')
     }
-    ElMessage.success('删除成功')
   }).catch(() => { })
 }
 
@@ -438,10 +646,19 @@ function handleBatchDelete() {
     confirmButtonText: '确定',
     cancelButtonText: '取消',
     type: 'warning'
-  }).then(() => {
-    tableData.value = tableData.value.filter(item => !selectedIds.value.includes(item.id))
-    selectedIds.value = []
-    ElMessage.success('删除成功')
+  }).then(async () => {
+    try {
+      const res = await batchDeleteEquipment(selectedIds.value)
+      if (res.code === 0 || res.code === 200) {
+        selectedIds.value = []
+        ElMessage.success('删除成功')
+        loadData()
+      } else {
+        ElMessage.error(res.msg || '删除失败')
+      }
+    } catch (error) {
+      ElMessage.error('删除失败')
+    }
   }).catch(() => { })
 }
 </script>

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 266 - 534
src/views/subSystem/drainage/jcsj/sbyx.vue


+ 104 - 28
src/views/subSystem/drainage/jcsj/yjsdgl.vue

@@ -34,6 +34,14 @@
       <el-table-column label="行政区域" prop="area" min-width="100" />
       <el-table-column label="数据时间" prop="time" min-width="120" />
       <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 }">
+          <el-tag :type="getWaterloggingLevelType(row.waterloggingLevel)" size="small">
+            {{ getWaterloggingLevelLabel(row.waterloggingLevel) }}
+          </el-tag>
+        </template>
+      </el-table-column>
       <el-table-column label="操作" width="180" align="center">
         <template #default="{ row }">
           <el-button link type="primary" @click="handleView(row)">查看</el-button>
@@ -82,7 +90,25 @@
           <el-col :span="12">
             <div class="info-item">
               <span class="info-label">易积水点数量:</span>
-              <span class="info-value">{{ (currentRow.waterloggingPoints || []).length }}</span>
+              <span class="info-value">{{ currentRow.waterloggingPoints.length }}</span>
+            </div>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <div class="info-item">
+              <span class="info-label">积水量:</span>
+              <span class="info-value">{{ currentRow.waterVolume }}</span>
+            </div>
+          </el-col>
+          <el-col :span="12">
+            <div class="info-item">
+              <span class="info-label">积水程度:</span>
+              <span class="info-value">
+                <el-tag :type="getWaterloggingLevelType(currentRow.waterloggingLevel)" size="small">
+                  {{ getWaterloggingLevelLabel(currentRow.waterloggingLevel) }}
+                </el-tag>
+              </span>
             </div>
           </el-col>
         </el-row>
@@ -123,6 +149,23 @@
             </el-form-item>
           </el-col>
         </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="积水量" prop="waterVolume">
+              <el-input v-model="editForm.waterVolume" type="number" placeholder="请输入积水量" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="积水程度" prop="waterloggingLevel">
+              <el-select v-model="editForm.waterloggingLevel" placeholder="请选择积水程度" style="width: 100%">
+                <el-option label="未积水" :value="0" />
+                <el-option label="轻度积水" :value="1" />
+                <el-option label="中度积水" :value="2" />
+                <el-option label="严重积水" :value="3" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
       </el-form>
 
       <!-- 地图标点区域 -->
@@ -175,6 +218,40 @@ function createMapMarker(map, bPoint, name) {
   return label
 }
 
+// 积水程度选项
+const waterloggingLevelOptions = [
+  { value: 0, label: '未积水', type: 'success' },
+  { value: 1, label: '轻度积水', type: 'info' },
+  { value: 2, label: '中度积水', type: 'warning' },
+  { value: 3, label: '严重积水', type: 'danger' }
+]
+
+function getWaterloggingLevelLabel(level) {
+  const option = waterloggingLevelOptions.find(item => item.value === level)
+  return option ? option.label : '未知'
+}
+
+function getWaterloggingLevelType(level) {
+  const option = waterloggingLevelOptions.find(item => item.value === level)
+  return option ? option.type : 'info'
+}
+
+// 安全解析 waterloggingPoints(后端返回 JSON 字符串,需转为数组使用)
+function parseWaterloggingPoints(raw) {
+  if (!raw) return []
+  if (Array.isArray(raw)) return raw
+  if (typeof raw === 'string') {
+    try {
+      const parsed = JSON.parse(raw)
+      return Array.isArray(parsed) ? parsed : []
+    } catch (e) {
+      console.warn('waterloggingPoints JSON 解析失败:', raw, e)
+      return []
+    }
+  }
+  return []
+}
+
 // ==================== 搜索参数 ====================
 const queryParams = ref({
   name: '',
@@ -233,27 +310,14 @@ function renderMainMapMarkers() {
 
   const allPoints = []
   tableData.value.forEach(row => {
-    const points = row.waterloggingPoints || []
-    if (typeof points === 'string') {
-      try {
-        const parsed = JSON.parse(points)
-        parsed.forEach(p => {
-          if (p.lng && p.lat) {
-            const bPoint = new BMapGL.Point(p.lng, p.lat)
-            createMapMarker(mainMapInstance, bPoint, p.name || row.name)
-            allPoints.push(bPoint)
-          }
-        })
-      } catch (e) {}
-    } else if (Array.isArray(points)) {
-      points.forEach(p => {
-        if (p.lng && p.lat) {
-          const bPoint = new BMapGL.Point(p.lng, p.lat)
-          createMapMarker(mainMapInstance, bPoint, p.name || row.name)
-          allPoints.push(bPoint)
-        }
-      })
-    }
+    const points = parseWaterloggingPoints(row.waterloggingPoints)
+    points.forEach(p => {
+      if (p.lng && p.lat) {
+        const bPoint = new BMapGL.Point(p.lng, p.lat)
+        createMapMarker(mainMapInstance, bPoint, p.name || row.name)
+        allPoints.push(bPoint)
+      }
+    })
   })
 
   if (allPoints.length > 0) {
@@ -282,7 +346,11 @@ const currentRow = ref({})
 let viewMapInstance = null
 
 function handleView(row) {
-  currentRow.value = { ...row }
+  currentRow.value = {
+    ...row,
+    // 将 JSON 字符串解析为数组,供模板和地图渲染使用
+    waterloggingPoints: parseWaterloggingPoints(row.waterloggingPoints)
+  }
   dialogVisible.value = true
 }
 
@@ -341,7 +409,9 @@ const editRowId = ref(null)
 const editForm = ref({
   name: '',
   area: '',
-  time: ''
+  time: '',
+  waterVolume: null,
+  waterloggingLevel: 0
 })
 
 const editRules = {
@@ -362,7 +432,9 @@ function handleAdd() {
   editForm.value = {
     name: '',
     area: '',
-    time: ''
+    time: '',
+    waterVolume: null,
+    waterloggingLevel: 0
   }
   editWaterloggingPoints.value = []
   editWaterloggingMarkerCount.value = 0
@@ -376,10 +448,12 @@ function handleEdit(row) {
   editForm.value = {
     name: row.name,
     area: row.area,
-    time: row.time
+    time: row.time,
+    waterVolume: row.waterVolume,
+    waterloggingLevel: row.waterloggingLevel ?? 0
   }
-  // 深拷贝标记点数据
-  editWaterloggingPoints.value = (row.waterloggingPoints || []).map(p => ({ ...p }))
+  // 深拷贝标记点数据(waterloggingPoints 从后端返回为 JSON 字符串,需先解析为数组)
+  editWaterloggingPoints.value = parseWaterloggingPoints(row.waterloggingPoints).map(p => ({ ...p }))
   editWaterloggingMarkerCount.value = editWaterloggingPoints.value.length
   editDialogVisible.value = true
 }
@@ -480,6 +554,8 @@ function handleEditSubmit() {
       name: editForm.value.name,
       area: editForm.value.area,
       time: editForm.value.time,
+      waterVolume: editForm.value.waterVolume,
+      waterloggingLevel: editForm.value.waterloggingLevel,
       waterloggingCount: editWaterloggingPoints.value.length,
       waterloggingPoints: JSON.stringify(editWaterloggingPoints.value.map(p => ({ name: p.name, lng: p.lng, lat: p.lat })))
     }

+ 60 - 16
src/views/subSystem/drainage/jcyj/dqyjGIS.vue

@@ -124,26 +124,63 @@
 <script setup name="DqyjGIS">
 import { ref, reactive, computed, onMounted, nextTick } from 'vue'
 import { Search } from '@element-plus/icons-vue'
+import { ElMessage } from 'element-plus'
 import locationIcon from '@/assets/images/location.png'
+import { getWarningCurrentPage } from '@/api/drainage'
+import useUserStore from '@/store/modules/user'
+
+// ==================== 用户信息 ====================
+const userStore = useUserStore()
+const isAdmin = computed(() => userStore.roles && userStore.roles.includes('admin'))
 
 // ==================== 等级筛选 ====================
 const levelChecked = reactive({ severe: true, important: true, normal: true })
 
-// ==================== 模拟预警数据(12个点位)====================
-const warningList = ref([
-  { id: 1, name: '沅陵大桥液位计', level: '严重', time: '2026-06-12 08:23:15', type: '液位超限', value: '3.82m', threshold: '2.50m', location: '沅陵大桥南侧排水口', lng: 110.396, lat: 28.468 },
-  { id: 2, name: '太常路流量计', level: '重要', time: '2026-06-12 07:45:30', type: '流量异常', value: '1250m³/h', threshold: '800m³/h', location: '太常路与沅江路交汇', lng: 110.382, lat: 28.459 },
-  { id: 3, name: '沅江路液位计', level: '一般', time: '2026-06-12 09:10:05', type: '液位超限', value: '1.85m', threshold: '1.50m', location: '沅江路中段排水井', lng: 110.403, lat: 28.447 },
-  { id: 4, name: '龙舟路雨量计', level: '严重', time: '2026-06-12 06:30:22', type: '降雨预警', value: '85mm/h', threshold: '50mm/h', location: '龙舟路与建设路交汇', lng: 110.388, lat: 28.436 },
-  { id: 5, name: '城东泵站液位计', level: '重要', time: '2026-06-12 08:55:18', type: '液位超限', value: '4.12m', threshold: '3.00m', location: '城东开发区泵站', lng: 110.412, lat: 28.461 },
-  { id: 6, name: '一号泵站流量计', level: '一般', time: '2026-06-12 09:22:40', type: '流量异常', value: '680m³/h', threshold: '500m³/h', location: '沅陵大桥西侧泵站', lng: 110.391, lat: 28.465 },
-  { id: 7, name: '二号泵站液位计', level: '严重', time: '2026-06-12 05:18:33', type: '液位超限', value: '5.20m', threshold: '3.00m', location: '太常片区排水泵站', lng: 110.378, lat: 28.451 },
-  { id: 8, name: '沅水南岸流量计', level: '重要', time: '2026-06-12 07:32:10', type: '流量异常', value: '920m³/h', threshold: '600m³/h', location: '沅水南岸排水口', lng: 110.368, lat: 28.448 },
-  { id: 9, name: '建设路液位计', level: '一般', time: '2026-06-12 09:40:55', type: '液位超限', value: '2.10m', threshold: '1.80m', location: '建设路下穿通道', lng: 110.393, lat: 28.441 },
-  { id: 10, name: '城东下穿液位计', level: '严重', time: '2026-06-12 06:05:28', type: '液位超限', value: '3.95m', threshold: '2.00m', location: '城东立交桥下', lng: 110.415, lat: 28.457 },
-  { id: 11, name: '主城区管网流量计', level: '重要', time: '2026-06-12 08:12:45', type: '流量异常', value: '1100m³/h', threshold: '700m³/h', location: '主城区排水干管节点', lng: 110.390, lat: 28.460 },
-  { id: 12, name: '沅江路东段液位计', level: '一般', time: '2026-06-12 09:55:12', type: '液位超限', value: '1.62m', threshold: '1.50m', location: '沅江路东段检测井', lng: 110.407, lat: 28.455 }
-])
+// ==================== 预警数据(从API加载)====================
+const warningList = ref([])
+
+// 预警级别数字→文字映射
+const levelMap = { 1: '严重', 2: '重要', 3: '一般' }
+
+// 加载当前预警数据
+async function loadData() {
+  try {
+    const params = {
+      userId: userStore.id,
+      isAdmin: isAdmin.value
+    }
+    if (userStore.deptId) {
+      params.deptId = userStore.deptId
+    }
+    const res = await getWarningCurrentPage(1, 1000, params)
+    const pageData = res.code !== undefined ? res.data : res
+    const records = pageData.records || []
+    const hasCoordCount = records.filter(w => w.longitude && w.latitude).length
+    console.log('[GIS预警] API返回记录数:', records.length, '有坐标记录数:', hasCoordCount)
+    if (records.length > 0 && hasCoordCount === 0) {
+      console.warn('[GIS预警] 所有预警均无经纬度坐标,请检查发布预警时设备是否有定位数据')
+    }
+    const noCoordRecords = records.filter(w => !w.longitude || !w.latitude)
+    if (noCoordRecords.length > 0) {
+      console.warn('[GIS预警] 无坐标预警(将被过滤):', noCoordRecords.map(w => ({ id: w.id, name: w.equipmentName, longitude: w.longitude, latitude: w.latitude })))
+    }
+    warningList.value = records.map(w => ({
+      id: w.id,
+      name: w.equipmentName || '-',
+      level: levelMap[w.warningLevel] || '一般',
+      time: w.warningTime || '-',
+      type: w.warningType || '-',
+      value: w.currentValue || '-',
+      threshold: w.thresholdValue || '-',
+      location: w.location || '-',
+      lng: parseFloat(w.longitude),
+      lat: parseFloat(w.latitude)
+    })).filter(w => w.lng && w.lat)
+  } catch (error) {
+    console.error('加载当前预警数据失败', error)
+    ElMessage.error('加载当前预警数据失败')
+  }
+}
 
 // ==================== 统计计算 ====================
 const severeCount = computed(() => warningList.value.filter(v => v.level === '严重').length)
@@ -215,6 +252,12 @@ function initMap() {
     warningList.value.forEach(point => {
       addMapMarker(point)
     })
+
+    // 默认定位到第一个预警点位
+    if (warningList.value.length > 0) {
+      const firstPoint = warningList.value[0]
+      mapInstance.centerAndZoom(new BMapGL.Point(firstPoint.lng, firstPoint.lat), 15)
+    }
   } catch (e) {
     console.error('百度地图初始化失败:', e)
   }
@@ -273,7 +316,8 @@ function openDetail(point) {
 }
 
 // ==================== 生命周期 ====================
-onMounted(() => {
+onMounted(async () => {
+  await loadData()
   nextTick(() => {
     initMap()
   })

+ 224 - 193
src/views/subSystem/drainage/jcyj/dqyjgl.vue

@@ -43,11 +43,11 @@
           :class="{ active: activeTab === tab }"
           v-for="tab in tabs"
           :key="tab"
-          @click="activeTab = tab"
+          @click="handleTabChange(tab)"
         >{{ tab }}</div>
       </div>
       <div class="filter-form">
-        <el-input v-model="filters.deviceId" placeholder="设备ID" clearable style="width:150px" />
+        <el-input v-model="filters.equipmentName" placeholder="设备名称" clearable style="width:150px" />
         <el-date-picker
           v-model="filters.dateRange"
           type="daterange"
@@ -57,81 +57,103 @@
           style="width:260px"
           value-format="YYYY-MM-DD"
         />
-        <el-select v-model="filters.level" placeholder="选择工单级别" clearable style="width:150px">
-          <el-option label="一般" value="一般" />
-          <el-option label="重要" value="重要" />
-          <el-option label="紧急" value="紧急" />
-        </el-select>
-        <el-select v-model="filters.type" placeholder="选择工单类型" clearable style="width:150px">
-          <el-option label="管线" value="管线" />
-          <el-option label="管点" value="管点" />
-          <el-option label="窨井" value="窨井" />
-          <el-option label="管网" value="管网" />
+        <el-select v-model="filters.warningLevel" placeholder="预警级别" clearable style="width:150px">
+          <el-option label="严重" :value="1" />
+          <el-option label="重要" :value="2" />
+          <el-option label="一般" :value="3" />
         </el-select>
+        <el-button type="primary" @click="handleSearch">搜索</el-button>
+        <el-button @click="handleClear">重置</el-button>
       </div>
     </div>
 
     <!-- ==================== 数据表格 ==================== -->
     <div class="table-panel">
-      <el-table :data="filteredList" border stripe style="width:100%">
-        <el-table-column prop="orderId" label="工单号" width="130" />
-        <el-table-column prop="status" label="工单状态" width="100">
+      <el-table :data="tableData" border stripe style="width:100%" v-loading="loading">
+        <el-table-column prop="warningNo" label="预警编号" width="140" />
+        <el-table-column prop="equipmentName" label="设备名称" min-width="140" show-overflow-tooltip />
+        <el-table-column prop="warningLevel" label="预警级别" width="90" align="center">
           <template #default="{ row }">
-            <el-tag :type="statusTagType(row.status)" size="small">{{ row.status }}</el-tag>
+            <el-tag :type="levelTagType(row.warningLevel)" size="small">{{ levelText(row.warningLevel) }}</el-tag>
           </template>
         </el-table-column>
-        <el-table-column prop="priority" label="优先级" width="90">
+        <el-table-column prop="currentValue" label="当前值" width="100" />
+        <el-table-column prop="thresholdValue" label="阈值" width="100" />
+        <el-table-column prop="location" label="位置" min-width="160" show-overflow-tooltip />
+        <el-table-column prop="warningTime" label="预警时间" width="160" />
+        <el-table-column prop="status" label="状态" width="90" align="center">
           <template #default="{ row }">
-            <span :class="'priority-' + row.priority">{{ row.priority }}</span>
+            <el-tag :type="statusTagType(row.status)" size="small">{{ statusText(row.status) }}</el-tag>
           </template>
         </el-table-column>
-        <el-table-column prop="orderType" label="工单类型" width="90" />
-        <el-table-column prop="description" label="故障描述" show-overflow-tooltip />
-        <el-table-column prop="createTime" label="创建时间" width="130" />
-        <el-table-column prop="approvalTime" label="审批时间" width="130" />
-        <el-table-column label="操作" width="80" fixed="right">
+        <el-table-column label="操作" width="220" fixed="right">
           <template #default="{ row }">
             <el-button type="primary" link size="small" @click="handleView(row)">查看</el-button>
+            <el-button type="warning" link size="small" @click="handleProcess(row)" v-if="row.status === 0 && !isAdmin">处理</el-button>
+            <el-button type="info" link size="small" @click="handleClose(row)" v-if="row.status !== 2">关闭</el-button>
+            <!-- 管理员看到status=1的可以审批 -->
+            <el-button v-if="row.status === 1 && isAdmin" type="primary" link size="small" @click="handleFinalApprove(row)">审批</el-button>
+            <el-button type="danger" link size="small" @click="handleDelete(row)">删除</el-button>
           </template>
         </el-table-column>
       </el-table>
+      <el-pagination
+        v-model:current-page="pageNum"
+        v-model:page-size="pageSize"
+        :page-sizes="[10, 20, 50, 100]"
+        :total="total"
+        layout="total, sizes, prev, pager, next, jumper"
+        style="margin-top: 16px; justify-content: flex-end"
+        @size-change="loadData"
+        @current-change="loadData"
+      />
     </div>
 
     <!-- ==================== 查看详情弹窗 ==================== -->
-    <el-dialog v-model="detailVisible" title="工单派发" width="650px" :close-on-click-modal="false">
+    <el-dialog v-model="detailVisible" title="预警详情" width="650px" :close-on-click-modal="false">
       <div class="detail-content" v-if="currentOrder">
-        <!-- 进度条 -->
-        <div class="steps-wrap">
-          <el-steps :active="currentOrder.step" align-center>
-            <el-step title="创建" :description="currentOrder.stepInfo[0]" />
-            <el-step title="领导审批" :description="currentOrder.stepInfo[1]" />
-            <el-step title="管理员审批" :description="currentOrder.stepInfo[2]" />
-            <el-step title="完成" :description="currentOrder.stepInfo[3]" />
-          </el-steps>
-        </div>
-
-        <!-- 申请内容 -->
+        <!-- 预警信息 -->
         <div class="section-box">
-          <div class="section-title">申请内容</div>
+          <div class="section-title">预警信息</div>
           <el-form label-width="90px" size="default">
             <el-row :gutter="20">
               <el-col :span="12">
-                <el-form-item label="角色名称">{{ currentOrder.roleName }}</el-form-item>
+                <el-form-item label="预警编号">{{ currentOrder.warningNo }}</el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="设备名称">{{ currentOrder.equipmentName }}</el-form-item>
+              </el-col>
+            </el-row>
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-form-item label="预警级别">
+                  <el-tag :type="levelTagType(currentOrder.warningLevel)" size="small">{{ levelText(currentOrder.warningLevel) }}</el-tag>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="预警类型">{{ currentOrder.warningType }}</el-form-item>
+              </el-col>
+            </el-row>
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-form-item label="当前值">{{ currentOrder.currentValue }}</el-form-item>
               </el-col>
               <el-col :span="12">
-                <el-form-item label="角色编码">{{ currentOrder.roleCode }}</el-form-item>
+                <el-form-item label="阈值">{{ currentOrder.thresholdValue }}</el-form-item>
               </el-col>
             </el-row>
             <el-row :gutter="20">
               <el-col :span="12">
-                <el-form-item label="所属部门">{{ currentOrder.department }}</el-form-item>
+                <el-form-item label="预警时间">{{ currentOrder.warningTime }}</el-form-item>
               </el-col>
               <el-col :span="12">
-                <el-form-item label="过期时间">{{ currentOrder.expireTime }}</el-form-item>
+                <el-form-item label="状态">
+                  <el-tag :type="statusTagType(currentOrder.status)" size="small">{{ statusText(currentOrder.status) }}</el-tag>
+                </el-form-item>
               </el-col>
             </el-row>
-            <el-form-item label="描述">
-              <div class="desc-text">{{ currentOrder.description }}</div>
+            <el-form-item label="位置">
+              <div class="desc-text">{{ currentOrder.location }}</div>
             </el-form-item>
           </el-form>
         </div>
@@ -139,7 +161,7 @@
         <!-- 人员安排 -->
         <div class="section-box">
           <div class="section-title">
-            人员安排
+            接收人员安排
             <el-button type="primary" size="small" style="float:right" @click="showAddPersonDialog">添加</el-button>
           </div>
           <el-table :data="currentOrder.personnel" border size="small" style="width:100%">
@@ -156,8 +178,7 @@
       </div>
 
       <template #footer>
-        <el-button type="primary" @click="handleApprove(true)">审批通过</el-button>
-        <el-button @click="handleApprove(false)">审批不通过</el-button>
+        <el-button @click="detailVisible = false">关 闭</el-button>
       </template>
     </el-dialog>
 
@@ -184,168 +205,202 @@
 
 <script setup name="Dqyjgl">
 import { ref, computed, onMounted, nextTick } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
 import { Promotion } from '@element-plus/icons-vue'
 import * as echarts from 'echarts'
+import { getWarningCurrentPage, updateWarningStatus, deleteWarning, firstApproveWarning, finalApproveWarning } from '@/api/drainage'
+import useUserStore from '@/store/modules/user'
 
-// ==================== 模拟数据 ====================
-const orderList = ref([
-  {
-    orderId: '110110101', status: '处理中', priority: '紧急', orderType: '管线',
-    description: '人民路DN300供水管线破裂,导致周边区域停水', createTime: '2025-06-10', approvalTime: '',
-    step: 1, stepInfo: ['张三 2025-06-10 08:30', '', '', ''],
-    roleName: '管线巡检员', roleCode: 'GXXJ001', department: '管网维护部', expireTime: '2025-12-31',
-    personnel: [{ name: '李明', phone: '13800138001', dept: '管网维护部' }]
-  },
-  {
-    orderId: '110110102', status: '待审批', priority: '重要', orderType: '管点',
-    description: '建设路管点水位传感器数据异常,持续报警', createTime: '2025-06-11', approvalTime: '',
-    step: 2, stepInfo: ['王五 2025-06-11 09:15', '赵六 2025-06-11 10:30', '', ''],
-    roleName: '设备检修员', roleCode: 'SBXJ002', department: '设备运维部', expireTime: '2025-12-31',
-    personnel: [{ name: '孙七', phone: '13900139002', dept: '设备运维部' }]
-  },
-  {
-    orderId: '110110103', status: '已审批', priority: '一般', orderType: '窨井',
-    description: '幸福路窨井盖缺失,存在安全隐患', createTime: '2025-06-08', approvalTime: '2025-06-09',
-    step: 4, stepInfo: ['周八 2025-06-08 14:00', '吴九 2025-06-08 16:00', '郑十 2025-06-09 09:00', '2025-06-09 10:00'],
-    roleName: '安全巡查员', roleCode: 'AQXC003', department: '安全管理部', expireTime: '2026-06-30',
-    personnel: [{ name: '刘一', phone: '13700137003', dept: '安全管理部' }]
-  },
-  {
-    orderId: '110110104', status: '处理中', priority: '紧急', orderType: '管网',
-    description: '科技路DN500排水管网堵塞,污水外溢严重', createTime: '2025-06-12', approvalTime: '',
-    step: 1, stepInfo: ['陈二 2025-06-12 07:45', '', '', ''],
-    roleName: '管网清淤员', roleCode: 'GWQY004', department: '管网维护部', expireTime: '2025-12-31',
-    personnel: [{ name: '张三', phone: '13600136004', dept: '管网维护部' }]
-  },
-  {
-    orderId: '110110105', status: '待审批', priority: '重要', orderType: '管线',
-    description: '和平路燃气管线与排水管线交叉处存在渗漏', createTime: '2025-06-11', approvalTime: '',
-    step: 2, stepInfo: ['李四 2025-06-11 11:20', '王五 2025-06-11 14:00', '', ''],
-    roleName: '管线检测员', roleCode: 'GXJC005', department: '管线检测中心', expireTime: '2026-01-15',
-    personnel: [{ name: '赵六', phone: '13500135005', dept: '管线检测中心' }]
-  },
-  {
-    orderId: '110110106', status: '已审批', priority: '一般', orderType: '管点',
-    description: '长江路管点流量计读数偏差较大需校准', createTime: '2025-06-07', approvalTime: '2025-06-08',
-    step: 4, stepInfo: ['孙七 2025-06-07 10:00', '周八 2025-06-07 15:30', '吴九 2025-06-08 08:00', '2025-06-08 11:00'],
-    roleName: '仪表校准员', roleCode: 'YBJZ006', department: '设备运维部', expireTime: '2026-03-31',
-    personnel: [{ name: '郑十', phone: '13400134006', dept: '设备运维部' }]
-  },
-  {
-    orderId: '110110107', status: '处理中', priority: '重要', orderType: '窨井',
-    description: '黄河路窨井内积水严重,排水不畅', createTime: '2025-06-12', approvalTime: '',
-    step: 1, stepInfo: ['刘一 2025-06-12 06:30', '', '', ''],
-    roleName: '排水巡查员', roleCode: 'PSXC007', department: '排水管理部', expireTime: '2025-12-31',
-    personnel: [{ name: '陈二', phone: '13300133007', dept: '排水管理部' }]
-  },
-  {
-    orderId: '110110108', status: '待审批', priority: '紧急', orderType: '管网',
-    description: '太湖路排水管网节点压力异常,疑似管网变形', createTime: '2025-06-12', approvalTime: '',
-    step: 2, stepInfo: ['张三 2025-06-12 09:00', '李四 2025-06-12 10:15', '', ''],
-    roleName: '管网检测员', roleCode: 'GWJC008', department: '管网维护部', expireTime: '2025-12-31',
-    personnel: [{ name: '王五', phone: '13200132008', dept: '管网维护部' }]
-  },
-  {
-    orderId: '110110109', status: '已审批', priority: '一般', orderType: '管线',
-    description: '珠江路供水管线阀门锈蚀需更换', createTime: '2025-06-05', approvalTime: '2025-06-06',
-    step: 4, stepInfo: ['赵六 2025-06-05 08:00', '孙七 2025-06-05 11:00', '周八 2025-06-06 09:30', '2025-06-06 14:00'],
-    roleName: '阀门维护员', roleCode: 'FMWH009', department: '管网维护部', expireTime: '2026-06-30',
-    personnel: [{ name: '吴九', phone: '13100131009', dept: '管网维护部' }]
-  },
-  {
-    orderId: '110110110', status: '处理中', priority: '一般', orderType: '管点',
-    description: '松江路管点遥测终端通信故障', createTime: '2025-06-12', approvalTime: '',
-    step: 1, stepInfo: ['郑十 2025-06-12 10:00', '', '', ''],
-    roleName: '通信维护员', roleCode: 'TXWH010', department: '信息中心', expireTime: '2025-12-31',
-    personnel: [{ name: '刘一', phone: '13000130010', dept: '信息中心' }]
-  }
-])
+// ==================== 用户信息 ====================
+const userStore = useUserStore()
+const isAdmin = computed(() => userStore.roles && userStore.roles.includes('admin'))
+
+// ==================== 表格数据 ====================
+const tableData = ref([])
+const total = ref(0)
+const loading = ref(false)
+const pageNum = ref(1)
+const pageSize = ref(10)
+
+// ==================== 等级/状态工具方法 ====================
+function levelText(level) {
+  const map = { 1: '严重', 2: '重要', 3: '一般' }
+  return map[level] || '-'
+}
+function levelTagType(level) {
+  const map = { 1: 'danger', 2: 'warning', 3: '' }
+  return map[level] || 'info'
+}
+function statusText(status) {
+  const map = { 0: '待处理', 1: '已处理', 2: '已完成' }
+  return map[status] ?? '-'
+}
+function statusTagType(status) {
+  const map = { 0: 'warning', 1: 'info', 2: 'success' }
+  return map[status] || 'info'
+}
 
 // ==================== 统计指标 ====================
 const statItems = computed(() => {
-  const today = '2025-06-12'
-  const todayList = orderList.value.filter(o => o.createTime === today)
+  const list = tableData.value
   return [
-    { label: '今日报警', value: todayList.length, cls: '' },
-    { label: '今日处理中', value: todayList.filter(o => o.status === '处理中').length, cls: 'red' },
-    { label: '今日待审批', value: todayList.filter(o => o.status === '待审批').length, cls: '' },
-    { label: '今日已审批', value: todayList.filter(o => o.status === '已审批').length, cls: '' }
+    { label: '今日预警', value: total.value, cls: '' },
+    { label: '已处理', value: list.filter(o => o.status === 1).length, cls: 'red' },
+    { label: '待处理', value: list.filter(o => o.status === 0).length, cls: '' },
+    { label: '已完成', value: list.filter(o => o.status === 2).length, cls: '' }
   ]
 })
 
 // ==================== 环形图配置 ====================
 const statusLegends = computed(() => [
-  { name: '处理中', value: orderList.value.filter(o => o.status === '处理中').length, color: '#409EFF' },
-  { name: '未处理', value: orderList.value.filter(o => o.status === '待审批').length, color: '#E6A23C' },
-  { name: '预期', value: orderList.value.filter(o => o.status === '已审批').length, color: '#67C23A' }
+  { name: '待处理', value: tableData.value.filter(o => o.status === 0).length, color: '#E6A23C' },
+  { name: '已处理', value: tableData.value.filter(o => o.status === 1).length, color: '#409EFF' },
+  { name: '已完成', value: tableData.value.filter(o => o.status === 2).length, color: '#67C23A' }
 ])
-const priorityLegends = computed(() => [
-  { name: '一般', value: orderList.value.filter(o => o.priority === '一般').length, color: '#409EFF' },
-  { name: '重要', value: orderList.value.filter(o => o.priority === '重要').length, color: '#E6A23C' },
-  { name: '紧急', value: orderList.value.filter(o => o.priority === '紧急').length, color: '#F56C6C' }
-])
-const typeLegends = computed(() => [
-  { name: '设备故障', value: orderList.value.filter(o => o.orderType === '管线').length, color: '#409EFF' },
-  { name: '设备保养', value: orderList.value.filter(o => o.orderType === '管点').length, color: '#E6A23C' },
-  { name: '隐患排查', value: orderList.value.filter(o => ['窨井', '管网'].includes(o.orderType)).length, color: '#67C23A' }
+const levelLegends = computed(() => [
+  { name: '严重', value: tableData.value.filter(o => o.warningLevel === 1).length, color: '#F56C6C' },
+  { name: '重要', value: tableData.value.filter(o => o.warningLevel === 2).length, color: '#E6A23C' },
+  { name: '一般', value: tableData.value.filter(o => o.warningLevel === 3).length, color: '#409EFF' }
 ])
+const typeLegends = computed(() => {
+  const types = [...new Set(tableData.value.map(o => o.warningType).filter(Boolean))]
+  const colors = ['#409EFF', '#E6A23C', '#67C23A', '#F56C6C']
+  return types.map((t, i) => ({
+    name: t,
+    value: tableData.value.filter(o => o.warningType === t).length,
+    color: colors[i % colors.length]
+  }))
+})
 const ringCharts = [
   { id: 'statusChart', title: '状态', legends: statusLegends },
-  { id: 'priorityChart', title: '优先级', legends: priorityLegends },
+  { id: 'levelChart', title: '级别', legends: levelLegends },
   { id: 'typeChart', title: '类型', legends: typeLegends }
 ]
 
 // ==================== 筛选 ====================
-const tabs = ['全部', '处理中', '待审批', '已审批']
+const tabs = ['全部', '待处理', '已处理', '已完成']
 const activeTab = ref('全部')
 const filters = ref({
-  deviceId: '',
+  equipmentName: '',
   dateRange: null,
-  level: '',
-  type: ''
+  warningLevel: ''
 })
 
-const filteredList = computed(() => {
-  let list = orderList.value
-  // Tab 筛选
-  if (activeTab.value !== '全部') {
-    list = list.filter(o => o.status === activeTab.value)
+// ==================== 数据加载 ====================
+async function loadData() {
+  loading.value = true
+  try {
+    const params = {}
+    if (filters.value.equipmentName) params.equipmentName = filters.value.equipmentName
+    if (filters.value.warningLevel) params.warningLevel = filters.value.warningLevel
+    if (filters.value.dateRange && filters.value.dateRange.length === 2) {
+      params.startTime = filters.value.dateRange[0]
+      params.endTime = filters.value.dateRange[1]
+    }
+    // Tab 映射为状态筛选
+    const tabStatusMap = { '待处理': 0, '已处理': 1, '已完成': 2 }
+    if (activeTab.value !== '全部' && tabStatusMap[activeTab.value] !== undefined) {
+      params.status = tabStatusMap[activeTab.value]
+    }
+    // 普通用户只能看到发给自己的预警,管理员能看到所有预警
+    params.userId = userStore.id
+    params.isAdmin = isAdmin.value
+    // 传递部门ID,用于按部门可见性过滤(publish_scope=2)
+    if (userStore.deptId) {
+      params.deptId = userStore.deptId
+    }
+    const res = await getWarningCurrentPage(pageNum.value, pageSize.value, params)
+    const pageData = res.code !== undefined ? res.data : res
+    tableData.value = pageData.records || []
+    total.value = pageData.total || 0
+  } catch (error) {
+    console.error('加载当前预警列表失败', error)
+    ElMessage.error('加载当前预警列表失败')
+  } finally {
+    loading.value = false
+    nextTick(() => initCharts())
   }
-  // 设备ID (模糊匹配工单号)
-  if (filters.value.deviceId) {
-    list = list.filter(o => o.orderId.includes(filters.value.deviceId))
+}
+
+function handleSearch() {
+  pageNum.value = 1
+  loadData()
+}
+
+function handleClear() {
+  filters.value = { equipmentName: '', dateRange: null, warningLevel: '' }
+  activeTab.value = '全部'
+  pageNum.value = 1
+  loadData()
+}
+
+function handleTabChange(tab) {
+  activeTab.value = tab
+  pageNum.value = 1
+  loadData()
+}
+
+// ==================== 操作:处理/关闭/删除 ====================
+async function handleProcess(row) {
+  try {
+    await ElMessageBox.confirm('确认处理该预警?', '处理确认', { type: 'warning' })
+    await firstApproveWarning(row.id, { userId: userStore.id, userName: userStore.name })
+    ElMessage.success('处理成功')
+    loadData()
+  } catch (error) {
+    if (error !== 'cancel') {
+      console.error('处理失败', error)
+      ElMessage.error('处理失败')
+    }
   }
-  // 工单级别
-  if (filters.value.level) {
-    list = list.filter(o => o.priority === filters.value.level)
+}
+
+async function handleClose(row) {
+  try {
+    await ElMessageBox.confirm('确认关闭该预警?', '提示', { type: 'warning' })
+    await updateWarningStatus(row.id, { status: 2 })
+    ElMessage.success('预警已关闭')
+    loadData()
+  } catch (error) {
+    if (error !== 'cancel') console.error('关闭预警失败', error)
   }
-  // 工单类型
-  if (filters.value.type) {
-    list = list.filter(o => o.orderType === filters.value.type)
+}
+
+async function handleDelete(row) {
+  try {
+    await ElMessageBox.confirm('确认删除该预警?删除后不可恢复。', '提示', { type: 'warning' })
+    await deleteWarning(row.id)
+    ElMessage.success('删除成功')
+    loadData()
+  } catch (error) {
+    if (error !== 'cancel') console.error('删除预警失败', error)
   }
-  // 日期范围
-  if (filters.value.dateRange && filters.value.dateRange.length === 2) {
-    const [start, end] = filters.value.dateRange
-    list = list.filter(o => o.createTime >= start && o.createTime <= end)
+}
+
+// ==================== 审批流程 ====================
+async function handleFinalApprove(row) {
+  try {
+    await ElMessageBox.confirm('确认审批通过该预警?', '审批确认')
+    await finalApproveWarning(row.id, { userId: userStore.id, userName: userStore.name })
+    ElMessage.success('审批完成')
+    loadData()
+  } catch (error) {
+    if (error !== 'cancel') {
+      console.error('审批失败', error)
+      ElMessage.error('审批失败')
+    }
   }
-  return list
-})
+}
 
 // ==================== 详情弹窗 ====================
 const detailVisible = ref(false)
 const currentOrder = ref(null)
 
 function handleView(row) {
-  currentOrder.value = { ...row, personnel: [...row.personnel] }
+  currentOrder.value = { ...row, personnel: [] }
   detailVisible.value = true
 }
 
-function handleAddPerson() {
-  if (currentOrder.value) {
-    currentOrder.value.personnel.push({ name: '', phone: '', dept: '' })
-  }
-}
-
 // ==================== 添加人员弹窗 ====================
 const addPersonVisible = ref(false)
 const personFormRef = ref(null)
@@ -371,30 +426,6 @@ function handleAddPersonConfirm() {
   })
 }
 
-function handleApprove(pass) {
-  if (pass) {
-    if (currentOrder.value) {
-      const idx = orderList.value.findIndex(o => o.orderId === currentOrder.value.orderId)
-      if (idx > -1) {
-        orderList.value[idx].status = '已审批'
-        orderList.value[idx].approvalTime = '2025-06-12'
-        orderList.value[idx].step = 4
-        orderList.value[idx].stepInfo[2] = '当前用户 2025-06-12'
-        orderList.value[idx].stepInfo[3] = '2025-06-12'
-      }
-    }
-  }
-  detailVisible.value = false
-  nextTick(() => initCharts())
-}
-
-function statusTagType(status) {
-  if (status === '处理中') return 'primary'
-  if (status === '待审批') return 'warning'
-  if (status === '已审批') return 'success'
-  return 'info'
-}
-
 // ==================== ECharts 初始化 ====================
 function initCharts() {
   ringCharts.forEach(chart => {
@@ -427,7 +458,7 @@ function initCharts() {
 }
 
 onMounted(() => {
-  nextTick(() => initCharts())
+  loadData()
 })
 </script>
 

+ 51 - 24
src/views/subSystem/drainage/jcyj/lsyjGIS.vue

@@ -99,10 +99,6 @@
             <span class="info-label">预警时间</span>
             <span class="info-value">{{ currentItem.time }}</span>
           </div>
-          <div class="info-row">
-            <span class="info-label">预警类型</span>
-            <span class="info-value">{{ currentItem.type }}</span>
-          </div>
           <div class="info-row">
             <span class="info-label">当前值</span>
             <span class="info-value highlight">{{ currentItem.value }}</span>
@@ -134,29 +130,53 @@
 <script setup name="LsyjglGIS">
 import { ref, reactive, computed, onMounted, nextTick } from 'vue'
 import { Search } from '@element-plus/icons-vue'
+import { ElMessage } from 'element-plus'
 import locationIcon from '@/assets/images/location.png'
+import { getWarningHistoryPage } from '@/api/drainage'
 
 // ==================== 等级筛选 ====================
 const levelChecked = reactive({ severe: true, important: true, normal: true })
 
-// ==================== 模拟历史预警数据(15个点位)====================
-const warningList = ref([
-  { id: 1, name: '沅陵大桥液位计', level: '严重', time: '2026-06-11 14:23:15', type: '液位超限', value: '4.10m', threshold: '2.50m', location: '沅陵大桥南侧排水口', status: '已处理', processTime: '2026-06-11 15:10:30', lng: 110.396, lat: 28.468 },
-  { id: 2, name: '太常路流量计', level: '重要', time: '2026-06-11 10:45:30', type: '流量异常', value: '1350m³/h', threshold: '800m³/h', location: '太常路与沅江路交汇', status: '已处理', processTime: '2026-06-11 11:30:20', lng: 110.382, lat: 28.459 },
-  { id: 3, name: '沅江路液位计', level: '一般', time: '2026-06-10 22:10:05', type: '液位超限', value: '1.92m', threshold: '1.50m', location: '沅江路中段排水井', status: '已关闭', processTime: '2026-06-11 08:00:00', lng: 110.403, lat: 28.447 },
-  { id: 4, name: '龙舟路雨量计', level: '严重', time: '2026-06-10 16:30:22', type: '降雨预警', value: '92mm/h', threshold: '50mm/h', location: '龙舟路与建设路交汇', status: '已处理', processTime: '2026-06-10 18:20:15', lng: 110.388, lat: 28.436 },
-  { id: 5, name: '城东泵站液位计', level: '重要', time: '2026-06-10 09:55:18', type: '液位超限', value: '3.80m', threshold: '3.00m', location: '城东开发区泵站', status: '已归档', processTime: '2026-06-10 14:00:00', lng: 110.412, lat: 28.461 },
-  { id: 6, name: '一号泵站流量计', level: '一般', time: '2026-06-09 20:22:40', type: '流量异常', value: '580m³/h', threshold: '500m³/h', location: '沅陵大桥西侧泵站', status: '已处理', processTime: '2026-06-09 21:15:00', lng: 110.391, lat: 28.465 },
-  { id: 7, name: '二号泵站液位计', level: '严重', time: '2026-06-09 15:18:33', type: '液位超限', value: '4.95m', threshold: '3.00m', location: '太常片区排水泵站', status: '已处理', processTime: '2026-06-09 16:45:00', lng: 110.378, lat: 28.451 },
-  { id: 8, name: '沅水南岸流量计', level: '重要', time: '2026-06-09 11:32:10', type: '流量异常', value: '1050m³/h', threshold: '600m³/h', location: '沅水南岸排水口', status: '已关闭', processTime: '2026-06-09 16:00:00', lng: 110.368, lat: 28.448 },
-  { id: 9, name: '建设路液位计', level: '一般', time: '2026-06-08 23:40:55', type: '液位超限', value: '2.05m', threshold: '1.80m', location: '建设路下穿通道', status: '已归档', processTime: '2026-06-09 09:00:00', lng: 110.393, lat: 28.441 },
-  { id: 10, name: '城东下穿液位计', level: '严重', time: '2026-06-08 18:05:28', type: '液位超限', value: '3.60m', threshold: '2.00m', location: '城东立交桥下', status: '已处理', processTime: '2026-06-08 19:30:00', lng: 110.415, lat: 28.457 },
-  { id: 11, name: '主城区管网流量计', level: '重要', time: '2026-06-08 12:12:45', type: '流量异常', value: '980m³/h', threshold: '700m³/h', location: '主城区排水干管节点', status: '已处理', processTime: '2026-06-08 13:40:00', lng: 110.390, lat: 28.460 },
-  { id: 12, name: '沅江路东段液位计', level: '一般', time: '2026-06-07 21:55:12', type: '液位超限', value: '1.72m', threshold: '1.50m', location: '沅江路东段检测井', status: '已关闭', processTime: '2026-06-08 06:00:00', lng: 110.407, lat: 28.455 },
-  { id: 13, name: '城北泵站液位计', level: '严重', time: '2026-06-07 14:28:50', type: '液位超限', value: '4.30m', threshold: '2.50m', location: '城北排水枢纽站', status: '已归档', processTime: '2026-06-07 17:00:00', lng: 110.385, lat: 28.472 },
-  { id: 14, name: '南岸雨量计', level: '重要', time: '2026-06-07 08:15:22', type: '降雨预警', value: '68mm/h', threshold: '50mm/h', location: '沅水南岸监测点', status: '已处理', processTime: '2026-06-07 10:30:00', lng: 110.372, lat: 28.443 },
-  { id: 15, name: '西郊流量计', level: '一般', time: '2026-06-06 17:40:08', type: '流量异常', value: '530m³/h', threshold: '500m³/h', location: '西郊排水管网节点', status: '已处理', processTime: '2026-06-06 18:50:00', lng: 110.360, lat: 28.455 }
-])
+// ==================== 历史预警数据(从API加载)====================
+const warningList = ref([])
+
+// 预警级别/状态数字→文字映射
+const levelMap = { 1: '严重', 2: '重要', 3: '一般' }
+const statusMap = { 0: '待处理', 1: '已处理', 2: '已完成' }
+
+// 加载历史预警数据
+async function loadData() {
+  try {
+    const res = await getWarningHistoryPage(1, 1000, {})
+    const pageData = res.code !== undefined ? res.data : res
+    const records = pageData.records || []
+    const hasCoordCount = records.filter(w => w.longitude && w.latitude).length
+    console.log('[历史预警GIS] API返回记录数:', records.length, '有坐标记录数:', hasCoordCount)
+    if (records.length > 0 && hasCoordCount === 0) {
+      console.warn('[历史预警GIS] 所有预警均无经纬度坐标,请检查发布预警时设备是否有定位数据')
+    }
+    const noCoordRecords = records.filter(w => !w.longitude || !w.latitude)
+    if (noCoordRecords.length > 0) {
+      console.warn('[历史预警GIS] 无坐标预警(将被过滤):', noCoordRecords.map(w => ({ id: w.id, name: w.equipmentName, longitude: w.longitude, latitude: w.latitude })))
+    }
+    warningList.value = records.map(w => ({
+      id: w.id,
+      name: w.equipmentName || '-',
+      level: levelMap[w.warningLevel] || '一般',
+      time: w.warningTime || '-',
+      value: w.currentValue || '-',
+      threshold: w.thresholdValue || '-',
+      location: w.location || '-',
+      status: statusMap[w.status] || '-',
+      processTime: w.processTime || '',
+      lng: parseFloat(w.longitude),
+      lat: parseFloat(w.latitude)
+    })).filter(w => w.lng && w.lat)
+  } catch (error) {
+    console.error('加载历史预警数据失败', error)
+    ElMessage.error('加载历史预警数据失败')
+  }
+}
 
 // ==================== 统计计算 ====================
 const severeCount = computed(() => warningList.value.filter(v => v.level === '严重').length)
@@ -185,7 +205,7 @@ function getLevelKey(level) {
 }
 
 function getStatusTagType(status) {
-  const map = { '已处理': 'success', '已关闭': 'info', '已归档': '' }
+  const map = { '待处理': 'warning', '已处理': 'info', '已完成': 'success' }
   return map[status] || ''
 }
 
@@ -233,6 +253,12 @@ function initMap() {
     warningList.value.forEach(point => {
       addMapMarker(point)
     })
+
+    // 默认定位到第一个预警点位
+    if (warningList.value.length > 0) {
+      const firstPoint = warningList.value[0]
+      mapInstance.centerAndZoom(new BMapGL.Point(firstPoint.lng, firstPoint.lat), 15)
+    }
   } catch (e) {
     console.error('百度地图初始化失败:', e)
   }
@@ -291,7 +317,8 @@ function openDetail(point) {
 }
 
 // ==================== 生命周期 ====================
-onMounted(() => {
+onMounted(async () => {
+  await loadData()
   nextTick(() => {
     initMap()
   })

+ 132 - 283
src/views/subSystem/drainage/jcyj/lsyjgl.vue

@@ -5,26 +5,22 @@
     <div class="search-panel">
       <div class="search-form">
         <div class="form-item">
-          <span class="form-label">设备ID:</span>
-          <el-input v-model="filters.deviceId" placeholder="请输入" clearable style="width:200px" />
+          <span class="form-label">设备名称:</span>
+          <el-input v-model="filters.equipmentName" placeholder="请输入" clearable style="width:200px" />
         </div>
         <div class="form-item">
-          <span class="form-label">所属区域:</span>
-          <el-select v-model="filters.area" placeholder="请选择" clearable style="width:160px">
-            <el-option label="沅陵大道" value="沅陵大道" />
-            <el-option label="城北路段" value="城北路段" />
-            <el-option label="河西片区" value="河西片区" />
-            <el-option label="人民路" value="人民路" />
-            <el-option label="南环路" value="南环路" />
-            <el-option label="开发区" value="开发区" />
+          <span class="form-label">预警级别:</span>
+          <el-select v-model="filters.warningLevel" placeholder="请选择" clearable style="width:160px">
+            <el-option label="严重" :value="1" />
+            <el-option label="重要" :value="2" />
+            <el-option label="一般" :value="3" />
           </el-select>
         </div>
         <div class="form-item">
-          <span class="form-label">设备状态:</span>
+          <span class="form-label">处理状态:</span>
           <el-select v-model="filters.status" placeholder="请选择" clearable style="width:160px">
-            <el-option label="已处理" value="已处理" />
-            <el-option label="已关闭" value="已关闭" />
-            <el-option label="已归档" value="已归档" />
+            <el-option label="已处理" :value="1" />
+            <el-option label="已完成" :value="2" />
           </el-select>
         </div>
         <el-button type="primary" @click="handleSearch">搜索</el-button>
@@ -34,80 +30,99 @@
 
     <!-- ==================== 数据表格 ==================== -->
     <div class="table-panel">
-      <el-table :data="filteredList" border style="width:100%">
-        <el-table-column prop="archiveNo" label="档案编号" min-width="130" />
-        <el-table-column prop="area" label="所属区域" min-width="120" />
-        <el-table-column prop="installDate" label="安装日期" min-width="120" />
-        <el-table-column prop="type" label="所属类型" min-width="100">
+      <el-table :data="tableData" border style="width:100%" v-loading="loading">
+        <el-table-column prop="warningNo" label="预警编号" min-width="140" />
+        <el-table-column prop="equipmentName" label="设备名称" min-width="140" show-overflow-tooltip />
+        <el-table-column prop="warningLevel" label="预警级别" width="90" align="center">
           <template #default="{ row }">
-            <span :class="typeClass(row.type)">{{ row.type }}</span>
+            <el-tag :type="levelTagType(row.warningLevel)" size="small">{{ levelText(row.warningLevel) }}</el-tag>
           </template>
         </el-table-column>
-        <el-table-column prop="address" label="地址" min-width="200" />
-        <el-table-column label="操作" width="100" fixed="right">
+        <el-table-column prop="currentValue" label="当前值" width="100" />
+        <el-table-column prop="thresholdValue" label="阈值" width="100" />
+        <el-table-column prop="location" label="位置" min-width="180" show-overflow-tooltip />
+        <el-table-column prop="warningTime" label="预警时间" width="160" />
+        <el-table-column prop="status" label="处理状态" width="90" align="center">
+          <template #default="{ row }">
+            <el-tag :type="statusTagType(row.status)" size="small">{{ statusText(row.status) }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="firstApproveTime" label="处理时间" width="160" />
+        <el-table-column label="操作" width="140" fixed="right">
           <template #default="{ row }">
             <el-button link type="primary" @click="handleView(row)">查看</el-button>
+            <el-button link type="danger" @click="handleDelete(row)">删除</el-button>
           </template>
         </el-table-column>
       </el-table>
+      <el-pagination
+        v-model:current-page="pageNum"
+        v-model:page-size="pageSize"
+        :page-sizes="[10, 20, 50, 100]"
+        :total="total"
+        layout="total, sizes, prev, pager, next, jumper"
+        style="margin-top: 16px; justify-content: flex-end"
+        @size-change="loadData"
+        @current-change="loadData"
+      />
     </div>
 
     <!-- ==================== 查看详情弹窗 ==================== -->
     <el-dialog
       v-model="dialogVisible"
-      title="异常管理"
+      title="历史预警详情"
       width="680px"
       :close-on-click-modal="false"
     >
       <div class="detail-body" v-if="currentDetail">
-        <!-- 左侧图片占位 -->
-        <div class="detail-img">
-          <el-icon class="img-placeholder-icon"><Picture /></el-icon>
-        </div>
         <!-- 右侧信息 -->
         <div class="detail-info">
-          <div class="info-code">编号:{{ currentDetail.code }}</div>
+          <div class="info-code">预警编号:{{ currentDetail.warningNo }}</div>
           <div class="info-grid">
             <div class="info-row">
               <div class="info-item">
-                <span class="info-label">名称:</span>
-                <span class="info-value">{{ currentDetail.name }}</span>
+                <span class="info-label">设备名称:</span>
+                <span class="info-value">{{ currentDetail.equipmentName }}</span>
               </div>
               <div class="info-item">
-                <span class="info-label">ID:</span>
-                <span class="info-value">{{ currentDetail.deviceId }}</span>
+                <span class="info-label">预警级别:</span>
+                <span class="info-value">
+                  <el-tag :type="levelTagType(currentDetail.warningLevel)" size="small">{{ levelText(currentDetail.warningLevel) }}</el-tag>
+                </span>
               </div>
             </div>
             <div class="info-row">
               <div class="info-item">
-                <span class="info-label">型号:</span>
-                <span class="info-value">{{ currentDetail.model }}</span>
+                <span class="info-label">当前值:</span>
+                <span class="info-value">{{ currentDetail.currentValue }}</span>
               </div>
               <div class="info-item">
-                <span class="info-label">异常日期:</span>
-                <span class="info-value">{{ currentDetail.abnormalDate }}</span>
+                <span class="info-label">阈值:</span>
+                <span class="info-value">{{ currentDetail.thresholdValue }}</span>
               </div>
             </div>
             <div class="info-row">
               <div class="info-item">
-                <span class="info-label">所属单位:</span>
-                <span class="info-value">{{ currentDetail.unit }}</span>
+                <span class="info-label">预警时间:</span>
+                <span class="info-value">{{ currentDetail.warningTime }}</span>
               </div>
+            </div>
+            <div class="info-row">
               <div class="info-item">
-                <span class="info-label">详细地址:</span>
-                <span class="info-value">{{ currentDetail.address }}</span>
+                <span class="info-label">处理状态:</span>
+                <span class="info-value">
+                  <el-tag :type="statusTagType(currentDetail.status)" size="small">{{ statusText(currentDetail.status) }}</el-tag>
+                </span>
               </div>
-            </div>
-            <div class="info-row single">
               <div class="info-item">
-                <span class="info-label">异常类型:</span>
-                <span class="info-value">{{ currentDetail.abnormalType }}</span>
+                <span class="info-label">处理时间:</span>
+                <span class="info-value">{{ currentDetail.firstApproveTime || '-' }}</span>
               </div>
             </div>
             <div class="info-row single">
               <div class="info-item">
-                <span class="info-label">描述:</span>
-                <span class="info-value desc-text">{{ currentDetail.description }}</span>
+                <span class="info-label">位置:</span>
+                <span class="info-value desc-text">{{ currentDetail.location }}</span>
               </div>
             </div>
           </div>
@@ -122,255 +137,85 @@
 </template>
 
 <script setup name="Lsyjgl">
-import { ref, reactive, computed } from 'vue'
-import { Picture } from '@element-plus/icons-vue'
-
-// ==================== 模拟数据 ====================
-const rawList = ref([
-  {
-    id: 1,
-    archiveNo: '110110101',
-    area: '沅陵大道',
-    installDate: '2025-03-15',
-    type: '管线',
-    address: '沅陵县沅陵大道18号',
-    status: '已归档',
-    deviceId: 'DEV-001',
-    detail: {
-      code: '0011001',
-      name: '地下管线传感器A',
-      deviceId: '001001',
-      model: 'ZK-PL18V',
-      abnormalDate: '2025-03-10',
-      unit: 'XX燃气公司',
-      address: '沅陵县沅陵大道18号',
-      abnormalType: '管网漏损,设备保养',
-      description: '设备检测到管线压力异常,压力持续低于正常阈值,疑似管道出现轻微渗漏,已派人员现场检查并完成修复处理,档案已归档。',
-    }
-  },
-  {
-    id: 2,
-    archiveNo: '110110102',
-    area: '城北路段',
-    installDate: '2025-04-02',
-    type: '管点',
-    address: '沅陵县城北路段32号',
-    status: '已处理',
-    deviceId: 'DEV-002',
-    detail: {
-      code: '0011002',
-      name: '管点监测仪B',
-      deviceId: '001002',
-      model: 'ZK-GP22',
-      abnormalDate: '2025-03-28',
-      unit: 'XX排水所',
-      address: '沅陵县城北路段32号',
-      abnormalType: '流量异常',
-      description: '管点流量监测数据显示异常波动,经现场核查为传感器接触不良,已更换传感器并恢复正常运行。',
-    }
-  },
-  {
-    id: 3,
-    archiveNo: '110110103',
-    area: '河西片区',
-    installDate: '2025-02-20',
-    type: '窨井',
-    address: '沅陵县河西大道7号',
-    status: '已关闭',
-    deviceId: 'DEV-003',
-    detail: {
-      code: '0011003',
-      name: '窨井液位仪C',
-      deviceId: '001003',
-      model: 'ZK-YJ15',
-      abnormalDate: '2025-02-18',
-      unit: 'XX市政管理',
-      address: '沅陵县河西大道7号',
-      abnormalType: '液位超限',
-      description: '窨井液位超过警戒线,经检查系周边排水管道堵塞导致,已疏通管道并清洁窨井,液位恢复正常。',
-    }
-  },
-  {
-    id: 4,
-    archiveNo: '110110104',
-    area: '人民路',
-    installDate: '2025-01-10',
-    type: '管线',
-    address: '沅陵县人民路55号',
-    status: '已归档',
-    deviceId: 'DEV-004',
-    detail: {
-      code: '0011004',
-      name: '管线压力计D',
-      deviceId: '001004',
-      model: 'ZK-PL30',
-      abnormalDate: '2025-01-08',
-      unit: 'XX供水公司',
-      address: '沅陵县人民路55号',
-      abnormalType: '压力超标',
-      description: '供水管线压力持续超过正常值,经排查为调压阀门故障,已更换阀门并调整压力至正常范围,事件闭环归档。',
-    }
-  },
-  {
-    id: 5,
-    archiveNo: '110110105',
-    area: '南环路',
-    installDate: '2025-05-06',
-    type: '窨井',
-    address: '沅陵县南环路101号',
-    status: '已处理',
-    deviceId: 'DEV-005',
-    detail: {
-      code: '0011005',
-      name: '窨井盖传感器E',
-      deviceId: '001005',
-      model: 'ZK-YG08',
-      abnormalDate: '2025-05-03',
-      unit: 'XX道路管理所',
-      address: '沅陵县南环路101号',
-      abnormalType: '井盖位移',
-      description: '井盖位移传感器触发报警,现场确认井盖被车辆碰撞移位,已立即复位并加固固定装置,恢复正常监测。',
-    }
-  },
-  {
-    id: 6,
-    archiveNo: '110110106',
-    area: '开发区',
-    installDate: '2024-12-15',
-    type: '管点',
-    address: '沅陵县开发区工业路3号',
-    status: '已归档',
-    deviceId: 'DEV-006',
-    detail: {
-      code: '0011006',
-      name: '管点水质仪F',
-      deviceId: '001006',
-      model: 'ZK-SZ12',
-      abnormalDate: '2024-12-12',
-      unit: 'XX环保监测站',
-      address: '沅陵县开发区工业路3号',
-      abnormalType: '水质异常',
-      description: '管点水质检测指标偏高,pH值超出正常范围,经溯源检查发现上游工业排水混入,已协调整改并跟踪复测达标。',
-    }
-  },
-  {
-    id: 7,
-    archiveNo: '110110107',
-    area: '沅陵大道',
-    installDate: '2025-06-01',
-    type: '管线',
-    address: '沅陵县沅陵大道208号',
-    status: '已关闭',
-    deviceId: 'DEV-007',
-    detail: {
-      code: '0011007',
-      name: '燃气管线监测G',
-      deviceId: '001007',
-      model: 'ZK-GX25V',
-      abnormalDate: '2025-05-28',
-      unit: 'XX燃气集团',
-      address: '沅陵县沅陵大道208号',
-      abnormalType: '燃气浓度超标',
-      description: '燃气浓度传感器检测到周边空气中燃气浓度升高,经排查为管线焊接点微渗,已完成补焊处理,复测合格后关闭预警。',
-    }
-  },
-  {
-    id: 8,
-    archiveNo: '110110108',
-    area: '城北路段',
-    installDate: '2025-03-22',
-    type: '窨井',
-    address: '沅陵县城北新区12号',
-    status: '已归档',
-    deviceId: 'DEV-008',
-    detail: {
-      code: '0011008',
-      name: '雨水窨井液位H',
-      deviceId: '001008',
-      model: 'ZK-YJ10R',
-      abnormalDate: '2025-03-20',
-      unit: 'XX排水管理所',
-      address: '沅陵县城北新区12号',
-      abnormalType: '液位持续升高',
-      description: '强降雨期间窨井液位快速上升至警戒值,系排水能力不足,现场增设临时泵站配合抽排,雨后进行清淤扩容处理。',
-    }
-  },
-  {
-    id: 9,
-    archiveNo: '110110109',
-    area: '河西片区',
-    installDate: '2025-04-18',
-    type: '管点',
-    address: '沅陵县河西小区88号',
-    status: '已处理',
-    deviceId: 'DEV-009',
-    detail: {
-      code: '0011009',
-      name: '管点流量计I',
-      deviceId: '001009',
-      model: 'ZK-LL20',
-      abnormalDate: '2025-04-15',
-      unit: 'XX自来水公司',
-      address: '沅陵县河西小区88号',
-      abnormalType: '流量骤降',
-      description: '流量计数据显示供水流量骤降,现场检查发现截止阀异常关闭,经人工开阀并排查原因为阀门控制模块故障,已更换模块恢复正常。',
-    }
-  },
-  {
-    id: 10,
-    archiveNo: '110110110',
-    area: '人民路',
-    installDate: '2025-05-15',
-    type: '管线',
-    address: '沅陵县人民路广场段',
-    status: '已归档',
-    deviceId: 'DEV-010',
-    detail: {
-      code: '0011010',
-      name: '污水管线监测J',
-      deviceId: '001010',
-      model: 'ZK-WS18',
-      abnormalDate: '2025-05-12',
-      unit: 'XX污水处理厂',
-      address: '沅陵县人民路广场段',
-      abnormalType: '管网堵塞',
-      description: '污水管线水位传感器持续报警,现场检查确认为管内油脂沉积导致严重堵塞,采用高压水车疏通后恢复正常,建议定期维护。',
-    }
-  },
-])
+import { ref, reactive, onMounted } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { getWarningHistoryPage, deleteWarning } from '@/api/drainage'
+
+// ==================== 等级/状态工具方法 ====================
+function levelText(level) {
+  const map = { 1: '严重', 2: '重要', 3: '一般' }
+  return map[level] || '-'
+}
+function levelTagType(level) {
+  const map = { 1: 'danger', 2: 'warning', 3: '' }
+  return map[level] || 'info'
+}
+function statusText(status) {
+  const map = { 0: '待处理', 1: '已处理', 2: '已完成' }
+  return map[status] ?? '-'
+}
+function statusTagType(status) {
+  const map = { 0: 'warning', 1: 'info', 2: 'success' }
+  return map[status] || 'info'
+}
+
+// ==================== 表格数据 ====================
+const tableData = ref([])
+const total = ref(0)
+const loading = ref(false)
+const pageNum = ref(1)
+const pageSize = ref(10)
 
 // ==================== 搜索筛选 ====================
 const filters = reactive({
-  deviceId: '',
-  area: '',
-  status: '',
+  equipmentName: '',
+  warningLevel: '',
+  status: ''
 })
 
-const filteredList = computed(() => {
-  return rawList.value.filter(item => {
-    const matchDevice = !filters.deviceId || item.deviceId.includes(filters.deviceId)
-    const matchArea = !filters.area || item.area === filters.area
-    const matchStatus = !filters.status || item.status === filters.status
-    return matchDevice && matchArea && matchStatus
-  })
-})
+// ==================== 数据加载 ====================
+async function loadData() {
+  loading.value = true
+  try {
+    const params = {}
+    if (filters.equipmentName) params.equipmentName = filters.equipmentName
+    if (filters.warningLevel) params.warningLevel = filters.warningLevel
+    if (filters.status !== '' && filters.status !== null) params.status = filters.status
+    const res = await getWarningHistoryPage(pageNum.value, pageSize.value, params)
+    const pageData = res.code !== undefined ? res.data : res
+    tableData.value = pageData.records || []
+    total.value = pageData.total || 0
+  } catch (error) {
+    console.error('加载历史预警列表失败', error)
+    ElMessage.error('加载历史预警列表失败')
+  } finally {
+    loading.value = false
+  }
+}
 
 function handleSearch() {
-  // computed 自动响应,无需额外操作
+  pageNum.value = 1
+  loadData()
 }
 
 function handleClear() {
-  filters.deviceId = ''
-  filters.area = ''
+  filters.equipmentName = ''
+  filters.warningLevel = ''
   filters.status = ''
+  pageNum.value = 1
+  loadData()
 }
 
-// ==================== 所属类型颜色 ====================
-function typeClass(type) {
-  if (type === '管线') return 'type-tag type-pipe'
-  if (type === '管点') return 'type-tag type-point'
-  if (type === '窨井') return 'type-tag type-well'
-  return ''
+// ==================== 操作:删除 ====================
+async function handleDelete(row) {
+  try {
+    await ElMessageBox.confirm('确认删除该预警?删除后不可恢复。', '提示', { type: 'warning' })
+    await deleteWarning(row.id)
+    ElMessage.success('删除成功')
+    loadData()
+  } catch (error) {
+    if (error !== 'cancel') console.error('删除预警失败', error)
+  }
 }
 
 // ==================== 查看详情弹窗 ====================
@@ -378,9 +223,13 @@ const dialogVisible = ref(false)
 const currentDetail = ref(null)
 
 function handleView(row) {
-  currentDetail.value = { ...row.detail, archiveNo: row.archiveNo }
+  currentDetail.value = { ...row }
   dialogVisible.value = true
 }
+
+onMounted(() => {
+  loadData()
+})
 </script>
 
 <style scoped>

+ 2 - 2
src/views/system/user/index.vue

@@ -220,7 +220,7 @@
                </el-col>
                <el-col :span="12">
                   <el-form-item v-if="form.userId == undefined" label="用户密码" prop="password">
-                     <el-input v-model="form.password" placeholder="请输入用户密码" type="password" maxlength="20" show-password />
+                     <el-input v-model="form.password" placeholder="请输入用户密码" type="password" maxlength="20" />
                   </el-form-item>
                </el-col>
             </el-row>
@@ -605,7 +605,7 @@ function handleUpdate(row) {
     form.value.roleIds = response.roleIds;
     open.value = true;
     title.value = "修改用户";
-    form.password = "";
+    form.value.password = "";
   });
 };
 

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.