Browse Source

排水系统

LAPTOP-JI2IUVG1\26646 4 days ago
parent
commit
dcc64a0f00

+ 99 - 3
src/api/drainage.js

@@ -70,7 +70,7 @@ 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' })
+  return request({ url: '/api/drainage/equipment/base/' + id, method: 'get', headers: { 'X-Silent-Error': 'true' } })
 }
 export function getEquipmentList() {
   return request({ url: '/api/drainage/equipment/base/list', method: 'get' })
@@ -87,8 +87,10 @@ export function deleteEquipment(id) {
 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 getEquipmentTypeList(parentTypeId) {
+  const params = {}
+  if (parentTypeId) params.parentTypeId = parentTypeId
+  return request({ url: '/api/drainage/equipmentType/list', method: 'get', params })
 }
 
 // ============ 设备状态 ============
@@ -188,3 +190,97 @@ export function finalApproveWarning(id, data) {
     params: data
   })
 }
+
+// 排水分区关联设备
+export function bindPartitionEquipment(data) {
+  return request({ url: '/api/drainage/partition/bindEquipment', method: 'post', data })
+}
+
+// 查询所有已被关联的设备ID(排水分区)
+export function getBoundEquipmentIds(excludeId) {
+  const params = {}
+  if (excludeId) params.excludeId = excludeId
+  return request({ url: '/api/drainage/partition/boundEquipmentIds', method: 'get', params, headers: { 'X-Silent-Error': 'true' } })
+}
+
+// 查询所有已被关联的设备ID(排水管网)
+export function getPipeNetworkBoundEquipmentIds(excludeId) {
+  const params = {}
+  if (excludeId) params.excludeId = excludeId
+  return request({ url: '/api/drainage/pipeNetwork/boundEquipmentIds', method: 'get', params, headers: { 'X-Silent-Error': 'true' } })
+}
+
+// 排水管网关联设备
+export function bindPipeNetworkEquipment(data) {
+  return request({ url: '/api/drainage/pipeNetwork/bindEquipment', method: 'post', data })
+}
+
+// 工程设施关联设备
+export function bindFacilityEquipment(data) {
+  return request({ url: '/api/drainage/facility/bindEquipment', method: 'post', data })
+}
+
+// 易积水点关联设备
+export function bindWaterloggingEquipment(data) {
+  return request({ url: '/api/drainage/waterlogging/bindEquipment', method: 'post', data })
+}
+
+// 报警数据表查询(alarm_data表)
+export function getAlarmDataPage(pageNum, pageSize, params) {
+  return request({ url: '/alarmData/findByPage', method: 'get', params: { pageNum, pageSize, ...params } })
+}
+
+// 处理报警(审核提交)
+export function handleAlarm(alarmId, handleUser, handleRemark) {
+  return request({
+    url: `/api/alarm/handle/${alarmId}`,
+    method: 'put',
+    params: { handleUser, handleRemark }
+  })
+}
+
+// 根据ID获取报警详情
+export function getAlarmById(id) {
+  return request({
+    url: `/api/alarm/${id}`,
+    method: 'get'
+  })
+}
+
+// 雷达流量计 - 获取设备最新监测数据
+export function getRadarDataByCode(deviceCode) {
+  return request({ url: '/api/radar/data/latest', method: 'get', params: { deviceCode } })
+}
+
+// 雷达流量计 - 历史数据分页查询(用于监测曲线)
+export function getRadarDataHistory(deviceCode, startTime, endTime) {
+  return request({ url: '/api/radar/data/page', method: 'get', params: { deviceCode, startTime, endTime, pageNum: 1, pageSize: 9999 } })
+}
+
+// ============ 泵站 (water_pump_station) ============
+export function getPumpStationPage(pageNum, pageSize, params) {
+  return request({ url: '/api/drainage/pumpStation/page', method: 'get', params: { pageNum, pageSize, ...params } })
+}
+
+// 根据ID查询泵站详情
+export function getPumpStationById(id) {
+  return request({ url: '/api/drainage/pumpStation/' + id, method: 'get', headers: { 'X-Silent-Error': 'true' } })
+}
+
+// 泵站近三个月月度统计
+export function getPumpStationMonthlyStats(ids, sinceDate) {
+  const params = { ids }
+  if (sinceDate) params.sinceDate = sinceDate
+  return request({ url: '/api/drainage/pumpStation/monthlyStats', method: 'get', params })
+}
+
+// 工程设施关联泵站
+export function bindPumpStation(data) {
+  return request({ url: '/api/drainage/facility/bindPumpStation', method: 'post', data })
+}
+
+// ============ 工单(运维派单)============
+// 新增工单(派单)
+export function addWorkOrder(data) {
+  return request({ url: '/manhole/workOrder/addWorkOrder', method: 'post', data })
+}

+ 175 - 210
src/views/subSystem/drainage/fxpg/zhfxtjfx.vue

@@ -2,26 +2,16 @@
   <div class="app-container">
     <!-- 搜索区域 -->
     <el-form :model="queryParams" ref="queryRef" :inline="true" class="search-form">
-      <el-form-item label="管线编号">
-        <el-input v-model="queryParams.pipeNo" placeholder="请输入" clearable style="width: 160px" />
+      <el-form-item label="管网名称">
+        <el-input v-model="queryParams.name" placeholder="请输入" clearable style="width: 160px" />
       </el-form-item>
-      <el-form-item label="管线类别">
-        <el-select v-model="queryParams.pipeType" placeholder="请选择" clearable style="width: 160px">
-          <el-option v-for="item in pipeTypeOptions" :key="item" :label="item" :value="item" />
+      <el-form-item label="管网类型">
+        <el-select v-model="queryParams.networkType" placeholder="请选择" clearable style="width: 160px">
+          <el-option v-for="item in networkTypeOptions" :key="item" :label="item" :value="item" />
         </el-select>
       </el-form-item>
       <el-form-item label="所属区域">
-        <el-select v-model="queryParams.area" placeholder="请选择" clearable style="width: 160px">
-          <el-option v-for="item in areaOptions" :key="item" :label="item" :value="item" />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="所在道路">
-        <el-input v-model="queryParams.road" placeholder="请输入" clearable style="width: 160px" />
-      </el-form-item>
-      <el-form-item label="风险级别">
-        <el-select v-model="queryParams.riskLevel" placeholder="请选择" clearable style="width: 160px">
-          <el-option v-for="item in riskLevelOptions" :key="item.value" :label="item.label" :value="item.value" />
-        </el-select>
+        <el-input v-model="queryParams.area" placeholder="请输入" clearable style="width: 160px" />
       </el-form-item>
       <el-form-item>
         <el-button type="primary" @click="handleQuery">搜索</el-button>
@@ -33,77 +23,64 @@
     <div class="stat-cards">
       <el-card class="stat-card stat-blue" shadow="hover">
         <div class="stat-card-inner">
-          <div class="stat-icon"><el-icon :size="32">
-              <DataLine />
-            </el-icon></div>
+          <div class="stat-icon"><el-icon :size="32"><DataLine /></el-icon></div>
           <div class="stat-info">
             <div class="stat-value">{{ stats.total }}</div>
-            <div class="stat-label">总管线数</div>
+            <div class="stat-label">总管数</div>
           </div>
         </div>
       </el-card>
       <el-card class="stat-card stat-red" shadow="hover">
         <div class="stat-card-inner">
-          <div class="stat-icon"><el-icon :size="32">
-              <Warning />
-            </el-icon></div>
+          <div class="stat-icon"><el-icon :size="32"><Warning /></el-icon></div>
           <div class="stat-info">
-            <div class="stat-value">{{ stats.high }}</div>
-            <div class="stat-label">高风险管线</div>
+            <div class="stat-value">{{ stats.withAlarm }}</div>
+            <div class="stat-label">有报警管网</div>
           </div>
         </div>
       </el-card>
       <el-card class="stat-card stat-orange" shadow="hover">
         <div class="stat-card-inner">
-          <div class="stat-icon"><el-icon :size="32">
-              <WarningFilled />
-            </el-icon></div>
+          <div class="stat-icon"><el-icon :size="32"><WarningFilled /></el-icon></div>
           <div class="stat-info">
-            <div class="stat-value">{{ stats.medium }}</div>
-            <div class="stat-label">中风险管线</div>
+            <div class="stat-value">{{ stats.totalAlarms }}</div>
+            <div class="stat-label">总报警数</div>
           </div>
         </div>
       </el-card>
       <el-card class="stat-card stat-green" shadow="hover">
         <div class="stat-card-inner">
-          <div class="stat-icon"><el-icon :size="32">
-              <CircleCheck />
-            </el-icon></div>
+          <div class="stat-icon"><el-icon :size="32"><CircleCheck /></el-icon></div>
           <div class="stat-info">
-            <div class="stat-value">{{ stats.low }}</div>
-            <div class="stat-label">低风险管线</div>
+            <div class="stat-value">{{ stats.noAlarm }}</div>
+            <div class="stat-label">无报警管网</div>
           </div>
         </div>
       </el-card>
     </div>
 
     <!-- 数据表格 -->
-    <el-table :data="pageList" border style="width: 100%">
-      <el-table-column label="序号" type="index" width="60" align="center" />
-      <el-table-column label="管线编号" prop="pipeNo" min-width="130" />
-      <el-table-column label="管线名称" prop="pipeName" min-width="150" />
-      <el-table-column label="管线类别" prop="pipeType" min-width="100" align="center">
+    <el-table :data="tableData" border style="width: 100%" v-loading="loading">
+      <el-table-column label="序号" type="index" width="60" align="center" :index="(i) => (pageNum - 1) * pageSize + i + 1" />
+      <el-table-column label="管网名称" prop="name" min-width="180" />
+      <el-table-column label="所属区域" prop="area" min-width="120" />
+      <el-table-column label="管网类型" prop="networkType" min-width="100" align="center">
         <template #default="{ row }">
-          <el-tag :type="pipeTypeTagType(row.pipeType)" effect="light">{{ row.pipeType }}</el-tag>
+          <el-tag :type="networkTypeTagType(row.networkType)" effect="light">{{ row.networkType }}</el-tag>
         </template>
       </el-table-column>
-      <el-table-column label="所属区域" prop="area" min-width="110" />
-      <el-table-column label="所在道路" prop="road" min-width="110" />
-      <el-table-column label="管线长度(m)" prop="length" min-width="110" align="center" />
-      <el-table-column label="管径(mm)" prop="diameter" min-width="100" align="center" />
-      <el-table-column label="埋深(m)" prop="depth" min-width="90" align="center" />
-      <el-table-column label="风险评分" prop="riskScore" min-width="90" align="center">
+      <el-table-column label="关联设备" prop="equipmentId" min-width="100" align="center">
         <template #default="{ row }">
-          <span :class="'score-' + row.riskLevel">{{ row.riskScore }}</span>
+          <span v-if="row.equipmentId">已关联</span>
+          <span v-else style="color:#909399">未关联</span>
         </template>
       </el-table-column>
-      <el-table-column label="风险级别" prop="riskLevel" min-width="100" align="center">
+      <el-table-column label="报警次数" min-width="100" align="center">
         <template #default="{ row }">
-          <el-tag :color="riskLevelColor(row.riskLevel)" effect="dark" style="border:none">{{ riskLevelText(row.riskLevel)
-          }}</el-tag>
+          <span :class="row._alarmCount > 0 ? 'alarm-has' : ''">{{ row._alarmCount ?? '-' }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="评估时间" prop="assessTime" min-width="120" align="center" />
+      <el-table-column label="创建时间" prop="createTime" min-width="160" align="center" />
       <el-table-column label="操作" width="100" align="center" fixed="right">
         <template #default="{ row }">
           <el-button link type="primary" @click="handleDetail(row)">查看详情</el-button>
@@ -113,64 +90,58 @@
 
     <!-- 分页 -->
     <div class="pagination-wrap">
-      <el-pagination v-model:current-page="page.current" v-model:page-size="page.size" :page-sizes="[10, 20, 50]"
-        :total="filteredList.length" layout="total, sizes, prev, pager, next, jumper" background />
+      <el-pagination
+        v-model:current-page="pageNum"
+        v-model:page-size="pageSize"
+        :page-sizes="[10, 20, 50]"
+        :total="total"
+        layout="total, sizes, prev, pager, next, jumper"
+        background
+        @size-change="loadData"
+        @current-change="loadData"
+      />
     </div>
 
     <!-- 详情弹窗 -->
-    <el-dialog v-model="detailVisible" title="管网综合风险评估详情" width="800px" destroy-on-close>
+    <el-dialog v-model="detailVisible" title="管网综合风险详情" width="800px" destroy-on-close>
       <template v-if="currentRow">
         <el-tabs v-model="detailTab">
           <el-tab-pane label="基本信息" name="basic">
             <el-descriptions :column="2" border>
-              <el-descriptions-item label="管线编号">{{ currentRow.pipeNo }}</el-descriptions-item>
-              <el-descriptions-item label="管线名称">{{ currentRow.pipeName }}</el-descriptions-item>
-              <el-descriptions-item label="管线类别">{{ currentRow.pipeType }}</el-descriptions-item>
+              <el-descriptions-item label="管网名称">{{ currentRow.name }}</el-descriptions-item>
               <el-descriptions-item label="所属区域">{{ currentRow.area }}</el-descriptions-item>
-              <el-descriptions-item label="所在道路">{{ currentRow.road }}</el-descriptions-item>
-              <el-descriptions-item label="建设年份">2018年</el-descriptions-item>
-              <el-descriptions-item label="管理单位">沅陵县市政管理处</el-descriptions-item>
-              <el-descriptions-item label="负责人">张工</el-descriptions-item>
-            </el-descriptions>
-          </el-tab-pane>
-          <el-tab-pane label="管线信息" name="pipe">
-            <el-descriptions :column="2" border>
-              <el-descriptions-item label="管径(mm)">{{ currentRow.diameter }}</el-descriptions-item>
-              <el-descriptions-item label="管线长度(m)">{{ currentRow.length }}</el-descriptions-item>
-              <el-descriptions-item label="埋深(m)">{{ currentRow.depth }}</el-descriptions-item>
-              <el-descriptions-item label="管材类型">钢筋混凝土</el-descriptions-item>
-              <el-descriptions-item label="坡度">0.3%</el-descriptions-item>
-              <el-descriptions-item label="流向">由北向南</el-descriptions-item>
-              <el-descriptions-item label="接口形式">承插式</el-descriptions-item>
-              <el-descriptions-item label="周边环境">城市主干道下方</el-descriptions-item>
+              <el-descriptions-item label="管网类型">
+                <el-tag :type="networkTypeTagType(currentRow.networkType)" effect="light">{{ currentRow.networkType }}</el-tag>
+              </el-descriptions-item>
+              <el-descriptions-item label="关联设备">{{ currentRow.equipmentId ? '已关联 (ID: ' + currentRow.equipmentId + ')' : '未关联' }}</el-descriptions-item>
+              <el-descriptions-item label="坐标信息" :span="2">{{ formatPoints(currentRow.points) }}</el-descriptions-item>
+              <el-descriptions-item label="创建时间">{{ currentRow.createTime }}</el-descriptions-item>
+              <el-descriptions-item label="更新时间">{{ currentRow.updateTime }}</el-descriptions-item>
             </el-descriptions>
           </el-tab-pane>
-          <el-tab-pane label="评估详情" name="assess">
-            <div style="margin-bottom:16px">
-              <el-tag :type="riskLevelColor(currentRow.riskLevel)" size="large">{{ riskLevelText(currentRow.riskLevel)
-              }}</el-tag>
-              <span style="margin-left:12px;font-size:18px;font-weight:bold;">综合评分:{{ currentRow.riskScore }}</span>
+          <el-tab-pane label="报警统计" name="alarm">
+            <div v-if="currentRow.equipmentId" v-loading="alarmLoading">
+              <div style="margin-bottom:16px">
+                <el-tag :type="currentRow._alarmCount > 0 ? 'danger' : 'success'" size="large">
+                  报警总数:{{ currentRow._alarmCount ?? 0 }}
+                </el-tag>
+              </div>
+              <el-table :data="alarmList" border size="small" v-if="alarmList.length > 0">
+                <el-table-column prop="alarmTime" label="报警时间" min-width="160" />
+                <el-table-column prop="alarmType" label="报警类型" min-width="100" />
+                <el-table-column prop="alarmContent" label="报警内容" min-width="200" />
+                <el-table-column prop="status" label="状态" min-width="80" align="center">
+                  <template #default="{ row }">
+                    <el-tag :type="row.status === 0 ? 'warning' : row.status === 1 ? 'primary' : 'success'" size="small">
+                      {{ row.status === 0 ? '未处理' : row.status === 1 ? '处理中' : '已处理' }}
+                    </el-tag>
+                  </template>
+                </el-table-column>
+              </el-table>
+              <el-empty v-else description="暂无报警数据" :image-size="60" />
             </div>
-            <el-table :data="assessItems" border size="small">
-              <el-table-column prop="name" label="评估指标" />
-              <el-table-column prop="weight" label="权重" width="80" align="center" />
-              <el-table-column prop="score" label="评分" width="80" align="center" />
-              <el-table-column prop="level" label="等级" width="100" align="center" />
-            </el-table>
+            <el-empty v-else description="该管网未关联设备,无报警数据" :image-size="60" />
           </el-tab-pane>
-          <!-- <el-tab-pane label="评估分析" name="analysis">
-            <el-alert type="info" :closable="false" style="margin-bottom:12px">
-              <template #title>
-                <strong>风险评估建议:</strong>
-                <span v-if="currentRow.riskLevel==='high'">该管线风险等级较高,建议3个月内安排专项检修,加强监测频率。</span>
-                <span v-else-if="currentRow.riskLevel==='medium'">该管线存在中等风险,建议6个月内纳入巡检计划。</span>
-                <span v-else>该管线风险较低,按常规周期巡检即可。</span>
-              </template>
-            </el-alert>
-            <p><strong>评估时间:</strong>{{ currentRow.assessTime }}</p>
-            <p><strong>评估人员:</strong>李工、王工</p>
-            <p><strong>下次评估建议时间:</strong>2026-05-01</p>
-          </el-tab-pane> -->
         </el-tabs>
       </template>
       <template #footer>
@@ -181,119 +152,138 @@
 </template>
 
 <script setup name="Zhfxtjfx">
-import { ref, computed } from 'vue'
+import { ref, computed, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
 import { DataLine, Warning, WarningFilled, CircleCheck } from '@element-plus/icons-vue'
+import { getPipeNetworkPage, getAlarmDataPage } from '@/api/drainage'
+
+const router = useRouter()
 
 // ==================== 下拉选项 ====================
-const pipeTypeOptions = ['雨水管', '污水管', '合流管', '燃气管']
-const areaOptions = ['沅陵大道', '城北路段', '河西片区', '人民路', '南环路', '开发区']
-const riskLevelOptions = [
-  { label: '高风险', value: 'high' },
-  { label: '中风险', value: 'medium' },
-  { label: '低风险', value: 'low' },
-  { label: '极低风险', value: 'veryLow' }
-]
+const networkTypeOptions = ['管线', '管点', '排口']
 
 // ==================== 搜索参数 ====================
-const queryParams = ref({
-  pipeNo: '',
-  pipeType: '',
-  area: '',
-  road: '',
-  riskLevel: ''
-})
+const queryParams = ref({ name: '', networkType: '管线', area: '' })
 const queryRef = ref(null)
 
+// ==================== 分页与数据 ====================
+const pageNum = ref(1)
+const pageSize = ref(10)
+const total = ref(0)
+const tableData = ref([])
+const loading = ref(false)
+
 const handleQuery = () => {
-  page.value.current = 1
+  pageNum.value = 1
+  loadData()
 }
+
 const resetQuery = () => {
-  queryParams.value = { pipeNo: '', pipeType: '', area: '', road: '', riskLevel: '' }
-  page.value.current = 1
+  queryParams.value = { name: '', networkType: '管线', area: '' }
+  pageNum.value = 1
+  loadData()
 }
 
-// ==================== 模拟数据 ====================
-const tableData = ref([
-  { id: 1, pipeNo: 'GX-2024-001', pipeName: '沅陵大道雨水主干线', pipeType: '雨水管', area: '沅陵大道', road: '沅陵大道', length: 1250, diameter: 800, depth: 2.5, riskScore: 87, riskLevel: 'high', assessTime: '2025-11-15' },
-  { id: 2, pipeNo: 'GX-2024-002', pipeName: '城北路段污水管线', pipeType: '污水管', area: '城北路段', road: '城北路', length: 980, diameter: 600, depth: 3.1, riskScore: 72, riskLevel: 'medium', assessTime: '2025-11-10' },
-  { id: 3, pipeNo: 'GX-2024-003', pipeName: '河西片区合流管线', pipeType: '合流管', area: '河西片区', road: '河西大道', length: 1560, diameter: 1000, depth: 2.8, riskScore: 45, riskLevel: 'low', assessTime: '2025-11-08' },
-  { id: 4, pipeNo: 'GX-2024-004', pipeName: '人民路雨水管线', pipeType: '雨水管', area: '人民路', road: '人民路', length: 870, diameter: 500, depth: 2.2, riskScore: 91, riskLevel: 'high', assessTime: '2025-11-05' },
-  { id: 5, pipeNo: 'GX-2024-005', pipeName: '南环路污水支线', pipeType: '污水管', area: '南环路', road: '南环路', length: 640, diameter: 400, depth: 1.9, riskScore: 38, riskLevel: 'veryLow', assessTime: '2025-11-02' },
-  { id: 6, pipeNo: 'GX-2024-006', pipeName: '开发区雨水管线A', pipeType: '雨水管', area: '开发区', road: '开发大道', length: 2100, diameter: 1200, depth: 3.5, riskScore: 76, riskLevel: 'medium', assessTime: '2025-10-28' },
-  { id: 7, pipeNo: 'GX-2024-007', pipeName: '沅陵大道污水支线', pipeType: '污水管', area: '沅陵大道', road: '沅陵大道', length: 560, diameter: 300, depth: 2.0, riskScore: 52, riskLevel: 'low', assessTime: '2025-10-25' },
-  { id: 8, pipeNo: 'GX-2024-008', pipeName: '城北路段雨水干线', pipeType: '雨水管', area: '城北路段', road: '城北路', length: 1320, diameter: 800, depth: 2.7, riskScore: 83, riskLevel: 'high', assessTime: '2025-10-22' },
-  { id: 9, pipeNo: 'GX-2024-009', pipeName: '河西片区污水管线', pipeType: '污水管', area: '河西片区', road: '河西大道', length: 780, diameter: 500, depth: 2.4, riskScore: 62, riskLevel: 'medium', assessTime: '2025-10-18' },
-  { id: 10, pipeNo: 'GX-2024-010', pipeName: '人民路合流管线', pipeType: '合流管', area: '人民路', road: '人民路', length: 1050, diameter: 700, depth: 3.0, riskScore: 29, riskLevel: 'veryLow', assessTime: '2025-10-15' },
-  { id: 11, pipeNo: 'GX-2024-011', pipeName: '南环路雨水管线', pipeType: '雨水管', area: '南环路', road: '南环路', length: 930, diameter: 600, depth: 2.3, riskScore: 68, riskLevel: 'medium', assessTime: '2025-10-12' },
-  { id: 12, pipeNo: 'GX-2024-012', pipeName: '开发区燃气管线', pipeType: '燃气管', area: '开发区', road: '开发大道', length: 1800, diameter: 200, depth: 1.5, riskScore: 94, riskLevel: 'high', assessTime: '2025-10-08' },
-  { id: 13, pipeNo: 'GX-2024-013', pipeName: '沅陵大道合流支线', pipeType: '合流管', area: '沅陵大道', road: '沅陵大道', length: 420, diameter: 400, depth: 1.8, riskScore: 41, riskLevel: 'low', assessTime: '2025-10-05' },
-  { id: 14, pipeNo: 'GX-2024-014', pipeName: '城北路段燃气管线', pipeType: '燃气管', area: '城北路段', road: '城北路', length: 690, diameter: 150, depth: 1.4, riskScore: 33, riskLevel: 'veryLow', assessTime: '2025-10-01' },
-  { id: 15, pipeNo: 'GX-2024-015', pipeName: '河西片区雨水干线', pipeType: '雨水管', area: '河西片区', road: '河西大道', length: 1150, diameter: 900, depth: 3.2, riskScore: 79, riskLevel: 'medium', assessTime: '2025-09-28' }
-])
-
-// ==================== 过滤与分页 ====================
-const filteredList = computed(() => {
-  return tableData.value.filter(item => {
-    if (queryParams.value.pipeNo && !item.pipeNo.includes(queryParams.value.pipeNo)) return false
-    if (queryParams.value.pipeType && item.pipeType !== queryParams.value.pipeType) return false
-    if (queryParams.value.area && item.area !== queryParams.value.area) return false
-    if (queryParams.value.road && !item.road.includes(queryParams.value.road)) return false
-    if (queryParams.value.riskLevel && item.riskLevel !== queryParams.value.riskLevel) return false
-    return true
-  })
-})
-
-const page = ref({ current: 1, size: 10 })
-const pageList = computed(() => {
-  const start = (page.value.current - 1) * page.value.size
-  return filteredList.value.slice(start, start + page.value.size)
-})
+// ==================== 加载数据 ====================
+const loadData = async () => {
+  loading.value = true
+  try {
+    const params = {}
+    if (queryParams.value.name) params.name = queryParams.value.name
+    if (queryParams.value.networkType) params.networkType = queryParams.value.networkType
+    if (queryParams.value.area) params.area = queryParams.value.area
+
+    const res = await getPipeNetworkPage(pageNum.value, pageSize.value, params)
+    const pageData = res.code !== undefined ? res.data : res
+    const rows = pageData.records || pageData.rows || []
+    const totalCount = pageData.total ?? 0
+    total.value = totalCount
+
+    // 为每条管网查询报警数量
+    const withAlarms = await Promise.all(
+      rows.map(async (item) => {
+        let alarmCount = 0
+        if (item.equipmentId) {
+          try {
+            const alarmRes = await getAlarmDataPage(1, 1, { equipmentId: item.equipmentId, equipmentType: 'drainage' })
+            const alarmPageData = alarmRes.code !== undefined ? alarmRes.data : alarmRes
+            alarmCount = alarmPageData.total ?? 0
+          } catch (e) { /* ignore */ }
+        }
+        return { ...item, _alarmCount: alarmCount }
+      })
+    )
+    tableData.value = withAlarms
+  } catch (e) {
+    console.error('加载管网数据失败', e)
+    tableData.value = []
+  } finally {
+    loading.value = false
+  }
+}
 
 // ==================== 统计 ====================
 const stats = computed(() => {
-  const list = filteredList.value
+  const list = tableData.value
+  const withAlarm = list.filter(i => i._alarmCount > 0).length
+  const totalAlarms = list.reduce((sum, i) => sum + (i._alarmCount || 0), 0)
   return {
-    total: list.length,
-    high: list.filter(i => i.riskLevel === 'high').length,
-    medium: list.filter(i => i.riskLevel === 'medium').length,
-    low: list.filter(i => i.riskLevel === 'low' || i.riskLevel === 'veryLow').length
+    total: total.value,
+    withAlarm,
+    totalAlarms,
+    noAlarm: list.length - withAlarm
   }
 })
 
 // ==================== 工具函数 ====================
-const pipeTypeTagType = (type) => {
-  const map = { '雨水管': '', '污水管': 'warning', '合流管': 'success', '燃气管': 'danger' }
+const networkTypeTagType = (type) => {
+  const map = { '管线': '', '管点': 'warning', '排口': 'success' }
   return map[type] || ''
 }
 
-const riskLevelColor = (level) => {
-  const map = { high: '#F56C6C', medium: '#E6A23C', low: '#409EFF', veryLow: '#67C23A' }
-  return map[level] || '#909399'
-}
-
-const riskLevelText = (level) => {
-  const map = { high: '高风险', medium: '中风险', low: '低风险', veryLow: '极低风险' }
-  return map[level] || '未知'
+const formatPoints = (points) => {
+  if (!points) return '暂无坐标'
+  try {
+    const parsed = typeof points === 'string' ? JSON.parse(points) : points
+    if (Array.isArray(parsed)) {
+      return parsed.map(p => `${p.lng || p[0]}, ${p.lat || p[1]}`).join(' → ')
+    }
+    return String(points)
+  } catch {
+    return String(points)
+  }
 }
 
 // ==================== 详情弹窗 ====================
 const detailVisible = ref(false)
 const currentRow = ref(null)
 const detailTab = ref('basic')
+const alarmLoading = ref(false)
+const alarmList = ref([])
 
-const assessItems = [
-  { name: '结构性缺陷', weight: '0.25', score: '82', level: '中等' },
-  { name: '功能性缺陷', weight: '0.20', score: '75', level: '中等' },
-  { name: '环境影响', weight: '0.20', score: '68', level: '较高' },
-  { name: '运行状态', weight: '0.20', score: '79', level: '中等' },
-  { name: '历史事故', weight: '0.15', score: '90', level: '良好' }
-]
-
-const handleDetail = (row) => {
+const handleDetail = async (row) => {
   currentRow.value = row
   detailTab.value = 'basic'
   detailVisible.value = true
+  alarmList.value = []
+
+  if (row.equipmentId) {
+    alarmLoading.value = true
+    try {
+      const res = await getAlarmDataPage(1, 20, { equipmentId: row.equipmentId, equipmentType: 'drainage' })
+      alarmList.value = res.rows || res.data?.rows || res.data || []
+    } catch (e) {
+      alarmList.value = []
+    } finally {
+      alarmLoading.value = false
+    }
+  }
 }
+
+// ==================== 初始化 ====================
+onMounted(() => {
+  loadData()
+})
 </script>
 
 <style scoped>
@@ -369,21 +359,10 @@ const handleDetail = (row) => {
   line-height: 1.2;
 }
 
-.stat-blue .stat-value {
-  color: #409EFF;
-}
-
-.stat-red .stat-value {
-  color: #F56C6C;
-}
-
-.stat-orange .stat-value {
-  color: #E6A23C;
-}
-
-.stat-green .stat-value {
-  color: #67C23A;
-}
+.stat-blue .stat-value { color: #409EFF; }
+.stat-red .stat-value { color: #F56C6C; }
+.stat-orange .stat-value { color: #E6A23C; }
+.stat-green .stat-value { color: #67C23A; }
 
 .stat-label {
   font-size: 14px;
@@ -391,30 +370,16 @@ const handleDetail = (row) => {
   margin-top: 4px;
 }
 
-/* 风险评分颜色 */
-.score-high {
+/* 报警数高亮 */
+.alarm-has {
   color: #F56C6C;
   font-weight: 700;
 }
 
-.score-medium {
-  color: #E6A23C;
-  font-weight: 700;
-}
-
-.score-low {
-  color: #409EFF;
-  font-weight: 700;
-}
-
-.score-veryLow {
-  color: #67C23A;
-  font-weight: 700;
-}
-
 /* 分页 */
 .pagination-wrap {
   display: flex;
   justify-content: flex-end;
   margin-top: 16px;
-}</style>
+}
+</style>

+ 281 - 428
src/views/subSystem/drainage/fxpg/zhfxxqfx.vue

@@ -2,8 +2,13 @@
   <div class="app-container">
     <!-- 顶部搜索区 -->
     <el-form :model="searchKey" :inline="true" class="search-form">
-      <el-form-item label="管线编号">
-        <el-input v-model="searchKey.pipeNo" placeholder="请输入管线编号" clearable style="width: 220px" />
+      <el-form-item label="管网名称">
+        <el-input v-model="searchKey.name" placeholder="请输入管网名称" clearable style="width: 220px" />
+      </el-form-item>
+      <el-form-item label="管网类型">
+        <el-select v-model="searchKey.networkType" placeholder="请选择" clearable style="width: 140px">
+          <el-option v-for="item in networkTypeOptions" :key="item" :label="item" :value="item" />
+        </el-select>
       </el-form-item>
       <el-form-item>
         <el-button type="primary" @click="handleSearch">搜索</el-button>
@@ -11,18 +16,23 @@
     </el-form>
 
     <div class="main-layout">
-      <!-- 左侧管线列表 -->
+      <!-- 左侧管列表 -->
       <div class="left-panel">
-        <div class="panel-title">管线列表</div>
-        <div class="pipe-list">
-          <div v-for="item in filteredList" :key="item.pipeNo" class="pipe-item"
-            :class="{ active: currentPipe?.pipeNo === item.pipeNo }" @click="selectPipe(item)">
-            <div class="pipe-item-no">{{ item.pipeNo }}</div>
-            <div class="pipe-item-name">{{ item.pipeName }}</div>
-            <el-tag size="small" :color="riskLevelColor(item.riskLevel)" effect="dark" style="border:none">{{
-              riskLevelText(item.riskLevel) }}</el-tag>
+        <div class="panel-title">管网列表</div>
+        <div class="pipe-list" v-loading="listLoading">
+          <div v-for="item in pipeList" :key="item.id" class="pipe-item"
+            :class="{ active: currentPipe?.id === item.id }" @click="selectPipe(item)">
+            <div class="pipe-item-name">{{ item.name }}</div>
+            <div class="pipe-item-meta">
+              <el-tag size="small" :type="networkTypeTagType(item.networkType)" effect="light">{{ item.networkType }}</el-tag>
+              <span class="pipe-item-area">{{ item.area }}</span>
+            </div>
+            <div v-if="item._alarmCount > 0" class="pipe-item-alarm">
+              <el-icon color="#F56C6C"><Warning /></el-icon>
+              <span>{{ item._alarmCount }} 条报警</span>
+            </div>
           </div>
-          <el-empty v-if="filteredList.length === 0" description="暂无数据" :image-size="60" />
+          <el-empty v-if="pipeList.length === 0 && !listLoading" description="暂无数据" :image-size="60" />
         </div>
       </div>
 
@@ -33,145 +43,88 @@
             <!-- Tab 1: 基本信息 -->
             <el-tab-pane label="基本信息" name="basic">
               <el-descriptions :column="2" border>
-                <el-descriptions-item label="管线编号">{{ currentPipe.pipeNo }}</el-descriptions-item>
-                <el-descriptions-item label="管线名称">{{ currentPipe.pipeName }}</el-descriptions-item>
-                <el-descriptions-item label="管线类别">
-                  <el-tag :type="pipeTypeTagType(currentPipe.pipeType)" effect="light">{{ currentPipe.pipeType }}</el-tag>
-                </el-descriptions-item>
+                <el-descriptions-item label="管网名称">{{ currentPipe.name }}</el-descriptions-item>
                 <el-descriptions-item label="所属区域">{{ currentPipe.area }}</el-descriptions-item>
-                <el-descriptions-item label="所在道路">{{ currentPipe.road }}</el-descriptions-item>
-                <el-descriptions-item label="起点坐标">{{ currentPipe.startCoord }}</el-descriptions-item>
-                <el-descriptions-item label="终点坐标">{{ currentPipe.endCoord }}</el-descriptions-item>
-                <el-descriptions-item label="建设年份">{{ currentPipe.buildYear }}</el-descriptions-item>
-                <el-descriptions-item label="使用年限">{{ currentPipe.serviceLife }} 年</el-descriptions-item>
-                <el-descriptions-item label="管理单位">{{ currentPipe.manageUnit }}</el-descriptions-item>
-                <el-descriptions-item label="负责人">{{ currentPipe.manager }}</el-descriptions-item>
-                <el-descriptions-item label="联系电话">{{ currentPipe.phone }}</el-descriptions-item>
-              </el-descriptions>
-            </el-tab-pane>
-
-            <!-- Tab 2: 管线信息 -->
-            <el-tab-pane label="管线信息" name="pipeInfo">
-              <el-descriptions :column="2" border>
-                <el-descriptions-item label="管径(mm)">{{ currentPipe.diameter }}</el-descriptions-item>
-                <el-descriptions-item label="管线长度(m)">{{ currentPipe.length }}</el-descriptions-item>
-                <el-descriptions-item label="埋深(m)">{{ currentPipe.depth }}</el-descriptions-item>
-                <el-descriptions-item label="管材类型">{{ currentPipe.material }}</el-descriptions-item>
-                <el-descriptions-item label="坡度">{{ currentPipe.slope }}‰</el-descriptions-item>
-                <el-descriptions-item label="流向">{{ currentPipe.flowDir }}</el-descriptions-item>
-                <el-descriptions-item label="接口形式">{{ currentPipe.jointType }}</el-descriptions-item>
-                <el-descriptions-item label="连接方式">{{ currentPipe.connectType }}</el-descriptions-item>
-                <el-descriptions-item label="上游节点">{{ currentPipe.upstreamNode }}</el-descriptions-item>
-                <el-descriptions-item label="下游节点">{{ currentPipe.downstreamNode }}</el-descriptions-item>
-                <el-descriptions-item label="周边环境描述" :span="2">{{ currentPipe.surroundEnv }}</el-descriptions-item>
+                <el-descriptions-item label="管网类型">
+                  <el-tag :type="networkTypeTagType(currentPipe.networkType)" effect="light">{{ currentPipe.networkType }}</el-tag>
+                </el-descriptions-item>
+                <el-descriptions-item label="关联设备">
+                  <span v-if="currentPipe.equipmentId">已关联 (ID: {{ currentPipe.equipmentId }})</span>
+                  <span v-else style="color:#909399">未关联</span>
+                </el-descriptions-item>
+                <el-descriptions-item label="坐标信息" :span="2">{{ formatPoints(currentPipe.points) }}</el-descriptions-item>
+                <el-descriptions-item label="创建时间">{{ currentPipe.createTime }}</el-descriptions-item>
+                <el-descriptions-item label="更新时间">{{ currentPipe.updateTime }}</el-descriptions-item>
               </el-descriptions>
             </el-tab-pane>
 
-            <!-- Tab 3: 评估详情 -->
-            <el-tab-pane label="评估详情" name="assessDetail">
-              <!-- 评估结果卡片 -->
-              <div class="assess-result-card">
-                <div class="assess-level" :style="{ background: riskLevelColor(currentPipe.riskLevel) }">
-                  <div class="assess-level-text">{{ riskLevelText(currentPipe.riskLevel) }}</div>
-                  <div class="assess-level-score">{{ currentPipe.riskScore }} 分</div>
-                </div>
-                <div class="assess-result-info">
-                  <div class="assess-result-row">
-                    <span class="label">评估时间:</span>
-                    <span>{{ currentPipe.assessTime }}</span>
+            <!-- Tab 2: 报警统计 -->
+            <el-tab-pane label="报警统计" name="alarm">
+              <div v-if="currentPipe.equipmentId" v-loading="alarmLoading">
+                <!-- 报警统计概览 -->
+                <div class="alarm-stats-bar">
+                  <div class="alarm-stat-item">
+                    <span class="alarm-stat-value">{{ alarmTotal }}</span>
+                    <span class="alarm-stat-label">总报警数</span>
                   </div>
-                  <div class="assess-result-row">
-                    <span class="label">评估机构:</span>
-                    <span>{{ currentPipe.assessOrg }}</span>
+                  <div class="alarm-stat-item">
+                    <span class="alarm-stat-value" style="color:#F56C6C">{{ alarmUnhandled }}</span>
+                    <span class="alarm-stat-label">未处理</span>
                   </div>
-                  <div class="assess-result-row">
-                    <span class="label">下次评估:</span>
-                    <span>{{ currentPipe.nextAssessTime }}</span>
+                  <div class="alarm-stat-item">
+                    <span class="alarm-stat-value" style="color:#E6A23C">{{ alarmHandling }}</span>
+                    <span class="alarm-stat-label">处理中</span>
                   </div>
-                </div>
-              </div>
-
-              <!-- 评估指标表格 -->
-              <el-table :data="currentPipe.indicators" border style="width: 100%; margin-top: 20px;">
-                <el-table-column label="指标名称" prop="name" min-width="160" />
-                <el-table-column label="权重" prop="weight" min-width="80" align="center">
-                  <template #default="{ row }">{{ (row.weight * 100).toFixed(0) }}%</template>
-                </el-table-column>
-                <el-table-column label="评分" prop="score" min-width="80" align="center">
-                  <template #default="{ row }">
-                    <span :style="{ color: scoreColor(row.score), fontWeight: 700 }">{{ row.score }}</span>
-                  </template>
-                </el-table-column>
-                <el-table-column label="等级" prop="level" min-width="100" align="center">
-                  <template #default="{ row }">
-                    <el-tag :color="indicatorLevelColor(row.level)" effect="dark" size="small" style="border:none">{{
-                      row.level }}</el-tag>
-                  </template>
-                </el-table-column>
-              </el-table>
-            </el-tab-pane>
-
-            <!-- Tab 4: 评估分析 -->
-            <!-- <el-tab-pane label="评估分析" name="assessAnalysis">
-              <el-row :gutter="20">
-                <el-col :span="12">
-                  <el-card shadow="hover">
-                    <template #header><span style="font-weight:600">风险维度雷达图</span></template>
-                    <div ref="radarChartRef" style="height: 350px;"></div>
-                  </el-card>
-                </el-col>
-                <el-col :span="12">
-                  <el-card shadow="hover">
-                    <template #header><span style="font-weight:600">风险趋势折线图</span></template>
-                    <div ref="lineChartRef" style="height: 350px;"></div>
-                  </el-card>
-                </el-col>
-              </el-row> -->
-
-            <!-- 评估建议 -->
-            <!-- <el-card shadow="hover" style="margin-top: 20px;">
-                <template #header><span style="font-weight:600">评估建议</span></template>
-                <div class="suggest-section">
-                  <div class="suggest-row">
-                    <span class="suggest-label">当前风险等级:</span>
-                    <el-tag :color="riskLevelColor(currentPipe.riskLevel)" effect="dark" style="border:none">{{
-                      riskLevelText(currentPipe.riskLevel) }}</el-tag>
-                  </div>
-                  <div class="suggest-row">
-                    <span class="suggest-label">风险说明:</span>
-                    <span>{{ currentPipe.riskDesc }}</span>
-                  </div>
-                  <div class="suggest-row">
-                    <span class="suggest-label">建议措施:</span>
-                    <span>{{ currentPipe.suggestion }}</span>
+                  <div class="alarm-stat-item">
+                    <span class="alarm-stat-value" style="color:#67C23A">{{ alarmHandled }}</span>
+                    <span class="alarm-stat-label">已处理</span>
                   </div>
                 </div>
-              </el-card> -->
-
-            <!-- 历史评估记录 -->
-            <!-- <el-card shadow="hover" style="margin-top: 20px;">
-                <template #header><span style="font-weight:600">历史评估记录</span></template>
-                <el-table :data="currentPipe.historyRecords" border style="width: 100%">
-                  <el-table-column label="评估时间" prop="time" min-width="120" align="center" />
-                  <el-table-column label="风险评分" prop="score" min-width="100" align="center">
-                    <template #default="{ row }">
-                      <span :style="{ color: scoreColor(row.score), fontWeight: 700 }">{{ row.score }}</span>
-                    </template>
-                  </el-table-column>
-                  <el-table-column label="风险级别" prop="level" min-width="100" align="center">
+
+                <!-- 报警列表 -->
+                <el-table :data="alarmList" border style="width: 100%; margin-top: 16px;" v-if="alarmList.length > 0">
+                  <el-table-column label="序号" type="index" width="60" align="center" />
+                  <el-table-column prop="alarmTime" label="报警时间" min-width="160" />
+                  <el-table-column prop="alarmType" label="报警类型" min-width="100" />
+                  <el-table-column prop="alarmContent" label="报警内容" min-width="220" />
+                  <el-table-column prop="status" label="状态" min-width="90" align="center">
                     <template #default="{ row }">
-                      <el-tag :color="riskLevelColor(row.riskLevel)" effect="dark" size="small" style="border:none">{{
-                        riskLevelText(row.riskLevel) }}</el-tag>
+                      <el-tag :type="row.status === 0 ? 'warning' : row.status === 1 ? 'primary' : 'success'" size="small">
+                        {{ row.status === 0 ? '未处理' : row.status === 1 ? '处理中' : '已处理' }}
+                      </el-tag>
                     </template>
                   </el-table-column>
-                  <el-table-column label="评估机构" prop="org" min-width="160" />
-                  <el-table-column label="备注" prop="remark" min-width="200" />
                 </el-table>
-              </el-card>
-            </el-tab-pane> -->
+                <el-empty v-else description="暂无报警数据" :image-size="60" />
+
+                <!-- 报警分页 -->
+                <div v-if="alarmTotal > 0" class="alarm-pagination">
+                  <el-pagination
+                    v-model:current-page="alarmPageNum"
+                    v-model:page-size="alarmPageSize"
+                    :page-sizes="[10, 20, 50]"
+                    :total="alarmTotal"
+                    layout="total, sizes, prev, pager, next"
+                    small
+                    background
+                    @size-change="loadAlarmData"
+                    @current-change="loadAlarmData"
+                  />
+                </div>
+              </div>
+              <el-empty v-else description="该管网未关联设备,无报警数据" :image-size="80" />
+            </el-tab-pane>
+
+            <!-- Tab 3: 报警趋势 -->
+            <el-tab-pane label="报警趋势" name="trend">
+              <div v-if="currentPipe.equipmentId && alarmTrendData.length > 0">
+                <div ref="trendChartRef" style="height: 350px;"></div>
+              </div>
+              <el-empty v-else description="暂无报警趋势数据" :image-size="80" />
+            </el-tab-pane>
           </el-tabs>
         </template>
-        <el-empty v-else description="请选择左侧管线查看详情" :image-size="120" />
+        <el-empty v-else description="请选择左侧管网查看详情" :image-size="120" />
       </div>
     </div>
   </div>
@@ -180,287 +133,204 @@
 <script setup name="Zhfxxqfx">
 import { ref, computed, watch, nextTick, onMounted } from 'vue'
 import { useRoute } from 'vue-router'
+import { Warning } from '@element-plus/icons-vue'
 import * as echarts from 'echarts'
+import { getPipeNetworkPage, getAlarmDataPage } from '@/api/drainage'
 
 const route = useRoute()
 
-// ==================== 搜索 ====================
-const searchKey = ref({ pipeNo: '' })
-const handleSearch = () => {
-  // 搜索时自动选中第一个匹配项
-  const list = filteredList.value
-  if (list.length > 0 && (!currentPipe.value || !list.find(i => i.pipeNo === currentPipe.value.pipeNo))) {
-    selectPipe(list[0])
-  }
-}
-
-// ==================== 模拟数据 ====================
-const pipeDataList = ref([
-  {
-    pipeNo: 'GX-2024-001', pipeName: '沅陵大道雨水主干线', pipeType: '雨水管', area: '沅陵大道', road: '沅陵大道',
-    startCoord: '110.3968, 28.4521', endCoord: '110.4085, 28.4589',
-    buildYear: 2008, serviceLife: 17, manageUnit: '沅陵县排水管理中心', manager: '张建国', phone: '13874452103',
-    diameter: 800, length: 1250, depth: 2.5, material: '钢筋混凝土', slope: 2.5, flowDir: '自西向东',
-    jointType: '承插式', connectType: '柔性连接', upstreamNode: 'J-001', downstreamNode: 'J-025',
-    surroundEnv: '两侧为商业建筑,人流密集,地下管线交错,靠近沅水河岸',
-    riskScore: 87, riskLevel: 'high', assessTime: '2025-11-15',
-    assessOrg: '湖南省管网检测中心', nextAssessTime: '2026-05-15',
-    riskDesc: '该管线使用年限较长,结构性缺陷较多,部分区域存在塌陷风险,需重点关注。',
-    suggestion: '建议3个月内进行全面检修,更换受损管段;加强日常监测频率至每日1次;雨季前完成清淤疏浚。',
-    indicators: [
-      { name: '结构性缺陷评分', weight: 0.3, score: 92, level: '高风险' },
-      { name: '功能性缺陷评分', weight: 0.25, score: 85, level: '高风险' },
-      { name: '环境影响评分', weight: 0.2, score: 78, level: '中风险' },
-      { name: '运行状态评分', weight: 0.15, score: 88, level: '高风险' },
-      { name: '历史事故评分', weight: 0.1, score: 95, level: '高风险' },
-      { name: '综合评分', weight: 1.0, score: 87, level: '高风险' }
-    ],
-    historyRecords: [
-      { time: '2025-11-15', score: 87, riskLevel: 'high', org: '湖南省管网检测中心', remark: '结构性缺陷加剧' },
-      { time: '2025-08-20', score: 82, riskLevel: 'high', org: '沅陵县排水管理中心', remark: '雨季检测发现新增裂缝' },
-      { time: '2025-05-10', score: 75, riskLevel: 'medium', org: '湖南省管网检测中心', remark: '常规评估' },
-      { time: '2025-01-15', score: 70, riskLevel: 'medium', org: '沅陵县排水管理中心', remark: '年度评估' }
-    ]
-  },
-  {
-    pipeNo: 'GX-2024-002', pipeName: '城北路段污水管线', pipeType: '污水管', area: '城北路段', road: '城北路',
-    startCoord: '110.3912, 28.4567', endCoord: '110.3998, 28.4601',
-    buildYear: 2012, serviceLife: 13, manageUnit: '沅陵县排水管理中心', manager: '李明辉', phone: '13974438921',
-    diameter: 600, length: 980, depth: 3.1, material: 'PE', slope: 3.0, flowDir: '自北向南',
-    jointType: '热熔连接', connectType: '刚性连接', upstreamNode: 'J-030', downstreamNode: 'J-048',
-    surroundEnv: '居民区密集,沿线有学校和医院,交通量较大',
-    riskScore: 72, riskLevel: 'medium', assessTime: '2025-11-10',
-    assessOrg: '湖南省管网检测中心', nextAssessTime: '2026-05-10',
-    riskDesc: '该管线存在功能性缺陷,部分管段渗漏较严重,需加强维护。',
-    suggestion: '建议6个月内进行渗漏点修复;增加CCTV检测频率至每季度1次;关注周边施工影响。',
-    indicators: [
-      { name: '结构性缺陷评分', weight: 0.3, score: 68, level: '中风险' },
-      { name: '功能性缺陷评分', weight: 0.25, score: 80, level: '中风险' },
-      { name: '环境影响评分', weight: 0.2, score: 65, level: '低风险' },
-      { name: '运行状态评分', weight: 0.15, score: 75, level: '中风险' },
-      { name: '历史事故评分', weight: 0.1, score: 70, level: '中风险' },
-      { name: '综合评分', weight: 1.0, score: 72, level: '中风险' }
-    ],
-    historyRecords: [
-      { time: '2025-11-10', score: 72, riskLevel: 'medium', org: '湖南省管网检测中心', remark: '渗漏加剧' },
-      { time: '2025-07-15', score: 65, riskLevel: 'medium', org: '沅陵县排水管理中心', remark: '常规评估' },
-      { time: '2025-03-20', score: 58, riskLevel: 'medium', org: '湖南省管网检测中心', remark: '发现渗漏点' }
-    ]
-  },
-  {
-    pipeNo: 'GX-2024-003', pipeName: '河西片区合流管线', pipeType: '合流管', area: '河西片区', road: '河西大道',
-    startCoord: '110.3856, 28.4489', endCoord: '110.3978, 28.4534',
-    buildYear: 2015, serviceLife: 10, manageUnit: '沅陵县排水管理中心', manager: '王志强', phone: '13787456210',
-    diameter: 1000, length: 1560, depth: 2.8, material: 'PVC', slope: 2.0, flowDir: '自南向北',
-    jointType: '胶圈承插', connectType: '柔性连接', upstreamNode: 'J-050', downstreamNode: 'J-075',
-    surroundEnv: '新开发区,建筑密度中等,绿化带较多',
-    riskScore: 45, riskLevel: 'low', assessTime: '2025-11-08',
-    assessOrg: '湖南省管网检测中心', nextAssessTime: '2026-11-08',
-    riskDesc: '管线整体状况良好,个别节点存在轻微功能性问题,风险可控。',
-    suggestion: '建议12个月内进行常规检修维护;保持现有监测频率;重点关注雨季排水情况。',
-    indicators: [
-      { name: '结构性缺陷评分', weight: 0.3, score: 40, level: '低风险' },
-      { name: '功能性缺陷评分', weight: 0.25, score: 48, level: '低风险' },
-      { name: '环境影响评分', weight: 0.2, score: 42, level: '低风险' },
-      { name: '运行状态评分', weight: 0.15, score: 50, level: '低风险' },
-      { name: '历史事故评分', weight: 0.1, score: 45, level: '低风险' },
-      { name: '综合评分', weight: 1.0, score: 45, level: '低风险' }
-    ],
-    historyRecords: [
-      { time: '2025-11-08', score: 45, riskLevel: 'low', org: '湖南省管网检测中心', remark: '年度评估' },
-      { time: '2025-05-12', score: 42, riskLevel: 'low', org: '沅陵县排水管理中心', remark: '常规评估' }
-    ]
-  },
-  {
-    pipeNo: 'GX-2024-004', pipeName: '人民路雨水管线', pipeType: '雨水管', area: '人民路', road: '人民路',
-    startCoord: '110.4001, 28.4502', endCoord: '110.4076, 28.4556',
-    buildYear: 2005, serviceLife: 20, manageUnit: '沅陵县排水管理中心', manager: '陈伟华', phone: '13687451230',
-    diameter: 500, length: 870, depth: 2.2, material: '铸铁', slope: 3.5, flowDir: '自东向西',
-    jointType: '法兰连接', connectType: '刚性连接', upstreamNode: 'J-080', downstreamNode: 'J-095',
-    surroundEnv: '老城区,道路狭窄,两侧建筑老旧,地下管线复杂',
-    riskScore: 91, riskLevel: 'high', assessTime: '2025-11-05',
-    assessOrg: '湖南省管网检测中心', nextAssessTime: '2026-02-05',
-    riskDesc: '该管线使用年限长,管材老化严重,多处存在腐蚀穿孔风险,需紧急处理。',
-    suggestion: '建议1个月内启动应急检修;制定管线更换方案;加密监测至每4小时1次;对高风险段实施临时加固。',
-    indicators: [
-      { name: '结构性缺陷评分', weight: 0.3, score: 95, level: '高风险' },
-      { name: '功能性缺陷评分', weight: 0.25, score: 90, level: '高风险' },
-      { name: '环境影响评分', weight: 0.2, score: 85, level: '高风险' },
-      { name: '运行状态评分', weight: 0.15, score: 92, level: '高风险' },
-      { name: '历史事故评分', weight: 0.1, score: 98, level: '高风险' },
-      { name: '综合评分', weight: 1.0, score: 91, level: '高风险' }
-    ],
-    historyRecords: [
-      { time: '2025-11-05', score: 91, riskLevel: 'high', org: '湖南省管网检测中心', remark: '管材腐蚀严重' },
-      { time: '2025-09-10', score: 85, riskLevel: 'high', org: '沅陵县排水管理中心', remark: '发现渗漏隐患' },
-      { time: '2025-06-18', score: 78, riskLevel: 'high', org: '湖南省管网检测中心', remark: '老化评估' },
-      { time: '2025-03-05', score: 72, riskLevel: 'medium', org: '沅陵县排水管理中心', remark: '常规评估' }
-    ]
-  },
-  {
-    pipeNo: 'GX-2024-005', pipeName: '南环路污水支线', pipeType: '污水管', area: '南环路', road: '南环路',
-    startCoord: '110.3945, 28.4432', endCoord: '110.4012, 28.4478',
-    buildYear: 2020, serviceLife: 5, manageUnit: '沅陵县排水管理中心', manager: '刘志远', phone: '13587412306',
-    diameter: 400, length: 640, depth: 1.9, material: 'PE', slope: 4.0, flowDir: '自南向北',
-    jointType: '热熔连接', connectType: '刚性连接', upstreamNode: 'J-100', downstreamNode: 'J-112',
-    surroundEnv: '新建住宅区,配套设施完善,绿化良好',
-    riskScore: 38, riskLevel: 'veryLow', assessTime: '2025-11-02',
-    assessOrg: '湖南省管网检测中心', nextAssessTime: '2026-11-02',
-    riskDesc: '管线状况优良,运行稳定,各项指标均在安全范围内。',
-    suggestion: '维持现有监测频率;定期进行常规巡检;做好预防性维护即可。',
-    indicators: [
-      { name: '结构性缺陷评分', weight: 0.3, score: 30, level: '极低风险' },
-      { name: '功能性缺陷评分', weight: 0.25, score: 35, level: '极低风险' },
-      { name: '环境影响评分', weight: 0.2, score: 40, level: '低风险' },
-      { name: '运行状态评分', weight: 0.15, score: 42, level: '低风险' },
-      { name: '历史事故评分', weight: 0.1, score: 45, level: '低风险' },
-      { name: '综合评分', weight: 1.0, score: 38, level: '极低风险' }
-    ],
-    historyRecords: [
-      { time: '2025-11-02', score: 38, riskLevel: 'veryLow', org: '湖南省管网检测中心', remark: '年度评估' },
-      { time: '2025-05-20', score: 35, riskLevel: 'veryLow', org: '沅陵县排水管理中心', remark: '常规评估' }
-    ]
-  }
-])
+// ==================== 选项 ====================
+const networkTypeOptions = ['管线', '管点', '排口']
 
-// ==================== 过滤与选中 ====================
-const filteredList = computed(() => {
-  if (!searchKey.value.pipeNo) return pipeDataList.value
-  return pipeDataList.value.filter(i => i.pipeNo.includes(searchKey.value.pipeNo))
-})
+// ==================== 搜索 ====================
+const searchKey = ref({ name: '', networkType: '管线' })
+const handleSearch = () => { loadPipeList() }
 
+// ==================== 管网列表 ====================
+const pipeList = ref([])
+const listLoading = ref(false)
 const currentPipe = ref(null)
 const activeTab = ref('basic')
 
+const loadPipeList = async () => {
+  listLoading.value = true
+  try {
+    const params = {}
+    if (searchKey.value.name) params.name = searchKey.value.name
+    if (searchKey.value.networkType) params.networkType = searchKey.value.networkType
+
+    // 加载较多数据用于左侧列表
+    const res = await getPipeNetworkPage(1, 200, params)
+    const pageData = res.code !== undefined ? res.data : res
+    const rows = pageData.records || pageData.rows || []
+
+    // 批量查询报警数
+    const withAlarms = await Promise.all(
+      rows.map(async (item) => {
+        let alarmCount = 0
+        if (item.equipmentId) {
+          try {
+            const alarmRes = await getAlarmDataPage(1, 1, { equipmentId: item.equipmentId, equipmentType: 'drainage' })
+            const alarmPageData = alarmRes.code !== undefined ? alarmRes.data : alarmRes
+            alarmCount = alarmPageData.total ?? 0
+          } catch (e) { /* ignore */ }
+        }
+        return { ...item, _alarmCount: alarmCount }
+      })
+    )
+    pipeList.value = withAlarms
+
+    // 如果有路由参数指定ID,自动选中
+    const queryId = route.query.id
+    if (queryId) {
+      const found = withAlarms.find(i => String(i.id) === String(queryId))
+      if (found) selectPipe(found)
+    } else if (withAlarms.length > 0 && !currentPipe.value) {
+      selectPipe(withAlarms[0])
+    }
+  } catch (e) {
+    console.error('加载管网列表失败', e)
+    pipeList.value = []
+  } finally {
+    listLoading.value = false
+  }
+}
+
 const selectPipe = (item) => {
   currentPipe.value = item
   activeTab.value = 'basic'
+  // 重置报警数据
+  alarmList.value = []
+  alarmTotal.value = 0
+  alarmPageNum.value = 1
+  alarmTrendData.value = []
 }
 
-// ==================== 工具函数 ====================
-const pipeTypeTagType = (type) => {
-  const map = { '雨水管': '', '污水管': 'warning', '合流管': 'success', '燃气管': 'danger' }
-  return map[type] || ''
-}
-
-const riskLevelColor = (level) => {
-  const map = { high: '#F56C6C', medium: '#E6A23C', low: '#409EFF', veryLow: '#67C23A' }
-  return map[level] || '#909399'
-}
-
-const riskLevelText = (level) => {
-  const map = { high: '高风险', medium: '中风险', low: '低风险', veryLow: '极低风险' }
-  return map[level] || '未知'
-}
-
-const scoreColor = (score) => {
-  if (score >= 80) return '#F56C6C'
-  if (score >= 60) return '#E6A23C'
-  if (score >= 40) return '#409EFF'
-  return '#67C23A'
-}
-
-const indicatorLevelColor = (level) => {
-  const map = { '高风险': '#F56C6C', '中风险': '#E6A23C', '低风险': '#409EFF', '极低风险': '#67C23A' }
-  return map[level] || '#909399'
+// ==================== 报警数据 ====================
+const alarmLoading = ref(false)
+const alarmList = ref([])
+const alarmTotal = ref(0)
+const alarmPageNum = ref(1)
+const alarmPageSize = ref(10)
+
+const alarmUnhandled = computed(() => alarmList.value.filter(i => i.status === 0).length)
+const alarmHandling = computed(() => alarmList.value.filter(i => i.status === 1).length)
+const alarmHandled = computed(() => alarmList.value.filter(i => i.status === 2).length)
+
+const loadAlarmData = async () => {
+  if (!currentPipe.value?.equipmentId) return
+  alarmLoading.value = true
+  try {
+    const res = await getAlarmDataPage(alarmPageNum.value, alarmPageSize.value, {
+      equipmentId: currentPipe.value.equipmentId,
+      equipmentType: 'drainage'
+    })
+    const pageData = res.code !== undefined ? res.data : res
+    alarmList.value = pageData.records || pageData.rows || []
+    alarmTotal.value = pageData.total ?? 0
+  } catch (e) {
+    alarmList.value = []
+    alarmTotal.value = 0
+  } finally {
+    alarmLoading.value = false
+  }
 }
 
-// ==================== ECharts ====================
-const radarChartRef = ref(null)
-const lineChartRef = ref(null)
-let radarChart = null
-let lineChart = null
-
-const initRadarChart = () => {
-  if (!radarChartRef.value || !currentPipe.value) return
-  radarChart = echarts.init(radarChartRef.value)
-  const indicators = currentPipe.value.indicators.slice(0, 5).map(i => ({
-    name: i.name.replace('评分', ''),
-    max: 100
-  }))
-  radarChart.setOption({
-    tooltip: { trigger: 'item' },
-    radar: {
-      indicator: indicators,
-      shape: 'polygon',
-      splitNumber: 5,
-      axisName: { color: '#606266', fontSize: 12 },
-      splitArea: { areaStyle: { color: ['rgba(64,158,255,0.05)', 'rgba(64,158,255,0.1)'] } }
-    },
-    series: [{
-      type: 'radar',
-      data: [{
-        value: currentPipe.value.indicators.slice(0, 5).map(i => i.score),
-        name: '风险评分',
-        areaStyle: { color: 'rgba(245,108,108,0.2)' },
-        lineStyle: { color: '#F56C6C', width: 2 },
-        itemStyle: { color: '#F56C6C' }
-      }]
-    }]
-  })
+// ==================== 报警趋势 ====================
+const alarmTrendData = ref([])
+const trendChartRef = ref(null)
+let trendChart = null
+
+const loadAlarmTrend = async () => {
+  if (!currentPipe.value?.equipmentId) return
+  try {
+    // 加载较多报警数据用于趋势分析
+    const res = await getAlarmDataPage(1, 500, {
+      equipmentId: currentPipe.value.equipmentId,
+      equipmentType: 'drainage'
+    })
+    const trendPageData = res.code !== undefined ? res.data : res
+    const rows = trendPageData.records || trendPageData.rows || []
+    // 按日期聚合报警数
+    const dateMap = {}
+    rows.forEach(item => {
+      const date = (item.alarmTime || '').substring(0, 10)
+      if (date) {
+        dateMap[date] = (dateMap[date] || 0) + 1
+      }
+    })
+    alarmTrendData.value = Object.entries(dateMap)
+      .sort((a, b) => a[0].localeCompare(b[0]))
+      .map(([date, count]) => ({ date, count }))
+  } catch (e) {
+    alarmTrendData.value = []
+  }
 }
 
-const initLineChart = () => {
-  if (!lineChartRef.value || !currentPipe.value) return
-  lineChart = echarts.init(lineChartRef.value)
-  const records = [...currentPipe.value.historyRecords].reverse()
-  lineChart.setOption({
+const initTrendChart = () => {
+  if (!trendChartRef.value || alarmTrendData.value.length === 0) return
+  if (trendChart) { trendChart.dispose(); trendChart = null }
+  trendChart = echarts.init(trendChartRef.value)
+  trendChart.setOption({
     tooltip: { trigger: 'axis' },
     grid: { left: 50, right: 30, top: 30, bottom: 30 },
     xAxis: {
       type: 'category',
-      data: records.map(r => r.time),
-      axisLabel: { fontSize: 11, color: '#909399' }
+      data: alarmTrendData.value.map(d => d.date),
+      axisLabel: { fontSize: 11, color: '#909399', rotate: 30 }
     },
     yAxis: {
       type: 'value',
-      min: 0, max: 100,
+      minInterval: 1,
       axisLabel: { color: '#909399' },
       splitLine: { lineStyle: { type: 'dashed' } }
     },
     series: [{
-      type: 'line',
-      data: records.map(r => r.score),
-      smooth: true,
-      symbol: 'circle',
-      symbolSize: 8,
-      lineStyle: { color: '#E6A23C', width: 2 },
-      itemStyle: { color: '#E6A23C' },
-      areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [{ offset: 0, color: 'rgba(230,162,60,0.3)' }, { offset: 1, color: 'rgba(230,162,60,0.02)' }] } },
-      markLine: {
-        silent: true,
-        lineStyle: { type: 'dashed', color: '#F56C6C' },
-        data: [{ yAxis: 80, label: { formatter: '高风险线', color: '#F56C6C', fontSize: 11 } }]
-      }
+      type: 'bar',
+      data: alarmTrendData.value.map(d => d.count),
+      itemStyle: { color: '#E6A23C', borderRadius: [4, 4, 0, 0] },
+      barMaxWidth: 30
     }]
   })
 }
 
-// 监听Tab切换到评估分析时初始化图表
+// ==================== 监听Tab切换 ====================
 watch(activeTab, (val) => {
-  if (val === 'assessAnalysis' && currentPipe.value) {
-    nextTick(() => {
-      initRadarChart()
-      initLineChart()
-    })
+  if (val === 'alarm' && currentPipe.value?.equipmentId) {
+    loadAlarmData()
+  }
+  if (val === 'trend' && currentPipe.value?.equipmentId) {
+    loadAlarmTrend().then(() => nextTick(() => initTrendChart()))
   }
 })
 
-// 监听选中管线变化时销毁旧图表
+// 监听选中管网变化时销毁图表
 watch(currentPipe, () => {
-  if (radarChart) { radarChart.dispose(); radarChart = null }
-  if (lineChart) { lineChart.dispose(); lineChart = null }
+  if (trendChart) { trendChart.dispose(); trendChart = null }
 })
 
-// 从路由参数中获取管线编号
-onMounted(() => {
-  const pipeNo = route.query.pipeNo
-  if (pipeNo) {
-    const found = pipeDataList.value.find(i => i.pipeNo === pipeNo)
-    if (found) selectPipe(found)
-  } else if (pipeDataList.value.length > 0) {
-    selectPipe(pipeDataList.value[0])
+// ==================== 工具函数 ====================
+const networkTypeTagType = (type) => {
+  const map = { '管线': '', '管点': 'warning', '排口': 'success' }
+  return map[type] || ''
+}
+
+const formatPoints = (points) => {
+  if (!points) return '暂无坐标'
+  try {
+    const parsed = typeof points === 'string' ? JSON.parse(points) : points
+    if (Array.isArray(parsed)) {
+      return parsed.map(p => `${p.lng || p[0]}, ${p.lat || p[1]}`).join(' → ')
+    }
+    return String(points)
+  } catch {
+    return String(points)
   }
+}
+
+// ==================== 初始化 ====================
+onMounted(() => {
+  loadPipeList()
 })
 </script>
 
@@ -524,12 +394,6 @@ onMounted(() => {
   border-color: #409EFF;
 }
 
-.pipe-item-no {
-  font-size: 13px;
-  color: #909399;
-  margin-bottom: 2px;
-}
-
 .pipe-item-name {
   font-size: 14px;
   color: #303133;
@@ -537,75 +401,64 @@ onMounted(() => {
   margin-bottom: 6px;
 }
 
+.pipe-item-meta {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 4px;
+}
+
+.pipe-item-area {
+  font-size: 12px;
+  color: #909399;
+}
+
+.pipe-item-alarm {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  font-size: 12px;
+  color: #F56C6C;
+  margin-top: 4px;
+}
+
 /* 右侧面板 */
 .right-panel {
   flex: 1;
   min-width: 0;
 }
 
-/* 评估结果卡片 */
-.assess-result-card {
+/* 报警统计栏 */
+.alarm-stats-bar {
   display: flex;
-  align-items: center;
   gap: 24px;
-  padding: 20px;
+  padding: 16px 20px;
   background: #f5f7fa;
   border-radius: 8px;
 }
 
-.assess-level {
-  width: 120px;
-  height: 90px;
-  border-radius: 8px;
+.alarm-stat-item {
   display: flex;
   flex-direction: column;
   align-items: center;
-  justify-content: center;
-  color: #fff;
-  flex-shrink: 0;
 }
 
-.assess-level-text {
-  font-size: 16px;
-  font-weight: 600;
-}
-
-.assess-level-score {
+.alarm-stat-value {
   font-size: 24px;
   font-weight: 700;
-  margin-top: 4px;
-}
-
-.assess-result-info {
-  flex: 1;
-}
-
-.assess-result-row {
-  font-size: 14px;
-  color: #606266;
-  margin-bottom: 8px;
+  color: #303133;
 }
 
-.assess-result-row .label {
+.alarm-stat-label {
+  font-size: 13px;
   color: #909399;
-  margin-right: 8px;
-}
-
-/* 评估建议 */
-.suggest-section {
-  padding: 4px 0;
-}
-
-.suggest-row {
-  font-size: 14px;
-  color: #606266;
-  margin-bottom: 12px;
-  line-height: 1.6;
+  margin-top: 4px;
 }
 
-.suggest-label {
-  color: #909399;
-  margin-right: 8px;
-  white-space: nowrap;
+/* 报警分页 */
+.alarm-pagination {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 12px;
 }
 </style>

+ 107 - 227
src/views/subSystem/drainage/jcbj/bjqd.vue

@@ -3,19 +3,19 @@
     <!-- 搜索区 -->
     <el-form :model="queryParams" ref="queryRef" :inline="true" class="search-form">
       <el-form-item label="设备名称">
-        <el-input v-model="queryParams.equipmentName" placeholder="请输入设备名称" clearable style="width: 180px" />
+        <el-input v-model="queryParams.deviceName" placeholder="请输入设备名称" clearable style="width: 180px" />
       </el-form-item>
-      <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-form-item label="告警等级">
+        <el-select v-model="queryParams.alarmLevel" placeholder="请选择" clearable style="width: 160px">
+          <el-option label="最高" :value="1" />
+          <el-option label="高" :value="2" />
+          <el-option label="中" :value="3" />
+          <el-option label="低" :value="4" />
         </el-select>
       </el-form-item>
       <el-form-item label="处理状态">
-        <el-select v-model="queryParams.status" placeholder="请选择" clearable style="width: 140px">
-          <el-option label="未下派" :value="-1" />
-          <el-option label="待处理" :value="0" />
+        <el-select v-model="queryParams.alarmStatus" placeholder="请选择" clearable style="width: 140px">
+          <el-option label="未处理" :value="0" />
           <el-option label="已处理" :value="1" />
         </el-select>
       </el-form-item>
@@ -38,22 +38,30 @@
 
     <!-- 数据表格 -->
     <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">
+      <el-table-column label="设备名称" prop="deviceName" min-width="130" show-overflow-tooltip />
+      <el-table-column label="设备编码" prop="deviceCode" min-width="120" show-overflow-tooltip />
+      <el-table-column label="设备类型" prop="deviceType" min-width="100" show-overflow-tooltip />
+      <el-table-column label="预警类型" prop="warningType" min-width="100" show-overflow-tooltip />
+      <el-table-column label="实际值" prop="actualValue" min-width="80" align="center" />
+      <el-table-column label="阈值范围" min-width="130" align="center">
         <template #default="{ row }">
-          <span :class="levelClass(row.warningLevel)">{{ levelText(row.warningLevel) }}</span>
+          {{ row.minValue ?? '-' }} ~ {{ row.maxValue ?? '-' }}
+        </template>
+      </el-table-column>
+      <el-table-column label="告警等级" min-width="90" align="center">
+        <template #default="{ row }">
+          <span :class="levelClass(row.alarmLevel)">{{ levelText(row.alarmLevel) }}</span>
         </template>
       </el-table-column>
-      <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">{{ statusText(row.status) }}</el-tag>
+          <el-tag :type="statusTagType(row.alarmStatus)" size="small" effect="plain">{{ statusText(row.alarmStatus) }}</el-tag>
         </template>
       </el-table-column>
-      <el-table-column label="操作" width="80" align="center">
+      <el-table-column label="报警时间" prop="alarmTime" min-width="160" />
+      <el-table-column label="处理人" prop="handleUser" min-width="90" show-overflow-tooltip />
+      <el-table-column label="处理时间" prop="handleTime" min-width="160" />
+      <el-table-column label="操作" width="80" align="center" fixed="right">
         <template #default="{ row }">
           <el-button link type="primary" @click="handleView(row)">查看</el-button>
         </template>
@@ -86,22 +94,50 @@
               <el-col :span="12">
                 <div class="info-item">
                   <span class="info-label">设备名称</span>
-                  <span class="info-value">{{ currentRow.equipmentName }}</span>
+                  <span class="info-value">{{ currentRow.deviceName || '-' }}</span>
                 </div>
               </el-col>
               <el-col :span="12">
                 <div class="info-item">
-                  <span class="info-label">报警时间</span>
-                  <span class="info-value">{{ currentRow.warningTime }}</span>
+                  <span class="info-label">设备编码</span>
+                  <span class="info-value">{{ currentRow.deviceCode || '-' }}</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.deviceType || '-' }}</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>
             <el-row :gutter="16">
               <el-col :span="12">
                 <div class="info-item">
-                  <span class="info-label">报警等级</span>
+                  <span class="info-label">实际值</span>
+                  <span class="info-value">{{ currentRow.actualValue ?? '-' }}</span>
+                </div>
+              </el-col>
+              <el-col :span="12">
+                <div class="info-item">
+                  <span class="info-label">阈值范围</span>
+                  <span class="info-value">{{ currentRow.minValue ?? '-' }} ~ {{ currentRow.maxValue ?? '-' }}</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">
-                    <span :class="levelClass(currentRow.warningLevel)">{{ levelText(currentRow.warningLevel) }}</span>
+                    <span :class="levelClass(currentRow.alarmLevel)">{{ levelText(currentRow.alarmLevel) }}</span>
                   </span>
                 </div>
               </el-col>
@@ -109,7 +145,7 @@
                 <div class="info-item">
                   <span class="info-label">处理状态</span>
                   <span class="info-value">
-                    <el-tag :type="statusTagType(currentRow.status)" size="small" effect="plain">{{ statusText(currentRow.status) }}</el-tag>
+                    <el-tag :type="statusTagType(currentRow.alarmStatus)" size="small" effect="plain">{{ statusText(currentRow.alarmStatus) }}</el-tag>
                   </span>
                 </div>
               </el-col>
@@ -117,61 +153,57 @@
             <el-row :gutter="16">
               <el-col :span="12">
                 <div class="info-item">
-                  <span class="info-label">当前值</span>
-                  <span class="info-value">{{ currentRow.currentValue || '-' }}</span>
+                  <span class="info-label">报警时间</span>
+                  <span class="info-value">{{ currentRow.alarmTime || '-' }}</span>
                 </div>
               </el-col>
               <el-col :span="12">
                 <div class="info-item">
-                  <span class="info-label">阈值</span>
-                  <span class="info-value">{{ currentRow.thresholdValue || '-' }}</span>
+                  <span class="info-label">偏离比率</span>
+                  <span class="info-value">{{ currentRow.deviationRatio ?? '-' }}</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.location }}</span>
+                  <span class="info-label">处理人</span>
+                  <span class="info-value">{{ currentRow.handleUser || '-' }}</span>
                 </div>
               </el-col>
               <el-col :span="12">
                 <div class="info-item">
-                  <span class="info-label">报警类型</span>
-                  <span class="info-value">{{ currentRow.warningType || '-' }}</span>
+                  <span class="info-label">处理时间</span>
+                  <span class="info-value">{{ currentRow.handleTime || '-' }}</span>
                 </div>
               </el-col>
             </el-row>
-            <div class="info-item desc-item">
-              <span class="info-label">描述</span>
-              <span class="info-value desc-text">{{ currentRow.content }}</span>
+            <div class="info-item desc-item" v-if="currentRow.handleRemark">
+              <span class="info-label">处理备注</span>
+              <span class="info-value desc-text">{{ currentRow.handleRemark }}</span>
+            </div>
+            <div class="info-item desc-item" v-if="currentRow.remark">
+              <span class="info-label">备注</span>
+              <span class="info-value desc-text">{{ currentRow.remark }}</span>
             </div>
           </div>
         </div>
       </div>
-
-
     </el-dialog>
   </div>
 </template>
 
 <script setup name="Bjqd">
-import { ref, computed, onMounted } from 'vue'
-import { ElMessage, ElMessageBox } from 'element-plus'
+import { ref, onMounted } from 'vue'
+import { ElMessage } 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'))
+import { getAlarmDataPage } from '@/api/drainage'
 
 // ==================== 搜索参数 ====================
 const queryParams = ref({
-  equipmentName: '',
-  warningLevel: '',
-  status: '',
+  deviceName: '',
+  alarmLevel: '',
+  alarmStatus: '',
   dateRange: []
 })
 const queryRef = ref(null)
@@ -182,52 +214,31 @@ function handleQuery() {
 }
 
 function resetQuery() {
-  queryParams.value = { equipmentName: '', warningLevel: '', status: '', dateRange: [] }
+  queryParams.value = { deviceName: '', alarmLevel: '', alarmStatus: '', dateRange: [] }
   pageNum.value = 1
   loadData()
 }
 
-// ==================== 等级/状态映射 ====================
-const levelMap = { 1: '严重', 2: '重要', 3: '一般' }
-const statusMap = { '-1': '未下派', 0: '待处理', 1: '已处理' }
+// ==================== 等级/状态映射(alarm_data表) ====================
+const levelMap = { 1: '最高', 2: '高', 3: '中', 4: '低' }
+const statusMap = { 0: '未处理', 1: '已处理' }
 
 function levelText(level) {
   return levelMap[level] || '-'
 }
 function levelClass(level) {
-  const map = { 1: 'level-critical', 2: 'level-major', 3: 'level-minor' }
+  const map = { 1: 'level-critical', 2: 'level-major', 3: 'level-minor', 4: 'level-low' }
   return map[level] || ''
 }
 function statusText(status) {
   return statusMap[status] ?? '-'
 }
 function statusTagType(status) {
-  const map = { '-1': 'info', 0: 'warning', 1: 'success' }
+  const map = { 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)
@@ -237,62 +248,19 @@ 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)
+    const params = { equipmentType: 'drainage' }
+    if (queryParams.value.deviceName) params.deviceName = queryParams.value.deviceName
+    if (queryParams.value.alarmLevel !== '' && queryParams.value.alarmLevel !== null) params.alarmLevel = queryParams.value.alarmLevel
+    if (queryParams.value.alarmStatus !== '' && queryParams.value.alarmStatus !== null) params.alarmStatus = queryParams.value.alarmStatus
+    if (queryParams.value.dateRange && queryParams.value.dateRange.length === 2) {
+      params.startTime = queryParams.value.dateRange[0]
+      params.endTime = queryParams.value.dateRange[1]
     }
 
-    allMergedData.value = filtered
-    total.value = allMergedData.value.length
-    const start = (pageNum.value - 1) * pageSize.value
-    tableData.value = allMergedData.value.slice(start, start + pageSize.value)
+    const res = await getAlarmDataPage(pageNum.value, pageSize.value, params)
+    const pageData = res.code !== undefined ? res.data : res
+    tableData.value = pageData.records || pageData.rows || []
+    total.value = pageData.total || 0
   } catch (error) {
     console.error('加载报警清单失败', error)
     ElMessage.error('加载报警清单失败')
@@ -302,52 +270,20 @@ async function loadData() {
 }
 
 function handlePageChange() {
-  const start = (pageNum.value - 1) * pageSize.value
-  tableData.value = allMergedData.value.slice(start, start + pageSize.value)
+  loadData()
 }
 
 function handleSizeChange() {
   pageNum.value = 1
-  tableData.value = allMergedData.value.slice(0, pageSize.value)
+  loadData()
 }
 
 // ==================== 查看详情 ====================
 const dialogVisible = ref(false)
 const currentRow = ref({})
-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)
-  }
-}
 
-async function handleView(row) {
+function handleView(row) {
   currentRow.value = { ...row }
-  dispatchUserId.value = ''
   dialogVisible.value = true
 }
 
@@ -355,69 +291,8 @@ function handleClose() {
   dialogVisible.value = false
 }
 
-async function handleDispatch() {
-  if (!dispatchUserId.value) {
-    ElMessage.warning('请选择下派人员')
-    return
-  }
-  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>
@@ -458,6 +333,11 @@ onMounted(() => {
   font-weight: 600;
 }
 
+.level-low {
+  color: #67c238;
+  font-weight: 600;
+}
+
 /* 详情弹窗 */
 .detail-panel {
   padding: 0 4px;

+ 31 - 95
src/views/subSystem/drainage/jcbj/bjsh.vue

@@ -197,7 +197,7 @@
 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 { getAlarmDataPage, handleAlarm } from '@/api/drainage'
 import useUserStore from '@/store/modules/user'
 
 // ==================== 用户信息 ====================
@@ -253,67 +253,29 @@ const loading = ref(false)
 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
+    // 调用 alarm_data 真实接口,只取未处理报警(alarmStatus=0)
+    const res = await getAlarmDataPage(1, 200, { equipmentType: 'drainage', alarmStatus: 0 })
+    const pageData = res.code !== undefined ? res.data : res
+    const records = pageData.records || pageData.rows || []
+    let tableData_local = records.map(row => ({
+      id: row.id,
+      equipmentId: row.deviceCode,
+      equipmentName: row.deviceName || '-',
+      warningTime: row.alarmTime,
+      warningLevel: row.alarmLevel,
+      content: row.warningType || '-',
+      location: '-',
+      longitude: row.longitude ? parseFloat(row.longitude) : null,
+      latitude: row.latitude ? parseFloat(row.latitude) : null,
+      currentValue: row.actualValue,
+      thresholdValue: row.maxValue,
+      warningType: row.warningType,
+      status: 1,  // 未处理(alarmStatus=0)在审核页面显示为"待审核"
+      source: 'alarm_data'
+    }))
+
+    // 前端筛选(equipmentName/warningLevel/status)
+    let filtered = tableData_local
     if (queryParams.value.equipmentName) {
       filtered = filtered.filter(item => item.equipmentName.includes(queryParams.value.equipmentName))
     }
@@ -401,41 +363,15 @@ async function submitAudit() {
   }
   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
-      })
+    // 调用 handleAlarm 真实接口处理报警
+    const res = await handleAlarm(currentAlarm.value.id, userStore.name || 'admin', auditForm.value.opinion || auditForm.value.result)
+    if (res.code === 200 || res.code === 0) {
+      ElMessage.success('审核提交成功')
+      auditVisible.value = false
+      loadData()
     } 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.error(res.msg || '审核提交失败')
     }
-
-    ElMessage.success('审核提交成功')
-    closeAudit()
-    loadData()
   } catch (error) {
     console.error('审核提交失败', error)
     ElMessage.error('审核提交失败')

+ 47 - 39
src/views/subSystem/drainage/jcbj/bjtx.vue

@@ -25,27 +25,28 @@
           v-for="item in alarmList"
           :key="item.id"
           class="alarm-card"
-          :class="'level-' + item.level"
+          :class="'level-' + item.alarmLevelText"
         >
           <div class="card-left">
-            <span class="level-icon" :class="'icon-' + item.level">
-              {{ item.level === '严重' ? '!!' : item.level === '重要' ? '!' : 'i' }}
+            <span class="level-icon" :class="'icon-' + item.alarmLevelText">
+              {{ item.alarmLevelText === '最高' ? '!!' : item.alarmLevelText === '高' ? '!' : 'i' }}
             </span>
           </div>
           <div class="card-body">
             <div class="card-row row-1">
-              <span class="alarm-time">{{ item.time }}</span>
-              <span class="alarm-level-tag" :class="'tag-' + item.level">{{ item.level }}</span>
+              <span class="alarm-time">{{ item.alarmTime || '-' }}</span>
+              <span class="alarm-level-tag" :class="'tag-' + item.alarmLevelText">{{ item.alarmLevelText }}</span>
             </div>
             <div class="card-row row-2">
-              <span class="device-name">{{ item.device }}</span>
+              <span class="device-name">{{ item.deviceName || '-' }}</span>
+              <span class="device-code">{{ item.deviceCode || '' }}</span>
             </div>
             <div class="card-row row-3">
-              <span class="alarm-metric">{{ item.metric }}</span>
+              <span class="alarm-metric">预警类型: {{ item.warningType || '-' }} | 实际值: {{ item.actualValue ?? '-' }}</span>
             </div>
             <div class="card-row row-4">
               <el-icon :size="14"><Location /></el-icon>
-              <span class="alarm-location">{{ item.location }}</span>
+              <span class="alarm-location">设备类型: {{ item.deviceType || '-' }} | 状态: {{ item.alarmStatus === 0 ? '未处理' : '已处理' }}</span>
             </div>
           </div>
         </div>
@@ -57,7 +58,7 @@
 <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 { getAlarmDataPage } from '@/api/drainage'
 import useUserStore from '@/store/modules/user'
 
 // ==================== 用户信息 ====================
@@ -83,41 +84,27 @@ function updateClock() {
 const soundOn = ref(true)
 
 // ==================== 等级映射 ====================
-const levelMap = { 1: '严重', 2: '重要', 3: '一般' }
+const alarmLevelMap = { 1: '最高', 2: '高', 3: '中', 4: '低' }
 
 // ==================== 报警数据 ====================
 const alarmList = ref([])
 
 async function loadAlarms() {
   try {
-    const res = await getEquipmentPage(1, 200, { equipmentTypeId: 'drainage' })
+    const res = await getAlarmDataPage(1, 200, { equipmentType: '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
+    alarmList.value = records.map(row => ({
+      ...row,
+      alarmLevelText: alarmLevelMap[row.alarmLevel] || '-'
+    }))
   } catch (error) {
     console.error('加载报警数据失败', error)
   }
 }
 
-const unHandledCount = computed(() => alarmList.value.filter(a => a.level === '严重' || a.level === '重要').length)
+const unHandledCount = computed(() => alarmList.value.filter(a => a.alarmStatus === 0).length)
 
 // ==================== 自动滚动 ====================
 const scrollWrapper = ref(null)
@@ -287,18 +274,22 @@ onUnmounted(() => {
   border-color: rgba(64, 158, 255, 0.45);
 }
 
-.alarm-card.level-严重 {
+.alarm-card.level-最高 {
   border-left: 4px solid #f56c6c;
 }
 
-.alarm-card.level-重要 {
+.alarm-card.level- {
   border-left: 4px solid #e6a23c;
 }
 
-.alarm-card.level-一般 {
+.alarm-card.level- {
   border-left: 4px solid #409eff;
 }
 
+.alarm-card.level-低 {
+  border-left: 4px solid #67c238;
+}
+
 .card-left {
   display: flex;
   align-items: center;
@@ -319,22 +310,27 @@ onUnmounted(() => {
   color: #fff;
 }
 
-.level-icon.icon-严重 {
+.level-icon.icon-最高 {
   background: radial-gradient(circle, #f56c6c, #c45656);
   box-shadow: 0 0 12px rgba(245, 108, 108, 0.5);
   animation: pulse-red 1.5s infinite;
 }
 
-.level-icon.icon-重要 {
+.level-icon.icon- {
   background: radial-gradient(circle, #e6a23c, #c88630);
   box-shadow: 0 0 10px rgba(230, 162, 60, 0.4);
 }
 
-.level-icon.icon-一般 {
+.level-icon.icon- {
   background: radial-gradient(circle, #409eff, #2d7dd2);
   box-shadow: 0 0 8px rgba(64, 158, 255, 0.3);
 }
 
+.level-icon.icon-低 {
+  background: radial-gradient(circle, #67c238, #4e9a2e);
+  box-shadow: 0 0 8px rgba(103, 194, 56, 0.3);
+}
+
 @keyframes pulse-red {
   0%, 100% { box-shadow: 0 0 12px rgba(245, 108, 108, 0.5); }
   50% { box-shadow: 0 0 24px rgba(245, 108, 108, 0.8); }
@@ -372,30 +368,42 @@ onUnmounted(() => {
   font-weight: 600;
 }
 
-.tag-严重 {
+.tag-最高 {
   background: rgba(245, 108, 108, 0.18);
   color: #f56c6c;
   border: 1px solid rgba(245, 108, 108, 0.4);
 }
 
-.tag-重要 {
+.tag- {
   background: rgba(230, 162, 60, 0.18);
   color: #e6a23c;
   border: 1px solid rgba(230, 162, 60, 0.4);
 }
 
-.tag-一般 {
+.tag- {
   background: rgba(64, 158, 255, 0.18);
   color: #409eff;
   border: 1px solid rgba(64, 158, 255, 0.4);
 }
 
+.tag-低 {
+  background: rgba(103, 194, 56, 0.18);
+  color: #67c238;
+  border: 1px solid rgba(103, 194, 56, 0.4);
+}
+
 .device-name {
   font-size: 15px;
   font-weight: 600;
   color: #dce8f0;
 }
 
+.device-code {
+  font-size: 12px;
+  color: #6a8da8;
+  margin-left: 8px;
+}
+
 .alarm-metric {
   font-size: 13px;
   color: #e6a23c;

File diff suppressed because it is too large
+ 637 - 391
src/views/subSystem/drainage/jcbj/bjyp.vue


+ 12 - 13
src/views/subSystem/drainage/jcbj/dqbj.vue

@@ -169,7 +169,7 @@ 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 { getAlarmDataPage } from '@/api/drainage'
 import useUserStore from '@/store/modules/user'
 
 // ==================== 用户信息 ====================
@@ -187,27 +187,26 @@ const alarmList = ref([])
 
 async function loadData() {
   try {
-    const res = await getEquipmentPage(1, 200, { equipmentTypeId: 'drainage' })
+    const res = await getAlarmDataPage(1, 200, { equipmentType: 'drainage' })
     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,
+        id: row.id,
+        name: row.deviceName || '-',
+        level: row.alarmLevel === 1 ? '严重' : row.alarmLevel === 2 ? '重要' : '一般',
+        time: row.alarmTime || '-',
+        type: row.warningType || '-',
+        value: row.actualValue || '-',
+        threshold: row.maxValue || '-',
+        location: '-',
+        deviceNo: row.deviceCode,
         envDesc: '-',
         lng: parseFloat(row.longitude),
         lat: parseFloat(row.latitude),
         historyRecords: []
       }))
-      .filter(w => w.lng && w.lat)
+      .filter(w => w.lng && w.lat && !isNaN(w.lng) && !isNaN(w.lat))
   } catch (error) {
     console.error('加载当前报警数据失败', error)
     ElMessage.error('加载当前报警数据失败')

+ 174 - 194
src/views/subSystem/drainage/jcsj/bzyxqk.vue

@@ -61,8 +61,8 @@
                 <tr>
                   <th>泵站名称</th>
                   <th>状态</th>
-                  <th>前池(m)</th>
-                  <th>后池(m)</th>
+                  <th>设计流量</th>
+                  <th>设计扬程</th>
                 </tr>
               </thead>
               <tbody>
@@ -73,8 +73,8 @@
                   <td>
                     <span :class="'status-tag status-' + row.statusClass">{{ row.status }}</span>
                   </td>
-                  <td>{{ row.frontLevel }}</td>
-                  <td>{{ row.rearLevel }}</td>
+                  <td>{{ row.designFlow }}</td>
+                  <td>{{ row.designHead }}</td>
                 </tr>
               </tbody>
             </table>
@@ -96,12 +96,7 @@
         <!-- 深度曲线 -->
         <div class="panel-box" style="flex: 1 1 0;">
           <div class="panel-title">
-            <span class="title-icon">▶</span> 深度曲线
-          </div>
-          <div class="time-btn-group">
-            <button v-for="t in timeTabs" :key="t.label"
-              :class="['time-btn', { active: activeTimeTab === t.label }]"
-              @click="switchTimeTab(t.label)">{{ t.label }}</button>
+            <span class="title-icon">▶</span>数据统计
           </div>
           <div id="bzyxqkLineChart" class="chart-area-lg"></div>
         </div>
@@ -144,8 +139,7 @@
             <table class="dark-table">
               <thead>
                 <tr>
-                  <th>序号</th>
-                  <th>泵站</th>
+                  <th>站</th>
                   <th>时间</th>
                   <th>类型</th>
                   <th>状态</th>
@@ -153,7 +147,6 @@
               </thead>
               <tbody>
                 <tr v-for="(row, i) in alarmList" :key="i">
-                  <td>{{ i + 1 }}</td>
                   <td :title="row.station">{{ row.station }}</td>
                   <td>{{ row.time }}</td>
                   <td>{{ row.type }}</td>
@@ -176,7 +169,7 @@
 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'
+import { getFacilityPage, getPumpStationById, getPumpStationMonthlyStats, getAlarmDataPage } from '@/api/drainage'
 
 // ==================== 实时时间 ====================
 const currentDateTime = ref('')
@@ -190,7 +183,7 @@ function updateTime() {
 }
 
 // ==================== 泵站概况 ====================
-// 泵站总数来自工程设施API;运行/停止/故障从设备在线/报警状态派生(设施API无状态字段)
+// 泵站总数/运行/故障/检修 来自 water_pump_station 表 status 字段
 const pumpStats = reactive({ total: 0, running: 0, stopped: 0, fault: 0 })
 
 const pieData = computed(() => [
@@ -199,20 +192,20 @@ const pieData = computed(() => [
   { name: '故障', value: pumpStats.fault, color: '#f56c6c' }
 ])
 
-// ==================== 泵站列表(来自工程设施API) ====================
+// ==================== 泵站列表 ====================
 const pumpList = ref([])
-
-// ==================== 设备列表(来自设备API) ====================
-const equipmentList = ref([])
+const facilityStartDate = ref('') // 工程设施最早关联日期
 
 const selectedPumpIndex = ref(-1)
 
 function selectPump(index) {
   selectedPumpIndex.value = index
   const pump = pumpList.value[index]
-  if (mapInstance && pump) {
-    mapInstance.centerAndZoom(new BMapGL.Point(pump.lng, pump.lat), 16)
-  }
+  if (!pump) return
+  // 加载深度曲线
+  initLineChart()
+  // 加载报警数据
+  loadPumpAlarm(pump.stationCode)
 }
 
 // ==================== 搜索 ====================
@@ -227,18 +220,6 @@ function onSearchPump() {
 }
 
 // ==================== 深度曲线 ====================
-const timeTabs = [
-  { label: '24小时' },
-  { label: '7天' },
-  { label: '15天' },
-  { label: '30天' }
-]
-const activeTimeTab = ref('24小时')
-
-function switchTimeTab(label) {
-  activeTimeTab.value = label
-  initLineChart()
-}
 
 // ==================== 视频监控 ====================
 // TODO: 暂无视频监控API,保留mock数据
@@ -250,80 +231,131 @@ const videoList = ref([
 ])
 
 // ==================== 报警记录 ====================
-// TODO: 暂无历史报警API,保留mock数据
-const alarmStats = reactive({ total: 12, handled: 8, unhandled: 4 })
-
-const alarmList = ref([
-  { station: '人民路泵站', time: '06-12 08:32', type: '设备故障', handled: false },
-  { station: '河西泵站', time: '06-12 07:15', type: '液位超限', handled: true },
-  { station: '沅陵一泵站', time: '06-11 22:40', type: '泵启停异常', handled: true },
-  { station: '人民路泵站', time: '06-11 18:22', type: '设备故障', handled: false },
-  { station: '南环泵站', time: '06-11 14:05', type: '液位超限', handled: true },
-  { station: '西环泵站', time: '06-11 09:30', type: '泵启停异常', handled: true },
-  { station: '开发区泵站', time: '06-10 20:18', type: '液位超限', handled: true },
-  { station: '城北泵站', time: '06-10 15:42', type: '泵启停异常', handled: true },
-  { station: '人民路泵站', time: '06-10 11:55', type: '设备故障', handled: false },
-  { station: '河西泵站', time: '06-09 23:10', type: '液位超限', handled: true },
-  { station: '沅陵一泵站', time: '06-09 16:38', type: '设备故障', handled: false },
-  { station: '北环泵站', time: '06-09 08:20', type: '泵启停异常', handled: true }
-])
+const alarmStats = reactive({ total: 0, handled: 0, unhandled: 0 })
+const alarmList = ref([])
+
+async function loadPumpAlarm(stationCode) {
+  if (!stationCode) return
+  try {
+    const res = await getAlarmDataPage(1, 100, { deviceCode: stationCode })
+    const pageData = res.code !== undefined ? res.data : res
+    const records = pageData.records || pageData.rows || []
+    alarmStats.total = records.length
+    alarmStats.handled = records.filter(r => r.status === 1 || r.status === '1').length
+    alarmStats.unhandled = alarmStats.total - alarmStats.handled
+    alarmList.value = records.slice(0, 20).map(r => ({
+      station: r.deviceName || r.deviceCode || stationCode,
+      time: r.alarmTime || r.createTime || '-',
+      type: r.alarmType || r.alarmContent || '-',
+      handled: r.status === 1 || r.status === '1'
+    }))
+  } catch (e) {
+    console.error('加载泵站报警失败', e)
+    alarmStats.total = 0
+    alarmStats.handled = 0
+    alarmStats.unhandled = 0
+    alarmList.value = []
+  }
+}
 
 // ==================== 数据加载 ====================
-// 从工程设施API获取泵站数据,从设备API获取设备数据
 async function loadData() {
   try {
-    // 并行请求泵站设施数据和设备数据
-    const [facilityRes, equipmentRes] = await Promise.all([
-      getFacilityPage(1, 1000, { facilityType: '泵站' }),
-      getEquipmentPage(1, 1000, { equipmentType: 'drainage' })
-    ])
-
-    // 解析泵站设施数据
+    // 1. 先查询设施类型为"泵站"的工程设施,获取关联的泵站ID和坐标
+    const facilityRes = await getFacilityPage(1, 1000, { facilityType: '泵站' })
     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 facilities = facilityPage.records || []
+
+    // 2. 收集所有关联的泵站ID和对应的设施坐标
+    const pumpIdFacilityMap = {} // { pumpId: { lng, lat, facilityName, dataTime } }
+    let earliestDataTime = null
+    facilities.forEach(f => {
+      if (f.equipmentId) {
+        // 解析设施坐标(来自points字段)
+        let fLng = 0, fLat = 0
+        if (f.points) {
+          try {
+            const pts = typeof f.points === 'string' ? JSON.parse(f.points) : f.points
+            if (Array.isArray(pts) && pts.length > 0) {
+              fLng = Number(pts[0].lng) || 0
+              fLat = Number(pts[0].lat) || 0
+            }
+          } catch (e) { /* ignore */ }
+        }
+        // 记录设施的数据时间(关联时间)
+        const fTime = f.dataTime || f.createTime || ''
+        if (fTime && (!earliestDataTime || fTime < earliestDataTime)) {
+          earliestDataTime = fTime
+        }
+        // 分割多个ID
+        const ids = f.equipmentId.split(',').map(s => s.trim()).filter(Boolean)
+        ids.forEach(id => {
+          pumpIdFacilityMap[id] = { lng: fLng, lat: fLat, facilityName: f.name || '', dataTime: fTime }
+        })
+      }
+    })
 
-    // 解析设备数据
-    const equipmentPage = equipmentRes.code !== undefined ? equipmentRes.data : equipmentRes
-    equipmentList.value = equipmentPage.records || []
+    const pumpIds = Object.keys(pumpIdFacilityMap)
+    // 保存最早关联日期
+    facilityStartDate.value = earliestDataTime ? earliestDataTime.substring(0, 10) : ''
+    if (pumpIds.length === 0) {
+      pumpList.value = []
+      pumpStats.total = 0
+      pumpStats.running = 0
+      pumpStats.stopped = 0
+      pumpStats.fault = 0
+      renderMapMarkers()
+      return
+    }
 
-    // 计算统计数据
-    updateStats()
+    // 3. 逐个查询泵站详情
+    const results = []
+    for (const id of pumpIds) {
+      try {
+        const res = await getPumpStationById(id)
+        const item = res.code !== undefined ? res.data : res
+        if (item && item.id) results.push(item)
+      } catch (e) { /* skip */ }
+    }
+
+    // 4. 组装泵站列表数据(坐标来自工程设施的points)
+    pumpList.value = results.map(item => {
+      let status = '正常', statusClass = 'running'
+      if (item.status === '1' || item.status === 1) { status = '故障'; statusClass = 'fault' }
+      else if (item.status === '2' || item.status === 2) { status = '检修'; statusClass = 'repair' }
+
+      const facilityInfo = pumpIdFacilityMap[String(item.id)] || {}
+      return {
+        id: item.id,
+        stationCode: item.stationCode || item.station_code || '',
+        name: item.stationName || item.station_name || '未命名泵站',
+        status,
+        statusClass,
+        designFlow: item.designFlow ?? item.design_flow ?? '-',
+        designHead: item.designHead ?? item.design_head ?? '-',
+        lng: facilityInfo.lng || 0,
+        lat: facilityInfo.lat || 0
+      }
+    })
+
+    // 计算统计
+    pumpStats.total = pumpList.value.length
+    pumpStats.running = pumpList.value.filter(p => p.statusClass === 'running').length
+    pumpStats.stopped = pumpList.value.filter(p => p.statusClass === 'repair').length
+    pumpStats.fault = pumpList.value.filter(p => p.statusClass === 'fault').length
 
-    // 数据加载完成后渲染地图标记
+    // 渲染地图
     renderMapMarkers()
+
+    // 默认选中第一个泵站加载曲线和报警
+    if (pumpList.value.length > 0) {
+      selectPump(0)
+    }
   } catch (e) {
-    console.error('加载泵站/设备数据失败:', 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
 
@@ -450,109 +482,52 @@ function initPieChart() {
   })
 }
 
-// TODO: 暂无实时液位数据API,深度曲线保留mock数据
-function initLineChart() {
-  const dom = document.getElementById('bzyxqkLineChart')
-  if (!dom) return
-  if (lineChart) lineChart.dispose()
-  lineChart = echarts.init(dom)
+async function initLineChart() {
+  const container = document.getElementById('bzyxqkLineChart')
+  if (!container) return
 
-  let xLabels = []
-  let frontData = []
-  let rearData = []
+  let chartInstance = echarts.getInstanceByDom(container)
+  if (!chartInstance) {
+    chartInstance = echarts.init(container)
+  }
+  lineChart = chartInstance
 
-  if (activeTimeTab.value === '24小时') {
-    for (let i = 0; i < 24; i++) {
-      xLabels.push(String(i).padStart(2, '0') + ':00')
-      frontData.push(Math.round((3 + Math.random() * 3) * 10) / 10)
-      rearData.push(Math.round((1 + Math.random() * 2) * 10) / 10)
-    }
-  } else if (activeTimeTab.value === '7天') {
-    for (let i = 6; i >= 0; i--) {
-      const d = new Date()
-      d.setDate(d.getDate() - i)
-      xLabels.push(`${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')}`)
-      frontData.push(Math.round((2.5 + Math.random() * 3.5) * 10) / 10)
-      rearData.push(Math.round((1 + Math.random() * 2) * 10) / 10)
-    }
-  } else if (activeTimeTab.value === '15天') {
-    for (let i = 14; i >= 0; i--) {
-      const d = new Date()
-      d.setDate(d.getDate() - i)
-      xLabels.push(`${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')}`)
-      frontData.push(Math.round((2 + Math.random() * 4) * 10) / 10)
-      rearData.push(Math.round((0.8 + Math.random() * 2.5) * 10) / 10)
-    }
-  } else {
-    for (let i = 29; i >= 0; i--) {
-      const d = new Date()
-      d.setDate(d.getDate() - i)
-      xLabels.push(`${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')}`)
-      frontData.push(Math.round((2 + Math.random() * 4) * 10) / 10)
-      rearData.push(Math.round((0.8 + Math.random() * 2.5) * 10) / 10)
-    }
+  // 收集所有已关联泵站ID
+  const allIds = pumpList.value.map(p => p.id).filter(Boolean)
+  if (allIds.length === 0) {
+    chartInstance.setOption({ title: { text: '暂无泵站数据', left: 'center', top: 'center', textStyle: { color: '#8ec6f0', fontSize: 14 } }, xAxis: { show: false }, yAxis: { show: false }, series: [] })
+    return
   }
 
-  lineChart.setOption({
-    tooltip: { trigger: 'axis' },
-    legend: {
-      data: ['前池液位', '后池液位'],
-      textStyle: { color: '#a0cfff', fontSize: 11 },
-      top: 0,
-      right: 10,
-      itemWidth: 14,
-      itemHeight: 8
-    },
-    grid: { left: 40, right: 14, top: 30, bottom: 25 },
-    xAxis: {
-      type: 'category',
-      data: xLabels,
-      axisLabel: { color: '#7ec8e3', fontSize: 9, interval: activeTimeTab.value === '24小时' ? 3 : 'auto' },
-      axisLine: { lineStyle: { color: '#1a3a5c' } }
-    },
-    yAxis: {
-      type: 'value',
-      name: 'm',
-      nameTextStyle: { color: '#7ec8e3', fontSize: 10 },
-      axisLabel: { color: '#7ec8e3', fontSize: 10 },
-      splitLine: { lineStyle: { color: '#1a3a5c' } },
-      axisLine: { show: false }
-    },
-    series: [
-      {
-        name: '前池液位',
-        type: 'line',
-        data: frontData,
-        smooth: true,
-        symbol: 'circle',
-        symbolSize: 4,
-        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.3)' },
-            { offset: 1, color: 'rgba(64,158,255,0.02)' }
-          ])
-        }
-      },
-      {
-        name: '后池液位',
-        type: 'line',
-        data: rearData,
-        smooth: true,
-        symbol: 'circle',
-        symbolSize: 4,
-        lineStyle: { color: '#67c23a', width: 2 },
-        itemStyle: { color: '#67c23a' },
-        areaStyle: {
-          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-            { offset: 0, color: 'rgba(103,194,58,0.3)' },
-            { offset: 1, color: 'rgba(103,194,58,0.02)' }
-          ])
-        }
-      }
-    ]
-  })
+  try {
+    const res = await getPumpStationMonthlyStats(allIds.join(','), facilityStartDate.value)
+    const statsData = res.code !== undefined ? res.data : res
+    if (!Array.isArray(statsData) || statsData.length === 0) {
+      chartInstance.setOption({ title: { text: '暂无统计数据', left: 'center', top: 'center', textStyle: { color: '#8ec6f0', fontSize: 14 } }, xAxis: { show: false }, yAxis: { show: false }, series: [] })
+      return
+    }
+
+    const months = statsData.map(d => d.month)
+    const flowData = statsData.map(d => d.designFlow || 0)
+    const headData = statsData.map(d => d.designHead || 0)
+
+    chartInstance.setOption({
+      tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
+      legend: { data: ['设计流量', '设计扬程'], top: 5, textStyle: { color: '#8ec6f0', fontSize: 11 } },
+      grid: { top: 40, bottom: 30, left: 50, right: 50 },
+      xAxis: { type: 'category', data: months, axisLabel: { color: '#8ec6f0', fontSize: 11 }, axisLine: { lineStyle: { color: '#3a5f7a' } } },
+      yAxis: [
+        { type: 'value', name: 'm³/h', nameTextStyle: { color: '#8ec6f0', fontSize: 10 }, axisLabel: { color: '#8ec6f0', fontSize: 10 }, splitLine: { lineStyle: { color: '#1e3a50' } } },
+        { type: 'value', name: 'm', nameTextStyle: { color: '#8ec6f0', fontSize: 10 }, axisLabel: { color: '#8ec6f0', fontSize: 10 }, splitLine: { show: false } }
+      ],
+      series: [
+        { name: '设计流量', type: 'bar', data: flowData, barWidth: '30%', itemStyle: { color: '#409eff', borderRadius: [4, 4, 0, 0] } },
+        { name: '设计扬程', type: 'bar', data: headData, yAxisIndex: 1, barWidth: '30%', itemStyle: { color: '#67c23a', borderRadius: [4, 4, 0, 0] } }
+      ]
+    })
+  } catch (e) {
+    console.error('加载泵站月度统计失败:', e)
+  }
 }
 
 // ==================== 生命周期 ====================
@@ -863,6 +838,11 @@ window.addEventListener('resize', () => {
   background: rgba(245, 108, 108, 0.15);
 }
 
+.status-repair {
+  color: #e6a23c;
+  background: rgba(230, 162, 60, 0.15);
+}
+
 /* ==================== 中间地图 ==================== */
 .map-container {
   flex: 1;

+ 245 - 63
src/views/subSystem/drainage/jcsj/gcssgl.vue

@@ -16,7 +16,7 @@
       <el-form-item label="设施名称">
         <el-input v-model="queryParams.name" placeholder="请输入" clearable style="width: 200px" />
       </el-form-item>
-      <el-form-item label="行政区域">
+      <el-form-item label="地点">
         <el-input v-model="queryParams.area" placeholder="请输入" clearable style="width: 200px" />
       </el-form-item>
       <el-form-item label="设施类型">
@@ -38,10 +38,11 @@
     </div>
 
     <!-- 数据表格 -->
-    <el-table v-loading="loading" :data="filteredList" border style="width: 100%" @selection-change="handleSelectionChange">
+    <el-table v-loading="loading" :data="filteredList" border style="width: 100%"
+      @selection-change="handleSelectionChange">
       <el-table-column type="selection" width="55" align="center" />
       <el-table-column label="监测点名称" prop="name" min-width="160" />
-      <el-table-column label="行政区域" prop="area" min-width="100" />
+      <el-table-column label="地点" prop="area" min-width="100" />
       <el-table-column label="设施类型" prop="facilityType" min-width="100" />
       <el-table-column label="数据时间" prop="time" min-width="120" />
       <el-table-column label="标记数量" prop="pointCount" min-width="80" />
@@ -61,16 +62,9 @@
     </el-table>
 
     <!-- 分页 -->
-    <el-pagination
-      v-model:current-page="pageNum"
-      v-model:page-size="pageSize"
-      :total="total"
-      :page-sizes="[10, 20, 50, 100]"
-      layout="total, sizes, prev, pager, next, jumper"
-      style="margin-top: 16px; justify-content: flex-end;"
-      @size-change="loadData"
-      @current-change="loadData"
-    />
+    <el-pagination v-model:current-page="pageNum" v-model:page-size="pageSize" :total="total"
+      :page-sizes="[10, 20, 50, 100]" layout="total, sizes, prev, pager, next, jumper"
+      style="margin-top: 16px; justify-content: flex-end;" @size-change="loadData" @current-change="loadData" />
 
     <!-- 查看详情对话框 -->
     <el-dialog v-model="dialogVisible" title="详情" width="700px" :before-close="handleClose" @opened="onDialogOpened">
@@ -84,7 +78,7 @@
           </el-col>
           <el-col :span="12">
             <div class="info-item">
-              <span class="info-label">行政区域:</span>
+              <span class="info-label">地点:</span>
               <span class="info-value">{{ currentRow.area }}</span>
             </div>
           </el-col>
@@ -145,8 +139,8 @@
             </el-form-item>
           </el-col>
           <el-col :span="12">
-            <el-form-item label="行政区域" prop="area">
-              <el-input v-model="editForm.area" placeholder="请输入行政区域" />
+            <el-form-item label="地点" prop="area">
+              <el-input v-model="editForm.area" placeholder="请输入地点" />
             </el-form-item>
           </el-col>
         </el-row>
@@ -205,13 +199,15 @@
           <el-button type="primary" @click="handleBindQuery">搜索</el-button>
         </el-form-item>
       </el-form>
-      <el-table v-loading="bindLoading" :data="bindTableData" border row-key="equipmentId"
-        @selection-change="handleBindSelectionChange" ref="bindTableRef">
-        <el-table-column type="selection" width="55" align="center" :reserve-selection="true" />
+      <el-table v-loading="bindLoading" :data="bindTableData" border row-key="equipmentId" ref="bindTableRef">
+        <el-table-column label="选择" width="55" align="center">
+          <template #default="{ row }">
+            <el-radio v-model="selectedEquipmentId" :label="row.equipmentId">&nbsp;</el-radio>
+          </template>
+        </el-table-column>
         <el-table-column label="设备名称" prop="equipmentName" min-width="120" />
         <el-table-column label="设备编号" prop="equipmentCode" min-width="120" />
         <el-table-column label="设备类型" prop="equipmentTypeName" min-width="100" />
-        <el-table-column label="安装位置" prop="installLocation" min-width="120" />
       </el-table>
       <el-pagination v-model:current-page="bindPageNum" v-model:page-size="bindPageSize" :total="bindTotal"
         :page-sizes="[10, 20, 50]" layout="total, sizes, prev, pager, next, jumper"
@@ -221,11 +217,47 @@
         <el-button type="primary" @click="submitBind">确 定</el-button>
       </template>
     </el-dialog>
+
+    <!-- 泵站关联弹框 -->
+    <el-dialog v-model="pumpDialogVisible" title="关联泵站" width="600px" @close="closePumpDialog">
+      <el-form :inline="true" :model="pumpQueryParams" style="margin-bottom: 8px;">
+        <el-form-item label="泵站名称">
+          <el-input v-model="pumpQueryParams.stationName" placeholder="请输入" clearable style="width:160px" />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" size="small" @click="handlePumpQuery">搜索</el-button>
+        </el-form-item>
+      </el-form>
+      <el-table v-loading="pumpLoading" :data="pumpTableData" border size="small" style="width:100%" ref="pumpTableRef">
+        <el-table-column label="选择" width="55" align="center">
+          <template #default="{ row }">
+            <el-radio v-model="selectedPumpId" :label="String(row.id)">&nbsp;</el-radio>
+          </template>
+        </el-table-column>
+        <el-table-column label="泵站名称" prop="stationName" min-width="120" />
+        <el-table-column label="状态" prop="status" width="70" align="center">
+          <template #default="{ row }">
+            <span :style="{ color: row.status === '0' ? '#67c23a' : row.status === '1' ? '#f56c6c' : '#e6a23c' }">
+              {{ row.status === '0' ? '正常' : row.status === '1' ? '故障' : '检修' }}
+            </span>
+          </template>
+        </el-table-column>
+        <el-table-column label="设计流量" prop="designFlow" width="100" align="center" />
+        <el-table-column label="设计扬程" prop="designHead" width="100" align="center" />
+      </el-table>
+      <el-pagination v-model:current-page="pumpPageNum" v-model:page-size="pumpPageSize" :total="pumpTotal"
+        :page-sizes="[10, 20, 50]" layout="total, prev, pager, next" size="small" style="margin-top: 8px;"
+        @size-change="loadPumpData" @current-change="loadPumpData" />
+      <template #footer>
+        <el-button @click="closePumpDialog">取 消</el-button>
+        <el-button type="primary" @click="submitPumpBind">确 定</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup name="Gcssgl">
-import { ref, computed, nextTick, onMounted } from 'vue'
+import { ref, reactive, computed, nextTick, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import locationIcon from '@/assets/images/location.png'
 import {
@@ -235,7 +267,10 @@ import {
   deleteFacility,
   getEquipmentPage,
   getEquipmentById,
-  bindFacilityEquipment
+  bindFacilityEquipment,
+  getPumpStationPage,
+  getPumpStationById,
+  bindPumpStation
 } from '@/api/drainage'
 
 // 地图标记创建(用Label+img代替Marker)
@@ -366,10 +401,18 @@ async function resolveEquipmentNames() {
       const names = []
       for (const id of ids) {
         try {
-          const res = await getEquipmentById(id.trim())
-          const data = res.code !== undefined ? res.data : res
-          const info = data && data.equipmentInfo ? data.equipmentInfo : data
-          if (info && info.equipmentName) names.push(info.equipmentName)
+          if (row.facilityType === '泵站') {
+            // 泵站类型,从 water_pump_station 表查询
+            const res = await getPumpStationById(id.trim())
+            const data = res.code !== undefined ? res.data : res
+            if (data && data.stationName) names.push(data.stationName)
+          } else {
+            // 其他类型,从设备表查询
+            const res = await getEquipmentById(id.trim())
+            const data = res.code !== undefined ? res.data : res
+            const info = data && data.equipmentInfo ? data.equipmentInfo : data
+            if (info && info.equipmentName) names.push(info.equipmentName)
+          }
         } catch (e) { /* skip */ }
       }
       map[row.id] = names.join('、')
@@ -485,7 +528,7 @@ const editForm = ref({
 
 const editRules = {
   name: [{ required: true, message: '请输入监测点名称', trigger: 'blur' }],
-  area: [{ required: true, message: '请输入行政区域', trigger: 'blur' }],
+  area: [{ required: true, message: '请输入地点', trigger: 'blur' }],
   facilityType: [{ required: true, message: '请选择设施类型', trigger: 'change' }],
   time: [{ required: true, message: '请选择数据时间', trigger: 'change' }]
 }
@@ -677,7 +720,7 @@ function handleDelete(row) {
     } catch (e) {
       ElMessage.error('删除失败')
     }
-  }).catch(() => {})
+  }).catch(() => { })
 }
 
 function handleBatchDelete() {
@@ -695,7 +738,7 @@ function handleBatchDelete() {
     } catch (e) {
       ElMessage.error('删除失败')
     }
-  }).catch(() => {})
+  }).catch(() => { })
 }
 
 // ==================== 关联设备 ====================
@@ -706,19 +749,32 @@ const bindTotal = ref(0)
 const bindPageNum = ref(1)
 const bindPageSize = ref(10)
 const bindQueryParams = ref({ equipmentName: '', equipmentTypeId: 'drainage' })
-const selectedEquipmentIds = ref([])
+const selectedEquipmentId = ref('')
 const currentPartitionId = ref('')
 const bindTableRef = ref(null)
 
 function handleBind(row) {
+  // 泵站类型走泵站弹框
+  if (row.facilityType === '泵站') {
+    currentPartitionId.value = row.id
+    pumpDialogVisible.value = true
+    pumpPageNum.value = 1
+    pumpQueryParams.value.stationName = ''
+    // 回显已关联泵站(取第一个)
+    selectedPumpId.value = row.equipmentId ? row.equipmentId.split(',')[0].trim() : ''
+    nextTick(() => { loadPumpData() })
+    return
+  }
+  // 其他类型走原有设备关联逻辑
   currentPartitionId.value = row.id
   bindDialogVisible.value = true
   bindPageNum.value = 1
   bindQueryParams.value.equipmentName = ''
+  // 回显已关联设备(取第一个)
   if (row.equipmentId) {
-    selectedEquipmentIds.value = row.equipmentId.split(',')
+    selectedEquipmentId.value = row.equipmentId.split(',')[0].trim()
   } else {
-    selectedEquipmentIds.value = []
+    selectedEquipmentId.value = ''
   }
   nextTick(() => {
     loadBindData()
@@ -737,14 +793,6 @@ async function loadBindData() {
     const pageData = res.code !== undefined ? res.data : res
     bindTableData.value = pageData.records || []
     bindTotal.value = pageData.total || 0
-    await nextTick()
-    if (selectedEquipmentIds.value.length > 0 && bindTableRef.value) {
-      bindTableData.value.forEach(row => {
-        if (selectedEquipmentIds.value.includes(row.equipmentId)) {
-          bindTableRef.value.toggleRowSelection(row, true)
-        }
-      })
-    }
   } catch (e) {
     console.error('加载设备列表失败', e)
   } finally {
@@ -752,25 +800,21 @@ async function loadBindData() {
   }
 }
 
-function handleBindSelectionChange(selection) {
-  selectedEquipmentIds.value = selection.map(item => item.equipmentId)
-}
-
 function closeBindDialog() {
   bindDialogVisible.value = false
   bindTableData.value = []
-  selectedEquipmentIds.value = []
+  selectedEquipmentId.value = ''
 }
 
 async function submitBind() {
-  if (selectedEquipmentIds.value.length === 0) {
+  if (!selectedEquipmentId.value) {
     ElMessage.warning('请选择要关联的设备')
     return
   }
   try {
     const res = await bindFacilityEquipment({
       partitionId: currentPartitionId.value,
-      equipmentIds: selectedEquipmentIds.value
+      equipmentIds: [selectedEquipmentId.value]
     })
     if (res.code === 0 || res.code === 200) {
       ElMessage.success('关联成功')
@@ -784,23 +828,161 @@ async function submitBind() {
     ElMessage.error('关联失败')
   }
 }
+
+// ==================== 泵站关联 ====================
+const pumpDialogVisible = ref(false)
+const pumpLoading = ref(false)
+const pumpTableData = ref([])
+const pumpTotal = ref(0)
+const pumpPageNum = ref(1)
+const pumpPageSize = ref(10)
+const pumpQueryParams = ref({ stationName: '' })
+const selectedPumpId = ref('')
+const pumpTableRef = ref(null)
+
+function handlePumpQuery() {
+  pumpPageNum.value = 1
+  loadPumpData()
+}
+
+async function loadPumpData() {
+  pumpLoading.value = true
+  try {
+    const res = await getPumpStationPage(pumpPageNum.value, pumpPageSize.value, pumpQueryParams.value)
+    const pageData = res.code !== undefined ? res.data : res
+    pumpTableData.value = pageData.records || []
+    pumpTotal.value = pageData.total || 0
+  } catch (e) {
+    console.error('加载泵站数据失败', e)
+  } finally {
+    pumpLoading.value = false
+  }
+}
+
+
+
+
+
+function closePumpDialog() {
+  pumpDialogVisible.value = false
+  pumpTableData.value = []
+  selectedPumpId.value = ''
+}
+
+async function submitPumpBind() {
+  if (!selectedPumpId.value) {
+    ElMessage.warning('请选择要关联的泵站')
+    return
+  }
+  try {
+    const res = await bindPumpStation({
+      partitionId: currentPartitionId.value,
+      equipmentIds: [selectedPumpId.value]
+    })
+    if (res.code === 0 || res.code === 200) {
+      ElMessage.success('关联成功')
+      closePumpDialog()
+      loadData()
+    } else {
+      ElMessage.error(res.msg || '关联失败')
+    }
+  } catch (e) {
+    console.error('关联泵站失败', e)
+    ElMessage.error('关联失败')
+  }
+}
 </script>
 
 <style scoped>
-.app-container { padding: 20px; }
-.search-form { margin-bottom: 16px; }
-.toolbar { margin-bottom: 16px; }
-.detail-info { margin-bottom: 16px; }
-.info-item { display: flex; align-items: center; margin-bottom: 12px; line-height: 32px; }
-.info-label { color: #909399; font-size: 14px; white-space: nowrap; }
-.info-value { color: #303133; font-size: 14px; }
-.map-section { border: 1px solid #e4e7ed; border-radius: 4px; overflow: hidden; }
-.map-tabs { display: flex; background: #f5f7fa; border-bottom: 1px solid #e4e7ed; }
-.map-tab { padding: 8px 24px; font-size: 14px; cursor: pointer; color: #606266; transition: all 0.3s; user-select: none; }
-.map-tab:hover { color: #409eff; }
-.map-tab.active { background: #409eff; color: #fff; }
-.map-tab-action { padding: 8px 24px; font-size: 14px; cursor: pointer; color: #f56c6c; transition: all 0.3s; user-select: none; margin-left: auto; }
-.map-tab-action:hover { color: #f78989; text-decoration: underline; }
-.map-container { width: 100%; height: 400px; }
-.marker-count { padding: 8px 16px; font-size: 13px; color: #909399; background: #f5f7fa; border-top: 1px solid #e4e7ed; }
+.app-container {
+  padding: 20px;
+}
+
+.search-form {
+  margin-bottom: 16px;
+}
+
+.toolbar {
+  margin-bottom: 16px;
+}
+
+.detail-info {
+  margin-bottom: 16px;
+}
+
+.info-item {
+  display: flex;
+  align-items: center;
+  margin-bottom: 12px;
+  line-height: 32px;
+}
+
+.info-label {
+  color: #909399;
+  font-size: 14px;
+  white-space: nowrap;
+}
+
+.info-value {
+  color: #303133;
+  font-size: 14px;
+}
+
+.map-section {
+  border: 1px solid #e4e7ed;
+  border-radius: 4px;
+  overflow: hidden;
+}
+
+.map-tabs {
+  display: flex;
+  background: #f5f7fa;
+  border-bottom: 1px solid #e4e7ed;
+}
+
+.map-tab {
+  padding: 8px 24px;
+  font-size: 14px;
+  cursor: pointer;
+  color: #606266;
+  transition: all 0.3s;
+  user-select: none;
+}
+
+.map-tab:hover {
+  color: #409eff;
+}
+
+.map-tab.active {
+  background: #409eff;
+  color: #fff;
+}
+
+.map-tab-action {
+  padding: 8px 24px;
+  font-size: 14px;
+  cursor: pointer;
+  color: #f56c6c;
+  transition: all 0.3s;
+  user-select: none;
+  margin-left: auto;
+}
+
+.map-tab-action:hover {
+  color: #f78989;
+  text-decoration: underline;
+}
+
+.map-container {
+  width: 100%;
+  height: 400px;
+}
+
+.marker-count {
+  padding: 8px 16px;
+  font-size: 13px;
+  color: #909399;
+  background: #f5f7fa;
+  border-top: 1px solid #e4e7ed;
+}
 </style>

+ 425 - 147
src/views/subSystem/drainage/jcsj/gwyxqk.vue

@@ -97,6 +97,10 @@
         <div class="panel-box">
           <div class="panel-title">
             <span class="title-icon">▶</span> 监测曲线
+            <select v-model="selectedCurveDevice" class="device-select" @change="onCurveDeviceChange">
+              <option value="">请选择设备</option>
+              <option v-for="d in linkedDeviceList" :key="d.equipmentId" :value="d.equipmentCode">{{ d.equipmentName }}</option>
+            </select>
           </div>
           <div class="curve-tabs">
             <span
@@ -170,7 +174,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'
+import { getPipeNetworkPage, getEquipmentPage, getAlarmDataPage, getRadarDataByCode, getRadarDataHistory } from '@/api/drainage'
 
 // ==================== 实时时间 ====================
 const currentDateTime = ref('')
@@ -201,22 +205,8 @@ const pieData = ref([
   { 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' },
-  { name: '河西大道排水管道B7', flow: 210.5, level: 3.15, status: '报警', statusClass: 'alarm' },
-  { name: '人民路地下管网C2', flow: 178.9, level: 2.68, status: '预警', statusClass: 'warning' },
-  { name: '南环路排水管道D3', flow: 56.2, level: 0.52, status: '正常', statusClass: 'normal' },
-  { name: '北环路排水泵站出口', flow: 195.4, level: 2.35, status: '预警', statusClass: 'warning' },
-  { name: '西环路河道排放口', flow: 302.1, level: 4.82, status: '报警', statusClass: 'alarm' },
-  { name: '东环路市政大楼段', flow: 78.5, level: 0.68, status: '正常', statusClass: 'normal' },
-  { name: '开发区管委会路段', flow: 245.7, level: 3.56, status: '报警', statusClass: 'alarm' },
-  { name: '城北二号排水井A7', flow: 112.3, level: 1.05, status: '正常', statusClass: 'normal' },
-  { name: '白羊坪村监测点', flow: 45.8, level: 0.35, status: '离线', statusClass: 'offline' },
-  { name: '杜家山监测站', flow: 92.1, level: 1.18, status: '正常', statusClass: 'normal' }
-])
+// ==================== 实时数据(从radar_data API加载)====================
+const realtimeData = ref([])
 
 // ==================== 监测曲线 ====================
 const curveTabs = [
@@ -227,20 +217,13 @@ const curveTabs = [
 ]
 const activeCurveTab = ref('24h')
 
-// ==================== 报警信息 ====================
-// TODO: 后端暂无报警信息API,以下为mock数据,待API就绪后替换
-const alarmStats = reactive({ total: 8, handled: 5, unhandled: 3 })
-
-const alarmList = ref([
-  { point: '西环路河道排放口', time: '06-12 08:32', type: '液位超限', handleStatus: '未处理', handleClass: 'alarm' },
-  { point: '开发区管委会路段', time: '06-12 08:28', type: '液位超限', handleStatus: '未处理', handleClass: 'alarm' },
-  { point: '河西大道排水管道B7', time: '06-12 08:15', type: '流量异常', handleStatus: '未处理', handleClass: 'alarm' },
-  { point: '人民路地下管网C2', time: '06-12 07:55', type: '液位超限', handleStatus: '已处理', handleClass: 'normal' },
-  { point: '北环路排水泵站出口', time: '06-12 07:42', type: '流量异常', handleStatus: '已处理', handleClass: 'normal' },
-  { point: '白羊坪村监测点', time: '06-12 07:30', type: '设备离线', handleStatus: '已处理', handleClass: 'normal' },
-  { point: '南环路排水管道D3', time: '06-11 22:18', type: '流量异常', handleStatus: '已处理', handleClass: 'normal' },
-  { point: '东环路市政大楼段', time: '06-11 20:05', type: '液位超限', handleStatus: '已处理', handleClass: 'normal' }
-])
+// ==================== 设备下拉与曲线选择 ====================
+const linkedDeviceList = ref([])
+const selectedCurveDevice = ref('')
+
+// ==================== 报警信息(从API加载)====================
+const alarmStats = reactive({ total: 0, handled: 0, unhandled: 0 })
+const alarmList = ref([])
 
 // ==================== 地图数据(从API加载)====================
 const mapPoints = ref([])
@@ -259,26 +242,124 @@ function getCurrentStatusText(val) {
   return map[val] || '未知'
 }
 
+// ==================== 分页响应兼容提取 ====================
+function extractPage(res) {
+  const page = res.code !== undefined ? res.data : res
+  return page.records || page.rows || []
+}
+
 // ==================== 数据加载 ====================
 async function loadMapData() {
   try {
-    // 获取设备分页数据(排水类型)
-    const eqRes = await getEquipmentPage(1, 999, { equipmentType: 'drainage' })
-    const eqPage = eqRes.code !== undefined ? eqRes.data : eqRes
-    const equipmentList = eqPage.records || []
-
-    // 获取管网分页数据
+    // 1. 获取所有管网
     const pnRes = await getPipeNetworkPage(1, 999, {})
-    const pnPage = pnRes.code !== undefined ? pnRes.data : pnRes
-    pipeNetworkData.value = pnPage.records || []
+    const pipeNetworks = extractPage(pnRes)
+    pipeNetworkData.value = pipeNetworks
+    console.log('[gwyxqk] 管网数据:', pipeNetworks.length, '条')
+
+    // 2. 从管网收集关联设备ID(去重)
+    const linkedEqIds = new Set()
+    pipeNetworks.forEach(pn => {
+      if (pn.equipmentId) {
+        // equipmentId 可能是逗号分隔的多个ID
+        String(pn.equipmentId).split(',').forEach(id => {
+          const trimmed = id.trim()
+          if (trimmed) linkedEqIds.add(trimmed)
+        })
+      }
+    })
+    console.log('[gwyxqk] 管网关联设备ID数量:', linkedEqIds.size)
+
+    // 3. 获取所有排水设备
+    const eqRes = await getEquipmentPage(1, 999, { equipmentTypeId: 'drainage' })
+    const allEquipment = extractPage(eqRes)
+    console.log('[gwyxqk] 排水设备总数:', allEquipment.length)
+
+    // 4. 匹配关联设备(同时兼容 id 和 equipmentId 字段)
+    const linkedEquipment = allEquipment.filter(eq => {
+      const eqId = String(eq.id || '')
+      const eqEquipId = String(eq.equipmentId || '')
+      return linkedEqIds.has(eqId) || linkedEqIds.has(eqEquipId)
+    })
+    console.log('[gwyxqk] 匹配到的关联设备:', linkedEquipment.length)
+
+    // 构建关联设备列表(供下拉选择)
+    linkedDeviceList.value = linkedEquipment.map(eq => ({
+      equipmentId: eq.equipmentId || eq.id,
+      equipmentCode: eq.equipmentCode || '',
+      equipmentName: eq.equipmentName || '未命名设备'
+    }))
+    if (linkedDeviceList.value.length > 0 && !selectedCurveDevice.value) {
+      selectedCurveDevice.value = linkedDeviceList.value[0].equipmentCode
+    }
+
+    // 5. 获取报警数据
+    let alarmRecords = []
+    try {
+      const alarmRes = await getAlarmDataPage(1, 999, { deviceType: 'drainage' })
+      alarmRecords = extractPage(alarmRes)
+    } catch (alarmErr) {
+      console.warn('[gwyxqk] 报警数据加载失败:', alarmErr)
+    }
+
+    // 构建报警设备编码集合
+    const alarmDeviceCodes = new Set(alarmRecords.map(a => a.deviceCode))
+
+    // 6. 确定用于展示和统计的设备列表(优先关联设备,无关联则用全部设备)
+    const displayEquipment = linkedEquipment.length > 0 ? linkedEquipment : allEquipment
+
+    // 对每个设备,始终优先使用关联管网的points字段坐标(与排水管网基础数据管理页面保持一致)
+    displayEquipment.forEach(eq => {
+      const parentNetwork = pipeNetworks.find(pn => {
+        const ids = String(pn.equipmentId || '').split(',').map(s => s.trim())
+        return ids.includes(String(eq.id || '')) || ids.includes(String(eq.equipmentId || ''))
+      })
+      if (parentNetwork) {
+        const pts = parsePipeNetworkPoints(parentNetwork)
+        if (pts.length > 0) {
+          eq.longitude = String(pts[0].lng)
+          eq.latitude = String(pts[0].lat)
+          console.log('[gwyxqk] 设备', eq.equipmentCode, '使用管网points坐标', pts[0].lng, pts[0].lat)
+        }
+      }
+    })
+
+    // 7. 统计概况
+    const totalCount = displayEquipment.length
+    const offlineCount = displayEquipment.filter(e => e.onlineStatus === 0).length
+    const alarmCount = displayEquipment.filter(e => e.onlineStatus === 1 && alarmDeviceCodes.has(e.equipmentCode)).length
+    const warningCount = displayEquipment.filter(e => e.onlineStatus === 1 && !alarmDeviceCodes.has(e.equipmentCode) && e.currentStatus === 3).length
+    const normalCount = totalCount - offlineCount - alarmCount - warningCount
+
+    overviewStats.total = totalCount
+    overviewStats.offline = offlineCount
+    overviewStats.alarm = alarmCount
+    overviewStats.normal = normalCount >= 0 ? normalCount : 0
+
+    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' }
+    ]
 
-    // 转换设备数据为地图点位
-    mapPoints.value = equipmentList
-      .filter(e => e.longitude && e.latitude)
+    // 8. 转换设备为地图点位(使用管网名称作为监测点名称)
+    mapPoints.value = displayEquipment
+      .filter(e => {
+        const lng = parseFloat(e.longitude)
+        const lat = parseFloat(e.latitude)
+        return lng && lat && !isNaN(lng) && !isNaN(lat)
+      })
       .map(e => {
         const statusInfo = getEquipStatusInfo(e)
+        // 查找设备关联的管网名称作为监测点名称
+        const parentPn = pipeNetworks.find(pn => {
+          const ids = String(pn.equipmentId || '').split(',').map(s => s.trim())
+          return ids.includes(String(e.id || '')) || ids.includes(String(e.equipmentId || ''))
+        })
+        const monitorName = parentPn ? (parentPn.name || parentPn.networkName || '监测点') : (e.equipmentName || '未命名设备')
         return {
-          name: e.equipmentName || '未命名设备',
+          name: monitorName,
           lng: parseFloat(e.longitude),
           lat: parseFloat(e.latitude),
           status: statusInfo.status,
@@ -286,26 +367,105 @@ async function loadMapData() {
           equipment: e
         }
       })
+    console.log('[gwyxqk] mapPoints生成:', mapPoints.value.length, '个点', mapPoints.value.map(p => p.name + '(' + p.lng + ',' + p.lat + ')'))
 
-    // 计算统计面板数据
-    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
+    // 9. 加载实时数据(传入管网列表用于获取监测点名称)
+    await loadRealtimeData(displayEquipment, pipeNetworks)
 
-    // 更新饼图数据
-    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' }
-    ]
+    // 10. 加载报警信息
+    loadAlarmData(alarmRecords, displayEquipment)
+
+    console.log('[gwyxqk] 数据加载完成')
   } catch (error) {
     console.error('加载地图数据失败:', error)
   }
 }
 
+// ==================== 实时数据加载(/latest 返回单条 RadarData 对象)====================
+async function loadRealtimeData(equipmentList, pipeNetworks) {
+  console.log('[gwyxqk] loadRealtimeData 收到设备:', equipmentList.length, '条', equipmentList.map(e => e.equipmentCode))
+  const rows = []
+  for (const eq of equipmentList) {
+    const code = eq.equipmentCode
+    // 监测点名称使用关联管网名称
+    const parentPn = (pipeNetworks || []).find(pn => {
+      const ids = String(pn.equipmentId || '').split(',').map(s => s.trim())
+      return ids.includes(String(eq.id || '')) || ids.includes(String(eq.equipmentId || ''))
+    })
+    const name = parentPn ? (parentPn.name || parentPn.networkName || '监测点') : (eq.equipmentName || '未命名设备')
+    let flow = '--'
+    let level = '--'
+    let status = '离线'
+    let statusClass = 'offline'
+
+    if (eq.onlineStatus === 1) {
+      status = '正常'
+      statusClass = 'normal'
+    }
+
+    if (code) {
+      try {
+        const radarRes = await getRadarDataByCode(code)
+        console.log('[gwyxqk] 设备', code, 'radar /latest 原始响应:', radarRes)
+        // /latest 接口直接返回单条 RadarData 对象
+        const latest = radarRes.code !== undefined ? radarRes.data : radarRes
+        if (latest) {
+          // 兼容驼峰和下划线两种字段命名
+          const waterLevel = latest.waterLevel ?? latest.water_level
+          const instantFlow = latest.meter1InstantFlow ?? latest.meter1_instant_flow
+          const flowSpeed = latest.flowSpeed ?? latest.flow_speed
+          level = waterLevel != null ? Number(waterLevel).toFixed(2) : '--'
+          flow = instantFlow != null ? Number(instantFlow).toFixed(1) :
+                 flowSpeed != null ? Number(flowSpeed).toFixed(1) : '--'
+        }
+      } catch (err) {
+        console.warn('[gwyxqk] 设备', code, 'radar_data查询失败:', err)
+      }
+    }
+
+    rows.push({ name, flow, level, status, statusClass })
+  }
+  realtimeData.value = rows
+  console.log('[gwyxqk] 实时数据加载完成:', rows.length, '条')
+}
+
+// ==================== 报警数据加载 ====================
+function loadAlarmData(alarmRecords, equipmentList) {
+  // 构建设备编码→名称映射
+  const codeNameMap = {}
+  equipmentList.forEach(eq => {
+    if (eq.equipmentCode) codeNameMap[eq.equipmentCode] = eq.equipmentName || eq.equipmentCode
+  })
+
+  // 过滤只保留关联设备的报警
+  const linkedCodes = new Set(Object.keys(codeNameMap))
+  const filtered = linkedCodes.size > 0
+    ? alarmRecords.filter(a => linkedCodes.has(a.deviceCode))
+    : alarmRecords
+
+  // 统计
+  const total = filtered.length
+  const handled = filtered.filter(a => a.handleStatus === 1 || a.handleStatus === '已处理' || a.status === 1).length
+  const unhandled = total - handled
+
+  alarmStats.total = total
+  alarmStats.handled = handled
+  alarmStats.unhandled = unhandled
+
+  // 列表
+  alarmList.value = filtered.slice(0, 50).map(a => {
+    const isHandled = a.handleStatus === 1 || a.handleStatus === '已处理' || a.status === 1
+    return {
+      point: codeNameMap[a.deviceCode] || a.deviceCode || '-',
+      time: a.alarmTime || a.createTime || '-',
+      type: a.warningType || a.alarmType || '-',
+      handleStatus: isHandled ? '已处理' : '未处理',
+      handleClass: isHandled ? 'normal' : 'alarm'
+    }
+  })
+  console.log('[gwyxqk] 报警数据加载完成:', total, '条')
+}
+
 // ==================== 地图初始化 ====================
 let mapInstance = null
 
@@ -329,12 +489,8 @@ function initMap() {
     mapInstance.centerAndZoom(centerPoint, 14)
     mapInstance.enableScrollWheelZoom(true)
 
-    // 添加管网标记点
-    pipeNetworkData.value.forEach(network => {
-      addPipeNetworkMarker(network)
-    })
-
-    // 添加设备监测点标记
+    // 地图只显示实时数据中的设备点位,自动定位到第一个设备
+    console.log('[gwyxqk] 初始化地图,设备点数量:', mapPoints.value.length)
     mapPoints.value.forEach(point => {
       addMapMarker(point)
     })
@@ -387,46 +543,67 @@ function addMapMarker(point) {
   })
 }
 
-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)
+// 解析管网points字段为坐标数组
+function parsePipeNetworkPoints(network) {
+  // 优先从 points JSON 字段解析
+  if (network.points) {
+    try {
+      const parsed = typeof network.points === 'string' ? JSON.parse(network.points) : network.points
+      if (Array.isArray(parsed) && parsed.length > 0) return parsed
+    } catch (e) { /* ignore */ }
+  }
+  // 回退到 longitude/latitude 字段
+  if (network.longitude && network.latitude) {
+    return [{ name: network.name || network.networkName || '管网', lng: Number(network.longitude), lat: Number(network.latitude) }]
+  }
+  return []
+}
 
-  // 点击标记弹出管网信息窗口
-  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>
+function addPipeNetworkMarker(network) {
+  const points = parsePipeNetworkPoints(network)
+  if (points.length === 0) {
+    console.log('[gwyxqk] 管网无坐标跳过:', network.name || network.networkName)
+    return
+  }
+  const networkName = network.name || network.networkName || '管网'
+  points.forEach(p => {
+    if (!p.lng || !p.lat) return
+    const bPoint = new BMapGL.Point(Number(p.lng), Number(p.lat))
+    const pointName = p.name || networkName
+
+    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;">${pointName}</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 infoWindow = new BMapGL.InfoWindow(infoContent, {
-      width: 240,
-      title: ''
+
+    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;">${pointName}</div>
+        <div>所属管网:${networkName}</div>
+        <div>管网类型:${network.networkType || '-'}</div>
+        <div>所在区域:${network.area || '-'}</div>
+        <div>经度:${p.lng}</div>
+        <div>纬度:${p.lat}</div>
+      </div>`
+      const infoWindow = new BMapGL.InfoWindow(infoContent, {
+        width: 240,
+        title: ''
+      })
+      mapInstance.openInfoWindow(infoWindow, bPoint)
     })
-    mapInstance.openInfoWindow(infoWindow, bPoint)
   })
 }
 
@@ -466,52 +643,92 @@ function initPieChart() {
   })
 }
 
-// TODO: 后端暂无历史监测曲线数据API,以下为mock随机数据,待API就绪后替换
-function generateLineData(tab) {
-  const labels = []
-  const flowData = []
-  const levelData = []
-  if (tab === '24h') {
-    for (let i = 0; i < 24; i++) {
-      labels.push(String(i).padStart(2, '0') + ':00')
-      flowData.push(Math.round((80 + Math.random() * 200) * 10) / 10)
-      levelData.push(Math.round((0.3 + Math.random() * 4) * 100) / 100)
-    }
-  } else if (tab === '7d') {
-    for (let i = 6; i >= 0; i--) {
-      const d = new Date()
-      d.setDate(d.getDate() - i)
-      labels.push(`${d.getMonth() + 1}/${d.getDate()}`)
-      flowData.push(Math.round((100 + Math.random() * 180) * 10) / 10)
-      levelData.push(Math.round((0.5 + Math.random() * 3.5) * 100) / 100)
-    }
-  } else if (tab === '15d') {
-    for (let i = 14; i >= 0; i--) {
-      const d = new Date()
-      d.setDate(d.getDate() - i)
-      labels.push(`${d.getMonth() + 1}/${d.getDate()}`)
-      flowData.push(Math.round((90 + Math.random() * 200) * 10) / 10)
-      levelData.push(Math.round((0.4 + Math.random() * 3.8) * 100) / 100)
-    }
-  } else {
-    for (let i = 29; i >= 0; i--) {
-      const d = new Date()
-      d.setDate(d.getDate() - i)
-      labels.push(`${d.getMonth() + 1}/${d.getDate()}`)
-      flowData.push(Math.round((85 + Math.random() * 210) * 10) / 10)
-      levelData.push(Math.round((0.3 + Math.random() * 4.2) * 100) / 100)
+// ==================== 监测曲线(/page 分页接口,按 startTime/endTime 查询)====================
+const curveChartData = ref({ labels: [], flowData: [], levelData: [] })
+
+// 时间格式化工具:yyyy-MM-dd HH:mm:ss
+function fmtDateTime(d) {
+  const pad = n => String(n).padStart(2, '0')
+  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
+}
+
+async function loadCurveHistory() {
+  const code = selectedCurveDevice.value
+  if (!code) {
+    curveChartData.value = { labels: [], flowData: [], levelData: [] }
+    return
+  }
+
+  // 根据 activeCurveTab 计算时间范围
+  const now = new Date()
+  const start = new Date()
+  const tab = activeCurveTab.value
+  if (tab === '24h') start.setHours(start.getHours() - 24)
+  else if (tab === '7d') start.setDate(start.getDate() - 7)
+  else if (tab === '15d') start.setDate(start.getDate() - 15)
+  else start.setDate(start.getDate() - 30)
+
+  const startTime = fmtDateTime(start)
+  const endTime = fmtDateTime(now)
+
+  try {
+    const res = await getRadarDataHistory(code, startTime, endTime)
+    console.log('[gwyxqk] 曲线历史数据原始响应:', res)
+    const pageData = res.code !== undefined ? res.data : res
+    const records = pageData.records || pageData.rows || (Array.isArray(pageData) ? pageData : [])
+    console.log('[gwyxqk] 曲线历史数据:', code, startTime, '~', endTime, records.length, '条')
+
+    const labels = []
+    const flowData = []
+    const levelData = []
+
+    records.forEach(r => {
+      const t = r.createTime || r.create_time || ''
+      // 24h 显示 HH:mm,其他显示 MM-dd
+      if (tab === '24h') {
+        labels.push(t.substring(11, 16))
+      } else {
+        labels.push(t.substring(5, 10))
+      }
+      // 兼容驼峰和下划线字段命名
+      const instantFlow = r.meter1InstantFlow ?? r.meter1_instant_flow
+      const waterLevel = r.waterLevel ?? r.water_level
+      flowData.push(instantFlow != null ? Number(instantFlow) : 0)
+      levelData.push(waterLevel != null ? Number(waterLevel) : 0)
+    })
+
+    // 如果数据为空,显示空图表提示
+    if (labels.length === 0) {
+      labels.push('暂无数据')
+      flowData.push(0)
+      levelData.push(0)
     }
+
+    curveChartData.value = { labels, flowData, levelData }
+  } catch (err) {
+    console.warn('[gwyxqk] 曲线历史数据加载失败:', err)
+    curveChartData.value = { labels: [], flowData: [], levelData: [] }
   }
-  return { labels, flowData, levelData }
 }
 
-function initLineChart(tab) {
+function onCurveDeviceChange() {
+  console.log('[gwyxqk] 切换曲线设备:', selectedCurveDevice.value)
+  loadCurveHistory().then(() => {
+    initLineChart()
+  })
+}
+
+function initLineChart(tab, labels, flowData, levelData) {
   const dom = document.getElementById('gwyxqkLineChart')
   if (!dom) return
   if (!lineChart) {
     lineChart = echarts.init(dom)
   }
-  const { labels, flowData, levelData } = generateLineData(tab || '24h')
+  const currentTab = tab || activeCurveTab.value
+  // 优先使用外部传入数据,否则从 curveChartData 取
+  const chartLabels = labels || curveChartData.value.labels
+  const chartFlow = flowData || curveChartData.value.flowData
+  const chartLevel = levelData || curveChartData.value.levelData
   lineChart.setOption({
     tooltip: { trigger: 'axis' },
     legend: {
@@ -525,8 +742,8 @@ function initLineChart(tab) {
     grid: { left: 40, right: 40, top: 30, bottom: 25 },
     xAxis: {
       type: 'category',
-      data: labels,
-      axisLabel: { color: '#7ec8e3', fontSize: 9, interval: tab === '30d' ? 4 : tab === '15d' ? 2 : 'auto' },
+      data: chartLabels,
+      axisLabel: { color: '#7ec8e3', fontSize: 9, interval: currentTab === '30d' ? 4 : currentTab === '15d' ? 2 : 'auto' },
       axisLine: { lineStyle: { color: '#1a3a5c' } }
     },
     yAxis: [
@@ -551,7 +768,7 @@ function initLineChart(tab) {
       {
         name: '流量(m³/h)',
         type: 'line',
-        data: flowData,
+        data: chartFlow,
         smooth: true,
         symbol: 'none',
         lineStyle: { color: '#409eff', width: 2 },
@@ -566,7 +783,7 @@ function initLineChart(tab) {
         name: '液位(m)',
         type: 'line',
         yAxisIndex: 1,
-        data: levelData,
+        data: chartLevel,
         smooth: true,
         symbol: 'none',
         lineStyle: { color: '#e6a23c', width: 2 },
@@ -581,13 +798,49 @@ function initLineChart(tab) {
   })
 }
 
-// TODO: 后端暂无流量统计数据API,以下为mock数据,待API就绪后替换
-function initBarChart() {
+// ==================== 流量统计(各设备最新流量柱状图,/latest 返回单条对象)====================
+async function initBarChart() {
   const dom = document.getElementById('gwyxqkBarChart')
   if (!dom) return
-  barChart = echarts.init(dom)
-  const names = ['沅陵大道', '城北路段', '河西大道', '人民路', '南环路', '北环泵站', '西环路', '东环路']
-  const values = [125.6, 89.3, 210.5, 178.9, 56.2, 195.4, 302.1, 78.5]
+  if (!barChart) {
+    barChart = echarts.init(dom)
+  }
+
+  const devices = linkedDeviceList.value
+  if (devices.length === 0) {
+    barChart.setOption({
+      xAxis: { type: 'category', data: [] },
+      yAxis: { type: 'value' },
+      series: [{ type: 'bar', data: [] }]
+    })
+    return
+  }
+
+  const names = []
+  const values = []
+  for (const d of devices) {
+    names.push(d.equipmentName || d.equipmentCode)
+    let flowVal = 0
+    if (d.equipmentCode) {
+      try {
+        const radarRes = await getRadarDataByCode(d.equipmentCode)
+        console.log('[gwyxqk] 柱状图设备', d.equipmentCode, '/latest 响应:', radarRes)
+        // /latest 直接返回单条对象
+        const latest = radarRes.code !== undefined ? radarRes.data : radarRes
+        if (latest) {
+          const instantFlow = latest.meter1InstantFlow ?? latest.meter1_instant_flow
+          const flowSpeed = latest.flowSpeed ?? latest.flow_speed
+          flowVal = instantFlow != null ? Number(instantFlow) :
+                    flowSpeed != null ? Number(flowSpeed) : 0
+        }
+      } catch (err) {
+        console.warn('[gwyxqk] 柱状图设备', d.equipmentCode, '数据加载失败:', err)
+      }
+    }
+    values.push(Math.round(flowVal * 10) / 10)
+  }
+
+  console.log('[gwyxqk] 流量统计柱状图数据:', names.length, '个设备')
   barChart.setOption({
     tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
     grid: { left: 50, right: 10, top: 10, bottom: 30 },
@@ -622,7 +875,14 @@ function initBarChart() {
 
 // ==================== 监测曲线Tab切换 ====================
 watch(activeCurveTab, (val) => {
-  initLineChart(val)
+  console.log('[gwyxqk] 切换曲线Tab:', val)
+  if (selectedCurveDevice.value) {
+    loadCurveHistory().then(() => {
+      initLineChart(val)
+    })
+  } else {
+    initLineChart(val)
+  }
 })
 
 // ==================== 生命周期 ====================
@@ -641,7 +901,14 @@ onMounted(async () => {
   setTimeout(() => {
     initMap()
     initPieChart()
-    initLineChart('24h')
+    // 加载默认设备的曲线历史数据
+    if (selectedCurveDevice.value) {
+      loadCurveHistory().then(() => {
+        initLineChart()
+      })
+    } else {
+      initLineChart()
+    }
     initBarChart()
   }, 300)
 })
@@ -1025,6 +1292,17 @@ window.addEventListener('resize', () => {
   background: rgba(64, 158, 255, 0.15);
 }
 
+.device-select {
+  margin-left: 10px;
+  background: #0d2137;
+  color: #7ec8e3;
+  border: 1px solid #1a3a5c;
+  border-radius: 3px;
+  padding: 2px 6px;
+  font-size: 11px;
+  outline: none;
+}
+
 .chart-area-sm {
   width: 100%;
   height: 150px;

+ 17 - 29
src/views/subSystem/drainage/jcsj/psfqgl.vue

@@ -78,7 +78,7 @@
           </el-col>
           <el-col :span="12">
             <div class="info-item">
-              <span class="info-label">行政区域:</span>
+              <span class="info-label">地点:</span>
               <span class="info-value">{{ currentRow.district }}</span>
             </div>
           </el-col>
@@ -219,19 +219,21 @@
         </el-form-item>
       </el-form>
 
-      <el-table ref="bindTableRef" v-loading="bindLoading" :data="bindTableData" row-key="equipmentId" border style="width: 100%" height="400"
-        @selection-change="handleBindSelectionChange">
-        <el-table-column type="selection" width="55" align="center" reserve-selection />
+      <el-table ref="bindTableRef" v-loading="bindLoading" :data="bindTableData" row-key="equipmentId" border
+        style="width: 100%" height="400">
+        <el-table-column label="选择" width="55" align="center">
+          <template #default="{ row }">
+            <el-radio v-model="selectedEquipmentId" :label="row.equipmentId">&nbsp;</el-radio>
+          </template>
+        </el-table-column>
         <el-table-column label="设备名称" prop="equipmentName" min-width="160" />
         <el-table-column label="设备编号" prop="equipmentCode" min-width="140" />
         <el-table-column label="设备类型" prop="equipmentTypeName" min-width="120" />
-        <el-table-column label="安装位置" prop="installLocation" min-width="160" />
       </el-table>
 
       <el-pagination v-model:current-page="bindPageNum" v-model:page-size="bindPageSize" :total="bindTotal"
         :page-sizes="[10, 20, 50, 100]" layout="total, sizes, prev, pager, next, jumper"
-        style="margin-top: 16px; justify-content: flex-end;" @size-change="loadBindData"
-        @current-change="loadBindData" />
+        style="margin-top: 16px; justify-content: flex-end;" @size-change="loadBindData" @current-change="loadBindData" />
 
       <template #footer>
         <el-button @click="closeBindDialog">取 消</el-button>
@@ -672,18 +674,17 @@ const bindTotal = ref(0)
 const bindPageNum = ref(1)
 const bindPageSize = ref(10)
 const bindLoading = ref(false)
-const selectedEquipmentIds = ref([])
-
-function handleBind(row) {
+const selectedEquipmentId = ref('')
+async function handleBind(row) {
   currentPartitionId.value = row.id
   bindDialogVisible.value = true
   bindPageNum.value = 1
   bindQueryParams.value.equipmentName = ''
-  // 回显已关联设备
+  // 回显已关联设备(取第一个)
   if (row.equipmentId) {
-    selectedEquipmentIds.value = row.equipmentId.split(',')
+    selectedEquipmentId.value = row.equipmentId.split(',')[0].trim()
   } else {
-    selectedEquipmentIds.value = []
+    selectedEquipmentId.value = ''
   }
   nextTick(() => {
     loadBindData()
@@ -702,15 +703,6 @@ async function loadBindData() {
     const pageData = res.code !== undefined ? res.data : res
     bindTableData.value = pageData.records || []
     bindTotal.value = pageData.total || 0
-    // 回显已选设备
-    await nextTick()
-    if (selectedEquipmentIds.value.length > 0 && bindTableRef.value) {
-      bindTableData.value.forEach(row => {
-        if (selectedEquipmentIds.value.includes(row.equipmentId)) {
-          bindTableRef.value.toggleRowSelection(row, true)
-        }
-      })
-    }
   } catch (e) {
     console.error('加载设备列表失败', e)
   } finally {
@@ -718,25 +710,21 @@ async function loadBindData() {
   }
 }
 
-function handleBindSelectionChange(selection) {
-  selectedEquipmentIds.value = selection.map(item => item.equipmentId)
-}
-
 function closeBindDialog() {
   bindDialogVisible.value = false
   currentPartitionId.value = null
-  selectedEquipmentIds.value = []
+  selectedEquipmentId.value = ''
 }
 
 async function submitBind() {
-  if (selectedEquipmentIds.value.length === 0) {
+  if (!selectedEquipmentId.value) {
     ElMessage.warning('请选择要关联的设备')
     return
   }
   try {
     const res = await bindPartitionEquipment({
       partitionId: currentPartitionId.value,
-      equipmentIds: selectedEquipmentIds.value
+      equipmentIds: [selectedEquipmentId.value]
     })
     if (res.code === 0 || res.code === 200) {
       ElMessage.success('关联成功')

+ 29 - 42
src/views/subSystem/drainage/jcsj/psgwgl.vue

@@ -5,7 +5,8 @@
       <div class="map-tabs">
         <div class="map-tab" :class="{ active: mainMapTab === 'all' }" @click="switchMainMapTab('all')">全部</div>
         <div class="map-tab" :class="{ active: mainMapTab === 'pipeline' }" @click="switchMainMapTab('pipeline')">管线</div>
-        <div class="map-tab" :class="{ active: mainMapTab === 'pipePoint' }" @click="switchMainMapTab('pipePoint')">管点</div>
+        <div class="map-tab" :class="{ active: mainMapTab === 'pipePoint' }" @click="switchMainMapTab('pipePoint')">管点
+        </div>
         <div class="map-tab" :class="{ active: mainMapTab === 'outlet' }" @click="switchMainMapTab('outlet')">排口</div>
       </div>
       <div id="psgwglMainMap" class="map-container" style="height: 350px;"></div>
@@ -16,7 +17,7 @@
       <el-form-item label="管网名称">
         <el-input v-model="queryParams.name" placeholder="请输入" clearable style="width: 200px" />
       </el-form-item>
-      <el-form-item label="行政区域">
+      <el-form-item label="地点">
         <el-input v-model="queryParams.area" placeholder="请输入" clearable style="width: 200px" />
       </el-form-item>
       <el-form-item label="管网类型">
@@ -38,10 +39,11 @@
     </div>
 
     <!-- 数据表格 -->
-    <el-table v-loading="loading" :data="filteredList" border style="width: 100%" @selection-change="handleSelectionChange">
+    <el-table v-loading="loading" :data="filteredList" border style="width: 100%"
+      @selection-change="handleSelectionChange">
       <el-table-column type="selection" width="55" align="center" />
       <el-table-column label="监测点名称" prop="name" min-width="160" />
-      <el-table-column label="行政区域" prop="area" min-width="100" />
+      <el-table-column label="地点" prop="area" min-width="100" />
       <el-table-column label="管网类型" prop="networkType" min-width="100" />
       <el-table-column label="数据时间" prop="time" min-width="120" />
       <el-table-column label="标记数量" prop="pointCount" min-width="80" />
@@ -61,16 +63,9 @@
     </el-table>
 
     <!-- 分页 -->
-    <el-pagination
-      v-model:current-page="pageNum"
-      v-model:page-size="pageSize"
-      :total="total"
-      :page-sizes="[10, 20, 50, 100]"
-      layout="total, sizes, prev, pager, next, jumper"
-      style="margin-top: 16px; justify-content: flex-end;"
-      @size-change="loadData"
-      @current-change="loadData"
-    />
+    <el-pagination v-model:current-page="pageNum" v-model:page-size="pageSize" :total="total"
+      :page-sizes="[10, 20, 50, 100]" layout="total, sizes, prev, pager, next, jumper"
+      style="margin-top: 16px; justify-content: flex-end;" @size-change="loadData" @current-change="loadData" />
 
     <!-- 查看详情对话框 -->
     <el-dialog v-model="dialogVisible" title="详情" width="700px" :before-close="handleClose" @opened="onDialogOpened">
@@ -84,7 +79,7 @@
           </el-col>
           <el-col :span="12">
             <div class="info-item">
-              <span class="info-label">行政区域:</span>
+              <span class="info-label">地点:</span>
               <span class="info-value">{{ currentRow.area }}</span>
             </div>
           </el-col>
@@ -145,8 +140,8 @@
             </el-form-item>
           </el-col>
           <el-col :span="12">
-            <el-form-item label="行政区域" prop="area">
-              <el-input v-model="editForm.area" placeholder="请输入行政区域" />
+            <el-form-item label="地点" prop="area">
+              <el-input v-model="editForm.area" placeholder="请输入地点" />
             </el-form-item>
           </el-col>
         </el-row>
@@ -205,9 +200,12 @@
           <el-button type="primary" @click="handleBindQuery">搜索</el-button>
         </el-form-item>
       </el-form>
-      <el-table v-loading="bindLoading" :data="bindTableData" border row-key="equipmentId"
-        @selection-change="handleBindSelectionChange" ref="bindTableRef">
-        <el-table-column type="selection" width="55" align="center" :reserve-selection="true" />
+      <el-table v-loading="bindLoading" :data="bindTableData" border row-key="equipmentId" ref="bindTableRef">
+        <el-table-column label="选择" width="55" align="center">
+          <template #default="{ row }">
+            <el-radio v-model="selectedEquipmentId" :label="row.equipmentId">&nbsp;</el-radio>
+          </template>
+        </el-table-column>
         <el-table-column label="设备名称" prop="equipmentName" min-width="120" />
         <el-table-column label="设备编号" prop="equipmentCode" min-width="120" />
         <el-table-column label="设备类型" prop="equipmentTypeName" min-width="100" />
@@ -524,7 +522,7 @@ const editForm = ref({
 
 const editRules = {
   name: [{ required: true, message: '请输入监测点名称', trigger: 'blur' }],
-  area: [{ required: true, message: '请输入行政区域', trigger: 'blur' }],
+  area: [{ required: true, message: '请输入地点', trigger: 'blur' }],
   networkType: [{ required: true, message: '请选择管网类型', trigger: 'change' }],
   time: [{ required: true, message: '请选择数据时间', trigger: 'change' }]
 }
@@ -751,7 +749,7 @@ function handleDelete(row) {
     } catch (e) {
       ElMessage.error('删除失败')
     }
-  }).catch(() => {})
+  }).catch(() => { })
 }
 
 function handleBatchDelete() {
@@ -770,7 +768,7 @@ function handleBatchDelete() {
     } catch (e) {
       ElMessage.error('删除失败')
     }
-  }).catch(() => {})
+  }).catch(() => { })
 }
 
 // ==================== 关联设备 ====================
@@ -781,19 +779,20 @@ const bindTotal = ref(0)
 const bindPageNum = ref(1)
 const bindPageSize = ref(10)
 const bindQueryParams = ref({ equipmentName: '', equipmentTypeId: 'drainage' })
-const selectedEquipmentIds = ref([])
+const selectedEquipmentId = ref('')
 const currentPartitionId = ref('')
 const bindTableRef = ref(null)
 
-function handleBind(row) {
+async function handleBind(row) {
   currentPartitionId.value = row.id
   bindDialogVisible.value = true
   bindPageNum.value = 1
   bindQueryParams.value.equipmentName = ''
+  // 回显已关联设备(取第一个)
   if (row.equipmentId) {
-    selectedEquipmentIds.value = row.equipmentId.split(',')
+    selectedEquipmentId.value = row.equipmentId.split(',')[0].trim()
   } else {
-    selectedEquipmentIds.value = []
+    selectedEquipmentId.value = ''
   }
   nextTick(() => {
     loadBindData()
@@ -812,14 +811,6 @@ async function loadBindData() {
     const pageData = res.code !== undefined ? res.data : res
     bindTableData.value = pageData.records || []
     bindTotal.value = pageData.total || 0
-    await nextTick()
-    if (selectedEquipmentIds.value.length > 0 && bindTableRef.value) {
-      bindTableData.value.forEach(row => {
-        if (selectedEquipmentIds.value.includes(row.equipmentId)) {
-          bindTableRef.value.toggleRowSelection(row, true)
-        }
-      })
-    }
   } catch (e) {
     console.error('加载设备列表失败', e)
   } finally {
@@ -827,25 +818,21 @@ async function loadBindData() {
   }
 }
 
-function handleBindSelectionChange(selection) {
-  selectedEquipmentIds.value = selection.map(item => item.equipmentId)
-}
-
 function closeBindDialog() {
   bindDialogVisible.value = false
   bindTableData.value = []
-  selectedEquipmentIds.value = []
+  selectedEquipmentId.value = ''
 }
 
 async function submitBind() {
-  if (selectedEquipmentIds.value.length === 0) {
+  if (!selectedEquipmentId.value) {
     ElMessage.warning('请选择要关联的设备')
     return
   }
   try {
     const res = await bindPipeNetworkEquipment({
       partitionId: currentPartitionId.value,
-      equipmentIds: selectedEquipmentIds.value
+      equipmentIds: [selectedEquipmentId.value]
     })
     if (res.code === 0 || res.code === 200) {
       ElMessage.success('关联成功')

+ 62 - 33
src/views/subSystem/drainage/jcsj/sbyx.vue

@@ -4,7 +4,9 @@
     <div class="stat-cards">
       <el-card class="stat-card stat-blue" shadow="hover">
         <div class="stat-card-inner">
-          <div class="stat-icon"><el-icon :size="36"><Monitor /></el-icon></div>
+          <div class="stat-icon"><el-icon :size="36">
+              <Monitor />
+            </el-icon></div>
           <div class="stat-info">
             <div class="stat-value">{{ stats.total }}</div>
             <div class="stat-label">设备总数</div>
@@ -13,7 +15,9 @@
       </el-card>
       <el-card class="stat-card stat-green" shadow="hover">
         <div class="stat-card-inner">
-          <div class="stat-icon"><el-icon :size="36"><CircleCheck /></el-icon></div>
+          <div class="stat-icon"><el-icon :size="36">
+              <CircleCheck />
+            </el-icon></div>
           <div class="stat-info">
             <div class="stat-value">{{ stats.online }}</div>
             <div class="stat-label">在线设备</div>
@@ -22,7 +26,9 @@
       </el-card>
       <el-card class="stat-card stat-gray" shadow="hover">
         <div class="stat-card-inner">
-          <div class="stat-icon"><el-icon :size="36"><SwitchButton /></el-icon></div>
+          <div class="stat-icon"><el-icon :size="36">
+              <SwitchButton />
+            </el-icon></div>
           <div class="stat-info">
             <div class="stat-value">{{ stats.offline }}</div>
             <div class="stat-label">离线设备</div>
@@ -31,7 +37,9 @@
       </el-card>
       <el-card class="stat-card stat-red" shadow="hover">
         <div class="stat-card-inner">
-          <div class="stat-icon"><el-icon :size="36"><Warning /></el-icon></div>
+          <div class="stat-icon"><el-icon :size="36">
+              <Warning />
+            </el-icon></div>
           <div class="stat-info">
             <div class="stat-value">{{ stats.alarm }}</div>
             <div class="stat-label">报警设备</div>
@@ -40,7 +48,9 @@
       </el-card>
       <el-card class="stat-card stat-cyan" shadow="hover">
         <div class="stat-card-inner">
-          <div class="stat-icon"><el-icon :size="36"><DataLine /></el-icon></div>
+          <div class="stat-icon"><el-icon :size="36">
+              <DataLine />
+            </el-icon></div>
           <div class="stat-info">
             <div class="stat-value">{{ stats.onlineRate }}%</div>
             <div class="stat-label">在线率</div>
@@ -69,7 +79,7 @@
     <div class="toolbar">
       <el-button type="warning" @click="handleExport">导出</el-button>
       <el-button type="primary" :disabled="selectedRows.length === 0" @click="handleBatchRepair">维修派单</el-button>
-      <el-button type="danger" :disabled="selectedRows.length === 0" @click="handleBatchWarning">发布预警</el-button>
+      <!-- <el-button type="danger" :disabled="selectedRows.length === 0" @click="handleBatchWarning">发布预警</el-button> -->
     </div>
 
     <!-- 实时数据表格 -->
@@ -111,15 +121,9 @@
     </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-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="detailDialogVisible" title="设备实时详情" width="750px">
@@ -157,7 +161,8 @@
             <div class="info-item">
               <span class="info-label">设备状态:</span>
               <span class="info-value">
-                <el-tag :type="statusTagType(currentRow.currentStatus)">{{ statusText(currentRow.currentStatus) }}</el-tag>
+                <el-tag :type="statusTagType(currentRow.currentStatus)">{{ statusText(currentRow.currentStatus)
+                }}</el-tag>
               </span>
             </div>
           </el-col>
@@ -165,7 +170,8 @@
             <div class="info-item">
               <span class="info-label">报警状态:</span>
               <span class="info-value">
-                <el-tag :type="currentRow.alarmStatus === 1 ? 'danger' : 'success'">{{ currentRow.alarmStatus === 1 ? '报警' : '正常' }}</el-tag>
+                <el-tag :type="currentRow.alarmStatus === 1 ? 'danger' : 'success'">{{ currentRow.alarmStatus === 1 ? '报警'
+                  : '正常' }}</el-tag>
               </span>
             </div>
           </el-col>
@@ -175,7 +181,8 @@
             <div class="info-item">
               <span class="info-label">在线状态:</span>
               <span class="info-value">
-                <el-tag :type="currentRow.onlineStatus === 1 ? 'success' : 'info'">{{ currentRow.onlineStatus === 1 ? '在线' : '离线' }}</el-tag>
+                <el-tag :type="currentRow.onlineStatus === 1 ? 'success' : 'info'">{{ currentRow.onlineStatus === 1 ? '在线'
+                  : '离线' }}</el-tag>
               </span>
             </div>
           </el-col>
@@ -234,7 +241,8 @@
     </el-dialog>
 
     <!-- 定位弹窗 -->
-    <el-dialog v-model="locateDialogVisible" title="设备定位" width="700px" @opened="onLocateDialogOpened" :before-close="handleLocateClose">
+    <el-dialog v-model="locateDialogVisible" title="设备定位" width="700px" @opened="onLocateDialogOpened"
+      :before-close="handleLocateClose">
       <div class="locate-info">
         <div class="locate-device">
           <span class="info-label">设备名称:</span>
@@ -244,7 +252,7 @@
         </div>
         <div id="sbyxLocationMap" class="map-container"></div>
         <div class="locate-coords">
-          经度:{{ locateTempLng }}  纬度:{{ locateTempLat }}
+          经度:{{ locateTempLng }} 纬度:{{ locateTempLat }}
         </div>
         <div class="locate-tip">点击地图可修改设备定位</div>
       </div>
@@ -302,13 +310,8 @@
           <el-input v-model="warningForm.thresholdValue" placeholder="请输入阈值" />
         </el-form-item>
         <el-form-item label="预警时间" prop="warningTime">
-          <el-date-picker
-            v-model="warningForm.warningTime"
-            type="datetime"
-            placeholder="请选择预警时间"
-            value-format="YYYY-MM-DD HH:mm:ss"
-            style="width: 100%"
-          />
+          <el-date-picker v-model="warningForm.warningTime" type="datetime" placeholder="请选择预警时间"
+            value-format="YYYY-MM-DD HH:mm:ss" style="width: 100%" />
         </el-form-item>
         <el-form-item label="预警内容" prop="content">
           <el-input v-model="warningForm.content" type="textarea" :rows="3" placeholder="请输入预警内容" />
@@ -336,7 +339,7 @@
 import { ref, computed, onMounted } from 'vue'
 import { ElMessage } from 'element-plus'
 import locationIcon from '@/assets/images/location.png'
-import { getEquipmentPage, publishWarning } from '@/api/drainage'
+import { getEquipmentPage, publishWarning, addWorkOrder } from '@/api/drainage'
 import { listUser, deptTreeSelect } from '@/api/system/user'
 import useUserStore from '@/store/modules/user'
 
@@ -582,6 +585,12 @@ const repairForm = ref({
   urgency: '',
   remark: ''
 })
+// 当前派单关联的设备行(支持多选批量派单)
+const repairDeviceRows = ref([])
+// 前端紧急程度值 -> 后端工单优先级值
+// 前端 urgency: 1-一般 2-紧急 3-非常紧急
+// 后端 orderLevel: 1-紧急 2-一般 3-低
+const urgencyToOrderLevel = { '1': 2, '2': 1, '3': 1 }
 
 const repairRules = {
   faultDesc: [{ required: true, message: '请输入故障描述', trigger: 'blur' }],
@@ -606,19 +615,40 @@ function openRepairDialog(name) {
 }
 
 function handleRepair(row) {
+  repairDeviceRows.value = [row]
   openRepairDialog(row.equipmentName)
 }
 
 function handleBatchRepair() {
   const names = selectedRows.value.map(r => r.equipmentName).join('、')
+  repairDeviceRows.value = [...selectedRows.value]
   openRepairDialog(names)
 }
 
 function handleRepairSubmit() {
-  repairFormRef.value.validate(valid => {
+  repairFormRef.value.validate(async valid => {
     if (!valid) return
-    ElMessage.success('维修派单成功')
-    repairDialogVisible.value = false
+    const rows = repairDeviceRows.value.filter(r => r.equipmentId)
+    if (rows.length === 0) {
+      ElMessage.warning('所选设备缺少设备ID,无法派单')
+      return
+    }
+    const orderLevel = urgencyToOrderLevel[repairForm.value.urgency]
+    try {
+      // 批量派单时为每个设备分别创建工单
+      for (const row of rows) {
+        await addWorkOrder({
+          deviceId: String(row.equipmentId),
+          orderType: 1,
+          orderLevel,
+          orderDesc: repairForm.value.faultDesc
+        })
+      }
+      ElMessage.success('维修派单成功')
+      repairDialogVisible.value = false
+    } catch (error) {
+      console.error('维修派单失败', error)
+    }
   })
 }
 
@@ -909,5 +939,4 @@ function handleWarningSubmit() {
   margin-top: 6px;
   font-size: 12px;
   color: #909399;
-}
-</style>
+}</style>

+ 13 - 19
src/views/subSystem/drainage/jcsj/yjsdgl.vue

@@ -13,7 +13,7 @@
       <el-form-item label="点位名称">
         <el-input v-model="queryParams.name" placeholder="请输入" clearable style="width: 200px" />
       </el-form-item>
-      <el-form-item label="行政区域">
+      <el-form-item label="地点">
         <el-input v-model="queryParams.area" placeholder="请输入" clearable style="width: 200px" />
       </el-form-item>
       <el-form-item>
@@ -28,10 +28,11 @@
     </div>
 
     <!-- 数据表格 -->
-    <el-table v-loading="loading" :data="filteredList" border style="width: 100%" @selection-change="handleSelectionChange">
+    <el-table v-loading="loading" :data="filteredList" border style="width: 100%"
+      @selection-change="handleSelectionChange">
       <el-table-column type="selection" width="55" align="center" />
       <el-table-column label="监测点名称" prop="name" min-width="160" />
-      <el-table-column label="行政区域" prop="area" min-width="100" />
+      <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" />
@@ -58,16 +59,9 @@
     </el-table>
 
     <!-- 分页 -->
-    <el-pagination
-      v-model:current-page="pageNum"
-      v-model:page-size="pageSize"
-      :total="total"
-      :page-sizes="[10, 20, 50, 100]"
-      layout="total, sizes, prev, pager, next, jumper"
-      style="margin-top: 16px; justify-content: flex-end;"
-      @size-change="loadData"
-      @current-change="loadData"
-    />
+    <el-pagination v-model:current-page="pageNum" v-model:page-size="pageSize" :total="total"
+      :page-sizes="[10, 20, 50, 100]" layout="total, sizes, prev, pager, next, jumper"
+      style="margin-top: 16px; justify-content: flex-end;" @size-change="loadData" @current-change="loadData" />
 
     <!-- 查看详情对话框 -->
     <el-dialog v-model="dialogVisible" title="详情" width="700px" :before-close="handleClose" @opened="onDialogOpened">
@@ -81,7 +75,7 @@
           </el-col>
           <el-col :span="12">
             <div class="info-item">
-              <span class="info-label">行政区域:</span>
+              <span class="info-label">地点:</span>
               <span class="info-value">{{ currentRow.area }}</span>
             </div>
           </el-col>
@@ -149,8 +143,8 @@
             </el-form-item>
           </el-col>
           <el-col :span="12">
-            <el-form-item label="行政区域" prop="area">
-              <el-input v-model="editForm.area" placeholder="请输入行政区域" />
+            <el-form-item label="地点" prop="area">
+              <el-input v-model="editForm.area" placeholder="请输入地点" />
             </el-form-item>
           </el-col>
         </el-row>
@@ -503,7 +497,7 @@ const editForm = ref({
 
 const editRules = {
   name: [{ required: true, message: '请输入监测点名称', trigger: 'blur' }],
-  area: [{ required: true, message: '请输入行政区域', trigger: 'blur' }],
+  area: [{ required: true, message: '请输入地点', trigger: 'blur' }],
   time: [{ required: true, message: '请选择数据时间', trigger: 'change' }]
 }
 
@@ -696,7 +690,7 @@ function handleDelete(row) {
     } catch (e) {
       ElMessage.error('删除失败')
     }
-  }).catch(() => {})
+  }).catch(() => { })
 }
 
 function handleBatchDelete() {
@@ -714,7 +708,7 @@ function handleBatchDelete() {
     } catch (e) {
       ElMessage.error('删除失败')
     }
-  }).catch(() => {})
+  }).catch(() => { })
 }
 
 // ==================== 关联设备 ====================

+ 23 - 14
src/views/subSystem/drainage/jcyj/dqyjGIS.vue

@@ -96,25 +96,27 @@
         </div>
         <div class="detail-info">
           <div class="info-row">
-            <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>
+            <span class="info-label">预警编号</span>
+            <span class="info-value">{{ currentItem.warningNo }}</span>
           </div>
           <div class="info-row">
-            <span class="info-label">当前值</span>
-            <span class="info-value highlight">{{ currentItem.value }}</span>
+            <span class="info-label">预警级别</span>
+            <span class="info-value">
+              <el-tag :type="getTagType(currentItem.level)" effect="dark" size="small">{{ currentItem.level }}</el-tag>
+            </span>
           </div>
           <div class="info-row">
-            <span class="info-label">阈值</span>
-            <span class="info-value">{{ currentItem.threshold }}</span>
+            <span class="info-label">预警时间</span>
+            <span class="info-value">{{ currentItem.createTime }}</span>
           </div>
           <div class="info-row">
             <span class="info-label">位置</span>
             <span class="info-value">{{ currentItem.location }}</span>
           </div>
+          <div class="info-row">
+            <span class="info-label">状态</span>
+            <span class="info-value">{{ currentItem.statusText }}</span>
+          </div>
         </div>
       </div>
     </el-dialog>
@@ -142,10 +144,18 @@ const warningList = ref([])
 // 预警级别数字→文字映射
 const levelMap = { 1: '严重', 2: '重要', 3: '一般' }
 
+function statusText(status) {
+  const map = { DRAFT: '草稿', PENDING: '待办', PROCESSING: '处置中', RELEASED: '已发布', HANDLED: '已处置', CLOSED: '已解除' }
+  return map[status] ?? '-'
+}
+
 // 加载当前预警数据
 async function loadData() {
   try {
     const params = {
+      equipmentType: 'drainage',
+      warningType: '2',
+      currentOnly: true,
       userId: userStore.id,
       isAdmin: isAdmin.value
     }
@@ -167,12 +177,11 @@ async function loadData() {
     warningList.value = records.map(w => ({
       id: w.id,
       name: w.equipmentName || '-',
+      warningNo: w.warningNo || '-',
       level: levelMap[w.warningLevel] || '一般',
-      time: w.warningTime || '-',
-      type: w.warningType || '-',
-      value: w.currentValue || '-',
-      threshold: w.thresholdValue || '-',
+      createTime: w.createTime || '-',
       location: w.location || '-',
+      statusText: statusText(w.status),
       lng: parseFloat(w.longitude),
       lat: parseFloat(w.latitude)
     })).filter(w => w.lng && w.lat)

+ 76 - 94
src/views/subSystem/drainage/jcyj/dqyjgl.vue

@@ -23,7 +23,7 @@
         </div>
       </div>
       <!-- 右侧数字指标 -->
-      <div class="stats-right">
+      <!-- <div class="stats-right">
         <div class="right-metric">
           <el-icon class="metric-icon"><Promotion /></el-icon>
           <span class="metric-num">212</span>
@@ -32,31 +32,19 @@
           <el-icon class="metric-icon orange"><Promotion /></el-icon>
           <span class="metric-num">128</span>
         </div>
-      </div>
+      </div> -->
     </div>
 
     <!-- ==================== 筛选区 ==================== -->
     <div class="filter-panel">
       <div class="filter-tabs">
-        <div
-          class="tab-item"
-          :class="{ active: activeTab === tab }"
-          v-for="tab in tabs"
-          :key="tab"
-          @click="handleTabChange(tab)"
-        >{{ tab }}</div>
+        <div class="tab-item" :class="{ active: activeTab === tab }" v-for="tab in tabs" :key="tab"
+          @click="handleTabChange(tab)">{{ tab }}</div>
       </div>
       <div class="filter-form">
-        <el-input v-model="filters.equipmentName" placeholder="设备名称" clearable style="width:150px" />
-        <el-date-picker
-          v-model="filters.dateRange"
-          type="daterange"
-          range-separator="至"
-          start-placeholder="开始日期"
-          end-placeholder="结束日期"
-          style="width:260px"
-          value-format="YYYY-MM-DD"
-        />
+        <el-input v-model="filters.equipmentName" placeholder="预警名称" clearable style="width:150px" />
+        <el-date-picker v-model="filters.dateRange" type="daterange" range-separator="至" start-placeholder="开始日期"
+          end-placeholder="结束日期" style="width:260px" value-format="YYYY-MM-DD" />
         <el-select v-model="filters.warningLevel" placeholder="预警级别" clearable style="width:150px">
           <el-option label="严重" :value="1" />
           <el-option label="重要" :value="2" />
@@ -71,16 +59,20 @@
     <div class="table-panel">
       <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="equipmentName" label="预警名称" min-width="140" show-overflow-tooltip />
         <el-table-column prop="warningLevel" label="预警级别" width="90" align="center">
           <template #default="{ row }">
             <el-tag :type="levelTagType(row.warningLevel)" size="small">{{ levelText(row.warningLevel) }}</el-tag>
           </template>
         </el-table-column>
-        <el-table-column prop="currentValue" label="当前值" width="100" />
-        <el-table-column prop="thresholdValue" label="阈值" width="100" />
+        <el-table-column prop="warningType" label="预警类型" width="110" align="center">
+          <template #default="{ row }">
+            {{ equipmentTypeMap.get(String(row.warningType)) || row.warningType || '-' }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="warningSpecial" label="预警专项" width="110" align="center" show-overflow-tooltip />
         <el-table-column prop="location" label="位置" min-width="160" show-overflow-tooltip />
-        <el-table-column prop="warningTime" label="预警时间" width="160" />
+        <el-table-column prop="createTime" 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>
@@ -89,24 +81,15 @@
         <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="info" link size="small" @click="handleClose(row)"
+              v-if="row.status !== 'CLOSED' && row.status !== 'HANDLED'">关闭</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"
-      />
+      <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>
 
     <!-- ==================== 查看详情弹窗 ==================== -->
@@ -121,35 +104,38 @@
                 <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-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-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-form-item label="预警类型">{{ equipmentTypeMap.get(String(currentOrder.warningType)) ||
+                  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-form-item label="预警专项">{{ currentOrder.warningSpecial || '-' }}</el-form-item>
               </el-col>
               <el-col :span="12">
-                <el-form-item label="阈值">{{ currentOrder.thresholdValue }}</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-row :gutter="20">
               <el-col :span="12">
-                <el-form-item label="预警时间">{{ currentOrder.warningTime }}</el-form-item>
+                <el-form-item label="预警时间">{{ currentOrder.createTime }}</el-form-item>
               </el-col>
               <el-col :span="12">
-                <el-form-item label="状态">
-                  <el-tag :type="statusTagType(currentOrder.status)" size="small">{{ statusText(currentOrder.status) }}</el-tag>
-                </el-form-item>
+                <el-form-item label="发布人">{{ currentOrder.publisher || '-' }}</el-form-item>
               </el-col>
             </el-row>
             <el-form-item label="位置">
@@ -159,7 +145,7 @@
         </div>
 
         <!-- 人员安排 -->
-        <div class="section-box">
+        <!-- <div class="section-box">
           <div class="section-title">
             接收人员安排
             <el-button type="primary" size="small" style="float:right" @click="showAddPersonDialog">添加</el-button>
@@ -170,11 +156,12 @@
             <el-table-column prop="dept" label="部门" />
             <el-table-column label="操作" width="80" align="center">
               <template #default="{ $index }">
-                <el-button link type="danger" size="small" @click="currentOrder.personnel.splice($index, 1)">删除</el-button>
+                <el-button link type="danger" size="small"
+                  @click="currentOrder.personnel.splice($index, 1)">删除</el-button>
               </template>
             </el-table-column>
           </el-table>
-        </div>
+        </div> -->
       </div>
 
       <template #footer>
@@ -208,7 +195,7 @@ 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 { getWarningCurrentPage, updateWarningStatus, deleteWarning, getEquipmentTypeList } from '@/api/drainage'
 import useUserStore from '@/store/modules/user'
 
 // ==================== 用户信息 ====================
@@ -232,30 +219,50 @@ function levelTagType(level) {
   return map[level] || 'info'
 }
 function statusText(status) {
-  const map = { 0: '待处理', 1: '已处理', 2: '已完成' }
+  const map = { DRAFT: '草稿', PENDING: '待办', PROCESSING: '处置中', RELEASED: '已发布', HANDLED: '已处置', CLOSED: '已解除' }
   return map[status] ?? '-'
 }
 function statusTagType(status) {
-  const map = { 0: 'warning', 1: 'info', 2: 'success' }
+  const map = { DRAFT: 'info', PENDING: 'warning', PROCESSING: 'warning', RELEASED: 'success', HANDLED: 'success', CLOSED: '' }
   return map[status] || 'info'
 }
 
+// ==================== 设备类型映射 ====================
+const equipmentTypeMap = ref(new Map())
+
+async function loadEquipmentTypes() {
+  try {
+    const res = await getEquipmentTypeList('2')
+    const list = res.code !== undefined ? res.data : res
+    const map = new Map()
+    if (Array.isArray(list)) {
+      list.forEach(item => {
+        map.set(String(item.id), item.typeName)
+      })
+    }
+    equipmentTypeMap.value = map
+  } catch (error) {
+    console.error('加载设备类型列表失败', error)
+  }
+}
+
 // ==================== 统计指标 ====================
 const statItems = computed(() => {
   const list = tableData.value
   return [
     { 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: '' }
+    { label: '待办', value: list.filter(o => o.status === 'PENDING').length, cls: 'red' },
+    { label: '处置中', value: list.filter(o => o.status === 'PROCESSING').length, cls: '' },
+    { label: '已发布', value: list.filter(o => o.status === 'RELEASED').length, cls: '' }
   ]
 })
 
 // ==================== 环形图配置 ====================
 const statusLegends = computed(() => [
-  { 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' }
+  { name: '草稿', value: tableData.value.filter(o => o.status === 'DRAFT').length, color: '#909399' },
+  { name: '待办', value: tableData.value.filter(o => o.status === 'PENDING').length, color: '#E6A23C' },
+  { name: '处置中', value: tableData.value.filter(o => o.status === 'PROCESSING').length, color: '#409EFF' },
+  { name: '已发布', value: tableData.value.filter(o => o.status === 'RELEASED').length, color: '#67C23A' }
 ])
 const levelLegends = computed(() => [
   { name: '严重', value: tableData.value.filter(o => o.warningLevel === 1).length, color: '#F56C6C' },
@@ -266,7 +273,7 @@ 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,
+    name: equipmentTypeMap.value.get(String(t)) || t,
     value: tableData.value.filter(o => o.warningType === t).length,
     color: colors[i % colors.length]
   }))
@@ -278,7 +285,7 @@ const ringCharts = [
 ]
 
 // ==================== 筛选 ====================
-const tabs = ['全部', '待处理', '已处理', '已完成']
+const tabs = ['全部', '草稿', '待办', '处置中', '已发布']
 const activeTab = ref('全部')
 const filters = ref({
   equipmentName: '',
@@ -291,7 +298,8 @@ async function loadData() {
   loading.value = true
   try {
     const params = {
-      equipmentType: 'drainage'
+      equipmentType: 'drainage',
+      warningType: '2'
     }
     if (filters.value.equipmentName) params.equipmentName = filters.value.equipmentName
     if (filters.value.warningLevel) params.warningLevel = filters.value.warningLevel
@@ -299,8 +307,10 @@ async function loadData() {
       params.startTime = filters.value.dateRange[0]
       params.endTime = filters.value.dateRange[1]
     }
+    // 告诉后端只返回当前预警(未处置的)
+    params.currentOnly = true
     // Tab 映射为状态筛选
-    const tabStatusMap = { '待处理': 0, '已处理': 1, '已完成': 2 }
+    const tabStatusMap = { '草稿': 'DRAFT', '待办': 'PENDING', '处置中': 'PROCESSING', '已发布': 'RELEASED' }
     if (activeTab.value !== '全部' && tabStatusMap[activeTab.value] !== undefined) {
       params.status = tabStatusMap[activeTab.value]
     }
@@ -342,25 +352,11 @@ function handleTabChange(tab) {
   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('处理失败')
-    }
-  }
-}
-
+// ==================== 操作:关闭/删除 ====================
 async function handleClose(row) {
   try {
     await ElMessageBox.confirm('确认关闭该预警?', '提示', { type: 'warning' })
-    await updateWarningStatus(row.id, { status: 2 })
+    await updateWarningStatus(row.id, { status: 'CLOSED' })
     ElMessage.success('预警已关闭')
     loadData()
   } catch (error) {
@@ -379,21 +375,6 @@ async function handleDelete(row) {
   }
 }
 
-// ==================== 审批流程 ====================
-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('审批失败')
-    }
-  }
-}
-
 // ==================== 详情弹窗 ====================
 const detailVisible = ref(false)
 const currentOrder = ref(null)
@@ -459,7 +440,8 @@ function initCharts() {
   })
 }
 
-onMounted(() => {
+onMounted(async () => {
+  await loadEquipmentTypes()
   loadData()
 })
 </script>

+ 22 - 20
src/views/subSystem/drainage/jcyj/lsyjGIS.vue

@@ -96,16 +96,18 @@
         </div>
         <div class="detail-info">
           <div class="info-row">
-            <span class="info-label">预警时间</span>
-            <span class="info-value">{{ currentItem.time }}</span>
+            <span class="info-label">预警编号</span>
+            <span class="info-value">{{ currentItem.warningNo }}</span>
           </div>
           <div class="info-row">
-            <span class="info-label">当前值</span>
-            <span class="info-value highlight">{{ currentItem.value }}</span>
+            <span class="info-label">预警级别</span>
+            <span class="info-value">
+              <el-tag :type="getTagType(currentItem.level)" effect="dark" size="small">{{ currentItem.level }}</el-tag>
+            </span>
           </div>
           <div class="info-row">
-            <span class="info-label">阈值</span>
-            <span class="info-value">{{ currentItem.threshold }}</span>
+            <span class="info-label">预警时间</span>
+            <span class="info-value">{{ currentItem.createTime }}</span>
           </div>
           <div class="info-row">
             <span class="info-label">位置</span>
@@ -114,13 +116,9 @@
           <div class="info-row">
             <span class="info-label">处理状态</span>
             <span class="info-value">
-              <el-tag :type="getStatusTagType(currentItem.status)" size="small">{{ currentItem.status }}</el-tag>
+              <el-tag :type="getStatusTagType(currentItem.statusKey)" size="small">{{ currentItem.statusText }}</el-tag>
             </span>
           </div>
-          <div class="info-row" v-if="currentItem.processTime">
-            <span class="info-label">处理时间</span>
-            <span class="info-value">{{ currentItem.processTime }}</span>
-          </div>
         </div>
       </div>
     </el-dialog>
@@ -142,12 +140,17 @@ const warningList = ref([])
 
 // 预警级别/状态数字→文字映射
 const levelMap = { 1: '严重', 2: '重要', 3: '一般' }
-const statusMap = { 0: '待处理', 1: '已处理', 2: '已完成' }
+const statusMap = { HANDLED: '已处置', CLOSED: '已解除', DRAFT: '草稿', PENDING: '待办', PROCESSING: '处置中', RELEASED: '已发布' }
 
 // 加载历史预警数据
 async function loadData() {
   try {
-    const res = await getWarningHistoryPage(1, 1000, {})
+    const res = await getWarningHistoryPage(1, 1000, {
+      equipmentType: 'drainage',
+      warningType: '2',
+      historyOnly: true,
+      status: 'HANDLED'
+    })
     const pageData = res.code !== undefined ? res.data : res
     const records = pageData.records || []
     const hasCoordCount = records.filter(w => w.longitude && w.latitude).length
@@ -162,13 +165,12 @@ async function loadData() {
     warningList.value = records.map(w => ({
       id: w.id,
       name: w.equipmentName || '-',
+      warningNo: w.warningNo || '-',
       level: levelMap[w.warningLevel] || '一般',
-      time: w.warningTime || '-',
-      value: w.currentValue || '-',
-      threshold: w.thresholdValue || '-',
+      createTime: w.createTime || '-',
       location: w.location || '-',
-      status: statusMap[w.status] || '-',
-      processTime: w.processTime || '',
+      statusKey: w.status || '',
+      statusText: statusMap[w.status] || '-',
       lng: parseFloat(w.longitude),
       lat: parseFloat(w.latitude)
     })).filter(w => w.lng && w.lat)
@@ -205,8 +207,8 @@ function getLevelKey(level) {
 }
 
 function getStatusTagType(status) {
-  const map = { '待处理': 'warning', '已处理': 'info', '已完成': 'success' }
-  return map[status] || ''
+  const map = { HANDLED: 'success', CLOSED: '', DRAFT: 'info', PENDING: 'warning', PROCESSING: 'warning', RELEASED: 'success' }
+  return map[status] || 'info'
 }
 
 // ==================== 搜索功能 ====================

+ 51 - 21
src/views/subSystem/drainage/jcyj/lsyjgl.vue

@@ -5,7 +5,7 @@
     <div class="search-panel">
       <div class="search-form">
         <div class="form-item">
-          <span class="form-label">设备名称:</span>
+          <span class="form-label">预警名称:</span>
           <el-input v-model="filters.equipmentName" placeholder="请输入" clearable style="width:200px" />
         </div>
         <div class="form-item">
@@ -19,8 +19,8 @@
         <div class="form-item">
           <span class="form-label">处理状态:</span>
           <el-select v-model="filters.status" placeholder="请选择" clearable style="width:160px">
-            <el-option label="已处理" :value="1" />
-            <el-option label="已完成" :value="2" />
+            <el-option label="已处置" value="HANDLED" />
+            <el-option label="已解除" value="CLOSED" />
           </el-select>
         </div>
         <el-button type="primary" @click="handleSearch">搜索</el-button>
@@ -32,22 +32,25 @@
     <div class="table-panel">
       <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="equipmentName" label="预警名称" min-width="140" show-overflow-tooltip />
         <el-table-column prop="warningLevel" label="预警级别" width="90" align="center">
           <template #default="{ row }">
             <el-tag :type="levelTagType(row.warningLevel)" size="small">{{ levelText(row.warningLevel) }}</el-tag>
           </template>
         </el-table-column>
-        <el-table-column prop="currentValue" label="当前值" width="100" />
-        <el-table-column prop="thresholdValue" label="阈值" width="100" />
+        <el-table-column prop="warningType" label="预警类型" width="110" align="center">
+          <template #default="{ row }">
+            {{ equipmentTypeMap.get(String(row.warningType)) || row.warningType || '-' }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="warningSpecial" label="预警专项" width="110" align="center" show-overflow-tooltip />
         <el-table-column prop="location" label="位置" min-width="180" show-overflow-tooltip />
-        <el-table-column prop="warningTime" label="预警时间" width="160" />
+        <el-table-column prop="createTime" 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>
@@ -81,7 +84,7 @@
           <div class="info-grid">
             <div class="info-row">
               <div class="info-item">
-                <span class="info-label">设备名称:</span>
+                <span class="info-label">预警名称:</span>
                 <span class="info-value">{{ currentDetail.equipmentName }}</span>
               </div>
               <div class="info-item">
@@ -93,18 +96,22 @@
             </div>
             <div class="info-row">
               <div class="info-item">
-                <span class="info-label">当前值:</span>
-                <span class="info-value">{{ currentDetail.currentValue }}</span>
+                <span class="info-label">预警类型:</span>
+                <span class="info-value">{{ equipmentTypeMap.get(String(currentDetail.warningType)) || currentDetail.warningType || '-' }}</span>
               </div>
               <div class="info-item">
-                <span class="info-label">阈值:</span>
-                <span class="info-value">{{ currentDetail.thresholdValue }}</span>
+                <span class="info-label">预警专项:</span>
+                <span class="info-value">{{ currentDetail.warningSpecial || '-' }}</span>
               </div>
             </div>
             <div class="info-row">
               <div class="info-item">
                 <span class="info-label">预警时间:</span>
-                <span class="info-value">{{ currentDetail.warningTime }}</span>
+                <span class="info-value">{{ currentDetail.createTime }}</span>
+              </div>
+              <div class="info-item">
+                <span class="info-label">发布人:</span>
+                <span class="info-value">{{ currentDetail.publisher || '-' }}</span>
               </div>
             </div>
             <div class="info-row">
@@ -115,8 +122,8 @@
                 </span>
               </div>
               <div class="info-item">
-                <span class="info-label">处理时间:</span>
-                <span class="info-value">{{ currentDetail.firstApproveTime || '-' }}</span>
+                <span class="info-label">发布时间:</span>
+                <span class="info-value">{{ currentDetail.publishTime || '-' }}</span>
               </div>
             </div>
             <div class="info-row single">
@@ -139,7 +146,26 @@
 <script setup name="Lsyjgl">
 import { ref, reactive, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
-import { getWarningHistoryPage, deleteWarning } from '@/api/drainage'
+import { getWarningHistoryPage, deleteWarning, getEquipmentTypeList } from '@/api/drainage'
+
+// ==================== 设备类型映射 ====================
+const equipmentTypeMap = ref(new Map())
+
+async function loadEquipmentTypes() {
+  try {
+    const res = await getEquipmentTypeList('2')
+    const list = res.code !== undefined ? res.data : res
+    const map = new Map()
+    if (Array.isArray(list)) {
+      list.forEach(item => {
+        map.set(String(item.id), item.typeName)
+      })
+    }
+    equipmentTypeMap.value = map
+  } catch (error) {
+    console.error('加载设备类型列表失败', error)
+  }
+}
 
 // ==================== 等级/状态工具方法 ====================
 function levelText(level) {
@@ -151,11 +177,11 @@ function levelTagType(level) {
   return map[level] || 'info'
 }
 function statusText(status) {
-  const map = { 0: '待处理', 1: '已处理', 2: '已完成' }
+  const map = { DRAFT: '草稿', PENDING: '待办', PROCESSING: '处置中', RELEASED: '已发布', HANDLED: '已处置', CLOSED: '已解除' }
   return map[status] ?? '-'
 }
 function statusTagType(status) {
-  const map = { 0: 'warning', 1: 'info', 2: 'success' }
+  const map = { DRAFT: 'info', PENDING: 'warning', PROCESSING: 'warning', RELEASED: 'success', HANDLED: 'success', CLOSED: '' }
   return map[status] || 'info'
 }
 
@@ -178,7 +204,10 @@ async function loadData() {
   loading.value = true
   try {
     const params = {
-      equipmentType: 'drainage'
+      equipmentType: 'drainage',
+      warningType: '2',
+      historyOnly: true,
+      status: 'HANDLED'
     }
     if (filters.equipmentName) params.equipmentName = filters.equipmentName
     if (filters.warningLevel) params.warningLevel = filters.warningLevel
@@ -229,7 +258,8 @@ function handleView(row) {
   dialogVisible.value = true
 }
 
-onMounted(() => {
+onMounted(async () => {
+  await loadEquipmentTypes()
   loadData()
 })
 </script>

+ 24 - 30
src/views/subSystem/manholeCover/device/statistics.vue

@@ -29,7 +29,9 @@
       <div class="chart-card">
         <div class="card-header">
           <span class="card-title">
-            <el-icon><Location /></el-icon>
+            <el-icon>
+              <Location />
+            </el-icon>
             设备区域分布(柱状图)
           </span>
           <el-radio-group v-model="barChartType" size="small">
@@ -46,7 +48,9 @@
       <div class="chart-card">
         <div class="card-header">
           <span class="card-title">
-            <el-icon><PieChart /></el-icon>
+            <el-icon>
+              <PieChart />
+            </el-icon>
             设备类型分布(饼图)
           </span>
           <el-radio-group v-model="pieChartType" size="small">
@@ -64,17 +68,13 @@
     <div class="data-table-container">
       <div class="table-header">
         <span class="card-title">
-          <el-icon><List /></el-icon>
+          <el-icon>
+            <List />
+          </el-icon>
           设备明细数据
         </span>
-        <el-input
-            v-model="searchKeyword"
-            placeholder="搜索设备名称/编号/区域"
-            clearable
-            style="width: 240px"
-            :prefix-icon="Search"
-            size="small"
-        />
+        <el-input v-model="searchKeyword" placeholder="搜索设备名称/编号/区域" clearable style="width: 240px" :prefix-icon="Search"
+          size="small" />
       </div>
       <el-table :data="paginatedTableData" stripe border style="width: 100%">
         <el-table-column prop="deviceCode" label="设备编号" width="130" />
@@ -84,7 +84,7 @@
             <el-tag :type="getTypeTagColor(row.deviceType)" size="small">{{ row.deviceType }}</el-tag>
           </template>
         </el-table-column>
-        <el-table-column prop="region" label="所属行政区域" width="140" />
+        <el-table-column prop="region" label="所属地点" width="140" />
         <el-table-column prop="address" label="详细位置" min-width="180" show-overflow-tooltip />
         <el-table-column prop="status" label="运行状态" width="100" align="center">
           <template #default="{ row }">
@@ -96,15 +96,9 @@
         <el-table-column prop="installDate" label="安装日期" width="120" />
       </el-table>
       <div class="pagination-wrapper">
-        <el-pagination
-            background
-            layout="total, prev, pager, next"
-            :total="filteredTableData.length"
-            :page-size="pageSize"
-            v-model:current-page="currentPage"
-            v-model:page-size="pageSize"
-            :page-sizes="[5, 10, 20]"
-        />
+        <el-pagination background layout="total, prev, pager, next" :total="filteredTableData.length"
+          :page-size="pageSize" v-model:current-page="currentPage" v-model:page-size="pageSize"
+          :page-sizes="[5, 10, 20]" />
       </div>
     </div>
   </div>
@@ -186,8 +180,8 @@ const regionDistribution = computed(() => {
     regionMap.set(region, (regionMap.get(region) || 0) + 1)
   })
   return Array.from(regionMap.entries())
-      .map(([region, count]) => ({ region, count }))
-      .sort((a, b) => b.count - a.count)
+    .map(([region, count]) => ({ region, count }))
+    .sort((a, b) => b.count - a.count)
 })
 
 // 设备类型分布
@@ -198,8 +192,8 @@ const typeDistribution = computed(() => {
     typeMap.set(type, (typeMap.get(type) || 0) + 1)
   })
   return Array.from(typeMap.entries())
-      .map(([type, count]) => ({ type, count }))
-      .sort((a, b) => b.count - a.count)
+    .map(([type, count]) => ({ type, count }))
+    .sort((a, b) => b.count - a.count)
 })
 
 // 柱状图配置
@@ -217,7 +211,7 @@ const regionBarOption = computed(() => {
     tooltip: {
       trigger: 'axis',
       axisPointer: { type: 'shadow' },
-      formatter: function(params) {
+      formatter: function (params) {
         if (barChartType.value === 'count') {
           return `${params[0].axisValue}<br/>${params[0].seriesName}: ${params[0].value} 台`
         } else {
@@ -320,9 +314,9 @@ const filteredTableData = computed(() => {
   if (searchKeyword.value) {
     const kw = searchKeyword.value.toLowerCase()
     data = data.filter(d =>
-        d.deviceCode.toLowerCase().includes(kw) ||
-        d.deviceName.toLowerCase().includes(kw) ||
-        d.region.toLowerCase().includes(kw)
+      d.deviceCode.toLowerCase().includes(kw) ||
+      d.deviceName.toLowerCase().includes(kw) ||
+      d.region.toLowerCase().includes(kw)
     )
   }
   return data
@@ -346,7 +340,7 @@ const resetFilters = () => {
 
 const exportData = () => {
   // 简单导出CSV
-  const headers = ['设备编号', '设备名称', '设备类型', '所属行政区域', '详细位置', '运行状态', '安装日期']
+  const headers = ['设备编号', '设备名称', '设备类型', '所属地点', '详细位置', '运行状态', '安装日期']
   const rows = filteredTableData.value.map(d => [
     d.deviceCode, d.deviceName, d.deviceType, d.region, d.address, d.status, d.installDate
   ])

Some files were not shown because too many files changed in this diff