Просмотр исходного кода

重构燃气监测报警一张图

zengy 1 день назад
Родитель
Сommit
c8cf0f3409

+ 1 - 8
src/api/pipeNetwork/basic.js

@@ -69,10 +69,6 @@ export function getManholeDevicePage(data) {
 export function getManholeDeviceStatistics(data) {
 export function getManholeDeviceStatistics(data) {
   return request({ url: '/manhole/device/statistics', method: 'post', data })
   return request({ url: '/manhole/device/statistics', method: 'post', data })
 }
 }
-export function getManholeAlarmDeviceList(data = {}) {
-  return request({ url: '/manhole/device/getManholeDataList', method: 'post', data })
-}
-
 // ============ 设备 GIS 地图(按 rootTypeId 统一查询供水/窨井等) ============
 // ============ 设备 GIS 地图(按 rootTypeId 统一查询供水/窨井等) ============
 // rootTypeId: 1-供水设备 4-窨井设备(空=全部),对应后端 EquipmentGISController
 // rootTypeId: 1-供水设备 4-窨井设备(空=全部),对应后端 EquipmentGISController
 export function getEquipmentGisPoints(params) {
 export function getEquipmentGisPoints(params) {
@@ -83,7 +79,7 @@ export function getEquipmentGisPoints(params) {
 export function getManholeRelDeviceData(equipmentId) {
 export function getManholeRelDeviceData(equipmentId) {
   return request({ url: '/manhole/device/rel/data/' + equipmentId, method: 'get' })
   return request({ url: '/manhole/device/rel/data/' + equipmentId, method: 'get' })
 }
 }
-// 态势图层 - 获取设备列表和统计(专用于 alarmSanalysis.vue)
+// 态势图层 - 获取设备列表和统计(窨井监测一张图等共用,alarmSanalysis 已下线)
 export function getManholeLayerData(data = {}) {
 export function getManholeLayerData(data = {}) {
   return request({ url: '/manhole/layer/getManholeLayerData', method: 'post', data })
   return request({ url: '/manhole/layer/getManholeLayerData', method: 'post', data })
 }
 }
@@ -145,9 +141,6 @@ export function getManholeAlarmMonitorList(data = {}) {
   return request({ url: '/manhole/device/getAlarmMonitorList', method: 'post', data })
   return request({ url: '/manhole/device/getAlarmMonitorList', method: 'post', data })
 }
 }
 
 
-export function getManholeAlarmDeviceDetail(id) {
-  return request({ url: '/manhole/device/getById/' + id, method: 'get' })
-}
 export function getManholeTerminalFaultPage(data) {
 export function getManholeTerminalFaultPage(data) {
   return request({ url: '/manhole/terminalFault/findByPage', method: 'post', data })
   return request({ url: '/manhole/terminalFault/findByPage', method: 'post', data })
 }
 }

+ 0 - 517
src/views/subSystem/gas/gasjcyj/rqjcbjyzt.vue

@@ -1,517 +0,0 @@
-<!--燃气监测报警一张图-->
-<template>
-  <div class="gas-alert-gis">
-    <!-- 页面头部 -->
-    <div class="page-header">
-      <div class="header-left">
-        <el-icon><MapLocation /></el-icon>
-        <span class="title">燃气安全监测报警图</span>
-        <div class="stats-badge">
-          <span class="stat-item critical"><i class="dot"></i>严重报警 {{ criticalCount }}</span>
-          <span class="stat-item warning"><i class="dot"></i>预警 {{ warningCount }}</span>
-          <span class="stat-item"><i class="dot"></i>监测点位 {{ totalPoints }}</span>
-        </div>
-      </div>
-      <div class="header-right">
-        <el-input v-model="searchKeyword" placeholder="搜索设施/地址" clearable :prefix-icon="Search" style="width:200px" @input="onSearch" />
-        <el-button type="primary" plain :icon="Refresh" @click="refreshData">刷新</el-button>
-        <el-button :icon="FullScreen" @click="fitBounds">全图</el-button>
-      </div>
-    </div>
-
-    <!-- 主内容区:左侧图层控制 + 右侧GIS地图 -->
-    <div class="main-layout">
-      <!-- 左侧图层控制面板 -->
-      <div class="layer-panel">
-        <div class="layer-header">报警态势图层</div>
-        <div class="layer-tree">
-          <el-checkbox-group v-model="visibleLayers" @change="onLayerChange">
-            <div class="layer-group">
-              <div class="group-title">燃气管网</div>
-              <el-checkbox value="highPressure">高压管线</el-checkbox>
-              <el-checkbox value="mediumPressure">中压管线</el-checkbox>
-              <el-checkbox value="lowPressure">低压管线</el-checkbox>
-            </div>
-            <div class="layer-group">
-              <div class="group-title">地下空间气体报警</div>
-              <el-checkbox value="gasCritical">严重报警(&gt;10%LEL)</el-checkbox>
-              <el-checkbox value="gasWarning">预警(5-10%LEL)</el-checkbox>
-              <el-checkbox value="gasNormal">正常(&lt;5%LEL)</el-checkbox>
-            </div>
-            <div class="layer-group">
-              <div class="group-title">管线压力/流量报警</div>
-              <el-checkbox value="pressureAlert">压力报警</el-checkbox>
-              <el-checkbox value="flowAlert">流量报警</el-checkbox>
-            </div>
-            <div class="layer-group">
-              <div class="group-title">场站燃气报警</div>
-              <el-checkbox value="stationCritical">场站严重报警</el-checkbox>
-              <el-checkbox value="stationWarning">场站预警</el-checkbox>
-            </div>
-          </el-checkbox-group>
-        </div>
-
-        <div class="legend-panel">
-          <div class="legend-title">报警图例</div>
-          <div><span class="legend-dot gas-critical"></span> 气体严重报警</div>
-          <div><span class="legend-dot gas-warning"></span> 气体预警</div>
-          <div><span class="legend-dot pressure"></span> 压力报警</div>
-          <div><span class="legend-dot flow"></span> 流量报警</div>
-          <div><span class="legend-dot station-critical"></span> 场站严重报警</div>
-          <div><span class="legend-dot station-warning"></span> 场站预警</div>
-          <div><span class="legend-line high"></span> 高压管线</div>
-          <div><span class="legend-line medium"></span> 中压管线</div>
-        </div>
-
-        <!-- 实时统计 -->
-        <div class="stats-panel">
-          <div class="stats-title">实时报警统计</div>
-          <div class="stats-row"><span>地下空间气体报警:</span><span class="value critical">{{ gasAlertCount }}</span></div>
-          <div class="stats-row"><span>管线压力报警:</span><span class="value warning">{{ pressureAlertCount }}</span></div>
-          <div class="stats-row"><span>管线流量报警:</span><span class="value warning">{{ flowAlertCount }}</span></div>
-          <div class="stats-row"><span>场站燃气报警:</span><span class="value critical">{{ stationAlertCount }}</span></div>
-        </div>
-      </div>
-
-      <!-- 右侧GIS地图 -->
-      <div class="gis-container">
-        <div class="map-toolbar">
-          <el-button-group>
-            <el-button size="small" :icon="ZoomIn" @click="zoomIn">放大</el-button>
-            <el-button size="small" :icon="ZoomOut" @click="zoomOut">缩小</el-button>
-            <el-button size="small" :icon="Location" @click="fitBounds">重置</el-button>
-          </el-button-group>
-          <div class="coordinate-info">坐标: {{ currentLng.toFixed(4) }}, {{ currentLat.toFixed(4) }}</div>
-        </div>
-        <div class="map-wrapper">
-          <div id="gasAlertBaiduMap" class="map-div"></div>
-          <div v-if="mapError" class="map-error">{{ mapError }}</div>
-        </div>
-
-        <!-- 详情抽屉 -->
-        <el-drawer v-model="detailDrawerVisible" :title="`报警详情 - ${currentFeature?.name}`" size="450px" direction="rtl" class="detail-drawer">
-          <div v-if="currentFeature" class="feature-detail">
-            <el-descriptions :column="1" border>
-              <el-descriptions-item label="设施名称">{{ currentFeature.name }}</el-descriptions-item>
-              <el-descriptions-item label="设施编号">{{ currentFeature.code || '—' }}</el-descriptions-item>
-              <el-descriptions-item label="设施类型">{{ currentFeature.typeName }}</el-descriptions-item>
-              <el-descriptions-item label="详细地址">{{ currentFeature.address }}</el-descriptions-item>
-              <el-descriptions-item label="报警级别">
-                <el-tag :type="currentFeature.level === 'critical' ? 'danger' : currentFeature.level === 'warning' ? 'warning' : 'success'">
-                  {{ currentFeature.level === 'critical' ? '严重报警' : currentFeature.level === 'warning' ? '预警' : '正常' }}
-                </el-tag>
-              </el-descriptions-item>
-              <el-descriptions-item v-if="currentFeature.concentration !== undefined" label="甲烷浓度">
-                <span :class="{ 'critical-value': currentFeature.concentration > 10 }">{{ currentFeature.concentration }} %LEL</span>
-              </el-descriptions-item>
-              <el-descriptions-item v-if="currentFeature.pressure !== undefined" label="管线压力">{{ currentFeature.pressure }} kPa</el-descriptions-item>
-              <el-descriptions-item v-if="currentFeature.flow !== undefined" label="瞬时流量">{{ currentFeature.flow }} Nm³/h</el-descriptions-item>
-              <el-descriptions-item v-if="currentFeature.threshold !== undefined" label="报警阈值">{{ currentFeature.threshold }} {{ currentFeature.thresholdUnit }}</el-descriptions-item>
-              <el-descriptions-item label="报警时间">{{ currentFeature.alertTime }}</el-descriptions-item>
-            </el-descriptions>
-            <div class="action-section" v-if="currentFeature.level !== 'normal'">
-              <el-button type="primary" size="small" @click="handleAlert">处理报警</el-button>
-              <el-button type="warning" size="small" @click="dispatchOrder">派发工单</el-button>
-            </div>
-          </div>
-        </el-drawer>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script setup>
-import { ref, computed, reactive, onMounted, onBeforeUnmount } from 'vue'
-import { MapLocation, Search, Refresh, FullScreen, ZoomIn, ZoomOut, Location } from '@element-plus/icons-vue'
-import { ElMessage } from 'element-plus'
-import { convertPipeline, convertPoints, convertCoord } from '@/utils/coordTransform'
-
-// ==================== 图层状态 ====================
-const visibleLayers = ref(['highPressure', 'mediumPressure', 'gasCritical', 'gasWarning', 'pressureAlert', 'stationCritical'])
-
-const overlayStore = {
-  highPressure: [], mediumPressure: [], lowPressure: [],
-  gasCritical: [], gasWarning: [], gasNormal: [],
-  pressureAlert: [], flowAlert: [],
-  stationCritical: [], stationWarning: []
-}
-
-// ==================== 百度地图相关 ====================
-let mapInstance = null
-let activeInfoWindow = null
-const mapError = ref('')
-const currentLng = ref(110.395)
-const currentLat = ref(28.470)
-const MAP_CENTER = { lng: 110.386, lat: 28.445 }
-const MAP_DEFAULT_ZOOM = 14
-
-function waitForBMapGL() {
-  return new Promise((resolve, reject) => {
-    if (window.BMapGL) { resolve(window.BMapGL); return }
-    let attempts = 0
-    const timer = setInterval(() => {
-      attempts++
-      if (window.BMapGL) { clearInterval(timer); resolve(window.BMapGL) }
-      if (attempts > 150) { clearInterval(timer); reject(new Error('百度地图API加载超时')) }
-    }, 200)
-  })
-}
-
-function initMap() {
-  const BMapGL = window.BMapGL
-  const el = document.getElementById('gasAlertBaiduMap')
-  if (!el) return
-  mapInstance = new BMapGL.Map(el, { enableMapClick: true })
-  const [mcLng, mcLat] = convertCoord(MAP_CENTER.lng, MAP_CENTER.lat)
-  const point = new BMapGL.Point(mcLng, mcLat)
-  mapInstance.centerAndZoom(point, MAP_DEFAULT_ZOOM)
-  mapInstance.enableScrollWheelZoom(true)
-  mapInstance.setMapStyleV2({ styleId: 'a0f4e6e6f9e7a6c5d8e7f7e7f7e7f7e5' })
-  mapInstance.addEventListener('mousemove', (e) => {
-    currentLng.value = e.latlng.lng
-    currentLat.value = e.latlng.lat
-  })
-  renderAllOverlays()
-}
-
-// ==================== 沅陵县静态数据 ====================
-
-// --- 管线数据 ---
-const pipelines = [
-  { id: 'P1', name: '燃气1', type: 'highPressure', color: '#f56c6c', width: 5,
-    path: [[110.404947, 28.433419], [110.404836, 28.433429], [110.403275, 28.433571], [110.401084, 28.433876], [110.399601, 28.433876], [110.398112, 28.433979], [110.396717, 28.434314], [110.394299, 28.434538], [110.394352, 28.434887], [110.391969, 28.434985], [110.384374, 28.435695], [110.381418, 28.43559], [110.377993, 28.436193], [110.376062, 28.436385], [110.374231, 28.43655], [110.372584, 28.437107], [110.371292, 28.4377], [110.370581, 28.438198], [110.369831, 28.438821]] },
-  { id: 'P2', name: '燃气2', type: 'mediumPressure', color: '#e6a23c', width: 4,
-    path: [[110.361433, 28.454979], [110.362363, 28.453169], [110.362851, 28.452213], [110.364377, 28.452443], [110.365138, 28.45246], [110.366462, 28.452157], [110.369549, 28.451441], [110.37219, 28.450897], [110.372753, 28.450901], [110.373875, 28.451194], [110.373913, 28.451107], [110.374597, 28.451476]] },
-]
-
-// --- 地下空间气体报警点 ---
-const gasPoints = ref([
-  { id: 'G1', name: '酉水大桥气体监测', type: 'gasMonitor', category: 'gasCritical', level: 'critical', concentration: 15.8, threshold: 10, thresholdUnit: '%LEL', lng: 110.369549, lat: 28.451441, address: '酉水大桥北段地下空间', alertTime: '2026-06-13 15:23:45', code: 'CH4-YL-001', typeName: '地下空间气体监测点' },
-  { id: 'G2', name: '酉水大桥2气体监测', type: 'gasMonitor', category: 'gasCritical', level: 'critical', concentration: 12.3, threshold: 10, thresholdUnit: '%LEL', lng: 110.373913, lat: 28.451107, address: '酉水大桥管沟监测点', alertTime: '2026-06-13 14:58:12', code: 'CH4-YL-002', typeName: '地下空间气体监测点' },
-])
-
-const pressurePoints = ref([
-  { id: 'PR1', name: '龙兴路压力报警', type: 'pressureMonitor', category: 'pressureAlert', level: 'critical', pressure: 432, threshold: 420, thresholdUnit: 'kPa', lng: 110.399601, lat: 28.433876, address: '学府路与太常路交叉口', alertTime: '2026-06-13 15:31:45', code: 'PR-YL-002', typeName: '压力监测点' },
-])
-
-const flowPoints = ref([
-  { id: 'FL1', name: '城东流量报警', type: 'flowMonitor', category: 'flowAlert', level: 'critical', flow: 2620, threshold: 2500, thresholdUnit: 'Nm³/h', lng: 110.407, lat: 28.454, address: '城东大道计量站', alertTime: '2026-06-13 15:31:45', code: 'FL-YL-002', typeName: '流量监测点' },
-  { id: 'FL2', name: '滨江路流量报警', type: 'flowMonitor', category: 'flowAlert', level: 'warning', flow: 820, threshold: 800, thresholdUnit: 'Nm³/h', lng: 110.375, lat: 28.432, address: '滨江路流量计井', alertTime: '2026-06-13 15:29:58', code: 'FL-YL-003', typeName: '流量监测点' },
-])
-
-const stationPoints = ref([
-  { id: 'S1', name: '龙兴路北场站', type: 'stationMonitor', category: 'stationCritical', level: 'critical', concentration: 48, threshold: 20, thresholdUnit: 'ppm', lng: 110.401084, lat: 28.433876, address: '古城路与建设路交叉口', alertTime: '2026-06-13 15:23:45', code: 'YL-LNG-01', typeName: '燃气场站' },
-  { id: 'S2', name: '太常路场站', type: 'stationMonitor', category: 'stationWarning', level: 'warning', concentration: 35, threshold: 20, thresholdUnit: 'ppm', lng: 110.370, lat: 28.456, address: '太常路南段', alertTime: '2026-06-13 14:32:05', code: 'YL-GY-01', typeName: '燃气场站' },
-])
-
-// ==================== 统计数据 ====================
-const allAlertFeatures = computed(() => [...gasPoints.value, ...pressurePoints.value, ...flowPoints.value, ...stationPoints.value])
-const totalPoints = computed(() => allAlertFeatures.value.length)
-const criticalCount = computed(() => allAlertFeatures.value.filter(f => f.level === 'critical').length)
-const warningCount = computed(() => allAlertFeatures.value.filter(f => f.level === 'warning').length)
-const gasAlertCount = computed(() => gasPoints.value.filter(f => f.level !== 'normal').length)
-const pressureAlertCount = computed(() => pressurePoints.value.length)
-const flowAlertCount = computed(() => flowPoints.value.length)
-const stationAlertCount = computed(() => stationPoints.value.length)
-
-// ==================== 地图覆盖物渲染 ====================
-function clearOverlays() {
-  Object.values(overlayStore).forEach(arr => {
-    arr.forEach(o => { try { mapInstance.removeOverlay(o) } catch {} })
-    arr.length = 0
-  })
-}
-
-function addToOverlayStore(layer, overlay) {
-  if (overlayStore[layer]) overlayStore[layer].push(overlay)
-}
-
-function renderPipelines() {
-  const BMapGL = window.BMapGL
-  pipelines.forEach(p => {
-    const points = p.path.map(c => {
-      const [bdLng, bdLat] = convertCoord(c[0], c[1])
-      return new BMapGL.Point(bdLng, bdLat)
-    })
-    const polyline = new BMapGL.Polyline(points, {
-      strokeColor: p.color, strokeWeight: p.width, strokeOpacity: 0.75
-    })
-    mapInstance.addOverlay(polyline)
-    addToOverlayStore(p.type, polyline)
-    polyline.addEventListener('click', () => {
-      currentFeature.value = { name: p.name, code: p.id, typeName: '燃气管线', address: '沅陵县太常片区', level: 'normal' }
-      detailDrawerVisible.value = true
-    })
-  })
-}
-
-function renderPointMarkers(features, layer, shape, size, bgColor) {
-  const BMapGL = window.BMapGL
-  features.forEach(f => {
-    if (f.category !== layer) return
-    const [bdLng, bdLat] = convertCoord(f.lng, f.lat)
-    const pt = new BMapGL.Point(bdLng, bdLat)
-    const label = new BMapGL.Label(f.name, {
-      position: pt,
-      offset: new BMapGL.Size(0, -18)
-    })
-    label.setStyle({
-      color: '#fff', fontSize: '10px', padding: '3px 8px',
-      backgroundColor: bgColor || (f.level === 'critical' ? '#f56c6c' : f.level === 'warning' ? '#e6a23c' : '#67c23a'),
-      borderRadius: '3px', border: 'none', whiteSpace: 'nowrap'
-    })
-    mapInstance.addOverlay(label)
-    addToOverlayStore(layer, label)
-
-    // 点标记(圆点)
-    const marker = new BMapGL.Marker(pt)
-    mapInstance.addOverlay(marker)
-    addToOverlayStore(layer, marker)
-
-    marker.addEventListener('click', () => {
-      currentFeature.value = f
-      detailDrawerVisible.value = true
-    })
-  })
-}
-
-function renderAllMarkers() {
-  const BMapGL = window.BMapGL
-
-  // 地下空间气体
-  gasPoints.value.forEach(f => {
-    renderPointMarkers([f], f.category, 'circle', 10,
-      f.level === 'critical' ? '#f56c6c' : f.level === 'warning' ? '#e6a23c' : '#67c23a')
-  })
-
-  // 管线压力报警 - 菱形
-  pressurePoints.value.forEach(f => {
-    const [bdLng, bdLat] = convertCoord(f.lng, f.lat)
-    const pt = new BMapGL.Point(bdLng, bdLat)
-    const label = new BMapGL.Label(f.name, { position: pt, offset: new BMapGL.Size(0, -18) })
-    label.setStyle({
-      color: '#fff', fontSize: '10px', padding: '3px 8px',
-      backgroundColor: f.level === 'critical' ? '#f56c6c' : '#e6a23c',
-      borderRadius: '2px', border: 'none', whiteSpace: 'nowrap'
-    })
-    mapInstance.addOverlay(label)
-    addToOverlayStore(f.category, label)
-
-    const marker = new BMapGL.Marker(pt)
-    mapInstance.addOverlay(marker)
-    addToOverlayStore(f.category, marker)
-    marker.addEventListener('click', () => {
-      currentFeature.value = f
-      detailDrawerVisible.value = true
-    })
-  })
-
-  // 管线流量报警 - 三角
-  flowPoints.value.forEach(f => {
-    const [bdLng, bdLat] = convertCoord(f.lng, f.lat)
-    const pt = new BMapGL.Point(bdLng, bdLat)
-    const label = new BMapGL.Label(f.name, { position: pt, offset: new BMapGL.Size(0, -18) })
-    label.setStyle({
-      color: '#fff', fontSize: '10px', padding: '3px 8px',
-      backgroundColor: f.level === 'critical' ? '#f56c6c' : '#e6a23c',
-      borderRadius: '2px', border: 'none', whiteSpace: 'nowrap'
-    })
-    mapInstance.addOverlay(label)
-    addToOverlayStore(f.category, label)
-
-    const marker = new BMapGL.Marker(pt)
-    mapInstance.addOverlay(marker)
-    addToOverlayStore(f.category, marker)
-    marker.addEventListener('click', () => {
-      currentFeature.value = f
-      detailDrawerVisible.value = true
-    })
-  })
-
-  // 场站燃气报警
-  stationPoints.value.forEach(f => {
-    const [bdLng, bdLat] = convertCoord(f.lng, f.lat)
-    const pt = new BMapGL.Point(bdLng, bdLat)
-    const label = new BMapGL.Label(f.name, { position: pt, offset: new BMapGL.Size(0, -18) })
-    label.setStyle({
-      color: '#fff', fontSize: '10px', padding: '3px 8px',
-      backgroundColor: f.level === 'critical' ? '#f56c6c' : '#e6a23c',
-      borderRadius: '10px', border: '2px solid #fff', whiteSpace: 'nowrap'
-    })
-    mapInstance.addOverlay(label)
-    addToOverlayStore(f.category, label)
-
-    const marker = new BMapGL.Marker(pt)
-    mapInstance.addOverlay(marker)
-    addToOverlayStore(f.category, marker)
-    marker.addEventListener('click', () => {
-      currentFeature.value = f
-      detailDrawerVisible.value = true
-    })
-  })
-}
-
-function renderAllOverlays() {
-  clearOverlays()
-  renderPipelines()
-  renderAllMarkers()
-  onLayerChange()
-}
-
-// ==================== 图层显隐 ====================
-function onLayerChange() {
-  const set = new Set(visibleLayers.value)
-  Object.entries(overlayStore).forEach(([layer, overlays]) => {
-    overlays.forEach(o => {
-      try {
-        if (set.has(layer)) o.show()
-        else o.hide()
-      } catch {}
-    })
-  })
-}
-
-// ==================== 地图操作 ====================
-function zoomIn() { mapInstance?.zoomIn() }
-function zoomOut() { mapInstance?.zoomOut() }
-function fitBounds() {
-  if (!mapInstance) return
-  const BMapGL = window.BMapGL
-  const [fLng, fLat] = convertCoord(MAP_CENTER.lng, MAP_CENTER.lat)
-  mapInstance.centerAndZoom(new BMapGL.Point(fLng, fLat), MAP_DEFAULT_ZOOM)
-}
-function refreshData() { ElMessage.success('数据已刷新') }
-
-// ==================== 搜索 ====================
-function onSearch() {
-  if (!mapInstance || !searchKeyword.value) return
-  const kw = searchKeyword.value.toLowerCase()
-  const hit = allAlertFeatures.value.find(f =>
-    f.name.toLowerCase().includes(kw) || f.address.toLowerCase().includes(kw))
-  if (hit) {
-    const BMapGL = window.BMapGL
-    const [sLng, sLat] = convertCoord(hit.lng, hit.lat)
-    mapInstance.centerAndZoom(new BMapGL.Point(sLng, sLat), 16)
-    currentFeature.value = hit
-  }
-}
-
-// ==================== 详情抽屉 ====================
-const currentFeature = ref(null)
-const detailDrawerVisible = ref(false)
-
-const handleAlert = () => { ElMessage.info('开始处理报警') }
-const dispatchOrder = () => { ElMessage.success('已派发处置工单') }
-
-// ==================== 生命周期 ====================
-onMounted(async () => {
-  try {
-    await waitForBMapGL()
-    initMap()
-  } catch (e) {
-    mapError.value = '百度地图加载失败,请刷新页面重试'
-  }
-})
-
-onBeforeUnmount(() => {
-  try {
-    clearOverlays()
-    if (activeInfoWindow) { activeInfoWindow.close(); activeInfoWindow = null }
-    mapInstance?.destroy()
-    mapInstance = null
-  } catch {}
-})
-</script>
-
-<style scoped>
-.gas-alert-gis {
-  background: #f0f2f6;
-  height: 100vh;
-  display: flex;
-  flex-direction: column;
-  overflow: hidden;
-}
-.page-header {
-  background: white;
-  padding: 12px 24px;
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  border-bottom: 1px solid #e9ecef;
-  flex-shrink: 0;
-}
-.header-left { display: flex; align-items: center; gap: 20px; }
-.header-left .title { font-size: 1.3rem; font-weight: 600; }
-.stats-badge { display: flex; gap: 20px; }
-.stat-item { font-size: 13px; display: flex; align-items: center; gap: 6px; }
-.stat-item.critical { color: #f56c6c; }
-.stat-item.warning { color: #e6a23c; }
-.stat-item .dot { width: 8px; height: 8px; border-radius: 50%; background: #67c23a; }
-.stat-item.critical .dot { background: #f56c6c; }
-.stat-item.warning .dot { background: #e6a23c; }
-.header-right { display: flex; gap: 12px; }
-
-.main-layout {
-  display: flex;
-  flex: 1;
-  overflow: hidden;
-  padding: 12px;
-  gap: 12px;
-}
-.layer-panel {
-  width: 260px;
-  background: white;
-  border-radius: 12px;
-  padding: 16px;
-  display: flex;
-  flex-direction: column;
-  gap: 16px;
-  overflow-y: auto;
-  flex-shrink: 0;
-}
-.layer-header { font-weight: 600; font-size: 16px; display: flex; align-items: center; gap: 8px; }
-.layer-group { margin-bottom: 12px; }
-.group-title { font-weight: 500; margin-bottom: 8px; font-size: 13px; color: #606266; }
-.layer-group .el-checkbox { display: block; margin-left: 8px; margin-bottom: 6px; }
-.legend-panel { border-top: 1px solid #e9ecef; padding-top: 12px; }
-.legend-title { font-weight: 500; margin-bottom: 8px; }
-.legend-dot { display: inline-block; width: 12px; height: 12px; border-radius: 50%; margin-right: 8px; }
-.legend-dot.gas-critical { background: #f56c6c; }
-.legend-dot.gas-warning { background: #e6a23c; }
-.legend-dot.pressure { background: #409eff; border-radius: 2px; }
-.legend-dot.flow { background: #67c23a; clip-path: polygon(50% 0%, 100% 100%, 0% 100%); }
-.legend-dot.station-critical { background: #f56c6c; border-radius: 10px; }
-.legend-dot.station-warning { background: #e6a23c; border-radius: 10px; }
-.legend-line { display: inline-block; width: 30px; height: 3px; margin-right: 8px; }
-.legend-line.high { background: #f56c6c; }
-.legend-line.medium { background: #e6a23c; }
-.stats-panel { background: #f5f7fa; border-radius: 8px; padding: 12px; }
-.stats-title { font-weight: 600; margin-bottom: 8px; }
-.stats-row { display: flex; justify-content: space-between; font-size: 13px; margin: 6px 0; }
-.stats-row .value.critical { color: #f56c6c; font-weight: bold; }
-.stats-row .value.warning { color: #e6a23c; font-weight: bold; }
-
-.gis-container {
-  flex: 1;
-  background: white;
-  border-radius: 12px;
-  display: flex;
-  flex-direction: column;
-  overflow: hidden;
-}
-.map-toolbar {
-  padding: 10px 16px;
-  border-bottom: 1px solid #e9ecef;
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  flex-shrink: 0;
-}
-.coordinate-info { font-size: 12px; color: #909399; }
-.map-wrapper { flex: 1; position: relative; overflow: hidden; }
-.map-div { width: 100%; height: 100%; }
-.map-error { position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); color: #f56c6c; font-size: 16px; }
-.feature-detail { padding: 8px; }
-.critical-value { color: #f56c6c; font-weight: bold; }
-.action-section { margin-top: 16px; display: flex; gap: 12px; justify-content: flex-end; }
-</style>

+ 0 - 929
src/views/subSystem/manholeCover/alarm/alarmSanalysis.vue

@@ -1,929 +0,0 @@
-<!--监测报警一张图-->
-<template>
-  <div class="gis-alert-dashboard">
-    <!-- 顶部状态栏 -->
-    <div class="top-bar">
-
-      <div class="alert-stats">
-        <div class="stat" @click="focusLayer('critical')">
-          <span class="stat-value critical">{{ criticalCount }}</span>
-          <span class="stat-label">严重报警</span>
-        </div>
-        <div class="stat" @click="focusLayer('warning')">
-          <span class="stat-value warning">{{ warningCount }}</span>
-          <span class="stat-label">一般报警</span>
-        </div>
-        <div class="stat" @click="focusLayer('normal')">
-          <span class="stat-value">{{ normalCount }}</span>
-          <span class="stat-label">正常井盖</span>
-        </div>
-        <div class="stat" @click="focusLayer('offline')">
-          <span class="stat-value offline">{{ offlineCount }}</span>
-          <span class="stat-label">离线设备</span>
-        </div>
-      </div>
-    </div>
-
-    <div class="main-layout">
-      <!-- 左侧图层控制面板 -->
-      <div class="layer-control">
-        <div class="layer-header">
-          <el-icon><Monitor /></el-icon> 态势图层
-        </div>
-        <div class="layer-filter-panel">
-          <el-input
-            v-model="queryParams.keyword"
-            clearable
-            :prefix-icon="Search"
-            placeholder="设备编号/名称/位置模糊查询"
-            @keyup.enter="handleSearch"
-          />
-          <div class="filter-actions">
-            <el-button type="primary" size="small" @click="handleSearch">查询</el-button>
-            <el-button size="small" :icon="RefreshRight" @click="handleReset">重置</el-button>
-          </div>
-          <div class="filter-summary">
-            <el-tag size="small" effect="plain">搜索结果 {{ totalDevices }} 台</el-tag>
-            <el-tag size="small" type="success" effect="plain">当前显示 {{ displayDevices.length }} 台</el-tag>
-            <el-tag v-if="activeQuickFilter" size="small" type="warning" effect="plain">态势筛选: {{ quickFilterLabel }}</el-tag>
-          </div>
-        </div>
-        <el-tree
-            ref="layerTreeRef"
-            :data="layerTreeData"
-            show-checkbox
-            check-strictly
-            node-key="id"
-            :default-checked-keys="checkedKeys"
-            :default-expand-all="true"
-            @check="onLayerChange"
-            class="layer-tree"
-        >
-          <template #default="{ node, data }">
-            <span class="layer-node">
-              <el-icon v-if="data.icon"><component :is="data.icon" /></el-icon>
-              <span>{{ node.label }}</span>
-              <el-badge v-if="data.alertCount" :value="data.alertCount" type="danger" class="layer-badge" />
-            </span>
-          </template>
-        </el-tree>
-        <div class="legend-panel">
-          <div class="legend-title">图例说明</div>
-          <div><span class="legend-marker critical-dot"></span> 严重报警</div>
-          <div><span class="legend-marker warning-dot"></span> 一般报警</div>
-          <div><span class="legend-marker normal-dot"></span> 正常在线</div>
-          <div><span class="legend-marker offline-dot"></span> 离线/停用</div>
-          <div><span class="legend-marker alarm-pulse"></span> 实时报警闪烁</div>
-        </div>
-        <div class="time-selector">
-          <el-date-picker v-model="timeRange" type="datetimerange" range-separator="至" size="small" :shortcuts="shortcuts" start-placeholder="开始" end-placeholder="结束" />
-        </div>
-        <div class="panel-tip">已选图层会实时过滤地图点位,支持结合关键字一起查询。</div>
-      </div>
-
-      <!-- 右侧GIS地图核心区 -->
-      <div class="gis-map-area">
-        <div class="map-toolbar">
-          <el-button-group>
-            <el-button size="small" :icon="ZoomIn" @click="zoomIn">放大</el-button>
-            <el-button size="small" :icon="ZoomOut" @click="zoomOut">缩小</el-button>
-            <el-button size="small" :icon="FullScreen" @click="resetView">重置视图</el-button>
-          </el-button-group>
-          <div class="current-stat">
-            <span><el-icon><Position /></el-icon> 设备总数: {{ totalDevices }}</span>
-            <span><el-icon><Monitor /></el-icon> 当前显示: {{ displayDevices.length }}</span>
-            <span class="alert-summary">报警中: {{ totalActiveAlerts }}</span>
-          </div>
-        </div>
-        <div class="map-container" ref="mapContainer" v-loading="listLoading">
-          <div ref="baiduMapRef" class="baidu-map"></div>
-          <div v-if="!listLoading && !displayDevices.length" class="empty-state">
-            当前筛选条件下暂无设备
-          </div>
-          <div v-if="mapError" class="map-error">
-            {{ mapError }}
-          </div>
-        </div>
-        <!-- 右下角报警聚合弹窗/详情 -->
-        <el-drawer v-model="drawerVisible" :title="selectedDevice?.name + ' - 监测报警详情'" direction="rtl" size="420px" class="detail-drawer">
-          <div v-if="selectedDevice" v-loading="detailLoading">
-            <el-descriptions :column="1" border>
-              <el-descriptions-item label="设备编号">{{ selectedDevice.deviceCode || '—' }}</el-descriptions-item>
-              <el-descriptions-item label="地理位置">{{ selectedDevice.address }}</el-descriptions-item>
-              <el-descriptions-item label="当前状态">
-                <el-tag :type="getStatusTag(selectedDevice.status)">{{ selectedDevice.statusText }}</el-tag>
-              </el-descriptions-item>
-              <el-descriptions-item label="报警级别" v-if="selectedDevice.hasAlert">
-                <el-tag :type="selectedDevice.level === 'critical' ? 'danger' : 'warning'">{{ selectedDevice.level === 'critical' ? '严重报警' : '一般报警' }}</el-tag>
-              </el-descriptions-item>
-              <el-descriptions-item label="报警类型">{{ selectedDevice.alertType || '无' }}</el-descriptions-item>
-              <el-descriptions-item label="实时监测值">{{ selectedDevice.realTimeValue || '—' }}</el-descriptions-item>
-              <el-descriptions-item label="报警阈值">{{ selectedDevice.threshold || '—' }}</el-descriptions-item>
-              <el-descriptions-item label="最近通讯">{{ selectedDevice.lastComm }}</el-descriptions-item>
-            </el-descriptions>
-            <el-divider>处置建议</el-divider>
-            <div class="advice-box">
-              <p>{{ getAlertAdvice(selectedDevice) }}</p>
-              <el-button type="primary" size="small" style="margin-top: 12px;" @click="dispatchWorkOrder">派发处置工单</el-button>
-            </div>
-            <el-divider v-if="selectedDevice.alertLogs?.length">报警记录</el-divider>
-            <el-timeline>
-              <el-timeline-item v-for="log in selectedDevice.alertLogs" :key="log.time" :timestamp="log.time" type="danger">
-                {{ log.content }}
-              </el-timeline-item>
-            </el-timeline>
-          </div>
-        </el-drawer>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script setup>
-import {computed, nextTick, onMounted, onUnmounted, ref, watch} from 'vue'
-import {
-  Connection,
-  FullScreen,
-  InfoFilled,
-  Monitor,
-  Position,
-  RefreshRight,
-  Search,
-  Setting,
-  WarningFilled,
-  ZoomIn,
-  ZoomOut
-} from '@element-plus/icons-vue'
-import {ElMessage} from 'element-plus'
-import {convertCoord} from '@/utils/coordTransform'
-import {getManholeAlarmDeviceDetail, getManholeAlarmDeviceList, getManholeLayerData} from '@/api/pipeNetwork/basic'
-
-// 树复选框默认勾选全部状态
-const DEFAULT_CHECKED_KEYS = ['current-1', 'current-3', 'alarm-1', 'alarm-0', 'online-1', 'online-0']
-const checkedKeys = ref([...DEFAULT_CHECKED_KEYS])
-const visibleLayers = ref(new Set(DEFAULT_CHECKED_KEYS))
-const layerTreeRef = ref(null)
-
-// 动态生成树数据(包括设备级子节点),每次 displayDevices 变化自动重算
-const layerTreeData = computed(() => {
-  const devices = displayDevices.value
-  const total = devices.length
-  const critical = devices.filter(m => m.status === 'critical').length
-  const warning = devices.filter(m => m.status === 'warning').length
-  const normal = devices.filter(m => m.status === 'normal').length
-  const offline = devices.filter(m => m.status === 'offline').length
-  const inUse = devices.filter(item => Number(item.raw?.equipmentStatus?.currentStatus) === 1).length
-  const maintenance = devices.filter(item => Number(item.raw?.equipmentStatus?.currentStatus) === 3).length
-
-  function buildDeviceNode(device) {
-    const typeId = Number(device.raw?.equipmentTypeId)
-    const typeName = typeId === 5 ? '供水窨井' : typeId === 9 ? '排水窨井' : typeId === 10 ? '燃气窨井' : (device.equipmentTypeName || '未知类型')
-    const code = device.deviceCode || device.id || ''
-    return {
-      id: `device-${device.equipmentId}`,
-      label: `${typeName}-${code}`,
-      disabled: true
-    }
-  }
-
-  return [
-    {
-      id: 'current-status',
-      label: '运维状态筛选',
-      icon: Setting,
-      alertCount: inUse + maintenance,
-      disabled: true,
-      children: [
-        { id: 'current-1', label: '在用设备', icon: Monitor, alertCount: inUse,
-          children: devices.filter(d => Number(d.raw?.equipmentStatus?.currentStatus) === 1).map(buildDeviceNode) },
-        { id: 'current-3', label: '维修设备', icon: Setting, alertCount: maintenance,
-          children: devices.filter(d => Number(d.raw?.equipmentStatus?.currentStatus) === 3).map(buildDeviceNode) }
-      ]
-    },
-    {
-      id: 'alarm-status',
-      label: '报警状态筛选',
-      icon: WarningFilled,
-      alertCount: critical + warning + normal,
-      disabled: true,
-      children: [
-        { id: 'alarm-1', label: '报警设备', icon: WarningFilled, alertCount: critical + warning,
-          children: devices.filter(d => d.status === 'critical' || d.status === 'warning').map(buildDeviceNode) },
-        { id: 'alarm-0', label: '正常设备', icon: InfoFilled, alertCount: normal,
-          children: devices.filter(d => d.status === 'normal').map(buildDeviceNode) }
-      ]
-    },
-    {
-      id: 'online-status',
-      label: '在线状态筛选',
-      icon: Connection,
-      alertCount: total,
-      disabled: true,
-      children: [
-        { id: 'online-1', label: '在线设备', icon: Connection, alertCount: total - offline,
-          children: devices.filter(d => d.status !== 'offline').map(buildDeviceNode) },
-        { id: 'online-0', label: '离线设备', icon: Connection, alertCount: offline,
-          children: devices.filter(d => d.status === 'offline').map(buildDeviceNode) }
-      ]
-    }
-  ]
-})
-const queryParams = ref({
-  keyword: ''
-})
-const mockManholes = ref([])
-const listLoading = ref(false)
-const detailLoading = ref(false)
-const activeQuickFilter = ref('')
-const baiduMapRef = ref(null)
-const mapError = ref('')
-
-const BAIDU_MAP_AK = import.meta.env.VITE_BAIDU_MAP_AK || ''
-const DEFAULT_MAP_CENTER = { lng: 110.393, lat: 28.452 }
-
-let mapInstance = null
-let mapOverlays = []
-let baiduMapScriptPromise = null
-
-const quickFilterLabelMap = {
-  critical: '严重报警',
-  warning: '一般报警',
-  normal: '正常井盖',
-  offline: '离线设备'
-}
-
-const quickFilterLabel = computed(() => quickFilterLabelMap[activeQuickFilter.value] || '')
-
-function isBatteryLow(device) {
-  const battery = toNumber(device.battery)
-  return battery !== null && battery <= 20
-}
-
-function matchesQuickFilter(device) {
-  if (!activeQuickFilter.value) return true
-  return device.status === activeQuickFilter.value
-}
-
-/**
- * 图层树勾选过滤:根据 visibleLayers 判断设备是否应显示
- * - 无状态记录的设备视为离线(onlineStatus=0)
- * - 某分类全部勾选时等同于不过滤(兼容无状态记录的设备)
- */
-function matchesLayerFilter(device) {
-  const keys = visibleLayers.value
-  // 没有勾选任何状态时显示全部
-  if (keys.size === 0) return true
-
-  const st = device.raw?.equipmentStatus || {}
-  const currentStatus = Number(st.currentStatus)
-  const alarmStatus = Number(st.alarmStatus)
-  const onlineStatus = Number(st.onlineStatus)
-  const hasNoStatus = !device.raw?.equipmentStatus
-
-  // OR 逻辑:匹配任意一个勾选状态即显示
-  if (keys.has('current-1') && currentStatus === 1) return true
-  if (keys.has('current-3') && currentStatus === 3) return true
-  if (keys.has('alarm-1') && alarmStatus === 1) return true
-  if (keys.has('alarm-0') && alarmStatus === 0) return true
-  if (keys.has('online-1') && onlineStatus === 1) return true
-  if (keys.has('online-0') && (onlineStatus === 0 || hasNoStatus)) return true
-
-  return false
-}
-
-const displayDevices = computed(() => {
-  return mockManholes.value.filter(device => {
-    // 图层树过滤
-    if (!matchesLayerFilter(device)) return false
-    // 顶部统计栏快速过滤
-    if (!matchesQuickFilter(device)) return false
-    return true
-  })
-})
-
-const criticalCount = computed(() => displayDevices.value.filter(m => m.status === 'critical').length)
-const warningCount = computed(() => displayDevices.value.filter(m => m.status === 'warning').length)
-const normalCount = computed(() => displayDevices.value.filter(m => m.status === 'normal').length)
-const offlineCount = computed(() => displayDevices.value.filter(m => m.status === 'offline').length)
-const totalDevices = computed(() => mockManholes.value.length)
-const totalActiveAlerts = computed(() => criticalCount.value + warningCount.value)
-
-const mapContainer = ref(null)
-const bounds = ref({ minLng: 121.45, maxLng: 121.55, minLat: 31.22, maxLat: 31.28 })
-
-function toNumber(value) {
-  const num = Number(value)
-  return Number.isFinite(num) ? num : null
-}
-
-function formatDateTime(value) {
-  return value || '—'
-}
-
-function isValidCoordinate(lng, lat) {
-  return lng !== null && lat !== null && lng >= 70 && lng <= 140 && lat >= 3 && lat <= 55
-}
-
-/**
- * 业务坐标(WGS84)转百度坐标点(BD-09)
- *
- * 设备台账的 longitude/latitude 存的是 WGS84,百度地图要 BD-09,
- * 直接传原值会整体偏移约 1.1 公里,必须转换后再构建 Point
- *
- * @param lng 业务经度
- * @param lat 业务纬度
- * @returns {BMapGL.Point} 百度坐标点
- */
-function toBdPoint(lng, lat) {
-  const [bdLng, bdLat] = convertCoord(Number(lng), Number(lat))
-  return new window.BMapGL.Point(bdLng, bdLat)
-}
-
-function getThresholdTextByCode(raw, warningCode) {
-  const list = Array.isArray(raw?.warningThresholdList) ? raw.warningThresholdList : []
-  const target = list.find(item => item.warningCode === warningCode)
-  if (!target) return '—'
-  const { minValue, maxValue } = target
-  if (minValue !== null && minValue !== undefined && maxValue !== null && maxValue !== undefined) {
-    return `${minValue} ~ ${maxValue}`
-  }
-  if (minValue !== null && minValue !== undefined) return `<= ${minValue}`
-  if (maxValue !== null && maxValue !== undefined) return `>= ${maxValue}`
-  return '—'
-}
-
-function buildAlertInfo(raw) {
-  const status = raw?.equipmentStatus || {}
-  const manholeData = raw?.manholeData || {}
-  const battery = toNumber(manholeData.batteryLevel)
-  const signal = toNumber(manholeData.signalStrength)
-  const tiltAngle = manholeData?.tiltAngle
-
-  if (Number(status.onlineStatus) !== 1) {
-    return { status: 'offline', statusText: '离线', hasAlert: false, level: '', alertType: '', realTimeValue: '—', threshold: '—' }
-  }
-
-  if (`${manholeData.waterLevelAlarmStatus}` === '1') {
-    return {
-      status: 'critical',
-      statusText: '严重报警',
-      hasAlert: true,
-      level: 'critical',
-      alertType: '水位报警',
-      realTimeValue: '报警',
-      threshold: getThresholdTextByCode(raw, 'WARN-WATER-LEVEL')
-    }
-  }
-
-  if (`${manholeData.waterInfiltrationAlarmStatus}` === '1') {
-    return {
-      status: 'critical',
-      statusText: '严重报警',
-      hasAlert: true,
-      level: 'critical',
-      alertType: '水浸报警',
-      realTimeValue: '报警',
-      threshold: getThresholdTextByCode(raw, 'WARN-WATER-INFILTRATION')
-    }
-  }
-
-  if (Number(status.alarmStatus) === 1) {
-    return {
-      status: 'critical',
-      statusText: '严重报警',
-      hasAlert: true,
-      level: 'critical',
-      alertType: tiltAngle ? '倾斜报警' : '设备报警',
-      realTimeValue: tiltAngle ? `${tiltAngle}°` : '报警',
-      threshold: tiltAngle ? getThresholdTextByCode(raw, 'WARN-TILT') : getThresholdTextByCode(raw, 'WARN-DEVICE-ALARM')
-    }
-  }
-
-  if ((battery !== null && battery <= 20) || (signal !== null && signal <= 20)) {
-    return {
-      status: 'warning',
-      statusText: '一般报警',
-      hasAlert: true,
-      level: 'warning',
-      alertType: battery !== null && battery <= 20 ? '低电量预警' : '弱信号预警',
-      realTimeValue: battery !== null && battery <= 20 ? `${battery}%` : `${signal}%`,
-      threshold: '20'
-    }
-  }
-
-  return { status: 'normal', statusText: '正常', hasAlert: false, level: '', alertType: '', realTimeValue: '—', threshold: '—' }
-}
-
-function buildAlertLogs(raw, alertInfo) {
-  if (!alertInfo?.hasAlert) return []
-  const timestamp = raw?.equipmentStatus?.statusUpdateTime || raw?.updateTime || raw?.installTime || ''
-  return [{
-    time: formatDateTime(timestamp),
-    content: `${alertInfo.alertType}${alertInfo.realTimeValue && alertInfo.realTimeValue !== '—' ? `:${alertInfo.realTimeValue}` : ''}${alertInfo.threshold && alertInfo.threshold !== '—' ? `,阈值 ${alertInfo.threshold}` : ''}`
-  }]
-}
-
-function normalizeDevice(raw, _index = 0, baseDevice = {}) {
-  const rawLng = toNumber(raw?.longitude)
-  const rawLat = toNumber(raw?.latitude)
-  const baseLng = toNumber(baseDevice.lng)
-  const baseLat = toNumber(baseDevice.lat)
-  const useRawCoordinate = isValidCoordinate(rawLng, rawLat)
-  const useBaseCoordinate = isValidCoordinate(baseLng, baseLat)
-  const lng = useRawCoordinate ? rawLng : (useBaseCoordinate ? baseLng : null)
-  const lat = useRawCoordinate ? rawLat : (useBaseCoordinate ? baseLat : null)
-  const alertInfo = buildAlertInfo(raw)
-  const address = raw?.equipmentLocation || baseDevice.address || '暂无位置信息'
-  return {
-    equipmentId: raw?.equipmentId || baseDevice.equipmentId || '',
-    id: raw?.equipmentCode || baseDevice.id || raw?.equipmentId || '',
-    deviceCode: raw?.equipmentCode || baseDevice.deviceCode || '',
-    name: raw?.equipmentName || baseDevice.name || raw?.equipmentCode || '未命名设备',
-    lng,
-    lat,
-    address,
-    status: alertInfo.status,
-    statusText: alertInfo.statusText,
-    hasAlert: alertInfo.hasAlert,
-    level: alertInfo.level,
-    alertType: alertInfo.alertType,
-    realTimeValue: alertInfo.realTimeValue,
-    threshold: alertInfo.threshold,
-    lastComm: formatDateTime(raw?.equipmentStatus?.statusUpdateTime || raw?.updateTime || raw?.installTime),
-    locationShort: address.length > 8 ? `${address.slice(0, 8)}...` : address,
-    battery: raw?.manholeData?.batteryLevel || '—',
-    alertLogs: buildAlertLogs(raw, alertInfo),
-    equipmentTypeName: raw?.equipmentTypeName || '—',
-    equipmentModel: raw?.equipmentModel || '—',
-    manufacturer: raw?.manufacturer || '—',
-    installTime: formatDateTime(raw?.installTime),
-    maintainer: raw?.maintainer || '—',
-    raw
-  }
-}
-
-function refreshLayerTree() {
-  // 已废弃:树数据由 layerTreeData computed 自动生成
-}
-
-function refreshBounds() {
-  const validList = displayDevices.value.filter(item => Number.isFinite(item.lng) && Number.isFinite(item.lat))
-  if (!validList.length) {
-    bounds.value = { minLng: 110.35, maxLng: 110.43, minLat: 28.42, maxLat: 28.5 }
-    return
-  }
-  const lngList = validList.map(item => item.lng)
-  const latList = validList.map(item => item.lat)
-  const minLng = Math.min(...lngList)
-  const maxLng = Math.max(...lngList)
-  const minLat = Math.min(...latList)
-  const maxLat = Math.max(...latList)
-  bounds.value = {
-    minLng: minLng - 0.01,
-    maxLng: maxLng + 0.01,
-    minLat: minLat - 0.01,
-    maxLat: maxLat + 0.01
-  }
-}
-
-function getStatusColor(status) {
-  if (status === 'critical') return '#f56c6c'
-  if (status === 'warning') return '#e6a23c'
-  if (status === 'offline') return '#909399'
-  return '#67c23a'
-}
-
-function getMarkerHtml(device, isActive = false) {
-  const color = getStatusColor(device.status)
-  const pulse = device.status === 'critical'
-    ? 'box-shadow: 0 0 0 8px rgba(245,108,108,0.18);'
-    : ''
-  const activeRing = isActive
-    ? 'border: 3px solid #ffffff; transform: scale(1.08);'
-    : 'border: 2px solid #ffffff;'
-  return `
-    <div style="display:flex;flex-direction:column;align-items:center;transform:translateY(-6px);cursor:pointer;">
-      <div style="max-width:160px;padding:4px 8px;border-radius:999px;background:rgba(20,33,61,0.82);color:#fff;font-size:12px;line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:6px;">
-        ${device.name}
-      </div>
-      <div style="width:18px;height:18px;border-radius:50%;background:${color};${activeRing}${pulse}"></div>
-    </div>
-  `
-}
-
-function clearMapOverlays() {
-  if (!mapInstance) return
-  mapOverlays.forEach((overlay) => {
-    try {
-      mapInstance.removeOverlay(overlay)
-    } catch (_) {}
-  })
-  mapOverlays = []
-}
-
-function fitMapViewport() {
-  if (!mapInstance || !window.BMapGL) return
-  if (!displayDevices.value.length) {
-    mapInstance.centerAndZoom(toBdPoint(DEFAULT_MAP_CENTER.lng, DEFAULT_MAP_CENTER.lat), 14)
-    return
-  }
-  const points = displayDevices.value
-    .filter(device => isValidCoordinate(device.lng, device.lat))
-    .map(device => toBdPoint(device.lng, device.lat))
-  if (!points.length) {
-    mapInstance.centerAndZoom(toBdPoint(DEFAULT_MAP_CENTER.lng, DEFAULT_MAP_CENTER.lat), 14)
-    return
-  }
-  mapInstance.setViewport(points)
-  if (mapInstance.getZoom() > 16) {
-    mapInstance.setZoom(16)
-  }
-  if (mapInstance.getZoom() < 12) {
-    mapInstance.setZoom(12)
-  }
-}
-
-function renderMapOverlays() {
-  if (!mapInstance || !window.BMapGL) return
-  console.log('[renderMapOverlays] 准备渲染的设备:', displayDevices.value.length)
-  clearMapOverlays()
-  const validDevices = displayDevices.value.filter(device => isValidCoordinate(device.lng, device.lat))
-  console.log('[renderMapOverlays] 有效坐标设备:', validDevices.length)
-  console.log('[renderMapOverlays] 无效坐标设备:', displayDevices.value.filter(d => !isValidCoordinate(d.lng, d.lat)).map(d => ({ id: d.id, lng: d.lng, lat: d.lat })))
-  
-  validDevices.forEach((device) => {
-    console.log('[renderMapOverlays] 渲染点位:', device.name, 'lng:', device.lng, 'lat:', device.lat)
-    const point = toBdPoint(device.lng, device.lat)
-    const isActive = selectedDevice.value?.equipmentId === device.equipmentId
-    const label = new window.BMapGL.Label(getMarkerHtml(device, isActive), {
-      position: point,
-      offset: new window.BMapGL.Size(-30, -52)
-    })
-    label.setStyle({
-      border: 'none',
-      background: 'transparent',
-      padding: '0'
-    })
-    label.addEventListener('click', async (event) => {
-      event?.domEvent?.stopPropagation?.()
-      await openDeviceDetail(device)
-    })
-    mapInstance.addOverlay(label)
-    mapOverlays.push(label)
-  })
-  fitMapViewport()
-}
-
-let _renderTimer = null
-function renderMap() {
-  if (!mapInstance || !window.BMapGL) return
-  if (_renderTimer) clearTimeout(_renderTimer)
-  _renderTimer = setTimeout(() => {
-    renderMapOverlays()
-    _renderTimer = null
-  }, 50)
-}
-
-function ensureBaiduMap() {
-  if (window.BMapGL) {
-    return Promise.resolve(window.BMapGL)
-  }
-  if (baiduMapScriptPromise) {
-    return baiduMapScriptPromise
-  }
-  if (!BAIDU_MAP_AK) {
-    return Promise.reject(new Error('未配置百度地图 AK,请在 .env.development 中配置 VITE_BAIDU_MAP_AK'))
-  }
-
-  baiduMapScriptPromise = new Promise((resolve, reject) => {
-    const existing = document.querySelector('script[data-baidu-map="webgl"]')
-    if (existing) {
-      let attempts = 0
-      const timer = window.setInterval(() => {
-        attempts++
-        if (window.BMapGL) {
-          window.clearInterval(timer)
-          resolve(window.BMapGL)
-        } else if (attempts >= 50) {
-          window.clearInterval(timer)
-          reject(new Error('百度地图脚本加载超时'))
-        }
-      }, 200)
-      return
-    }
-
-    const script = document.createElement('script')
-    script.src = `https://api.map.baidu.com/api?type=webgl&v=1.0&ak=${BAIDU_MAP_AK}`
-    script.async = true
-    script.defer = true
-    script.setAttribute('data-baidu-map', 'webgl')
-    script.onload = () => {
-      let attempts = 0
-      const timer = window.setInterval(() => {
-        attempts++
-        if (window.BMapGL) {
-          window.clearInterval(timer)
-          resolve(window.BMapGL)
-        } else if (attempts >= 50) {
-          window.clearInterval(timer)
-          reject(new Error('百度地图脚本初始化超时'))
-        }
-      }, 200)
-    }
-    script.onerror = () => reject(new Error('百度地图脚本加载失败'))
-    document.head.appendChild(script)
-  })
-
-  return baiduMapScriptPromise
-}
-
-async function initMap() {
-  if (!baiduMapRef.value) return
-  try {
-    await ensureBaiduMap()
-    if (!mapInstance) {
-      mapInstance = new window.BMapGL.Map(baiduMapRef.value, { enableMapClick: true })
-      mapInstance.enableScrollWheelZoom(true)
-      mapInstance.setMinZoom(11)
-      mapInstance.setMaxZoom(18)
-      mapInstance.centerAndZoom(toBdPoint(DEFAULT_MAP_CENTER.lng, DEFAULT_MAP_CENTER.lat), 14)
-      mapInstance.addEventListener('click', () => {
-        drawerVisible.value = false
-      })
-    }
-    mapError.value = ''
-    renderMap()
-  } catch (error) {
-    mapError.value = error?.message || '百度地图加载失败'
-  }
-}
-
-async function loadDeviceList() {
-  listLoading.value = true
-  try {
-    const keyword = queryParams.value.keyword?.trim()
-    
-    // 只传 keyword 和 isQueryManholeData,不传状态筛选
-    // 树勾选过滤在 displayDevices computed 中做前端过滤
-    const res = await getManholeLayerData({
-      keyword: keyword || undefined,
-      isQueryManholeData: true
-    })
-    
-    const result = res.data || {}
-    mockManholes.value = (result.devices || []).map((item, index) => normalizeDevice(item, index))
-    
-    // 同步统计信息
-    const stats = result.statistics || {}
-    console.log('[alarmSanalysis] 后端统计数据:', stats)
-    
-    // displayDevices computed 和 watch 自动触发树、地图刷新
-  } catch (error) {
-    mockManholes.value = []
-    const msg = error?.message || error?.msg || '未知错误'
-    console.error('[alarmSanalysis] 加载失败:', error)
-    ElMessage.error('井盖设备数据加载失败: ' + msg)
-  } finally {
-    listLoading.value = false
-  }
-}
-
-async function openDeviceDetail(device) {
-  if (!device?.equipmentId) return
-  selectedDevice.value = device
-  drawerVisible.value = true
-  detailLoading.value = true
-  try {
-    const res = await getManholeAlarmDeviceDetail(device.equipmentId)
-    if (res?.data) {
-      const index = mockManholes.value.findIndex(item => item.equipmentId === device.equipmentId)
-      selectedDevice.value = normalizeDevice(res.data, index, device)
-      if (mapInstance && window.BMapGL && isValidCoordinate(selectedDevice.value.lng, selectedDevice.value.lat)) {
-        mapInstance.panTo(toBdPoint(selectedDevice.value.lng, selectedDevice.value.lat))
-      }
-      renderMap()
-    }
-  } catch (error) {
-    ElMessage.error('设备详情加载失败')
-  } finally {
-    detailLoading.value = false
-  }
-}
-
-function zoomIn() {
-  if (!mapInstance) return
-  mapInstance.setZoom(Math.min(mapInstance.getZoom() + 1, 18))
-}
-function zoomOut() {
-  if (!mapInstance) return
-  mapInstance.setZoom(Math.max(mapInstance.getZoom() - 1, 11))
-}
-function resetView() {
-  if (!mapInstance || !window.BMapGL) return
-  fitMapViewport()
-}
-
-const onLayerChange = (_data, checkedInfo) => {
-  // check-strictly 模式下 checkedKeys 只含叶子节点,仅需排除 device-*
-  const statusKeys = checkedInfo.checkedKeys.filter(key => !key.startsWith('device-'))
-  const newSet = new Set(statusKeys)
-  const oldSet = visibleLayers.value
-  // 值未变时跳过,阻断 setCheckedKeys → onLayerChange → watch 循环
-  if (newSet.size === oldSet.size && [...newSet].every(k => oldSet.has(k))) return
-  visibleLayers.value = newSet
-  checkedKeys.value = statusKeys
-}
-const focusLayer = async (type) => {
-  activeQuickFilter.value = activeQuickFilter.value === type ? '' : type
-  const target = displayDevices.value.find(item => item.status === type)
-  if (!target) {
-    if (activeQuickFilter.value) {
-      ElMessage.info('当前筛选下无对应设备')
-    }
-    return
-  }
-  if (!activeQuickFilter.value) {
-    ElMessage.info('当前无对应设备')
-    return
-  }
-  await nextTick()
-  renderMap()
-}
-
-const selectedDevice = ref(null)
-const drawerVisible = ref(false)
-const timeRange = ref([])
-const shortcuts = [{ text: '最近24小时', value: () => [new Date(Date.now() - 86400000), new Date()] }]
-
-const getStatusTag = (status) => {
-  if (status === 'critical') return 'danger'
-  if (status === 'warning') return 'warning'
-  if (status === 'offline') return 'info'
-  return 'success'
-}
-const getAlertAdvice = (device) => {
-  if (device.alertType?.includes('倾斜')) return '建议立即安排检修人员现场确认井盖固定情况,如损坏需更换。'
-  if (device.alertType?.includes('水位')) return '井内水位过高,需紧急排水,检查管道是否堵塞。'
-  if (device.alertType?.includes('水浸')) return '建议核查井内积水和周边排水情况,必要时安排现场排查。'
-  if (device.alertType?.includes('低电量')) return '建议尽快安排电池更换,避免设备掉线。'
-  if (device.alertType?.includes('弱信号')) return '建议核查设备天线、网络覆盖和安装环境。'
-  return '请根据现场情况迅速核实并处置。'
-}
-const dispatchWorkOrder = () => { ElMessage.success('已派发工单至维护班组') }
-
-function handleSearch() {
-  activeQuickFilter.value = ''
-  loadDeviceList()
-}
-
-function handleReset() {
-  queryParams.value.keyword = ''
-  activeQuickFilter.value = ''
-  visibleLayers.value = new Set(DEFAULT_CHECKED_KEYS)
-  checkedKeys.value = [...DEFAULT_CHECKED_KEYS]
-  loadDeviceList()
-}
-
-watch(displayDevices, async () => {
-  refreshBounds()
-  await nextTick()
-  // 树数据变化后 el-tree 内部状态被重置,用 setCheckedKeys 恢复勾选
-  layerTreeRef.value?.setCheckedKeys(checkedKeys.value)
-  renderMap()
-}, { deep: true })
-
-onMounted(async () => {
-  await nextTick()
-  await initMap()
-  await loadDeviceList()
-  // renderMap 由 watch(displayDevices) 自动触发,避免重复渲染
-  window.addEventListener('resize', renderMap)
-})
-onUnmounted(() => {
-  window.removeEventListener('resize', renderMap)
-  clearMapOverlays()
-  mapInstance = null
-})
-</script>
-
-<style scoped>
-.gis-alert-dashboard {
-  background: #eef2f0;
-  height: 100vh;
-  display: flex;
-  flex-direction: column;
-  overflow: hidden;
-}
-.top-bar {
-  background: white;
-  padding: 10px 24px;
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  box-shadow: 0 2px 10px rgba(0,0,0,0.05);
-}
-.logo-area { display: flex; align-items: center; gap: 14px; }
-.title { font-size: 1.5rem; font-weight: 700; background: linear-gradient(135deg, #1e5128, #409eff); -webkit-background-clip: text; background-clip: text; color: transparent; }
-.alert-stats { display: flex; gap: 24px; }
-.stat { text-align: center; cursor: pointer; padding: 4px 16px; border-radius: 40px; transition: 0.2s; }
-.stat:hover { background: #f0f2f5; }
-.stat-value { font-size: 26px; font-weight: bold; display: block; line-height: 1.2; }
-.stat-value.critical { color: #f56c6c; }
-.stat-value.warning { color: #e6a23c; }
-.stat-value.offline { color: #909399; }
-.stat-label { font-size: 12px; color: #606266; }
-.main-layout { display: flex; flex: 1; gap: 16px; padding: 16px; overflow: hidden; }
-.layer-control {
-  width: 280px;
-  background: white;
-  border-radius: 20px;
-  padding: 16px;
-  display: flex;
-  flex-direction: column;
-  gap: 14px;
-  overflow-y: auto;
-}
-.layer-header { font-weight: bold; display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
-.layer-filter-panel {
-  display: flex;
-  flex-direction: column;
-  gap: 10px;
-}
-.filter-actions {
-  display: flex;
-  gap: 8px;
-}
-.filter-summary {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 6px;
-}
-.layer-tree { flex: 1; }
-.layer-badge { margin-left: auto; }
-.legend-panel { border-top: 1px solid #e9ecef; padding-top: 12px; margin-top: 8px; }
-.legend-title { margin-bottom: 8px; font-weight: 500; font-size: 13px; }
-.legend-marker { display: inline-block; width: 14px; height: 14px; border-radius: 14px; margin-right: 8px; vertical-align: middle; }
-.critical-dot { background: #f56c6c; box-shadow: 0 0 0 2px rgba(245,108,108,0.4); }
-.warning-dot { background: #e6a23c; }
-.normal-dot { background: #67c23a; }
-.offline-dot { background: #909399; }
-.alarm-pulse { width: 14px; height: 14px; background: red; border-radius: 50%; animation: pulse 1s infinite; display: inline-block; margin-right: 8px; }
-@keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.4; transform: scale(1.2); } 100% { opacity: 1; } }
-.gis-map-area {
-  flex: 1;
-  background: white;
-  border-radius: 20px;
-  display: flex;
-  flex-direction: column;
-  overflow: hidden;
-}
-.map-toolbar {
-  padding: 12px 16px;
-  border-bottom: 1px solid #edf2f0;
-  display: flex;
-  justify-content: space-between;
-}
-.current-stat { display: flex; gap: 20px; font-size: 13px; }
-.map-container {
-  flex: 1;
-  position: relative;
-  overflow: hidden;
-}
-.baidu-map {
-  width: 100%;
-  height: 100%;
-}
-.empty-state {
-  position: absolute;
-  top: 50%;
-  left: 50%;
-  transform: translate(-50%, -50%);
-  color: #909399;
-  font-size: 14px;
-  background: rgba(255, 255, 255, 0.92);
-  padding: 10px 18px;
-  border-radius: 12px;
-  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
-  z-index: 2;
-}
-.map-error {
-  position: absolute;
-  top: 16px;
-  right: 16px;
-  max-width: 320px;
-  color: #f56c6c;
-  background: rgba(255, 255, 255, 0.95);
-  border: 1px solid rgba(245, 108, 108, 0.25);
-  border-radius: 12px;
-  padding: 10px 12px;
-  font-size: 12px;
-  line-height: 1.6;
-  z-index: 3;
-}
-.advice-box { background: #f8f9fa; padding: 12px; border-radius: 12px; }
-.panel-tip {
-  font-size: 12px;
-  color: #909399;
-  line-height: 1.5;
-}
-</style>

+ 2 - 3
src/views/subSystem/waterSupply/alarm/alarmGIS.vue

@@ -2,7 +2,7 @@
   <div class="alarm-page alarm-gis-page">
   <div class="alarm-page alarm-gis-page">
     <header class="gis-page-heading">
     <header class="gis-page-heading">
       <div><h1>当前报警 GIS 一张图</h1><p>按设备查看报警分布,关联监测曲线与报警记录</p></div>
       <div><h1>当前报警 GIS 一张图</h1><p>按设备查看报警分布,关联监测曲线与报警记录</p></div>
-      <span class="gis-view-label"><el-icon><MapLocation /></el-icon>供水监测视图</span>
+      <span class="gis-view-label"><el-icon><MapLocation /></el-icon>{{ viewLabel }}</span>
     </header>
     </header>
 
 
     <section class="gis-filter-card">
     <section class="gis-filter-card">
@@ -105,7 +105,6 @@
 <script setup name="WaterSupplyAlarmGIS">
 <script setup name="WaterSupplyAlarmGIS">
 import { ref, computed, watch, nextTick, onBeforeUnmount } from 'vue'
 import { ref, computed, watch, nextTick, onBeforeUnmount } from 'vue'
 import { Search, Refresh, MapLocation, Location, Aim, Plus, Minus, ArrowRight, Warning, Loading } from '@element-plus/icons-vue'
 import { Search, Refresh, MapLocation, Location, Aim, Plus, Minus, ArrowRight, Warning, Loading } from '@element-plus/icons-vue'
-import { WATER_DEVICE_ROOT_TYPE_ID } from '@/utils/waterHomeModel'
 import GisMapCanvas from '@/components/GisMapCanvas.vue'
 import GisMapCanvas from '@/components/GisMapCanvas.vue'
 import { getAlarmMap, getAlarmOptions, getAlarms } from '@/api/alarm'
 import { getAlarmMap, getAlarmOptions, getAlarms } from '@/api/alarm'
 import AlarmDetail from '../../alarm/AlarmDetail.vue'
 import AlarmDetail from '../../alarm/AlarmDetail.vue'
@@ -117,6 +116,7 @@ import { useAlarmScope } from '../../alarm/common/js/useAlarmScope'
 import '../../alarm/common/styles/alarmOperations.scss'
 import '../../alarm/common/styles/alarmOperations.scss'
 
 
 const { scope, scopeKey } = useAlarmScope()
 const { scope, scopeKey } = useAlarmScope()
+const viewLabel = computed(() => ({ 1: '供水监测视图', 3: '燃气监测视图', 4: '窨井监测视图' }[scope.value.rootTypeId] || '设备监测视图'))
 const baseUrl = import.meta.env.VITE_APP_BASE_API
 const baseUrl = import.meta.env.VITE_APP_BASE_API
 const keyword = ref(''), level = ref(), options = ref({}), rows = ref([]), selected = ref(null), tab = ref('curve'), curveId = ref('')
 const keyword = ref(''), level = ref(), options = ref({}), rows = ref([]), selected = ref(null), tab = ref('curve'), curveId = ref('')
 const loading = ref(false), error = ref(''), mapError = ref(''), mapReady = ref(false), mapKey = ref(0), mapRef = ref(), detailRef = ref(), cluster = ref([])
 const loading = ref(false), error = ref(''), mapError = ref(''), mapReady = ref(false), mapKey = ref(0), mapRef = ref(), detailRef = ref(), cluster = ref([])
@@ -168,7 +168,6 @@ function retryMap() { mapError.value = ''; mapReady.value = false; ++mapKey.valu
 function reset() { keyword.value = ''; level.value = undefined; return load() }
 function reset() { keyword.value = ''; level.value = undefined; return load() }
 watch(scopeKey, async () => {
 watch(scopeKey, async () => {
   ++generation; rows.value = []; selected.value = null; detailVisible.value = false; loading.value = false; error.value = ''; options.value = {}
   ++generation; rows.value = []; selected.value = null; detailVisible.value = false; loading.value = false; error.value = ''; options.value = {}
-  if (scope.value.rootTypeId !== WATER_DEVICE_ROOT_TYPE_ID) { error.value = '该地图仅适用于供水子系统'; return }
   const key = scopeKey.value
   const key = scopeKey.value
   try { const result = await getAlarmOptions({ ...scope.value }); if (key === scopeKey.value) options.value = result.data || {} }
   try { const result = await getAlarmOptions({ ...scope.value }); if (key === scopeKey.value) options.value = result.data || {} }
   catch (e) { if (key === scopeKey.value) { error.value = e.message || '查询选项加载失败'; return } }
   catch (e) { if (key === scopeKey.value) { error.value = e.message || '查询选项加载失败'; return } }