LAPTOP-JI2IUVG1\26646 1 долоо хоног өмнө
parent
commit
999cdb3d87

+ 1122 - 8
src/views/subSystem/drainage/home.vue

@@ -1,14 +1,1128 @@
-<!--排水管网安全运行监测系统-->
-<script setup>
+<template>
+  <div class="drainage-home-screen">
+    <!-- ==================== 顶部标题栏 ==================== -->
+    <div class="screen-header">
+      <div class="header-left">
+        <span class="header-time">{{ currentDateTime }}</span>
+      </div>
+      <div class="header-center">
+        <h1 class="header-title">排水管网安全运行监测系统</h1>
+      </div>
+      <div class="header-right"></div>
+    </div>
 
-</script>
+    <!-- ==================== Tab 1: 雨水污水 ==================== -->
+    <div v-show="activeTab === 0" class="screen-body">
+      <div class="panel-left">
+        <div class="panel-box">
+          <div class="panel-title"><span class="title-icon">▶</span> 天气预报</div>
+          <div class="weather-cards">
+            <div class="weather-card" v-for="(w, i) in weatherList" :key="i">
+              <div class="weather-date">{{ w.date }}</div>
+              <div class="weather-icon">{{ getWeatherIcon(w.text) }}</div>
+              <div class="weather-desc">{{ w.desc }}</div>
+              <div class="weather-temp">{{ w.tempRange }}</div>
+              <div class="weather-wind">{{ w.wind }}</div>
+            </div>
+          </div>
+        </div>
+        <div class="panel-box">
+          <div class="panel-title"><span class="title-icon">▶</span> 监测点概况</div>
+          <div class="overview-cards">
+            <div class="overview-card"><div class="ov-label">监测总点数</div><div class="ov-value">{{ rwOverview.total }}</div></div>
+            <div class="overview-card"><div class="ov-label">在线监测点</div><div class="ov-value online">{{ rwOverview.online }}</div></div>
+            <div class="overview-card"><div class="ov-label">离线监测点</div><div class="ov-value offline">{{ rwOverview.offline }}</div></div>
+          </div>
+          <div class="pie-chart-wrapper">
+            <div id="rwPieChart" class="chart-area"></div>
+            <div class="pie-legend">
+              <div class="legend-item"><span class="legend-dot" style="background:#67c23a"></span><span class="legend-label">在线率</span><span class="legend-value">{{ rwOnlineRate }}%</span></div>
+              <div class="legend-item"><span class="legend-dot" style="background:#f56c6c"></span><span class="legend-label">离线率</span><span class="legend-value">{{ 100 - rwOnlineRate }}%</span></div>
+            </div>
+          </div>
+        </div>
+        <div class="panel-box">
+          <div class="panel-title"><span class="title-icon">▶</span> 实时数据 <span class="refresh-time">刷新: {{ refreshTime }}</span></div>
+          <div class="realtime-table-wrap">
+            <table class="dark-table">
+              <thead><tr><th>序号</th><th>监测点</th><th>时间</th><th>积水量</th><th>排水量</th></tr></thead>
+              <tbody>
+                <tr v-for="(row, i) in rwRealtimeData" :key="i">
+                  <td>{{ i + 1 }}</td><td :title="row.name">{{ row.name }}</td><td>{{ row.time }}</td><td>{{ row.waterlogging }}</td><td>{{ row.drainage }}</td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </div>
+      </div>
+      <div class="panel-center">
+        <div id="rwMap" class="map-container"></div>
+      </div>
+      <div class="panel-right">
+        <div class="panel-box">
+          <div class="panel-title"><span class="title-icon">▶</span> 近日排水量</div>
+          <div id="rwDrainageBarChart" class="chart-area-sm"></div>
+        </div>
+        <div class="panel-box">
+          <div class="panel-title"><span class="title-icon">▶</span> 近日积水量</div>
+          <div id="rwWaterlogBarChart" class="chart-area-sm"></div>
+        </div>
+        <div class="panel-box">
+          <div class="panel-title"><span class="title-icon">▶</span> 预警信息</div>
+          <div class="warn-summary">
+            <div class="warn-card"><div class="warn-icon">✅</div><div class="warn-label">设备在线</div><div class="warn-value online">{{ rwWarnSummary.online }}</div></div>
+            <div class="warn-card"><div class="warn-icon">❌</div><div class="warn-label">设备离线</div><div class="warn-value offline">{{ rwWarnSummary.offline }}</div></div>
+          </div>
+          <div class="realtime-table-wrap">
+            <table class="dark-table">
+              <thead><tr><th>序号</th><th>设备名称</th><th>时间</th><th>设备编号</th><th>设备类型</th></tr></thead>
+              <tbody>
+                <tr v-for="(row, i) in rwWarnData" :key="i">
+                  <td>{{ i + 1 }}</td><td :title="row.deviceName">{{ row.deviceName }}</td><td>{{ row.time }}</td><td>{{ row.deviceCode }}</td><td>{{ row.deviceType }}</td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </div>
+      </div>
+    </div>
 
-<template>
-  <div class="app-container">
-    <h1>排水管网安全运行监测系统</h1>
+    <!-- ==================== Tab 2: 设备管理 ==================== -->
+    <div v-show="activeTab === 1" class="screen-body">
+      <div class="panel-left">
+        <div class="panel-box">
+          <div class="panel-title"><span class="title-icon">▶</span> 基础信息</div>
+          <div class="overview-cards">
+            <div class="overview-card"><div class="ov-label">设备总数</div><div class="ov-value">{{ deviceOverview.total }}</div></div>
+            <div class="overview-card"><div class="ov-label">在线点</div><div class="ov-value online">{{ deviceOverview.online }}</div></div>
+            <div class="overview-card"><div class="ov-label">离线点</div><div class="ov-value offline">{{ deviceOverview.offline }}</div></div>
+          </div>
+          <div class="pie-chart-wrapper">
+            <div id="devicePieChart" class="chart-area"></div>
+            <div class="pie-legend">
+              <div class="legend-item"><span class="legend-dot" style="background:#67c23a"></span><span class="legend-label">在线率</span><span class="legend-value">{{ deviceOnlineRate }}%</span></div>
+              <div class="legend-item"><span class="legend-dot" style="background:#f56c6c"></span><span class="legend-label">离线率</span><span class="legend-value">{{ 100 - deviceOnlineRate }}%</span></div>
+            </div>
+          </div>
+        </div>
+        <div class="panel-box">
+          <div class="panel-title"><span class="title-icon">▶</span> 维修记录</div>
+          <div id="deviceRepairChart" class="chart-area-sm"></div>
+        </div>
+        <div class="panel-box">
+          <div class="panel-title"><span class="title-icon">▶</span> 点位数据 <span class="refresh-time">刷新: {{ refreshTime }}</span></div>
+          <div class="realtime-table-wrap">
+            <table class="dark-table">
+              <thead><tr><th>序号</th><th>设备名称</th><th>时间</th><th>安装位置</th><th>在线状态</th></tr></thead>
+              <tbody>
+                <tr v-for="(row, i) in devicePointData" :key="i">
+                  <td>{{ i + 1 }}</td><td :title="row.name">{{ row.name }}</td><td>{{ row.time }}</td><td :title="row.location">{{ row.location }}</td>
+                  <td><span :class="'level-tag level-' + row.statusClass">{{ row.status }}</span></td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </div>
+      </div>
+      <div class="panel-center">
+        <div id="deviceMap" class="map-container"></div>
+      </div>
+      <div class="panel-right">
+        <div class="panel-box">
+          <div class="panel-title"><span class="title-icon">▶</span> 设备信息</div>
+          <div class="realtime-table-wrap">
+            <table class="dark-table">
+              <thead><tr><th>序号</th><th>设备名称</th><th>设备编号</th><th>所在位置</th><th>运行状态</th></tr></thead>
+              <tbody>
+                <tr v-for="(row, i) in deviceList" :key="i">
+                  <td>{{ i + 1 }}</td><td :title="row.name">{{ row.name }}</td><td>{{ row.code }}</td><td :title="row.location">{{ row.location }}</td>
+                  <td><span :class="'level-tag level-' + row.statusClass">{{ row.status }}</span></td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </div>
+        <div class="panel-box">
+          <div class="panel-title"><span class="title-icon">▶</span> 警告信息</div>
+          <div class="realtime-table-wrap">
+            <table class="dark-table warn-table">
+              <thead><tr><th>告警等级</th><th>设备编码</th><th>时间</th><th>报警内容</th></tr></thead>
+              <tbody>
+                <tr v-for="(row, i) in deviceWarnData" :key="i">
+                  <td><span :class="'level-tag level-' + row.levelClass">{{ row.levelText }}</span></td><td>{{ row.code }}</td><td>{{ row.time }}</td><td :title="row.content">{{ row.content }}</td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </div>
+        <div class="panel-box">
+          <div class="panel-title"><span class="title-icon">▶</span> 报警统计</div>
+          <div class="alarm-stat">
+            <div class="alarm-stat-item"><div class="alarm-icon">⚠️</div><div class="alarm-label">一般危险</div><div class="alarm-value yellow">{{ alarmStat.general }}</div></div>
+            <div class="alarm-stat-item"><div class="alarm-icon">🚨</div><div class="alarm-label">严重危险</div><div class="alarm-value red">{{ alarmStat.severe }}</div></div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- ==================== Tab 3: 积水管理 ==================== -->
+    <div v-show="activeTab === 2" class="screen-body">
+      <div class="panel-left">
+        <div class="panel-box">
+          <div class="panel-title"><span class="title-icon">▶</span> 天气预报</div>
+          <div class="weather-cards">
+            <div class="weather-card" v-for="(w, i) in weatherList" :key="i">
+              <div class="weather-date">{{ w.date }}</div>
+              <div class="weather-icon">{{ getWeatherIcon(w.text) }}</div>
+              <div class="weather-desc">{{ w.desc }}</div>
+              <div class="weather-temp">{{ w.tempRange }}</div>
+              <div class="weather-wind">{{ w.wind }}</div>
+            </div>
+          </div>
+        </div>
+        <div class="panel-box">
+          <div class="panel-title"><span class="title-icon">▶</span> 监测点概况</div>
+          <div class="overview-cards">
+            <div class="overview-card"><div class="ov-label">在线</div><div class="ov-value online">{{ waterOverview.online }}</div></div>
+            <div class="overview-card"><div class="ov-label">离线</div><div class="ov-value offline">{{ waterOverview.offline }}</div></div>
+            <div class="overview-card"><div class="ov-label">在线率</div><div class="ov-value">{{ waterOnlineRate }}%</div></div>
+          </div>
+          <div class="pie-chart-wrapper">
+            <div id="waterPieChart" class="chart-area"></div>
+            <div class="pie-legend">
+              <div class="legend-item"><span class="legend-dot" style="background:#67c23a"></span><span class="legend-label">在线率</span><span class="legend-value">{{ waterOnlineRate }}%</span></div>
+              <div class="legend-item"><span class="legend-dot" style="background:#f56c6c"></span><span class="legend-label">离线率</span><span class="legend-value">{{ 100 - waterOnlineRate }}%</span></div>
+            </div>
+          </div>
+          <div class="level-counts">
+            <div class="level-count"><span class="dot green"></span>未积水: {{ waterLevelCount.none }}</div>
+            <div class="level-count"><span class="dot blue"></span>轻度积水: {{ waterLevelCount.light }}</div>
+            <div class="level-count"><span class="dot orange"></span>中度积水: {{ waterLevelCount.moderate }}</div>
+            <div class="level-count"><span class="dot red"></span>严重积水: {{ waterLevelCount.severe }}</div>
+          </div>
+        </div>
+        <div class="panel-box">
+          <div class="panel-title"><span class="title-icon">▶</span> 实时数据 <span class="refresh-time">刷新: {{ refreshTime }}</span></div>
+          <div class="realtime-table-wrap">
+            <table class="dark-table">
+              <thead><tr><th>序号</th><th>监测点</th><th>时间</th><th>积水量</th><th>级别</th></tr></thead>
+              <tbody>
+                <tr v-for="(row, i) in waterRealtimeData" :key="i">
+                  <td>{{ i + 1 }}</td><td :title="row.name">{{ row.name }}</td><td>{{ row.time }}</td><td>{{ row.depth }}cm</td>
+                  <td><span :class="'level-tag level-' + row.levelClass">{{ row.level }}</span></td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </div>
+      </div>
+      <div class="panel-center">
+        <div id="waterMap" class="map-container"></div>
+      </div>
+      <div class="panel-right">
+        <div class="panel-box">
+          <div class="panel-title"><span class="title-icon">▶</span> 积水分析</div>
+          <div class="analysis-form">
+            <div class="form-row"><label>年份</label><select v-model="analysisYear"><option v-for="y in analysisYears" :key="y" :value="y">{{ y }}</option></select></div>
+            <div class="form-row"><label>积水等级</label><select v-model="analysisLevel"><option v-for="opt in analysisLevels" :key="opt.value" :value="opt.value">{{ opt.label }}</option></select></div>
+            <button class="analysis-btn" @click="doWaterAnalysis">开始分析</button>
+            <div class="analysis-result">{{ waterAnalysisResult }}</div>
+          </div>
+        </div>
+        <div class="panel-box">
+          <div class="panel-title"><span class="title-icon">▶</span> 积水统计</div>
+          <div class="stat-tabs">
+            <span :class="{active: waterStatType==='month'}" @click="waterStatType='month'">按月</span>
+            <span :class="{active: waterStatType==='year'}" @click="waterStatType='year'">按年</span>
+          </div>
+          <div id="waterStatChart" class="chart-area-sm"></div>
+        </div>
+        <div class="panel-box">
+          <div class="panel-title"><span class="title-icon">▶</span> 预警信息</div>
+          <div class="warn-summary">
+            <div class="warn-card"><div class="warn-icon">💧</div><div class="warn-label">积水报警</div><div class="warn-value">{{ waterWarnSummary.water }}</div></div>
+            <div class="warn-card"><div class="warn-icon">✅</div><div class="warn-label">设备在线</div><div class="warn-value online">{{ waterWarnSummary.online }}</div></div>
+            <div class="warn-card"><div class="warn-icon">❌</div><div class="warn-label">设备离线</div><div class="warn-value offline">{{ waterWarnSummary.offline }}</div></div>
+          </div>
+          <div class="realtime-table-wrap">
+            <table class="dark-table">
+              <thead><tr><th>序号</th><th>地址</th><th>时间</th><th>报警类型</th><th>实时数据</th></tr></thead>
+              <tbody>
+                <tr v-for="(row, i) in waterWarnData" :key="i">
+                  <td>{{ i + 1 }}</td><td :title="row.addr">{{ row.addr }}</td><td>{{ row.time }}</td><td>{{ row.type }}</td><td>{{ row.value }}</td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- ==================== 底部 Tab 栏 ==================== -->
+    <div class="bottom-tabs">
+      <div v-for="tab in tabs" :key="tab.value" class="bottom-tab" :class="{ active: activeTab === tab.value }" @click="switchTab(tab.value)">
+        {{ tab.label }}
+      </div>
+    </div>
+
+    <!-- ==================== 设备详情弹窗 ==================== -->
+    <div v-if="deviceModalVisible" class="device-modal" @click.self="closeDeviceModal">
+      <div class="device-modal-content">
+        <div class="device-modal-header">
+          <span class="device-modal-title">设备详情</span>
+          <span class="device-modal-close" @click="closeDeviceModal">×</span>
+        </div>
+        <div class="device-info-list">
+          <div class="device-info-item"><span class="info-label">设备编号</span><span class="info-value">{{ selectedDevice.code }}</span></div>
+          <div class="device-info-item"><span class="info-label">设备名称</span><span class="info-value">{{ selectedDevice.name }}</span></div>
+          <div class="device-info-item"><span class="info-label">安装日期</span><span class="info-value">{{ selectedDevice.installDate }}</span></div>
+          <div class="device-info-item"><span class="info-label">运行状态</span><span class="info-value"><span :class="'level-tag level-' + selectedDevice.statusClass">{{ selectedDevice.status }}</span></span></div>
+          <div class="device-info-item"><span class="info-label">频率</span><span class="info-value">{{ selectedDevice.frequency }}</span></div>
+          <div class="device-info-item"><span class="info-label">地址</span><span class="info-value">{{ selectedDevice.address }}</span></div>
+        </div>
+      </div>
+    </div>
   </div>
 </template>
 
-<style scoped lang="scss">
+<script setup name="DrainageHome">
+import { ref, computed, onMounted, onUnmounted, nextTick, watch } from 'vue'
+import * as echarts from 'echarts'
+import locationIcon from '@/assets/images/location.png'
+import { getEquipmentPage, getWaterloggingPage, getPartitionPage, getEquipmentById, getAlarmDataPage } from '@/api/drainage'
+import { getAlarmPage } from '@/api/pipeNetwork/hazard'
+
+const BAIDU_MAP_AK = 'mvSNSN4CPN6WJs2Xi6sQFNmWwCLRgU6T'
+const MAP_STYLE_ID = '7a6c3490bbc3d1e3e9d8602530301e10'
+
+// ==================== 公共:实时时间 ====================
+const currentDateTime = ref('')
+const refreshTime = ref('')
+let timer = null
+function updateTime() {
+  const now = new Date()
+  const pad = n => String(n).padStart(2, '0')
+  const weekDays = ['日', '一', '二', '三', '四', '五', '六']
+  currentDateTime.value = `${now.getFullYear()}/${pad(now.getMonth() + 1)}/${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())} 星期${weekDays[now.getDay()]}`
+}
+function updateRefreshTime() {
+  const now = new Date()
+  const pad = n => String(n).padStart(2, '0')
+  refreshTime.value = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`
+}
+
+// ==================== 公共:Tab 切换 ====================
+const tabs = [
+  { label: '雨水污水', value: 0 },
+  { label: '设备管理', value: 1 },
+  { label: '积水管理', value: 2 }
+]
+const activeTab = ref(0)
+
+// ==================== 公共:天气 ====================
+const weatherList = ref([])
+function getWeatherIcon(text) {
+  if (!text) return '☀️'
+  const t = String(text)
+  if (t.includes('晴')) return '☀️'
+  if (t.includes('多云')) return '⛅'
+  if (t.includes('阴')) return '☁️'
+  if (t.includes('雨') && t.includes('雪')) return '🌨️'
+  if (t.includes('雪')) return '❄️'
+  if (t.includes('雷')) return '⛈️'
+  if (t.includes('雨')) return '🌧️'
+  if (t.includes('雾') || t.includes('霾')) return '🌫️'
+  return '☁️'
+}
+function jsonpRequest(url) {
+  return new Promise((resolve, reject) => {
+    const callbackName = 'drainageHomeWeatherCallback_' + Date.now()
+    const script = document.createElement('script')
+    script.src = url + (url.includes('?') ? '&' : '?') + 'callback=' + callbackName
+    script.onerror = () => { document.head.removeChild(script); delete window[callbackName]; reject(new Error('天气接口加载失败')) }
+    window[callbackName] = (data) => { document.head.removeChild(script); delete window[callbackName]; resolve(data) }
+    document.head.appendChild(script)
+  })
+}
+async function loadWeatherData() {
+  try {
+    const url = `/baidu-weather/weather/v1/?district_id=431222&data_type=all&ak=${BAIDU_MAP_AK}&output=json`
+    const response = await fetch(url)
+    const res = await response.json()
+    if (res && (res.status === 0 || res.status === '0') && res.result && Array.isArray(res.result.forecasts)) {
+      weatherList.value = res.result.forecasts.slice(0, 3).map(day => {
+        const dateStr = day.date || ''
+        const monthDay = dateStr.length >= 10 ? `${dateStr.slice(5, 7)}/${dateStr.slice(8, 10)}` : dateStr
+        return { date: monthDay, text: day.text_day || day.text_night || '多云', desc: day.text_day || '多云', tempRange: `${day.low || '--'}°C ~ ${day.high || '--'}°C`, wind: `${day.wd_day || ''} ${day.wc_day || ''}` }
+      })
+    } else { useMockWeather() }
+  } catch (e) { console.error('加载天气数据失败:', e); useMockWeather() }
+}
+function useMockWeather() {
+  const now = new Date(); const pad = n => String(n).padStart(2, '0')
+  const formatDate = (offset) => { const d = new Date(now.getTime() + offset * 24 * 60 * 60 * 1000); return `${pad(d.getMonth() + 1)}/${pad(d.getDate())}` }
+  weatherList.value = [
+    { date: formatDate(0), text: '多云', desc: '多云', tempRange: '18°C ~ 26°C', wind: '东南风 2级' },
+    { date: formatDate(1), text: '小雨', desc: '小雨', tempRange: '16°C ~ 23°C', wind: '东风 3级' },
+    { date: formatDate(2), text: '阴', desc: '阴', tempRange: '15°C ~ 22°C', wind: '北风 2级' }
+  ]
+}
+
+// ==================== Tab 1: 雨水污水 ====================
+const rwOverview = ref({ total: 0, online: 0, offline: 0 })
+const rwOnlineRate = computed(() => {
+  const total = Number(rwOverview.value.total || 0)
+  return total ? Math.round((Number(rwOverview.value.online || 0) / total) * 100) : 0
+})
+const rwRealtimeData = ref([])
+const rwWarnSummary = ref({ online: 0, offline: 0 })
+const rwWarnData = ref([])
+const rwMapPoints = ref([])
+
+// ==================== Tab 2: 设备管理 ====================
+const deviceOverview = ref({ total: 0, online: 0, offline: 0 })
+const deviceOnlineRate = computed(() => {
+  const total = Number(deviceOverview.value.total || 0)
+  return total ? Math.round((Number(deviceOverview.value.online || 0) / total) * 100) : 0
+})
+const devicePointData = ref([])
+const deviceList = ref([])
+const deviceWarnData = ref([])
+const alarmStat = ref({ general: 0, severe: 0 })
+const deviceMapPoints = ref([])
+const deviceModalVisible = ref(false)
+const selectedDevice = ref({ code: '', name: '', installDate: '', status: '', statusClass: '', frequency: '', address: '' })
+
+// ==================== Tab 3: 积水管理 ====================
+const waterOverview = ref({ online: 98, offline: 12 })
+const waterOnlineRate = computed(() => {
+  const online = Number(waterOverview.value.online || 0)
+  const offline = Number(waterOverview.value.offline || 0)
+  const total = online + offline
+  return total ? Math.round((online / total) * 100) : 0
+})
+const waterLevelCount = ref({ none: 45, light: 32, moderate: 18, severe: 15 })
+const waterRealtimeData = ref([])
+const waterWarnSummary = ref({ water: 8, online: 98, offline: 12 })
+const waterWarnData = ref([])
+const waterMapPoints = ref([])
+const analysisYear = ref(new Date().getFullYear())
+const analysisYears = Array.from({ length: 5 }, (_, i) => new Date().getFullYear() - i)
+const analysisLevel = ref('all')
+const analysisLevels = [{ value: 'all', label: '全部等级' }, { value: 'light', label: '轻度' }, { value: 'moderate', label: '中度' }, { value: 'severe', label: '严重' }]
+const waterAnalysisResult = ref('点击“开始分析”生成积水趋势分析结果。')
+const waterStatType = ref('month')
+
+// ==================== 地图实例 ====================
+const rwMap = ref(null), deviceMap = ref(null), waterMap = ref(null)
+
+function initBMapGL(containerId, mapRef, points, clickHandler) {
+  const container = document.getElementById(containerId)
+  if (!container || typeof BMapGL === 'undefined') { console.warn('BMapGL 未加载或容器不存在:', containerId); return }
+  try {
+    const map = new BMapGL.Map(containerId)
+    map.enableScrollWheelZoom(true)
+    map.setMapStyleV2({ styleId: MAP_STYLE_ID })
+    mapRef.value = map
+    if (points.length > 0) {
+      points.forEach(p => addLabelMarker(map, p, clickHandler))
+      const viewPoints = points.map(p => new BMapGL.Point(p.lng, p.lat))
+      map.setViewport(viewPoints)
+      if (map.getZoom() < 13) map.setZoom(13)
+    } else {
+      map.centerAndZoom(new BMapGL.Point(110.393, 28.452), 14)
+    }
+  } catch (e) { console.error('百度地图初始化失败:', e) }
+}
+
+function addLabelMarker(map, point, clickHandler) {
+  const bPoint = new BMapGL.Point(point.lng, point.lat)
+  const color = point.color || '#409eff'
+  const icon = point.icon || locationIcon
+  const html = `<div style="text-align:center;cursor:pointer;">
+    <div style="color:#fff;background:${color};border-radius:4px;padding:2px 6px;font-size:11px;white-space:nowrap;display:inline-block;margin-bottom:2px;">${point.name}</div>
+    ${point.subText ? `<div style="color:#fff;font-size:10px;background:rgba(0,0,0,0.6);border-radius:3px;padding:1px 4px;margin-bottom:2px;">${point.subText}</div>` : ''}
+    <div><img src="${icon}" style="width:24px;height:24px;display:block;margin:0 auto;"/></div>
+  </div>`
+  const label = new BMapGL.Label(html, { position: bPoint, offset: new BMapGL.Size(-30, -55) })
+  label.setStyle({ border: 'none', background: 'transparent', padding: '0' })
+  map.addOverlay(label)
+  if (clickHandler) {
+    label.addEventListener('click', () => clickHandler(point))
+  }
+}
+
+// ==================== ECharts 实例 ====================
+const chartInstances = {}
+
+function initChart(domId, option) {
+  const dom = document.getElementById(domId)
+  if (!dom) return null
+  if (chartInstances[domId]) chartInstances[domId].dispose()
+  chartInstances[domId] = echarts.init(dom)
+  chartInstances[domId].setOption(option)
+  return chartInstances[domId]
+}
+
+function getPieOption(rate) {
+  return {
+    tooltip: { trigger: 'item', formatter: '{b}: {d}%' },
+    graphic: [
+      { type: 'text', left: 'center', top: '35%', style: { text: '在线率', fill: '#a0cfff', fontSize: 11, textAlign: 'center' } },
+      { type: 'text', left: 'center', top: '50%', style: { text: `${rate}%`, fill: '#fff', fontSize: 18, fontWeight: 'bold', textAlign: 'center' } }
+    ],
+    series: [{
+      type: 'pie', radius: ['40%', '65%'], center: ['50%', '50%'], label: { show: false }, labelLine: { show: false },
+      data: [
+        { name: '在线', value: rate, itemStyle: { color: '#67c23a' } },
+        { name: '离线', value: 100 - rate, itemStyle: { color: '#f56c6c' } }
+      ],
+      emphasis: { scale: true, scaleSize: 5 }
+    }]
+  }
+}
+
+function getBarOption(xData, yData, name = '数值', color = '#409eff') {
+  return {
+    tooltip: { trigger: 'axis' },
+    grid: { left: 40, right: 10, top: 10, bottom: 25 },
+    xAxis: { type: 'category', data: xData, axisLabel: { color: '#7ec8e3', fontSize: 9 }, axisLine: { lineStyle: { color: '#1a3a5c' } } },
+    yAxis: { type: 'value', name, nameTextStyle: { color: '#7ec8e3', fontSize: 10 }, axisLabel: { color: '#7ec8e3', fontSize: 10 }, splitLine: { lineStyle: { color: '#1a3a5c' } }, axisLine: { show: false } },
+    series: [{ type: 'bar', data: yData, barWidth: 10, itemStyle: { color, borderRadius: [2, 2, 0, 0] } }]
+  }
+}
+
+// ==================== 数据加载(mock + API 占位) ====================
+async function loadAllData() {
+  await loadRwTabData()
+  await loadEquipmentData()
+  await loadWaterloggingData()
+}
+
+async function loadEquipmentData() {
+  try {
+    const eqPageRes = await getEquipmentPage(1, 999, { equipmentTypeId: 'drainage' })
+    const eqPageData = eqPageRes.code !== undefined ? eqPageRes.data : eqPageRes
+    const allEquipments = eqPageData?.records || eqPageData?.rows || []
+    console.log('[设备管理调试] 设备列表总数:', allEquipments.length)
+    if (allEquipments.length > 0) console.log('[设备管理调试] 设备示例:', JSON.stringify(allEquipments[0]))
+
+    // 统计在线/离线
+    const online = allEquipments.filter(e => e.onlineStatus === 1).length
+    const offline = allEquipments.filter(e => e.onlineStatus !== 1).length
+    deviceOverview.value = { total: allEquipments.length, online, offline }
+
+    // 设备列表
+    deviceList.value = allEquipments.map(e => ({
+      name: e.equipmentName || '',
+      code: e.equipmentCode || '',
+      location: e.equipmentLocation || '',
+      status: e.onlineStatus === 1 ? '在线' : '离线',
+      statusClass: e.onlineStatus === 1 ? 'none' : 'severe'
+    }))
+
+    // 点位数据
+    devicePointData.value = allEquipments.map(e => ({
+      name: e.equipmentName || '',
+      time: e.createTime || '',
+      location: e.equipmentLocation || '',
+      status: e.onlineStatus === 1 ? '在线' : '离线',
+      statusClass: e.onlineStatus === 1 ? 'none' : 'severe'
+    }))
+
+    // 地图标记
+    deviceMapPoints.value = allEquipments
+      .filter(e => e.longitude && e.latitude)
+      .map(e => ({
+        name: e.equipmentName || '',
+        lng: Number(e.longitude),
+        lat: Number(e.latitude),
+        color: e.onlineStatus === 1 ? '#67c23a' : '#f56c6c',
+        icon: locationIcon,
+        subText: e.onlineStatus === 1 ? '在线' : '离线'
+      }))
+
+    // 查询alarm_data报警数据(只查排水设备)
+    try {
+      const alarmDataRes = await getAlarmDataPage(1, 50, { deviceType: 'drainage' })
+      const alarmPageData = alarmDataRes.code !== undefined ? alarmDataRes.data : alarmDataRes
+      const alarmDataList = alarmPageData?.records || alarmPageData?.rows || []
+      console.log('[大屏调试] alarm_data返回:', alarmDataList.length, alarmDataList.length > 0 ? JSON.stringify(alarmDataList[0]) : '')
+      deviceWarnData.value = alarmDataList.slice(0, 10).map(a => {
+        const lvl = a.alarmLevel != null ? a.alarmLevel : (a.alarm_level != null ? a.alarm_level : null)
+        // alarmLevel: 1-最高 4-最低,为null时根据warningCode推断
+        let levelText = '--'
+        let levelClass = 'yellow'
+        if (lvl != null && lvl > 0) {
+          levelText = lvl === 1 ? '一级' : lvl === 2 ? '二级' : lvl === 3 ? '三级' : '四级'
+          levelClass = lvl <= 2 ? 'red' : lvl === 3 ? 'orange' : 'yellow'
+        } else {
+          levelText = '预警'
+          levelClass = 'orange'
+        }
+        return {
+          level: lvl || 3,
+          levelText,
+          levelClass,
+          code: a.deviceCode || a.device_code || '',
+          time: a.alarmTime || a.alarm_time || a.createTime || '',
+          content: a.warningType || a.warning_type || a.remark || ''
+        }
+      })
+      // 报警统计
+      const general = alarmDataList.filter(a => (a.alarmLevel || a.alarm_level || 0) >= 3).length
+      const severe = alarmDataList.filter(a => (a.alarmLevel || a.alarm_level || 0) >= 1 && (a.alarmLevel || a.alarm_level || 0) <= 2).length
+      alarmStat.value = { general, severe }
+    } catch (e) {
+      deviceWarnData.value = []
+    }
+  } catch (e) { console.error('加载设备数据失败:', e) }
+}
+
+async function loadWaterloggingData() {
+  try {
+    const res = await getWaterloggingPage(1, 999, {})
+    const pageData = res.code !== undefined ? res.data : res
+    const list = pageData?.records || pageData?.rows || []
+    console.log('[大屏调试] 积水数据:', list.length, list.length > 0 ? JSON.stringify(list[0]) : '')
+
+    // 解析waterloggingPoints JSON字符串为数组
+    function parseWaterloggingPoints(raw) {
+      if (!raw) return []
+      if (Array.isArray(raw)) return raw
+      if (typeof raw === 'string') {
+        try {
+          const parsed = JSON.parse(raw)
+          return Array.isArray(parsed) ? parsed : []
+        } catch (e) { return [] }
+      }
+      return []
+    }
+
+    // 积水等级判断(用waterloggingLevel字段:0=未积水, 1=轻度, 2=中度, 3=严重)
+    const levelMap = [
+      { label: '未积水', cls: 'none', color: '#67c23a' },
+      { label: '轻度积水', cls: 'light', color: '#409eff' },
+      { label: '中度积水', cls: 'moderate', color: '#e6a23c' },
+      { label: '严重积水', cls: 'severe', color: '#f56c6c' }
+    ]
+    const getLevelInfo = (item) => {
+      const lvl = Number(item.waterloggingLevel || 0)
+      return levelMap[lvl] || levelMap[0]
+    }
+
+    // 地图点位(从 waterloggingPoints 字段解析坐标)
+    const allMapPoints = []
+    list.forEach(item => {
+      const points = parseWaterloggingPoints(item.waterloggingPoints)
+      const lv = getLevelInfo(item)
+      points.forEach(p => {
+        if (p.lng && p.lat) {
+          allMapPoints.push({
+            name: p.name || item.name || '积水点',
+            lng: Number(p.lng),
+            lat: Number(p.lat),
+            color: lv.color,
+            subText: `${lv.label}`,
+            depth: item.waterVolume || item.waterDepth || 0,
+            level: lv.label,
+            levelClass: lv.cls
+          })
+        }
+      })
+    })
+    waterMapPoints.value = allMapPoints
+    console.log('[大屏调试] 积水地图点位数:', allMapPoints.length)
+
+    // 积水等级统计
+    let none = 0, light = 0, moderate = 0, severe = 0
+    list.forEach(p => {
+      const lv = getLevelInfo(p)
+      if (lv.cls === 'none') none++
+      else if (lv.cls === 'light') light++
+      else if (lv.cls === 'moderate') moderate++
+      else severe++
+    })
+    waterLevelCount.value = { none, light, moderate, severe }
+
+    // 实时数据表格
+    waterRealtimeData.value = list.slice(0, 12).map((p, i) => {
+      const lv = getLevelInfo(p)
+      return {
+        name: p.name || p.pointName || `积水点-${i + 1}`,
+        time: p.time || p.dataTime || p.updateTime || p.createTime || '',
+        depth: p.waterVolume || p.waterDepth || p.depth || 0,
+        level: lv.label,
+        levelClass: lv.cls
+      }
+    })
+
+    // 设备在线统计 - 获取关联设备
+    const allEqIds = []
+    list.forEach(p => {
+      if (p.equipmentId) {
+        p.equipmentId.split(',').forEach(id => {
+          const trimmed = id.trim()
+          if (trimmed) allEqIds.push(trimmed)
+        })
+      }
+    })
+    const uniqueEqIds = [...new Set(allEqIds)]
+
+    let online = 0, offline = 0
+    const linkedDeviceCodes = new Set()
+    if (uniqueEqIds.length > 0) {
+      try {
+        const eqRes = await getEquipmentPage(1, 999, { equipmentTypeId: 'drainage' })
+        const eqData = eqRes.code !== undefined ? eqRes.data : eqRes
+        const allEqs = eqData?.records || eqData?.rows || []
+        const eqMap = {}
+        allEqs.forEach(eq => { eqMap[eq.equipmentId] = eq })
+        uniqueEqIds.forEach(id => {
+          const eq = eqMap[id]
+          if (eq) {
+            if (eq.onlineStatus === 1) online++
+            else offline++
+            if (eq.equipmentCode) linkedDeviceCodes.add(eq.equipmentCode)
+          } else { offline++ }
+        })
+      } catch (e) {
+        online = uniqueEqIds.length
+      }
+    }
+    waterOverview.value = { online, offline }
+
+    // 报警信息 - 根据关联设备编码查alarm_data表
+    console.log('[大屏调试] 积水关联设备编码:', [...linkedDeviceCodes])
+    try {
+      const alarmRes = await getAlarmDataPage(1, 50, { deviceType: 'drainage' })
+      const alarmPageData = alarmRes.code !== undefined ? alarmRes.data : alarmRes
+      const alarmList = alarmPageData?.records || alarmPageData?.rows || []
+      // 只保留关联设备编码的报警
+      const filteredAlarms = linkedDeviceCodes.size > 0
+        ? alarmList.filter(a => linkedDeviceCodes.has(a.deviceCode))
+        : []
+      waterWarnData.value = filteredAlarms.slice(0, 10).map(a => ({
+        addr: a.deviceCode || '',
+        time: a.alarmTime || a.alarm_time || a.createTime || '',
+        type: a.warningType || a.warning_type || '积水报警',
+        value: a.actualValue != null ? `${a.actualValue}cm` : '--'
+      }))
+      waterWarnSummary.value = { water: filteredAlarms.length, online, offline }
+    } catch (e) {
+      waterWarnData.value = []
+      waterWarnSummary.value = { water: 0, online, offline }
+    }
+
+  } catch (e) {
+    console.error('加载积水数据失败:', e)
+  }
+}
+
+async function loadRwTabData() {
+  try {
+    // 1. 获取所有排水分区数据
+    const partRes = await getPartitionPage(1, 999, {})
+    // 兼容 AjaxResult包装 和 直接返回Page对象 两种格式
+    const pageData = partRes.code !== undefined ? partRes.data : partRes
+    const partitionList = pageData?.records || pageData?.rows || []
+    console.log('[大屏调试] 分区数据条数:', partitionList.length)
+    if (partitionList.length > 0) console.log('[大屏调试] 分区示例:', JSON.stringify(partitionList[0]))
+
+    // 2. 收集关联设备ID并查询设备状态
+    const allEquipmentIds = []
+    partitionList.forEach(p => {
+      if (p.equipmentId) {
+        p.equipmentId.split(',').forEach(id => {
+          const trimmed = id.trim()
+          if (trimmed) allEquipmentIds.push(trimmed)
+        })
+      }
+    })
+    const uniqueIds = [...new Set(allEquipmentIds)]
+
+    // 查询所有排水设备,通过ID匹配统计在线/离线
+    let online = 0, offline = 0
+    const equipmentDetails = {}
+    try {
+      const eqPageRes = await getEquipmentPage(1, 999, { equipmentTypeId: 'drainage' })
+      const eqPageData = eqPageRes.code !== undefined ? eqPageRes.data : eqPageRes
+      const allEquipments = eqPageData?.records || eqPageData?.rows || []
+      console.log('[大屏调试] 设备列表总数:', allEquipments.length, '分区关联设备ID:', uniqueIds)
+      if (allEquipments.length > 0) console.log('[大屏调试] 设备示例:', JSON.stringify(allEquipments[0]))
+      // 建立设备ID映射(设备主键字段是equipmentId)
+      const eqMap = {}
+      allEquipments.forEach(eq => { eqMap[eq.equipmentId] = eq })
+      console.log('[大屏调试] 设备ID映射keys:', Object.keys(eqMap).slice(0, 5))
+      // 统计关联设备状态
+      for (const id of uniqueIds) {
+        const eq = eqMap[id]
+        if (eq) {
+          console.log(`[大屏调试] 设备${id}匹配成功, onlineStatus=`, eq.onlineStatus)
+          if (eq.onlineStatus === 1) online++
+          else offline++
+          equipmentDetails[id] = { name: eq.equipmentName || '', code: eq.equipmentCode || '', typeName: eq.equipmentTypeName || '', isOnline: eq.onlineStatus === 1 }
+        } else {
+          console.log(`[大屏调试] 设备${id}在列表中未找到`)
+          offline++
+        }
+      }
+    } catch (e) {
+      console.error('[大屏调试] getEquipmentPage失败:', e)
+      // 如果分页查询失败,退回逐个查询
+      for (const id of uniqueIds) {
+        try {
+          const eqRes = await getEquipmentById(id)
+          const data = eqRes.code !== undefined ? eqRes.data : eqRes
+          console.log(`[大屏调试] getEquipmentById(${id}) 返回:`, JSON.stringify(data))
+          const info = data?.equipmentInfo || data
+          if (info?.onlineStatus === 1) online++
+          else offline++
+          equipmentDetails[id] = { name: info?.equipmentName || '', code: info?.equipmentCode || '', typeName: info?.equipmentTypeName || '', isOnline: info?.onlineStatus === 1 }
+        } catch (e2) { offline++ }
+      }
+    }
+    const total = uniqueIds.length
+    rwOverview.value = { total, online, offline }
+    rwWarnSummary.value = { online, offline }
+
+    // 3. 地图标记:使用排水分区的坐标
+    rwMapPoints.value = partitionList
+      .filter(p => p.longitude && p.latitude)
+      .map(p => ({
+        name: p.name || p.partitionName || '排水分区',
+        lng: Number(p.longitude),
+        lat: Number(p.latitude),
+        color: p.equipmentId ? '#67c23a' : '#409eff',
+        subText: p.partitionType || ''
+      }))
+
+    // 4. 实时数据表格:使用分区的真实排水量/积水量
+    rwRealtimeData.value = partitionList.map((p, i) => ({
+      name: p.name || p.partitionName || `监测点-${i + 1}`,
+      time: p.time || p.dataTime || p.createTime || '',
+      waterlogging: p.waterlogging != null ? p.waterlogging : '--',
+      drainage: p.drainage != null ? p.drainage : '--'
+    }))
+
+    // 5. 报警设备列表(只显示分区关联设备的报警)
+    try {
+      const alarmRes = await getAlarmPage(1, 100, { equipmentTypeId: 'drainage' })
+      const alarmPageData = alarmRes.code !== undefined ? alarmRes.data : alarmRes
+      const alarmList = alarmPageData?.records || alarmPageData?.rows || []
+      // 只保留关联设备的报警记录
+      const linkedEquipIds = new Set(uniqueIds)
+      const linkedEquipCodes = new Set(Object.values(equipmentDetails).map(eq => eq.code).filter(Boolean))
+      const filteredAlarms = alarmList.filter(a => {
+        return linkedEquipIds.has(a.equipmentId) || linkedEquipCodes.has(a.equipmentCode || a.deviceCode)
+      })
+      rwWarnData.value = filteredAlarms.slice(0, 10).map(a => {
+        const code = a.equipmentCode || a.deviceCode || a.code || ''
+        const codeNameMap = {}
+        Object.values(equipmentDetails).forEach(eq => { if (eq.code) codeNameMap[eq.code] = eq.name })
+        return {
+          deviceName: a.equipmentName || a.deviceName || a.name || codeNameMap[code] || '',
+          time: a.alarmTime || a.createTime || '',
+          deviceCode: code,
+          deviceType: a.equipmentTypeName || a.deviceType || '排水设备'
+        }
+      })
+    } catch (e) {
+      rwWarnData.value = []
+    }
+  } catch (e) {
+    console.error('加载雨水污水Tab数据失败:', e)
+  }
+}
+
+
+// ==================== Tab 图表初始化 ====================
+function initTab1Charts() {
+  initChart('rwPieChart', getPieOption(rwOnlineRate.value))
+  // 近日排水量/积水量 - 用分区真实数据作柱状图
+  const names = rwRealtimeData.value.map(r => r.name?.substring(0, 4) || '')
+  const drainVals = rwRealtimeData.value.map(r => Number(r.drainage) || 0)
+  const waterVals = rwRealtimeData.value.map(r => Number(r.waterlogging) || 0)
+  initChart('rwDrainageBarChart', getBarOption(names, drainVals, '', '#409eff'))
+  initChart('rwWaterlogBarChart', getBarOption(names, waterVals, '', '#e6a23c'))
+}
+
+function initTab2Charts() {
+  initChart('devicePieChart', getPieOption(deviceOnlineRate.value))
+  const months = ['1月', '2月', '3月', '4月', '5月', '6月']
+  initChart('deviceRepairChart', getBarOption(months, months.map(() => Math.floor(Math.random() * 20)), '次', '#e6a23c'))
+}
+
+function initTab3Charts() {
+  initChart('waterPieChart', getPieOption(waterOnlineRate.value))
+  updateWaterStatChart()
+}
+
+async function updateWaterStatChart() {
+  // 查询积水数据,按时间统计
+  try {
+    const res = await getWaterloggingPage(1, 9999, {})
+    const pageData = res.code !== undefined ? res.data : res
+    const allList = pageData?.records || pageData?.rows || []
+    let xData = [], yData = []
+    const now = new Date()
+    if (waterStatType.value === 'month') {
+      // 近6个月统计
+      xData = []
+      yData = []
+      for (let i = 5; i >= 0; i--) {
+        const d = new Date(now.getFullYear(), now.getMonth() - i, 1)
+        const month = d.getMonth() + 1
+        const year = d.getFullYear()
+        xData.push(`${month}月`)
+        const count = allList.filter(item => {
+          const t = item.time || item.dataTime || item.createTime || ''
+          if (!t) return false
+          const itemDate = new Date(t)
+          return itemDate.getFullYear() === year && itemDate.getMonth() + 1 === month
+        }).length
+        yData.push(count)
+      }
+    } else {
+      // 近5年统计
+      const currentYear = now.getFullYear()
+      for (let i = 4; i >= 0; i--) {
+        const y = currentYear - i
+        xData.push(String(y))
+        const count = allList.filter(item => {
+          const t = item.time || item.dataTime || item.createTime || ''
+          if (!t) return false
+          return new Date(t).getFullYear() === y
+        }).length
+        yData.push(count)
+      }
+    }
+    initChart('waterStatChart', getBarOption(xData, yData, '次', '#00e5ff'))
+  } catch (e) {
+    console.error('积水统计加载失败:', e)
+    initChart('waterStatChart', getBarOption([], [], '次', '#00e5ff'))
+  }
+}
+
+async function doWaterAnalysis() {
+  waterAnalysisResult.value = '正在查询...'
+  try {
+    const res = await getWaterloggingPage(1, 9999, {})
+    const pageData = res.code !== undefined ? res.data : res
+    const allList = pageData?.records || pageData?.rows || []
+    const year = analysisYear.value
+    const level = analysisLevel.value
+    // 按年份过滤
+    let filtered = allList.filter(item => {
+      const t = item.time || item.dataTime || item.createTime || ''
+      if (!t) return false
+      return new Date(t).getFullYear() === year
+    })
+    // 按等级过滤
+    if (level !== 'all') {
+      const getLevelCls = (item) => {
+        const depth = Number(item.waterDepth || item.depth || item.waterlogging || 0)
+        if (depth <= 0) return 'none'
+        if (depth <= 15) return 'light'
+        if (depth <= 30) return 'moderate'
+        return 'severe'
+      }
+      filtered = filtered.filter(item => getLevelCls(item) === level)
+    }
+    const count = filtered.length
+    const levelText = analysisLevels.find(l => l.value === level)?.label || '全部等级'
+    waterAnalysisResult.value = `${year}年${levelText}积水事件共 ${count} 起。`
+  } catch (e) {
+    waterAnalysisResult.value = '查询失败,请稍后重试。'
+    console.error('积水分析查询失败:', e)
+  }
+}
+
+watch(waterStatType, () => nextTick(updateWaterStatChart))
+
+// ==================== Tab 切换与地图初始化 ====================
+function initTabMaps() {
+  initBMapGL('rwMap', rwMap, rwMapPoints.value, null)
+  initBMapGL('deviceMap', deviceMap, deviceMapPoints.value, (point) => { selectedDevice.value = point; deviceModalVisible.value = true })
+  initBMapGL('waterMap', waterMap, waterMapPoints.value, null)
+}
+
+function switchTab(tab) {
+  activeTab.value = tab
+  nextTick(() => {
+    Object.values(chartInstances).forEach(c => c && c.resize())
+    if (tab === 0 && !rwMap.value) initBMapGL('rwMap', rwMap, rwMapPoints.value, null)
+    if (tab === 1 && !deviceMap.value) initBMapGL('deviceMap', deviceMap, deviceMapPoints.value, (point) => { selectedDevice.value = point; deviceModalVisible.value = true })
+    if (tab === 2 && !waterMap.value) initBMapGL('waterMap', waterMap, waterMapPoints.value, null)
+
+  })
+}
+
+function closeDeviceModal() { deviceModalVisible.value = false }
+
+// ==================== 生命周期 ====================
+onMounted(async () => {
+  updateTime()
+  timer = setInterval(() => { updateTime(); updateRefreshTime() }, 1000)
+  updateRefreshTime()
+  await Promise.all([loadAllData(), loadWeatherData()])
+  setTimeout(() => {
+    initTabMaps()
+    initTab1Charts()
+    initTab2Charts()
+    initTab3Charts()
+  }, 300)
+})
+
+onUnmounted(() => {
+  if (timer) clearInterval(timer)
+  Object.values(chartInstances).forEach(c => c && c.dispose())
+})
+
+window.addEventListener('resize', () => {
+  Object.values(chartInstances).forEach(c => c && c.resize())
+})
+</script>
+
+<style scoped>
+/* ==================== 全局 ==================== */
+.drainage-home-screen { width: 100%; height: 100vh; background: #0a1929; color: #fff; font-family: 'Microsoft YaHei', sans-serif; overflow: hidden; display: flex; flex-direction: column; position: relative; }
+
+/* ==================== 顶部 ==================== */
+.screen-header { height: 64px; background: linear-gradient(180deg, #0d2137 0%, #0a1929 100%); border-bottom: 1px solid rgba(64, 158, 255, 0.3); display: flex; align-items: center; justify-content: space-between; padding: 0 24px; flex-shrink: 0; position: relative; }
+.screen-header::after { content: ''; position: absolute; bottom: 0; left: 10%; right: 10%; height: 1px; background: linear-gradient(90deg, transparent, #409eff, transparent); }
+.header-left { flex: 1; text-align: left; }
+.header-time { font-size: 15px; color: #a0cfff; letter-spacing: 1px; }
+.header-center { flex: 2; text-align: center; }
+.header-title { font-size: 26px; font-weight: 700; margin: 0; letter-spacing: 4px; background: linear-gradient(180deg, #fff 0%, #7ec8e3 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
+.header-right { flex: 1; text-align: right; }
+
+/* ==================== 主体三栏 ==================== */
+.screen-body { flex: 1; display: flex; padding: 12px 12px 90px 12px; gap: 12px; min-height: 0; }
+.panel-left, .panel-right { width: 22%; min-width: 260px; display: flex; flex-direction: column; gap: 12px; min-height: 0; }
+.panel-center { flex: 1; display: flex; flex-direction: column; min-height: 0; position: relative; }
+
+/* ==================== 面板盒子 ==================== */
+.panel-box { background: rgba(6, 30, 65, 0.8); border: 1px solid rgba(64, 158, 255, 0.2); border-radius: 6px; padding: 12px; display: flex; flex-direction: column; flex: 1; min-height: 0; overflow: hidden; }
+.panel-title { font-size: 14px; font-weight: 600; color: #7ec8e3; margin-bottom: 10px; display: flex; align-items: center; gap: 4px; flex-shrink: 0; }
+.title-icon { color: #409eff; font-size: 10px; }
+.refresh-time { margin-left: auto; font-size: 11px; color: #607080; font-weight: 400; }
+
+/* ==================== 天气 ==================== */
+.weather-cards { display: flex; gap: 8px; flex-shrink: 0; }
+.panel-left .panel-box:first-child { flex: none; }
+.panel-left .panel-box:nth-child(2) { flex: none; }
+.weather-card { flex: 1; background: rgba(20, 60, 110, 0.5); border: 1px solid rgba(64, 158, 255, 0.15); border-radius: 6px; padding: 10px 8px; text-align: center; }
+.weather-date { font-size: 12px; color: #a0cfff; margin-bottom: 4px; }
+.weather-icon { font-size: 24px; margin: 4px 0; }
+.weather-desc { font-size: 12px; color: #d0e8ff; margin-bottom: 2px; }
+.weather-temp { font-size: 11px; color: #7ec8e3; }
+.weather-wind { font-size: 10px; color: #607080; margin-top: 2px; }
+
+/* ==================== 概况卡片 ==================== */
+.overview-cards { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin-bottom: 12px; flex-shrink: 0; }
+.overview-card { background: rgba(20, 60, 110, 0.4); border: 1px solid rgba(64, 158, 255, 0.15); border-radius: 4px; padding: 12px 8px; text-align: center; }
+.ov-label { font-size: 12px; color: #a0cfff; }
+.ov-value { font-size: 20px; font-weight: 700; color: #fff; margin-top: 6px; }
+.ov-value.online { color: #67c23a; }
+.ov-value.offline { color: #f56c6c; }
+.ov-value.warn { color: #e6a23c; }
+
+/* ==================== 环形图 ==================== */
+.pie-chart-wrapper { flex: 1; display: flex; align-items: center; min-height: 0; }
+.chart-area { width: 55%; height: 100%; min-height: 120px; }
+.pie-legend { width: 45%; display: flex; flex-direction: column; gap: 8px; }
+.legend-item { display: flex; align-items: center; gap: 6px; font-size: 12px; }
+.legend-dot { width: 10px; height: 10px; border-radius: 2px; flex-shrink: 0; }
+.legend-label { color: #a0cfff; flex: 1; }
+.legend-value { color: #fff; font-weight: 600; }
+
+/* ==================== 表格 ==================== */
+.realtime-table-wrap { flex: 1; overflow-y: auto; min-height: 0; }
+.dark-table { width: 100%; border-collapse: collapse; font-size: 12px; }
+.dark-table th { background: rgba(20, 60, 110, 0.6); color: #7ec8e3; padding: 7px 6px; text-align: center; border-bottom: 1px solid rgba(64, 158, 255, 0.2); font-weight: 500; white-space: nowrap; }
+.dark-table td { padding: 6px; text-align: center; color: #d0e8ff; border-bottom: 1px solid rgba(64, 158, 255, 0.08); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 80px; }
+.dark-table tbody tr:nth-child(odd) { background: rgba(10, 25, 50, 0.4); }
+.dark-table tbody tr:nth-child(even) { background: rgba(20, 40, 70, 0.3); }
+.dark-table tbody tr:hover { background: rgba(64, 158, 255, 0.1); }
+.level-tag { padding: 1px 6px; border-radius: 3px; font-size: 11px; }
+.level-none { color: #67c23a; background: rgba(103, 194, 58, 0.15); }
+.level-light { color: #409eff; background: rgba(64, 158, 255, 0.15); }
+.level-moderate { color: #e6a23c; background: rgba(230, 162, 60, 0.15); }
+.level-severe { color: #f56c6c; background: rgba(245, 108, 108, 0.15); }
+
+/* ==================== 地图 ==================== */
+.map-container { flex: 1; border-radius: 6px; overflow: hidden; border: 1px solid rgba(64, 158, 255, 0.2); min-height: 0; }
+.chart-area-sm { width: 100%; flex: 1; min-height: 130px; }
+
+/* ==================== 预警卡片 ==================== */
+.warn-summary { display: flex; gap: 8px; margin-bottom: 10px; flex-shrink: 0; }
+.warn-card { flex: 1; background: rgba(20, 60, 110, 0.4); border: 1px solid rgba(64, 158, 255, 0.15); border-radius: 6px; padding: 10px 4px; text-align: center; }
+.warn-icon { font-size: 20px; }
+.warn-label { font-size: 11px; color: #a0cfff; margin-top: 4px; }
+.warn-value { font-size: 16px; font-weight: 700; color: #fff; margin-top: 2px; }
+.warn-value.online { color: #67c23a; }
+.warn-value.offline { color: #f56c6c; }
+
+/* ==================== 警告信息 ==================== */
+.warn-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; }
+.warn-dot.red { background: #f56c6c; box-shadow: 0 0 6px #f56c6c; }
+.warn-dot.orange { background: #e6a23c; box-shadow: 0 0 6px #e6a23c; }
+.warn-dot.yellow { background: #f2d66c; box-shadow: 0 0 6px #f2d66c; }
+
+/* ==================== 报警统计 ==================== */
+.alarm-stat { display: flex; gap: 12px; justify-content: center; align-items: center; flex: 1; }
+.alarm-stat-item { text-align: center; }
+.alarm-icon { font-size: 28px; }
+.alarm-label { font-size: 12px; color: #a0cfff; margin-top: 4px; }
+.alarm-value { font-size: 20px; font-weight: 700; margin-top: 2px; }
+.alarm-value.yellow { color: #f2d66c; }
+.alarm-value.red { color: #f56c6c; }
+
+/* ==================== 积水等级数量 ==================== */
+.level-counts { display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px; margin-top: 10px; font-size: 12px; color: #a0cfff; flex-shrink: 0; }
+.level-count { display: flex; align-items: center; gap: 6px; }
+.dot { width: 8px; height: 8px; border-radius: 50%; }
+.dot.green { background: #67c23a; }
+.dot.blue { background: #409eff; }
+.dot.orange { background: #e6a23c; }
+.dot.red { background: #f56c6c; }
+
+/* ==================== 积水分析 ==================== */
+.analysis-form { display: flex; flex-direction: column; gap: 10px; }
+.form-row { display: flex; align-items: center; gap: 8px; }
+.form-row label { color: #a0cfff; font-size: 12px; width: 60px; }
+.form-row select { flex: 1; background: rgba(10, 25, 50, 0.6); border: 1px solid rgba(64, 158, 255, 0.3); color: #fff; padding: 5px 8px; border-radius: 4px; font-size: 12px; }
+.analysis-btn { background: rgba(64, 158, 255, 0.2); border: 1px solid rgba(64, 158, 255, 0.5); color: #7ec8e3; padding: 7px; border-radius: 4px; cursor: pointer; font-size: 12px; transition: all 0.3s; }
+.analysis-btn:hover { background: rgba(64, 158, 255, 0.4); color: #fff; }
+.analysis-result { background: rgba(10, 25, 50, 0.5); border: 1px solid rgba(64, 158, 255, 0.15); border-radius: 4px; padding: 8px; font-size: 12px; color: #d0e8ff; line-height: 1.6; min-height: 60px; }
+
+/* ==================== 统计切换 ==================== */
+.stat-tabs { display: flex; gap: 8px; margin-bottom: 8px; flex-shrink: 0; }
+.stat-tabs span { flex: 1; text-align: center; padding: 5px; font-size: 12px; color: #a0cfff; background: rgba(10, 25, 50, 0.5); border: 1px solid rgba(64, 158, 255, 0.2); border-radius: 4px; cursor: pointer; }
+.stat-tabs span.active { background: rgba(64, 158, 255, 0.3); color: #fff; border-color: rgba(64, 158, 255, 0.6); }
+
+/* ==================== 底部 Tab ==================== */
+.bottom-tabs { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); display: flex; gap: 16px; z-index: 100; background: rgba(6, 30, 65, 0.7); padding: 8px 16px; border-radius: 12px; border: 1px solid rgba(64, 158, 255, 0.2); }
+.bottom-tab { padding: 8px 32px; border-radius: 8px; font-size: 14px; font-weight: 600; color: #0a1929; background: #fff; cursor: pointer; transition: all 0.3s; border: 1px solid transparent; }
+.bottom-tab:hover { background: #e6f2ff; }
+.bottom-tab.active { background: #409eff; color: #fff; border-color: #409eff; box-shadow: 0 0 12px rgba(64, 158, 255, 0.5); }
+
+/* ==================== 弹窗 ==================== */
+.device-modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.7); display: flex; align-items: center; justify-content: center; z-index: 1000; }
+.device-modal-content { width: 420px; max-width: 90vw; background: rgba(6, 30, 65, 0.95); border: 1px solid rgba(64, 158, 255, 0.4); border-radius: 8px; padding: 16px; box-shadow: 0 0 20px rgba(64, 158, 255, 0.2); }
+.device-modal-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
+.device-modal-title { font-size: 15px; font-weight: 600; color: #7ec8e3; }
+.device-modal-close { font-size: 22px; color: #a0cfff; cursor: pointer; line-height: 1; }
+.device-modal-close:hover { color: #fff; }
+.device-info-list { display: flex; flex-direction: column; gap: 10px; }
+.device-info-item { display: flex; justify-content: space-between; font-size: 13px; padding: 8px; background: rgba(10, 25, 50, 0.5); border-radius: 4px; }
+.info-label { color: #a0cfff; }
+.info-value { color: #fff; }
 
-</style>
+/* ==================== 滚动条 ==================== */
+::-webkit-scrollbar { width: 4px; }
+::-webkit-scrollbar-track { background: rgba(10, 25, 50, 0.3); }
+::-webkit-scrollbar-thumb { background: rgba(64, 158, 255, 0.3); border-radius: 2px; }
+::-webkit-scrollbar-thumb:hover { background: rgba(64, 158, 255, 0.5); }
+</style>

+ 178 - 4
src/views/subSystem/drainage/jcsj/gcssgl.vue

@@ -45,10 +45,16 @@
       <el-table-column label="设施类型" prop="facilityType" min-width="100" />
       <el-table-column label="数据时间" prop="time" min-width="120" />
       <el-table-column label="标记数量" prop="pointCount" min-width="80" />
-      <el-table-column label="操作" width="180" align="center">
+      <el-table-column label="关联设备" min-width="160">
+        <template #default="{ row }">
+          {{ equipmentNameMap[row.id] || '' }}
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" width="240" align="center">
         <template #default="{ row }">
           <el-button link type="primary" @click="handleView(row)">查看</el-button>
           <el-button link type="primary" @click="handleEdit(row)">修改</el-button>
+          <el-button link type="primary" @click="handleBind(row)">关联设备</el-button>
           <el-button link type="danger" @click="handleDelete(row)">删除</el-button>
         </template>
       </el-table-column>
@@ -107,6 +113,18 @@
         </el-row>
       </div>
 
+      <!-- 关联设备 -->
+      <div v-if="viewEquipmentNames.length" class="detail-info" style="margin-top: 12px;">
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <div class="info-item">
+              <span class="info-label">关联设备:</span>
+              <span class="info-value">{{ viewEquipmentNames.join('、') }}</span>
+            </div>
+          </el-col>
+        </el-row>
+      </div>
+
       <!-- 地图区域 -->
       <div class="map-section">
         <div class="map-tabs">
@@ -176,18 +194,48 @@
         <el-button type="primary" @click="handleEditSubmit">确 定</el-button>
       </template>
     </el-dialog>
+
+    <!-- 关联设备弹框 -->
+    <el-dialog v-model="bindDialogVisible" title="关联设备" width="700px" @close="closeBindDialog">
+      <el-form :inline="true" :model="bindQueryParams" style="margin-bottom: 12px;">
+        <el-form-item label="设备名称">
+          <el-input v-model="bindQueryParams.equipmentName" placeholder="请输入设备名称" clearable />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleBindQuery">搜索</el-button>
+        </el-form-item>
+      </el-form>
+      <el-table v-loading="bindLoading" :data="bindTableData" border row-key="equipmentId"
+        @selection-change="handleBindSelectionChange" ref="bindTableRef">
+        <el-table-column type="selection" width="55" align="center" :reserve-selection="true" />
+        <el-table-column label="设备名称" prop="equipmentName" min-width="120" />
+        <el-table-column label="设备编号" prop="equipmentCode" min-width="120" />
+        <el-table-column label="设备类型" prop="equipmentTypeName" min-width="100" />
+        <el-table-column label="安装位置" prop="installLocation" min-width="120" />
+      </el-table>
+      <el-pagination v-model:current-page="bindPageNum" v-model:page-size="bindPageSize" :total="bindTotal"
+        :page-sizes="[10, 20, 50]" layout="total, sizes, prev, pager, next, jumper"
+        style="margin-top: 12px; justify-content: flex-end;" @size-change="loadBindData" @current-change="loadBindData" />
+      <template #footer>
+        <el-button @click="closeBindDialog">取 消</el-button>
+        <el-button type="primary" @click="submitBind">确 定</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup name="Gcssgl">
-import { ref, computed, onMounted } from 'vue'
+import { ref, computed, nextTick, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import locationIcon from '@/assets/images/location.png'
 import {
   getFacilityPage,
   addFacility,
   updateFacility,
-  deleteFacility
+  deleteFacility,
+  getEquipmentPage,
+  getEquipmentById,
+  bindFacilityEquipment
 } from '@/api/drainage'
 
 // 地图标记创建(用Label+img代替Marker)
@@ -299,6 +347,7 @@ async function loadData() {
     tableData.value = records
     total.value = pageData.total || 0
     renderMainMapMarkers()
+    resolveEquipmentNames()
   } catch (e) {
     console.error('加载工程设施数据失败', e)
   } finally {
@@ -306,6 +355,29 @@ async function loadData() {
   }
 }
 
+// 关联设备名称映射
+const equipmentNameMap = ref({})
+
+async function resolveEquipmentNames() {
+  const map = {}
+  for (const row of tableData.value) {
+    if (row.equipmentId) {
+      const ids = row.equipmentId.split(',')
+      const names = []
+      for (const id of ids) {
+        try {
+          const res = await getEquipmentById(id.trim())
+          const data = res.code !== undefined ? res.data : res
+          const info = data && data.equipmentInfo ? data.equipmentInfo : data
+          if (info && info.equipmentName) names.push(info.equipmentName)
+        } catch (e) { /* skip */ }
+      }
+      map[row.id] = names.join('、')
+    }
+  }
+  equipmentNameMap.value = map
+}
+
 onMounted(() => {
   loadData()
   setTimeout(() => { initMainMap() }, 500)
@@ -327,15 +399,30 @@ function handleQuery() {
 // ==================== 查看详情对话框 ====================
 const dialogVisible = ref(false)
 const currentRow = ref({})
+const viewEquipmentNames = ref([])
 let viewMapInstance = null
 
 const currentRowPointCount = computed(() => {
   return parsePoints(currentRow.value.points).length
 })
 
-function handleView(row) {
+async function handleView(row) {
   currentRow.value = { ...row }
   dialogVisible.value = true
+  viewEquipmentNames.value = []
+  if (row.equipmentId) {
+    const ids = row.equipmentId.split(',')
+    const names = []
+    for (const id of ids) {
+      try {
+        const res = await getEquipmentById(id.trim())
+        const data = res.code !== undefined ? res.data : res
+        const info = data && data.equipmentInfo ? data.equipmentInfo : data
+        if (info && info.equipmentName) names.push(info.equipmentName)
+      } catch (e) { /* skip */ }
+    }
+    viewEquipmentNames.value = names
+  }
 }
 
 function handleClose() {
@@ -610,6 +697,93 @@ function handleBatchDelete() {
     }
   }).catch(() => {})
 }
+
+// ==================== 关联设备 ====================
+const bindDialogVisible = ref(false)
+const bindLoading = ref(false)
+const bindTableData = ref([])
+const bindTotal = ref(0)
+const bindPageNum = ref(1)
+const bindPageSize = ref(10)
+const bindQueryParams = ref({ equipmentName: '', equipmentTypeId: 'drainage' })
+const selectedEquipmentIds = ref([])
+const currentPartitionId = ref('')
+const bindTableRef = ref(null)
+
+function handleBind(row) {
+  currentPartitionId.value = row.id
+  bindDialogVisible.value = true
+  bindPageNum.value = 1
+  bindQueryParams.value.equipmentName = ''
+  if (row.equipmentId) {
+    selectedEquipmentIds.value = row.equipmentId.split(',')
+  } else {
+    selectedEquipmentIds.value = []
+  }
+  nextTick(() => {
+    loadBindData()
+  })
+}
+
+function handleBindQuery() {
+  bindPageNum.value = 1
+  loadBindData()
+}
+
+async function loadBindData() {
+  bindLoading.value = true
+  try {
+    const res = await getEquipmentPage(bindPageNum.value, bindPageSize.value, bindQueryParams.value)
+    const pageData = res.code !== undefined ? res.data : res
+    bindTableData.value = pageData.records || []
+    bindTotal.value = pageData.total || 0
+    await nextTick()
+    if (selectedEquipmentIds.value.length > 0 && bindTableRef.value) {
+      bindTableData.value.forEach(row => {
+        if (selectedEquipmentIds.value.includes(row.equipmentId)) {
+          bindTableRef.value.toggleRowSelection(row, true)
+        }
+      })
+    }
+  } catch (e) {
+    console.error('加载设备列表失败', e)
+  } finally {
+    bindLoading.value = false
+  }
+}
+
+function handleBindSelectionChange(selection) {
+  selectedEquipmentIds.value = selection.map(item => item.equipmentId)
+}
+
+function closeBindDialog() {
+  bindDialogVisible.value = false
+  bindTableData.value = []
+  selectedEquipmentIds.value = []
+}
+
+async function submitBind() {
+  if (selectedEquipmentIds.value.length === 0) {
+    ElMessage.warning('请选择要关联的设备')
+    return
+  }
+  try {
+    const res = await bindFacilityEquipment({
+      partitionId: currentPartitionId.value,
+      equipmentIds: selectedEquipmentIds.value
+    })
+    if (res.code === 0 || res.code === 200) {
+      ElMessage.success('关联成功')
+      closeBindDialog()
+      loadData()
+    } else {
+      ElMessage.error(res.msg || '关联失败')
+    }
+  } catch (e) {
+    console.error('关联设备失败', e)
+    ElMessage.error('关联失败')
+  }
+}
 </script>
 
 <style scoped>

+ 55 - 43
src/views/subSystem/drainage/jcsj/jsqc.vue

@@ -155,7 +155,7 @@ function updateRefreshTime() {
 const weatherList = ref([])
 
 // 百度地图 AK(与 index.html 中加载百度地图 SDK 使用同一个)
-const BAIDU_MAP_AK = 'vMma3LYQQyrfhlFUm0CFzXFQhBrwxAmi'
+const BAIDU_MAP_AK = 'mvSNSN4CPN6WJs2Xi6sQFNmWwCLRgU6T'
 
 // 天气文字 -> emoji 图标映射
 function getWeatherIcon(text) {
@@ -194,8 +194,9 @@ function jsonpRequest(url) {
 
 async function loadWeatherData() {
   try {
-    const url = `https://api.map.baidu.com/weather/v1/?district_id=500156&data_type=all&ak=${BAIDU_MAP_AK}&output=json`
-    const res = await jsonpRequest(url)
+    const url = `/baidu-weather/weather/v1/?district_id=431222&data_type=all&ak=${BAIDU_MAP_AK}&output=json`
+    const response = await fetch(url)
+    const res = await response.json()
     if (res && (res.status === 0 || res.status === '0') && res.result && Array.isArray(res.result.forecasts)) {
       weatherList.value = res.result.forecasts.slice(0, 3).map(day => {
         const dateStr = day.date || ''
@@ -462,50 +463,61 @@ function initPieChart() {
   })
 }
 
-function initLineChart() {
-  // TODO: 对接24小时积水深度趋势API后替换mock数据
+async function initLineChart() {
+  // 统计最近6个月积水事件数
   const dom = document.getElementById('jsqcLineChart')
   if (!dom) return
   lineChart = echarts.init(dom)
-  const hours = []
-  const depthData = []
-  for (let i = 0; i < 24; i++) {
-    hours.push(String(i).padStart(2, '0') + ':00')
-    depthData.push(i < 8 || i > 20 ? Math.round(Math.random() * 3 * 10) / 10 : Math.round((5 + Math.random() * 20) * 10) / 10)
+  try {
+    const res = await getWaterloggingPage(1, 9999)
+    const pageData = res && res.code !== undefined ? res.data : res
+    const list = Array.isArray(pageData.records) ? pageData.records : (Array.isArray(pageData) ? pageData : [])
+    const now = new Date()
+    const xData = []
+    const yData = []
+    for (let i = 5; i >= 0; i--) {
+      const d = new Date(now.getFullYear(), now.getMonth() - i, 1)
+      const month = d.getMonth() + 1
+      const year = d.getFullYear()
+      xData.push(`${month}月`)
+      const count = list.filter(item => {
+        const t = item.time || item.dataTime || item.updateTime || item.createTime || ''
+        if (!t) return false
+        const itemDate = new Date(t)
+        return itemDate.getFullYear() === year && itemDate.getMonth() + 1 === month
+      }).length
+      yData.push(count)
+    }
+    lineChart.setOption({
+      tooltip: { trigger: 'axis' },
+      grid: { left: 40, right: 10, top: 10, bottom: 25 },
+      xAxis: {
+        type: 'category',
+        data: xData,
+        axisLabel: { color: '#7ec8e3', fontSize: 10 },
+        axisLine: { lineStyle: { color: '#1a3a5c' } }
+      },
+      yAxis: {
+        type: 'value',
+        name: '次',
+        nameTextStyle: { color: '#7ec8e3', fontSize: 10 },
+        axisLabel: { color: '#7ec8e3', fontSize: 10 },
+        splitLine: { lineStyle: { color: '#1a3a5c' } },
+        axisLine: { show: false }
+      },
+      series: [{
+        type: 'bar',
+        data: yData,
+        barWidth: 12,
+        itemStyle: {
+          color: '#00e5ff',
+          borderRadius: [2, 2, 0, 0]
+        }
+      }]
+    })
+  } catch (e) {
+    console.error('积水统计加载失败:', e)
   }
-  lineChart.setOption({
-    tooltip: { trigger: 'axis' },
-    grid: { left: 40, right: 10, top: 10, bottom: 25 },
-    xAxis: {
-      type: 'category',
-      data: hours,
-      axisLabel: { color: '#7ec8e3', fontSize: 9, interval: 2 },
-      axisLine: { lineStyle: { color: '#1a3a5c' } }
-    },
-    yAxis: {
-      type: 'value',
-      name: 'cm',
-      nameTextStyle: { color: '#7ec8e3', fontSize: 10 },
-      axisLabel: { color: '#7ec8e3', fontSize: 10 },
-      splitLine: { lineStyle: { color: '#1a3a5c' } },
-      axisLine: { show: false }
-    },
-    series: [{
-      type: 'bar',
-      data: depthData,
-      barWidth: 8,
-      itemStyle: {
-        color: (params) => {
-          const val = params.value
-          if (val >= 20) return '#f56c6c'
-          if (val >= 10) return '#e6a23c'
-          if (val > 0) return '#409eff'
-          return '#67c23a'
-        },
-        borderRadius: [2, 2, 0, 0]
-      }
-    }]
-  })
 }
 
 // ==================== 积水深度曲线弹窗 ====================

+ 185 - 3
src/views/subSystem/drainage/jcsj/psfqgl.vue

@@ -46,10 +46,16 @@
       <el-table-column label="排水量" prop="drainage" min-width="90" />
       <el-table-column label="积水量" prop="waterlogging" min-width="90" />
       <el-table-column label="积水等级" prop="level" min-width="80" />
-      <el-table-column label="操作" width="180" align="center">
+      <el-table-column label="关联设备" min-width="160">
+        <template #default="{ row }">
+          {{ equipmentNameMap[row.id] || '' }}
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" width="240" align="center">
         <template #default="{ row }">
           <el-button link type="primary" @click="handleView(row)">查看</el-button>
           <el-button link type="primary" @click="handleEdit(row)">修改</el-button>
+          <el-button link type="primary" @click="handleBind(row)">关联设备</el-button>
           <el-button link type="danger" @click="handleDelete(row)">删除</el-button>
         </template>
       </el-table-column>
@@ -107,6 +113,18 @@
         </el-row>
       </div>
 
+      <!-- 关联设备 -->
+      <div v-if="viewEquipmentNames.length" class="detail-info" style="margin-top: 12px;">
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <div class="info-item">
+              <span class="info-label">关联设备:</span>
+              <span class="info-value">{{ viewEquipmentNames.join('、') }}</span>
+            </div>
+          </el-col>
+        </el-row>
+      </div>
+
       <!-- 地图区域 -->
       <div class="map-section">
         <div class="map-tabs">
@@ -189,6 +207,37 @@
         <el-button type="primary" @click="handleEditSubmit">确 定</el-button>
       </template>
     </el-dialog>
+
+    <!-- 关联设备对话框 -->
+    <el-dialog v-model="bindDialogVisible" title="关联设备" width="800px">
+      <el-form :model="bindQueryParams" :inline="true" class="search-form">
+        <el-form-item label="设备名称">
+          <el-input v-model="bindQueryParams.equipmentName" placeholder="请输入设备名称" clearable style="width: 220px" />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleBindQuery">搜索</el-button>
+        </el-form-item>
+      </el-form>
+
+      <el-table ref="bindTableRef" v-loading="bindLoading" :data="bindTableData" row-key="equipmentId" border style="width: 100%" height="400"
+        @selection-change="handleBindSelectionChange">
+        <el-table-column type="selection" width="55" align="center" reserve-selection />
+        <el-table-column label="设备名称" prop="equipmentName" min-width="160" />
+        <el-table-column label="设备编号" prop="equipmentCode" min-width="140" />
+        <el-table-column label="设备类型" prop="equipmentTypeName" min-width="120" />
+        <el-table-column label="安装位置" prop="installLocation" min-width="160" />
+      </el-table>
+
+      <el-pagination v-model:current-page="bindPageNum" v-model:page-size="bindPageSize" :total="bindTotal"
+        :page-sizes="[10, 20, 50, 100]" layout="total, sizes, prev, pager, next, jumper"
+        style="margin-top: 16px; justify-content: flex-end;" @size-change="loadBindData"
+        @current-change="loadBindData" />
+
+      <template #footer>
+        <el-button @click="closeBindDialog">取 消</el-button>
+        <el-button type="primary" @click="submitBind">确 定</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
@@ -201,7 +250,10 @@ import {
   addPartition,
   updatePartition,
   deletePartition,
-  batchDeletePartition
+  batchDeletePartition,
+  getEquipmentPage,
+  getEquipmentById,
+  bindPartitionEquipment
 } from '@/api/drainage'
 
 // 地图标记创建(用Label+img代替Marker)
@@ -293,6 +345,8 @@ async function loadData() {
     tableData.value = pageData.records || []
     total.value = pageData.total || 0
     renderMainMapMarkers()
+    // 解析关联设备名称
+    resolveEquipmentNames()
   } catch (e) {
     console.error('加载排水分区数据失败', e)
   } finally {
@@ -300,6 +354,29 @@ async function loadData() {
   }
 }
 
+// 关联设备名称映射(分区ID -> 设备名称字符串)
+const equipmentNameMap = ref({})
+
+async function resolveEquipmentNames() {
+  const map = {}
+  for (const row of tableData.value) {
+    if (row.equipmentId) {
+      const ids = row.equipmentId.split(',')
+      const names = []
+      for (const id of ids) {
+        try {
+          const res = await getEquipmentById(id.trim())
+          const data = res.code !== undefined ? res.data : res
+          const info = data && data.equipmentInfo ? data.equipmentInfo : data
+          if (info && info.equipmentName) names.push(info.equipmentName)
+        } catch (e) { /* skip */ }
+      }
+      map[row.id] = names.join('、')
+    }
+  }
+  equipmentNameMap.value = map
+}
+
 onMounted(() => {
   loadData()
   initMainMap()
@@ -325,9 +402,26 @@ const dialogVisible = ref(false)
 const currentRow = ref({})
 let viewMapInstance = null
 
-function handleView(row) {
+const viewEquipmentNames = ref([])
+
+async function handleView(row) {
   currentRow.value = { ...row }
   dialogVisible.value = true
+  // 解析关联设备名称
+  viewEquipmentNames.value = []
+  if (row.equipmentId) {
+    const ids = row.equipmentId.split(',')
+    const names = []
+    for (const id of ids) {
+      try {
+        const res = await getEquipmentById(id.trim())
+        const data = res.code !== undefined ? res.data : res
+        const info = data && data.equipmentInfo ? data.equipmentInfo : data
+        if (info && info.equipmentName) names.push(info.equipmentName)
+      } catch (e) { /* skip */ }
+    }
+    viewEquipmentNames.value = names
+  }
 }
 
 function handleClose() {
@@ -568,6 +662,94 @@ function handleBatchDelete() {
     }
   }).catch(() => { })
 }
+// ==================== 关联设备 ====================
+const bindDialogVisible = ref(false)
+const currentPartitionId = ref(null)
+const bindTableRef = ref(null)
+const bindQueryParams = ref({ equipmentName: '', equipmentTypeId: 'drainage' })
+const bindTableData = ref([])
+const bindTotal = ref(0)
+const bindPageNum = ref(1)
+const bindPageSize = ref(10)
+const bindLoading = ref(false)
+const selectedEquipmentIds = ref([])
+
+function handleBind(row) {
+  currentPartitionId.value = row.id
+  bindDialogVisible.value = true
+  bindPageNum.value = 1
+  bindQueryParams.value.equipmentName = ''
+  // 回显已关联设备
+  if (row.equipmentId) {
+    selectedEquipmentIds.value = row.equipmentId.split(',')
+  } else {
+    selectedEquipmentIds.value = []
+  }
+  nextTick(() => {
+    loadBindData()
+  })
+}
+
+function handleBindQuery() {
+  bindPageNum.value = 1
+  loadBindData()
+}
+
+async function loadBindData() {
+  bindLoading.value = true
+  try {
+    const res = await getEquipmentPage(bindPageNum.value, bindPageSize.value, bindQueryParams.value)
+    const pageData = res.code !== undefined ? res.data : res
+    bindTableData.value = pageData.records || []
+    bindTotal.value = pageData.total || 0
+    // 回显已选设备
+    await nextTick()
+    if (selectedEquipmentIds.value.length > 0 && bindTableRef.value) {
+      bindTableData.value.forEach(row => {
+        if (selectedEquipmentIds.value.includes(row.equipmentId)) {
+          bindTableRef.value.toggleRowSelection(row, true)
+        }
+      })
+    }
+  } catch (e) {
+    console.error('加载设备列表失败', e)
+  } finally {
+    bindLoading.value = false
+  }
+}
+
+function handleBindSelectionChange(selection) {
+  selectedEquipmentIds.value = selection.map(item => item.equipmentId)
+}
+
+function closeBindDialog() {
+  bindDialogVisible.value = false
+  currentPartitionId.value = null
+  selectedEquipmentIds.value = []
+}
+
+async function submitBind() {
+  if (selectedEquipmentIds.value.length === 0) {
+    ElMessage.warning('请选择要关联的设备')
+    return
+  }
+  try {
+    const res = await bindPartitionEquipment({
+      partitionId: currentPartitionId.value,
+      equipmentIds: selectedEquipmentIds.value
+    })
+    if (res.code === 0 || res.code === 200) {
+      ElMessage.success('关联成功')
+      closeBindDialog()
+      loadData()
+    } else {
+      ElMessage.error(res.msg || '关联失败')
+    }
+  } catch (e) {
+    console.error('关联设备失败', e)
+    ElMessage.error('关联失败')
+  }
+}
 </script>
 
 <style scoped>

+ 178 - 4
src/views/subSystem/drainage/jcsj/psgwgl.vue

@@ -45,10 +45,16 @@
       <el-table-column label="管网类型" prop="networkType" min-width="100" />
       <el-table-column label="数据时间" prop="time" min-width="120" />
       <el-table-column label="标记数量" prop="pointCount" min-width="80" />
-      <el-table-column label="操作" width="180" align="center">
+      <el-table-column label="关联设备" min-width="160">
+        <template #default="{ row }">
+          {{ equipmentNameMap[row.id] || '' }}
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" width="240" align="center">
         <template #default="{ row }">
           <el-button link type="primary" @click="handleView(row)">查看</el-button>
           <el-button link type="primary" @click="handleEdit(row)">修改</el-button>
+          <el-button link type="primary" @click="handleBind(row)">关联设备</el-button>
           <el-button link type="danger" @click="handleDelete(row)">删除</el-button>
         </template>
       </el-table-column>
@@ -107,6 +113,18 @@
         </el-row>
       </div>
 
+      <!-- 关联设备 -->
+      <div v-if="viewEquipmentNames.length" class="detail-info" style="margin-top: 12px;">
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <div class="info-item">
+              <span class="info-label">关联设备:</span>
+              <span class="info-value">{{ viewEquipmentNames.join('、') }}</span>
+            </div>
+          </el-col>
+        </el-row>
+      </div>
+
       <!-- 地图区域 -->
       <div class="map-section">
         <div class="map-tabs">
@@ -176,18 +194,48 @@
         <el-button type="primary" @click="handleEditSubmit">确 定</el-button>
       </template>
     </el-dialog>
+
+    <!-- 关联设备弹框 -->
+    <el-dialog v-model="bindDialogVisible" title="关联设备" width="700px" @close="closeBindDialog">
+      <el-form :inline="true" :model="bindQueryParams" style="margin-bottom: 12px;">
+        <el-form-item label="设备名称">
+          <el-input v-model="bindQueryParams.equipmentName" placeholder="请输入设备名称" clearable />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleBindQuery">搜索</el-button>
+        </el-form-item>
+      </el-form>
+      <el-table v-loading="bindLoading" :data="bindTableData" border row-key="equipmentId"
+        @selection-change="handleBindSelectionChange" ref="bindTableRef">
+        <el-table-column type="selection" width="55" align="center" :reserve-selection="true" />
+        <el-table-column label="设备名称" prop="equipmentName" min-width="120" />
+        <el-table-column label="设备编号" prop="equipmentCode" min-width="120" />
+        <el-table-column label="设备类型" prop="equipmentTypeName" min-width="100" />
+        <el-table-column label="安装位置" prop="installLocation" min-width="120" />
+      </el-table>
+      <el-pagination v-model:current-page="bindPageNum" v-model:page-size="bindPageSize" :total="bindTotal"
+        :page-sizes="[10, 20, 50]" layout="total, sizes, prev, pager, next, jumper"
+        style="margin-top: 12px; justify-content: flex-end;" @size-change="loadBindData" @current-change="loadBindData" />
+      <template #footer>
+        <el-button @click="closeBindDialog">取 消</el-button>
+        <el-button type="primary" @click="submitBind">确 定</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup name="Psgwgl">
-import { ref, computed, onMounted } from 'vue'
+import { ref, computed, nextTick, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import locationIcon from '@/assets/images/location.png'
 import {
   getPipeNetworkPage,
   addDrainagePipeNetwork,
   updateDrainagePipeNetwork,
-  deleteDrainagePipeNetwork
+  deleteDrainagePipeNetwork,
+  getEquipmentPage,
+  getEquipmentById,
+  bindPipeNetworkEquipment
 } from '@/api/drainage'
 
 // 地图标记创建(用Label+img代替Marker)
@@ -322,6 +370,7 @@ async function loadData() {
     tableData.value = records
     total.value = pageData.total || 0
     renderMainMapMarkers()
+    resolveEquipmentNames()
   } catch (e) {
     console.error('加载排水管网数据失败', e)
   } finally {
@@ -329,6 +378,29 @@ async function loadData() {
   }
 }
 
+// 关联设备名称映射
+const equipmentNameMap = ref({})
+
+async function resolveEquipmentNames() {
+  const map = {}
+  for (const row of tableData.value) {
+    if (row.equipmentId) {
+      const ids = row.equipmentId.split(',')
+      const names = []
+      for (const id of ids) {
+        try {
+          const res = await getEquipmentById(id.trim())
+          const data = res.code !== undefined ? res.data : res
+          const info = data && data.equipmentInfo ? data.equipmentInfo : data
+          if (info && info.equipmentName) names.push(info.equipmentName)
+        } catch (e) { /* skip */ }
+      }
+      map[row.id] = names.join('、')
+    }
+  }
+  equipmentNameMap.value = map
+}
+
 onMounted(() => {
   loadData()
   setTimeout(() => { initMainMap() }, 500)
@@ -352,15 +424,30 @@ function handleQuery() {
 // ==================== 查看详情对话框 ====================
 const dialogVisible = ref(false)
 const currentRow = ref({})
+const viewEquipmentNames = ref([])
 let viewMapInstance = null
 
 const currentRowPointCount = computed(() => {
   return parsePoints(currentRow.value.points).length
 })
 
-function handleView(row) {
+async function handleView(row) {
   currentRow.value = { ...row }
   dialogVisible.value = true
+  viewEquipmentNames.value = []
+  if (row.equipmentId) {
+    const ids = row.equipmentId.split(',')
+    const names = []
+    for (const id of ids) {
+      try {
+        const res = await getEquipmentById(id.trim())
+        const data = res.code !== undefined ? res.data : res
+        const info = data && data.equipmentInfo ? data.equipmentInfo : data
+        if (info && info.equipmentName) names.push(info.equipmentName)
+      } catch (e) { /* skip */ }
+    }
+    viewEquipmentNames.value = names
+  }
 }
 
 function handleClose() {
@@ -685,6 +772,93 @@ function handleBatchDelete() {
     }
   }).catch(() => {})
 }
+
+// ==================== 关联设备 ====================
+const bindDialogVisible = ref(false)
+const bindLoading = ref(false)
+const bindTableData = ref([])
+const bindTotal = ref(0)
+const bindPageNum = ref(1)
+const bindPageSize = ref(10)
+const bindQueryParams = ref({ equipmentName: '', equipmentTypeId: 'drainage' })
+const selectedEquipmentIds = ref([])
+const currentPartitionId = ref('')
+const bindTableRef = ref(null)
+
+function handleBind(row) {
+  currentPartitionId.value = row.id
+  bindDialogVisible.value = true
+  bindPageNum.value = 1
+  bindQueryParams.value.equipmentName = ''
+  if (row.equipmentId) {
+    selectedEquipmentIds.value = row.equipmentId.split(',')
+  } else {
+    selectedEquipmentIds.value = []
+  }
+  nextTick(() => {
+    loadBindData()
+  })
+}
+
+function handleBindQuery() {
+  bindPageNum.value = 1
+  loadBindData()
+}
+
+async function loadBindData() {
+  bindLoading.value = true
+  try {
+    const res = await getEquipmentPage(bindPageNum.value, bindPageSize.value, bindQueryParams.value)
+    const pageData = res.code !== undefined ? res.data : res
+    bindTableData.value = pageData.records || []
+    bindTotal.value = pageData.total || 0
+    await nextTick()
+    if (selectedEquipmentIds.value.length > 0 && bindTableRef.value) {
+      bindTableData.value.forEach(row => {
+        if (selectedEquipmentIds.value.includes(row.equipmentId)) {
+          bindTableRef.value.toggleRowSelection(row, true)
+        }
+      })
+    }
+  } catch (e) {
+    console.error('加载设备列表失败', e)
+  } finally {
+    bindLoading.value = false
+  }
+}
+
+function handleBindSelectionChange(selection) {
+  selectedEquipmentIds.value = selection.map(item => item.equipmentId)
+}
+
+function closeBindDialog() {
+  bindDialogVisible.value = false
+  bindTableData.value = []
+  selectedEquipmentIds.value = []
+}
+
+async function submitBind() {
+  if (selectedEquipmentIds.value.length === 0) {
+    ElMessage.warning('请选择要关联的设备')
+    return
+  }
+  try {
+    const res = await bindPipeNetworkEquipment({
+      partitionId: currentPartitionId.value,
+      equipmentIds: selectedEquipmentIds.value
+    })
+    if (res.code === 0 || res.code === 200) {
+      ElMessage.success('关联成功')
+      closeBindDialog()
+      loadData()
+    } else {
+      ElMessage.error(res.msg || '关联失败')
+    }
+  } catch (e) {
+    console.error('关联设备失败', e)
+    ElMessage.error('关联失败')
+  }
+}
 </script>
 
 <style scoped>

+ 178 - 4
src/views/subSystem/drainage/jcsj/yjsdgl.vue

@@ -42,10 +42,16 @@
           </el-tag>
         </template>
       </el-table-column>
-      <el-table-column label="操作" width="180" align="center">
+      <el-table-column label="关联设备" min-width="160">
+        <template #default="{ row }">
+          {{ equipmentNameMap[row.id] || '' }}
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" width="240" align="center">
         <template #default="{ row }">
           <el-button link type="primary" @click="handleView(row)">查看</el-button>
           <el-button link type="primary" @click="handleEdit(row)">修改</el-button>
+          <el-button link type="primary" @click="handleBind(row)">关联设备</el-button>
           <el-button link type="danger" @click="handleDelete(row)">删除</el-button>
         </template>
       </el-table-column>
@@ -114,6 +120,18 @@
         </el-row>
       </div>
 
+      <!-- 关联设备 -->
+      <div v-if="viewEquipmentNames.length" class="detail-info" style="margin-top: 12px;">
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <div class="info-item">
+              <span class="info-label">关联设备:</span>
+              <span class="info-value">{{ viewEquipmentNames.join('、') }}</span>
+            </div>
+          </el-col>
+        </el-row>
+      </div>
+
       <!-- 地图区域 -->
       <div class="map-section">
         <div id="yjsdglViewMap" class="map-container"></div>
@@ -185,18 +203,48 @@
         <el-button type="primary" @click="handleEditSubmit">确 定</el-button>
       </template>
     </el-dialog>
+
+    <!-- 关联设备弹框 -->
+    <el-dialog v-model="bindDialogVisible" title="关联设备" width="700px" @close="closeBindDialog">
+      <el-form :inline="true" :model="bindQueryParams" style="margin-bottom: 12px;">
+        <el-form-item label="设备名称">
+          <el-input v-model="bindQueryParams.equipmentName" placeholder="请输入设备名称" clearable />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleBindQuery">搜索</el-button>
+        </el-form-item>
+      </el-form>
+      <el-table v-loading="bindLoading" :data="bindTableData" border row-key="equipmentId"
+        @selection-change="handleBindSelectionChange" ref="bindTableRef">
+        <el-table-column type="selection" width="55" align="center" :reserve-selection="true" />
+        <el-table-column label="设备名称" prop="equipmentName" min-width="120" />
+        <el-table-column label="设备编号" prop="equipmentCode" min-width="120" />
+        <el-table-column label="设备类型" prop="equipmentTypeName" min-width="100" />
+        <el-table-column label="安装位置" prop="installLocation" min-width="120" />
+      </el-table>
+      <el-pagination v-model:current-page="bindPageNum" v-model:page-size="bindPageSize" :total="bindTotal"
+        :page-sizes="[10, 20, 50]" layout="total, sizes, prev, pager, next, jumper"
+        style="margin-top: 12px; justify-content: flex-end;" @size-change="loadBindData" @current-change="loadBindData" />
+      <template #footer>
+        <el-button @click="closeBindDialog">取 消</el-button>
+        <el-button type="primary" @click="submitBind">确 定</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup name="Yjsdgl">
-import { ref, computed, onMounted } from 'vue'
+import { ref, computed, nextTick, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import locationIcon from '@/assets/images/location.png'
 import {
   getWaterloggingPage,
   addWaterlogging,
   updateWaterlogging,
-  deleteWaterlogging
+  deleteWaterlogging,
+  getEquipmentPage,
+  getEquipmentById,
+  bindWaterloggingEquipment
 } from '@/api/drainage'
 
 // 地图标记创建(用Label+img代替Marker)
@@ -275,6 +323,7 @@ async function loadData() {
     tableData.value = pageData.records || []
     total.value = pageData.total || 0
     renderMainMapMarkers()
+    resolveEquipmentNames()
   } catch (e) {
     console.error('加载易积水点数据失败', e)
   } finally {
@@ -282,6 +331,29 @@ async function loadData() {
   }
 }
 
+// 关联设备名称映射
+const equipmentNameMap = ref({})
+
+async function resolveEquipmentNames() {
+  const map = {}
+  for (const row of tableData.value) {
+    if (row.equipmentId) {
+      const ids = row.equipmentId.split(',')
+      const names = []
+      for (const id of ids) {
+        try {
+          const res = await getEquipmentById(id.trim())
+          const data = res.code !== undefined ? res.data : res
+          const info = data && data.equipmentInfo ? data.equipmentInfo : data
+          if (info && info.equipmentName) names.push(info.equipmentName)
+        } catch (e) { /* skip */ }
+      }
+      map[row.id] = names.join('、')
+    }
+  }
+  equipmentNameMap.value = map
+}
+
 onMounted(() => {
   loadData()
   setTimeout(() => { initMainMap() }, 500)
@@ -343,15 +415,30 @@ function handleQuery() {
 // ==================== 查看详情对话框 ====================
 const dialogVisible = ref(false)
 const currentRow = ref({})
+const viewEquipmentNames = ref([])
 let viewMapInstance = null
 
-function handleView(row) {
+async function handleView(row) {
   currentRow.value = {
     ...row,
     // 将 JSON 字符串解析为数组,供模板和地图渲染使用
     waterloggingPoints: parseWaterloggingPoints(row.waterloggingPoints)
   }
   dialogVisible.value = true
+  viewEquipmentNames.value = []
+  if (row.equipmentId) {
+    const ids = row.equipmentId.split(',')
+    const names = []
+    for (const id of ids) {
+      try {
+        const res = await getEquipmentById(id.trim())
+        const data = res.code !== undefined ? res.data : res
+        const info = data && data.equipmentInfo ? data.equipmentInfo : data
+        if (info && info.equipmentName) names.push(info.equipmentName)
+      } catch (e) { /* skip */ }
+    }
+    viewEquipmentNames.value = names
+  }
 }
 
 function handleClose() {
@@ -629,6 +716,93 @@ function handleBatchDelete() {
     }
   }).catch(() => {})
 }
+
+// ==================== 关联设备 ====================
+const bindDialogVisible = ref(false)
+const bindLoading = ref(false)
+const bindTableData = ref([])
+const bindTotal = ref(0)
+const bindPageNum = ref(1)
+const bindPageSize = ref(10)
+const bindQueryParams = ref({ equipmentName: '', equipmentTypeId: 'drainage' })
+const selectedEquipmentIds = ref([])
+const currentPartitionId = ref('')
+const bindTableRef = ref(null)
+
+function handleBind(row) {
+  currentPartitionId.value = row.id
+  bindDialogVisible.value = true
+  bindPageNum.value = 1
+  bindQueryParams.value.equipmentName = ''
+  if (row.equipmentId) {
+    selectedEquipmentIds.value = row.equipmentId.split(',')
+  } else {
+    selectedEquipmentIds.value = []
+  }
+  nextTick(() => {
+    loadBindData()
+  })
+}
+
+function handleBindQuery() {
+  bindPageNum.value = 1
+  loadBindData()
+}
+
+async function loadBindData() {
+  bindLoading.value = true
+  try {
+    const res = await getEquipmentPage(bindPageNum.value, bindPageSize.value, bindQueryParams.value)
+    const pageData = res.code !== undefined ? res.data : res
+    bindTableData.value = pageData.records || []
+    bindTotal.value = pageData.total || 0
+    await nextTick()
+    if (selectedEquipmentIds.value.length > 0 && bindTableRef.value) {
+      bindTableData.value.forEach(row => {
+        if (selectedEquipmentIds.value.includes(row.equipmentId)) {
+          bindTableRef.value.toggleRowSelection(row, true)
+        }
+      })
+    }
+  } catch (e) {
+    console.error('加载设备列表失败', e)
+  } finally {
+    bindLoading.value = false
+  }
+}
+
+function handleBindSelectionChange(selection) {
+  selectedEquipmentIds.value = selection.map(item => item.equipmentId)
+}
+
+function closeBindDialog() {
+  bindDialogVisible.value = false
+  bindTableData.value = []
+  selectedEquipmentIds.value = []
+}
+
+async function submitBind() {
+  if (selectedEquipmentIds.value.length === 0) {
+    ElMessage.warning('请选择要关联的设备')
+    return
+  }
+  try {
+    const res = await bindWaterloggingEquipment({
+      partitionId: currentPartitionId.value,
+      equipmentIds: selectedEquipmentIds.value
+    })
+    if (res.code === 0 || res.code === 200) {
+      ElMessage.success('关联成功')
+      closeBindDialog()
+      loadData()
+    } else {
+      ElMessage.error(res.msg || '关联失败')
+    }
+  } catch (e) {
+    console.error('关联设备失败', e)
+    ElMessage.error('关联失败')
+  }
+}
 </script>
 
 <style scoped>