Jelajahi Sumber

排水系统前端静态页面提交

LAPTOP-JI2IUVG1\26646 1 bulan lalu
induk
melakukan
219bc33fb5

+ 364 - 1
src/views/subSystem/drainage/fxpg/zhfxtjfx.vue

@@ -1,13 +1,376 @@
 <template>
   <div class="app-container">
-    <h2>综合风险统计分析</h2>
+    <!-- 搜索区域 -->
+    <el-form :model="queryParams" ref="queryRef" :inline="true" class="search-form">
+      <el-form-item label="管线编号">
+        <el-input v-model="queryParams.pipeNo" placeholder="请输入" clearable style="width: 160px" />
+      </el-form-item>
+      <el-form-item label="管线类别">
+        <el-select v-model="queryParams.pipeType" placeholder="请选择" clearable style="width: 160px">
+          <el-option v-for="item in pipeTypeOptions" :key="item" :label="item" :value="item" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="所属区域">
+        <el-select v-model="queryParams.area" placeholder="请选择" clearable style="width: 160px">
+          <el-option v-for="item in areaOptions" :key="item" :label="item" :value="item" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="所在道路">
+        <el-input v-model="queryParams.road" placeholder="请输入" clearable style="width: 160px" />
+      </el-form-item>
+      <el-form-item label="风险级别">
+        <el-select v-model="queryParams.riskLevel" placeholder="请选择" clearable style="width: 160px">
+          <el-option v-for="item in riskLevelOptions" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" @click="handleQuery">搜索</el-button>
+        <el-button @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <!-- 统计概览卡片 -->
+    <div class="stat-cards">
+      <el-card class="stat-card stat-blue" shadow="hover">
+        <div class="stat-card-inner">
+          <div class="stat-icon"><el-icon :size="32"><DataLine /></el-icon></div>
+          <div class="stat-info">
+            <div class="stat-value">{{ stats.total }}</div>
+            <div class="stat-label">总管线数</div>
+          </div>
+        </div>
+      </el-card>
+      <el-card class="stat-card stat-red" shadow="hover">
+        <div class="stat-card-inner">
+          <div class="stat-icon"><el-icon :size="32"><Warning /></el-icon></div>
+          <div class="stat-info">
+            <div class="stat-value">{{ stats.high }}</div>
+            <div class="stat-label">高风险管线</div>
+          </div>
+        </div>
+      </el-card>
+      <el-card class="stat-card stat-orange" shadow="hover">
+        <div class="stat-card-inner">
+          <div class="stat-icon"><el-icon :size="32"><WarningFilled /></el-icon></div>
+          <div class="stat-info">
+            <div class="stat-value">{{ stats.medium }}</div>
+            <div class="stat-label">中风险管线</div>
+          </div>
+        </div>
+      </el-card>
+      <el-card class="stat-card stat-green" shadow="hover">
+        <div class="stat-card-inner">
+          <div class="stat-icon"><el-icon :size="32"><CircleCheck /></el-icon></div>
+          <div class="stat-info">
+            <div class="stat-value">{{ stats.low }}</div>
+            <div class="stat-label">低风险管线</div>
+          </div>
+        </div>
+      </el-card>
+    </div>
+
+    <!-- 数据表格 -->
+    <el-table :data="pageList" border style="width: 100%">
+      <el-table-column label="序号" type="index" width="60" align="center" />
+      <el-table-column label="管线编号" prop="pipeNo" min-width="130" />
+      <el-table-column label="管线名称" prop="pipeName" min-width="150" />
+      <el-table-column label="管线类别" prop="pipeType" min-width="100" align="center">
+        <template #default="{ row }">
+          <el-tag :type="pipeTypeTagType(row.pipeType)" effect="light">{{ row.pipeType }}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="所属区域" prop="area" min-width="110" />
+      <el-table-column label="所在道路" prop="road" min-width="110" />
+      <el-table-column label="管线长度(m)" prop="length" min-width="110" align="center" />
+      <el-table-column label="管径(mm)" prop="diameter" min-width="100" align="center" />
+      <el-table-column label="埋深(m)" prop="depth" min-width="90" align="center" />
+      <el-table-column label="风险评分" prop="riskScore" min-width="90" align="center">
+        <template #default="{ row }">
+          <span :class="'score-' + row.riskLevel">{{ row.riskScore }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="风险级别" prop="riskLevel" min-width="100" align="center">
+        <template #default="{ row }">
+          <el-tag :color="riskLevelColor(row.riskLevel)" effect="dark" style="border:none">{{ riskLevelText(row.riskLevel) }}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="评估时间" prop="assessTime" min-width="120" align="center" />
+      <el-table-column label="操作" width="100" align="center" fixed="right">
+        <template #default="{ row }">
+          <el-button link type="primary" @click="handleDetail(row)">查看详情</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 分页 -->
+    <div class="pagination-wrap">
+      <el-pagination
+        v-model:current-page="page.current"
+        v-model:page-size="page.size"
+        :page-sizes="[10, 20, 50]"
+        :total="filteredList.length"
+        layout="total, sizes, prev, pager, next, jumper"
+        background
+      />
+    </div>
+
+    <!-- 详情弹窗 -->
+    <el-dialog v-model="detailVisible" title="管网综合风险评估详情" width="800px" destroy-on-close>
+      <template v-if="currentRow">
+        <el-tabs v-model="detailTab">
+          <el-tab-pane label="基本信息" name="basic">
+            <el-descriptions :column="2" border>
+              <el-descriptions-item label="管线编号">{{ currentRow.pipeNo }}</el-descriptions-item>
+              <el-descriptions-item label="管线名称">{{ currentRow.pipeName }}</el-descriptions-item>
+              <el-descriptions-item label="管线类别">{{ currentRow.pipeType }}</el-descriptions-item>
+              <el-descriptions-item label="所属区域">{{ currentRow.area }}</el-descriptions-item>
+              <el-descriptions-item label="所在道路">{{ currentRow.road }}</el-descriptions-item>
+              <el-descriptions-item label="建设年份">2018年</el-descriptions-item>
+              <el-descriptions-item label="管理单位">沅陵县市政管理处</el-descriptions-item>
+              <el-descriptions-item label="负责人">张工</el-descriptions-item>
+            </el-descriptions>
+          </el-tab-pane>
+          <el-tab-pane label="管线信息" name="pipe">
+            <el-descriptions :column="2" border>
+              <el-descriptions-item label="管径(mm)">{{ currentRow.diameter }}</el-descriptions-item>
+              <el-descriptions-item label="管线长度(m)">{{ currentRow.length }}</el-descriptions-item>
+              <el-descriptions-item label="埋深(m)">{{ currentRow.depth }}</el-descriptions-item>
+              <el-descriptions-item label="管材类型">钢筋混凝土</el-descriptions-item>
+              <el-descriptions-item label="坡度">0.3%</el-descriptions-item>
+              <el-descriptions-item label="流向">由北向南</el-descriptions-item>
+              <el-descriptions-item label="接口形式">承插式</el-descriptions-item>
+              <el-descriptions-item label="周边环境">城市主干道下方</el-descriptions-item>
+            </el-descriptions>
+          </el-tab-pane>
+          <el-tab-pane label="评估详情" name="assess">
+            <div style="margin-bottom:16px">
+              <el-tag :type="riskLevelColor(currentRow.riskLevel)" size="large">{{ riskLevelText(currentRow.riskLevel) }}</el-tag>
+              <span style="margin-left:12px;font-size:18px;font-weight:bold;">综合评分:{{ currentRow.riskScore }}</span>
+            </div>
+            <el-table :data="assessItems" border size="small">
+              <el-table-column prop="name" label="评估指标" />
+              <el-table-column prop="weight" label="权重" width="80" align="center" />
+              <el-table-column prop="score" label="评分" width="80" align="center" />
+              <el-table-column prop="level" label="等级" width="100" align="center" />
+            </el-table>
+          </el-tab-pane>
+          <el-tab-pane label="评估分析" name="analysis">
+            <el-alert type="info" :closable="false" style="margin-bottom:12px">
+              <template #title>
+                <strong>风险评估建议:</strong>
+                <span v-if="currentRow.riskLevel==='high'">该管线风险等级较高,建议3个月内安排专项检修,加强监测频率。</span>
+                <span v-else-if="currentRow.riskLevel==='medium'">该管线存在中等风险,建议6个月内纳入巡检计划。</span>
+                <span v-else>该管线风险较低,按常规周期巡检即可。</span>
+              </template>
+            </el-alert>
+            <p><strong>评估时间:</strong>{{ currentRow.assessTime }}</p>
+            <p><strong>评估人员:</strong>李工、王工</p>
+            <p><strong>下次评估建议时间:</strong>2026-05-01</p>
+          </el-tab-pane>
+        </el-tabs>
+      </template>
+      <template #footer>
+        <el-button @click="detailVisible = false">关 闭</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup name="Zhfxtjfx">
+import { ref, computed } from 'vue'
+import { DataLine, Warning, WarningFilled, CircleCheck } from '@element-plus/icons-vue'
+
+// ==================== 下拉选项 ====================
+const pipeTypeOptions = ['雨水管', '污水管', '合流管', '燃气管']
+const areaOptions = ['沅陵大道', '城北路段', '河西片区', '人民路', '南环路', '开发区']
+const riskLevelOptions = [
+  { label: '高风险', value: 'high' },
+  { label: '中风险', value: 'medium' },
+  { label: '低风险', value: 'low' },
+  { label: '极低风险', value: 'veryLow' }
+]
+
+// ==================== 搜索参数 ====================
+const queryParams = ref({
+  pipeNo: '',
+  pipeType: '',
+  area: '',
+  road: '',
+  riskLevel: ''
+})
+const queryRef = ref(null)
+
+const handleQuery = () => {
+  page.value.current = 1
+}
+const resetQuery = () => {
+  queryParams.value = { pipeNo: '', pipeType: '', area: '', road: '', riskLevel: '' }
+  page.value.current = 1
+}
+
+// ==================== 模拟数据 ====================
+const tableData = ref([
+  { id: 1, pipeNo: 'GX-2024-001', pipeName: '沅陵大道雨水主干线', pipeType: '雨水管', area: '沅陵大道', road: '沅陵大道', length: 1250, diameter: 800, depth: 2.5, riskScore: 87, riskLevel: 'high', assessTime: '2025-11-15' },
+  { id: 2, pipeNo: 'GX-2024-002', pipeName: '城北路段污水管线', pipeType: '污水管', area: '城北路段', road: '城北路', length: 980, diameter: 600, depth: 3.1, riskScore: 72, riskLevel: 'medium', assessTime: '2025-11-10' },
+  { id: 3, pipeNo: 'GX-2024-003', pipeName: '河西片区合流管线', pipeType: '合流管', area: '河西片区', road: '河西大道', length: 1560, diameter: 1000, depth: 2.8, riskScore: 45, riskLevel: 'low', assessTime: '2025-11-08' },
+  { id: 4, pipeNo: 'GX-2024-004', pipeName: '人民路雨水管线', pipeType: '雨水管', area: '人民路', road: '人民路', length: 870, diameter: 500, depth: 2.2, riskScore: 91, riskLevel: 'high', assessTime: '2025-11-05' },
+  { id: 5, pipeNo: 'GX-2024-005', pipeName: '南环路污水支线', pipeType: '污水管', area: '南环路', road: '南环路', length: 640, diameter: 400, depth: 1.9, riskScore: 38, riskLevel: 'veryLow', assessTime: '2025-11-02' },
+  { id: 6, pipeNo: 'GX-2024-006', pipeName: '开发区雨水管线A', pipeType: '雨水管', area: '开发区', road: '开发大道', length: 2100, diameter: 1200, depth: 3.5, riskScore: 76, riskLevel: 'medium', assessTime: '2025-10-28' },
+  { id: 7, pipeNo: 'GX-2024-007', pipeName: '沅陵大道污水支线', pipeType: '污水管', area: '沅陵大道', road: '沅陵大道', length: 560, diameter: 300, depth: 2.0, riskScore: 52, riskLevel: 'low', assessTime: '2025-10-25' },
+  { id: 8, pipeNo: 'GX-2024-008', pipeName: '城北路段雨水干线', pipeType: '雨水管', area: '城北路段', road: '城北路', length: 1320, diameter: 800, depth: 2.7, riskScore: 83, riskLevel: 'high', assessTime: '2025-10-22' },
+  { id: 9, pipeNo: 'GX-2024-009', pipeName: '河西片区污水管线', pipeType: '污水管', area: '河西片区', road: '河西大道', length: 780, diameter: 500, depth: 2.4, riskScore: 62, riskLevel: 'medium', assessTime: '2025-10-18' },
+  { id: 10, pipeNo: 'GX-2024-010', pipeName: '人民路合流管线', pipeType: '合流管', area: '人民路', road: '人民路', length: 1050, diameter: 700, depth: 3.0, riskScore: 29, riskLevel: 'veryLow', assessTime: '2025-10-15' },
+  { id: 11, pipeNo: 'GX-2024-011', pipeName: '南环路雨水管线', pipeType: '雨水管', area: '南环路', road: '南环路', length: 930, diameter: 600, depth: 2.3, riskScore: 68, riskLevel: 'medium', assessTime: '2025-10-12' },
+  { id: 12, pipeNo: 'GX-2024-012', pipeName: '开发区燃气管线', pipeType: '燃气管', area: '开发区', road: '开发大道', length: 1800, diameter: 200, depth: 1.5, riskScore: 94, riskLevel: 'high', assessTime: '2025-10-08' },
+  { id: 13, pipeNo: 'GX-2024-013', pipeName: '沅陵大道合流支线', pipeType: '合流管', area: '沅陵大道', road: '沅陵大道', length: 420, diameter: 400, depth: 1.8, riskScore: 41, riskLevel: 'low', assessTime: '2025-10-05' },
+  { id: 14, pipeNo: 'GX-2024-014', pipeName: '城北路段燃气管线', pipeType: '燃气管', area: '城北路段', road: '城北路', length: 690, diameter: 150, depth: 1.4, riskScore: 33, riskLevel: 'veryLow', assessTime: '2025-10-01' },
+  { id: 15, pipeNo: 'GX-2024-015', pipeName: '河西片区雨水干线', pipeType: '雨水管', area: '河西片区', road: '河西大道', length: 1150, diameter: 900, depth: 3.2, riskScore: 79, riskLevel: 'medium', assessTime: '2025-09-28' }
+])
+
+// ==================== 过滤与分页 ====================
+const filteredList = computed(() => {
+  return tableData.value.filter(item => {
+    if (queryParams.value.pipeNo && !item.pipeNo.includes(queryParams.value.pipeNo)) return false
+    if (queryParams.value.pipeType && item.pipeType !== queryParams.value.pipeType) return false
+    if (queryParams.value.area && item.area !== queryParams.value.area) return false
+    if (queryParams.value.road && !item.road.includes(queryParams.value.road)) return false
+    if (queryParams.value.riskLevel && item.riskLevel !== queryParams.value.riskLevel) return false
+    return true
+  })
+})
+
+const page = ref({ current: 1, size: 10 })
+const pageList = computed(() => {
+  const start = (page.value.current - 1) * page.value.size
+  return filteredList.value.slice(start, start + page.value.size)
+})
+
+// ==================== 统计 ====================
+const stats = computed(() => {
+  const list = filteredList.value
+  return {
+    total: list.length,
+    high: list.filter(i => i.riskLevel === 'high').length,
+    medium: list.filter(i => i.riskLevel === 'medium').length,
+    low: list.filter(i => i.riskLevel === 'low' || i.riskLevel === 'veryLow').length
+  }
+})
+
+// ==================== 工具函数 ====================
+const pipeTypeTagType = (type) => {
+  const map = { '雨水管': '', '污水管': 'warning', '合流管': 'success', '燃气管': 'danger' }
+  return map[type] || ''
+}
+
+const riskLevelColor = (level) => {
+  const map = { high: '#F56C6C', medium: '#E6A23C', low: '#409EFF', veryLow: '#67C23A' }
+  return map[level] || '#909399'
+}
+
+const riskLevelText = (level) => {
+  const map = { high: '高风险', medium: '中风险', low: '低风险', veryLow: '极低风险' }
+  return map[level] || '未知'
+}
 
+// ==================== 详情弹窗 ====================
+const detailVisible = ref(false)
+const currentRow = ref(null)
+const detailTab = ref('basic')
+
+const assessItems = [
+  { name: '结构性缺陷', weight: '0.25', score: '82', level: '中等' },
+  { name: '功能性缺陷', weight: '0.20', score: '75', level: '中等' },
+  { name: '环境影响', weight: '0.20', score: '68', level: '较高' },
+  { name: '运行状态', weight: '0.20', score: '79', level: '中等' },
+  { name: '历史事故', weight: '0.15', score: '90', level: '良好' }
+]
+
+const handleDetail = (row) => {
+  currentRow.value = row
+  detailTab.value = 'basic'
+  detailVisible.value = true
+}
 </script>
 
 <style scoped>
+.app-container {
+  padding: 20px;
+}
+
+/* 搜索表单 */
+.search-form {
+  margin-bottom: 16px;
+}
+
+/* 统计卡片 */
+.stat-cards {
+  display: flex;
+  gap: 16px;
+  margin-bottom: 20px;
+}
+
+.stat-card {
+  flex: 1;
+  min-width: 0;
+}
+
+.stat-card :deep(.el-card__body) {
+  padding: 16px 20px;
+}
+
+.stat-card-inner {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+}
+
+.stat-icon {
+  width: 56px;
+  height: 56px;
+  border-radius: 12px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+}
+
+.stat-blue .stat-icon { background: rgba(64, 158, 255, 0.1); color: #409EFF; }
+.stat-red .stat-icon { background: rgba(245, 108, 108, 0.1); color: #F56C6C; }
+.stat-orange .stat-icon { background: rgba(230, 162, 60, 0.1); color: #E6A23C; }
+.stat-green .stat-icon { background: rgba(103, 194, 58, 0.1); color: #67C23A; }
+
+.stat-info {
+  flex: 1;
+  min-width: 0;
+}
+
+.stat-value {
+  font-size: 28px;
+  font-weight: 700;
+  line-height: 1.2;
+}
+
+.stat-blue .stat-value { color: #409EFF; }
+.stat-red .stat-value { color: #F56C6C; }
+.stat-orange .stat-value { color: #E6A23C; }
+.stat-green .stat-value { color: #67C23A; }
+
+.stat-label {
+  font-size: 14px;
+  color: #909399;
+  margin-top: 4px;
+}
+
+/* 风险评分颜色 */
+.score-high { color: #F56C6C; font-weight: 700; }
+.score-medium { color: #E6A23C; font-weight: 700; }
+.score-low { color: #409EFF; font-weight: 700; }
+.score-veryLow { color: #67C23A; font-weight: 700; }
 
+/* 分页 */
+.pagination-wrap {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 16px;
+}
 </style>

+ 600 - 1
src/views/subSystem/drainage/fxpg/zhfxxqfx.vue

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

+ 400 - 1
src/views/subSystem/drainage/jcbj/bjqd.vue

@@ -1,13 +1,412 @@
 <template>
   <div class="app-container">
-    <h2>报警清单</h2>
+    <!-- 搜索区 -->
+    <el-form :model="queryParams" ref="queryRef" :inline="true" class="search-form">
+      <el-form-item label="设备ID">
+        <el-input v-model="queryParams.deviceId" placeholder="请输入" clearable style="width: 180px" />
+      </el-form-item>
+      <el-form-item label="所属区域">
+        <el-select v-model="queryParams.area" placeholder="请选择" clearable style="width: 160px">
+          <el-option v-for="a in areaOptions" :key="a" :label="a" :value="a" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="设备状态">
+        <el-select v-model="queryParams.status" placeholder="请选择" clearable style="width: 140px">
+          <el-option label="未处理" value="未处理" />
+          <el-option label="处理中" value="处理中" />
+          <el-option label="已处理" value="已处理" />
+          <el-option label="已关闭" value="已关闭" />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" @click="handleQuery">搜索</el-button>
+        <el-button @click="handleClear">清除</el-button>
+      </el-form-item>
+    </el-form>
+
+    <!-- 数据表格 -->
+    <el-table :data="filteredList" border style="width: 100%" size="default">
+      <el-table-column label="档案编号" prop="archiveNo" min-width="130" />
+      <el-table-column label="所属区域" prop="area" min-width="100" />
+      <el-table-column label="报警时间" prop="alarmTime" min-width="160" />
+      <el-table-column label="所属类型" prop="type" min-width="90" align="center">
+        <template #default="{ row }">
+          <span :class="typeClass(row.type)">{{ row.type }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="报警等级" prop="level" min-width="90" align="center">
+        <template #default="{ row }">
+          <span :class="levelClass(row.level)">{{ row.level }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="地址" prop="address" min-width="180" show-overflow-tooltip />
+      <el-table-column label="处理状态" prop="status" min-width="90" align="center">
+        <template #default="{ row }">
+          <el-tag :type="statusTagType(row.status)" size="small" effect="plain">{{ row.status }}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" width="80" align="center">
+        <template #default="{ row }">
+          <el-button link type="primary" @click="handleView(row)">查看</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 查看详情弹窗 -->
+    <el-dialog v-model="dialogVisible" title="异常管理" width="680px" :before-close="handleClose">
+      <div class="detail-panel">
+        <div class="detail-top">
+          <div class="detail-image">
+            <el-icon :size="48" color="#b0c4de"><Monitor /></el-icon>
+          </div>
+          <div class="detail-info">
+            <div class="detail-number">编号: {{ currentRow.archiveNo }}</div>
+            <el-row :gutter="16">
+              <el-col :span="12">
+                <div class="info-item">
+                  <span class="info-label">名称</span>
+                  <span class="info-value">{{ currentRow.deviceName }}</span>
+                </div>
+              </el-col>
+              <el-col :span="12">
+                <div class="info-item">
+                  <span class="info-label">ID</span>
+                  <span class="info-value">{{ currentRow.deviceId }}</span>
+                </div>
+              </el-col>
+            </el-row>
+            <el-row :gutter="16">
+              <el-col :span="12">
+                <div class="info-item">
+                  <span class="info-label">型号</span>
+                  <span class="info-value">{{ currentRow.model }}</span>
+                </div>
+              </el-col>
+              <el-col :span="12">
+                <div class="info-item">
+                  <span class="info-label">异常日期</span>
+                  <span class="info-value">{{ currentRow.alarmTime }}</span>
+                </div>
+              </el-col>
+            </el-row>
+            <el-row :gutter="16">
+              <el-col :span="12">
+                <div class="info-item">
+                  <span class="info-label">所属单位</span>
+                  <span class="info-value">{{ currentRow.unit }}</span>
+                </div>
+              </el-col>
+              <el-col :span="12">
+                <div class="info-item">
+                  <span class="info-label">详细地址</span>
+                  <span class="info-value">{{ currentRow.address }}</span>
+                </div>
+              </el-col>
+            </el-row>
+            <div class="info-item">
+              <span class="info-label">异常类型</span>
+              <span class="info-value">
+                <span :class="typeClass(currentRow.type)">{{ currentRow.type }}</span>
+                <span style="margin-left: 8px;">{{ currentRow.level }}</span>
+              </span>
+            </div>
+            <div class="info-item desc-item">
+              <span class="info-label">描述</span>
+              <span class="info-value desc-text">{{ currentRow.description }}</span>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 底部操作区 -->
+      <div class="detail-footer">
+        <div class="dispatch-row">
+          <span class="dispatch-label">审核下发</span>
+          <el-select v-model="dispatchPerson" placeholder="请选择人员" style="flex: 1; margin-left: 12px;">
+            <el-option v-for="p in personOptions" :key="p" :label="p" :value="p" />
+          </el-select>
+        </div>
+        <el-button type="primary" style="width: 100%; margin-top: 14px;" @click="handleDispatch">下派</el-button>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
 <script setup name="Bjqd">
+import { ref, computed } from 'vue'
+import { ElMessage } from 'element-plus'
+import { Monitor } from '@element-plus/icons-vue'
+
+// ==================== 搜索参数 ====================
+const queryParams = ref({
+  deviceId: '',
+  area: '',
+  status: ''
+})
+
+const areaOptions = ['沅陵北路', '沅陵南路', '沅陵东路', '沅陵西路', '沅陵中路']
+
+function handleQuery() {
+  // computed 自动过滤
+}
+
+function handleClear() {
+  queryParams.value = { deviceId: '', area: '', status: '' }
+}
+
+// ==================== 表格数据 ====================
+const tableData = ref([
+  {
+    id: 1, archiveNo: 'BJ20250101001', area: '沅陵北路', alarmTime: '2025-12-31 08:15:30',
+    type: '管线', level: '严重', address: '**市沅陵北路1号排水口',
+    status: '未处理', deviceId: 'YL-001', deviceName: '液位传感器1号', model: 'YW-200A',
+    unit: 'XX排水公司', description: '沅陵北路1号排水口液位传感器检测到液位严重超限,当前液位3.5m,超过阈值3.0m,需立即安排人员前往现场排查管网是否存在堵塞或溢流风险。'
+  },
+  {
+    id: 2, archiveNo: 'BJ20250101002', area: '沅陵南路', alarmTime: '2025-12-31 09:22:15',
+    type: '管点', level: '重要', address: '**市沅陵南路2号窨井',
+    status: '处理中', deviceId: 'LL-012', deviceName: '流量计12号', model: 'LL-300B',
+    unit: 'XX排水公司', description: '沅陵南路2号窨井流量计检测到流量异常升高,当前流量12.8m³/h,超过阈值10.0m³/h,疑似管网渗漏或雨水倒灌,需核实原因。'
+  },
+  {
+    id: 3, archiveNo: 'BJ20250101003', area: '沅陵东路', alarmTime: '2025-12-30 14:05:42',
+    type: '窨井', level: '一般', address: '**市沅陵东路3号管点',
+    status: '已处理', deviceId: 'YL-005', deviceName: '雨量计5号', model: 'YJ-100C',
+    unit: 'XX排水公司', description: '沅陵东路3号管点雨量计检测到降雨量超标,当前58mm/h,超过阈值50mm/h,需关注该区域排水系统运行状态。'
+  },
+  {
+    id: 4, archiveNo: 'BJ20250101004', area: '沅陵西路', alarmTime: '2025-12-30 16:33:08',
+    type: '管线', level: '严重', address: '**市沅陵西路4号泵站',
+    status: '未处理', deviceId: 'YL-008', deviceName: '液位传感器8号', model: 'YW-200A',
+    unit: 'XX排水公司', description: '沅陵西路4号泵站液位传感器检测到液位严重超限,当前液位4.2m,超过阈值3.0m,存在溢流风险,需紧急处理。'
+  },
+  {
+    id: 5, archiveNo: 'BJ20250101005', area: '沅陵北路', alarmTime: '2025-12-29 10:18:55',
+    type: '管点', level: '重要', address: '**市沅陵北路5号排水口',
+    status: '已关闭', deviceId: 'LL-003', deviceName: '流量计3号', model: 'LL-300B',
+    unit: 'XX排水公司', description: '沅陵北路5号排水口流量计检测到流量异常,当前流量8.5m³/h,超过阈值6.0m³/h,已排查确认为上游施工影响,现已关闭。'
+  },
+  {
+    id: 6, archiveNo: 'BJ20250101006', area: '沅陵中路', alarmTime: '2025-12-29 11:45:20',
+    type: '窨井', level: '一般', address: '**市沅陵中路6号窨井',
+    status: '已处理', deviceId: 'YL-015', deviceName: '液位传感器15号', model: 'YW-200A',
+    unit: 'XX排水公司', description: '沅陵中路6号窨井液位传感器检测到液位轻微超限,当前2.8m,超过阈值2.5m,已排查并恢复正常。'
+  },
+  {
+    id: 7, archiveNo: 'BJ20250101007', area: '沅陵南路', alarmTime: '2025-12-28 07:50:33',
+    type: '管线', level: '严重', address: '**市沅陵南路7号管点',
+    status: '未处理', deviceId: 'WD-002', deviceName: '温度传感器2号', model: 'WD-150D',
+    unit: 'XX排水公司', description: '沅陵南路7号管点温度传感器检测到温度异常,当前45℃,超过阈值40℃,可能存在管道热水排放或异常工况,需排查。'
+  },
+  {
+    id: 8, archiveNo: 'BJ20250101008', area: '沅陵东路', alarmTime: '2025-12-28 13:28:47',
+    type: '管点', level: '重要', address: '**市沅陵东路8号泵站',
+    status: '处理中', deviceId: 'YL-021', deviceName: '压力传感器21号', model: 'YL-500E',
+    unit: 'XX排水公司', description: '沅陵东路8号泵站压力传感器检测到压力超标,当前0.8MPa,超过阈值0.6MPa,正在安排专业人员检修。'
+  },
+  {
+    id: 9, archiveNo: 'BJ20250101009', area: '沅陵西路', alarmTime: '2025-12-27 19:10:05',
+    type: '窨井', level: '一般', address: '**市沅陵西路9号排水口',
+    status: '已处理', deviceId: 'LL-007', deviceName: '流量计7号', model: 'LL-300B',
+    unit: 'XX排水公司', description: '沅陵西路9号排水口流量计检测到流量轻微异常,当前15.2m³/h,超过阈值10.0m³/h,确认为暴雨导致,已处理完毕。'
+  },
+  {
+    id: 10, archiveNo: 'BJ20250101010', area: '沅陵北路', alarmTime: '2025-12-27 22:55:19',
+    type: '管线', level: '重要', address: '**市沅陵北路10号窨井',
+    status: '未处理', deviceId: 'YL-009', deviceName: '液位传感器9号', model: 'YW-200A',
+    unit: 'XX排水公司', description: '沅陵北路10号窨井液位传感器检测到液位超限,当前3.2m,超过阈值3.0m,需安排巡检。'
+  },
+  {
+    id: 11, archiveNo: 'BJ20250101011', area: '沅陵中路', alarmTime: '2025-12-26 06:42:31',
+    type: '管点', level: '一般', address: '**市沅陵中路排水干管',
+    status: '已关闭', deviceId: 'LL-018', deviceName: '流量计18号', model: 'LL-300B',
+    unit: 'XX排水公司', description: '沅陵中路排水干管流量计检测到轻微流量波动,当前7.2m³/h,超过阈值6.0m³/h,经核实为设备校准偏差,已关闭。'
+  },
+  {
+    id: 12, archiveNo: 'BJ20250101012', area: '沅陵南路', alarmTime: '2025-12-25 15:08:56',
+    type: '窨井', level: '严重', address: '**市沅陵南路污水检查井',
+    status: '处理中', deviceId: 'YL-033', deviceName: '液位传感器33号', model: 'YW-200A',
+    unit: 'XX排水公司', description: '沅陵南路污水检查井液位传感器检测到液位严重超限,当前4.0m,超过阈值3.0m,存在污水外溢风险,正在紧急处理中。'
+  }
+])
+
+// ==================== 过滤列表 ====================
+const filteredList = computed(() => {
+  return tableData.value.filter(item => {
+    const idMatch = !queryParams.value.deviceId || item.deviceId.toLowerCase().includes(queryParams.value.deviceId.toLowerCase())
+    const areaMatch = !queryParams.value.area || item.area === queryParams.value.area
+    const statusMatch = !queryParams.value.status || item.status === queryParams.value.status
+    return idMatch && areaMatch && statusMatch
+  })
+})
 
+// ==================== 样式辅助 ====================
+function typeClass(type) {
+  const map = { '管线': 'type-pipeline', '管点': 'type-point', '窨井': 'type-manhole' }
+  return map[type] || ''
+}
+
+function levelClass(level) {
+  const map = { '严重': 'level-critical', '重要': 'level-major', '一般': 'level-minor' }
+  return map[level] || ''
+}
+
+function statusTagType(status) {
+  const map = { '未处理': 'danger', '处理中': 'warning', '已处理': 'success', '已关闭': 'info' }
+  return map[status] || 'info'
+}
+
+// ==================== 查看详情 ====================
+const dialogVisible = ref(false)
+const currentRow = ref({})
+const dispatchPerson = ref('')
+const personOptions = ['张伟', '李明', '王芳', '赵强', '刘洋', '陈静']
+
+function handleView(row) {
+  currentRow.value = { ...row }
+  dispatchPerson.value = ''
+  dialogVisible.value = true
+}
+
+function handleClose() {
+  dialogVisible.value = false
+}
+
+function handleDispatch() {
+  if (!dispatchPerson.value) {
+    ElMessage.warning('请选择下派人员')
+    return
+  }
+  ElMessage.success(`已下派给 ${dispatchPerson.value}`)
+  dialogVisible.value = false
+}
 </script>
 
 <style scoped>
+.app-container {
+  padding: 20px;
+  background: #fff;
+  min-height: calc(100vh - 84px);
+}
+
+.search-form {
+  margin-bottom: 16px;
+  padding: 16px 16px 0;
+  background: #f5f7fa;
+  border-radius: 6px;
+}
+
+/* 类型彩色文字 */
+.type-pipeline {
+  color: #409eff;
+  font-weight: 600;
+}
+
+.type-point {
+  color: #67c23a;
+  font-weight: 600;
+}
+
+.type-manhole {
+  color: #e6a23c;
+  font-weight: 600;
+}
+
+/* 等级彩色文字 */
+.level-critical {
+  color: #f56c6c;
+  font-weight: 700;
+}
+
+.level-major {
+  color: #e6a23c;
+  font-weight: 700;
+}
+
+.level-minor {
+  color: #409eff;
+  font-weight: 600;
+}
+
+/* 详情弹窗 */
+.detail-panel {
+  padding: 0 4px;
+}
+
+.detail-top {
+  display: flex;
+  gap: 24px;
+}
+
+.detail-image {
+  width: 150px;
+  height: 150px;
+  flex-shrink: 0;
+  background: #dce6f0;
+  border-radius: 8px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border: 1px solid #c0cfe0;
+}
+
+.detail-info {
+  flex: 1;
+}
+
+.detail-number {
+  font-size: 20px;
+  font-weight: 700;
+  color: #303133;
+  margin-bottom: 14px;
+}
+
+.info-item {
+  display: flex;
+  align-items: flex-start;
+  margin-bottom: 10px;
+  line-height: 22px;
+}
+
+.info-label {
+  color: #909399;
+  font-size: 14px;
+  white-space: nowrap;
+  min-width: 65px;
+  flex-shrink: 0;
+}
+
+.info-value {
+  color: #303133;
+  font-size: 14px;
+  flex: 1;
+}
+
+.desc-item {
+  align-items: flex-start;
+}
+
+.desc-text {
+  line-height: 22px;
+  color: #606266;
+}
+
+/* 底部操作区 */
+.detail-footer {
+  margin-top: 20px;
+  padding-top: 16px;
+  border-top: 1px solid #ebeef5;
+}
+
+.dispatch-row {
+  display: flex;
+  align-items: center;
+}
 
+.dispatch-label {
+  font-size: 14px;
+  color: #303133;
+  font-weight: 600;
+  white-space: nowrap;
+}
 </style>

+ 392 - 3
src/views/subSystem/drainage/jcbj/bjsh.vue

@@ -1,13 +1,402 @@
 <template>
-  <div class="app-container">
-    <h2>报警审核</h2>
+  <div class="app-container bjsh-page">
+    <!-- ==================== 上半部分:GIS地图 ==================== -->
+    <div class="map-section">
+      <div id="bjshMap" class="map-container"></div>
+    </div>
+
+    <!-- ==================== 下半部分:搜索 + 报警审核表格 ==================== -->
+    <div class="table-section">
+      <!-- 搜索区 -->
+      <el-form :model="queryParams" ref="queryRef" :inline="true" class="search-form">
+        <el-form-item label="设备名称">
+          <el-input v-model="queryParams.deviceName" placeholder="请输入设备名称" clearable style="width: 200px" />
+        </el-form-item>
+        <el-form-item label="报警等级">
+          <el-select v-model="queryParams.level" placeholder="请选择" clearable style="width: 200px">
+            <el-option label="严重" value="严重" />
+            <el-option label="重要" value="重要" />
+            <el-option label="一般" value="一般" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="审核状态">
+          <el-select v-model="queryParams.auditStatus" placeholder="请选择" clearable style="width: 200px">
+            <el-option label="待审核" value="待审核" />
+            <el-option label="已审核" value="已审核" />
+            <el-option label="已解除" value="已解除" />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleQuery">搜索</el-button>
+          <el-button @click="resetQuery">重置</el-button>
+        </el-form-item>
+      </el-form>
+
+      <!-- 报警审核表格 -->
+      <el-table :data="filteredData" border stripe style="width: 100%">
+        <el-table-column prop="alarmNo" label="报警编号" width="140" />
+        <el-table-column prop="deviceName" label="设备名称" min-width="140" />
+        <el-table-column prop="level" label="报警等级" width="100">
+          <template #default="{ row }">
+            <el-tag :type="levelTagType(row.level)" size="small">{{ row.level }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="alarmTime" label="报警时间" width="160" />
+        <el-table-column prop="currentValue" label="监测值" width="100">
+          <template #default="{ row }">
+            <span :style="{ color: row.currentValue > row.threshold ? '#f56c6c' : '#303133' }">{{ row.currentValue }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="auditStatus" label="审核状态" width="100">
+          <template #default="{ row }">
+            <el-tag :type="auditStatusTagType(row.auditStatus)" size="small">{{ row.auditStatus }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="160" align="center">
+          <template #default="{ row }">
+            <el-button v-if="row.auditStatus === '待审核'" link type="primary" @click="openAudit(row)">审核</el-button>
+            <el-button link type="primary" @click="openView(row)">查看</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+
+    <!-- ==================== 审核弹窗 ==================== -->
+    <el-dialog v-model="auditVisible" title="报警审核" width="700px" :before-close="closeAudit">
+      <!-- 报警信息展示 -->
+      <el-descriptions :column="2" border size="small" style="margin-bottom: 16px">
+        <el-descriptions-item label="设备名称">{{ currentAlarm.deviceName }}</el-descriptions-item>
+        <el-descriptions-item label="报警时间">{{ currentAlarm.alarmTime }}</el-descriptions-item>
+        <el-descriptions-item label="报警等级">
+          <el-tag :type="levelTagType(currentAlarm.level)" size="small">{{ currentAlarm.level }}</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="指标值">{{ currentAlarm.indicator }}:{{ currentAlarm.currentValue }}(阈值 {{ currentAlarm.threshold }})</el-descriptions-item>
+      </el-descriptions>
+
+      <!-- 审核操作 -->
+      <div class="audit-form-section">
+        <div class="section-title">审核操作</div>
+        <el-form :model="auditForm" label-width="80px" style="margin-top: 12px">
+          <el-form-item label="审核结果" required>
+            <el-select v-model="auditForm.result" placeholder="请选择审核结果" style="width: 240px">
+              <el-option label="确认报警" value="确认报警" />
+              <el-option label="解除报警" value="解除报警" />
+              <el-option label="误报处理" value="误报处理" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="审核意见" required>
+            <el-input v-model="auditForm.opinion" type="textarea" :rows="3" placeholder="请输入审核意见" />
+          </el-form-item>
+          <el-form-item label="附件上传">
+            <el-upload
+              action="#"
+              :auto-upload="false"
+              :limit="3"
+              :on-change="handleFileChange"
+              :on-remove="handleFileRemove"
+            >
+              <el-button type="primary" size="small">选择文件</el-button>
+              <template #tip>
+                <div class="upload-tip">支持上传图片/文档,最多3个文件</div>
+              </template>
+            </el-upload>
+          </el-form-item>
+        </el-form>
+      </div>
+
+      <!-- 处置流程时间线 -->
+      <div class="audit-timeline-section">
+        <div class="section-title">处置流程</div>
+        <el-timeline style="margin-top: 12px; padding-left: 2px">
+          <el-timeline-item timestamp="2026-06-12 08:30" placement="top" color="#f56c6c">
+            报警触发 - {{ currentAlarm.deviceName }} {{ currentAlarm.indicator }}超标
+          </el-timeline-item>
+          <el-timeline-item timestamp="2026-06-12 08:35" placement="top" color="#e6a23c">
+            系统通知 - 已推送至值守人员
+          </el-timeline-item>
+          <el-timeline-item timestamp="当前" placement="top" color="#409eff">
+            人工审核 - 审核进行中
+          </el-timeline-item>
+          <el-timeline-item timestamp="待完成" placement="top" color="#909399">
+            处置完成
+          </el-timeline-item>
+        </el-timeline>
+      </div>
+
+      <template #footer>
+        <el-button @click="closeAudit">取消</el-button>
+        <el-button type="primary" @click="submitAudit">提交审核</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- ==================== 查看详情弹窗 ==================== -->
+    <el-dialog v-model="viewVisible" title="报警详情" width="600px">
+      <el-descriptions :column="2" border size="small">
+        <el-descriptions-item label="报警编号">{{ viewRow.alarmNo }}</el-descriptions-item>
+        <el-descriptions-item label="设备名称">{{ viewRow.deviceName }}</el-descriptions-item>
+        <el-descriptions-item label="报警等级">
+          <el-tag :type="levelTagType(viewRow.level)" size="small">{{ viewRow.level }}</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="报警时间">{{ viewRow.alarmTime }}</el-descriptions-item>
+        <el-descriptions-item label="监测指标">{{ viewRow.indicator }}</el-descriptions-item>
+        <el-descriptions-item label="实时值">{{ viewRow.currentValue }}</el-descriptions-item>
+        <el-descriptions-item label="阈值">{{ viewRow.threshold }}</el-descriptions-item>
+        <el-descriptions-item label="审核状态">
+          <el-tag :type="auditStatusTagType(viewRow.auditStatus)" size="small">{{ viewRow.auditStatus }}</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="审核结果" :span="2">{{ viewRow.auditResult || '—' }}</el-descriptions-item>
+        <el-descriptions-item label="审核意见" :span="2">{{ viewRow.auditOpinion || '—' }}</el-descriptions-item>
+      </el-descriptions>
+      <!-- 处置流程时间线 -->
+      <div style="margin-top: 16px">
+        <div class="section-title">处置流程</div>
+        <el-timeline style="margin-top: 12px; padding-left: 2px">
+          <el-timeline-item timestamp="2026-06-12 08:30" placement="top" color="#f56c6c">
+            报警触发 - {{ viewRow.deviceName }} {{ viewRow.indicator }}超标
+          </el-timeline-item>
+          <el-timeline-item timestamp="2026-06-12 08:35" placement="top" color="#e6a23c">
+            系统通知 - 已推送至值守人员
+          </el-timeline-item>
+          <el-timeline-item
+            v-if="viewRow.auditStatus !== '待审核'"
+            timestamp="2026-06-12 10:00"
+            placement="top"
+            color="#409eff"
+          >
+            人工审核 - {{ viewRow.auditResult }}:{{ viewRow.auditOpinion }}
+          </el-timeline-item>
+          <el-timeline-item
+            v-if="viewRow.auditStatus === '已解除'"
+            timestamp="2026-06-12 11:00"
+            placement="top"
+            color="#67c23a"
+          >
+            处置完成 - 报警已解除
+          </el-timeline-item>
+        </el-timeline>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
 <script setup name="Bjsh">
+import { ref, computed, onMounted } from 'vue'
+import { ElMessage } from 'element-plus'
+import locationIcon from '@/assets/images/location.png'
+
+// ==================== 地图变量 ====================
+let mapInstance = null
+
+// ==================== 搜索参数 ====================
+const queryParams = ref({
+  deviceName: '',
+  level: '',
+  auditStatus: ''
+})
+const queryRef = ref(null)
+
+function handleQuery() {
+  ElMessage.success('搜索完成')
+}
+function resetQuery() {
+  queryParams.value = { deviceName: '', level: '', auditStatus: '' }
+}
+
+// ==================== 报警等级 / 审核状态 tag类型 ====================
+function levelTagType(level) {
+  if (level === '严重') return 'danger'
+  if (level === '重要') return 'warning'
+  return ''
+}
+function auditStatusTagType(status) {
+  if (status === '已审核') return 'success'
+  if (status === '已解除') return 'info'
+  return 'warning'
+}
+
+// ==================== 模拟数据(12条报警审核数据) ====================
+const alarmList = ref([
+  { alarmNo: 'BJ20260612001', deviceName: '沅陵桥水位传感器', level: '严重', alarmTime: '2026-06-12 08:30', indicator: '水位', currentValue: 2.85, threshold: 2.0, auditStatus: '待审核', auditResult: '', auditOpinion: '', lng: 110.398, lat: 28.465 },
+  { alarmNo: 'BJ20260612002', deviceName: '太常片区流量计', level: '重要', alarmTime: '2026-06-12 09:15', indicator: '流量', currentValue: 5.6, threshold: 4.0, auditStatus: '待审核', auditResult: '', auditOpinion: '', lng: 110.372, lat: 28.455 },
+  { alarmNo: 'BJ20260612003', deviceName: '城南液位传感器', level: '一般', alarmTime: '2026-06-12 10:00', indicator: '液位', currentValue: 0.8, threshold: 0.6, auditStatus: '已审核', auditResult: '确认报警', auditOpinion: '液位持续偏高,需持续关注', lng: 110.405, lat: 28.442 },
+  { alarmNo: 'BJ20260612004', deviceName: '河西水质监测仪', level: '严重', alarmTime: '2026-06-12 07:45', indicator: 'COD', currentValue: 120, threshold: 80, auditStatus: '待审核', auditResult: '', auditOpinion: '', lng: 110.368, lat: 28.448 },
+  { alarmNo: 'BJ20260612005', deviceName: '北门流量计', level: '重要', alarmTime: '2026-06-12 11:30', indicator: '流量', currentValue: 4.8, threshold: 4.0, auditStatus: '已解除', auditResult: '解除报警', auditOpinion: '暴雨导致临时流量增大,现已恢复正常', lng: 110.410, lat: 28.470 },
+  { alarmNo: 'BJ20260612006', deviceName: '东郊水位传感器', level: '一般', alarmTime: '2026-06-11 22:10', indicator: '水位', currentValue: 1.8, threshold: 1.5, auditStatus: '待审核', auditResult: '', auditOpinion: '', lng: 110.420, lat: 28.440 },
+  { alarmNo: 'BJ20260612007', deviceName: '中心区压力传感器', level: '严重', alarmTime: '2026-06-11 18:20', indicator: '压力', currentValue: 0.85, threshold: 0.5, auditStatus: '已审核', auditResult: '确认报警', auditOpinion: '管网压力异常,需紧急维修', lng: 110.395, lat: 28.452 },
+  { alarmNo: 'BJ20260612008', deviceName: '沅水南岸流量计', level: '重要', alarmTime: '2026-06-11 15:40', indicator: '流量', currentValue: 6.2, threshold: 5.0, auditStatus: '待审核', auditResult: '', auditOpinion: '', lng: 110.380, lat: 28.435 },
+  { alarmNo: 'BJ20260612009', deviceName: '西渡口液位传感器', level: '一般', alarmTime: '2026-06-11 12:00', indicator: '液位', currentValue: 0.7, threshold: 0.6, auditStatus: '已解除', auditResult: '误报处理', auditOpinion: '传感器漂移导致数据异常', lng: 110.360, lat: 28.460 },
+  { alarmNo: 'BJ20260612010', deviceName: '开发区水质监测仪', level: '重要', alarmTime: '2026-06-10 09:50', indicator: '氨氮', currentValue: 8.5, threshold: 5.0, auditStatus: '待审核', auditResult: '', auditOpinion: '', lng: 110.425, lat: 28.475 },
+  { alarmNo: 'BJ20260612011', deviceName: '老城区液位传感器', level: '严重', alarmTime: '2026-06-10 06:20', indicator: '液位', currentValue: 1.2, threshold: 0.8, auditStatus: '已审核', auditResult: '确认报警', auditOpinion: '排水不畅导致液位超标', lng: 110.393, lat: 28.450 },
+  { alarmNo: 'BJ20260612012', deviceName: '工业区流量计', level: '一般', alarmTime: '2026-06-09 14:30', indicator: '流量', currentValue: 3.5, threshold: 3.0, auditStatus: '待审核', auditResult: '', auditOpinion: '', lng: 110.435, lat: 28.430 }
+])
 
+const filteredData = computed(() => {
+  let list = alarmList.value
+  if (queryParams.value.deviceName) {
+    list = list.filter(item => item.deviceName.includes(queryParams.value.deviceName))
+  }
+  if (queryParams.value.level) {
+    list = list.filter(item => item.level === queryParams.value.level)
+  }
+  if (queryParams.value.auditStatus) {
+    list = list.filter(item => item.auditStatus === queryParams.value.auditStatus)
+  }
+  return list
+})
+
+// ==================== 审核弹窗 ====================
+const auditVisible = ref(false)
+const currentAlarm = ref({})
+const auditForm = ref({ result: '', opinion: '' })
+const auditFiles = ref([])
+
+function openAudit(row) {
+  currentAlarm.value = { ...row }
+  auditForm.value = { result: '', opinion: '' }
+  auditFiles.value = []
+  auditVisible.value = true
+}
+function closeAudit() {
+  auditVisible.value = false
+}
+function handleFileChange(file) {
+  auditFiles.value.push(file)
+}
+function handleFileRemove(file) {
+  const idx = auditFiles.value.findIndex(f => f.uid === file.uid)
+  if (idx !== -1) auditFiles.value.splice(idx, 1)
+}
+function submitAudit() {
+  if (!auditForm.value.result) {
+    ElMessage.warning('请选择审核结果')
+    return
+  }
+  if (!auditForm.value.opinion) {
+    ElMessage.warning('请输入审核意见')
+    return
+  }
+  // 更新审核状态
+  const idx = alarmList.value.findIndex(item => item.alarmNo === currentAlarm.value.alarmNo)
+  if (idx !== -1) {
+    const statusMap = { '确认报警': '已审核', '解除报警': '已解除', '误报处理': '已解除' }
+    alarmList.value[idx].auditStatus = statusMap[auditForm.value.result] || '已审核'
+    alarmList.value[idx].auditResult = auditForm.value.result
+    alarmList.value[idx].auditOpinion = auditForm.value.opinion
+  }
+  ElMessage.success('审核提交成功')
+  closeAudit()
+}
+
+// ==================== 查看详情弹窗 ====================
+const viewVisible = ref(false)
+const viewRow = ref({})
+function openView(row) {
+  viewRow.value = { ...row }
+  viewVisible.value = true
+}
+
+// ==================== 地图初始化 ====================
+onMounted(() => {
+  initMap()
+})
+
+function initMap() {
+  const container = document.getElementById('bjshMap')
+  if (!container || typeof BMapGL === 'undefined') {
+    console.warn('BMapGL 未加载或容器不存在')
+    return
+  }
+  try {
+    mapInstance = new BMapGL.Map('bjshMap')
+    const centerPoint = new BMapGL.Point(110.393, 28.452)
+    mapInstance.centerAndZoom(centerPoint, 15)
+    mapInstance.enableScrollWheelZoom(true)
+
+    // 添加报警点位标记
+    alarmList.value.forEach(point => {
+      const levelColor = {
+        '严重': '#f56c6c',
+        '重要': '#e6a23c',
+        '一般': '#409eff'
+      }
+      const color = levelColor[point.level] || '#409eff'
+      const bPoint = new BMapGL.Point(point.lng, point.lat)
+
+      const html = `<div style="text-align:center;cursor:pointer;">
+        <div style="color:#fff;background:${color};border-radius:4px;padding:2px 6px;font-size:11px;white-space:nowrap;display:inline-block;margin-bottom:2px;">${point.deviceName}</div>
+        <div style="color:#fff;font-size:10px;background:rgba(0,0,0,0.6);border-radius:3px;padding:1px 4px;margin-bottom:2px;">${point.level} | ${point.indicator}:${point.currentValue}</div>
+        <div><img src="${locationIcon}" style="width:24px;height:24px;display:block;margin:0 auto;"/></div>
+      </div>`
+
+      const label = new BMapGL.Label(html, {
+        position: bPoint,
+        offset: new BMapGL.Size(-30, -55)
+      })
+      label.setStyle({
+        border: 'none',
+        background: 'transparent',
+        padding: '0'
+      })
+      mapInstance.addOverlay(label)
+
+      // 点击标记弹出信息窗口
+      label.addEventListener('click', function () {
+        const statusColor = { '待审核': '#e6a23c', '已审核': '#67c23a', '已解除': '#909399' }
+        const sColor = statusColor[point.auditStatus] || '#409eff'
+        const infoContent = `<div style="font-size:13px;line-height:1.8;">
+          <div><b>${point.deviceName}</b></div>
+          <div>报警等级:<b style="color:${color}">${point.level}</b></div>
+          <div>报警时间:${point.alarmTime}</div>
+          <div>监测值:<b style="color:${color}">${point.indicator}:${point.currentValue}</b></div>
+          <div>阈值:${point.threshold}</div>
+          <div>审核状态:<b style="color:${sColor}">${point.auditStatus}</b></div>
+        </div>`
+        const infoWindow = new BMapGL.InfoWindow(infoContent, {
+          width: 220,
+          title: ''
+        })
+        mapInstance.openInfoWindow(infoWindow, bPoint)
+      })
+    })
+  } catch (e) {
+    console.error('地图初始化失败:', e)
+  }
+}
 </script>
 
 <style scoped>
+.bjsh-page {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.map-section {
+  width: 100%;
+}
+.map-container {
+  width: 100%;
+  height: 350px;
+  border: 1px solid #e4e7ed;
+  border-radius: 4px;
+}
+
+.table-section {
+  width: 100%;
+}
+.table-section .search-form {
+  margin-bottom: 12px;
+}
 
-</style>
+/* 弹窗内部样式 */
+.section-title {
+  font-size: 15px;
+  font-weight: bold;
+  margin-bottom: 0;
+}
+.upload-tip {
+  font-size: 12px;
+  color: #909399;
+  margin-top: 4px;
+}
+</style>

+ 416 - 2
src/views/subSystem/drainage/jcbj/bjtx.vue

@@ -1,13 +1,427 @@
 <template>
-  <div class="app-container">
-    <h2>报警提醒</h2>
+  <div class="bjtx-page">
+    <!-- 顶部栏 -->
+    <div class="top-bar">
+      <div class="top-left">
+        <h2 class="page-title">实时报警提醒</h2>
+        <span class="current-time">{{ currentTime }}</span>
+      </div>
+      <div class="top-right">
+        <div class="alarm-stats">
+          <span class="stat-item">今日报警 <em class="stat-num">{{ alarmList.length }}</em> 条</span>
+          <span class="stat-divider">/</span>
+          <span class="stat-item">未处理 <em class="stat-num warn">{{ unHandledCount }}</em> 条</span>
+        </div>
+        <div class="sound-toggle" :class="{ active: soundOn }" @click="soundOn = !soundOn">
+          <el-icon :size="20"><component :is="soundOn ? 'Bell' : 'Mute'" /></el-icon>
+        </div>
+      </div>
+    </div>
+
+    <!-- 滚动报警列表 -->
+    <div class="alarm-scroll-wrapper" ref="scrollWrapper">
+      <div class="alarm-scroll-inner" :style="{ transform: `translateY(${scrollOffset}px)` }">
+        <div
+          v-for="item in alarmList"
+          :key="item.id"
+          class="alarm-card"
+          :class="'level-' + item.level"
+        >
+          <div class="card-left">
+            <span class="level-icon" :class="'icon-' + item.level">
+              {{ item.level === '严重' ? '!!' : item.level === '重要' ? '!' : 'i' }}
+            </span>
+          </div>
+          <div class="card-body">
+            <div class="card-row row-1">
+              <span class="alarm-time">{{ item.time }}</span>
+              <span class="alarm-level-tag" :class="'tag-' + item.level">{{ item.level }}</span>
+            </div>
+            <div class="card-row row-2">
+              <span class="device-name">{{ item.device }}</span>
+            </div>
+            <div class="card-row row-3">
+              <span class="alarm-metric">{{ item.metric }}</span>
+            </div>
+            <div class="card-row row-4">
+              <el-icon :size="14"><Location /></el-icon>
+              <span class="alarm-location">{{ item.location }}</span>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
   </div>
 </template>
 
 <script setup name="Bjtx">
+import { ref, computed, onMounted, onUnmounted } from 'vue'
+import { Bell, Mute, Location } from '@element-plus/icons-vue'
+
+// ==================== 时钟 ====================
+const currentTime = ref('')
+let clockTimer = null
+
+function updateClock() {
+  const now = new Date()
+  const y = now.getFullYear()
+  const M = String(now.getMonth() + 1).padStart(2, '0')
+  const d = String(now.getDate()).padStart(2, '0')
+  const h = String(now.getHours()).padStart(2, '0')
+  const m = String(now.getMinutes()).padStart(2, '0')
+  const s = String(now.getSeconds()).padStart(2, '0')
+  currentTime.value = `${y}-${M}-${d} ${h}:${m}:${s}`
+}
+
+// ==================== 声音开关 ====================
+const soundOn = ref(true)
+
+// ==================== 模拟数据 ====================
+const devices = ['液位传感器YL-001', '流量计LL-012', '雨量计YL-005', '液位传感器YL-008', '流量计LL-003',
+  '液位传感器YL-015', '温度传感器WD-002', '压力传感器YL-021', '流量计LL-007', '液位传感器YL-009']
+const metrics = [
+  { text: '液位超限 3.5m > 阈值3.0m', level: '严重' },
+  { text: '流量异常 12.8m³/h > 阈值10.0m³/h', level: '重要' },
+  { text: '雨量超标 58mm/h > 阈值50mm/h', level: '一般' },
+  { text: '液位超限 2.8m > 阈值2.5m', level: '重要' },
+  { text: '流量异常 8.5m³/h > 阈值6.0m³/h', level: '一般' },
+  { text: '液位超限 4.2m > 阈值3.0m', level: '严重' },
+  { text: '温度异常 45℃ > 阈值40℃', level: '一般' },
+  { text: '压力超标 0.8MPa > 阈值0.6MPa', level: '重要' },
+  { text: '液位超限 3.2m > 阈值3.0m', level: '一般' },
+  { text: '流量异常 15.2m³/h > 阈值10.0m³/h', level: '严重' }
+]
+const locations = ['沅陵北路1号排水口', '沅陵南路2号窨井', '沅陵东路3号管点', '沅陵西路4号泵站',
+  '沅陵北路5号排水口', '沅陵中路6号窨井', '沅陵南路7号管点', '沅陵东路8号泵站',
+  '沅陵西路9号排水口', '沅陵北路10号窨井']
+
+let alarmIdCounter = 100
+const alarmList = ref([])
+
+function formatTime(date) {
+  const h = String(date.getHours()).padStart(2, '0')
+  const m = String(date.getMinutes()).padStart(2, '0')
+  const s = String(date.getSeconds()).padStart(2, '0')
+  return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${h}:${m}:${s}`
+}
+
+function generateAlarm() {
+  const metric = metrics[Math.floor(Math.random() * metrics.length)]
+  const device = devices[Math.floor(Math.random() * devices.length)]
+  const location = locations[Math.floor(Math.random() * locations.length)]
+  return {
+    id: alarmIdCounter++,
+    time: formatTime(new Date()),
+    device,
+    metric: metric.text,
+    level: metric.level,
+    location
+  }
+}
+
+// 初始化15条数据
+function initAlarms() {
+  const now = Date.now()
+  for (let i = 0; i < 18; i++) {
+    const alarm = generateAlarm()
+    alarm.time = formatTime(new Date(now - (18 - i) * 180000)) // 每3分钟一条
+    alarmList.value.push(alarm)
+  }
+}
+
+const unHandledCount = computed(() => alarmList.value.filter(a => a.level === '严重' || a.level === '重要').length)
+
+// ==================== 自动滚动 ====================
+const scrollWrapper = ref(null)
+const scrollOffset = ref(0)
+let scrollTimer = null
+let addTimer = null
+const CARD_HEIGHT = 128 // 卡片高度 + 间距
+let scrollPaused = false
+
+function startScroll() {
+  scrollTimer = setInterval(() => {
+    if (scrollPaused) return
+    scrollOffset.value -= 1
+    // 每滚动一个卡片高度,将第一条数据移到末尾
+    if (Math.abs(scrollOffset.value) >= CARD_HEIGHT) {
+      const first = alarmList.value.shift()
+      if (first) alarmList.value.push(first)
+      scrollOffset.value = 0
+    }
+  }, 30)
+}
+
+function addNewAlarm() {
+  addTimer = setInterval(() => {
+    const alarm = generateAlarm()
+    alarmList.value.push(alarm)
+  }, 3000)
+}
 
+// ==================== 生命周期 ====================
+onMounted(() => {
+  updateClock()
+  clockTimer = setInterval(updateClock, 1000)
+  initAlarms()
+  startScroll()
+  addNewAlarm()
+
+  // 鼠标悬停暂停滚动
+  const wrapper = scrollWrapper.value
+  if (wrapper) {
+    wrapper.addEventListener('mouseenter', () => { scrollPaused = true })
+    wrapper.addEventListener('mouseleave', () => { scrollPaused = false })
+  }
+})
+
+onUnmounted(() => {
+  if (clockTimer) clearInterval(clockTimer)
+  if (scrollTimer) clearInterval(scrollTimer)
+  if (addTimer) clearInterval(addTimer)
+})
 </script>
 
 <style scoped>
+.bjtx-page {
+  width: 100%;
+  height: 100%;
+  min-height: calc(100vh - 84px);
+  background: linear-gradient(180deg, #0a1628 0%, #0d2137 50%, #0a1628 100%);
+  display: flex;
+  flex-direction: column;
+  color: #e0e6ed;
+  overflow: hidden;
+}
+
+/* 顶部栏 */
+.top-bar {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 18px 28px;
+  background: rgba(10, 30, 60, 0.85);
+  border-bottom: 1px solid rgba(64, 158, 255, 0.25);
+  flex-shrink: 0;
+}
+
+.top-left {
+  display: flex;
+  align-items: baseline;
+  gap: 18px;
+}
+
+.page-title {
+  font-size: 22px;
+  font-weight: 700;
+  color: #fff;
+  letter-spacing: 2px;
+  margin: 0;
+  background: linear-gradient(90deg, #409eff, #67c8ff);
+  -webkit-background-clip: text;
+  -webkit-text-fill-color: transparent;
+}
+
+.current-time {
+  font-size: 14px;
+  color: #8bb8e8;
+  font-family: 'Courier New', monospace;
+}
+
+.top-right {
+  display: flex;
+  align-items: center;
+  gap: 24px;
+}
+
+.alarm-stats {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 14px;
+  color: #a0c4e8;
+}
+
+.stat-num {
+  font-style: normal;
+  font-weight: 700;
+  font-size: 18px;
+  color: #409eff;
+}
+
+.stat-num.warn {
+  color: #e6a23c;
+}
+
+.stat-divider {
+  color: #3a5a7a;
+}
+
+.sound-toggle {
+  width: 38px;
+  height: 38px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border-radius: 8px;
+  cursor: pointer;
+  background: rgba(255, 255, 255, 0.06);
+  border: 1px solid rgba(64, 158, 255, 0.3);
+  color: #606266;
+  transition: all 0.3s;
+}
+
+.sound-toggle.active {
+  color: #409eff;
+  border-color: #409eff;
+  background: rgba(64, 158, 255, 0.12);
+}
+
+/* 滚动列表 */
+.alarm-scroll-wrapper {
+  flex: 1;
+  overflow: hidden;
+  padding: 16px 28px;
+}
+
+.alarm-scroll-inner {
+  transition: none;
+}
+
+/* 报警卡片 */
+.alarm-card {
+  display: flex;
+  align-items: stretch;
+  background: rgba(12, 30, 55, 0.85);
+  border: 1px solid rgba(64, 158, 255, 0.15);
+  border-radius: 10px;
+  margin-bottom: 10px;
+  overflow: hidden;
+  transition: border-color 0.3s;
+  height: 118px;
+}
+
+.alarm-card:hover {
+  border-color: rgba(64, 158, 255, 0.45);
+}
+
+.alarm-card.level-严重 {
+  border-left: 4px solid #f56c6c;
+}
+
+.alarm-card.level-重要 {
+  border-left: 4px solid #e6a23c;
+}
+
+.alarm-card.level-一般 {
+  border-left: 4px solid #409eff;
+}
+
+.card-left {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 60px;
+  flex-shrink: 0;
+}
+
+.level-icon {
+  width: 36px;
+  height: 36px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 16px;
+  font-weight: 800;
+  color: #fff;
+}
+
+.level-icon.icon-严重 {
+  background: radial-gradient(circle, #f56c6c, #c45656);
+  box-shadow: 0 0 12px rgba(245, 108, 108, 0.5);
+  animation: pulse-red 1.5s infinite;
+}
+
+.level-icon.icon-重要 {
+  background: radial-gradient(circle, #e6a23c, #c88630);
+  box-shadow: 0 0 10px rgba(230, 162, 60, 0.4);
+}
+
+.level-icon.icon-一般 {
+  background: radial-gradient(circle, #409eff, #2d7dd2);
+  box-shadow: 0 0 8px rgba(64, 158, 255, 0.3);
+}
+
+@keyframes pulse-red {
+  0%, 100% { box-shadow: 0 0 12px rgba(245, 108, 108, 0.5); }
+  50% { box-shadow: 0 0 24px rgba(245, 108, 108, 0.8); }
+}
+
+.card-body {
+  flex: 1;
+  padding: 12px 16px;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  gap: 6px;
+}
+
+.card-row {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.row-1 {
+  justify-content: space-between;
+}
+
+.alarm-time {
+  font-size: 12px;
+  color: #6a8da8;
+  font-family: 'Courier New', monospace;
+}
+
+.alarm-level-tag {
+  font-size: 11px;
+  padding: 1px 10px;
+  border-radius: 10px;
+  font-weight: 600;
+}
+
+.tag-严重 {
+  background: rgba(245, 108, 108, 0.18);
+  color: #f56c6c;
+  border: 1px solid rgba(245, 108, 108, 0.4);
+}
+
+.tag-重要 {
+  background: rgba(230, 162, 60, 0.18);
+  color: #e6a23c;
+  border: 1px solid rgba(230, 162, 60, 0.4);
+}
+
+.tag-一般 {
+  background: rgba(64, 158, 255, 0.18);
+  color: #409eff;
+  border: 1px solid rgba(64, 158, 255, 0.4);
+}
+
+.device-name {
+  font-size: 15px;
+  font-weight: 600;
+  color: #dce8f0;
+}
+
+.alarm-metric {
+  font-size: 13px;
+  color: #e6a23c;
+  font-weight: 500;
+}
 
+.alarm-location {
+  font-size: 12px;
+  color: #6a8da8;
+}
 </style>

+ 457 - 3
src/views/subSystem/drainage/jcbj/bjyp.vue

@@ -1,13 +1,467 @@
 <template>
-  <div class="app-container">
-    <h2>报警研判</h2>
+  <div class="app-container bjyp-page">
+    <!-- ==================== 搜索区 ==================== -->
+    <el-form :model="queryParams" ref="queryRef" :inline="true" class="search-form">
+      <el-form-item label="设备名称">
+        <el-input v-model="queryParams.deviceName" placeholder="请输入设备名称" clearable style="width: 200px" />
+      </el-form-item>
+      <el-form-item label="报警等级">
+        <el-select v-model="queryParams.level" placeholder="请选择" clearable style="width: 200px">
+          <el-option label="严重" value="严重" />
+          <el-option label="重要" value="重要" />
+          <el-option label="一般" value="一般" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="时间范围">
+        <el-date-picker
+          v-model="queryParams.dateRange"
+          type="daterange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          style="width: 260px"
+          value-format="YYYY-MM-DD"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" @click="handleQuery">搜索</el-button>
+        <el-button @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <!-- ==================== 报警列表 ==================== -->
+    <el-table :data="filteredData" border stripe style="width: 100%">
+      <el-table-column prop="alarmNo" label="报警编号" width="140" />
+      <el-table-column prop="deviceName" label="设备名称" min-width="140" />
+      <el-table-column prop="alarmTime" label="报警时间" width="160" />
+      <el-table-column prop="level" label="报警等级" width="100">
+        <template #default="{ row }">
+          <el-tag :type="levelTagType(row.level)" size="small">{{ row.level }}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column prop="indicator" label="监测指标" width="120" />
+      <el-table-column prop="currentValue" label="实时值" width="100" />
+      <el-table-column prop="threshold" label="阈值" width="100" />
+      <el-table-column prop="status" label="研判状态" width="100">
+        <template #default="{ row }">
+          <el-tag :type="row.status === '已研判' ? 'success' : 'warning'" size="small">{{ row.status }}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" width="120" align="center">
+        <template #default="{ row }">
+          <el-button link type="primary" @click="openAnalysis(row)">研判分析</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- ==================== 研判分析弹窗 ==================== -->
+    <el-dialog v-model="analysisVisible" title="研判分析" width="900px" :before-close="closeAnalysis" @opened="onDialogOpened">
+      <!-- 设备基本信息 -->
+      <div class="analysis-header">
+        <el-descriptions :column="3" border size="small">
+          <el-descriptions-item label="设备名称">{{ currentAlarm.deviceName }}</el-descriptions-item>
+          <el-descriptions-item label="设备编号">{{ currentAlarm.deviceNo }}</el-descriptions-item>
+          <el-descriptions-item label="设备位置">{{ currentAlarm.location }}</el-descriptions-item>
+          <el-descriptions-item label="报警等级">
+            <el-tag :type="levelTagType(currentAlarm.level)" size="small">{{ currentAlarm.level }}</el-tag>
+          </el-descriptions-item>
+          <el-descriptions-item label="报警时间">{{ currentAlarm.alarmTime }}</el-descriptions-item>
+          <el-descriptions-item label="监测指标">{{ currentAlarm.indicator }}</el-descriptions-item>
+        </el-descriptions>
+      </div>
+
+      <!-- 监测曲线 -->
+      <div class="analysis-chart-section">
+        <div class="chart-title-bar">
+          <span class="chart-title">监测曲线</span>
+          <div class="chart-tabs">
+            <span
+              class="chart-tab"
+              :class="{ active: chartPeriod === '24h' }"
+              @click="changeChartPeriod('24h')"
+            >24小时</span>
+            <span
+              class="chart-tab"
+              :class="{ active: chartPeriod === '7d' }"
+              @click="changeChartPeriod('7d')"
+            >7天</span>
+            <span
+              class="chart-tab"
+              :class="{ active: chartPeriod === '15d' }"
+              @click="changeChartPeriod('15d')"
+            >15天</span>
+            <span
+              class="chart-tab"
+              :class="{ active: chartPeriod === '30d' }"
+              @click="changeChartPeriod('30d')"
+            >30天</span>
+          </div>
+        </div>
+        <div id="bjypChart" class="chart-container"></div>
+      </div>
+
+      <!-- 对比分析 -->
+      <div class="analysis-compare-section">
+        <div class="compare-title">对比分析</div>
+        <div class="compare-content">
+          <!-- 日均值/月均值统计 -->
+          <div class="compare-stats">
+            <div class="stat-box">
+              <div class="stat-label">日均值</div>
+              <div class="stat-value">{{ currentAlarm.dailyAvg }}</div>
+            </div>
+            <div class="stat-box">
+              <div class="stat-label">月均值</div>
+              <div class="stat-value">{{ currentAlarm.monthlyAvg }}</div>
+            </div>
+            <div class="stat-box highlight">
+              <div class="stat-label">当前值</div>
+              <div class="stat-value">{{ currentAlarm.currentValue }}</div>
+            </div>
+            <div class="stat-box">
+              <div class="stat-label">阈值</div>
+              <div class="stat-value">{{ currentAlarm.threshold }}</div>
+            </div>
+          </div>
+          <!-- 上下游对比表格 -->
+          <div class="compare-table-wrap">
+            <el-table :data="upDownStreamData" border size="small" style="width: 100%">
+              <el-table-column prop="name" label="设备名称" min-width="120" />
+              <el-table-column prop="direction" label="方向" width="80">
+                <template #default="{ row }">
+                  <el-tag :type="row.direction === '上游' ? 'info' : 'primary'" size="small">{{ row.direction }}</el-tag>
+                </template>
+              </el-table-column>
+              <el-table-column prop="value" label="监测值" width="100" />
+              <el-table-column prop="threshold" label="阈值" width="100" />
+              <el-table-column prop="status" label="状态" width="80">
+                <template #default="{ row }">
+                  <span :style="{ color: row.value > row.threshold ? '#f56c6c' : '#67c23a' }">
+                    {{ row.value > row.threshold ? '超标' : '正常' }}
+                  </span>
+                </template>
+              </el-table-column>
+            </el-table>
+          </div>
+        </div>
+      </div>
+
+      <!-- 研判结论 -->
+      <div class="analysis-conclusion-section">
+        <div class="conclusion-title">研判结论</div>
+        <el-form :model="conclusionForm" label-width="80px" style="margin-top: 12px">
+          <el-form-item label="研判结果" required>
+            <el-select v-model="conclusionForm.result" placeholder="请选择研判结果" style="width: 240px">
+              <el-option label="误报" value="误报" />
+              <el-option label="设备故障" value="设备故障" />
+              <el-option label="真实报警" value="真实报警" />
+              <el-option label="待观察" value="待观察" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="研判说明" required>
+            <el-input v-model="conclusionForm.desc" type="textarea" :rows="3" placeholder="请输入研判说明" />
+          </el-form-item>
+        </el-form>
+      </div>
+
+      <template #footer>
+        <el-button @click="closeAnalysis">取消</el-button>
+        <el-button type="primary" @click="submitConclusion">提交研判</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup name="Bjyp">
+import { ref, computed, nextTick } from 'vue'
+import { ElMessage } from 'element-plus'
+import * as echarts from 'echarts'
+
+// ==================== 搜索参数 ====================
+const queryParams = ref({
+  deviceName: '',
+  level: '',
+  dateRange: []
+})
+const queryRef = ref(null)
+
+function handleQuery() {
+  // 模拟搜索
+  ElMessage.success('搜索完成')
+}
+function resetQuery() {
+  queryParams.value = { deviceName: '', level: '', dateRange: [] }
+}
+
+// ==================== 报警等级 tag 类型 ====================
+function levelTagType(level) {
+  if (level === '严重') return 'danger'
+  if (level === '重要') return 'warning'
+  return '' // 一般 - 默认蓝色
+}
+
+// ==================== 模拟数据 ====================
+const alarmList = ref([
+  { alarmNo: 'BJ20260612001', deviceName: '沅陵桥水位传感器', deviceNo: 'DEV-YL-001', location: '沅陵大桥南侧', alarmTime: '2026-06-12 08:30:00', level: '严重', indicator: '水位', currentValue: 2.85, threshold: 2.0, status: '待研判', dailyAvg: 1.52, monthlyAvg: 1.35 },
+  { alarmNo: 'BJ20260612002', deviceName: '太常片区流量计', deviceNo: 'DEV-TC-002', location: '太常片区排水口', alarmTime: '2026-06-12 09:15:00', level: '重要', indicator: '流量', currentValue: 5.6, threshold: 4.0, status: '待研判', dailyAvg: 3.2, monthlyAvg: 2.8 },
+  { alarmNo: 'BJ20260612003', deviceName: '城南液位传感器', deviceNo: 'DEV-CN-003', location: '城南排水管网节点', alarmTime: '2026-06-12 10:00:00', level: '一般', indicator: '液位', currentValue: 0.8, threshold: 0.6, status: '已研判', dailyAvg: 0.45, monthlyAvg: 0.42 },
+  { alarmNo: 'BJ20260612004', deviceName: '河西水质监测仪', deviceNo: 'DEV-HX-004', location: '河西排水泵站', alarmTime: '2026-06-12 07:45:00', level: '严重', indicator: 'COD', currentValue: 120, threshold: 80, status: '待研判', dailyAvg: 55, monthlyAvg: 48 },
+  { alarmNo: 'BJ20260612005', deviceName: '北门流量计', deviceNo: 'DEV-BM-005', location: '北门排水口', alarmTime: '2026-06-12 11:30:00', level: '重要', indicator: '流量', currentValue: 4.8, threshold: 4.0, status: '已研判', dailyAvg: 2.9, monthlyAvg: 2.5 },
+  { alarmNo: 'BJ20260612006', deviceName: '东郊水位传感器', deviceNo: 'DEV-DJ-006', location: '东郊排水管网', alarmTime: '2026-06-11 22:10:00', level: '一般', indicator: '水位', currentValue: 1.8, threshold: 1.5, status: '待研判', dailyAvg: 1.1, monthlyAvg: 0.95 },
+  { alarmNo: 'BJ20260612007', deviceName: '中心区压力传感器', deviceNo: 'DEV-ZX-007', location: '中心区管网节点', alarmTime: '2026-06-11 18:20:00', level: '严重', indicator: '压力', currentValue: 0.85, threshold: 0.5, status: '待研判', dailyAvg: 0.32, monthlyAvg: 0.28 },
+  { alarmNo: 'BJ20260612008', deviceName: '沅水南岸流量计', deviceNo: 'DEV-YS-008', location: '沅水南岸排水口', alarmTime: '2026-06-11 15:40:00', level: '重要', indicator: '流量', currentValue: 6.2, threshold: 5.0, status: '已研判', dailyAvg: 3.8, monthlyAvg: 3.2 },
+  { alarmNo: 'BJ20260612009', deviceName: '西渡口液位传感器', deviceNo: 'DEV-XD-009', location: '西渡口排水节点', alarmTime: '2026-06-11 12:00:00', level: '一般', indicator: '液位', currentValue: 0.7, threshold: 0.6, status: '待研判', dailyAvg: 0.38, monthlyAvg: 0.35 },
+  { alarmNo: 'BJ20260612010', deviceName: '开发区水质监测仪', deviceNo: 'DEV-KF-010', location: '开发区排水管网', alarmTime: '2026-06-10 09:50:00', level: '重要', indicator: '氨氮', currentValue: 8.5, threshold: 5.0, status: '待研判', dailyAvg: 3.2, monthlyAvg: 2.8 }
+])
+
+const filteredData = computed(() => {
+  let list = alarmList.value
+  if (queryParams.value.deviceName) {
+    list = list.filter(item => item.deviceName.includes(queryParams.value.deviceName))
+  }
+  if (queryParams.value.level) {
+    list = list.filter(item => item.level === queryParams.value.level)
+  }
+  return list
+})
+
+// ==================== 研判分析弹窗 ====================
+const analysisVisible = ref(false)
+const currentAlarm = ref({})
+const chartPeriod = ref('24h')
+let chartInstance = null
+
+// 研判结论表单
+const conclusionForm = ref({
+  result: '',
+  desc: ''
+})
 
+// 上下游对比数据
+const upDownStreamData = ref([])
+
+function generateUpDownStream(alarm) {
+  const base = alarm.currentValue
+  return [
+    { name: alarm.deviceName + '(上游A)', direction: '上游', value: base * 0.72, threshold: alarm.threshold, status: '' },
+    { name: alarm.deviceName + '(上游B)', direction: '上游', value: base * 0.85, threshold: alarm.threshold, status: '' },
+    { name: alarm.deviceName + '(下游A)', direction: '下游', value: base * 1.1, threshold: alarm.threshold, status: '' },
+    { name: alarm.deviceName + '(下游B)', direction: '下游', value: base * 0.95, threshold: alarm.threshold, status: '' }
+  ]
+}
+
+// 生成曲线模拟数据
+function generateChartData(period) {
+  const threshold = currentAlarm.value.threshold || 2.0
+  let count = 0
+  let unit = ''
+  if (period === '24h') { count = 24; unit = '时' }
+  else if (period === '7d') { count = 7; unit = '日' }
+  else if (period === '15d') { count = 15; unit = '日' }
+  else { count = 30; unit = '日' }
+
+  const times = []
+  const values = []
+  const baseVal = currentAlarm.value.dailyAvg || 1.5
+  for (let i = 0; i < count; i++) {
+    if (period === '24h') {
+      times.push(i + unit)
+    } else {
+      times.push('第' + (i + 1) + unit)
+    }
+    // 模拟波动,最后几个点接近或超过阈值
+    const noise = (Math.random() - 0.5) * baseVal * 0.4
+    if (i >= count - 3) {
+      values.push(threshold + Math.random() * baseVal * 0.3)
+    } else {
+      values.push(Math.max(0, baseVal + noise))
+    }
+  }
+  return { times, values, threshold }
+}
+
+function openAnalysis(row) {
+  currentAlarm.value = { ...row }
+  upDownStreamData.value = generateUpDownStream(row)
+  conclusionForm.value = { result: '', desc: '' }
+  chartPeriod.value = '24h'
+  analysisVisible.value = true
+}
+
+function closeAnalysis() {
+  analysisVisible.value = false
+  if (chartInstance) {
+    chartInstance.dispose()
+    chartInstance = null
+  }
+}
+
+function onDialogOpened() {
+  nextTick(() => {
+    initChart()
+  })
+}
+
+function initChart() {
+  const container = document.getElementById('bjypChart')
+  if (!container) return
+  if (chartInstance) {
+    chartInstance.dispose()
+  }
+  chartInstance = echarts.init(container)
+  updateChart()
+}
+
+function updateChart() {
+  if (!chartInstance) return
+  const data = generateChartData(chartPeriod.value)
+  chartInstance.setOption({
+    tooltip: { trigger: 'axis' },
+    legend: { data: ['监测值', '阈值'], bottom: 0 },
+    grid: { left: '3%', right: '4%', top: '10%', bottom: '15%', containLabel: true },
+    xAxis: { type: 'category', data: data.times, boundaryGap: false },
+    yAxis: { type: 'value', name: currentAlarm.value.indicator || '值' },
+    series: [
+      {
+        name: '监测值',
+        type: 'line',
+        data: data.values,
+        smooth: true,
+        lineStyle: { color: '#409eff', width: 2 },
+        itemStyle: { color: '#409eff' },
+        areaStyle: { color: 'rgba(64,158,255,0.15)' }
+      },
+      {
+        name: '阈值',
+        type: 'line',
+        data: Array(data.times.length).fill(data.threshold),
+        lineStyle: { color: '#f56c6c', width: 2, type: 'dashed' },
+        itemStyle: { color: '#f56c6c' }
+      }
+    ]
+  })
+}
+
+function changeChartPeriod(period) {
+  chartPeriod.value = period
+  updateChart()
+}
+
+function submitConclusion() {
+  if (!conclusionForm.value.result) {
+    ElMessage.warning('请选择研判结果')
+    return
+  }
+  if (!conclusionForm.value.desc) {
+    ElMessage.warning('请输入研判说明')
+    return
+  }
+  // 更新状态
+  const idx = alarmList.value.findIndex(item => item.alarmNo === currentAlarm.value.alarmNo)
+  if (idx !== -1) {
+    alarmList.value[idx].status = '已研判'
+  }
+  ElMessage.success('研判提交成功')
+  closeAnalysis()
+}
 </script>
 
 <style scoped>
+.bjyp-page .search-form {
+  margin-bottom: 16px;
+}
+
+/* 弹窗内部样式 */
+.analysis-header {
+  margin-bottom: 16px;
+}
+
+.chart-title-bar {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 8px;
+}
+.chart-title {
+  font-size: 15px;
+  font-weight: bold;
+}
+.chart-tabs {
+  display: flex;
+  gap: 8px;
+}
+.chart-tab {
+  padding: 4px 12px;
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 13px;
+  color: #606266;
+  transition: all 0.2s;
+}
+.chart-tab.active {
+  background: #409eff;
+  color: #fff;
+  border-color: #409eff;
+}
+.chart-container {
+  width: 100%;
+  height: 280px;
+}
+
+.analysis-compare-section {
+  margin-top: 16px;
+}
+.compare-title {
+  font-size: 15px;
+  font-weight: bold;
+  margin-bottom: 12px;
+}
+.compare-content {
+  display: flex;
+  gap: 20px;
+}
+.compare-stats {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+  min-width: 120px;
+}
+.stat-box {
+  padding: 8px 16px;
+  border: 1px solid #e4e7ed;
+  border-radius: 4px;
+  text-align: center;
+}
+.stat-box .stat-label {
+  font-size: 12px;
+  color: #909399;
+  margin-bottom: 4px;
+}
+.stat-box .stat-value {
+  font-size: 18px;
+  font-weight: bold;
+  color: #303133;
+}
+.stat-box.highlight {
+  border-color: #f56c6c;
+  background: #fef0f0;
+}
+.stat-box.highlight .stat-value {
+  color: #f56c6c;
+}
+.compare-table-wrap {
+  flex: 1;
+}
 
-</style>
+.analysis-conclusion-section {
+  margin-top: 16px;
+  border-top: 1px solid #e4e7ed;
+  padding-top: 16px;
+}
+.conclusion-title {
+  font-size: 15px;
+  font-weight: bold;
+  margin-bottom: 0;
+}
+</style>

+ 845 - 2
src/views/subSystem/drainage/jcbj/dqbj.vue

@@ -1,13 +1,856 @@
 <template>
-  <div class="app-container">
-    <h2>当前报警</h2>
+  <div class="gis-page">
+    <!-- 左侧浮动面板 -->
+    <div class="side-panel">
+      <div class="panel-header">
+        <span class="panel-title">当前报警分布</span>
+      </div>
+
+      <!-- 等级筛选 -->
+      <div class="panel-section">
+        <div class="section-label">等级筛选</div>
+        <div class="layer-list">
+          <div class="layer-item">
+            <el-checkbox v-model="levelChecked.severe" @change="onLevelChange">
+              <span class="level-dot" style="background:#f56c6c"></span> 严重
+            </el-checkbox>
+          </div>
+          <div class="layer-item">
+            <el-checkbox v-model="levelChecked.important" @change="onLevelChange">
+              <span class="level-dot" style="background:#e6a23c"></span> 重要
+            </el-checkbox>
+          </div>
+          <div class="layer-item">
+            <el-checkbox v-model="levelChecked.normal" @change="onLevelChange">
+              <span class="level-dot" style="background:#409eff"></span> 一般
+            </el-checkbox>
+          </div>
+        </div>
+      </div>
+
+      <!-- 统计数据 -->
+      <div class="panel-section">
+        <div class="section-label">报警统计</div>
+        <div class="stats-row">
+          <div class="stat-card">
+            <div class="stat-value">{{ alarmList.length }}</div>
+            <div class="stat-text">总数</div>
+          </div>
+          <div class="stat-card stat-severe">
+            <div class="stat-value">{{ severeCount }}</div>
+            <div class="stat-text">严重</div>
+          </div>
+          <div class="stat-card stat-important">
+            <div class="stat-value">{{ importantCount }}</div>
+            <div class="stat-text">重要</div>
+          </div>
+          <div class="stat-card stat-normal">
+            <div class="stat-value">{{ normalCount }}</div>
+            <div class="stat-text">一般</div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 搜索框 -->
+      <div class="panel-section">
+        <div class="section-label">快速定位</div>
+        <el-input
+          v-model="searchKey"
+          placeholder="输入设备名称搜索"
+          clearable
+          prefix-icon="Search"
+          @input="onSearch"
+        />
+        <div class="search-results" v-if="searchResults.length > 0">
+          <div
+            class="search-item"
+            v-for="item in searchResults"
+            :key="item.id"
+            @click="locatePoint(item)"
+          >
+            <span class="level-dot" :style="{ background: getLevelColor(item.level) }"></span>
+            <span class="search-name">{{ item.name }}</span>
+            <span class="search-level" :class="getLevelClass(item.level)">{{ item.level }}</span>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 地图容器 -->
+    <div id="dqbjGISMap" class="map-container"></div>
+
+    <!-- 详情弹窗 -->
+    <el-dialog
+      v-model="detailVisible"
+      :title="currentItem.name"
+      width="620px"
+      :close-on-click-modal="false"
+      class="detail-dialog"
+      @opened="onDialogOpened"
+      @closed="onDialogClosed"
+    >
+      <div class="detail-content">
+        <!-- 基本信息 -->
+        <div class="detail-section">
+          <div class="section-title">基本信息</div>
+          <div class="detail-header">
+            <el-tag :type="getTagType(currentItem.level)" effect="dark" size="small">
+              {{ currentItem.level }}
+            </el-tag>
+            <span class="detail-name">{{ currentItem.name }}</span>
+          </div>
+          <div class="detail-info">
+            <div class="info-row">
+              <span class="info-label">设备编号</span>
+              <span class="info-value">{{ currentItem.deviceNo }}</span>
+            </div>
+            <div class="info-row">
+              <span class="info-label">报警时间</span>
+              <span class="info-value">{{ currentItem.time }}</span>
+            </div>
+            <div class="info-row">
+              <span class="info-label">报警类型</span>
+              <span class="info-value">{{ currentItem.type }}</span>
+            </div>
+            <div class="info-row">
+              <span class="info-label">当前值</span>
+              <span class="info-value highlight">{{ currentItem.value }}</span>
+            </div>
+            <div class="info-row">
+              <span class="info-label">阈值</span>
+              <span class="info-value">{{ currentItem.threshold }}</span>
+            </div>
+            <div class="info-row">
+              <span class="info-label">位置</span>
+              <span class="info-value">{{ currentItem.location }}</span>
+            </div>
+          </div>
+        </div>
+
+        <!-- 监测曲线 -->
+        <div class="detail-section">
+          <div class="section-title">监测曲线(近24小时)</div>
+          <div ref="chartRef" class="monitor-chart"></div>
+        </div>
+
+        <!-- 周边环境 -->
+        <div class="detail-section">
+          <div class="section-title">周边环境</div>
+          <div class="env-desc">{{ currentItem.envDesc }}</div>
+        </div>
+
+        <!-- 历史记录 -->
+        <div class="detail-section">
+          <div class="section-title">历史记录</div>
+          <el-timeline>
+            <el-timeline-item
+              v-for="(record, index) in currentItem.historyRecords"
+              :key="index"
+              :timestamp="record.time"
+              placement="top"
+              :type="getTagType(record.level)"
+            >
+              <div class="timeline-content">
+                <span class="timeline-name">{{ record.name }}</span>
+                <span class="timeline-type">{{ record.type }}</span>
+                <span class="timeline-value">{{ record.value }}</span>
+              </div>
+            </el-timeline-item>
+          </el-timeline>
+        </div>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
 <script setup name="Dqbj">
+import { ref, reactive, computed, onMounted, nextTick } from 'vue'
+import { Search } from '@element-plus/icons-vue'
+import * as echarts from 'echarts'
+import locationIcon from '@/assets/images/location.png'
+
+// ==================== 等级筛选 ====================
+const levelChecked = reactive({ severe: true, important: true, normal: true })
+
+// ==================== 模拟报警数据(12个点位)====================
+const alarmList = ref([
+  { id: 1, name: '沅陵大桥液位计', level: '严重', time: '2026-06-12 08:23:15', type: '液位超限', value: '3.82m', threshold: '2.50m', location: '沅陵大桥南侧排水口', deviceNo: 'WL-YW-001', envDesc: '城市主干道沅陵大桥下方,周边有居民区和商业区,车流量大,排水口位于桥南侧汇水区。', lng: 110.396, lat: 28.468, historyRecords: [
+    { time: '2026-06-12 08:23', name: '沅陵大桥液位计', level: '严重', type: '液位超限', value: '3.82m' },
+    { time: '2026-06-11 14:10', name: '沅陵大桥液位计', level: '重要', type: '液位超限', value: '2.80m' },
+    { time: '2026-06-10 20:45', name: '沅陵大桥液位计', level: '一般', type: '液位超限', value: '2.60m' },
+    { time: '2026-06-09 16:30', name: '沅陵大桥液位计', level: '重要', type: '流量异常', value: '920m³/h' },
+    { time: '2026-06-08 09:15', name: '沅陵大桥液位计', level: '一般', type: '液位超限', value: '2.55m' }
+  ]},
+  { id: 2, name: '太常路流量计', level: '重要', time: '2026-06-12 07:45:30', type: '流量异常', value: '1250m³/h', threshold: '800m³/h', location: '太常路与沅江路交汇', deviceNo: 'WL-LL-002', envDesc: '太常路与沅江路交叉口,周边为老旧居民区,排水管网老化,暴雨时易积水。', lng: 110.382, lat: 28.459, historyRecords: [
+    { time: '2026-06-12 07:45', name: '太常路流量计', level: '重要', type: '流量异常', value: '1250m³/h' },
+    { time: '2026-06-11 22:30', name: '太常路流量计', level: '一般', type: '流量异常', value: '860m³/h' },
+    { time: '2026-06-10 15:20', name: '太常路流量计', level: '严重', type: '流量异常', value: '1380m³/h' },
+    { time: '2026-06-09 11:50', name: '太常路流量计', level: '重要', type: '流量异常', value: '950m³/h' },
+    { time: '2026-06-08 08:10', name: '太常路流量计', level: '一般', type: '流量异常', value: '820m³/h' }
+  ]},
+  { id: 3, name: '沅江路液位计', level: '一般', time: '2026-06-12 09:10:05', type: '液位超限', value: '1.85m', threshold: '1.50m', location: '沅江路中段排水井', deviceNo: 'WL-YW-003', envDesc: '沅江路中段市政排水井,沿江景观带,周边有休闲广场和停车场。', lng: 110.403, lat: 28.447, historyRecords: [
+    { time: '2026-06-12 09:10', name: '沅江路液位计', level: '一般', type: '液位超限', value: '1.85m' },
+    { time: '2026-06-11 19:40', name: '沅江路液位计', level: '一般', type: '液位超限', value: '1.60m' },
+    { time: '2026-06-10 12:25', name: '沅江路液位计', level: '重要', type: '液位超限', value: '2.20m' },
+    { time: '2026-06-09 06:55', name: '沅江路液位计', level: '一般', type: '流量异常', value: '560m³/h' },
+    { time: '2026-06-08 14:30', name: '沅江路液位计', level: '一般', type: '液位超限', value: '1.55m' }
+  ]},
+  { id: 4, name: '龙舟路雨量计', level: '严重', time: '2026-06-12 06:30:22', type: '降雨预警', value: '85mm/h', threshold: '50mm/h', location: '龙舟路与建设路交汇', deviceNo: 'WL-YL-004', envDesc: '龙舟路低洼路段,周边有学校和居民区,降雨时路面汇水迅速,排水能力不足。', lng: 110.388, lat: 28.436, historyRecords: [
+    { time: '2026-06-12 06:30', name: '龙舟路雨量计', level: '严重', type: '降雨预警', value: '85mm/h' },
+    { time: '2026-06-11 17:15', name: '龙舟路雨量计', level: '重要', type: '降雨预警', value: '62mm/h' },
+    { time: '2026-06-10 21:40', name: '龙舟路雨量计', level: '严重', type: '降雨预警', value: '78mm/h' },
+    { time: '2026-06-09 13:20', name: '龙舟路雨量计', level: '一般', type: '降雨预警', value: '52mm/h' },
+    { time: '2026-06-08 05:50', name: '龙舟路雨量计', level: '重要', type: '降雨预警', value: '65mm/h' }
+  ]},
+  { id: 5, name: '城东泵站液位计', level: '重要', time: '2026-06-12 08:55:18', type: '液位超限', value: '4.12m', threshold: '3.00m', location: '城东开发区泵站', deviceNo: 'WL-YW-005', envDesc: '城东开发区核心泵站,周边为工业区和新建住宅区,排水压力较大。', lng: 110.412, lat: 28.461, historyRecords: [
+    { time: '2026-06-12 08:55', name: '城东泵站液位计', level: '重要', type: '液位超限', value: '4.12m' },
+    { time: '2026-06-11 23:10', name: '城东泵站液位计', level: '严重', type: '液位超限', value: '4.50m' },
+    { time: '2026-06-10 18:35', name: '城东泵站液位计', level: '重要', type: '液位超限', value: '3.80m' },
+    { time: '2026-06-09 10:25', name: '城东泵站液位计', level: '一般', type: '液位超限', value: '3.10m' },
+    { time: '2026-06-08 07:45', name: '城东泵站液位计', level: '重要', type: '流量异常', value: '1050m³/h' }
+  ]},
+  { id: 6, name: '一号泵站流量计', level: '一般', time: '2026-06-12 09:22:40', type: '流量异常', value: '680m³/h', threshold: '500m³/h', location: '沅陵大桥西侧泵站', deviceNo: 'WL-LL-006', envDesc: '沅陵大桥西侧主排水泵站,靠近沅水河岸,承担主城区防洪排涝任务。', lng: 110.391, lat: 28.465, historyRecords: [
+    { time: '2026-06-12 09:22', name: '一号泵站流量计', level: '一般', type: '流量异常', value: '680m³/h' },
+    { time: '2026-06-11 16:50', name: '一号泵站流量计', level: '一般', type: '流量异常', value: '540m³/h' },
+    { time: '2026-06-10 14:15', name: '一号泵站流量计', level: '重要', type: '流量异常', value: '780m³/h' },
+    { time: '2026-06-09 09:30', name: '一号泵站流量计', level: '一般', type: '流量异常', value: '520m³/h' },
+    { time: '2026-06-08 20:40', name: '一号泵站流量计', level: '重要', type: '流量异常', value: '720m³/h' }
+  ]},
+  { id: 7, name: '二号泵站液位计', level: '严重', time: '2026-06-12 05:18:33', type: '液位超限', value: '5.20m', threshold: '3.00m', location: '太常片区排水泵站', deviceNo: 'WL-YW-007', envDesc: '太常片区主要排水泵站,地势低洼,暴雨时周边积水严重,影响片区居民出行。', lng: 110.378, lat: 28.451, historyRecords: [
+    { time: '2026-06-12 05:18', name: '二号泵站液位计', level: '严重', type: '液位超限', value: '5.20m' },
+    { time: '2026-06-11 12:40', name: '二号泵站液位计', level: '严重', type: '液位超限', value: '4.80m' },
+    { time: '2026-06-10 07:25', name: '二号泵站液位计', level: '重要', type: '液位超限', value: '3.60m' },
+    { time: '2026-06-09 22:10', name: '二号泵站液位计', level: '严重', type: '液位超限', value: '5.05m' },
+    { time: '2026-06-08 15:55', name: '二号泵站液位计', level: '重要', type: '液位超限', value: '3.90m' }
+  ]},
+  { id: 8, name: '沅水南岸流量计', level: '重要', time: '2026-06-12 07:32:10', type: '流量异常', value: '920m³/h', threshold: '600m³/h', location: '沅水南岸排水口', deviceNo: 'WL-LL-008', envDesc: '沅水南岸排水出口,连接城区主排水管网,雨季时排水量激增。', lng: 110.368, lat: 28.448, historyRecords: [
+    { time: '2026-06-12 07:32', name: '沅水南岸流量计', level: '重要', type: '流量异常', value: '920m³/h' },
+    { time: '2026-06-11 21:15', name: '沅水南岸流量计', level: '一般', type: '流量异常', value: '640m³/h' },
+    { time: '2026-06-10 16:45', name: '沅水南岸流量计', level: '重要', type: '流量异常', value: '880m³/h' },
+    { time: '2026-06-09 11:30', name: '沅水南岸流量计', level: '重要', type: '流量异常', value: '750m³/h' },
+    { time: '2026-06-08 08:20', name: '沅水南岸流量计', level: '一般', type: '流量异常', value: '610m³/h' }
+  ]},
+  { id: 9, name: '建设路液位计', level: '一般', time: '2026-06-12 09:40:55', type: '液位超限', value: '2.10m', threshold: '1.80m', location: '建设路下穿通道', deviceNo: 'WL-YW-009', envDesc: '建设路下穿通道积水监测点,通道较深,暴雨时积水速度快,需及时抽排。', lng: 110.393, lat: 28.441, historyRecords: [
+    { time: '2026-06-12 09:40', name: '建设路液位计', level: '一般', type: '液位超限', value: '2.10m' },
+    { time: '2026-06-11 18:25', name: '建设路液位计', level: '一般', type: '液位超限', value: '1.90m' },
+    { time: '2026-06-10 13:50', name: '建设路液位计', level: '重要', type: '液位超限', value: '2.50m' },
+    { time: '2026-06-09 08:15', name: '建设路液位计', level: '一般', type: '液位超限', value: '1.85m' },
+    { time: '2026-06-08 22:30', name: '建设路液位计', level: '重要', type: '液位超限', value: '2.30m' }
+  ]},
+  { id: 10, name: '城东下穿液位计', level: '严重', time: '2026-06-12 06:05:28', type: '液位超限', value: '3.95m', threshold: '2.00m', location: '城东立交桥下', deviceNo: 'WL-YW-010', envDesc: '城东立交桥下穿路段,路面最低点积水严重,周边为城市主干道和居民区。', lng: 110.415, lat: 28.457, historyRecords: [
+    { time: '2026-06-12 06:05', name: '城东下穿液位计', level: '严重', type: '液位超限', value: '3.95m' },
+    { time: '2026-06-11 15:30', name: '城东下穿液位计', level: '严重', type: '液位超限', value: '3.50m' },
+    { time: '2026-06-10 10:20', name: '城东下穿液位计', level: '重要', type: '液位超限', value: '2.60m' },
+    { time: '2026-06-09 04:45', name: '城东下穿液位计', level: '严重', type: '液位超限', value: '3.80m' },
+    { time: '2026-06-08 19:10', name: '城东下穿液位计', level: '重要', type: '液位超限', value: '2.40m' }
+  ]},
+  { id: 11, name: '主城区管网流量计', level: '重要', time: '2026-06-12 08:12:45', type: '流量异常', value: '1100m³/h', threshold: '700m³/h', location: '主城区排水干管节点', deviceNo: 'WL-LL-011', envDesc: '主城区排水干管关键节点,连接多个支管,周边为商业中心和行政办公区。', lng: 110.390, lat: 28.460, historyRecords: [
+    { time: '2026-06-12 08:12', name: '主城区管网流量计', level: '重要', type: '流量异常', value: '1100m³/h' },
+    { time: '2026-06-11 20:55', name: '主城区管网流量计', level: '一般', type: '流量异常', value: '740m³/h' },
+    { time: '2026-06-10 14:40', name: '主城区管网流量计', level: '重要', type: '流量异常', value: '950m³/h' },
+    { time: '2026-06-09 09:15', name: '主城区管网流量计', level: '一般', type: '流量异常', value: '720m³/h' },
+    { time: '2026-06-08 17:30', name: '主城区管网流量计', level: '重要', type: '流量异常', value: '880m³/h' }
+  ]},
+  { id: 12, name: '沅江路东段液位计', level: '一般', time: '2026-06-12 09:55:12', type: '液位超限', value: '1.62m', threshold: '1.50m', location: '沅江路东段检测井', deviceNo: 'WL-YW-012', envDesc: '沅江路东段市政排水检测井,靠近河堤,雨季时受河水顶托影响排水。', lng: 110.407, lat: 28.455, historyRecords: [
+    { time: '2026-06-12 09:55', name: '沅江路东段液位计', level: '一般', type: '液位超限', value: '1.62m' },
+    { time: '2026-06-11 17:40', name: '沅江路东段液位计', level: '一般', type: '液位超限', value: '1.55m' },
+    { time: '2026-06-10 12:10', name: '沅江路东段液位计', level: '重要', type: '液位超限', value: '1.90m' },
+    { time: '2026-06-09 07:35', name: '沅江路东段液位计', level: '一般', type: '液位超限', value: '1.52m' },
+    { time: '2026-06-08 21:50', name: '沅江路东段液位计', level: '一般', type: '流量异常', value: '510m³/h' }
+  ]}
+])
+
+// ==================== 统计计算 ====================
+const severeCount = computed(() => alarmList.value.filter(v => v.level === '严重').length)
+const importantCount = computed(() => alarmList.value.filter(v => v.level === '重要').length)
+const normalCount = computed(() => alarmList.value.filter(v => v.level === '一般').length)
+
+// ==================== 工具方法 ====================
+function getLevelColor(level) {
+  const map = { '严重': '#f56c6c', '重要': '#e6a23c', '一般': '#409eff' }
+  return map[level] || '#409eff'
+}
+
+function getLevelClass(level) {
+  const map = { '严重': 'level-severe', '重要': 'level-important', '一般': 'level-normal' }
+  return map[level] || ''
+}
+
+function getTagType(level) {
+  const map = { '严重': 'danger', '重要': 'warning', '一般': '' }
+  return map[level] || ''
+}
+
+function getLevelKey(level) {
+  const map = { '严重': 'severe', '重要': 'important', '一般': 'normal' }
+  return map[level] || 'normal'
+}
+
+// ==================== 搜索功能 ====================
+const searchKey = ref('')
+const searchResults = ref([])
+
+function onSearch() {
+  if (!searchKey.value.trim()) {
+    searchResults.value = []
+    return
+  }
+  const key = searchKey.value.trim().toLowerCase()
+  searchResults.value = alarmList.value.filter(v =>
+    v.name.toLowerCase().includes(key) || v.location.toLowerCase().includes(key)
+  )
+}
+
+function locatePoint(item) {
+  if (mapInstance) {
+    mapInstance.centerAndZoom(new BMapGL.Point(item.lng, item.lat), 17)
+    openDetail(item)
+  }
+  searchResults.value = []
+  searchKey.value = ''
+}
+
+// ==================== 地图相关 ====================
+let mapInstance = null
+const markerMap = new Map()
+
+function initMap() {
+  const container = document.getElementById('dqbjGISMap')
+  if (!container || typeof BMapGL === 'undefined') {
+    console.warn('BMapGL 未加载或容器不存在')
+    return
+  }
+
+  try {
+    mapInstance = new BMapGL.Map('dqbjGISMap')
+    const centerPoint = new BMapGL.Point(110.393, 28.452)
+    mapInstance.centerAndZoom(centerPoint, 15)
+    mapInstance.enableScrollWheelZoom(true)
+
+    alarmList.value.forEach(point => {
+      addMapMarker(point)
+    })
+  } catch (e) {
+    console.error('百度地图初始化失败:', e)
+  }
+}
+
+function addMapMarker(point) {
+  const color = getLevelColor(point.level)
+  const bPoint = new BMapGL.Point(point.lng, point.lat)
+
+  const html = `<div style="text-align:center;cursor:pointer;">
+    <div style="color:#fff;background:${color};border-radius:4px;padding:2px 8px;font-size:11px;white-space:nowrap;display:inline-block;margin-bottom:2px;box-shadow:0 1px 4px rgba(0,0,0,0.3);">
+      ${point.name}
+    </div>
+    <div><img src="${locationIcon}" style="width:28px;height:28px;display:block;margin:0 auto;"/></div>
+  </div>`
 
+  const label = new BMapGL.Label(html, {
+    position: bPoint,
+    offset: new BMapGL.Size(-30, -50)
+  })
+  label.setStyle({
+    border: 'none',
+    background: 'transparent',
+    padding: '0',
+    zIndex: '10'
+  })
+  mapInstance.addOverlay(label)
+  markerMap.set(point.id, label)
+
+  label.addEventListener('click', function () {
+    openDetail(point)
+  })
+}
+
+// ==================== 等级筛选 ====================
+function onLevelChange() {
+  alarmList.value.forEach(point => {
+    const label = markerMap.get(point.id)
+    if (!label) return
+    const key = getLevelKey(point.level)
+    const visible = levelChecked[key]
+    label.show()
+    if (!visible) {
+      label.hide()
+    }
+  })
+}
+
+// ==================== 详情弹窗 ====================
+const detailVisible = ref(false)
+const currentItem = ref({})
+const chartRef = ref(null)
+let chartInstance = null
+
+function openDetail(point) {
+  currentItem.value = { ...point }
+  detailVisible.value = true
+}
+
+function onDialogOpened() {
+  nextTick(() => {
+    initChart()
+  })
+}
+
+function onDialogClosed() {
+  if (chartInstance) {
+    chartInstance.dispose()
+    chartInstance = null
+  }
+}
+
+// ==================== ECharts 监测曲线 ====================
+function initChart() {
+  if (!chartRef.value) return
+  chartInstance = echarts.init(chartRef.value)
+
+  // 生成近24小时模拟数据
+  const hours = []
+  const values = []
+  const now = new Date()
+  const thresholdVal = parseFloat(currentItem.value.threshold) || 2.5
+  const currentVal = parseFloat(currentItem.value) || 0
+  const isLevel = currentItem.type === '液位超限' || currentItem.type === '降雨预警'
+
+  for (let i = 23; i >= 0; i--) {
+    const h = new Date(now.getTime() - i * 3600000)
+    hours.push(h.getHours() + ':00')
+    // 模拟波动数据
+    const base = isLevel ? thresholdVal * 0.6 : thresholdVal * 0.5
+    const peak = isLevel ? currentVal : currentVal * 0.9
+    const noise = (Math.random() - 0.5) * base * 0.3
+    const val = i < 6 ? peak - (6 - i) * (peak - base) / 8 + noise : base + noise + (i > 16 ? (i - 16) * (peak - base) / 8 : 0)
+    values.push(Math.max(0, parseFloat(val.toFixed(2))))
+  }
+
+  const unit = isLevel ? 'm' : 'm³/h'
+  const thresholdLine = Array(24).fill(thresholdVal)
+
+  chartInstance.setOption({
+    grid: { top: 30, right: 20, bottom: 30, left: 50 },
+    tooltip: {
+      trigger: 'axis',
+      formatter: (params) => {
+        let s = params[0].axisValue + '<br/>'
+        params.forEach(p => {
+          s += `<span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:${p.color};margin-right:4px;"></span>${p.seriesName}: ${p.value}${unit}<br/>`
+        })
+        return s
+      }
+    },
+    xAxis: {
+      type: 'category',
+      data: hours,
+      axisLabel: { fontSize: 10, color: '#999' },
+      axisLine: { lineStyle: { color: '#ddd' } }
+    },
+    yAxis: {
+      type: 'value',
+      axisLabel: { fontSize: 10, color: '#999', formatter: '{value}' + unit },
+      splitLine: { lineStyle: { color: '#f0f0f0' } }
+    },
+    series: [
+      {
+        name: isLevel ? '液位' : '流量',
+        type: 'line',
+        data: values,
+        smooth: true,
+        lineStyle: { color: '#409eff', width: 2 },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: 'rgba(64,158,255,0.3)' },
+            { offset: 1, color: 'rgba(64,158,255,0.02)' }
+          ])
+        },
+        itemStyle: { color: '#409eff' },
+        symbol: 'circle',
+        symbolSize: 4
+      },
+      {
+        name: '阈值',
+        type: 'line',
+        data: thresholdLine,
+        lineStyle: { color: '#f56c6c', width: 1, type: 'dashed' },
+        itemStyle: { color: '#f56c6c' },
+        symbol: 'none'
+      }
+    ]
+  })
+}
+
+// ==================== 生命周期 ====================
+onMounted(() => {
+  nextTick(() => {
+    initMap()
+  })
+})
 </script>
 
 <style scoped>
+.gis-page {
+  position: relative;
+  width: 100%;
+  height: 100vh;
+  overflow: hidden;
+}
+
+.map-container {
+  width: 100%;
+  height: 100%;
+}
+
+/* ==================== 左侧浮动面板 ==================== */
+.side-panel {
+  position: absolute;
+  top: 16px;
+  left: 16px;
+  width: 280px;
+  background: rgba(6, 30, 65, 0.85);
+  border-radius: 8px;
+  z-index: 100;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
+  padding: 0;
+  max-height: calc(100% - 32px);
+  overflow-y: auto;
+}
+
+.side-panel::-webkit-scrollbar {
+  width: 4px;
+}
+
+.side-panel::-webkit-scrollbar-thumb {
+  background: rgba(255, 255, 255, 0.2);
+  border-radius: 2px;
+}
+
+.panel-header {
+  padding: 16px 18px 12px;
+  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+}
+
+.panel-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #fff;
+  letter-spacing: 1px;
+}
+
+.panel-section {
+  padding: 14px 18px;
+  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
+}
+
+.panel-section:last-child {
+  border-bottom: none;
+}
+
+.section-label {
+  font-size: 12px;
+  color: rgba(255, 255, 255, 0.5);
+  margin-bottom: 10px;
+  text-transform: uppercase;
+  letter-spacing: 1px;
+}
+
+/* 等级列表 */
+.layer-list {
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+}
+
+.layer-item :deep(.el-checkbox) {
+  color: rgba(255, 255, 255, 0.85);
+  height: auto;
+}
+
+.layer-item :deep(.el-checkbox__label) {
+  color: rgba(255, 255, 255, 0.85);
+  font-size: 13px;
+  display: inline-flex;
+  align-items: center;
+  gap: 4px;
+}
+
+.layer-item :deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
+  background: #409eff;
+  border-color: #409eff;
+}
+
+.level-dot {
+  display: inline-block;
+  width: 10px;
+  height: 10px;
+  border-radius: 50%;
+  margin-right: 2px;
+  vertical-align: middle;
+}
+
+/* 统计卡片 */
+.stats-row {
+  display: flex;
+  gap: 8px;
+}
+
+.stat-card {
+  flex: 1;
+  background: rgba(255, 255, 255, 0.08);
+  border-radius: 6px;
+  padding: 10px 4px;
+  text-align: center;
+}
+
+.stat-card.stat-severe {
+  background: rgba(245, 108, 108, 0.15);
+}
+
+.stat-card.stat-important {
+  background: rgba(230, 162, 60, 0.15);
+}
+
+.stat-card.stat-normal {
+  background: rgba(64, 158, 255, 0.15);
+}
+
+.stat-value {
+  font-size: 20px;
+  font-weight: 700;
+  color: #fff;
+  line-height: 1.2;
+}
+
+.stat-severe .stat-value {
+  color: #f56c6c;
+}
+
+.stat-important .stat-value {
+  color: #e6a23c;
+}
+
+.stat-normal .stat-value {
+  color: #409eff;
+}
+
+.stat-text {
+  font-size: 11px;
+  color: rgba(255, 255, 255, 0.5);
+  margin-top: 4px;
+}
+
+/* 搜索 */
+.side-panel :deep(.el-input__wrapper) {
+  background: rgba(255, 255, 255, 0.08);
+  box-shadow: none;
+  border: 1px solid rgba(255, 255, 255, 0.12);
+}
+
+.side-panel :deep(.el-input__inner) {
+  color: #fff;
+  font-size: 13px;
+}
+
+.side-panel :deep(.el-input__inner::placeholder) {
+  color: rgba(255, 255, 255, 0.35);
+}
+
+.side-panel :deep(.el-input__prefix .el-icon) {
+  color: rgba(255, 255, 255, 0.4);
+}
+
+.search-results {
+  margin-top: 8px;
+  max-height: 200px;
+  overflow-y: auto;
+  border-radius: 4px;
+}
+
+.search-results::-webkit-scrollbar {
+  width: 3px;
+}
+
+.search-results::-webkit-scrollbar-thumb {
+  background: rgba(255, 255, 255, 0.15);
+  border-radius: 2px;
+}
+
+.search-item {
+  display: flex;
+  align-items: center;
+  padding: 7px 10px;
+  cursor: pointer;
+  border-radius: 4px;
+  transition: background 0.15s;
+}
+
+.search-item:hover {
+  background: rgba(255, 255, 255, 0.1);
+}
+
+.search-name {
+  flex: 1;
+  font-size: 12px;
+  color: rgba(255, 255, 255, 0.85);
+  margin-left: 6px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.search-level {
+  font-size: 11px;
+  padding: 1px 6px;
+  border-radius: 3px;
+}
+
+.search-level.level-severe {
+  color: #f56c6c;
+  background: rgba(245, 108, 108, 0.15);
+}
+
+.search-level.level-important {
+  color: #e6a23c;
+  background: rgba(230, 162, 60, 0.15);
+}
+
+.search-level.level-normal {
+  color: #409eff;
+  background: rgba(64, 158, 255, 0.15);
+}
+
+/* ==================== 详情弹窗样式 ==================== */
+.detail-dialog :deep(.el-dialog) {
+  border-radius: 10px;
+  overflow: hidden;
+}
+
+.detail-dialog :deep(.el-dialog__header) {
+  background: #1a1a2e;
+  padding: 14px 20px;
+  margin: 0;
+}
+
+.detail-dialog :deep(.el-dialog__title) {
+  color: #fff;
+  font-size: 15px;
+}
+
+.detail-dialog :deep(.el-dialog__headerbtn .el-dialog__close) {
+  color: rgba(255, 255, 255, 0.6);
+}
+
+.detail-dialog :deep(.el-dialog__body) {
+  padding: 0;
+  max-height: 70vh;
+  overflow-y: auto;
+}
+
+.detail-content {
+  padding: 20px;
+}
+
+.detail-section {
+  margin-bottom: 20px;
+}
+
+.detail-section:last-child {
+  margin-bottom: 0;
+}
+
+.section-title {
+  font-size: 14px;
+  font-weight: 600;
+  color: #303133;
+  margin-bottom: 12px;
+  padding-left: 10px;
+  border-left: 3px solid #409eff;
+}
+
+.detail-header {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  margin-bottom: 12px;
+}
+
+.detail-name {
+  font-size: 15px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.detail-info {
+  display: flex;
+  flex-direction: column;
+}
+
+.info-row {
+  display: flex;
+  align-items: center;
+  padding: 8px 0;
+  border-bottom: 1px solid #f5f5f5;
+}
+
+.info-row:last-child {
+  border-bottom: none;
+}
+
+.info-label {
+  width: 80px;
+  flex-shrink: 0;
+  font-size: 13px;
+  color: #909399;
+}
+
+.info-value {
+  font-size: 13px;
+  color: #303133;
+}
+
+.info-value.highlight {
+  color: #f56c6c;
+  font-weight: 600;
+}
+
+/* 监测曲线 */
+.monitor-chart {
+  width: 100%;
+  height: 240px;
+}
+
+/* 周边环境 */
+.env-desc {
+  font-size: 13px;
+  color: #606266;
+  line-height: 1.8;
+  background: #f8f9fa;
+  padding: 12px 16px;
+  border-radius: 6px;
+}
+
+/* 历史记录时间线 */
+.timeline-content {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.timeline-name {
+  font-size: 13px;
+  color: #303133;
+  font-weight: 500;
+}
+
+.timeline-type {
+  font-size: 12px;
+  color: #909399;
+}
 
+.timeline-value {
+  font-size: 12px;
+  color: #f56c6c;
+  font-weight: 500;
+}
 </style>

+ 661 - 2
src/views/subSystem/drainage/jcyj/dqyjgl.vue

@@ -1,13 +1,672 @@
 <template>
-  <div class="app-container">
-    <h2>当前预警管理</h2>
+  <div class="app-container dqyjgl-page">
+    <!-- ==================== 顶部统计区 ==================== -->
+    <div class="stats-panel">
+      <!-- 左侧4个统计指标 -->
+      <div class="stats-left">
+        <div class="stat-item" v-for="item in statItems" :key="item.label">
+          <div class="stat-value" :class="item.cls">{{ item.value }}</div>
+          <div class="stat-label">{{ item.label }}</div>
+        </div>
+      </div>
+      <!-- 中间3个环形图 -->
+      <div class="stats-center">
+        <div class="ring-chart-box" v-for="chart in ringCharts" :key="chart.title">
+          <div :id="chart.id" class="ring-chart"></div>
+          <div class="ring-legend">
+            <div class="legend-row" v-for="(leg, i) in chart.legends.value" :key="i">
+              <span class="legend-dot" :style="{ background: leg.color }"></span>
+              <span class="legend-text">{{ leg.name }}</span>
+              <span class="legend-num">{{ leg.value }}</span>
+            </div>
+          </div>
+        </div>
+      </div>
+      <!-- 右侧数字指标 -->
+      <div class="stats-right">
+        <div class="right-metric">
+          <el-icon class="metric-icon"><Promotion /></el-icon>
+          <span class="metric-num">212</span>
+        </div>
+        <div class="right-metric">
+          <el-icon class="metric-icon orange"><Promotion /></el-icon>
+          <span class="metric-num">128</span>
+        </div>
+      </div>
+    </div>
+
+    <!-- ==================== 筛选区 ==================== -->
+    <div class="filter-panel">
+      <div class="filter-tabs">
+        <div
+          class="tab-item"
+          :class="{ active: activeTab === tab }"
+          v-for="tab in tabs"
+          :key="tab"
+          @click="activeTab = tab"
+        >{{ tab }}</div>
+      </div>
+      <div class="filter-form">
+        <el-input v-model="filters.deviceId" placeholder="设备ID" clearable style="width:150px" />
+        <el-date-picker
+          v-model="filters.dateRange"
+          type="daterange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          style="width:260px"
+          value-format="YYYY-MM-DD"
+        />
+        <el-select v-model="filters.level" placeholder="选择工单级别" clearable style="width:150px">
+          <el-option label="一般" value="一般" />
+          <el-option label="重要" value="重要" />
+          <el-option label="紧急" value="紧急" />
+        </el-select>
+        <el-select v-model="filters.type" placeholder="选择工单类型" clearable style="width:150px">
+          <el-option label="管线" value="管线" />
+          <el-option label="管点" value="管点" />
+          <el-option label="窨井" value="窨井" />
+          <el-option label="管网" value="管网" />
+        </el-select>
+      </div>
+    </div>
+
+    <!-- ==================== 数据表格 ==================== -->
+    <div class="table-panel">
+      <el-table :data="filteredList" border stripe style="width:100%">
+        <el-table-column prop="orderId" label="工单号" width="130" />
+        <el-table-column prop="status" label="工单状态" width="100">
+          <template #default="{ row }">
+            <el-tag :type="statusTagType(row.status)" size="small">{{ row.status }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="priority" label="优先级" width="90">
+          <template #default="{ row }">
+            <span :class="'priority-' + row.priority">{{ row.priority }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="orderType" label="工单类型" width="90" />
+        <el-table-column prop="description" label="故障描述" show-overflow-tooltip />
+        <el-table-column prop="createTime" label="创建时间" width="130" />
+        <el-table-column prop="approvalTime" label="审批时间" width="130" />
+        <el-table-column label="操作" width="80" fixed="right">
+          <template #default="{ row }">
+            <el-button type="primary" link size="small" @click="handleView(row)">查看</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+
+    <!-- ==================== 查看详情弹窗 ==================== -->
+    <el-dialog v-model="detailVisible" title="工单派发" width="650px" :close-on-click-modal="false">
+      <div class="detail-content" v-if="currentOrder">
+        <!-- 进度条 -->
+        <div class="steps-wrap">
+          <el-steps :active="currentOrder.step" align-center>
+            <el-step title="创建" :description="currentOrder.stepInfo[0]" />
+            <el-step title="领导审批" :description="currentOrder.stepInfo[1]" />
+            <el-step title="管理员审批" :description="currentOrder.stepInfo[2]" />
+            <el-step title="完成" :description="currentOrder.stepInfo[3]" />
+          </el-steps>
+        </div>
+
+        <!-- 申请内容 -->
+        <div class="section-box">
+          <div class="section-title">申请内容</div>
+          <el-form label-width="90px" size="default">
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-form-item label="角色名称">{{ currentOrder.roleName }}</el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="角色编码">{{ currentOrder.roleCode }}</el-form-item>
+              </el-col>
+            </el-row>
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-form-item label="所属部门">{{ currentOrder.department }}</el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="过期时间">{{ currentOrder.expireTime }}</el-form-item>
+              </el-col>
+            </el-row>
+            <el-form-item label="描述">
+              <div class="desc-text">{{ currentOrder.description }}</div>
+            </el-form-item>
+          </el-form>
+        </div>
+
+        <!-- 人员安排 -->
+        <div class="section-box">
+          <div class="section-title">
+            人员安排
+            <el-button type="primary" size="small" style="float:right" @click="showAddPersonDialog">添加</el-button>
+          </div>
+          <el-table :data="currentOrder.personnel" border size="small" style="width:100%">
+            <el-table-column prop="name" label="姓名" />
+            <el-table-column prop="phone" label="联系方式" />
+            <el-table-column prop="dept" label="部门" />
+            <el-table-column label="操作" width="80" align="center">
+              <template #default="{ $index }">
+                <el-button link type="danger" size="small" @click="currentOrder.personnel.splice($index, 1)">删除</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+      </div>
+
+      <template #footer>
+        <el-button type="primary" @click="handleApprove(true)">审批通过</el-button>
+        <el-button @click="handleApprove(false)">审批不通过</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 添加人员弹窗 -->
+    <el-dialog v-model="addPersonVisible" title="添加人员" width="450px">
+      <el-form :model="personForm" :rules="personRules" ref="personFormRef" label-width="80px">
+        <el-form-item label="姓名" prop="name">
+          <el-input v-model="personForm.name" placeholder="请输入姓名" />
+        </el-form-item>
+        <el-form-item label="联系方式" prop="phone">
+          <el-input v-model="personForm.phone" placeholder="请输入联系方式" />
+        </el-form-item>
+        <el-form-item label="部门" prop="dept">
+          <el-input v-model="personForm.dept" placeholder="请输入部门" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="addPersonVisible = false">取 消</el-button>
+        <el-button type="primary" @click="handleAddPersonConfirm">确 定</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup name="Dqyjgl">
+import { ref, computed, onMounted, nextTick } from 'vue'
+import { Promotion } from '@element-plus/icons-vue'
+import * as echarts from 'echarts'
+
+// ==================== 模拟数据 ====================
+const orderList = ref([
+  {
+    orderId: '110110101', status: '处理中', priority: '紧急', orderType: '管线',
+    description: '人民路DN300供水管线破裂,导致周边区域停水', createTime: '2025-06-10', approvalTime: '',
+    step: 1, stepInfo: ['张三 2025-06-10 08:30', '', '', ''],
+    roleName: '管线巡检员', roleCode: 'GXXJ001', department: '管网维护部', expireTime: '2025-12-31',
+    personnel: [{ name: '李明', phone: '13800138001', dept: '管网维护部' }]
+  },
+  {
+    orderId: '110110102', status: '待审批', priority: '重要', orderType: '管点',
+    description: '建设路管点水位传感器数据异常,持续报警', createTime: '2025-06-11', approvalTime: '',
+    step: 2, stepInfo: ['王五 2025-06-11 09:15', '赵六 2025-06-11 10:30', '', ''],
+    roleName: '设备检修员', roleCode: 'SBXJ002', department: '设备运维部', expireTime: '2025-12-31',
+    personnel: [{ name: '孙七', phone: '13900139002', dept: '设备运维部' }]
+  },
+  {
+    orderId: '110110103', status: '已审批', priority: '一般', orderType: '窨井',
+    description: '幸福路窨井盖缺失,存在安全隐患', createTime: '2025-06-08', approvalTime: '2025-06-09',
+    step: 4, stepInfo: ['周八 2025-06-08 14:00', '吴九 2025-06-08 16:00', '郑十 2025-06-09 09:00', '2025-06-09 10:00'],
+    roleName: '安全巡查员', roleCode: 'AQXC003', department: '安全管理部', expireTime: '2026-06-30',
+    personnel: [{ name: '刘一', phone: '13700137003', dept: '安全管理部' }]
+  },
+  {
+    orderId: '110110104', status: '处理中', priority: '紧急', orderType: '管网',
+    description: '科技路DN500排水管网堵塞,污水外溢严重', createTime: '2025-06-12', approvalTime: '',
+    step: 1, stepInfo: ['陈二 2025-06-12 07:45', '', '', ''],
+    roleName: '管网清淤员', roleCode: 'GWQY004', department: '管网维护部', expireTime: '2025-12-31',
+    personnel: [{ name: '张三', phone: '13600136004', dept: '管网维护部' }]
+  },
+  {
+    orderId: '110110105', status: '待审批', priority: '重要', orderType: '管线',
+    description: '和平路燃气管线与排水管线交叉处存在渗漏', createTime: '2025-06-11', approvalTime: '',
+    step: 2, stepInfo: ['李四 2025-06-11 11:20', '王五 2025-06-11 14:00', '', ''],
+    roleName: '管线检测员', roleCode: 'GXJC005', department: '管线检测中心', expireTime: '2026-01-15',
+    personnel: [{ name: '赵六', phone: '13500135005', dept: '管线检测中心' }]
+  },
+  {
+    orderId: '110110106', status: '已审批', priority: '一般', orderType: '管点',
+    description: '长江路管点流量计读数偏差较大需校准', createTime: '2025-06-07', approvalTime: '2025-06-08',
+    step: 4, stepInfo: ['孙七 2025-06-07 10:00', '周八 2025-06-07 15:30', '吴九 2025-06-08 08:00', '2025-06-08 11:00'],
+    roleName: '仪表校准员', roleCode: 'YBJZ006', department: '设备运维部', expireTime: '2026-03-31',
+    personnel: [{ name: '郑十', phone: '13400134006', dept: '设备运维部' }]
+  },
+  {
+    orderId: '110110107', status: '处理中', priority: '重要', orderType: '窨井',
+    description: '黄河路窨井内积水严重,排水不畅', createTime: '2025-06-12', approvalTime: '',
+    step: 1, stepInfo: ['刘一 2025-06-12 06:30', '', '', ''],
+    roleName: '排水巡查员', roleCode: 'PSXC007', department: '排水管理部', expireTime: '2025-12-31',
+    personnel: [{ name: '陈二', phone: '13300133007', dept: '排水管理部' }]
+  },
+  {
+    orderId: '110110108', status: '待审批', priority: '紧急', orderType: '管网',
+    description: '太湖路排水管网节点压力异常,疑似管网变形', createTime: '2025-06-12', approvalTime: '',
+    step: 2, stepInfo: ['张三 2025-06-12 09:00', '李四 2025-06-12 10:15', '', ''],
+    roleName: '管网检测员', roleCode: 'GWJC008', department: '管网维护部', expireTime: '2025-12-31',
+    personnel: [{ name: '王五', phone: '13200132008', dept: '管网维护部' }]
+  },
+  {
+    orderId: '110110109', status: '已审批', priority: '一般', orderType: '管线',
+    description: '珠江路供水管线阀门锈蚀需更换', createTime: '2025-06-05', approvalTime: '2025-06-06',
+    step: 4, stepInfo: ['赵六 2025-06-05 08:00', '孙七 2025-06-05 11:00', '周八 2025-06-06 09:30', '2025-06-06 14:00'],
+    roleName: '阀门维护员', roleCode: 'FMWH009', department: '管网维护部', expireTime: '2026-06-30',
+    personnel: [{ name: '吴九', phone: '13100131009', dept: '管网维护部' }]
+  },
+  {
+    orderId: '110110110', status: '处理中', priority: '一般', orderType: '管点',
+    description: '松江路管点遥测终端通信故障', createTime: '2025-06-12', approvalTime: '',
+    step: 1, stepInfo: ['郑十 2025-06-12 10:00', '', '', ''],
+    roleName: '通信维护员', roleCode: 'TXWH010', department: '信息中心', expireTime: '2025-12-31',
+    personnel: [{ name: '刘一', phone: '13000130010', dept: '信息中心' }]
+  }
+])
+
+// ==================== 统计指标 ====================
+const statItems = computed(() => {
+  const today = '2025-06-12'
+  const todayList = orderList.value.filter(o => o.createTime === today)
+  return [
+    { label: '今日报警', value: todayList.length, cls: '' },
+    { label: '今日处理中', value: todayList.filter(o => o.status === '处理中').length, cls: 'red' },
+    { label: '今日待审批', value: todayList.filter(o => o.status === '待审批').length, cls: '' },
+    { label: '今日已审批', value: todayList.filter(o => o.status === '已审批').length, cls: '' }
+  ]
+})
+
+// ==================== 环形图配置 ====================
+const statusLegends = computed(() => [
+  { name: '处理中', value: orderList.value.filter(o => o.status === '处理中').length, color: '#409EFF' },
+  { name: '未处理', value: orderList.value.filter(o => o.status === '待审批').length, color: '#E6A23C' },
+  { name: '预期', value: orderList.value.filter(o => o.status === '已审批').length, color: '#67C23A' }
+])
+const priorityLegends = computed(() => [
+  { name: '一般', value: orderList.value.filter(o => o.priority === '一般').length, color: '#409EFF' },
+  { name: '重要', value: orderList.value.filter(o => o.priority === '重要').length, color: '#E6A23C' },
+  { name: '紧急', value: orderList.value.filter(o => o.priority === '紧急').length, color: '#F56C6C' }
+])
+const typeLegends = computed(() => [
+  { name: '设备故障', value: orderList.value.filter(o => o.orderType === '管线').length, color: '#409EFF' },
+  { name: '设备保养', value: orderList.value.filter(o => o.orderType === '管点').length, color: '#E6A23C' },
+  { name: '隐患排查', value: orderList.value.filter(o => ['窨井', '管网'].includes(o.orderType)).length, color: '#67C23A' }
+])
+const ringCharts = [
+  { id: 'statusChart', title: '状态', legends: statusLegends },
+  { id: 'priorityChart', title: '优先级', legends: priorityLegends },
+  { id: 'typeChart', title: '类型', legends: typeLegends }
+]
+
+// ==================== 筛选 ====================
+const tabs = ['全部', '处理中', '待审批', '已审批']
+const activeTab = ref('全部')
+const filters = ref({
+  deviceId: '',
+  dateRange: null,
+  level: '',
+  type: ''
+})
+
+const filteredList = computed(() => {
+  let list = orderList.value
+  // Tab 筛选
+  if (activeTab.value !== '全部') {
+    list = list.filter(o => o.status === activeTab.value)
+  }
+  // 设备ID (模糊匹配工单号)
+  if (filters.value.deviceId) {
+    list = list.filter(o => o.orderId.includes(filters.value.deviceId))
+  }
+  // 工单级别
+  if (filters.value.level) {
+    list = list.filter(o => o.priority === filters.value.level)
+  }
+  // 工单类型
+  if (filters.value.type) {
+    list = list.filter(o => o.orderType === filters.value.type)
+  }
+  // 日期范围
+  if (filters.value.dateRange && filters.value.dateRange.length === 2) {
+    const [start, end] = filters.value.dateRange
+    list = list.filter(o => o.createTime >= start && o.createTime <= end)
+  }
+  return list
+})
+
+// ==================== 详情弹窗 ====================
+const detailVisible = ref(false)
+const currentOrder = ref(null)
+
+function handleView(row) {
+  currentOrder.value = { ...row, personnel: [...row.personnel] }
+  detailVisible.value = true
+}
+
+function handleAddPerson() {
+  if (currentOrder.value) {
+    currentOrder.value.personnel.push({ name: '', phone: '', dept: '' })
+  }
+}
 
+// ==================== 添加人员弹窗 ====================
+const addPersonVisible = ref(false)
+const personFormRef = ref(null)
+const personForm = ref({ name: '', phone: '', dept: '' })
+const personRules = {
+  name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
+  phone: [{ required: true, message: '请输入联系方式', trigger: 'blur' }],
+  dept: [{ required: true, message: '请输入部门', trigger: 'blur' }]
+}
+
+function showAddPersonDialog() {
+  personForm.value = { name: '', phone: '', dept: '' }
+  addPersonVisible.value = true
+}
+
+function handleAddPersonConfirm() {
+  personFormRef.value.validate(valid => {
+    if (!valid) return
+    if (currentOrder.value) {
+      currentOrder.value.personnel.push({ ...personForm.value })
+    }
+    addPersonVisible.value = false
+  })
+}
+
+function handleApprove(pass) {
+  if (pass) {
+    if (currentOrder.value) {
+      const idx = orderList.value.findIndex(o => o.orderId === currentOrder.value.orderId)
+      if (idx > -1) {
+        orderList.value[idx].status = '已审批'
+        orderList.value[idx].approvalTime = '2025-06-12'
+        orderList.value[idx].step = 4
+        orderList.value[idx].stepInfo[2] = '当前用户 2025-06-12'
+        orderList.value[idx].stepInfo[3] = '2025-06-12'
+      }
+    }
+  }
+  detailVisible.value = false
+  nextTick(() => initCharts())
+}
+
+function statusTagType(status) {
+  if (status === '处理中') return 'primary'
+  if (status === '待审批') return 'warning'
+  if (status === '已审批') return 'success'
+  return 'info'
+}
+
+// ==================== ECharts 初始化 ====================
+function initCharts() {
+  ringCharts.forEach(chart => {
+    const dom = document.getElementById(chart.id)
+    if (!dom) return
+    const instance = echarts.getInstanceByDom(dom)
+    if (instance) instance.dispose()
+
+    const legends = chart.legends.value
+    const chartInstance = echarts.init(dom)
+    chartInstance.setOption({
+      title: {
+        text: chart.title,
+        left: 'center',
+        top: 'center',
+        textStyle: { fontSize: 13, color: '#333', fontWeight: 'normal' }
+      },
+      tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
+      series: [{
+        type: 'pie',
+        radius: ['45%', '70%'],
+        center: ['50%', '50%'],
+        avoidLabelOverlap: false,
+        label: { show: false },
+        emphasis: { label: { show: false } },
+        data: legends.map(l => ({ name: l.name, value: l.value, itemStyle: { color: l.color } }))
+      }]
+    })
+  })
+}
+
+onMounted(() => {
+  nextTick(() => initCharts())
+})
 </script>
 
 <style scoped>
+.dqyjgl-page {
+  padding: 16px;
+  background: #f5f5f5;
+  min-height: calc(100vh - 84px);
+}
+
+/* ==================== 顶部统计区 ==================== */
+.stats-panel {
+  display: flex;
+  align-items: stretch;
+  background: #fff;
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+  padding: 16px 20px;
+  margin-bottom: 12px;
+  gap: 20px;
+}
+
+.stats-left {
+  display: flex;
+  flex-direction: column;
+  justify-content: space-around;
+  min-width: 130px;
+  padding-right: 16px;
+  border-right: 1px solid #ebeef5;
+}
+
+.stat-item {
+  display: flex;
+  align-items: baseline;
+  gap: 8px;
+  padding: 6px 0;
+}
+
+.stat-value {
+  font-size: 24px;
+  font-weight: 700;
+  color: #303133;
+}
+
+.stat-value.red {
+  color: #F56C6C;
+}
+
+.stat-label {
+  font-size: 13px;
+  color: #909399;
+}
+
+.stats-center {
+  display: flex;
+  gap: 16px;
+  flex: 1;
+}
+
+.ring-chart-box {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  flex: 1;
+  min-width: 0;
+}
+
+.ring-chart {
+  width: 130px;
+  height: 130px;
+  flex-shrink: 0;
+}
+
+.ring-legend {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+}
+
+.legend-row {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  font-size: 12px;
+  color: #606266;
+}
+
+.legend-dot {
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  flex-shrink: 0;
+}
+
+.legend-text {
+  white-space: nowrap;
+}
+
+.legend-num {
+  font-weight: 600;
+  margin-left: 4px;
+}
+
+.stats-right {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  gap: 16px;
+  min-width: 80px;
+  border-left: 1px solid #ebeef5;
+  padding-left: 16px;
+}
+
+.right-metric {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+}
+
+.metric-icon {
+  font-size: 22px;
+  color: #E6A23C;
+}
+
+.metric-icon.orange {
+  color: #F56C6C;
+}
+
+.metric-num {
+  font-size: 22px;
+  font-weight: 700;
+  color: #303133;
+}
+
+/* ==================== 筛选区 ==================== */
+.filter-panel {
+  background: #fff;
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+  padding: 12px 16px;
+  margin-bottom: 12px;
+}
+
+.filter-tabs {
+  display: flex;
+  gap: 0;
+  margin-bottom: 12px;
+  border-bottom: 1px solid #ebeef5;
+}
+
+.tab-item {
+  padding: 8px 20px;
+  font-size: 14px;
+  color: #606266;
+  cursor: pointer;
+  border-bottom: 2px solid transparent;
+  transition: all 0.2s;
+}
+
+.tab-item:hover {
+  color: #409EFF;
+}
+
+.tab-item.active {
+  color: #409EFF;
+  border-bottom-color: #409EFF;
+  font-weight: 600;
+}
+
+.filter-form {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  flex-wrap: wrap;
+}
+
+/* ==================== 表格区 ==================== */
+.table-panel {
+  background: #fff;
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+  padding: 16px;
+}
+
+.priority-一般 {
+  color: #409EFF;
+}
+
+.priority-重要 {
+  color: #E6A23C;
+}
+
+.priority-紧急 {
+  color: #F56C6C;
+  font-weight: 600;
+}
+
+/* ==================== 详情弹窗 ==================== */
+.detail-content {
+  padding: 0 8px;
+}
+
+.steps-wrap {
+  margin-bottom: 24px;
+  padding: 8px 0;
+}
+
+.section-box {
+  border: 1px solid #ebeef5;
+  border-radius: 4px;
+  padding: 16px;
+  margin-bottom: 16px;
+}
+
+.section-title {
+  font-size: 14px;
+  font-weight: 600;
+  color: #303133;
+  margin-bottom: 12px;
+  overflow: hidden;
+}
+
+.desc-text {
+  background: #f5f7fa;
+  border-radius: 4px;
+  padding: 10px;
+  font-size: 13px;
+  color: #606266;
+  line-height: 1.6;
+  max-height: 120px;
+  overflow-y: auto;
+}
+
+:deep(.el-form-item__label) {
+  color: #909399;
+}
 
+:deep(.el-form-item__content) {
+  color: #303133;
+}
 </style>

+ 513 - 2
src/views/subSystem/drainage/jcyj/lsyjgl.vue

@@ -1,13 +1,524 @@
 <template>
-  <div class="app-container">
-    <h2>历史预警管理</h2>
+  <div class="app-container lsyjgl-page">
+
+    <!-- ==================== 搜索区域 ==================== -->
+    <div class="search-panel">
+      <div class="search-form">
+        <div class="form-item">
+          <span class="form-label">设备ID:</span>
+          <el-input v-model="filters.deviceId" placeholder="请输入" clearable style="width:200px" />
+        </div>
+        <div class="form-item">
+          <span class="form-label">所属区域:</span>
+          <el-select v-model="filters.area" placeholder="请选择" clearable style="width:160px">
+            <el-option label="沅陵大道" value="沅陵大道" />
+            <el-option label="城北路段" value="城北路段" />
+            <el-option label="河西片区" value="河西片区" />
+            <el-option label="人民路" value="人民路" />
+            <el-option label="南环路" value="南环路" />
+            <el-option label="开发区" value="开发区" />
+          </el-select>
+        </div>
+        <div class="form-item">
+          <span class="form-label">设备状态:</span>
+          <el-select v-model="filters.status" placeholder="请选择" clearable style="width:160px">
+            <el-option label="已处理" value="已处理" />
+            <el-option label="已关闭" value="已关闭" />
+            <el-option label="已归档" value="已归档" />
+          </el-select>
+        </div>
+        <el-button type="primary" @click="handleSearch">搜索</el-button>
+        <el-button @click="handleClear">清除</el-button>
+      </div>
+    </div>
+
+    <!-- ==================== 数据表格 ==================== -->
+    <div class="table-panel">
+      <el-table :data="filteredList" border style="width:100%">
+        <el-table-column prop="archiveNo" label="档案编号" min-width="130" />
+        <el-table-column prop="area" label="所属区域" min-width="120" />
+        <el-table-column prop="installDate" label="安装日期" min-width="120" />
+        <el-table-column prop="type" label="所属类型" min-width="100">
+          <template #default="{ row }">
+            <span :class="typeClass(row.type)">{{ row.type }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="address" label="地址" min-width="200" />
+        <el-table-column label="操作" width="100" fixed="right">
+          <template #default="{ row }">
+            <el-button link type="primary" @click="handleView(row)">查看</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+
+    <!-- ==================== 查看详情弹窗 ==================== -->
+    <el-dialog
+      v-model="dialogVisible"
+      title="异常管理"
+      width="680px"
+      :close-on-click-modal="false"
+    >
+      <div class="detail-body" v-if="currentDetail">
+        <!-- 左侧图片占位 -->
+        <div class="detail-img">
+          <el-icon class="img-placeholder-icon"><Picture /></el-icon>
+        </div>
+        <!-- 右侧信息 -->
+        <div class="detail-info">
+          <div class="info-code">编号:{{ currentDetail.code }}</div>
+          <div class="info-grid">
+            <div class="info-row">
+              <div class="info-item">
+                <span class="info-label">名称:</span>
+                <span class="info-value">{{ currentDetail.name }}</span>
+              </div>
+              <div class="info-item">
+                <span class="info-label">ID:</span>
+                <span class="info-value">{{ currentDetail.deviceId }}</span>
+              </div>
+            </div>
+            <div class="info-row">
+              <div class="info-item">
+                <span class="info-label">型号:</span>
+                <span class="info-value">{{ currentDetail.model }}</span>
+              </div>
+              <div class="info-item">
+                <span class="info-label">异常日期:</span>
+                <span class="info-value">{{ currentDetail.abnormalDate }}</span>
+              </div>
+            </div>
+            <div class="info-row">
+              <div class="info-item">
+                <span class="info-label">所属单位:</span>
+                <span class="info-value">{{ currentDetail.unit }}</span>
+              </div>
+              <div class="info-item">
+                <span class="info-label">详细地址:</span>
+                <span class="info-value">{{ currentDetail.address }}</span>
+              </div>
+            </div>
+            <div class="info-row single">
+              <div class="info-item">
+                <span class="info-label">异常类型:</span>
+                <span class="info-value">{{ currentDetail.abnormalType }}</span>
+              </div>
+            </div>
+            <div class="info-row single">
+              <div class="info-item">
+                <span class="info-label">描述:</span>
+                <span class="info-value desc-text">{{ currentDetail.description }}</span>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <template #footer>
+        <el-button @click="dialogVisible = false">关闭</el-button>
+      </template>
+    </el-dialog>
+
   </div>
 </template>
 
 <script setup name="Lsyjgl">
+import { ref, reactive, computed } from 'vue'
+import { Picture } from '@element-plus/icons-vue'
+
+// ==================== 模拟数据 ====================
+const rawList = ref([
+  {
+    id: 1,
+    archiveNo: '110110101',
+    area: '沅陵大道',
+    installDate: '2025-03-15',
+    type: '管线',
+    address: '沅陵县沅陵大道18号',
+    status: '已归档',
+    deviceId: 'DEV-001',
+    detail: {
+      code: '0011001',
+      name: '地下管线传感器A',
+      deviceId: '001001',
+      model: 'ZK-PL18V',
+      abnormalDate: '2025-03-10',
+      unit: 'XX燃气公司',
+      address: '沅陵县沅陵大道18号',
+      abnormalType: '管网漏损,设备保养',
+      description: '设备检测到管线压力异常,压力持续低于正常阈值,疑似管道出现轻微渗漏,已派人员现场检查并完成修复处理,档案已归档。',
+    }
+  },
+  {
+    id: 2,
+    archiveNo: '110110102',
+    area: '城北路段',
+    installDate: '2025-04-02',
+    type: '管点',
+    address: '沅陵县城北路段32号',
+    status: '已处理',
+    deviceId: 'DEV-002',
+    detail: {
+      code: '0011002',
+      name: '管点监测仪B',
+      deviceId: '001002',
+      model: 'ZK-GP22',
+      abnormalDate: '2025-03-28',
+      unit: 'XX排水所',
+      address: '沅陵县城北路段32号',
+      abnormalType: '流量异常',
+      description: '管点流量监测数据显示异常波动,经现场核查为传感器接触不良,已更换传感器并恢复正常运行。',
+    }
+  },
+  {
+    id: 3,
+    archiveNo: '110110103',
+    area: '河西片区',
+    installDate: '2025-02-20',
+    type: '窨井',
+    address: '沅陵县河西大道7号',
+    status: '已关闭',
+    deviceId: 'DEV-003',
+    detail: {
+      code: '0011003',
+      name: '窨井液位仪C',
+      deviceId: '001003',
+      model: 'ZK-YJ15',
+      abnormalDate: '2025-02-18',
+      unit: 'XX市政管理',
+      address: '沅陵县河西大道7号',
+      abnormalType: '液位超限',
+      description: '窨井液位超过警戒线,经检查系周边排水管道堵塞导致,已疏通管道并清洁窨井,液位恢复正常。',
+    }
+  },
+  {
+    id: 4,
+    archiveNo: '110110104',
+    area: '人民路',
+    installDate: '2025-01-10',
+    type: '管线',
+    address: '沅陵县人民路55号',
+    status: '已归档',
+    deviceId: 'DEV-004',
+    detail: {
+      code: '0011004',
+      name: '管线压力计D',
+      deviceId: '001004',
+      model: 'ZK-PL30',
+      abnormalDate: '2025-01-08',
+      unit: 'XX供水公司',
+      address: '沅陵县人民路55号',
+      abnormalType: '压力超标',
+      description: '供水管线压力持续超过正常值,经排查为调压阀门故障,已更换阀门并调整压力至正常范围,事件闭环归档。',
+    }
+  },
+  {
+    id: 5,
+    archiveNo: '110110105',
+    area: '南环路',
+    installDate: '2025-05-06',
+    type: '窨井',
+    address: '沅陵县南环路101号',
+    status: '已处理',
+    deviceId: 'DEV-005',
+    detail: {
+      code: '0011005',
+      name: '窨井盖传感器E',
+      deviceId: '001005',
+      model: 'ZK-YG08',
+      abnormalDate: '2025-05-03',
+      unit: 'XX道路管理所',
+      address: '沅陵县南环路101号',
+      abnormalType: '井盖位移',
+      description: '井盖位移传感器触发报警,现场确认井盖被车辆碰撞移位,已立即复位并加固固定装置,恢复正常监测。',
+    }
+  },
+  {
+    id: 6,
+    archiveNo: '110110106',
+    area: '开发区',
+    installDate: '2024-12-15',
+    type: '管点',
+    address: '沅陵县开发区工业路3号',
+    status: '已归档',
+    deviceId: 'DEV-006',
+    detail: {
+      code: '0011006',
+      name: '管点水质仪F',
+      deviceId: '001006',
+      model: 'ZK-SZ12',
+      abnormalDate: '2024-12-12',
+      unit: 'XX环保监测站',
+      address: '沅陵县开发区工业路3号',
+      abnormalType: '水质异常',
+      description: '管点水质检测指标偏高,pH值超出正常范围,经溯源检查发现上游工业排水混入,已协调整改并跟踪复测达标。',
+    }
+  },
+  {
+    id: 7,
+    archiveNo: '110110107',
+    area: '沅陵大道',
+    installDate: '2025-06-01',
+    type: '管线',
+    address: '沅陵县沅陵大道208号',
+    status: '已关闭',
+    deviceId: 'DEV-007',
+    detail: {
+      code: '0011007',
+      name: '燃气管线监测G',
+      deviceId: '001007',
+      model: 'ZK-GX25V',
+      abnormalDate: '2025-05-28',
+      unit: 'XX燃气集团',
+      address: '沅陵县沅陵大道208号',
+      abnormalType: '燃气浓度超标',
+      description: '燃气浓度传感器检测到周边空气中燃气浓度升高,经排查为管线焊接点微渗,已完成补焊处理,复测合格后关闭预警。',
+    }
+  },
+  {
+    id: 8,
+    archiveNo: '110110108',
+    area: '城北路段',
+    installDate: '2025-03-22',
+    type: '窨井',
+    address: '沅陵县城北新区12号',
+    status: '已归档',
+    deviceId: 'DEV-008',
+    detail: {
+      code: '0011008',
+      name: '雨水窨井液位H',
+      deviceId: '001008',
+      model: 'ZK-YJ10R',
+      abnormalDate: '2025-03-20',
+      unit: 'XX排水管理所',
+      address: '沅陵县城北新区12号',
+      abnormalType: '液位持续升高',
+      description: '强降雨期间窨井液位快速上升至警戒值,系排水能力不足,现场增设临时泵站配合抽排,雨后进行清淤扩容处理。',
+    }
+  },
+  {
+    id: 9,
+    archiveNo: '110110109',
+    area: '河西片区',
+    installDate: '2025-04-18',
+    type: '管点',
+    address: '沅陵县河西小区88号',
+    status: '已处理',
+    deviceId: 'DEV-009',
+    detail: {
+      code: '0011009',
+      name: '管点流量计I',
+      deviceId: '001009',
+      model: 'ZK-LL20',
+      abnormalDate: '2025-04-15',
+      unit: 'XX自来水公司',
+      address: '沅陵县河西小区88号',
+      abnormalType: '流量骤降',
+      description: '流量计数据显示供水流量骤降,现场检查发现截止阀异常关闭,经人工开阀并排查原因为阀门控制模块故障,已更换模块恢复正常。',
+    }
+  },
+  {
+    id: 10,
+    archiveNo: '110110110',
+    area: '人民路',
+    installDate: '2025-05-15',
+    type: '管线',
+    address: '沅陵县人民路广场段',
+    status: '已归档',
+    deviceId: 'DEV-010',
+    detail: {
+      code: '0011010',
+      name: '污水管线监测J',
+      deviceId: '001010',
+      model: 'ZK-WS18',
+      abnormalDate: '2025-05-12',
+      unit: 'XX污水处理厂',
+      address: '沅陵县人民路广场段',
+      abnormalType: '管网堵塞',
+      description: '污水管线水位传感器持续报警,现场检查确认为管内油脂沉积导致严重堵塞,采用高压水车疏通后恢复正常,建议定期维护。',
+    }
+  },
+])
+
+// ==================== 搜索筛选 ====================
+const filters = reactive({
+  deviceId: '',
+  area: '',
+  status: '',
+})
+
+const filteredList = computed(() => {
+  return rawList.value.filter(item => {
+    const matchDevice = !filters.deviceId || item.deviceId.includes(filters.deviceId)
+    const matchArea = !filters.area || item.area === filters.area
+    const matchStatus = !filters.status || item.status === filters.status
+    return matchDevice && matchArea && matchStatus
+  })
+})
+
+function handleSearch() {
+  // computed 自动响应,无需额外操作
+}
 
+function handleClear() {
+  filters.deviceId = ''
+  filters.area = ''
+  filters.status = ''
+}
+
+// ==================== 所属类型颜色 ====================
+function typeClass(type) {
+  if (type === '管线') return 'type-tag type-pipe'
+  if (type === '管点') return 'type-tag type-point'
+  if (type === '窨井') return 'type-tag type-well'
+  return ''
+}
+
+// ==================== 查看详情弹窗 ====================
+const dialogVisible = ref(false)
+const currentDetail = ref(null)
+
+function handleView(row) {
+  currentDetail.value = { ...row.detail, archiveNo: row.archiveNo }
+  dialogVisible.value = true
+}
 </script>
 
 <style scoped>
+.lsyjgl-page {
+  background: #fff;
+  min-height: 100%;
+  padding: 16px;
+}
+
+/* 搜索区 */
+.search-panel {
+  background: #fff;
+  border: 1px solid #e4e7ed;
+  border-radius: 4px;
+  padding: 16px 20px;
+  margin-bottom: 16px;
+}
+
+.search-form {
+  display: flex;
+  align-items: center;
+  flex-wrap: wrap;
+  gap: 12px;
+}
+
+.form-item {
+  display: flex;
+  align-items: center;
+}
+
+.form-label {
+  font-size: 14px;
+  color: #606266;
+  white-space: nowrap;
+  margin-right: 4px;
+}
+
+/* 表格区 */
+.table-panel {
+  background: #fff;
+  border-radius: 4px;
+}
+
+/* 所属类型标签 */
+.type-tag {
+  font-weight: 500;
+  font-size: 13px;
+}
+
+.type-pipe {
+  color: #409eff;
+}
+
+.type-point {
+  color: #67c23a;
+}
+
+.type-well {
+  color: #e6a23c;
+}
+
+/* 详情弹窗 */
+.detail-body {
+  display: flex;
+  gap: 24px;
+  padding: 8px 4px;
+}
+
+/* 左侧图片占位 */
+.detail-img {
+  flex-shrink: 0;
+  width: 150px;
+  height: 150px;
+  background: #d9ecff;
+  border: 2px solid #b3d8ff;
+  border-radius: 4px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.img-placeholder-icon {
+  font-size: 64px;
+  color: #409eff;
+  opacity: 0.7;
+}
+
+/* 右侧详情信息 */
+.detail-info {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+}
+
+.info-code {
+  font-size: 15px;
+  font-weight: 600;
+  color: #303133;
+  padding-bottom: 6px;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.info-grid {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+}
+
+.info-row {
+  display: flex;
+  gap: 16px;
+}
+
+.info-row.single {
+  display: block;
+}
+
+.info-item {
+  flex: 1;
+  display: flex;
+  align-items: baseline;
+  font-size: 14px;
+}
+
+.info-label {
+  color: #909399;
+  white-space: nowrap;
+  min-width: 64px;
+}
+
+.info-value {
+  color: #303133;
+}
 
+.desc-text {
+  color: #606266;
+  line-height: 1.6;
+  word-break: break-all;
+}
 </style>

+ 632 - 2
src/views/subSystem/drainage/spjc/jcspGIS.vue

@@ -1,13 +1,643 @@
 <template>
-  <div class="app-container">
-    <h2>监测视频GIS</h2>
+  <div class="jcsp-gis-page">
+    <!-- 左侧浮动面板 -->
+    <div class="side-panel">
+      <div class="panel-header">
+        <span class="panel-title">视频资源分布</span>
+      </div>
+
+      <!-- 图层筛选 -->
+      <div class="panel-section">
+        <div class="section-label">图层筛选</div>
+        <div class="layer-list">
+          <div class="layer-item" v-for="item in layerOptions" :key="item.value">
+            <el-checkbox
+              v-model="layerChecked[item.value]"
+              @change="onLayerChange"
+            >
+              <span class="layer-dot" :style="{ background: item.color }"></span>
+              {{ item.label }}
+            </el-checkbox>
+          </div>
+        </div>
+      </div>
+
+      <!-- 视频统计 -->
+      <div class="panel-section">
+        <div class="section-label">视频统计</div>
+        <div class="stats-row">
+          <div class="stat-card">
+            <div class="stat-value">{{ videoList.length }}</div>
+            <div class="stat-text">总数</div>
+          </div>
+          <div class="stat-card stat-online">
+            <div class="stat-value">{{ onlineCount }}</div>
+            <div class="stat-text">在线</div>
+          </div>
+          <div class="stat-card stat-offline">
+            <div class="stat-value">{{ offlineCount }}</div>
+            <div class="stat-text">离线</div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 搜索框 -->
+      <div class="panel-section">
+        <div class="section-label">快速定位</div>
+        <el-input
+          v-model="searchKey"
+          placeholder="输入视频名称搜索"
+          clearable
+          prefix-icon="Search"
+          @input="onSearch"
+        />
+        <div class="search-results" v-if="searchResults.length > 0">
+          <div
+            class="search-item"
+            v-for="item in searchResults"
+            :key="item.id"
+            @click="locateVideo(item)"
+          >
+            <span class="layer-dot" :style="{ background: getTypeColor(item.type) }"></span>
+            <span class="search-name">{{ item.name }}</span>
+            <span class="search-status" :class="item.status === '在线' ? 'online' : 'offline'">
+              {{ item.status }}
+            </span>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 地图容器 -->
+    <div id="jcspGISMap" class="map-container"></div>
+
+    <!-- 视频弹窗 -->
+    <el-dialog
+      v-model="videoDialogVisible"
+      :title="currentVideo.name"
+      width="480px"
+      :close-on-click-modal="false"
+      class="video-dialog"
+      @closed="onDialogClosed"
+    >
+      <div class="video-player" :class="{ offline: currentVideo.status === '离线' }">
+        <div class="player-placeholder" v-if="currentVideo.status === '离线'">
+          <el-icon :size="48"><VideoCamera /></el-icon>
+          <span>设备离线,无法播放</span>
+        </div>
+        <div class="player-placeholder playing" v-else>
+          <el-icon :size="48" class="play-icon"><VideoCamera /></el-icon>
+          <span>实时监控画面</span>
+          <div class="live-badge">LIVE</div>
+        </div>
+      </div>
+      <div class="video-info">
+        <div class="info-row">
+          <span class="info-label">视频名称</span>
+          <span class="info-value">{{ currentVideo.name }}</span>
+        </div>
+        <div class="info-row">
+          <span class="info-label">设备类型</span>
+          <span class="info-value">
+            <span class="type-tag" :style="{ background: getTypeColor(currentVideo.type), color: '#fff' }">
+              {{ getTypeLabel(currentVideo.type) }}
+            </span>
+          </span>
+        </div>
+        <div class="info-row">
+          <span class="info-label">安装位置</span>
+          <span class="info-value">{{ currentVideo.location }}</span>
+        </div>
+        <div class="info-row">
+          <span class="info-label">在线状态</span>
+          <span class="info-value" :class="currentVideo.status === '在线' ? 'status-online' : 'status-offline'">
+            {{ currentVideo.status }}
+          </span>
+        </div>
+        <div class="info-row">
+          <span class="info-label">关联设备</span>
+          <span class="info-value">{{ currentVideo.device }}</span>
+        </div>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
 <script setup name="JcspGIS">
+import { ref, reactive, computed, onMounted, watch, nextTick } from 'vue'
+import { VideoCamera, Search } from '@element-plus/icons-vue'
+import locationIcon from '@/assets/images/location.png'
+
+// ==================== 图层配置 ====================
+const layerOptions = [
+  { label: '积水点视频', value: 'jsd', color: '#409eff' },
+  { label: '泵站视频', value: 'bz', color: '#67c23a' },
+  { label: '下穿立交视频', value: 'xclj', color: '#e6a23c' },
+  { label: '管网视频', value: 'gw', color: '#9b59b6' }
+]
+
+const layerChecked = reactive({
+  jsd: true,
+  bz: true,
+  xclj: true,
+  gw: true
+})
+
+// ==================== 模拟视频数据(18个点位)====================
+const videoList = ref([
+  { id: 1, name: '沅陵大桥积水点', type: 'jsd', lng: 110.396, lat: 28.468, status: '在线', location: '沅陵大桥南侧', device: 'WL-JSD-001' },
+  { id: 2, name: '太常路积水点', type: 'jsd', lng: 110.382, lat: 28.459, status: '在线', location: '太常路与沅江路交汇', device: 'WL-JSD-002' },
+  { id: 3, name: '沅江路积水点', type: 'jsd', lng: 110.403, lat: 28.447, status: '离线', location: '沅江路中段', device: 'WL-JSD-003' },
+  { id: 4, name: '龙舟路积水点', type: 'jsd', lng: 110.388, lat: 28.436, status: '在线', location: '龙舟路与建设路交汇', device: 'WL-JSD-004' },
+  { id: 5, name: '城东积水点', type: 'jsd', lng: 110.412, lat: 28.461, status: '在线', location: '城东开发区入口', device: 'WL-JSD-005' },
+  { id: 6, name: '一号泵站', type: 'bz', lng: 110.391, lat: 28.465, status: '在线', location: '沅陵大桥西侧', device: 'WL-BZ-001' },
+  { id: 7, name: '二号泵站', type: 'bz', lng: 110.378, lat: 28.451, status: '在线', location: '太常片区排水口', device: 'WL-BZ-002' },
+  { id: 8, name: '三号泵站', type: 'bz', lng: 110.407, lat: 28.455, status: '离线', location: '沅江路东段', device: 'WL-BZ-003' },
+  { id: 9, name: '四号泵站', type: 'bz', lng: 110.395, lat: 28.439, status: '在线', location: '城区排水枢纽', device: 'WL-BZ-004' },
+  { id: 10, name: '五号泵站', type: 'bz', lng: 110.368, lat: 28.448, status: '在线', location: '沅水南岸排水站', device: 'WL-BZ-005' },
+  { id: 11, name: '沅陵大桥下穿', type: 'xclj', lng: 110.398, lat: 28.463, status: '在线', location: '沅陵大桥下穿通道', device: 'WL-XC-001' },
+  { id: 12, name: '太常路下穿', type: 'xclj', lng: 110.384, lat: 28.453, status: '在线', location: '太常路下穿隧道', device: 'WL-XC-002' },
+  { id: 13, name: '建设路下穿', type: 'xclj', lng: 110.393, lat: 28.441, status: '离线', location: '建设路下穿通道', device: 'WL-XC-003' },
+  { id: 14, name: '城东下穿立交', type: 'xclj', lng: 110.415, lat: 28.457, status: '在线', location: '城东立交桥下', device: 'WL-XC-004' },
+  { id: 15, name: '主城区管网A段', type: 'gw', lng: 110.390, lat: 28.460, status: '在线', location: '主城区排水干管', device: 'WL-GW-001' },
+  { id: 16, name: '太常片区管网', type: 'gw', lng: 110.376, lat: 28.446, status: '在线', location: '太常片区管网节点', device: 'WL-GW-002' },
+  { id: 17, name: '沅江路管网', type: 'gw', lng: 110.405, lat: 28.449, status: '离线', location: '沅江路管网检测点', device: 'WL-GW-003' },
+  { id: 18, name: '城东管网B段', type: 'gw', lng: 110.418, lat: 28.465, status: '在线', location: '城东管网检测井', device: 'WL-GW-004' }
+])
+
+// ==================== 统计计算 ====================
+const onlineCount = computed(() => videoList.value.filter(v => v.status === '在线').length)
+const offlineCount = computed(() => videoList.value.filter(v => v.status === '离线').length)
+
+// ==================== 搜索功能 ====================
+const searchKey = ref('')
+const searchResults = ref([])
+
+function onSearch() {
+  if (!searchKey.value.trim()) {
+    searchResults.value = []
+    return
+  }
+  const key = searchKey.value.trim().toLowerCase()
+  searchResults.value = videoList.value.filter(v =>
+    v.name.toLowerCase().includes(key) || v.location.toLowerCase().includes(key)
+  )
+}
+
+function locateVideo(item) {
+  if (mapInstance) {
+    mapInstance.centerAndZoom(new BMapGL.Point(item.lng, item.lat), 17)
+    // 触发对应标记的点击
+    const label = markerMap.get(item.id)
+    if (label) {
+      // 打开视频弹窗
+      openVideoDialog(item)
+    }
+  }
+  searchResults.value = []
+  searchKey.value = ''
+}
+
+// ==================== 工具方法 ====================
+function getTypeColor(type) {
+  const map = { jsd: '#409eff', bz: '#67c23a', xclj: '#e6a23c', gw: '#9b59b6' }
+  return map[type] || '#409eff'
+}
+
+function getTypeLabel(type) {
+  const map = { jsd: '积水点视频', bz: '泵站视频', xclj: '下穿立交视频', gw: '管网视频' }
+  return map[type] || '未知类型'
+}
+
+// ==================== 地图相关 ====================
+let mapInstance = null
+const markerMap = new Map() // id -> label
+
+function initMap() {
+  const container = document.getElementById('jcspGISMap')
+  if (!container || typeof BMapGL === 'undefined') {
+    console.warn('BMapGL 未加载或容器不存在')
+    return
+  }
+
+  try {
+    mapInstance = new BMapGL.Map('jcspGISMap')
+    const centerPoint = new BMapGL.Point(110.393, 28.452)
+    mapInstance.centerAndZoom(centerPoint, 15)
+    mapInstance.enableScrollWheelZoom(true)
+
+    // 添加所有标记
+    videoList.value.forEach(point => {
+      addMapMarker(point)
+    })
+  } catch (e) {
+    console.error('百度地图初始化失败:', e)
+  }
+}
+
+function addMapMarker(point) {
+  const color = getTypeColor(point.type)
+  const typeLabel = getTypeLabel(point.type)
+  const bPoint = new BMapGL.Point(point.lng, point.lat)
+  const statusDot = point.status === '在线'
+    ? '<span style="display:inline-block;width:6px;height:6px;border-radius:50%;background:#67c23a;margin-right:3px;vertical-align:middle;"></span>'
+    : '<span style="display:inline-block;width:6px;height:6px;border-radius:50%;background:#f56c6c;margin-right:3px;vertical-align:middle;"></span>'
+
+  const html = `<div style="text-align:center;cursor:pointer;">
+    <div style="color:#fff;background:${color};border-radius:4px;padding:2px 8px;font-size:11px;white-space:nowrap;display:inline-block;margin-bottom:2px;box-shadow:0 1px 4px rgba(0,0,0,0.3);">
+      ${statusDot}${point.name}
+    </div>
+    <div><img src="${locationIcon}" style="width:28px;height:28px;display:block;margin:0 auto;"/></div>
+  </div>`
 
+  const label = new BMapGL.Label(html, {
+    position: bPoint,
+    offset: new BMapGL.Size(-30, -50)
+  })
+  label.setStyle({
+    border: 'none',
+    background: 'transparent',
+    padding: '0',
+    zIndex: '10'
+  })
+  mapInstance.addOverlay(label)
+
+  // 存储标记引用
+  markerMap.set(point.id, label)
+
+  // 点击标记打开视频弹窗
+  label.addEventListener('click', function () {
+    openVideoDialog(point)
+  })
+}
+
+// ==================== 图层筛选 ====================
+function onLayerChange() {
+  videoList.value.forEach(point => {
+    const label = markerMap.get(point.id)
+    if (!label) return
+    const visible = layerChecked[point.type]
+    label.show()
+    if (!visible) {
+      label.hide()
+    }
+  })
+}
+
+// ==================== 视频弹窗 ====================
+const videoDialogVisible = ref(false)
+const currentVideo = ref({})
+
+function openVideoDialog(point) {
+  currentVideo.value = { ...point }
+  videoDialogVisible.value = true
+}
+
+function onDialogClosed() {
+  currentVideo.value = {}
+}
+
+// ==================== 生命周期 ====================
+onMounted(() => {
+  nextTick(() => {
+    initMap()
+  })
+})
 </script>
 
 <style scoped>
+.jcsp-gis-page {
+  position: relative;
+  width: 100%;
+  height: calc(100vh - 84px);
+  overflow: hidden;
+}
+
+.map-container {
+  width: 100%;
+  height: 100%;
+}
+
+/* ==================== 左侧浮动面板 ==================== */
+.side-panel {
+  position: absolute;
+  top: 16px;
+  left: 16px;
+  width: 280px;
+  background: rgba(20, 30, 48, 0.92);
+  border-radius: 8px;
+  z-index: 100;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
+  padding: 0;
+  max-height: calc(100% - 32px);
+  overflow-y: auto;
+}
+
+.side-panel::-webkit-scrollbar {
+  width: 4px;
+}
+.side-panel::-webkit-scrollbar-thumb {
+  background: rgba(255, 255, 255, 0.2);
+  border-radius: 2px;
+}
+
+.panel-header {
+  padding: 16px 18px 12px;
+  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+}
+
+.panel-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #fff;
+  letter-spacing: 1px;
+}
+
+.panel-section {
+  padding: 14px 18px;
+  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
+}
+
+.panel-section:last-child {
+  border-bottom: none;
+}
+
+.section-label {
+  font-size: 12px;
+  color: rgba(255, 255, 255, 0.5);
+  margin-bottom: 10px;
+  text-transform: uppercase;
+  letter-spacing: 1px;
+}
+
+/* 图层列表 */
+.layer-list {
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+}
+
+.layer-item :deep(.el-checkbox) {
+  color: rgba(255, 255, 255, 0.85);
+  height: auto;
+}
+
+.layer-item :deep(.el-checkbox__label) {
+  color: rgba(255, 255, 255, 0.85);
+  font-size: 13px;
+  display: inline-flex;
+  align-items: center;
+  gap: 4px;
+}
+
+.layer-item :deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
+  background: #409eff;
+  border-color: #409eff;
+}
+
+.layer-dot {
+  display: inline-block;
+  width: 10px;
+  height: 10px;
+  border-radius: 50%;
+  margin-right: 2px;
+  vertical-align: middle;
+}
+
+/* 统计卡片 */
+.stats-row {
+  display: flex;
+  gap: 10px;
+}
+
+.stat-card {
+  flex: 1;
+  background: rgba(255, 255, 255, 0.08);
+  border-radius: 6px;
+  padding: 10px 8px;
+  text-align: center;
+}
+
+.stat-card.stat-online {
+  background: rgba(103, 194, 58, 0.15);
+}
+
+.stat-card.stat-offline {
+  background: rgba(245, 108, 108, 0.15);
+}
+
+.stat-value {
+  font-size: 22px;
+  font-weight: 700;
+  color: #fff;
+  line-height: 1.2;
+}
+
+.stat-online .stat-value {
+  color: #67c23a;
+}
+
+.stat-offline .stat-value {
+  color: #f56c6c;
+}
+
+.stat-text {
+  font-size: 11px;
+  color: rgba(255, 255, 255, 0.5);
+  margin-top: 4px;
+}
+
+/* 搜索 */
+.side-panel :deep(.el-input__wrapper) {
+  background: rgba(255, 255, 255, 0.08);
+  box-shadow: none;
+  border: 1px solid rgba(255, 255, 255, 0.12);
+}
+
+.side-panel :deep(.el-input__inner) {
+  color: #fff;
+  font-size: 13px;
+}
+
+.side-panel :deep(.el-input__inner::placeholder) {
+  color: rgba(255, 255, 255, 0.35);
+}
+
+.side-panel :deep(.el-input__prefix .el-icon) {
+  color: rgba(255, 255, 255, 0.4);
+}
+
+.search-results {
+  margin-top: 8px;
+  max-height: 200px;
+  overflow-y: auto;
+  border-radius: 4px;
+}
+
+.search-results::-webkit-scrollbar {
+  width: 3px;
+}
+.search-results::-webkit-scrollbar-thumb {
+  background: rgba(255, 255, 255, 0.15);
+  border-radius: 2px;
+}
+
+.search-item {
+  display: flex;
+  align-items: center;
+  padding: 7px 10px;
+  cursor: pointer;
+  border-radius: 4px;
+  transition: background 0.15s;
+}
+
+.search-item:hover {
+  background: rgba(255, 255, 255, 0.1);
+}
+
+.search-name {
+  flex: 1;
+  font-size: 12px;
+  color: rgba(255, 255, 255, 0.85);
+  margin-left: 6px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.search-status {
+  font-size: 11px;
+  padding: 1px 6px;
+  border-radius: 3px;
+}
+
+.search-status.online {
+  color: #67c23a;
+  background: rgba(103, 194, 58, 0.15);
+}
+
+.search-status.offline {
+  color: #f56c6c;
+  background: rgba(245, 108, 108, 0.15);
+}
+
+/* ==================== 视频弹窗样式 ==================== */
+.video-dialog :deep(.el-dialog) {
+  border-radius: 10px;
+  overflow: hidden;
+}
+
+.video-dialog :deep(.el-dialog__header) {
+  background: #1a1a2e;
+  padding: 14px 20px;
+  margin: 0;
+}
+
+.video-dialog :deep(.el-dialog__title) {
+  color: #fff;
+  font-size: 15px;
+}
+
+.video-dialog :deep(.el-dialog__headerbtn .el-dialog__close) {
+  color: rgba(255, 255, 255, 0.6);
+}
+
+.video-dialog :deep(.el-dialog__body) {
+  padding: 0;
+}
+
+.video-player {
+  width: 100%;
+  height: 300px;
+  background: #000;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  position: relative;
+}
+
+.player-placeholder {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 12px;
+  color: rgba(255, 255, 255, 0.3);
+  font-size: 14px;
+}
+
+.player-placeholder.playing {
+  color: rgba(255, 255, 255, 0.6);
+}
+
+.play-icon {
+  color: #409eff;
+  animation: pulse 2s infinite;
+}
+
+@keyframes pulse {
+  0%, 100% { opacity: 1; }
+  50% { opacity: 0.5; }
+}
+
+.live-badge {
+  position: absolute;
+  top: 12px;
+  right: 12px;
+  background: #f56c6c;
+  color: #fff;
+  font-size: 11px;
+  font-weight: 700;
+  padding: 2px 8px;
+  border-radius: 3px;
+  letter-spacing: 1px;
+  animation: blink 1.5s infinite;
+}
+
+@keyframes blink {
+  0%, 100% { opacity: 1; }
+  50% { opacity: 0.4; }
+}
+
+.video-info {
+  padding: 16px 20px;
+}
+
+.info-row {
+  display: flex;
+  align-items: center;
+  padding: 8px 0;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.info-row:last-child {
+  border-bottom: none;
+}
+
+.info-label {
+  width: 80px;
+  flex-shrink: 0;
+  font-size: 13px;
+  color: #909399;
+}
+
+.info-value {
+  font-size: 13px;
+  color: #303133;
+}
+
+.type-tag {
+  font-size: 11px;
+  padding: 2px 8px;
+  border-radius: 3px;
+}
+
+.status-online {
+  color: #67c23a;
+  font-weight: 500;
+}
 
+.status-offline {
+  color: #f56c6c;
+  font-weight: 500;
+}
 </style>

+ 637 - 2
src/views/subSystem/drainage/spjc/spfzgl.vue

@@ -1,13 +1,648 @@
 <template>
-  <div class="app-container">
-    <h2>视频分组管理</h2>
+  <div class="spfzgl-container">
+    <!-- 左侧分组树 -->
+    <div class="left-panel">
+      <div class="panel-header">
+        <span class="panel-title">视频分组</span>
+        <el-button type="primary" link @click="handleAddGroup">
+          <el-icon><Plus /></el-icon> 新增组
+        </el-button>
+      </div>
+      <el-tree
+        ref="treeRef"
+        :data="treeData"
+        :props="treeProps"
+        node-key="id"
+        highlight-current
+        default-expand-all
+        @node-click="handleNodeClick"
+      >
+        <template #default="{ node, data }">
+          <span class="tree-node">
+            <span class="tree-node-label">{{ node.label }}</span>
+            <span v-if="data.count !== undefined" class="tree-node-count">({{ data.count }})</span>
+            <span v-if="data.custom" class="tree-node-actions">
+              <el-icon class="action-icon" @click.stop="handleDeleteGroup(node, data)"><Delete /></el-icon>
+            </span>
+          </span>
+        </template>
+      </el-tree>
+    </div>
+
+    <!-- 右侧视频列表 -->
+    <div class="right-panel">
+      <!-- 搜索栏 -->
+      <el-form :model="queryParams" :inline="true" class="search-form">
+        <el-form-item label="视频名称">
+          <el-input v-model="queryParams.name" placeholder="请输入视频名称" clearable style="width: 200px" />
+        </el-form-item>
+        <el-form-item label="状态">
+          <el-select v-model="queryParams.status" placeholder="全部" clearable style="width: 130px">
+            <el-option label="在线" value="在线" />
+            <el-option label="离线" value="离线" />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleQuery">搜索</el-button>
+          <el-button @click="resetQuery">重置</el-button>
+        </el-form-item>
+      </el-form>
+
+      <!-- 操作栏 -->
+      <div class="toolbar">
+        <el-button type="primary" :disabled="selectedIds.length === 0" @click="handleCollectBatch">
+          <el-icon><Star /></el-icon> 收藏到分组
+        </el-button>
+      </div>
+
+      <!-- 视频卡片网格 -->
+      <el-row :gutter="16">
+        <el-col :span="8" v-for="item in pagedList" :key="item.id">
+          <div class="video-card" :class="{ 'is-selected': selectedIds.includes(item.id) }" @click="toggleSelect(item.id)">
+            <div class="video-thumb">
+              <el-icon :size="48" color="#fff"><VideoPlay /></el-icon>
+            </div>
+            <div class="video-info">
+              <div class="video-name">
+                <el-checkbox :model-value="selectedIds.includes(item.id)" @click.stop @change="toggleSelect(item.id)" />
+                <span class="name-text">{{ item.name }}</span>
+              </div>
+              <div class="video-meta">
+                <span :class="['status-tag', item.status === '在线' ? 'online' : 'offline']">{{ item.status }}</span>
+                <span class="meta-text">关联设备:{{ item.device }}</span>
+              </div>
+              <div class="video-meta">
+                <span class="meta-text">安装位置:{{ item.location }}</span>
+              </div>
+              <div class="video-actions">
+                <el-button link type="primary" @click.stop="handleViewVideo(item)">查看视频</el-button>
+                <el-button link :type="item.collected ? 'warning' : 'default'" @click.stop="handleToggleCollect(item)">
+                  <el-icon><Star /></el-icon>
+                  {{ item.collected ? '已收藏' : '收藏' }}
+                </el-button>
+              </div>
+            </div>
+          </div>
+        </el-col>
+      </el-row>
+
+      <!-- 分页 -->
+      <div class="pagination-wrap">
+        <el-pagination
+          v-model:current-page="pagination.current"
+          v-model:page-size="pagination.size"
+          :page-sizes="[9, 18, 27]"
+          :total="filteredList.length"
+          layout="total, sizes, prev, pager, next"
+          background
+        />
+      </div>
+    </div>
+
+    <!-- 新增收藏组弹窗 -->
+    <el-dialog v-model="addGroupVisible" title="新增收藏组" width="420px" :before-close="() => addGroupVisible = false">
+      <el-form ref="addGroupFormRef" :model="addGroupForm" :rules="addGroupRules" label-width="90px">
+        <el-form-item label="组名称" prop="name">
+          <el-input v-model="addGroupForm.name" placeholder="请输入收藏组名称" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="addGroupVisible = false">取 消</el-button>
+        <el-button type="primary" @click="handleAddGroupSubmit">确 定</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 收藏到分组弹窗 -->
+    <el-dialog v-model="collectDialogVisible" title="收藏到分组" width="420px" :before-close="() => collectDialogVisible = false">
+      <el-form label-width="90px">
+        <el-form-item label="选择分组">
+          <el-select v-model="collectTargetGroup" placeholder="请选择收藏组" style="width: 100%">
+            <el-option v-for="g in customGroups" :key="g.id" :label="g.label" :value="g.id" />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="collectDialogVisible = false">取 消</el-button>
+        <el-button type="primary" @click="handleCollectSubmit">确 定</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 查看视频弹窗 -->
+    <el-dialog v-model="videoDialogVisible" title="视频查看" width="680px" :before-close="() => videoDialogVisible = false">
+      <div class="video-player">
+        <div class="video-placeholder">
+          <el-icon :size="56" color="#fff"><VideoPlay /></el-icon>
+          <span class="video-placeholder-text">视频播放区域</span>
+        </div>
+      </div>
+      <div class="video-detail-info">
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <div class="info-item">
+              <span class="info-label">视频名称:</span>
+              <span class="info-value">{{ currentVideo.name || '-' }}</span>
+            </div>
+          </el-col>
+          <el-col :span="12">
+            <div class="info-item">
+              <span class="info-label">视频状态:</span>
+              <span :class="currentVideo.status === '在线' ? 'status-online' : 'status-offline'">{{ currentVideo.status || '-' }}</span>
+            </div>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <div class="info-item">
+              <span class="info-label">关联设备:</span>
+              <span class="info-value">{{ currentVideo.device || '-' }}</span>
+            </div>
+          </el-col>
+          <el-col :span="12">
+            <div class="info-item">
+              <span class="info-label">安装位置:</span>
+              <span class="info-value">{{ currentVideo.location || '-' }}</span>
+            </div>
+          </el-col>
+        </el-row>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
 <script setup name="Spfzgl">
+import { ref, computed } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { Plus, Delete, Star, VideoPlay } from '@element-plus/icons-vue'
+
+// ==================== 视频模拟数据 ====================
+const videoList = ref([
+  { id: 1, name: '沅陵大道积水点摄像头', type: '积水点', status: '在线', device: '液位计 YW-001', location: '沅陵大道与人民路交叉口', collected: true, collectGroup: 'g1' },
+  { id: 2, name: '城北路段积水监测视频', type: '积水点', status: '在线', device: '液位计 YW-002', location: '城北路段低洼处', collected: true, collectGroup: 'g1' },
+  { id: 3, name: '河西片区积水点监控', type: '积水点', status: '离线', device: '液位计 YW-003', location: '河西片区涵洞入口', collected: false, collectGroup: '' },
+  { id: 4, name: '南环路积水监测摄像头', type: '积水点', status: '在线', device: '液位计 YW-004', location: '南环路低洼路段', collected: false, collectGroup: '' },
+  { id: 5, name: '人民路积水点视频', type: '积水点', status: '在线', device: '液位计 YW-005', location: '人民路地下通道入口', collected: false, collectGroup: '' },
+  { id: 6, name: '开发区排水泵站视频', type: '泵站', status: '在线', device: '流量计 LL-001', location: '开发区排水泵站出口', collected: true, collectGroup: 'g2' },
+  { id: 7, name: '城北泵站监控摄像头', type: '泵站', status: '在线', device: '流量计 LL-002', location: '城北泵站进水口', collected: false, collectGroup: '' },
+  { id: 8, name: '河西泵站视频监控', type: '泵站', status: '离线', device: '流量计 LL-003', location: '河西泵站出水口', collected: false, collectGroup: '' },
+  { id: 9, name: '南环泵站监测视频', type: '泵站', status: '在线', device: '流量计 LL-004', location: '南环泵站机房', collected: true, collectGroup: 'g2' },
+  { id: 10, name: '沅陵大道下穿立交监控', type: '下穿立交', status: '在线', device: '液位计 YW-006', location: '沅陵大道下穿段', collected: false, collectGroup: '' },
+  { id: 11, name: '城北下穿立交视频', type: '下穿立交', status: '在线', device: '液位计 YW-007', location: '城北路下穿段', collected: false, collectGroup: '' },
+  { id: 12, name: '河西下穿涵洞监控', type: '下穿立交', status: '离线', device: '液位计 YW-008', location: '河西大道下穿涵洞', collected: false, collectGroup: '' },
+  { id: 13, name: '人民路管网监测视频', type: '管网', status: '在线', device: '流量计 LL-005', location: '人民路主干排水管道', collected: false, collectGroup: '' },
+  { id: 14, name: '城北路段管网监控', type: '管网', status: '在线', device: '流量计 LL-006', location: '城北路段排水管网', collected: false, collectGroup: '' },
+  { id: 15, name: '开发区管网监测摄像头', type: '管网', status: '离线', device: '流量计 LL-007', location: '开发区排水管网出口', collected: false, collectGroup: '' }
+])
+
+// ==================== 分组树 ====================
+const typeCountMap = computed(() => {
+  const map = {}
+  videoList.value.forEach(v => {
+    map[v.type] = (map[v.type] || 0) + 1
+  })
+  return map
+})
+
+const customGroups = ref([
+  { id: 'g1', label: '常用监测组', custom: true, count: 2 },
+  { id: 'g2', label: '泵站重点组', custom: true, count: 2 }
+])
+
+let groupSeq = 3
+
+const treeData = computed(() => {
+  return [
+    { id: 'all', label: '全部视频', count: videoList.value.length },
+    {
+      id: 'type',
+      label: '按类型分组',
+      children: [
+        { id: 'type-积水点', label: '积水点视频', count: typeCountMap.value['积水点'] || 0 },
+        { id: 'type-泵站', label: '泵站视频', count: typeCountMap.value['泵站'] || 0 },
+        { id: 'type-下穿立交', label: '下穿立交视频', count: typeCountMap.value['下穿立交'] || 0 },
+        { id: 'type-管网', label: '管网视频', count: typeCountMap.value['管网'] || 0 }
+      ]
+    },
+    {
+      id: 'fav',
+      label: '我的收藏',
+      children: customGroups.value
+    }
+  ]
+})
+
+const treeProps = { children: 'children', label: 'label' }
+const treeRef = ref(null)
+const currentNodeId = ref('all')
+
+function handleNodeClick(data) {
+  currentNodeId.value = data.id
+  pagination.value.current = 1
+}
+
+// ==================== 搜索 ====================
+const queryParams = ref({ name: '', status: '' })
+
+function handleQuery() {
+  pagination.value.current = 1
+}
+
+function resetQuery() {
+  queryParams.value = { name: '', status: '' }
+  pagination.value.current = 1
+}
 
+// ==================== 过滤 + 分页 ====================
+const filteredList = computed(() => {
+  return videoList.value.filter(item => {
+    const nameMatch = !queryParams.value.name || item.name.includes(queryParams.value.name)
+    const statusMatch = !queryParams.value.status || item.status === queryParams.value.status
+    let groupMatch = true
+    if (currentNodeId.value === 'all') {
+      groupMatch = true
+    } else if (currentNodeId.value.startsWith('type-')) {
+      const typeName = currentNodeId.value.replace('type-', '')
+      groupMatch = item.type === typeName
+    } else if (currentNodeId.value === 'fav' || currentNodeId.value === 'type') {
+      groupMatch = true
+    } else {
+      // 自定义收藏组
+      groupMatch = item.collected && item.collectGroup === currentNodeId.value
+    }
+    return nameMatch && statusMatch && groupMatch
+  })
+})
+
+const pagination = ref({ current: 1, size: 9 })
+
+const pagedList = computed(() => {
+  const start = (pagination.value.current - 1) * pagination.value.size
+  return filteredList.value.slice(start, start + pagination.value.size)
+})
+
+// ==================== 多选 ====================
+const selectedIds = ref([])
+
+function toggleSelect(id) {
+  const idx = selectedIds.value.indexOf(id)
+  if (idx > -1) {
+    selectedIds.value.splice(idx, 1)
+  } else {
+    selectedIds.value.push(id)
+  }
+}
+
+// ==================== 新增收藏组 ====================
+const addGroupVisible = ref(false)
+const addGroupFormRef = ref(null)
+const addGroupForm = ref({ name: '' })
+const addGroupRules = { name: [{ required: true, message: '请输入组名称', trigger: 'blur' }] }
+
+function handleAddGroup() {
+  addGroupForm.value = { name: '' }
+  addGroupVisible.value = true
+}
+
+function handleAddGroupSubmit() {
+  addGroupFormRef.value.validate(valid => {
+    if (!valid) return
+    const newId = 'g' + groupSeq++
+    customGroups.value.push({ id: newId, label: addGroupForm.value.name, custom: true, count: 0 })
+    ElMessage.success('新增收藏组成功')
+    addGroupVisible.value = false
+  })
+}
+
+// ==================== 删除收藏组 ====================
+function handleDeleteGroup(node, data) {
+  ElMessageBox.confirm(`确认删除收藏组"${data.label}"吗?`, '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  }).then(() => {
+    const idx = customGroups.value.findIndex(g => g.id === data.id)
+    if (idx > -1) {
+      // 取消该组下视频的收藏
+      videoList.value.forEach(v => {
+        if (v.collectGroup === data.id) {
+          v.collected = false
+          v.collectGroup = ''
+        }
+      })
+      customGroups.value.splice(idx, 1)
+      if (currentNodeId.value === data.id) {
+        currentNodeId.value = 'all'
+      }
+      ElMessage.success('删除成功')
+    }
+  }).catch(() => {})
+}
+
+// ==================== 收藏 ====================
+const collectDialogVisible = ref(false)
+const collectTargetGroup = ref('')
+
+function handleCollectBatch() {
+  if (customGroups.value.length === 0) {
+    ElMessage.warning('请先创建收藏组')
+    return
+  }
+  collectTargetGroup.value = customGroups.value[0].id
+  collectDialogVisible.value = true
+}
+
+function handleCollectSubmit() {
+  if (!collectTargetGroup.value) {
+    ElMessage.warning('请选择收藏组')
+    return
+  }
+  selectedIds.value.forEach(id => {
+    const v = videoList.value.find(item => item.id === id)
+    if (v) {
+      v.collected = true
+      v.collectGroup = collectTargetGroup.value
+    }
+  })
+  // 更新收藏组计数
+  updateGroupCounts()
+  ElMessage.success('收藏成功')
+  selectedIds.value = []
+  collectDialogVisible.value = false
+}
+
+function handleToggleCollect(item) {
+  if (item.collected) {
+    item.collected = false
+    item.collectGroup = ''
+    ElMessage.success('已取消收藏')
+  } else {
+    if (customGroups.value.length === 0) {
+      ElMessage.warning('请先创建收藏组')
+      return
+    }
+    // 默认收藏到第一个组
+    item.collected = true
+    item.collectGroup = customGroups.value[0].id
+    ElMessage.success('已收藏到「' + customGroups.value[0].label + '」')
+  }
+  updateGroupCounts()
+}
+
+function updateGroupCounts() {
+  customGroups.value.forEach(g => {
+    g.count = videoList.value.filter(v => v.collected && v.collectGroup === g.id).length
+  })
+}
+
+// ==================== 查看视频 ====================
+const videoDialogVisible = ref(false)
+const currentVideo = ref({})
+
+function handleViewVideo(item) {
+  currentVideo.value = { ...item }
+  videoDialogVisible.value = true
+}
 </script>
 
 <style scoped>
+.spfzgl-container {
+  display: flex;
+  height: calc(100vh - 84px);
+  background: #f5f7fa;
+}
+
+.left-panel {
+  width: 250px;
+  min-width: 250px;
+  background: #fff;
+  border-right: 1px solid #e4e7ed;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+
+.panel-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 14px 16px;
+  border-bottom: 1px solid #e4e7ed;
+}
+
+.panel-title {
+  font-size: 15px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.left-panel :deep(.el-tree) {
+  flex: 1;
+  overflow-y: auto;
+  padding: 8px 0;
+}
+
+.tree-node {
+  display: flex;
+  align-items: center;
+  flex: 1;
+  font-size: 14px;
+}
+
+.tree-node-label {
+  flex: 1;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.tree-node-count {
+  color: #909399;
+  font-size: 12px;
+  margin-left: 4px;
+}
+
+.tree-node-actions {
+  margin-left: 8px;
+  display: none;
+}
+
+.tree-node:hover .tree-node-actions {
+  display: inline-flex;
+}
+
+.action-icon {
+  color: #f56c6c;
+  cursor: pointer;
+  font-size: 14px;
+}
+
+.action-icon:hover {
+  color: #e04040;
+}
+
+.right-panel {
+  flex: 1;
+  padding: 16px;
+  overflow-y: auto;
+}
+
+.search-form {
+  margin-bottom: 12px;
+  background: #fff;
+  padding: 16px 16px 0;
+  border-radius: 4px;
+}
+
+.toolbar {
+  margin-bottom: 12px;
+}
+
+.video-card {
+  background: #fff;
+  border-radius: 6px;
+  overflow: hidden;
+  margin-bottom: 16px;
+  border: 2px solid transparent;
+  cursor: pointer;
+  transition: border-color 0.2s, box-shadow 0.2s;
+}
+
+.video-card:hover {
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+}
+
+.video-card.is-selected {
+  border-color: #409eff;
+}
+
+.video-thumb {
+  width: 100%;
+  height: 200px;
+  background: #000;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.video-info {
+  padding: 12px;
+}
+
+.video-name {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 8px;
+}
+
+.name-text {
+  font-size: 14px;
+  font-weight: 500;
+  color: #303133;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  flex: 1;
+}
+
+.video-meta {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 6px;
+}
+
+.status-tag {
+  display: inline-block;
+  padding: 1px 8px;
+  border-radius: 3px;
+  font-size: 12px;
+  font-weight: 500;
+}
+
+.status-tag.online {
+  background: #e1f3d8;
+  color: #67c23a;
+}
+
+.status-tag.offline {
+  background: #fde2e2;
+  color: #f56c6c;
+}
+
+.meta-text {
+  font-size: 13px;
+  color: #909399;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.video-actions {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  margin-top: 8px;
+  padding-top: 8px;
+  border-top: 1px solid #f0f0f0;
+}
+
+.pagination-wrap {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 16px;
+}
+
+/* 查看视频弹窗 */
+.video-player {
+  margin-bottom: 20px;
+}
+
+.video-placeholder {
+  width: 100%;
+  height: 320px;
+  background: #000;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  border-radius: 4px;
+}
+
+.video-placeholder-text {
+  color: #999;
+  margin-top: 10px;
+  font-size: 14px;
+}
+
+.video-detail-info {
+  padding: 0 4px;
+}
+
+.info-item {
+  display: flex;
+  align-items: center;
+  margin-bottom: 12px;
+  line-height: 32px;
+}
+
+.info-label {
+  color: #909399;
+  font-size: 14px;
+  white-space: nowrap;
+}
+
+.info-value {
+  color: #303133;
+  font-size: 14px;
+}
+
+.status-online {
+  color: #67c23a;
+  font-weight: bold;
+}
 
+.status-offline {
+  color: #f56c6c;
+  font-weight: bold;
+}
 </style>

+ 427 - 1
src/views/subSystem/drainage/spjc/spgngl.vue

@@ -1,13 +1,439 @@
 <template>
   <div class="app-container">
-    <h2>视频关联管理</h2>
+    <!-- 搜索区域 -->
+    <el-form :model="queryParams" ref="queryRef" :inline="true" class="search-form">
+      <el-form-item label="设备名称">
+        <el-input v-model="queryParams.deviceName" placeholder="请输入" clearable style="width: 180px" />
+      </el-form-item>
+      <el-form-item label="所属区域">
+        <el-select v-model="queryParams.area" placeholder="请选择" clearable style="width: 180px">
+          <el-option v-for="item in areaOptions" :key="item" :label="item" :value="item" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="设备类型">
+        <el-select v-model="queryParams.deviceType" placeholder="请选择" clearable style="width: 180px">
+          <el-option v-for="item in deviceTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="关联状态">
+        <el-select v-model="queryParams.status" placeholder="请选择" clearable style="width: 180px">
+          <el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" @click="handleQuery">搜索</el-button>
+        <el-button @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <!-- 操作按钮区域 -->
+    <div class="toolbar">
+      <el-button type="primary" @click="handleAdd">新增关联</el-button>
+      <el-button type="danger" plain :disabled="selectedRows.length === 0" @click="handleBatchUnbind">批量解除关联</el-button>
+    </div>
+
+    <!-- 数据表格 -->
+    <el-table :data="pagedList" border style="width: 100%" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="序号" type="index" width="60" align="center" />
+      <el-table-column label="设备编号" prop="deviceNo" min-width="130" />
+      <el-table-column label="设备名称" prop="deviceName" min-width="140" />
+      <el-table-column label="设备类型" prop="deviceType" min-width="110">
+        <template #default="{ row }">
+          <span>{{ deviceTypeText(row.deviceType) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="安装位置" prop="location" min-width="150" />
+      <el-table-column label="所属区域" prop="area" min-width="100" />
+      <el-table-column label="关联视频" prop="videoName" min-width="150">
+        <template #default="{ row }">
+          <span>{{ row.videoName || '-' }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="关联状态" prop="status" min-width="100" align="center">
+        <template #default="{ row }">
+          <el-tag :type="row.status === '1' ? 'success' : 'info'">{{ row.status === '1' ? '已关联' : '未关联' }}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" width="220" align="center" fixed="right">
+        <template #default="{ row }">
+          <el-button link type="primary" @click="handleBind(row)" v-if="row.status !== '1'">关联视频</el-button>
+          <el-button link type="danger" @click="handleUnbind(row)" v-if="row.status === '1'">解除关联</el-button>
+          <el-button link type="primary" @click="handleViewVideo(row)" v-if="row.status === '1'">查看视频</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 分页 -->
+    <div class="pagination-wrap">
+      <el-pagination
+        v-model:current-page="pagination.current"
+        v-model:page-size="pagination.size"
+        :page-sizes="[10, 20, 50]"
+        :total="filteredList.length"
+        layout="total, sizes, prev, pager, next, jumper"
+        background
+      />
+    </div>
+
+    <!-- 新增关联弹窗 -->
+    <el-dialog v-model="bindDialogVisible" title="设备关联视频" width="560px" :before-close="handleBindClose">
+      <el-form ref="bindFormRef" :model="bindForm" :rules="bindRules" label-width="100px">
+        <el-form-item label="监测设备" prop="deviceId">
+          <el-select v-model="bindForm.deviceId" placeholder="请选择监测设备" style="width: 100%" filterable>
+            <el-option v-for="item in unboundDevices" :key="item.id" :label="item.deviceName + '(' + item.deviceNo + ')'" :value="item.id" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="关联视频" prop="videoId">
+          <el-select v-model="bindForm.videoId" placeholder="请选择关联视频" style="width: 100%" filterable>
+            <el-option v-for="item in videoList" :key="item.id" :label="item.name" :value="item.id" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="关联说明" prop="remark">
+          <el-input v-model="bindForm.remark" type="textarea" :rows="3" placeholder="请输入关联说明" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="handleBindClose">取 消</el-button>
+        <el-button type="primary" @click="handleBindSubmit">确 定</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 查看视频弹窗 -->
+    <el-dialog v-model="videoDialogVisible" title="关联视频查看" width="700px" :before-close="handleVideoClose">
+      <div class="video-player">
+        <div class="video-placeholder">
+          <el-icon :size="48" color="#fff"><VideoPlay /></el-icon>
+          <span class="video-placeholder-text">视频播放区域</span>
+        </div>
+      </div>
+      <div class="video-info">
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <div class="info-item">
+              <span class="info-label">视频名称:</span>
+              <span class="info-value">{{ currentVideo.videoName || '-' }}</span>
+            </div>
+          </el-col>
+          <el-col :span="12">
+            <div class="info-item">
+              <span class="info-label">关联设备:</span>
+              <span class="info-value">{{ currentVideo.deviceName || '-' }}</span>
+            </div>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <div class="info-item">
+              <span class="info-label">安装位置:</span>
+              <span class="info-value">{{ currentVideo.location || '-' }}</span>
+            </div>
+          </el-col>
+          <el-col :span="12">
+            <div class="info-item">
+              <span class="info-label">视频状态:</span>
+              <span class="info-value">
+                <span :class="currentVideo.videoStatus === '在线' ? 'status-online' : 'status-offline'">
+                  {{ currentVideo.videoStatus || '-' }}
+                </span>
+              </span>
+            </div>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <div class="info-item">
+              <span class="info-label">最近记录时间:</span>
+              <span class="info-value">{{ currentVideo.lastRecordTime || '-' }}</span>
+            </div>
+          </el-col>
+        </el-row>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
 <script setup name="Spgngl">
+import { ref, computed } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { VideoPlay } from '@element-plus/icons-vue'
+
+// ==================== 选项配置 ====================
+const areaOptions = ['沅陵大道', '城北路段', '河西片区', '人民路', '南环路', '开发区']
+
+const deviceTypeOptions = [
+  { label: '液位计', value: '1' },
+  { label: '流量计', value: '2' },
+  { label: '雨量计', value: '3' },
+  { label: '水质监测仪', value: '4' },
+  { label: '视频监控', value: '5' }
+]
+
+const statusOptions = [
+  { label: '已关联', value: '1' },
+  { label: '未关联', value: '0' }
+]
+
+function deviceTypeText(val) {
+  const map = { '1': '液位计', '2': '流量计', '3': '雨量计', '4': '水质监测仪', '5': '视频监控' }
+  return map[val] || '-'
+}
+
+// ==================== 视频列表 ====================
+const videoList = ref([
+  { id: 1, name: '沅陵大道与人民路交叉口摄像头' },
+  { id: 2, name: '城北路段管网监控摄像头' },
+  { id: 3, name: '河西片区排水口监控' },
+  { id: 4, name: '人民路地下管网监测视频' },
+  { id: 5, name: '南环路污水处理厂入口监控' },
+  { id: 6, name: '开发区排水泵站视频监控' },
+  { id: 7, name: '北环路河道排放口监控' },
+  { id: 8, name: '沅陵大道排水井监测视频' }
+])
+
+// ==================== 搜索参数 ====================
+const queryParams = ref({
+  deviceName: '',
+  area: '',
+  deviceType: '',
+  status: ''
+})
+
+// ==================== 表格数据 ====================
+const tableData = ref([
+  { id: 1, deviceNo: 'DEV-2025-001', deviceName: '城北液位监测仪', deviceType: '1', location: '城北路段排水井A3号', area: '城北路段', videoId: 2, videoName: '城北路段管网监控摄像头', status: '1', remark: '' },
+  { id: 2, deviceNo: 'DEV-2025-002', deviceName: '河西流量监测计', deviceType: '2', location: '河西大道排水管道B7号', area: '河西片区', videoId: 3, videoName: '河西片区排水口监控', status: '1', remark: '' },
+  { id: 3, deviceNo: 'DEV-2025-003', deviceName: '南区水质监测仪', deviceType: '4', location: '南环路污水处理厂入口', area: '南环路', videoId: 5, videoName: '南环路污水处理厂入口监控', status: '1', remark: '' },
+  { id: 4, deviceNo: 'DEV-2025-004', deviceName: '东区雨量计', deviceType: '3', location: '东环路市政大楼楼顶', area: '开发区', videoId: null, videoName: '', status: '0', remark: '' },
+  { id: 5, deviceNo: 'DEV-2025-005', deviceName: '人民路液位计', deviceType: '1', location: '人民路地下管网C2号', area: '人民路', videoId: 4, videoName: '人民路地下管网监测视频', status: '1', remark: '' },
+  { id: 6, deviceNo: 'DEV-2025-006', deviceName: '开发区流量计', deviceType: '2', location: '开发区排水泵站出口', area: '开发区', videoId: null, videoName: '', status: '0', remark: '' },
+  { id: 7, deviceNo: 'DEV-2025-007', deviceName: '沅陵大道雨量计', deviceType: '3', location: '沅陵大道气象站', area: '沅陵大道', videoId: 1, videoName: '沅陵大道与人民路交叉口摄像头', status: '1', remark: '' },
+  { id: 8, deviceNo: 'DEV-2025-008', deviceName: '河西水质监测仪', deviceType: '4', location: '河西片区河道排放口', area: '河西片区', videoId: null, videoName: '', status: '0', remark: '' },
+  { id: 9, deviceNo: 'DEV-2025-009', deviceName: '城北视频监控', deviceType: '5', location: '城北路段排水泵站', area: '城北路段', videoId: null, videoName: '', status: '0', remark: '' },
+  { id: 10, deviceNo: 'DEV-2025-010', deviceName: '南环路液位计', deviceType: '1', location: '南环路排水井D5号', area: '南环路', videoId: null, videoName: '', status: '0', remark: '' },
+  { id: 11, deviceNo: 'DEV-2025-011', deviceName: '人民路流量计', deviceType: '2', location: '人民路主干排水管道', area: '人民路', videoId: 4, videoName: '人民路地下管网监测视频', status: '1', remark: '' },
+  { id: 12, deviceNo: 'DEV-2025-012', deviceName: '沅陵大道液位计', deviceType: '1', location: '沅陵大道排水井E2号', area: '沅陵大道', videoId: 8, videoName: '沅陵大道排水井监测视频', status: '1', remark: '' }
+])
+
+// 多选
+const selectedRows = ref([])
+function handleSelectionChange(selection) {
+  selectedRows.value = selection
+}
+
+// 过滤后的列表
+const filteredList = computed(() => {
+  return tableData.value.filter(item => {
+    const nameMatch = !queryParams.value.deviceName || item.deviceName.includes(queryParams.value.deviceName)
+    const areaMatch = !queryParams.value.area || item.area === queryParams.value.area
+    const typeMatch = !queryParams.value.deviceType || item.deviceType === queryParams.value.deviceType
+    const statusMatch = !queryParams.value.status || item.status === queryParams.value.status
+    return nameMatch && areaMatch && typeMatch && statusMatch
+  })
+})
+
+// 分页
+const pagination = ref({
+  current: 1,
+  size: 10
+})
+
+const pagedList = computed(() => {
+  const start = (pagination.value.current - 1) * pagination.value.size
+  const end = start + pagination.value.size
+  return filteredList.value.slice(start, end)
+})
+
+// 搜索
+function handleQuery() {
+  pagination.value.current = 1
+}
+
+// 重置
+function resetQuery() {
+  queryParams.value = {
+    deviceName: '',
+    area: '',
+    deviceType: '',
+    status: ''
+  }
+  pagination.value.current = 1
+}
+
+// ==================== 新增关联弹窗 ====================
+const bindDialogVisible = ref(false)
+const bindFormRef = ref(null)
+
+const unboundDevices = computed(() => {
+  return tableData.value.filter(item => item.status === '0')
+})
+
+const bindForm = ref({
+  deviceId: null,
+  videoId: null,
+  remark: ''
+})
 
+const bindRules = {
+  deviceId: [{ required: true, message: '请选择监测设备', trigger: 'change' }],
+  videoId: [{ required: true, message: '请选择关联视频', trigger: 'change' }]
+}
+
+function handleAdd() {
+  bindForm.value = { deviceId: null, videoId: null, remark: '' }
+  bindDialogVisible.value = true
+}
+
+function handleBind(row) {
+  bindForm.value = { deviceId: row.id, videoId: null, remark: '' }
+  bindDialogVisible.value = true
+}
+
+function handleBindClose() {
+  bindDialogVisible.value = false
+}
+
+function handleBindSubmit() {
+  bindFormRef.value.validate(valid => {
+    if (!valid) return
+    const device = tableData.value.find(item => item.id === bindForm.value.deviceId)
+    const video = videoList.value.find(item => item.id === bindForm.value.videoId)
+    if (device && video) {
+      device.videoId = video.id
+      device.videoName = video.name
+      device.status = '1'
+      device.remark = bindForm.value.remark
+      ElMessage.success('关联成功')
+    }
+    bindDialogVisible.value = false
+  })
+}
+
+// ==================== 解除关联 ====================
+function handleUnbind(row) {
+  ElMessageBox.confirm(`确认解除设备"${row.deviceName}"与视频"${row.videoName}"的关联吗?`, '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  }).then(() => {
+    row.videoId = null
+    row.videoName = ''
+    row.status = '0'
+    row.remark = ''
+    ElMessage.success('解除关联成功')
+  }).catch(() => {})
+}
+
+function handleBatchUnbind() {
+  const boundRows = selectedRows.value.filter(item => item.status === '1')
+  if (boundRows.length === 0) {
+    ElMessage.warning('选中的设备中没有已关联的记录')
+    return
+  }
+  ElMessageBox.confirm(`确认解除选中的 ${boundRows.length} 条关联吗?`, '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  }).then(() => {
+    boundRows.forEach(row => {
+      row.videoId = null
+      row.videoName = ''
+      row.status = '0'
+      row.remark = ''
+    })
+    ElMessage.success('批量解除关联成功')
+  }).catch(() => {})
+}
+
+// ==================== 查看视频弹窗 ====================
+const videoDialogVisible = ref(false)
+const currentVideo = ref({})
+
+function handleViewVideo(row) {
+  currentVideo.value = {
+    videoName: row.videoName,
+    deviceName: row.deviceName,
+    location: row.location,
+    videoStatus: Math.random() > 0.3 ? '在线' : '离线',
+    lastRecordTime: '2025-12-15 14:32:18'
+  }
+  videoDialogVisible.value = true
+}
+
+function handleVideoClose() {
+  videoDialogVisible.value = false
+}
 </script>
 
 <style scoped>
+.app-container {
+  padding: 20px;
+}
+
+.search-form {
+  margin-bottom: 16px;
+}
+
+.toolbar {
+  margin-bottom: 16px;
+}
+
+.pagination-wrap {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 16px;
+}
+
+.video-player {
+  margin-bottom: 20px;
+}
+
+.video-placeholder {
+  width: 100%;
+  height: 300px;
+  background: #000;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  border-radius: 4px;
+}
+
+.video-placeholder-text {
+  color: #999;
+  margin-top: 10px;
+  font-size: 14px;
+}
+
+.video-info {
+  padding: 0 4px;
+}
+
+.info-item {
+  display: flex;
+  align-items: center;
+  margin-bottom: 12px;
+  line-height: 32px;
+}
+
+.info-label {
+  color: #909399;
+  font-size: 14px;
+  white-space: nowrap;
+}
+
+.info-value {
+  color: #303133;
+  font-size: 14px;
+}
+
+.status-online {
+  color: #67c23a;
+  font-weight: bold;
+}
 
+.status-offline {
+  color: #f56c6c;
+  font-weight: bold;
+}
 </style>

+ 336 - 2
src/views/subSystem/drainage/spjc/spplgl.vue

@@ -1,13 +1,347 @@
 <template>
-  <div class="app-container">
-    <h2>视频批量管理</h2>
+  <div class="spplgl-container">
+    <!-- 顶部工具栏 -->
+    <div class="toolbar">
+      <div class="toolbar-left">
+        <span class="toolbar-label">视图模式:</span>
+        <el-button-group>
+          <el-button :type="gridMode === 1 ? 'primary' : 'default'" @click="gridMode = 1">1宫格</el-button>
+          <el-button :type="gridMode === 4 ? 'primary' : 'default'" @click="gridMode = 4">4宫格</el-button>
+          <el-button :type="gridMode === 9 ? 'primary' : 'default'" @click="gridMode = 9">9宫格</el-button>
+        </el-button-group>
+        <span class="toolbar-label" style="margin-left: 20px;">监控视频:</span>
+        <el-select
+          v-model="selectedVideoIds"
+          multiple
+          collapse-tags
+          collapse-tags-tooltip
+          placeholder="选择监控视频"
+          style="width: 340px"
+        >
+          <el-option v-for="v in allVideos" :key="v.id" :label="v.name" :value="v.id" />
+        </el-select>
+      </div>
+      <div class="toolbar-right">
+        <el-button @click="handleFullscreen" :icon="FullScreen">全屏</el-button>
+        <el-button :disabled="currentPage <= 1" @click="currentPage--">上一页</el-button>
+        <span class="page-info">{{ currentPage }} / {{ totalPages }}</span>
+        <el-button :disabled="currentPage >= totalPages" @click="currentPage++">下一页</el-button>
+      </div>
+    </div>
+
+    <!-- 视频宫格区域 -->
+    <div class="grid-area" ref="gridAreaRef">
+      <div
+        class="video-grid"
+        :style="gridStyle"
+      >
+        <div
+          v-for="(slot, idx) in currentSlots"
+          :key="slot ? slot.id : 'empty-' + idx"
+          class="grid-cell"
+          @dblclick="handleDblClick(slot)"
+        >
+          <template v-if="slot">
+            <div class="cell-player">
+              <el-icon :size="gridMode <= 4 ? 48 : 32" color="#fff"><VideoPlay /></el-icon>
+              <span class="cell-player-name">{{ slot.name }}</span>
+            </div>
+            <div class="cell-bar">
+              <span class="cell-bar-name">{{ slot.name }}</span>
+              <span :class="['cell-bar-status', slot.status === '在线' ? 'online' : 'offline']">{{ slot.status }}</span>
+              <span class="cell-bar-time">{{ currentTime }}</span>
+            </div>
+            <span class="cell-index">{{ idx + 1 }}</span>
+          </template>
+          <template v-else>
+            <div class="cell-player cell-empty">
+              <el-icon :size="32" color="#555"><VideoPlay /></el-icon>
+              <span class="cell-empty-text">未选择视频</span>
+            </div>
+            <span class="cell-index">{{ idx + 1 }}</span>
+          </template>
+        </div>
+      </div>
+    </div>
   </div>
 </template>
 
 <script setup name="Spplgl">
+import { ref, computed, onMounted, onUnmounted } from 'vue'
+import { FullScreen, VideoPlay } from '@element-plus/icons-vue'
+import { ElMessage } from 'element-plus'
+
+// ==================== 模拟视频数据 ====================
+const allVideos = ref([
+  { id: 1, name: '沅陵大道积水点摄像头', status: '在线', type: '积水点' },
+  { id: 2, name: '城北路段积水监测视频', status: '在线', type: '积水点' },
+  { id: 3, name: '河西片区积水点监控', status: '离线', type: '积水点' },
+  { id: 4, name: '南环路积水监测摄像头', status: '在线', type: '积水点' },
+  { id: 5, name: '人民路积水点视频', status: '在线', type: '积水点' },
+  { id: 6, name: '开发区排水泵站视频', status: '在线', type: '泵站' },
+  { id: 7, name: '城北泵站监控摄像头', status: '在线', type: '泵站' },
+  { id: 8, name: '河西泵站视频监控', status: '离线', type: '泵站' },
+  { id: 9, name: '沅陵大道下穿立交监控', status: '在线', type: '下穿立交' },
+  { id: 10, name: '城北下穿立交视频', status: '在线', type: '下穿立交' },
+  { id: 11, name: '人民路管网监测视频', status: '在线', type: '管网' },
+  { id: 12, name: '城北路段管网监控', status: '离线', type: '管网' }
+])
+
+// 默认选中前4个
+const selectedVideoIds = ref([1, 2, 3, 4])
+
+// ==================== 宫格模式 ====================
+const gridMode = ref(4)
+const currentPage = ref(1)
+const gridAreaRef = ref(null)
+
+const selectedVideos = computed(() => {
+  return selectedVideoIds.value
+    .map(id => allVideos.value.find(v => v.id === id))
+    .filter(Boolean)
+})
+
+const totalPages = computed(() => {
+  if (selectedVideos.value.length === 0) return 1
+  return Math.ceil(selectedVideos.value.length / gridMode.value)
+})
+
+const currentSlots = computed(() => {
+  const start = (currentPage.value - 1) * gridMode.value
+  const end = start + gridMode.value
+  const pageVideos = selectedVideos.value.slice(start, end)
+  // 补齐空位
+  const slots = []
+  for (let i = 0; i < gridMode.value; i++) {
+    slots.push(pageVideos[i] || null)
+  }
+  return slots
+})
+
+const gridStyle = computed(() => {
+  const cols = gridMode.value === 1 ? 1 : gridMode.value === 4 ? 2 : 3
+  return {
+    'grid-template-columns': `repeat(${cols}, 1fr)`,
+    'grid-template-rows': gridMode.value === 1 ? '1fr' : (gridMode.value === 4 ? 'repeat(2, 1fr)' : 'repeat(3, 1fr)')
+  }
+})
+
+// ==================== 实时时间 ====================
+const currentTime = ref('')
+let timer = null
 
+function updateTime() {
+  const now = new Date()
+  const pad = n => String(n).padStart(2, '0')
+  currentTime.value = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`
+}
+
+onMounted(() => {
+  updateTime()
+  timer = setInterval(updateTime, 1000)
+})
+
+onUnmounted(() => {
+  if (timer) clearInterval(timer)
+})
+
+// ==================== 交互 ====================
+function handleDblClick(slot) {
+  if (!slot) return
+  if (gridMode.value !== 1) {
+    gridMode.value = 1
+    // 找到该视频在选中列表中的索引,跳转到对应页
+    const idx = selectedVideos.value.findIndex(v => v.id === slot.id)
+    if (idx >= 0) {
+      currentPage.value = Math.floor(idx / 1) + 1
+    }
+    ElMessage.info(`已放大查看:${slot.name}`)
+  } else {
+    gridMode.value = 4
+    currentPage.value = 1
+  }
+}
+
+function handleFullscreen() {
+  if (!gridAreaRef.value) return
+  if (document.fullscreenElement) {
+    document.exitFullscreen()
+  } else {
+    gridAreaRef.value.requestFullscreen()
+  }
+}
 </script>
 
 <style scoped>
+.spplgl-container {
+  display: flex;
+  flex-direction: column;
+  height: calc(100vh - 84px);
+  background: #1a1a2e;
+}
+
+.toolbar {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 10px 20px;
+  background: #16213e;
+  border-bottom: 1px solid #2a2a4a;
+  flex-shrink: 0;
+}
+
+.toolbar-left {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.toolbar-right {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.toolbar-label {
+  color: #c0c4cc;
+  font-size: 14px;
+  white-space: nowrap;
+}
+
+.page-info {
+  color: #c0c4cc;
+  font-size: 14px;
+  padding: 0 8px;
+  min-width: 60px;
+  text-align: center;
+}
+
+/* 深色主题下 select 样式调整 */
+.toolbar :deep(.el-select) {
+  --el-fill-color-blank: #1a1a2e;
+}
+
+.toolbar :deep(.el-input__wrapper) {
+  background: #1a1a2e;
+  box-shadow: 0 0 0 1px #3a3a5a inset;
+}
+
+.toolbar :deep(.el-input__inner) {
+  color: #c0c4cc;
+}
+
+/* 宫格区域 */
+.grid-area {
+  flex: 1;
+  padding: 8px;
+  overflow: hidden;
+}
+
+.grid-area:fullscreen {
+  padding: 8px;
+  background: #1a1a2e;
+}
+
+.video-grid {
+  display: grid;
+  gap: 6px;
+  height: 100%;
+}
+
+.grid-cell {
+  background: #0f0f23;
+  border-radius: 4px;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  position: relative;
+  border: 1px solid #2a2a4a;
+  cursor: pointer;
+  transition: border-color 0.2s;
+}
+
+.grid-cell:hover {
+  border-color: #409eff;
+}
+
+.cell-player {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  background: #000;
+}
+
+.cell-player-name {
+  color: #666;
+  font-size: 13px;
+  margin-top: 10px;
+}
+
+.cell-empty {
+  background: #0a0a1a;
+}
+
+.cell-empty-text {
+  color: #444;
+  font-size: 13px;
+  margin-top: 8px;
+}
+
+.cell-bar {
+  display: flex;
+  align-items: center;
+  padding: 6px 10px;
+  background: #16213e;
+  gap: 10px;
+  flex-shrink: 0;
+}
+
+.cell-bar-name {
+  color: #c0c4cc;
+  font-size: 12px;
+  flex: 1;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.cell-bar-status {
+  font-size: 12px;
+  font-weight: 500;
+  padding: 0 6px;
+  border-radius: 2px;
+}
+
+.cell-bar-status.online {
+  color: #67c23a;
+  background: rgba(103, 194, 58, 0.15);
+}
+
+.cell-bar-status.offline {
+  color: #f56c6c;
+  background: rgba(245, 108, 108, 0.15);
+}
+
+.cell-bar-time {
+  color: #606266;
+  font-size: 12px;
+  white-space: nowrap;
+}
 
+.cell-index {
+  position: absolute;
+  top: 6px;
+  left: 8px;
+  background: rgba(0, 0, 0, 0.6);
+  color: #fff;
+  font-size: 12px;
+  width: 22px;
+  height: 22px;
+  border-radius: 4px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
 </style>

+ 976 - 2
src/views/subSystem/drainage/ssjc/bzyxqk.vue

@@ -1,13 +1,987 @@
 <template>
-  <div class="app-container">
-    <h2>泵站运行情况</h2>
+  <div class="bzyxqk-screen">
+    <!-- ==================== 顶部标题栏 ==================== -->
+    <div class="screen-header">
+      <div class="header-left">
+        <span class="header-time">{{ currentDateTime }}</span>
+      </div>
+      <div class="header-center">
+        <h1 class="header-title">泵站运行情况GIS一张图</h1>
+      </div>
+      <div class="header-right"></div>
+    </div>
+
+    <!-- ==================== 主体三栏布局 ==================== -->
+    <div class="screen-body">
+      <!-- ========== 左侧面板 ========== -->
+      <div class="panel-left">
+        <!-- 泵站概况 -->
+        <div class="panel-box" style="flex: 0 0 auto;">
+          <div class="panel-title">
+            <span class="title-icon">▶</span> 泵站概况
+          </div>
+          <div class="stats-cards">
+            <div class="stat-card">
+              <div class="stat-val">{{ pumpStats.total }}</div>
+              <div class="stat-label">泵站总数</div>
+            </div>
+            <div class="stat-card">
+              <div class="stat-val green">{{ pumpStats.running }}</div>
+              <div class="stat-label">运行中</div>
+            </div>
+            <div class="stat-card">
+              <div class="stat-val gray">{{ pumpStats.stopped }}</div>
+              <div class="stat-label">已停止</div>
+            </div>
+            <div class="stat-card">
+              <div class="stat-val red">{{ pumpStats.fault }}</div>
+              <div class="stat-label">故障</div>
+            </div>
+          </div>
+          <div class="pie-chart-wrapper">
+            <div id="bzyxqkPieChart" class="chart-area"></div>
+            <div class="pie-legend">
+              <div class="legend-item" v-for="(item, i) in pieData" :key="i">
+                <span class="legend-dot" :style="{ background: item.color }"></span>
+                <span class="legend-label">{{ item.name }}</span>
+                <span class="legend-value">{{ item.value }}</span>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 泵站列表 -->
+        <div class="panel-box" style="flex: 1 1 0;">
+          <div class="panel-title">
+            <span class="title-icon">▶</span> 泵站列表
+          </div>
+          <div class="pump-table-wrap">
+            <table class="dark-table">
+              <thead>
+                <tr>
+                  <th>泵站名称</th>
+                  <th>状态</th>
+                  <th>前池(m)</th>
+                  <th>后池(m)</th>
+                </tr>
+              </thead>
+              <tbody>
+                <tr v-for="(row, i) in pumpList" :key="i"
+                    :class="{ 'row-active': selectedPumpIndex === i }"
+                    @click="selectPump(i)">
+                  <td :title="row.name">{{ row.name }}</td>
+                  <td>
+                    <span :class="'status-tag status-' + row.statusClass">{{ row.status }}</span>
+                  </td>
+                  <td>{{ row.frontLevel }}</td>
+                  <td>{{ row.rearLevel }}</td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </div>
+      </div>
+
+      <!-- ========== 中间地图区域 ========== -->
+      <div class="panel-center">
+        <div class="map-search">
+          <input type="text" placeholder="搜索泵站" class="search-input" v-model="searchKeyword" @input="onSearchPump" />
+          <span class="search-icon">🔍</span>
+        </div>
+        <div id="bzyxqkMap" class="map-container"></div>
+      </div>
+
+      <!-- ========== 右侧面板 ========== -->
+      <div class="panel-right">
+        <!-- 深度曲线 -->
+        <div class="panel-box" style="flex: 1 1 0;">
+          <div class="panel-title">
+            <span class="title-icon">▶</span> 深度曲线
+          </div>
+          <div class="time-btn-group">
+            <button v-for="t in timeTabs" :key="t.label"
+              :class="['time-btn', { active: activeTimeTab === t.label }]"
+              @click="switchTimeTab(t.label)">{{ t.label }}</button>
+          </div>
+          <div id="bzyxqkLineChart" class="chart-area-lg"></div>
+        </div>
+
+        <!-- 视频监控 -->
+        <div class="panel-box" style="flex: 0 0 auto;">
+          <div class="panel-title">
+            <span class="title-icon">▶</span> 视频监控
+          </div>
+          <div class="video-grid">
+            <div class="video-item" v-for="(v, i) in videoList" :key="i">
+              <div class="video-placeholder">
+                <span class="camera-icon">📷</span>
+              </div>
+              <div class="video-name">{{ v.name }}</div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 报警记录 -->
+        <div class="panel-box" style="flex: 1 1 0;">
+          <div class="panel-title">
+            <span class="title-icon">▶</span> 历史报警记录
+          </div>
+          <div class="alert-stats">
+            <div class="alert-stat-card">
+              <div class="alert-stat-val">{{ alarmStats.total }}</div>
+              <div class="alert-stat-label">报警总数</div>
+            </div>
+            <div class="alert-stat-card">
+              <div class="alert-stat-val green">{{ alarmStats.handled }}</div>
+              <div class="alert-stat-label">已处置</div>
+            </div>
+            <div class="alert-stat-card">
+              <div class="alert-stat-val red">{{ alarmStats.unhandled }}</div>
+              <div class="alert-stat-label">未处置</div>
+            </div>
+          </div>
+          <div class="alarm-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 alarmList" :key="i">
+                  <td>{{ i + 1 }}</td>
+                  <td :title="row.station">{{ row.station }}</td>
+                  <td>{{ row.time }}</td>
+                  <td>{{ row.type }}</td>
+                  <td>
+                    <span :class="'handle-tag ' + (row.handled ? 'handled' : 'unhandled')">
+                      {{ row.handled ? '已处置' : '未处置' }}
+                    </span>
+                  </td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </div>
+      </div>
+    </div>
   </div>
 </template>
 
 <script setup name="Bzyxqk">
+import { ref, reactive, onMounted, onUnmounted } from 'vue'
+import * as echarts from 'echarts'
+import locationIcon from '@/assets/images/location.png'
+
+// ==================== 实时时间 ====================
+const currentDateTime = 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()]}`
+}
+
+// ==================== 泵站概况 ====================
+const pumpStats = reactive({ total: 10, running: 6, stopped: 3, fault: 1 })
+
+const pieData = ref([
+  { name: '运行中', value: 6, color: '#67c23a' },
+  { name: '已停止', value: 3, color: '#909399' },
+  { name: '故障', value: 1, color: '#f56c6c' }
+])
+
+// ==================== 泵站列表 ====================
+const pumpList = ref([
+  { name: '沅陵一泵站', status: '运行', statusClass: 'running', frontLevel: 3.2, rearLevel: 1.8, lng: 110.393, lat: 28.452, runHours: 12.5 },
+  { name: '城北泵站', status: '运行', statusClass: 'running', frontLevel: 2.8, rearLevel: 1.5, lng: 110.385, lat: 28.462, runHours: 8.3 },
+  { name: '河西泵站', status: '运行', statusClass: 'running', frontLevel: 4.1, rearLevel: 2.3, lng: 110.378, lat: 28.456, runHours: 15.1 },
+  { name: '人民路泵站', status: '故障', statusClass: 'fault', frontLevel: 5.6, rearLevel: 4.9, lng: 110.396, lat: 28.451, runHours: 0 },
+  { name: '南环泵站', status: '运行', statusClass: 'running', frontLevel: 2.1, rearLevel: 1.2, lng: 110.402, lat: 28.444, runHours: 6.7 },
+  { name: '北环泵站', status: '停止', statusClass: 'stopped', frontLevel: 1.5, rearLevel: 1.3, lng: 110.401, lat: 28.460, runHours: 0 },
+  { name: '西环泵站', status: '运行', statusClass: 'running', frontLevel: 3.6, rearLevel: 2.0, lng: 110.372, lat: 28.449, runHours: 10.4 },
+  { name: '东环泵站', status: '停止', statusClass: 'stopped', frontLevel: 1.8, rearLevel: 1.6, lng: 110.410, lat: 28.454, runHours: 0 },
+  { name: '开发区泵站', status: '运行', statusClass: 'running', frontLevel: 3.0, rearLevel: 1.9, lng: 110.415, lat: 28.458, runHours: 9.2 },
+  { name: '城北二泵站', status: '停止', statusClass: 'stopped', frontLevel: 1.2, rearLevel: 1.1, lng: 110.394, lat: 28.465, runHours: 0 }
+])
+
+const selectedPumpIndex = ref(-1)
+
+function selectPump(index) {
+  selectedPumpIndex.value = index
+  const pump = pumpList.value[index]
+  if (mapInstance && pump) {
+    mapInstance.centerAndZoom(new BMapGL.Point(pump.lng, pump.lat), 16)
+  }
+}
+
+// ==================== 搜索 ====================
+const searchKeyword = ref('')
+
+function onSearchPump() {
+  if (!searchKeyword.value.trim()) return
+  const idx = pumpList.value.findIndex(p => p.name.includes(searchKeyword.value.trim()))
+  if (idx >= 0) {
+    selectPump(idx)
+  }
+}
+
+// ==================== 深度曲线 ====================
+const timeTabs = [
+  { label: '24小时' },
+  { label: '7天' },
+  { label: '15天' },
+  { label: '30天' }
+]
+const activeTimeTab = ref('24小时')
+
+function switchTimeTab(label) {
+  activeTimeTab.value = label
+  initLineChart()
+}
+
+// ==================== 视频监控 ====================
+const videoList = ref([
+  { name: '沅陵一泵站' },
+  { name: '城北泵站' },
+  { name: '河西泵站' },
+  { name: '人民路泵站' }
+])
+
+// ==================== 报警记录 ====================
+const alarmStats = reactive({ total: 12, handled: 8, unhandled: 4 })
+
+const alarmList = ref([
+  { station: '人民路泵站', time: '06-12 08:32', type: '设备故障', handled: false },
+  { station: '河西泵站', time: '06-12 07:15', type: '液位超限', handled: true },
+  { station: '沅陵一泵站', time: '06-11 22:40', type: '泵启停异常', handled: true },
+  { station: '人民路泵站', time: '06-11 18:22', type: '设备故障', handled: false },
+  { station: '南环泵站', time: '06-11 14:05', type: '液位超限', handled: true },
+  { station: '西环泵站', time: '06-11 09:30', type: '泵启停异常', handled: true },
+  { station: '开发区泵站', time: '06-10 20:18', type: '液位超限', handled: true },
+  { station: '城北泵站', time: '06-10 15:42', type: '泵启停异常', handled: true },
+  { station: '人民路泵站', time: '06-10 11:55', type: '设备故障', handled: false },
+  { station: '河西泵站', time: '06-09 23:10', type: '液位超限', handled: true },
+  { station: '沅陵一泵站', time: '06-09 16:38', type: '设备故障', handled: false },
+  { station: '北环泵站', time: '06-09 08:20', type: '泵启停异常', handled: true }
+])
+
+// ==================== 地图初始化 ====================
+let mapInstance = null
+
+function initMap() {
+  const container = document.getElementById('bzyxqkMap')
+  if (!container || typeof BMapGL === 'undefined') {
+    console.warn('BMapGL 未加载或容器不存在')
+    return
+  }
+
+  try {
+    mapInstance = new BMapGL.Map('bzyxqkMap')
+    const centerPoint = new BMapGL.Point(110.393, 28.452)
+    mapInstance.centerAndZoom(centerPoint, 15)
+    mapInstance.enableScrollWheelZoom(true)
+
+    // 添加泵站标记
+    pumpList.value.forEach(pump => {
+      addMapMarker(pump)
+    })
+  } catch (e) {
+    console.error('百度地图初始化失败:', e)
+  }
+}
+
+function addMapMarker(pump) {
+  const statusColor = {
+    'running': '#67c23a',
+    'stopped': '#909399',
+    'fault': '#f56c6c'
+  }
+  const statusText = {
+    'running': '运行',
+    'stopped': '停止',
+    'fault': '故障'
+  }
+  const color = statusColor[pump.statusClass] || '#409eff'
+  const bPoint = new BMapGL.Point(pump.lng, pump.lat)
+
+  const html = `<div style="text-align:center;cursor:pointer;">
+    <div style="color:#fff;background:${color};border-radius:4px;padding:2px 6px;font-size:11px;white-space:nowrap;display:inline-block;margin-bottom:2px;">${pump.name}</div>
+    <div style="color:#fff;font-size:10px;background:rgba(0,0,0,0.6);border-radius:3px;padding:1px 4px;margin-bottom:2px;">${statusText[pump.statusClass]} | 前池${pump.frontLevel}m</div>
+    <div><img src="${locationIcon}" style="width:24px;height:24px;display:block;margin:0 auto;"/></div>
+  </div>`
+
+  const label = new BMapGL.Label(html, {
+    position: bPoint,
+    offset: new BMapGL.Size(-30, -55)
+  })
+  label.setStyle({
+    border: 'none',
+    background: 'transparent',
+    padding: '0'
+  })
+  mapInstance.addOverlay(label)
 
+  // 点击标记弹出详情
+  label.addEventListener('click', function () {
+    const infoContent = `<div style="color:#333;font-size:13px;padding:4px;">
+      <div style="font-weight:bold;margin-bottom:6px;font-size:14px;">${pump.name}</div>
+      <div>运行状态:<b style="color:${color}">${statusText[pump.statusClass]}</b></div>
+      <div>前池液位:<b>${pump.frontLevel} m</b></div>
+      <div>后池液位:<b>${pump.rearLevel} m</b></div>
+      <div>运行时长:<b>${pump.runHours} h</b></div>
+      <div>经度:${pump.lng}</div>
+      <div>纬度:${pump.lat}</div>
+    </div>`
+    const infoWindow = new BMapGL.InfoWindow(infoContent, {
+      width: 220,
+      title: ''
+    })
+    mapInstance.openInfoWindow(infoWindow, bPoint)
+  })
+}
+
+// ==================== ECharts 图表 ====================
+let pieChart = null
+let lineChart = null
+
+function initPieChart() {
+  const dom = document.getElementById('bzyxqkPieChart')
+  if (!dom) return
+  pieChart = echarts.init(dom)
+  const total = pieData.value.reduce((s, d) => s + d.value, 0)
+  pieChart.setOption({
+    tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
+    legend: { show: false },
+    graphic: [{
+      type: 'text',
+      left: 'center',
+      top: '38%',
+      style: { text: '泵站总数', fill: '#a0cfff', fontSize: 12, textAlign: 'center' }
+    }, {
+      type: 'text',
+      left: 'center',
+      top: '52%',
+      style: { text: String(total), fill: '#fff', fontSize: 22, fontWeight: 'bold', textAlign: 'center' }
+    }],
+    series: [{
+      type: 'pie',
+      radius: ['45%', '70%'],
+      center: ['50%', '50%'],
+      label: { show: false },
+      labelLine: { show: false },
+      data: pieData.value.map(d => ({ name: d.name, value: d.value, itemStyle: { color: d.color } })),
+      emphasis: { scale: true, scaleSize: 5 }
+    }]
+  })
+}
+
+function initLineChart() {
+  const dom = document.getElementById('bzyxqkLineChart')
+  if (!dom) return
+  if (lineChart) lineChart.dispose()
+  lineChart = echarts.init(dom)
+
+  let xLabels = []
+  let frontData = []
+  let rearData = []
+
+  if (activeTimeTab.value === '24小时') {
+    for (let i = 0; i < 24; i++) {
+      xLabels.push(String(i).padStart(2, '0') + ':00')
+      frontData.push(Math.round((3 + Math.random() * 3) * 10) / 10)
+      rearData.push(Math.round((1 + Math.random() * 2) * 10) / 10)
+    }
+  } else if (activeTimeTab.value === '7天') {
+    for (let i = 6; i >= 0; i--) {
+      const d = new Date()
+      d.setDate(d.getDate() - i)
+      xLabels.push(`${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')}`)
+      frontData.push(Math.round((2.5 + Math.random() * 3.5) * 10) / 10)
+      rearData.push(Math.round((1 + Math.random() * 2) * 10) / 10)
+    }
+  } else if (activeTimeTab.value === '15天') {
+    for (let i = 14; i >= 0; i--) {
+      const d = new Date()
+      d.setDate(d.getDate() - i)
+      xLabels.push(`${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')}`)
+      frontData.push(Math.round((2 + Math.random() * 4) * 10) / 10)
+      rearData.push(Math.round((0.8 + Math.random() * 2.5) * 10) / 10)
+    }
+  } else {
+    for (let i = 29; i >= 0; i--) {
+      const d = new Date()
+      d.setDate(d.getDate() - i)
+      xLabels.push(`${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')}`)
+      frontData.push(Math.round((2 + Math.random() * 4) * 10) / 10)
+      rearData.push(Math.round((0.8 + Math.random() * 2.5) * 10) / 10)
+    }
+  }
+
+  lineChart.setOption({
+    tooltip: { trigger: 'axis' },
+    legend: {
+      data: ['前池液位', '后池液位'],
+      textStyle: { color: '#a0cfff', fontSize: 11 },
+      top: 0,
+      right: 10,
+      itemWidth: 14,
+      itemHeight: 8
+    },
+    grid: { left: 40, right: 14, top: 30, bottom: 25 },
+    xAxis: {
+      type: 'category',
+      data: xLabels,
+      axisLabel: { color: '#7ec8e3', fontSize: 9, interval: activeTimeTab.value === '24小时' ? 3 : 'auto' },
+      axisLine: { lineStyle: { color: '#1a3a5c' } }
+    },
+    yAxis: {
+      type: 'value',
+      name: 'm',
+      nameTextStyle: { color: '#7ec8e3', fontSize: 10 },
+      axisLabel: { color: '#7ec8e3', fontSize: 10 },
+      splitLine: { lineStyle: { color: '#1a3a5c' } },
+      axisLine: { show: false }
+    },
+    series: [
+      {
+        name: '前池液位',
+        type: 'line',
+        data: frontData,
+        smooth: true,
+        symbol: 'circle',
+        symbolSize: 4,
+        lineStyle: { color: '#409eff', width: 2 },
+        itemStyle: { color: '#409eff' },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: 'rgba(64,158,255,0.3)' },
+            { offset: 1, color: 'rgba(64,158,255,0.02)' }
+          ])
+        }
+      },
+      {
+        name: '后池液位',
+        type: 'line',
+        data: rearData,
+        smooth: true,
+        symbol: 'circle',
+        symbolSize: 4,
+        lineStyle: { color: '#67c23a', width: 2 },
+        itemStyle: { color: '#67c23a' },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: 'rgba(103,194,58,0.3)' },
+            { offset: 1, color: 'rgba(103,194,58,0.02)' }
+          ])
+        }
+      }
+    ]
+  })
+}
+
+// ==================== 生命周期 ====================
+onMounted(() => {
+  updateTime()
+  timer = setInterval(updateTime, 1000)
+
+  setTimeout(() => {
+    initMap()
+    initPieChart()
+    initLineChart()
+  }, 300)
+})
+
+onUnmounted(() => {
+  if (timer) clearInterval(timer)
+  if (pieChart) pieChart.dispose()
+  if (lineChart) lineChart.dispose()
+  if (mapInstance) mapInstance = null
+})
+
+window.addEventListener('resize', () => {
+  if (pieChart) pieChart.resize()
+  if (lineChart) lineChart.resize()
+})
 </script>
 
 <style scoped>
+/* ==================== 全局暗色大屏 ==================== */
+.bzyxqk-screen {
+  width: 100%;
+  height: 100vh;
+  background: #0a1929;
+  color: #fff;
+  font-family: 'Microsoft YaHei', sans-serif;
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
+}
+
+/* ==================== 顶部标题栏 ==================== */
+.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;
+  text-shadow: none;
+}
+
+.header-right {
+  flex: 1;
+  text-align: right;
+}
+
+/* ==================== 主体三栏 ==================== */
+.screen-body {
+  flex: 1;
+  display: flex;
+  padding: 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;
+  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;
+}
+
+/* ==================== 泵站概况统计卡片 ==================== */
+.stats-cards {
+  display: flex;
+  gap: 8px;
+  margin-bottom: 10px;
+  flex-shrink: 0;
+}
+
+.stat-card {
+  flex: 1;
+  background: rgba(10, 25, 50, 0.6);
+  border: 1px solid rgba(64, 158, 255, 0.15);
+  border-radius: 6px;
+  padding: 8px 4px;
+  text-align: center;
+}
+
+.stat-val {
+  font-size: 20px;
+  font-weight: 700;
+  color: #409eff;
+}
+
+.stat-val.green {
+  color: #67c23a;
+}
+
+.stat-val.gray {
+  color: #909399;
+}
+
+.stat-val.red {
+  color: #f56c6c;
+}
+
+.stat-label {
+  font-size: 10px;
+  color: #7ec8e3;
+  margin-top: 2px;
+}
+
+/* ==================== 环形图 ==================== */
+.pie-chart-wrapper {
+  display: flex;
+  align-items: center;
+  height: 140px;
+}
+
+.chart-area {
+  width: 55%;
+  height: 140px;
+}
+
+.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;
+}
+
+/* ==================== 泵站列表表格 ==================== */
+.pump-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);
+  cursor: pointer;
+}
+
+.dark-table tbody tr.row-active {
+  background: rgba(64, 158, 255, 0.2) !important;
+  border-left: 2px solid #409eff;
+}
+
+.status-tag {
+  padding: 1px 6px;
+  border-radius: 3px;
+  font-size: 11px;
+}
+
+.status-running {
+  color: #67c23a;
+  background: rgba(103, 194, 58, 0.15);
+}
+
+.status-stopped {
+  color: #909399;
+  background: rgba(144, 147, 153, 0.15);
+}
+
+.status-fault {
+  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;
+}
+
+.map-search {
+  position: absolute;
+  top: 12px;
+  left: 12px;
+  z-index: 10;
+  display: flex;
+  align-items: center;
+}
+
+.search-input {
+  background: rgba(6, 30, 65, 0.85);
+  border: 1px solid rgba(64, 158, 255, 0.3);
+  color: #a0cfff;
+  padding: 6px 30px 6px 12px;
+  border-radius: 4px;
+  font-size: 12px;
+  outline: none;
+  width: 160px;
+}
+
+.search-input::placeholder {
+  color: #4a6a8a;
+}
+
+.search-icon {
+  position: absolute;
+  right: 8px;
+  font-size: 14px;
+  cursor: pointer;
+}
+
+/* ==================== 深度曲线 ==================== */
+.time-btn-group {
+  display: flex;
+  gap: 0;
+  margin-bottom: 8px;
+  flex-shrink: 0;
+  background: rgba(10, 25, 50, 0.6);
+  border: 1px solid rgba(64, 158, 255, 0.2);
+  border-radius: 4px;
+  overflow: hidden;
+}
+
+.time-btn {
+  flex: 1;
+  background: transparent;
+  border: none;
+  color: #a0cfff;
+  padding: 5px 8px;
+  font-size: 11px;
+  cursor: pointer;
+  transition: all 0.3s;
+  border-right: 1px solid rgba(64, 158, 255, 0.1);
+}
+
+.time-btn:last-child {
+  border-right: none;
+}
+
+.time-btn.active {
+  background: rgba(64, 158, 255, 0.25);
+  color: #fff;
+  font-weight: 600;
+}
+
+.time-btn:hover {
+  background: rgba(64, 158, 255, 0.15);
+}
+
+.chart-area-lg {
+  width: 100%;
+  flex: 1;
+  min-height: 130px;
+}
+
+/* ==================== 视频监控 ==================== */
+.video-grid {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 8px;
+  flex-shrink: 0;
+}
+
+.video-item {
+  text-align: center;
+}
+
+.video-placeholder {
+  background: rgba(10, 25, 50, 0.8);
+  border: 1px solid rgba(64, 158, 255, 0.15);
+  border-radius: 4px;
+  height: 70px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.camera-icon {
+  font-size: 22px;
+  opacity: 0.5;
+}
+
+.video-name {
+  font-size: 10px;
+  color: #a0cfff;
+  margin-top: 4px;
+}
+
+/* ==================== 报警记录 ==================== */
+.alert-stats {
+  display: flex;
+  gap: 8px;
+  margin-bottom: 8px;
+  flex-shrink: 0;
+}
+
+.alert-stat-card {
+  flex: 1;
+  background: rgba(10, 25, 50, 0.6);
+  border: 1px solid rgba(64, 158, 255, 0.15);
+  border-radius: 6px;
+  padding: 8px;
+  text-align: center;
+}
+
+.alert-stat-val {
+  font-size: 20px;
+  font-weight: 700;
+  color: #e6a23c;
+}
+
+.alert-stat-val.green {
+  color: #67c23a;
+}
+
+.alert-stat-val.red {
+  color: #f56c6c;
+}
+
+.alert-stat-label {
+  font-size: 10px;
+  color: #7ec8e3;
+  margin-top: 2px;
+}
+
+.alarm-table-wrap {
+  flex: 1;
+  overflow-y: auto;
+  min-height: 0;
+}
+
+.handle-tag {
+  padding: 1px 6px;
+  border-radius: 3px;
+  font-size: 11px;
+}
+
+.handle-tag.handled {
+  color: #67c23a;
+  background: rgba(103, 194, 58, 0.15);
+}
+
+.handle-tag.unhandled {
+  color: #f56c6c;
+  background: rgba(245, 108, 108, 0.15);
+}
+
+/* ==================== 滚动条样式 ==================== */
+::-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>

+ 975 - 2
src/views/subSystem/drainage/ssjc/gwyxqk.vue

@@ -1,13 +1,986 @@
 <template>
-  <div class="app-container">
-    <h2>管网运行情况</h2>
+  <div class="gwyxqk-screen">
+    <!-- ==================== 顶部标题栏 ==================== -->
+    <div class="screen-header">
+      <div class="header-left">
+        <span class="header-time">{{ currentDateTime }}</span>
+      </div>
+      <div class="header-center">
+        <h1 class="header-title">管网运行情况GIS一张图</h1>
+      </div>
+      <div class="header-right">
+      </div>
+    </div>
+
+    <!-- ==================== 主体三栏布局 ==================== -->
+    <div class="screen-body">
+      <!-- ========== 左侧面板 ========== -->
+      <div class="panel-left">
+        <!-- 管网概况 -->
+        <div class="panel-box">
+          <div class="panel-title">
+            <span class="title-icon">▶</span> 管网概况
+          </div>
+          <div class="stats-cards">
+            <div class="stat-card">
+              <div class="stat-val">{{ overviewStats.total }}</div>
+              <div class="stat-label">监测点总数</div>
+            </div>
+            <div class="stat-card">
+              <div class="stat-val green">{{ overviewStats.normal }}</div>
+              <div class="stat-label">正常运行</div>
+            </div>
+            <div class="stat-card">
+              <div class="stat-val orange">{{ overviewStats.alarm }}</div>
+              <div class="stat-label">异常报警</div>
+            </div>
+            <div class="stat-card">
+              <div class="stat-val gray">{{ overviewStats.offline }}</div>
+              <div class="stat-label">离线设备</div>
+            </div>
+          </div>
+          <div class="pie-chart-wrapper">
+            <div id="gwyxqkPieChart" class="chart-area"></div>
+            <div class="pie-legend">
+              <div class="legend-item" v-for="(item, i) in pieData" :key="i">
+                <span class="legend-dot" :style="{ background: item.color }"></span>
+                <span class="legend-label">{{ item.name }}</span>
+                <span class="legend-value">{{ item.value }}</span>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 实时数据表 -->
+        <div class="panel-box" style="flex: 1;">
+          <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>流量(m³/h)</th>
+                  <th>液位(m)</th>
+                  <th>状态</th>
+                </tr>
+              </thead>
+              <tbody>
+                <tr v-for="(row, i) in realtimeData" :key="i">
+                  <td :title="row.name">{{ row.name }}</td>
+                  <td>{{ row.flow }}</td>
+                  <td>{{ row.level }}</td>
+                  <td>
+                    <span :class="'status-tag status-' + row.statusClass">{{ row.status }}</span>
+                  </td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </div>
+      </div>
+
+      <!-- ========== 中间地图区域 ========== -->
+      <div class="panel-center">
+        <div class="map-search">
+          <input type="text" placeholder="输入监测点名称" class="search-input" />
+          <span class="search-icon">🔍</span>
+        </div>
+        <div id="gwyxqkMap" 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="curve-tabs">
+            <span
+              v-for="tab in curveTabs"
+              :key="tab.value"
+              :class="['curve-tab', { active: activeCurveTab === tab.value }]"
+              @click="activeCurveTab = tab.value"
+            >{{ tab.label }}</span>
+          </div>
+          <div id="gwyxqkLineChart" class="chart-area-sm"></div>
+        </div>
+
+        <!-- 流量统计 -->
+        <div class="panel-box">
+          <div class="panel-title">
+            <span class="title-icon">▶</span> 流量统计
+          </div>
+          <div id="gwyxqkBarChart" class="chart-area-sm"></div>
+        </div>
+
+        <!-- 报警信息 -->
+        <div class="panel-box">
+          <div class="panel-title">
+            <span class="title-icon">▶</span> 报警信息
+          </div>
+          <div class="alert-stats">
+            <div class="alert-stat-card">
+              <div class="alert-stat-val">{{ alarmStats.total }}</div>
+              <div class="alert-stat-label">报警总数</div>
+            </div>
+            <div class="alert-stat-card">
+              <div class="alert-stat-val green">{{ alarmStats.handled }}</div>
+              <div class="alert-stat-label">已处理</div>
+            </div>
+            <div class="alert-stat-card">
+              <div class="alert-stat-val red">{{ alarmStats.unhandled }}</div>
+              <div class="alert-stat-label">未处理</div>
+            </div>
+          </div>
+          <div class="alert-table-wrap">
+            <table class="dark-table">
+              <thead>
+                <tr>
+                  <th>序号</th>
+                  <th>监测点</th>
+                  <th>时间</th>
+                  <th>类型</th>
+                  <th>状态</th>
+                </tr>
+              </thead>
+              <tbody>
+                <tr v-for="(row, i) in alarmList" :key="i">
+                  <td>{{ i + 1 }}</td>
+                  <td :title="row.point">{{ row.point }}</td>
+                  <td>{{ row.time }}</td>
+                  <td>{{ row.type }}</td>
+                  <td>
+                    <span :class="'status-tag status-' + row.handleClass">{{ row.handleStatus }}</span>
+                  </td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </div>
+      </div>
+    </div>
   </div>
 </template>
 
 <script setup name="Gwyxqk">
+import { ref, reactive, onMounted, onUnmounted, watch } from 'vue'
+import * as echarts from 'echarts'
+import locationIcon from '@/assets/images/location.png'
+
+// ==================== 实时时间 ====================
+const currentDateTime = 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()]}`
+}
+
+// ==================== 刷新时间 ====================
+const refreshTime = ref('')
+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())}`
+}
+
+// ==================== 管网概况 ====================
+const overviewStats = reactive({ total: 15, normal: 10, alarm: 3, offline: 2 })
+
+const pieData = ref([
+  { name: '正常运行', value: 10, color: '#67c23a' },
+  { name: '预警', value: 2, color: '#e6a23c' },
+  { name: '报警', value: 3, color: '#f56c6c' }
+])
+
+// ==================== 实时数据 ====================
+const realtimeData = ref([
+  { name: '沅陵大道1号闸口', flow: 125.6, level: 0.85, status: '正常', statusClass: 'normal' },
+  { name: '城北路段排水井A3', flow: 89.3, level: 1.42, status: '正常', statusClass: 'normal' },
+  { name: '河西大道排水管道B7', flow: 210.5, level: 3.15, status: '报警', statusClass: 'alarm' },
+  { name: '人民路地下管网C2', flow: 178.9, level: 2.68, status: '预警', statusClass: 'warning' },
+  { name: '南环路排水管道D3', flow: 56.2, level: 0.52, status: '正常', statusClass: 'normal' },
+  { name: '北环路排水泵站出口', flow: 195.4, level: 2.35, status: '预警', statusClass: 'warning' },
+  { name: '西环路河道排放口', flow: 302.1, level: 4.82, status: '报警', statusClass: 'alarm' },
+  { name: '东环路市政大楼段', flow: 78.5, level: 0.68, status: '正常', statusClass: 'normal' },
+  { name: '开发区管委会路段', flow: 245.7, level: 3.56, status: '报警', statusClass: 'alarm' },
+  { name: '城北二号排水井A7', flow: 112.3, level: 1.05, status: '正常', statusClass: 'normal' },
+  { name: '白羊坪村监测点', flow: 45.8, level: 0.35, status: '离线', statusClass: 'offline' },
+  { name: '杜家山监测站', flow: 92.1, level: 1.18, status: '正常', statusClass: 'normal' }
+])
+
+// ==================== 监测曲线 ====================
+const curveTabs = [
+  { label: '24小时', value: '24h' },
+  { label: '7天', value: '7d' },
+  { label: '15天', value: '15d' },
+  { label: '30天', value: '30d' }
+]
+const activeCurveTab = ref('24h')
+
+// ==================== 报警信息 ====================
+const alarmStats = reactive({ total: 8, handled: 5, unhandled: 3 })
+
+const alarmList = ref([
+  { point: '西环路河道排放口', time: '06-12 08:32', type: '液位超限', handleStatus: '未处理', handleClass: 'alarm' },
+  { point: '开发区管委会路段', time: '06-12 08:28', type: '液位超限', handleStatus: '未处理', handleClass: 'alarm' },
+  { point: '河西大道排水管道B7', time: '06-12 08:15', type: '流量异常', handleStatus: '未处理', handleClass: 'alarm' },
+  { point: '人民路地下管网C2', time: '06-12 07:55', type: '液位超限', handleStatus: '已处理', handleClass: 'normal' },
+  { point: '北环路排水泵站出口', time: '06-12 07:42', type: '流量异常', handleStatus: '已处理', handleClass: 'normal' },
+  { point: '白羊坪村监测点', time: '06-12 07:30', type: '设备离线', handleStatus: '已处理', handleClass: 'normal' },
+  { point: '南环路排水管道D3', time: '06-11 22:18', type: '流量异常', handleStatus: '已处理', handleClass: 'normal' },
+  { point: '东环路市政大楼段', time: '06-11 20:05', type: '液位超限', handleStatus: '已处理', handleClass: 'normal' }
+])
+
+// ==================== 地图监测点数据 ====================
+const mapPoints = ref([
+  { name: '沅陵大道1号闸口', lng: 110.393, lat: 28.452, flow: 125.6, level: 0.85, status: '正常' },
+  { name: '城北路段排水井A3', lng: 110.385, lat: 28.462, flow: 89.3, level: 1.42, status: '正常' },
+  { name: '河西大道排水管道B7', lng: 110.378, lat: 28.456, flow: 210.5, level: 3.15, status: '报警' },
+  { name: '人民路地下管网C2', lng: 110.396, lat: 28.451, flow: 178.9, level: 2.68, status: '预警' },
+  { name: '南环路排水管道D3', lng: 110.402, lat: 28.444, flow: 56.2, level: 0.52, status: '正常' },
+  { name: '北环路排水泵站出口', lng: 110.401, lat: 28.460, flow: 195.4, level: 2.35, status: '预警' },
+  { name: '西环路河道排放口', lng: 110.372, lat: 28.449, flow: 302.1, level: 4.82, status: '报警' },
+  { name: '东环路市政大楼段', lng: 110.410, lat: 28.454, flow: 78.5, level: 0.68, status: '正常' },
+  { name: '开发区管委会路段', lng: 110.415, lat: 28.458, flow: 245.7, level: 3.56, status: '报警' },
+  { name: '城北二号排水井A7', lng: 110.394, lat: 28.465, flow: 112.3, level: 1.05, status: '正常' },
+  { name: '白羊坪村监测点', lng: 110.380, lat: 28.468, flow: 45.8, level: 0.35, status: '离线' },
+  { name: '杜家山监测站', lng: 110.405, lat: 28.467, flow: 92.1, level: 1.18, status: '正常' },
+  { name: '阳光水岸监测点', lng: 110.388, lat: 28.445, flow: 67.4, level: 0.72, status: '正常' },
+  { name: '大营村监测站', lng: 110.418, lat: 28.446, flow: 55.9, level: 0.48, status: '正常' },
+  { name: '能瓦溪村委监测点', lng: 110.398, lat: 28.470, flow: 38.2, level: 0.28, status: '离线' }
+])
+
+// ==================== 地图初始化 ====================
+let mapInstance = null
+
+function initMap() {
+  const container = document.getElementById('gwyxqkMap')
+  if (!container || typeof BMapGL === 'undefined') {
+    console.warn('BMapGL 未加载或容器不存在')
+    return
+  }
+
+  try {
+    mapInstance = new BMapGL.Map('gwyxqkMap')
+    const centerPoint = new BMapGL.Point(110.393, 28.452)
+    mapInstance.centerAndZoom(centerPoint, 15)
+    mapInstance.enableScrollWheelZoom(true)
+
+    // 添加标记点
+    mapPoints.value.forEach(point => {
+      addMapMarker(point)
+    })
+  } catch (e) {
+    console.error('百度地图初始化失败:', e)
+  }
+}
+
+function addMapMarker(point) {
+  const statusColor = {
+    '正常': '#67c23a',
+    '预警': '#e6a23c',
+    '报警': '#f56c6c',
+    '离线': '#909399'
+  }
+  const color = statusColor[point.status] || '#409eff'
+  const bPoint = new BMapGL.Point(point.lng, point.lat)
+
+  const html = `<div style="text-align:center;cursor:pointer;">
+    <div style="color:#fff;background:${color};border-radius:4px;padding:2px 6px;font-size:11px;white-space:nowrap;display:inline-block;margin-bottom:2px;">${point.name}</div>
+    <div style="color:#fff;font-size:10px;background:rgba(0,0,0,0.6);border-radius:3px;padding:1px 4px;margin-bottom:2px;">流量:${point.flow}m³/h | 液位:${point.level}m</div>
+    <div><img src="${locationIcon}" style="width:24px;height:24px;display:block;margin:0 auto;"/></div>
+  </div>`
+
+  const label = new BMapGL.Label(html, {
+    position: bPoint,
+    offset: new BMapGL.Size(-30, -55)
+  })
+  label.setStyle({
+    border: 'none',
+    background: 'transparent',
+    padding: '0'
+  })
+  mapInstance.addOverlay(label)
+
+  // 点击标记弹出信息窗口
+  label.addEventListener('click', function () {
+    const infoContent = `<div style="color:#333;font-size:13px;padding:4px;">
+      <div style="font-weight:bold;margin-bottom:6px;">${point.name}</div>
+      <div>流量:<b style="color:#409eff">${point.flow} m³/h</b></div>
+      <div>液位:<b style="color:${color}">${point.level} m</b></div>
+      <div>状态:<b style="color:${color}">${point.status}</b></div>
+      <div>经度:${point.lng}</div>
+      <div>纬度:${point.lat}</div>
+    </div>`
+    const infoWindow = new BMapGL.InfoWindow(infoContent, {
+      width: 220,
+      title: ''
+    })
+    mapInstance.openInfoWindow(infoWindow, bPoint)
+  })
+}
 
+// ==================== ECharts 图表 ====================
+let pieChart = null
+let lineChart = null
+let barChart = null
+
+function initPieChart() {
+  const dom = document.getElementById('gwyxqkPieChart')
+  if (!dom) return
+  pieChart = echarts.init(dom)
+  const total = pieData.value.reduce((s, d) => s + d.value, 0)
+  pieChart.setOption({
+    tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
+    legend: { show: false },
+    graphic: [{
+      type: 'text',
+      left: 'center',
+      top: '38%',
+      style: { text: '管网状态', fill: '#a0cfff', fontSize: 12, textAlign: 'center' }
+    }, {
+      type: 'text',
+      left: 'center',
+      top: '52%',
+      style: { text: String(total), fill: '#fff', fontSize: 22, fontWeight: 'bold', textAlign: 'center' }
+    }],
+    series: [{
+      type: 'pie',
+      radius: ['45%', '70%'],
+      center: ['50%', '50%'],
+      label: { show: false },
+      labelLine: { show: false },
+      data: pieData.value.map(d => ({ name: d.name, value: d.value, itemStyle: { color: d.color } })),
+      emphasis: { scale: true, scaleSize: 5 }
+    }]
+  })
+}
+
+function generateLineData(tab) {
+  const labels = []
+  const flowData = []
+  const levelData = []
+  if (tab === '24h') {
+    for (let i = 0; i < 24; i++) {
+      labels.push(String(i).padStart(2, '0') + ':00')
+      flowData.push(Math.round((80 + Math.random() * 200) * 10) / 10)
+      levelData.push(Math.round((0.3 + Math.random() * 4) * 100) / 100)
+    }
+  } else if (tab === '7d') {
+    for (let i = 6; i >= 0; i--) {
+      const d = new Date()
+      d.setDate(d.getDate() - i)
+      labels.push(`${d.getMonth() + 1}/${d.getDate()}`)
+      flowData.push(Math.round((100 + Math.random() * 180) * 10) / 10)
+      levelData.push(Math.round((0.5 + Math.random() * 3.5) * 100) / 100)
+    }
+  } else if (tab === '15d') {
+    for (let i = 14; i >= 0; i--) {
+      const d = new Date()
+      d.setDate(d.getDate() - i)
+      labels.push(`${d.getMonth() + 1}/${d.getDate()}`)
+      flowData.push(Math.round((90 + Math.random() * 200) * 10) / 10)
+      levelData.push(Math.round((0.4 + Math.random() * 3.8) * 100) / 100)
+    }
+  } else {
+    for (let i = 29; i >= 0; i--) {
+      const d = new Date()
+      d.setDate(d.getDate() - i)
+      labels.push(`${d.getMonth() + 1}/${d.getDate()}`)
+      flowData.push(Math.round((85 + Math.random() * 210) * 10) / 10)
+      levelData.push(Math.round((0.3 + Math.random() * 4.2) * 100) / 100)
+    }
+  }
+  return { labels, flowData, levelData }
+}
+
+function initLineChart(tab) {
+  const dom = document.getElementById('gwyxqkLineChart')
+  if (!dom) return
+  if (!lineChart) {
+    lineChart = echarts.init(dom)
+  }
+  const { labels, flowData, levelData } = generateLineData(tab || '24h')
+  lineChart.setOption({
+    tooltip: { trigger: 'axis' },
+    legend: {
+      data: ['流量(m³/h)', '液位(m)'],
+      textStyle: { color: '#7ec8e3', fontSize: 10 },
+      top: 0,
+      right: 10,
+      itemWidth: 12,
+      itemHeight: 8
+    },
+    grid: { left: 40, right: 40, top: 30, bottom: 25 },
+    xAxis: {
+      type: 'category',
+      data: labels,
+      axisLabel: { color: '#7ec8e3', fontSize: 9, interval: tab === '30d' ? 4 : tab === '15d' ? 2 : 'auto' },
+      axisLine: { lineStyle: { color: '#1a3a5c' } }
+    },
+    yAxis: [
+      {
+        type: 'value',
+        name: 'm³/h',
+        nameTextStyle: { color: '#7ec8e3', fontSize: 10 },
+        axisLabel: { color: '#7ec8e3', fontSize: 10 },
+        splitLine: { lineStyle: { color: '#1a3a5c' } },
+        axisLine: { show: false }
+      },
+      {
+        type: 'value',
+        name: 'm',
+        nameTextStyle: { color: '#e6a23c', fontSize: 10 },
+        axisLabel: { color: '#e6a23c', fontSize: 10 },
+        splitLine: { show: false },
+        axisLine: { show: false }
+      }
+    ],
+    series: [
+      {
+        name: '流量(m³/h)',
+        type: 'line',
+        data: flowData,
+        smooth: true,
+        symbol: 'none',
+        lineStyle: { color: '#409eff', width: 2 },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: 'rgba(64,158,255,0.3)' },
+            { offset: 1, color: 'rgba(64,158,255,0.02)' }
+          ])
+        }
+      },
+      {
+        name: '液位(m)',
+        type: 'line',
+        yAxisIndex: 1,
+        data: levelData,
+        smooth: true,
+        symbol: 'none',
+        lineStyle: { color: '#e6a23c', width: 2 },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: 'rgba(230,162,60,0.3)' },
+            { offset: 1, color: 'rgba(230,162,60,0.02)' }
+          ])
+        }
+      }
+    ]
+  })
+}
+
+function initBarChart() {
+  const dom = document.getElementById('gwyxqkBarChart')
+  if (!dom) return
+  barChart = echarts.init(dom)
+  const names = ['沅陵大道', '城北路段', '河西大道', '人民路', '南环路', '北环泵站', '西环路', '东环路']
+  const values = [125.6, 89.3, 210.5, 178.9, 56.2, 195.4, 302.1, 78.5]
+  barChart.setOption({
+    tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
+    grid: { left: 50, right: 10, top: 10, bottom: 30 },
+    xAxis: {
+      type: 'category',
+      data: names,
+      axisLabel: { color: '#7ec8e3', fontSize: 9, rotate: 20 },
+      axisLine: { lineStyle: { color: '#1a3a5c' } }
+    },
+    yAxis: {
+      type: 'value',
+      name: 'm³/h',
+      nameTextStyle: { color: '#7ec8e3', fontSize: 10 },
+      axisLabel: { color: '#7ec8e3', fontSize: 10 },
+      splitLine: { lineStyle: { color: '#1a3a5c' } },
+      axisLine: { show: false }
+    },
+    series: [{
+      type: 'bar',
+      data: values,
+      barWidth: 14,
+      itemStyle: {
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+          { offset: 0, color: '#409eff' },
+          { offset: 1, color: '#1a3a5c' }
+        ]),
+        borderRadius: [2, 2, 0, 0]
+      }
+    }]
+  })
+}
+
+// ==================== 监测曲线Tab切换 ====================
+watch(activeCurveTab, (val) => {
+  initLineChart(val)
+})
+
+// ==================== 生命周期 ====================
+onMounted(() => {
+  updateTime()
+  timer = setInterval(() => {
+    updateTime()
+    updateRefreshTime()
+  }, 1000)
+  updateRefreshTime()
+
+  // 延迟初始化确保 DOM 渲染完成
+  setTimeout(() => {
+    initMap()
+    initPieChart()
+    initLineChart('24h')
+    initBarChart()
+  }, 300)
+})
+
+onUnmounted(() => {
+  if (timer) clearInterval(timer)
+  if (pieChart) pieChart.dispose()
+  if (lineChart) lineChart.dispose()
+  if (barChart) barChart.dispose()
+  if (mapInstance) mapInstance = null
+})
+
+// 监听窗口大小变化,重绘图表
+window.addEventListener('resize', () => {
+  if (pieChart) pieChart.resize()
+  if (lineChart) lineChart.resize()
+  if (barChart) barChart.resize()
+})
 </script>
 
 <style scoped>
+/* ==================== 全局暗色大屏 ==================== */
+.gwyxqk-screen {
+  width: 100%;
+  height: 100vh;
+  background: #0a1929;
+  color: #fff;
+  font-family: 'Microsoft YaHei', sans-serif;
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
+}
+
+/* ==================== 顶部标题栏 ==================== */
+.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;
+  text-shadow: none;
+}
+
+.header-right {
+  flex: 1;
+  text-align: right;
+}
+
+/* ==================== 主体三栏 ==================== */
+.screen-body {
+  flex: 1;
+  display: flex;
+  padding: 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;
+  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;
+}
+
+/* ==================== 管网概况统计卡片 ==================== */
+.stats-cards {
+  display: flex;
+  gap: 8px;
+  margin-bottom: 10px;
+  flex-shrink: 0;
+}
+
+.stat-card {
+  flex: 1;
+  background: rgba(10, 25, 50, 0.6);
+  border: 1px solid rgba(64, 158, 255, 0.15);
+  border-radius: 6px;
+  padding: 8px 4px;
+  text-align: center;
+}
+
+.stat-val {
+  font-size: 20px;
+  font-weight: 700;
+  color: #409eff;
+}
+
+.stat-val.green {
+  color: #67c23a;
+}
+
+.stat-val.orange {
+  color: #e6a23c;
+}
+
+.stat-val.gray {
+  color: #909399;
+}
+
+.stat-label {
+  font-size: 10px;
+  color: #7ec8e3;
+  margin-top: 2px;
+}
+
+/* ==================== 环形图 ==================== */
+.pie-chart-wrapper {
+  display: flex;
+  align-items: center;
+  height: 140px;
+}
+
+.chart-area {
+  width: 55%;
+  height: 140px;
+}
+
+.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);
+}
+
+.status-tag {
+  padding: 1px 6px;
+  border-radius: 3px;
+  font-size: 11px;
+}
+
+.status-normal {
+  color: #67c23a;
+  background: rgba(103, 194, 58, 0.15);
+}
+
+.status-warning {
+  color: #e6a23c;
+  background: rgba(230, 162, 60, 0.15);
+}
+
+.status-alarm {
+  color: #f56c6c;
+  background: rgba(245, 108, 108, 0.15);
+}
+
+.status-offline {
+  color: #909399;
+  background: rgba(144, 147, 153, 0.15);
+}
+
+/* ==================== 中间地图 ==================== */
+.map-container {
+  flex: 1;
+  border-radius: 6px;
+  overflow: hidden;
+  border: 1px solid rgba(64, 158, 255, 0.2);
+  min-height: 0;
+}
+
+.map-search {
+  position: absolute;
+  top: 12px;
+  left: 12px;
+  z-index: 10;
+  display: flex;
+  align-items: center;
+}
+
+.search-input {
+  background: rgba(6, 30, 65, 0.85);
+  border: 1px solid rgba(64, 158, 255, 0.3);
+  color: #a0cfff;
+  padding: 6px 30px 6px 12px;
+  border-radius: 4px;
+  font-size: 12px;
+  outline: none;
+  width: 160px;
+}
+
+.search-input::placeholder {
+  color: #4a6a8a;
+}
+
+.search-icon {
+  position: absolute;
+  right: 8px;
+  font-size: 14px;
+  cursor: pointer;
+}
+
+/* ==================== 监测曲线Tab ==================== */
+.curve-tabs {
+  display: flex;
+  gap: 0;
+  background: rgba(10, 25, 50, 0.6);
+  border: 1px solid rgba(64, 158, 255, 0.2);
+  border-radius: 4px;
+  margin-bottom: 8px;
+  flex-shrink: 0;
+  overflow: hidden;
+}
+
+.curve-tab {
+  flex: 1;
+  text-align: center;
+  padding: 5px 4px;
+  font-size: 11px;
+  color: #a0cfff;
+  cursor: pointer;
+  transition: all 0.3s;
+  border-right: 1px solid rgba(64, 158, 255, 0.1);
+}
+
+.curve-tab:last-child {
+  border-right: none;
+}
+
+.curve-tab.active {
+  background: rgba(64, 158, 255, 0.25);
+  color: #fff;
+  font-weight: 600;
+}
+
+.curve-tab:hover {
+  background: rgba(64, 158, 255, 0.15);
+}
+
+.chart-area-sm {
+  width: 100%;
+  height: 150px;
+  flex-shrink: 0;
+}
+
+/* ==================== 报警信息 ==================== */
+.alert-stats {
+  display: flex;
+  gap: 8px;
+  margin-bottom: 8px;
+  flex-shrink: 0;
+}
+
+.alert-stat-card {
+  flex: 1;
+  background: rgba(10, 25, 50, 0.6);
+  border: 1px solid rgba(64, 158, 255, 0.15);
+  border-radius: 6px;
+  padding: 8px;
+  text-align: center;
+}
+
+.alert-stat-val {
+  font-size: 20px;
+  font-weight: 700;
+  color: #e6a23c;
+}
+
+.alert-stat-val.green {
+  color: #67c23a;
+}
+
+.alert-stat-val.red {
+  color: #f56c6c;
+}
+
+.alert-stat-label {
+  font-size: 10px;
+  color: #7ec8e3;
+  margin-top: 2px;
+}
+
+.alert-table-wrap {
+  flex: 1;
+  overflow-y: auto;
+  min-height: 0;
+}
+
+/* ==================== 滚动条样式 ==================== */
+::-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>