Forráskód Böngészése

feat(manholeCover): 重构井盖报警页面功能

- 将监测点列表改为设备列表,优化设备展示信息
- 添加设备在线状态、报警状态等实时监控信息
- 实现设备详情加载和阈值配置功能
- 更新阈值设置界面,支持最小值和最大值配置
- 集成后端API实现设备数据获取和阈值保存
- 优化页面布局和样式,提升用户体验
- 添加数据加载状态和空状态显示
- 实现设备搜索和筛选功能
- 添加刷新按钮和保存加载状态提示
nahida 1 hete
szülő
commit
f873cb5ba8
1 módosított fájl, 500 hozzáadás és 351 törlés
  1. 500 351
      src/views/subSystem/manholeCover/alarm/index.vue

+ 500 - 351
src/views/subSystem/manholeCover/alarm/index.vue

@@ -1,388 +1,438 @@
-<!--监测阈值管理-->
 <template>
   <div class="threshold-management">
-    <!-- 主内容区域 -->
     <div class="main-content">
-      <!-- 左侧:监测点列表 -->
       <div class="point-list-panel">
         <div class="panel-title">
-          <span>监测点列表</span>
-          <el-input v-model="searchKeyword" placeholder="搜索设备编号/名称" clearable :prefix-icon="Search" size="small" style="width: 180px" />
+          <span>设备列表</span>
+          <el-button :icon="Refresh" text @click="loadDeviceList">刷新</el-button>
         </div>
-        <div class="point-list">
+        <el-input
+          v-model="searchKeyword"
+          placeholder="搜索设备编号/名称"
+          clearable
+          :prefix-icon="Search"
+          size="small"
+        />
+
+        <div v-loading="listLoading" class="point-list">
           <div
-              v-for="point in filteredPoints"
-              :key="point.id"
-              class="point-item"
-              :class="{ active: selectedPoint?.id === point.id }"
-              @click="selectPoint(point)"
+            v-for="point in filteredPoints"
+            :key="point.equipmentId"
+            class="point-item"
+            :class="{ active: selectedPoint?.equipmentId === point.equipmentId }"
+            @click="selectPoint(point)"
           >
             <div class="point-header">
-              <span class="point-name">{{ point.name }}</span>
-              <el-tag :type="getPointStatusType(point)" size="small">{{ point.status }}</el-tag>
+              <span class="point-name">{{ point.equipmentName || point.equipmentCode || '未命名设备' }}</span>
+              <el-tag :type="getAlarmTagType(point)" size="small">
+                {{ getAlarmText(point) }}
+              </el-tag>
             </div>
             <div class="point-location">
-              <el-icon><Location /></el-icon> {{ point.location }}
+              <el-icon><Location /></el-icon>
+              <span>{{ point.equipmentLocation || '暂无位置' }}</span>
+            </div>
+            <div class="point-meta">
+              <span>编号:{{ point.equipmentCode || '-' }}</span>
+              <span>类型:{{ point.equipmentTypeName || '-' }}</span>
+            </div>
+            <div class="point-status-row">
+              <el-tag size="small" effect="plain" :type="getOnlineTagType(point.equipmentStatus?.onlineStatus)">
+                {{ getOnlineText(point.equipmentStatus?.onlineStatus) }}
+              </el-tag>
+              <el-tag size="small" effect="plain" :type="getUseStatusTagType(point.equipmentStatus?.currentStatus)">
+                {{ getUseStatusText(point.equipmentStatus?.currentStatus) }}
+              </el-tag>
             </div>
-            <div class="point-device">{{ point.deviceModel }}</div>
           </div>
+
+          <el-empty
+            v-if="!listLoading && !filteredPoints.length"
+            description="暂无井盖监测设备"
+            :image-size="120"
+          />
         </div>
       </div>
 
-      <!-- 右侧:阈值配置详情区 -->
-      <div class="config-panel" v-if="selectedPoint">
-        <!-- 设备基本信息卡片 -->
+      <div v-if="selectedPoint" v-loading="detailLoading" class="config-panel">
         <div class="info-card">
           <div class="card-title">
-            <span><el-icon><Monitor /></el-icon> 监测设备基本信息</span>
+            <span><el-icon><Monitor /></el-icon> 设备基本信息</span>
           </div>
           <el-descriptions :column="2" border>
-            <el-descriptions-item label="设备编号">{{ selectedPoint.deviceId }}</el-descriptions-item>
-            <el-descriptions-item label="设备类型">{{ selectedPoint.deviceType }}</el-descriptions-item>
-            <el-descriptions-item label="设备型号">{{ selectedPoint.deviceModel }}</el-descriptions-item>
-            <el-descriptions-item label="生产厂商">{{ selectedPoint.manufacturer }}</el-descriptions-item>
-            <el-descriptions-item label="安装日期">{{ selectedPoint.installDate }}</el-descriptions-item>
-            <el-descriptions-item label="通讯协议">{{ selectedPoint.protocol }}</el-descriptions-item>
-            <el-descriptions-item label="监测点ID">{{ selectedPoint.id }}</el-descriptions-item>
-            <el-descriptions-item label="最近校准">{{ selectedPoint.lastCalibration }}</el-descriptions-item>
+            <el-descriptions-item label="设备编号">{{ selectedDetail?.equipmentCode || '-' }}</el-descriptions-item>
+            <el-descriptions-item label="设备名称">{{ selectedDetail?.equipmentName || '-' }}</el-descriptions-item>
+            <el-descriptions-item label="设备类型">{{ selectedDetail?.equipmentTypeName || '-' }}</el-descriptions-item>
+            <el-descriptions-item label="设备型号">{{ selectedDetail?.equipmentModel || '-' }}</el-descriptions-item>
+            <el-descriptions-item label="生产厂商">{{ selectedDetail?.manufacturer || '-' }}</el-descriptions-item>
+            <el-descriptions-item label="安装时间">{{ formatDateTime(selectedDetail?.installTime) }}</el-descriptions-item>
+            <el-descriptions-item label="设备位置">{{ selectedDetail?.equipmentLocation || '-' }}</el-descriptions-item>
+            <el-descriptions-item label="维护人员">{{ selectedDetail?.maintainer || '-' }}</el-descriptions-item>
           </el-descriptions>
         </div>
 
-        <!-- 阈值配置区域 - 多参数标签页 -->
+        <div class="info-card">
+          <div class="card-title">
+            <span><el-icon><Warning /></el-icon> 最新监测状态</span>
+          </div>
+          <div class="status-grid">
+            <div class="status-item">
+              <div class="status-label">在线状态</div>
+              <div class="status-value">{{ getOnlineText(selectedDetail?.equipmentStatus?.onlineStatus) }}</div>
+            </div>
+            <div class="status-item">
+              <div class="status-label">报警状态</div>
+              <div class="status-value">{{ getAlarmText(selectedDetail) }}</div>
+            </div>
+            <div class="status-item">
+              <div class="status-label">倾斜角度</div>
+              <div class="status-value">{{ appendUnit(selectedDetail?.manholeData?.tiltAngle, '°') }}</div>
+            </div>
+            <div class="status-item">
+              <div class="status-label">电池电量</div>
+              <div class="status-value">{{ appendUnit(selectedDetail?.manholeData?.batteryLevel, '%') }}</div>
+            </div>
+            <div class="status-item">
+              <div class="status-label">信号强度</div>
+              <div class="status-value">{{ appendUnit(selectedDetail?.manholeData?.signalStrength, '%') }}</div>
+            </div>
+            <div class="status-item">
+              <div class="status-label">水浸状态</div>
+              <div class="status-value">{{ getBinaryAlarmText(selectedDetail?.manholeData?.waterInfiltrationAlarmStatus) }}</div>
+            </div>
+            <div class="status-item">
+              <div class="status-label">水位状态</div>
+              <div class="status-value">{{ getBinaryAlarmText(selectedDetail?.manholeData?.waterLevelAlarmStatus) }}</div>
+            </div>
+            <div class="status-item">
+              <div class="status-label">激活状态</div>
+              <div class="status-value">{{ selectedDetail?.manholeData?.activationStatus || '-' }}</div>
+            </div>
+          </div>
+        </div>
+
         <div class="threshold-card">
           <div class="card-title">
             <span><el-icon><Setting /></el-icon> 报警阈值设置</span>
             <div class="card-actions">
-              <el-button size="small" @click="resetThresholds">重置</el-button>
-              <el-button size="small" type="primary" @click="saveThresholds">保存设置</el-button>
+              <el-button :icon="Refresh" size="small" @click="resetThresholds">重置</el-button>
+              <el-button :loading="saveLoading" size="small" type="primary" @click="saveThresholds">保存设置</el-button>
             </div>
           </div>
 
-          <el-tabs v-model="activeTab" class="threshold-tabs">
-            <!-- 倾斜阈值 -->
-            <el-tab-pane label="倾斜监测" name="tilt">
-              <div class="threshold-config">
-                <div class="threshold-item">
-                  <div class="item-label">
-                    <span>倾斜角度阈值</span>
-                    <el-tooltip content="当井盖倾斜角度超过此值时触发报警" placement="top">
-                      <el-icon><QuestionFilled /></el-icon>
-                    </el-tooltip>
-                  </div>
-                  <div class="item-control">
-                    <el-input-number v-model="thresholds.tilt.warning" :min="0" :max="45" :step="0.5" size="default" style="width: 120px" />
-                    <span class="unit">度</span>
-                    <span class="level-tag warning">预警值</span>
-                    <el-input-number v-model="thresholds.tilt.critical" :min="0" :max="90" :step="0.5" size="default" style="width: 120px; margin-left: 20px" />
-                    <span class="unit">度</span>
-                    <span class="level-tag critical">告警值</span>
-                  </div>
-                  <div class="history-suggestion">
-                    <el-icon><DataLine /></el-icon>
-                    历史数据分析建议: 基于近30天数据,95%倾斜角在 <strong>{{ suggested.tilt.suggestedWarning }}°</strong> 以内,建议预警值 ≤ {{ suggested.tilt.suggestedWarning }}
-                  </div>
-                </div>
-              </div>
-            </el-tab-pane>
-
-            <!-- 水位/溢水阈值 -->
-            <el-tab-pane label="水位监测" name="water">
-              <div class="threshold-config">
-                <div class="threshold-item">
-                  <div class="item-label">水位深度阈值</div>
-                  <div class="item-control">
-                    <el-input-number v-model="thresholds.water.warning" :min="0" :max="100" :step="1" style="width: 120px" />
-                    <span class="unit">cm</span>
-                    <span class="level-tag warning">预警值</span>
-                    <el-input-number v-model="thresholds.water.critical" :min="0" :max="150" :step="1" style="width: 120px; margin-left: 20px" />
-                    <span class="unit">cm</span>
-                    <span class="level-tag critical">告警值</span>
-                  </div>
-                  <div class="history-suggestion">
-                    <el-icon><DataLine /></el-icon>
-                    历史最高水位: {{ selectedPoint.historyData?.maxWaterLevel || 32 }}cm,建议告警值设置在 {{ suggested.water.suggestedCritical }}cm
-                  </div>
-                </div>
-              </div>
-            </el-tab-pane>
-
-            <!-- 震动阈值 -->
-            <el-tab-pane label="震动监测" name="vibration">
-              <div class="threshold-config">
-                <div class="threshold-item">
-                  <div class="item-label">震动强度阈值</div>
-                  <div class="item-control">
-                    <el-input-number v-model="thresholds.vibration.warning" :min="0" :max="200" :step="5" style="width: 120px" />
-                    <span class="unit">mg</span>
-                    <span class="level-tag warning">预警值</span>
-                    <el-input-number v-model="thresholds.vibration.critical" :min="0" :max="300" :step="5" style="width: 120px; margin-left: 20px" />
-                    <span class="unit">mg</span>
-                    <span class="level-tag critical">告警值</span>
-                  </div>
-                  <div class="history-suggestion">
-                    <el-icon><DataLine /></el-icon>
-                    历史震动峰值: {{ selectedPoint.historyData?.maxVibration || 78 }}mg,建议阈值动态调整
-                  </div>
-                </div>
-              </div>
-            </el-tab-pane>
+          <div class="threshold-tip">
+            当前版本先对接后端已支持的 3 类阈值:倾斜、水位、温度。
+          </div>
 
-            <!-- 气体阈值 -->
-            <el-tab-pane label="有害气体" name="gas">
-              <div class="threshold-config">
-                <div class="threshold-item">
-                  <div class="item-label">H₂S浓度阈值</div>
-                  <div class="item-control">
-                    <el-input-number v-model="thresholds.gas.warning" :min="0" :max="50" :step="1" style="width: 120px" />
-                    <span class="unit">ppm</span>
-                    <span class="level-tag warning">预警值</span>
-                    <el-input-number v-model="thresholds.gas.critical" :min="0" :max="100" :step="1" style="width: 120px; margin-left: 20px" />
-                    <span class="unit">ppm</span>
-                    <span class="level-tag critical">告警值</span>
-                  </div>
-                </div>
-              </div>
-            </el-tab-pane>
+          <el-tabs v-model="activeTab" class="threshold-tabs">
+            <el-tab-pane
+              v-for="item in thresholdConfigList"
+              :key="item.key"
+              :label="item.label"
+              :name="item.key"
+            >
+              <div class="threshold-item">
+                <div class="item-label">{{ item.desc }}</div>
+                <div class="item-control">
+                  <span class="field-label">最小值</span>
+                  <el-input-number
+                    v-model="thresholds[item.key].minValue"
+                    :controls="false"
+                    :precision="item.precision"
+                    :step="item.step"
+                    class="threshold-input"
+                    placeholder="未设置"
+                  />
+                  <span class="unit">{{ item.unit }}</span>
 
-            <!-- 电池/信号阈值 -->
-            <el-tab-pane label="设备状态" name="device">
-              <div class="threshold-config">
-                <div class="threshold-item">
-                  <div class="item-label">电池电压阈值</div>
-                  <div class="item-control">
-                    <el-input-number v-model="thresholds.battery.warning" :min="0" :max="100" :step="5" style="width: 120px" />
-                    <span class="unit">%</span>
-                    <span class="level-tag warning">低电量预警</span>
-                  </div>
+                  <span class="field-label">最大值</span>
+                  <el-input-number
+                    v-model="thresholds[item.key].maxValue"
+                    :controls="false"
+                    :precision="item.precision"
+                    :step="item.step"
+                    class="threshold-input"
+                    placeholder="未设置"
+                  />
+                  <span class="unit">{{ item.unit }}</span>
                 </div>
-                <div class="threshold-item">
-                  <div class="item-label">信号强度阈值</div>
-                  <div class="item-control">
-                    <el-input-number v-model="thresholds.signal.warning" :min="0" :max="100" :step="5" style="width: 120px" />
-                    <span class="unit">%</span>
-                    <span class="level-tag warning">弱信号预警</span>
-                  </div>
+                <el-input
+                  v-model="thresholds[item.key].remark"
+                  maxlength="100"
+                  placeholder="请输入备注"
+                />
+                <div class="saved-hint">
+                  预警编码:{{ item.warningCode }}
+                  <span v-if="thresholds[item.key].id">,当前记录ID:{{ thresholds[item.key].id }}</span>
                 </div>
               </div>
             </el-tab-pane>
           </el-tabs>
         </div>
-
-        <!-- 历史监测数据趋势图 (帮助科学设置阈值) -->
-        <div class="history-chart-card">
-          <div class="card-title">
-            <span><el-icon><TrendCharts /></el-icon> 历史监测数据趋势(近30天)</span>
-            <el-select v-model="historyMetric" size="small" style="width: 120px">
-              <el-option label="倾斜角度" value="tilt" />
-              <el-option label="水位深度" value="water" />
-              <el-option label="震动强度" value="vibration" />
-              <el-option label="气体浓度" value="gas" />
-            </el-select>
-          </div>
-          <div class="chart-container">
-            <v-chart :option="historyChartOption" autoresize />
-          </div>
-
-        </div>
       </div>
 
-      <!-- 未选中状态 -->
       <div v-else class="empty-panel">
-        <el-empty description="请从左侧选择一个监测点查看阈值配置" :image-size="140" />
+        <el-empty description="请从左侧选择设备查看阈值配置" :image-size="140" />
       </div>
     </div>
   </div>
 </template>
 
 <script setup>
-import { ref, computed, watch } from 'vue'
-import { Location, Monitor, Setting, QuestionFilled, DataLine, TrendCharts, Document, Check, Search } from '@element-plus/icons-vue'
-import { use } from 'echarts/core'
-import { CanvasRenderer } from 'echarts/renderers'
-import { LineChart } from 'echarts/charts'
-import { TitleComponent, TooltipComponent, LegendComponent, GridComponent, ToolboxComponent } from 'echarts/components'
+import {computed, onMounted, ref} from 'vue'
+import {ElMessage} from 'element-plus'
+import {Location, Monitor, Refresh, Search, Setting, Warning} from '@element-plus/icons-vue'
+import {
+  getManholeAlarmDeviceDetail,
+  getManholeAlarmDeviceList,
+  saveManholeAlarmThreshold
+} from '@/api/pipeNetwork/basic'
 
-use([CanvasRenderer, LineChart, TitleComponent, TooltipComponent, LegendComponent, GridComponent, ToolboxComponent])
-
-// 模拟监测点数据
-const monitoringPoints = ref([
+const thresholdConfigList = [
   {
-    id: 'MP-1001',
-    name: '人民路监测点',
-    location: '人民路与解放路口东50m',
-    status: '正常',
-    deviceId: 'MN-1001',
-    deviceType: '智能井盖监测仪',
-    deviceModel: 'WATCHMAN-S2',
-    manufacturer: '华盾科技',
-    installDate: '2024-03-15',
-    protocol: 'LoRaWAN',
-    lastCalibration: '2025-02-10',
-    historyData: {
-      tiltHistory: [1.2, 1.5, 2.1, 1.8, 2.3, 3.2, 2.8, 2.5, 3.0, 2.2, 1.9, 2.4, 3.5, 4.1, 3.8, 3.2, 2.9, 3.3, 4.5, 5.2, 4.8, 4.2, 3.6, 3.9, 4.3, 5.0, 4.6, 4.1, 3.7, 4.2],
-      waterHistory: [5, 6, 7, 8, 10, 12, 15, 18, 20, 22, 25, 28, 30, 32, 35, 38, 36, 34, 33, 35, 37, 40, 42, 39, 36, 34, 32, 30, 28, 26],
-      vibrationHistory: [12, 15, 18, 22, 25, 30, 35, 42, 48, 52, 58, 62, 65, 70, 68, 72, 75, 78, 80, 76, 72, 68, 64, 60, 55, 50, 45, 40, 38, 35],
-      gasHistory: [0.5, 0.8, 1.2, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0, 6.5, 7.0, 7.5, 8.0, 8.5, 9.0, 9.5, 10.0, 9.8, 9.5, 9.0, 8.5, 8.0, 7.5, 7.0, 6.5, 6.0],
-      maxWaterLevel: 42,
-      maxVibration: 80
-    }
+    key: 'tilt',
+    label: '倾斜监测',
+    warningType: '倾斜预警',
+    warningCode: 'WARN-TILT',
+    unit: '°',
+    step: 0.1,
+    precision: 1,
+    desc: '当倾斜角度小于等于最小值或大于等于最大值时触发报警'
   },
   {
-    id: 'MP-1002',
-    name: '滨江路监测点',
-    location: '滨江路化工园区南门',
-    status: '告警',
-    deviceId: 'MN-1002',
-    deviceType: '防爆井盖监测仪',
-    deviceModel: 'WATCHMAN-EX',
-    manufacturer: '华盾科技',
-    installDate: '2024-06-20',
-    protocol: 'NB-IoT',
-    lastCalibration: '2025-01-15',
-    historyData: {
-      tiltHistory: [0.5, 0.8, 1.0, 1.2, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 5.2, 6.5, 7.8, 8.2, 9.0, 8.5, 7.5, 6.8, 5.5, 4.8, 4.0, 3.5, 3.0, 2.5, 2.0, 1.8, 1.5, 1.2, 1.0, 0.8],
-      waterHistory: [15, 18, 22, 28, 35, 42, 50, 58, 62, 55, 48, 45, 42, 40, 38, 35, 32, 30, 28, 25, 22, 20, 18, 16, 15, 14, 13, 12, 11, 10],
-      vibrationHistory: [20, 25, 32, 40, 48, 55, 62, 70, 78, 85, 92, 98, 105, 110, 115, 108, 100, 92, 85, 78, 70, 62, 55, 48, 42, 38, 35, 32, 28, 25],
-      gasHistory: [2, 3, 5, 7, 10, 15, 20, 25, 30, 28, 25, 22, 20, 18, 16, 15, 14, 12, 10, 9, 8, 7, 6, 5, 4, 3.5, 3, 2.5, 2, 1.5],
-      maxWaterLevel: 62,
-      maxVibration: 115
-    }
+    key: 'waterLevel',
+    label: '水位监测',
+    warningType: '水位预警',
+    warningCode: 'WARN-WATER-LEVEL',
+    unit: 'cm',
+    step: 0.1,
+    precision: 1,
+    desc: '用于井盖水位报警阈值配置'
   },
   {
-    id: 'MP-1003',
-    name: '高新区监测点',
-    location: '高新大道云计算中心旁',
-    status: '正常',
-    deviceId: 'MN-1004',
-    deviceType: '智能井盖监测仪',
-    deviceModel: 'WATCHMAN-S2',
-    manufacturer: '华盾科技',
-    installDate: '2024-09-10',
-    protocol: 'LoRaWAN',
-    lastCalibration: '2025-03-01',
-    historyData: {
-      tiltHistory: [0.3, 0.5, 0.4, 0.6, 0.8, 0.7, 0.9, 1.0, 0.8, 0.6, 0.5, 0.7, 0.9, 1.1, 1.0, 0.8, 0.6, 0.5, 0.4, 0.6, 0.8, 0.7, 0.5, 0.3, 0.4, 0.6, 0.9, 1.2, 0.9, 0.7],
-      waterHistory: [3, 4, 5, 6, 5, 7, 8, 6, 5, 4, 5, 6, 7, 8, 9, 10, 8, 7, 6, 5, 4, 3, 4, 5, 6, 7, 8, 9, 7, 6],
-      vibrationHistory: [8, 10, 12, 15, 14, 16, 18, 20, 18, 15, 12, 10, 11, 13, 15, 17, 16, 14, 12, 10, 9, 8, 7, 9, 11, 13, 15, 14, 12, 10],
-      gasHistory: [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.5, 0.4, 0.3, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.5, 0.4, 0.3, 0.2],
-      maxWaterLevel: 10,
-      maxVibration: 20
-    }
+    key: 'temperature',
+    label: '温度监测',
+    warningType: '温度预警',
+    warningCode: 'WARN-TEMPERATURE',
+    unit: '℃',
+    step: 0.1,
+    precision: 1,
+    desc: '用于设备温度报警阈值配置'
   }
-])
+]
 
 const searchKeyword = ref('')
-const selectedPoint = ref(monitoringPoints.value[0])
 const activeTab = ref('tilt')
-const historyMetric = ref('tilt')
-
-// 当前阈值配置
-const thresholds = ref({
-  tilt: { warning: 5, critical: 10 },
-  water: { warning: 25, critical: 40 },
-  vibration: { warning: 60, critical: 100 },
-  gas: { warning: 10, critical: 20 },
-  battery: { warning: 20 },
-  signal: { warning: 30 }
-})
+const listLoading = ref(false)
+const detailLoading = ref(false)
+const saveLoading = ref(false)
+const deviceList = ref([])
+const selectedPoint = ref(null)
+const selectedDetail = ref(null)
+const thresholds = ref(createEmptyThresholds())
 
-// 智能建议值(基于历史数据)
-const suggested = computed(() => {
-  const hist = selectedPoint.value?.historyData
-  if (!hist) return { tilt: { suggestedWarning: 5 }, water: { suggestedCritical: 40 }, vibration: {} }
-  const tilt95 = hist.tiltHistory ? Math.ceil(hist.tiltHistory.sort((a,b)=>a-b)[Math.floor(hist.tiltHistory.length*0.95)] * 10) / 10 : 5
-  const waterMax = hist.maxWaterLevel || 40
-  return {
-    tilt: { suggestedWarning: Math.min(tilt95 + 1, 12) },
-    water: { suggestedCritical: Math.min(waterMax + 5, 80) },
-    vibration: { suggestedPeak: hist.maxVibration || 80 }
-  }
-})
+function createEmptyThresholds() {
+  return thresholdConfigList.reduce((acc, item) => {
+    acc[item.key] = {
+      id: '',
+      minValue: null,
+      maxValue: null,
+      remark: ''
+    }
+    return acc
+  }, {})
+}
+
+function normalizeThresholdList(list) {
+  const safeList = Array.isArray(list) ? list : []
+  const result = createEmptyThresholds()
+  thresholdConfigList.forEach((item) => {
+    const matched = safeList.find(threshold => threshold.warningCode === item.warningCode)
+    if (matched) {
+      result[item.key] = {
+        id: matched.id || '',
+        minValue: matched.minValue ?? null,
+        maxValue: matched.maxValue ?? null,
+        remark: matched.remark || ''
+      }
+    }
+  })
+  return result
+}
 
 const filteredPoints = computed(() => {
-  if (!searchKeyword.value) return monitoringPoints.value
-  const kw = searchKeyword.value.toLowerCase()
-  return monitoringPoints.value.filter(p =>
-      p.name.toLowerCase().includes(kw) ||
-      p.deviceId.toLowerCase().includes(kw) ||
-      p.location.toLowerCase().includes(kw)
-  )
+  const keyword = searchKeyword.value.trim().toLowerCase()
+  if (!keyword) return deviceList.value
+  return deviceList.value.filter(item => {
+    const name = `${item.equipmentName || ''}`.toLowerCase()
+    const code = `${item.equipmentCode || ''}`.toLowerCase()
+    const location = `${item.equipmentLocation || ''}`.toLowerCase()
+    return name.includes(keyword) || code.includes(keyword) || location.includes(keyword)
+  })
 })
 
-const getPointStatusType = (point) => point.status === '正常' ? 'success' : 'danger'
+function getOnlineText(status) {
+  return Number(status) === 1 ? '在线' : '离线'
+}
 
-const selectPoint = (point) => {
-  selectedPoint.value = point
-  // 加载该点已有阈值(演示用,模拟从后端加载)
-  if (point.id === 'MP-1002') {
-    thresholds.value = { tilt: { warning: 8, critical: 15 }, water: { warning: 35, critical: 55 }, vibration: { warning: 80, critical: 120 }, gas: { warning: 15, critical: 25 }, battery: { warning: 20 }, signal: { warning: 30 } }
-  } else {
-    thresholds.value = { tilt: { warning: 5, critical: 10 }, water: { warning: 25, critical: 40 }, vibration: { warning: 60, critical: 100 }, gas: { warning: 10, critical: 20 }, battery: { warning: 20 }, signal: { warning: 30 } }
-  }
+function getOnlineTagType(status) {
+  return Number(status) === 1 ? 'success' : 'info'
+}
+
+function getAlarmText(point) {
+  return Number(point?.equipmentStatus?.alarmStatus) === 1 ? '报警' : '正常'
 }
 
-const historyChartOption = computed(() => {
-  const point = selectedPoint.value
-  if (!point) return {}
-  let data = []
-  let yName = ''
-  let color = '#409eff'
-  switch (historyMetric.value) {
-    case 'tilt': data = point.historyData.tiltHistory; yName = '倾斜角度 (°)'; color = '#e6a23c'; break
-    case 'water': data = point.historyData.waterHistory; yName = '水位深度 (cm)'; color = '#3b82f6'; break
-    case 'vibration': data = point.historyData.vibrationHistory; yName = '震动强度 (mg)'; color = '#f56c6c'; break
-    case 'gas': data = point.historyData.gasHistory; yName = '气体浓度 (ppm)'; color = '#9b59b6'; break
+function getAlarmTagType(point) {
+  return Number(point?.equipmentStatus?.alarmStatus) === 1 ? 'danger' : 'success'
+}
+
+function getUseStatusText(status) {
+  const map = {
+    1: '在用',
+    2: '闲置',
+    3: '维修',
+    4: '报废',
+    5: '待入库'
   }
-  const days = data.map((_, idx) => `Day ${idx + 1}`)
-  return {
-    tooltip: { trigger: 'axis' },
-    xAxis: { type: 'category', data: days, axisLabel: { rotate: 45, interval: 5 } },
-    yAxis: { type: 'value', name: yName },
-    series: [{ data, type: 'line', smooth: true, lineStyle: { color, width: 2 }, areaStyle: { opacity: 0.1, color }, symbol: 'circle', symbolSize: 6 }],
-    grid: { containLabel: true, left: '10%' }
+  return map[Number(status)] || '未知'
+}
+
+function getUseStatusTagType(status) {
+  const map = {
+    1: 'success',
+    2: 'info',
+    3: 'warning',
+    4: 'danger',
+    5: ''
   }
-})
+  return map[Number(status)] || 'info'
+}
 
-const getMetricName = (metric) => {
-  const map = { tilt: '倾斜角度', water: '水位深度', vibration: '震动强度', gas: '气体浓度' }
-  return map[metric]
+function getBinaryAlarmText(status) {
+  return `${status}` === '1' ? '报警' : `${status}` === '0' ? '正常' : '-'
 }
 
-const getSuggestedThreshold = () => {
-  const metric = historyMetric.value
-  if (metric === 'tilt') return `${suggested.value.tilt.suggestedWarning}°`
-  if (metric === 'water') return `${suggested.value.water.suggestedCritical}cm`
-  if (metric === 'vibration') return `${selectedPoint.value?.historyData?.maxVibration || 80}mg`
-  return '科学建议值'
+function appendUnit(value, unit) {
+  if (value === undefined || value === null || value === '') return '-'
+  return `${value}${unit}`
 }
 
-const applySuggestedThreshold = () => {
-  const metric = historyMetric.value
-  if (metric === 'tilt') thresholds.value.tilt.warning = suggested.value.tilt.suggestedWarning
-  if (metric === 'water') thresholds.value.water.critical = suggested.value.water.suggestedCritical
-  if (metric === 'vibration') thresholds.value.vibration.critical = (selectedPoint.value?.historyData?.maxVibration || 80) + 10
-  if (metric === 'gas') thresholds.value.gas.warning = 12
-  ElMessage.success('已应用AI建议阈值')
+function formatDateTime(value) {
+  return value || '-'
 }
 
-const resetThresholds = () => {
-  selectPoint(selectedPoint.value)
-  ElMessage.info('已重置为当前监测点默认阈值')
+async function loadDeviceList() {
+  listLoading.value = true
+  try {
+    const res = await getManholeAlarmDeviceList({
+      isQueryManholeData: true
+    })
+    const list = Array.isArray(res?.data) ? res.data : []
+    deviceList.value = list
+
+    if (!list.length) {
+      selectedPoint.value = null
+      selectedDetail.value = null
+      thresholds.value = createEmptyThresholds()
+      return
+    }
+
+    const currentId = selectedPoint.value?.equipmentId
+    const currentPoint = list.find(item => item.equipmentId === currentId) || list[0]
+    await selectPoint(currentPoint)
+  } catch (error) {
+    deviceList.value = []
+    selectedPoint.value = null
+    selectedDetail.value = null
+  } finally {
+    listLoading.value = false
+  }
 }
 
-const saveThresholds = () => {
-  ElMessage.success('阈值配置已保存,将即时生效')
+async function selectPoint(point) {
+  if (!point?.equipmentId) return
+  selectedPoint.value = point
+  await loadDeviceDetail(point.equipmentId)
 }
 
-const exportConfig = () => {
-  ElMessage.info('导出阈值配置文件(模拟)')
+async function loadDeviceDetail(id) {
+  detailLoading.value = true
+  try {
+    const res = await getManholeAlarmDeviceDetail(id)
+    selectedDetail.value = res?.data || null
+    thresholds.value = normalizeThresholdList(selectedDetail.value?.warningThresholdList)
+  } catch (error) {
+    selectedDetail.value = null
+    thresholds.value = createEmptyThresholds()
+  } finally {
+    detailLoading.value = false
+  }
+}
+
+async function resetThresholds() {
+  if (!selectedPoint.value?.equipmentId) return
+  await loadDeviceDetail(selectedPoint.value.equipmentId)
+  ElMessage.success('已恢复为服务端最新阈值')
 }
 
-const applyAllThresholds = () => {
-  ElMessage.success('已对所有监测点应用推荐阈值(演示)')
+function hasThresholdValue(item) {
+  return (
+    (item.minValue !== null && item.minValue !== undefined) ||
+    (item.maxValue !== null && item.maxValue !== undefined)
+  )
+}
+
+async function saveThresholds() {
+  if (!selectedDetail.value?.equipmentCode) {
+    ElMessage.warning('未获取到设备编码,无法保存')
+    return
+  }
+
+  const invalidConfigs = thresholdConfigList.filter((config) => {
+    const formItem = thresholds.value[config.key]
+    return formItem.id && !hasThresholdValue(formItem)
+  })
+
+  if (invalidConfigs.length) {
+    ElMessage.warning(`${invalidConfigs[0].label}请至少保留一个阈值`)
+    return
+  }
+
+  const payloads = thresholdConfigList
+    .map((config) => {
+      const formItem = thresholds.value[config.key]
+      if (!hasThresholdValue(formItem) && !formItem.id) return null
+      return {
+        id: formItem.id || undefined,
+        deviceCode: selectedDetail.value.equipmentCode,
+        warningType: config.warningType,
+        warningCode: config.warningCode,
+        minValue: formItem.minValue,
+        maxValue: formItem.maxValue,
+        remark: formItem.remark || ''
+      }
+    })
+    .filter(Boolean)
+
+  if (!payloads.length) {
+    ElMessage.warning('请先填写至少一项阈值')
+    return
+  }
+
+  saveLoading.value = true
+  try {
+    await Promise.all(payloads.map(item => saveManholeAlarmThreshold(item)))
+    ElMessage.success('阈值保存成功')
+    await loadDeviceDetail(selectedPoint.value.equipmentId)
+  } finally {
+    saveLoading.value = false
+  }
 }
 
-import { ElMessage } from 'element-plus'
+onMounted(() => {
+  loadDeviceList()
+})
 </script>
 
 <style scoped>
@@ -391,99 +441,198 @@ import { ElMessage } from 'element-plus'
   background: #f0f2f6;
   min-height: 100vh;
 }
-.page-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  background: white;
-  padding: 16px 24px;
-  border-radius: 16px;
-  margin-bottom: 20px;
-}
-.header-left h2 { margin: 0 0 6px 0; font-weight: 600; }
+
 .main-content {
   display: flex;
   gap: 20px;
 }
+
 .point-list-panel {
-  width: 320px;
-  background: white;
+  width: 340px;
+  background: #fff;
   border-radius: 16px;
   padding: 16px;
   display: flex;
   flex-direction: column;
+  gap: 12px;
 }
+
 .panel-title {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  margin-bottom: 16px;
   font-weight: 600;
 }
+
 .point-list {
-  max-height: calc(100vh - 200px);
+  max-height: calc(100vh - 180px);
   overflow-y: auto;
 }
+
 .point-item {
+  border: 1px solid #e5e7eb;
+  border-radius: 12px;
   padding: 14px;
   margin-bottom: 12px;
-  border-radius: 12px;
   cursor: pointer;
-  transition: all 0.2s;
-  border: 1px solid #e4e7ed;
-}
-.point-item:hover { background: #f5f7fa; transform: translateX(2px); }
-.point-item.active { background: #ecf5ff; border-color: #409eff; }
-.point-header { display: flex; justify-content: space-between; margin-bottom: 6px; }
-.point-name { font-weight: 600; }
-.point-location { font-size: 12px; color: #6c757d; display: flex; align-items: center; gap: 4px; margin-bottom: 6px; }
-.point-device { font-size: 12px; color: #409eff; }
+  transition: all 0.2s ease;
+}
+
+.point-item:hover {
+  background: #f8fbff;
+  border-color: #bfdbfe;
+}
+
+.point-item.active {
+  background: #ecf5ff;
+  border-color: #409eff;
+}
+
+.point-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 8px;
+}
+
+.point-name {
+  font-weight: 600;
+  color: #303133;
+}
+
+.point-location {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  color: #606266;
+  font-size: 12px;
+  margin-bottom: 8px;
+}
+
+.point-meta {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+  font-size: 12px;
+  color: #409eff;
+  margin-bottom: 8px;
+}
+
+.point-status-row {
+  display: flex;
+  gap: 8px;
+}
+
 .config-panel {
   flex: 1;
   display: flex;
   flex-direction: column;
   gap: 20px;
 }
-.info-card, .threshold-card, .history-chart-card {
-  background: white;
+
+.info-card,
+.threshold-card {
+  background: #fff;
   border-radius: 16px;
-  padding: 16px 20px;
-  box-shadow: 0 2px 8px rgba(0,0,0,0.04);
+  padding: 18px 20px;
+  box-shadow: 0 2px 8px rgba(15, 23, 42, 0.04);
 }
+
 .card-title {
   display: flex;
   justify-content: space-between;
   align-items: center;
   margin-bottom: 16px;
+  padding-left: 12px;
+  border-left: 4px solid #409eff;
+  font-size: 16px;
   font-weight: 600;
+}
+
+.card-actions {
+  display: flex;
+  gap: 8px;
+}
+
+.status-grid {
+  display: grid;
+  grid-template-columns: repeat(4, minmax(0, 1fr));
+  gap: 12px;
+}
+
+.status-item {
+  background: #f8fafc;
+  border: 1px solid #edf2f7;
+  border-radius: 12px;
+  padding: 14px;
+}
+
+.status-label {
+  color: #909399;
+  font-size: 13px;
+  margin-bottom: 8px;
+}
+
+.status-value {
+  color: #303133;
   font-size: 16px;
-  border-left: 4px solid #409eff;
-  padding-left: 12px;
+  font-weight: 600;
 }
+
+.threshold-tip {
+  margin-bottom: 12px;
+  padding: 10px 12px;
+  border-radius: 8px;
+  background: #f4f9ff;
+  color: #606266;
+  font-size: 13px;
+}
+
 .threshold-item {
-  margin-bottom: 24px;
-  padding-bottom: 16px;
-  border-bottom: 1px solid #f0f0f0;
+  padding: 8px 4px 4px;
 }
+
 .item-label {
-  font-weight: 500;
-  margin-bottom: 10px;
+  margin-bottom: 16px;
+  color: #606266;
+  font-size: 14px;
+}
+
+.item-control {
   display: flex;
   align-items: center;
-  gap: 6px;
+  flex-wrap: wrap;
+  gap: 8px;
+  margin-bottom: 16px;
 }
-.item-control {
+
+.field-label {
+  color: #303133;
+  font-weight: 500;
+}
+
+.threshold-input {
+  width: 160px;
+}
+
+.unit {
+  color: #909399;
+  margin-right: 16px;
+}
+
+.saved-hint {
+  margin-top: 10px;
+  color: #909399;
+  font-size: 12px;
+}
+
+.empty-panel {
+  flex: 1;
   display: flex;
+  justify-content: center;
   align-items: center;
-  gap: 6px;
-  margin-bottom: 12px;
+  background: #fff;
+  border-radius: 16px;
 }
-.unit { margin-right: 12px; color: #6c757d; }
-.level-tag { font-size: 12px; padding: 2px 8px; border-radius: 12px; margin-left: 8px; }
-.level-tag.warning { background: #fdf6ec; color: #e6a23c; }
-.level-tag.critical { background: #fef0f0; color: #f56c6c; }
-.history-suggestion { background: #f8f9fc; padding: 8px 12px; border-radius: 8px; font-size: 13px; display: flex; align-items: center; gap: 8px; color: #606266; }
-.chart-container { height: 260px; margin: 12px 0; }
-.ai-suggestion { display: flex; justify-content: space-between; align-items: center; margin-top: 12px; }
-.empty-panel { flex: 1; display: flex; align-items: center; justify-content: center; background: white; border-radius: 16px; }
-</style>
+</style>