瀏覽代碼

feat: 更新管网与生命线公司相关页面及API接口

nahida 1 月之前
父節點
當前提交
60f241eaf3

+ 3 - 0
.env.development

@@ -6,3 +6,6 @@ VITE_APP_ENV = 'development'
 
 # 若依管理系统/开发环境
 VITE_APP_BASE_API = '/dev-api'
+
+# MinIO文件访问基础地址
+VITE_APP_MINIO_BASE_URL = 'http://111.23.174.45:9000/'

+ 7 - 0
src/api/pipeNetwork/pdeviceInform.js

@@ -63,6 +63,13 @@ export function getMonitorPoint(id) {
   });
 }
 
+export function listMonitorPointAll() {
+  return request({
+    url: "/MonitorPoint/getList",
+    method: "get"
+  });
+}
+
 export function addMonitorPoint(data) {
   return request({
     url: "/MonitorPoint/save",

+ 204 - 57
src/api/smxyjldczxt/yjxxfb/yjfb.js

@@ -1,76 +1,223 @@
 import request from '@/utils/request'
 
-// 查询参数列表
-export function getdbyjlistData(query) {
-    return request({
-        url: '/warning/list',
-        method: 'get',
-        params: query
-    })
+function buildWarningFormData(payload = {}) {
+  const formData = new FormData()
+  const warning = payload.warning || {}
+
+  Object.keys(warning).forEach((key) => {
+    const value = warning[key]
+    formData.append(`warning.${key}`, value ?? '')
+  })
+
+  ;(payload.alarmIds || []).forEach((id) => {
+    formData.append('alarmIds', id)
+  })
+
+  ;(payload.files || []).forEach((file) => {
+    formData.append('files', file)
+  })
+
+  return formData
 }
 
+// 预警发布列表/综合查询
+export function getdbyjlistData(query) {
+  return request({
+    url: '/warning/search',
+    method: 'get',
+    params: query
+  })
+}
 
-// 预警存储草稿
+// 保存预警草稿
 export function submitYjxxData(data) {
-    return request({
-        url: '/warning/save',
-        method: 'post',
-        params: data, // 重点:用 data,不要用 params!不要手动 stringify!
-        headers: {
-            'Content-Type': 'application/json'
-        }
-    })
+  return request({
+    url: '/warning/save',
+    method: 'post',
+    data: buildWarningFormData(data)
+  })
 }
 
-// 修改预警存储
+// 修改预警
 export function updateYjxxData(data) {
-    return request({
-        url: '/warning/update',
-        method: 'put',
-        params: data, // 重点:用 data,不要用 params!不要手动 stringify!
-        headers: {
-            'Content-Type': 'application/json'
-        }
-    })
+  return request({
+    url: '/warning/update',
+    method: 'put',
+    data: buildWarningFormData(data)
+  })
 }
 
-// 获取预警类型(级别)字典数据
+// 获取字典数据
 export function getAlertTypeDictionaryData(query) {
-    return request({
-        url: '/system/dict/data/list',
-        method: 'get',
-        params: query
-    })
+  return request({
+    url: '/system/dict/data/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 预警详情
+export function getWarningDetailData(warningId) {
+  return request({
+    url: `/warning/detail/full/${warningId}`,
+    method: 'get'
+  })
 }
 
-// 预警存储草稿
+// 发布预警
 export function releaseYjxxData(warningId) {
-    return request({
-        url: '/warning/publish/' + warningId,
-        method: 'post',
-        // params: warningId,
-        headers: {
-            'Content-Type': 'application/json'
-        }
-    })
+  return request({
+    url: `/warning/publish/${warningId}`,
+    method: 'post'
+  })
 }
 
-// 删除表数据
+// 删除预警
 export function deleteYjxxData(warningId) {
-    return request({
-        url: '/warning/delete/',
-        method: 'delete',
-        params:{
-            warningId:warningId
-        }
-    })
-}
-
-// 查询新增预警中的报警关联分页数据
-export function updateWarningAlarm(warningId) {
-    return request({
-        url: '/alarmData/findByPage',
-        method: 'get',
-        params:warningId
-    })
+  return request({
+    url: '/warning/delete',
+    method: 'delete',
+    params: {
+      warningId
+    }
+  })
+}
+
+// 预警统计
+export function getWarningStatisticsData() {
+  return request({
+    url: '/warning/dashboard/statistics',
+    method: 'get'
+  })
+}
+
+// 预警事项列表
+export function getWarningItemListData(query) {
+  return request({
+    url: '/warning/item/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 根据类型和级别查询原因
+export function getWarningReasonListData(query) {
+  return request({
+    url: '/warning/reason/query',
+    method: 'get',
+    params: query
+  })
+}
+
+// 关联报警
+export function updateWarningAlarm(data) {
+  return request({
+    url: '/warning/link/alarms',
+    method: 'post',
+    params: data
+  })
+}
+
+// 获取已关联报警列表
+export function getLinkedAlarmListData(warningId) {
+  return request({
+    url: `/warning/link/alarms/${warningId}`,
+    method: 'get'
+  })
+}
+
+// 报警分页列表
+export function getAlarmPageData(query) {
+  return request({
+    url: '/alarmData/findByPage',
+    method: 'get',
+    params: query
+  })
+}
+
+// 预警升级
+export function upgradeWarningData(data) {
+  return request({
+    url: '/warning/upgrade',
+    method: 'post',
+    params: data
+  })
+}
+
+// 预警解除
+export function resolveWarningData(data) {
+  return request({
+    url: '/warning/resolve',
+    method: 'post',
+    params: data
+  })
+}
+
+// 预警退回
+export function returnWarningData(data) {
+  return request({
+    url: '/warning/return',
+    method: 'post',
+    params: data
+  })
+}
+
+// 预警督办
+export function supervisionWarningData(data) {
+  return request({
+    url: '/warning/supervision',
+    method: 'post',
+    params: data
+  })
+}
+
+// 添加领导批示
+export function instructionWarningData(data) {
+  return request({
+    url: '/warning/instruction',
+    method: 'post',
+    params: data
+  })
+}
+
+// 处置过程跟踪 - 进行中的预警
+export function getWarningProcessingListData(query) {
+  return request({
+    url: '/warning/processing/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 处置过程跟踪 - 已完成的预警
+export function getWarningCompletedListData(query) {
+  return request({
+    url: '/warning/completed/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 预警处置完整流程
+export function getWarningProcessData(warningId) {
+  return request({
+    url: `/warning/process/${warningId}`,
+    method: 'get'
+  })
+}
+
+// 预警流程统计
+export function getWarningProcessStatisticsData(warningId) {
+  return request({
+    url: `/warning/process/statistics/${warningId}`,
+    method: 'get'
+  })
+}
+
+// 预警流程可视化路线图
+export function getWarningDiagramData(warningId) {
+  return request({
+    url: `/warning/diagram/${warningId}`,
+    method: 'get'
+  })
 }

+ 557 - 593
src/views/subSystem/lifeCompany/lAlertInformation/LCorrelation.vue

@@ -1,751 +1,715 @@
-<!--报警信息关联-->
+<!-- 报警信息关联 -->
 <template>
-  <div class="early-warning-association">
-    <!-- 统计卡片 -->
-    <div class="stats-grid">
-      <div class="stat-card">
-        <div class="stat-icon blue">
-          <el-icon><Warning /></el-icon>
+  <div class="app-container alarm-correlation-page">
+    <el-card shadow="never" class="page-card header-card">
+      <div class="page-header">
+        <div>
+          <div class="page-title">报警信息关联</div>
+          <div class="page-subtitle">统一管理报警与预警的关联关系,将设备超指标异常报警与预警信息建立对应,作为发布和处置的重要依据</div>
         </div>
-        <div class="stat-info">
-          <span class="stat-value">{{ stats.totalAlerts }}</span>
-          <span class="stat-label">总报警数</span>
-        </div>
-      </div>
-      <div class="stat-card">
-        <div class="stat-icon orange">
-          <el-icon><Link /></el-icon>
-        </div>
-        <div class="stat-info">
-          <span class="stat-value">{{ stats.associatedCount }}</span>
-          <span class="stat-label">已关联</span>
-        </div>
-      </div>
-      <div class="stat-card">
-        <div class="stat-icon green">
-          <el-icon><Check /></el-icon>
-        </div>
-        <div class="stat-info">
-          <span class="stat-value">{{ stats.resolvedCount }}</span>
-          <span class="stat-label">已处置</span>
-        </div>
-      </div>
-      <div class="stat-card">
-        <div class="stat-icon purple">
-          <el-icon><Bell /></el-icon>
-        </div>
-        <div class="stat-info">
-          <span class="stat-value">{{ stats.activeWarnings }}</span>
-          <span class="stat-label">进行中预警</span>
+        <div class="header-actions">
+          <el-button type="primary" icon="Refresh" @click="loadAllData">刷新数据</el-button>
         </div>
       </div>
+    </el-card>
+
+    <div class="summary-grid">
+      <el-card shadow="hover" class="summary-card primary">
+        <div class="summary-label">报警总数</div>
+        <div class="summary-value">{{ stats.totalAlarms }}</div>
+        <div class="summary-desc">当前查询条件下加载到的报警记录</div>
+      </el-card>
+      <el-card shadow="hover" class="summary-card success">
+        <div class="summary-label">已关联</div>
+        <div class="summary-value">{{ stats.linkedCount }}</div>
+        <div class="summary-desc">已建立报警与预警关联关系</div>
+      </el-card>
+      <el-card shadow="hover" class="summary-card warning">
+        <div class="summary-label">未关联</div>
+        <div class="summary-value">{{ stats.unlinkedCount }}</div>
+        <div class="summary-desc">仍待人工选择预警进行关联</div>
+      </el-card>
+      <el-card shadow="hover" class="summary-card info">
+        <div class="summary-label">预警数量</div>
+        <div class="summary-value">{{ availableWarnings.length }}</div>
+        <div class="summary-desc">可作为关联目标的预警信息</div>
+      </el-card>
     </div>
 
-    <!-- 筛选区域 -->
-    <div class="filter-bar">
-      <el-form :inline="true" :model="filters" class="filter-form">
-        <el-form-item label="行业类型">
-          <el-select v-model="filters.industry" placeholder="全部行业" clearable style="width: 140px">
-            <el-option label="环保监测" value="环保监测" />
-            <el-option label="水利监测" value="水利监测" />
-            <el-option label="气象监测" value="气象监测" />
-            <el-option label="地质灾害" value="地质灾害" />
-            <el-option label="消防监测" value="消防监测" />
-          </el-select>
+    <el-card shadow="never" class="page-card">
+      <el-form :model="queryParams" :inline="true">
+        <el-form-item label="设备编码">
+          <el-input
+            v-model="queryParams.deviceCode"
+            placeholder="请输入设备编码"
+            clearable
+            style="width: 220px"
+            @keyup.enter="handleQuery"
+          />
         </el-form-item>
-        <el-form-item label="设备类型">
-          <el-select v-model="filters.deviceType" placeholder="全部设备" clearable style="width: 140px">
-            <el-option label="水质监测仪" value="水质监测仪" />
-            <el-option label="水位计" value="水位计" />
-            <el-option label="雨量计" value="雨量计" />
-            <el-option label="气体传感器" value="气体传感器" />
-            <el-option label="温湿度传感器" value="温湿度传感器" />
+        <el-form-item label="预警类型">
+          <el-select v-model="queryParams.warningType" placeholder="请选择预警类型" clearable style="width: 180px">
+            <el-option
+              v-for="item in warningTypeOptions"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue"
+            />
           </el-select>
         </el-form-item>
-        <el-form-item label="报警等级">
-          <el-select v-model="filters.level" placeholder="全部等级" clearable style="width: 140px">
-            <el-option label="一级报警" value="一级" />
-            <el-option label="二级报警" value="二级" />
-            <el-option label="三级报警" value="三级" />
+        <el-form-item label="报警状态">
+          <el-select v-model="queryParams.alarmStatus" placeholder="请选择报警状态" clearable style="width: 180px">
+            <el-option label="未处理" :value="0" />
+            <el-option label="已处理" :value="1" />
           </el-select>
         </el-form-item>
         <el-form-item label="关联状态">
-          <el-select v-model="filters.associationStatus" placeholder="全部状态" clearable style="width: 140px">
-            <el-option label="已关联" value="associated" />
-            <el-option label="未关联" value="unassociated" />
+          <el-select v-model="queryParams.associationStatus" placeholder="请选择关联状态" clearable style="width: 180px">
+            <el-option label="已关联" value="linked" />
+            <el-option label="未关联" value="unlinked" />
           </el-select>
         </el-form-item>
         <el-form-item>
-          <el-button type="primary" @click="handleSearch">
-            <el-icon><Search /></el-icon> 查询
-          </el-button>
-          <el-button @click="resetFilters">
-            <el-icon><Refresh /></el-icon> 重置
-          </el-button>
-
-          <el-button type="primary" :icon="Refresh" @click="refreshData">刷新数据</el-button>
+          <el-button type="primary" icon="Search" @click="handleQuery">查询</el-button>
+          <el-button icon="Refresh" @click="resetQuery">重置</el-button>
         </el-form-item>
       </el-form>
-    </div>
+    </el-card>
 
-    <!-- 报警列表表格 -->
-    <div class="alert-table-container">
-      <el-table
-          :data="filteredAlerts"
-          v-loading="loading"
-          stripe
-          border
-          style="width: 100%"
-          @selection-change="handleSelectionChange"
-      >
+    <el-card shadow="never" class="page-card">
+      <el-table v-loading="loading" :data="pagedAlarmList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" />
-        <el-table-column prop="deviceName" label="设备名称" min-width="140">
-          <template #default="{ row }">
-            <div class="device-info">
-              <span class="device-name">{{ row.deviceName }}</span>
-              <span class="device-code">{{ row.deviceCode }}</span>
-            </div>
+        <el-table-column label="设备编码" prop="deviceCode" min-width="160" />
+        <el-table-column label="预警类型" prop="warningTypeText" width="120" />
+        <el-table-column label="预警编码" prop="warningCode" width="160" />
+        <el-table-column label="阈值范围" prop="thresholdText" width="160" />
+        <el-table-column label="实际值" prop="actualValue" width="120" />
+        <el-table-column label="报警时间" prop="alarmTime" width="180" />
+        <el-table-column label="报警状态" width="100" align="center">
+          <template #default="scope">
+            <el-tag :type="scope.row.alarmStatus === 1 ? 'success' : 'warning'">
+              {{ scope.row.alarmStatus === 1 ? "已处理" : "未处理" }}
+            </el-tag>
           </template>
         </el-table-column>
-        <el-table-column prop="industry" label="行业类型" width="100">
-          <template #default="{ row }">
-            <el-tag size="small">{{ row.industry }}</el-tag>
+        <el-table-column label="关联状态" width="100" align="center">
+          <template #default="scope">
+            <el-tag :type="scope.row.linkedWarning ? 'success' : 'info'">
+              {{ scope.row.linkedWarning ? "已关联" : "未关联" }}
+            </el-tag>
           </template>
         </el-table-column>
-        <el-table-column prop="indicator" label="监测指标" width="120" />
-        <el-table-column prop="alertValue" label="报警阈值" width="100" />
-        <el-table-column prop="actualValue" label="实际值" width="100">
-          <template #default="{ row }">
-            <span :class="['value-highlight', getValueClass(row)]">{{ row.actualValue }}</span>
-          </template>
-        </el-table-column>
-        <el-table-column prop="alertLevel" label="报警等级" width="90">
-          <template #default="{ row }">
-            <el-tag :type="getLevelType(row.alertLevel)" size="small">{{ row.alertLevel }}级</el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column prop="alertTime" label="报警时间" width="160" />
-        <el-table-column prop="associationStatus" label="关联状态" width="100">
-          <template #default="{ row }">
-            <el-tag v-if="row.associationStatus === 'associated'" type="success" size="small">已关联</el-tag>
-            <el-tag v-else type="info" size="small">未关联</el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column prop="associatedWarning" label="关联预警" min-width="140">
-          <template #default="{ row }">
-            <span v-if="row.associatedWarning" class="warning-link" @click="viewWarningDetail(row.associatedWarning)">
-              {{ row.associatedWarning.title }}
+        <el-table-column label="关联预警" min-width="180">
+          <template #default="scope">
+            <span
+              v-if="scope.row.linkedWarning"
+              class="warning-link"
+              @click="openWarningDetail(scope.row.linkedWarning.warningId)"
+            >
+              {{ scope.row.linkedWarning.warningName }}
             </span>
-            <span v-else class="no-warning">—</span>
+            <span v-else class="empty-text">-</span>
           </template>
         </el-table-column>
-        <el-table-column label="操作" width="200" fixed="right">
-          <template #default="{ row }">
-            <el-button
-                v-if="row.associationStatus !== 'associated'"
-                type="primary" link size="small"
-                @click="openAssociationDialog(row)"
-            >
-              <el-icon><Link /></el-icon> 关联预警
-            </el-button>
-            <el-button
-                v-else
-                type="warning" link size="small"
-                @click="openDisassociationDialog(row)"
-            >
-              <el-icon><Link /></el-icon> 解除关联
+        <el-table-column label="操作" fixed="right" width="220" align="center">
+          <template #default="scope">
+            <el-button link type="primary" @click="openAssociationDialog(scope.row)" v-if="!scope.row.linkedWarning">
+              关联预警
             </el-button>
-            <el-button type="info" link size="small" @click="viewAlertDetail(row)">
-              <el-icon><View /></el-icon> 详情
+            <el-button link type="success" @click="openWarningDetail(scope.row.linkedWarning.warningId)" v-if="scope.row.linkedWarning">
+              查看预警
             </el-button>
+            <el-button link type="info" @click="openAlarmDetail(scope.row)">报警详情</el-button>
           </template>
         </el-table-column>
       </el-table>
 
-      <!-- 分页 -->
-      <div class="pagination-wrapper">
-        <el-pagination
-            v-model:current-page="pagination.currentPage"
-            v-model:page-size="pagination.pageSize"
-            :page-sizes="[10, 20, 50, 100]"
-            :total="total"
-            layout="total, sizes, prev, pager, next, jumper"
-            @size-change="handleSizeChange"
-            @current-change="handleCurrentChange"
-        />
-      </div>
-    </div>
-
-    <!-- 批量操作栏 -->
-    <div class="batch-actions" v-if="selectedAlerts.length > 0">
-      <span class="selected-info">已选择 {{ selectedAlerts.length }} 条报警记录</span>
-      <el-button type="primary" @click="openBatchAssociationDialog">
-        <el-icon><Link /></el-icon> 批量关联预警
-      </el-button>
-      <el-button @click="clearSelection">取消选择</el-button>
+      <pagination
+        v-show="total > 0"
+        :total="total"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        @pagination="handlePageChange"
+      />
+    </el-card>
+
+    <div class="batch-bar" v-if="selectedRows.length">
+      <span>已选择 {{ selectedRows.length }} 条报警</span>
+      <el-button type="primary" @click="openBatchAssociationDialog">批量关联预警</el-button>
+      <el-button @click="selectedRows = []">清空选择</el-button>
     </div>
 
-    <!-- 关联预警对话框 -->
-    <el-dialog v-model="associationDialog.visible" title="关联预警信息" width="600px">
-      <el-form :model="associationDialog.form" label-width="100px">
-        <el-form-item label="报警来源">
-          <el-input :value="associationDialog.alert?.deviceName" disabled />
-        </el-form-item>
+    <el-dialog v-model="associationDialog.visible" title="关联预警" width="620px" append-to-body>
+      <el-form label-width="100px">
         <el-form-item label="设备编码">
-          <el-input :value="associationDialog.alert?.deviceCode" disabled />
+          <el-input :model-value="associationDialog.row?.deviceCode || '-'" disabled />
+        </el-form-item>
+        <el-form-item label="预警类型">
+          <el-input :model-value="associationDialog.row?.warningTypeText || '-'" disabled />
         </el-form-item>
         <el-form-item label="报警内容">
-          <el-input
-              :value="`${associationDialog.alert?.indicator}:${associationDialog.alert?.actualValue}(阈值 ${associationDialog.alert?.alertValue})`"
-              disabled
-              type="textarea"
-              :rows="2"
-          />
+          <el-input :model-value="buildAlarmContent(associationDialog.row)" type="textarea" :rows="3" disabled />
         </el-form-item>
-        <el-form-item label="关联预警" required>
-          <el-select
-              v-model="associationDialog.form.warningId"
-              placeholder="请选择关联的预警信息"
-              filterable
-              style="width: 100%"
-          >
+        <el-form-item label="选择预警">
+          <el-select v-model="associationDialog.warningId" placeholder="请选择预警" filterable style="width: 100%">
             <el-option
-                v-for="warning in availableWarnings"
-                :key="warning.id"
-                :label="warning.title"
-                :value="warning.id"
+              v-for="item in availableWarnings"
+              :key="item.warningId"
+              :label="item.warningName"
+              :value="item.warningId"
             >
               <div class="warning-option">
-                <span>{{ warning.title }}</span>
-                <el-tag :type="getWarningLevelType(warning.level)" size="mini">{{ warning.level }}级</el-tag>
+                <span>{{ item.warningName }}</span>
+                <el-tag :type="getWarningLevelType(item.warningLevel)">{{ item.warningLevelText }}</el-tag>
               </div>
             </el-option>
           </el-select>
         </el-form-item>
-        <el-form-item label="关联说明">
-          <el-input type="textarea" v-model="associationDialog.form.remark" placeholder="请输入关联说明(可选)" :rows="3" />
-        </el-form-item>
       </el-form>
       <template #footer>
-        <el-button @click="associationDialog.visible = false">取消</el-button>
-        <el-button type="primary" :loading="submitLoading" @click="confirmAssociation">确认关联</el-button>
+        <div class="dialog-footer">
+          <el-button @click="associationDialog.visible = false">取 消</el-button>
+          <el-button type="primary" :loading="submitLoading" @click="confirmAssociation">确 定</el-button>
+        </div>
       </template>
     </el-dialog>
 
-    <!-- 批量关联对话框 -->
-    <el-dialog v-model="batchDialogVisible" title="批量关联预警" width="600px">
+    <el-dialog v-model="batchDialog.visible" title="批量关联预警" width="620px" append-to-body>
       <el-form label-width="100px">
-        <el-form-item label="选中报警">
-          <el-input :value="`已选择 ${selectedAlerts.length} 条未关联报警`" disabled />
+        <el-form-item label="报警数量">
+          <el-input :model-value="`已选 ${batchSelectableCount} 条未关联报警`" disabled />
         </el-form-item>
-        <el-form-item label="选择预警" required>
-          <el-select v-model="batchWarningId" placeholder="请选择预警" style="width: 100%">
+        <el-form-item label="选择预警">
+          <el-select v-model="batchDialog.warningId" placeholder="请选择预警" filterable style="width: 100%">
             <el-option
-                v-for="warning in availableWarnings"
-                :key="warning.id"
-                :label="warning.title"
-                :value="warning.id"
+              v-for="item in availableWarnings"
+              :key="item.warningId"
+              :label="item.warningName"
+              :value="item.warningId"
             />
           </el-select>
         </el-form-item>
       </el-form>
       <template #footer>
-        <el-button @click="batchDialogVisible = false">取消</el-button>
-        <el-button type="primary" :loading="submitLoading" @click="confirmBatchAssociation">确认批量关联</el-button>
+        <div class="dialog-footer">
+          <el-button @click="batchDialog.visible = false">取 消</el-button>
+          <el-button type="primary" :loading="submitLoading" @click="confirmBatchAssociation">确 定</el-button>
+        </div>
       </template>
     </el-dialog>
 
-    <!-- 预警详情弹窗 -->
-    <el-dialog v-model="warningDetailDialog.visible" title="预警详情" width="500px">
-      <el-descriptions :column="1" border>
-        <el-descriptions-item label="预警标题">{{ warningDetailDialog.warning?.title }}</el-descriptions-item>
-        <el-descriptions-item label="预警等级">
-          <el-tag :type="getWarningLevelType(warningDetailDialog.warning?.level)">{{ warningDetailDialog.warning?.level }}级</el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="发布单位">{{ warningDetailDialog.warning?.publisher }}</el-descriptions-item>
-        <el-descriptions-item label="发布时间">{{ warningDetailDialog.warning?.publishTime }}</el-descriptions-item>
-        <el-descriptions-item label="预警内容" class="pre-wrap">{{ warningDetailDialog.warning?.content }}</el-descriptions-item>
-        <el-descriptions-item label="处置状态">
-          <el-tag :type="warningDetailDialog.warning?.disposalStatus === '已处置' ? 'success' : 'warning'">
-            {{ warningDetailDialog.warning?.disposalStatus || '待处置' }}
-          </el-tag>
-        </el-descriptions-item>
-      </el-descriptions>
+    <el-dialog v-model="warningDetailVisible" title="预警详情" width="900px" append-to-body>
+      <template v-if="warningDetailData.basicInfo">
+        <el-descriptions :column="2" border>
+          <el-descriptions-item label="预警编号">{{ warningDetailData.basicInfo.warningNo || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="预警名称">{{ warningDetailData.basicInfo.warningName || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="预警类型">{{ formatTypeText(warningDetailData.basicInfo.warningType) }}</el-descriptions-item>
+          <el-descriptions-item label="预警级别">{{ formatLevelText(warningDetailData.basicInfo.warningLevel) }}</el-descriptions-item>
+          <el-descriptions-item label="预警位置" :span="2">{{ warningDetailData.basicInfo.location || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="发布时间">{{ warningDetailData.basicInfo.publishTime || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="状态">{{ warningDetailData.basicInfo.status || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="内容描述" :span="2">{{ warningDetailData.basicInfo.warningContent || "-" }}</el-descriptions-item>
+        </el-descriptions>
+
+        <el-divider content-position="left">已关联报警</el-divider>
+        <el-table :data="warningDetailData.alarmList || []" border size="small">
+          <el-table-column label="设备编码" prop="device_code" min-width="160" />
+          <el-table-column label="预警类型" prop="warning_type" width="120" />
+          <el-table-column label="预警编码" prop="warning_code" width="160" />
+          <el-table-column label="阈值" prop="warning_value" width="120" />
+          <el-table-column label="实际值" prop="actual_value" width="120" />
+          <el-table-column label="报警时间" prop="alarm_time" width="180" />
+        </el-table>
+      </template>
     </el-dialog>
 
-    <!-- 报警详情弹窗 -->
-    <el-dialog v-model="alertDetailDialog.visible" title="报警详情" width="500px">
-      <el-descriptions :column="1" border>
-        <el-descriptions-item label="设备名称">{{ alertDetailDialog.alert?.deviceName }}</el-descriptions-item>
-        <el-descriptions-item label="设备编码">{{ alertDetailDialog.alert?.deviceCode }}</el-descriptions-item>
-        <el-descriptions-item label="行业类型">{{ alertDetailDialog.alert?.industry }}</el-descriptions-item>
-        <el-descriptions-item label="监测指标">{{ alertDetailDialog.alert?.indicator }}</el-descriptions-item>
-        <el-descriptions-item label="报警阈值">{{ alertDetailDialog.alert?.alertValue }}</el-descriptions-item>
-        <el-descriptions-item label="实际值">{{ alertDetailDialog.alert?.actualValue }}</el-descriptions-item>
-        <el-descriptions-item label="报警等级">{{ alertDetailDialog.alert?.alertLevel }}级</el-descriptions-item>
-        <el-descriptions-item label="报警时间">{{ alertDetailDialog.alert?.alertTime }}</el-descriptions-item>
-        <el-descriptions-item label="设备位置">{{ alertDetailDialog.alert?.location }}</el-descriptions-item>
-        <el-descriptions-item label="处置建议" class="pre-wrap">{{ alertDetailDialog.alert?.suggestion || '请及时查看现场情况' }}</el-descriptions-item>
+    <el-dialog v-model="alarmDetailVisible" title="报警详情" width="680px" append-to-body>
+      <el-descriptions :column="2" border v-if="currentAlarmDetail">
+        <el-descriptions-item label="报警ID">{{ currentAlarmDetail.id || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="设备编码">{{ currentAlarmDetail.deviceCode || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="预警类型">{{ currentAlarmDetail.warningTypeText || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="预警编码">{{ currentAlarmDetail.warningCode || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="阈值范围">{{ currentAlarmDetail.thresholdText || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="实际值">{{ currentAlarmDetail.actualValue || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="报警时间">{{ currentAlarmDetail.alarmTime || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="报警状态">{{ currentAlarmDetail.alarmStatus === 1 ? "已处理" : "未处理" }}</el-descriptions-item>
+        <el-descriptions-item label="处理时间">{{ currentAlarmDetail.handleTime || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="处理人">{{ currentAlarmDetail.handleUser || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="处理备注" :span="2">{{ currentAlarmDetail.handleRemark || currentAlarmDetail.remark || "-" }}</el-descriptions-item>
       </el-descriptions>
     </el-dialog>
-
-    <!-- 解除关联确认 -->
-    <el-dialog v-model="disassociationDialog.visible" title="解除关联确认" width="400px">
-      <p>确定要解除以下报警与预警的关联关系吗?</p>
-      <p class="disassociate-info">
-        报警:{{ disassociationDialog.alert?.deviceName }} - {{ disassociationDialog.alert?.indicator }}<br>
-        关联预警:{{ disassociationDialog.alert?.associatedWarning?.title }}
-      </p>
-      <p class="warning-text">解除后该报警将变为未关联状态,可能影响后续处置参考。</p>
-      <template #footer>
-        <el-button @click="disassociationDialog.visible = false">取消</el-button>
-        <el-button type="danger" :loading="submitLoading" @click="confirmDisassociation">确认解除</el-button>
-      </template>
-    </el-dialog>
   </div>
 </template>
 
 <script setup>
-import { ref, computed, reactive, onMounted } from 'vue'
-import { ElMessage, ElMessageBox } from 'element-plus'
-import { Warning, Link, Check, Bell, Search, Refresh, View } from '@element-plus/icons-vue'
-
-// 加载状态
-const loading = ref(false)
-const submitLoading = ref(false)
-
-// 统计
-const stats = ref({
-  totalAlerts: 0,
-  associatedCount: 0,
-  resolvedCount: 0,
-  activeWarnings: 0
-})
-
-// 筛选
-const filters = reactive({
-  industry: '',
-  deviceType: '',
-  level: '',
-  associationStatus: ''
-})
-
-// 分页
-const pagination = reactive({
-  currentPage: 1,
-  pageSize: 20
-})
-const total = ref(0)
-
-// 选中
-const selectedAlerts = ref([])
-
-// 原始数据
-const alertsData = ref([])
-const availableWarnings = ref([])
-
-// 对话框
+import { computed, onMounted, reactive, ref } from "vue";
+import { ElMessage } from "element-plus";
+import {
+  getAlarmPageData,
+  getAlertTypeDictionaryData,
+  getLinkedAlarmListData,
+  getWarningDetailData,
+  getdbyjlistData,
+  updateWarningAlarm
+} from "@/api/smxyjldczxt/yjxxfb/yjfb.js";
+
+const loading = ref(false);
+const submitLoading = ref(false);
+const warningTypeOptions = ref([]);
+const availableWarnings = ref([]);
+const rawAlarmList = ref([]);
+const selectedRows = ref([]);
+const linkedAlarmMap = ref({});
+
+const total = ref(0);
+const warningDetailVisible = ref(false);
+const alarmDetailVisible = ref(false);
+const warningDetailData = ref({});
+const currentAlarmDetail = ref(null);
+
 const associationDialog = reactive({
   visible: false,
-  alert: null,
-  form: { warningId: '', remark: '' }
-})
-
-const batchDialogVisible = ref(false)
-const batchWarningId = ref('')
-
-const warningDetailDialog = reactive({ visible: false, warning: null })
-const alertDetailDialog = reactive({ visible: false, alert: null })
-const disassociationDialog = reactive({ visible: false, alert: null })
-
-// ====================== 数据初始化 ======================
-const initMockData = () => {
-  alertsData.value = [
-    { id: 1, deviceName: '河道水质监测站A', deviceCode: 'DEV-SG-001', industry: '水利监测', deviceType: '水质监测仪', indicator: 'pH值', alertValue: '> 8.5', actualValue: '8.9', alertLevel: '二级', alertTime: '2024-01-15 08:23:15', associationStatus: 'associated', associatedWarning: { id: 'W001', title: 'XX河段水质异常预警', level: '二级', publisher: '水利局', publishTime: '2024-01-15 09:00:00', content: '监测到pH值超标,建议加强巡查', disposalStatus: '处置中' }, location: 'XX省XX市XX河段', suggestion: '检查上游排污情况' },
-    { id: 2, deviceName: '水库水位监测点', deviceCode: 'DEV-SL-002', industry: '水利监测', deviceType: '水位计', indicator: '水位', alertValue: '> 85m', actualValue: '86.2m', alertLevel: '一级', alertTime: '2024-01-15 10:05:32', associationStatus: 'associated', associatedWarning: { id: 'W002', title: 'XX水库汛期水位预警', level: '一级', publisher: '防汛办', publishTime: '2024-01-15 10:30:00', content: '水位超过警戒线,建议开闸泄洪', disposalStatus: '已处置' }, location: 'XX省XX市XX水库', suggestion: '立即启动泄洪预案' },
-    { id: 3, deviceName: '空气质量监测站', deviceCode: 'DEV-AQ-003', industry: '环保监测', deviceType: '气体传感器', indicator: 'PM2.5', alertValue: '> 150μg/m³', actualValue: '178μg/m³', alertLevel: '二级', alertTime: '2024-01-15 14:12:08', associationStatus: 'unassociated', associatedWarning: null, location: 'XX市XX区', suggestion: '发布重污染天气预警' },
-    { id: 4, deviceName: '雨量监测站', deviceCode: 'DEV-RF-004', industry: '气象监测', deviceType: '雨量计', indicator: '降雨量', alertValue: '> 50mm/h', actualValue: '62mm/h', alertLevel: '一级', alertTime: '2024-01-15 15:45:21', associationStatus: 'unassociated', associatedWarning: null, location: 'XX省XX市XX区', suggestion: '发布暴雨红色预警' },
-    { id: 5, deviceName: '边坡位移监测仪', deviceCode: 'DEV-LS-005', industry: '地质灾害', deviceType: '位移传感器', indicator: '位移量', alertValue: '> 10mm', actualValue: '15.3mm', alertLevel: '一级', alertTime: '2024-01-15 16:30:45', associationStatus: 'associated', associatedWarning: { id: 'W003', title: 'XX公路边坡滑坡预警', level: '一级', publisher: '自然资源局', publishTime: '2024-01-15 17:00:00', content: '边坡位移加速,建议封闭道路', disposalStatus: '处置中' }, location: 'XX省XX市XX公路K102+300', suggestion: '立即封闭交通,组织专家评估' },
-    { id: 6, deviceName: '消防栓压力监测', deviceCode: 'DEV-FS-006', industry: '消防监测', deviceType: '压力传感器', indicator: '水压', alertValue: '< 0.2MPa', actualValue: '0.15MPa', alertLevel: '三级', alertTime: '2024-01-16 09:15:33', associationStatus: 'unassociated', associatedWarning: null, location: 'XX市XX商业区', suggestion: '检查消防栓阀门及管网' },
-    { id: 7, deviceName: '烟气排放监测', deviceCode: 'DEV-FG-007', industry: '环保监测', deviceType: '气体传感器', indicator: 'SO₂浓度', alertValue: '> 100mg/m³', actualValue: '135mg/m³', alertLevel: '二级', alertTime: '2024-01-16 11:42:19', associationStatus: 'unassociated', associatedWarning: null, location: 'XX市XX工业园', suggestion: '核查企业环保设施运行情况' },
-    { id: 8, deviceName: '土壤墒情监测站', deviceCode: 'DEV-SM-008', industry: '水利监测', deviceType: '土壤传感器', indicator: '土壤含水量', alertValue: '< 15%', actualValue: '12%', alertLevel: '三级', alertTime: '2024-01-16 13:20:55', associationStatus: 'associated', associatedWarning: { id: 'W004', title: 'XX地区干旱预警', level: '三级', publisher: '农业农村局', publishTime: '2024-01-16 14:00:00', content: '土壤墒情较差,建议灌溉', disposalStatus: '待处置' }, location: 'XX省XX市XX农业区', suggestion: '建议启动抗旱灌溉措施' }
-  ]
-
-  availableWarnings.value = [
-    { id: 'W001', title: 'XX河段水质异常预警', level: '二级', publisher: '水利局', publishTime: '2024-01-15 09:00:00', content: '监测到pH值超标,建议加强巡查', disposalStatus: '处置中' },
-    { id: 'W002', title: 'XX水库汛期水位预警', level: '一级', publisher: '防汛办', publishTime: '2024-01-15 10:30:00', content: '水位超过警戒线,建议开闸泄洪', disposalStatus: '已处置' },
-    { id: 'W003', title: 'XX公路边坡滑坡预警', level: '一级', publisher: '自然资源局', publishTime: '2024-01-15 17:00:00', content: '边坡位移加速,建议封闭道路', disposalStatus: '处置中' },
-    { id: 'W004', title: 'XX地区干旱预警', level: '三级', publisher: '农业农村局', publishTime: '2024-01-16 14:00:00', content: '土壤墒情较差,建议灌溉', disposalStatus: '待处置' },
-    { id: 'W005', title: 'XX区域重污染天气预警', level: '二级', publisher: '生态环境局', publishTime: '2024-01-16 08:00:00', content: 'PM2.5浓度持续上升', disposalStatus: '处置中' },
-    { id: 'W006', title: 'XX区暴雨红色预警', level: '一级', publisher: '气象局', publishTime: '2024-01-15 16:00:00', content: '预计未来3小时降雨量超100mm', disposalStatus: '已处置' }
-  ]
-}
-
-// ====================== 筛选与分页 ======================
-const filteredList = computed(() => {
-  let data = [...alertsData.value]
-  if (filters.industry) data = data.filter(i => i.industry === filters.industry)
-  if (filters.deviceType) data = data.filter(i => i.deviceType === filters.deviceType)
-  if (filters.level) data = data.filter(i => i.alertLevel === filters.level)
-  if (filters.associationStatus) data = data.filter(i => i.associationStatus === filters.associationStatus)
-  return data
-})
-
-const filteredAlerts = computed(() => {
-  const start = (pagination.currentPage - 1) * pagination.pageSize
-  const end = start + pagination.pageSize
-  return filteredList.value.slice(start, end)
-})
-
-// ====================== 工具方法 ======================
-const getLevelType = (level) => ({ 一级: 'danger', 二级: 'warning', 三级: 'info' }[level] || 'info')
-const getWarningLevelType = (level) => ({ 一级: 'danger', 二级: 'warning', 三级: 'success' }[level] || 'info')
-const getValueClass = (row) => row.alertLevel === '一级' ? 'danger' : row.alertLevel === '二级' ? 'warning' : ''
-
-// ====================== 搜索 / 重置 / 刷新 ======================
-const handleSearch = () => {
-  pagination.currentPage = 1
-  updateTotalAndStats()
-  ElMessage.success('查询成功')
-}
-
-const resetFilters = () => {
-  filters.industry = ''
-  filters.deviceType = ''
-  filters.level = ''
-  filters.associationStatus = ''
-  pagination.currentPage = 1
-  updateTotalAndStats()
-  ElMessage.success('已重置')
-}
-
-const refreshData = async () => {
-  loading.value = true
-  await new Promise(r => setTimeout(r, 300))
-  initMockData()
-  updateTotalAndStats()
-  loading.value = false
-  ElMessage.success('刷新完成')
-}
-
-// ====================== 统计更新 ======================
-const updateTotalAndStats = () => {
-  total.value = filteredList.value.length
-  stats.value.totalAlerts = alertsData.value.length
-  stats.value.associatedCount = alertsData.value.filter(i => i.associationStatus === 'associated').length
-  stats.value.resolvedCount = alertsData.value.filter(i => i.associatedWarning?.disposalStatus === '已处置').length
-  stats.value.activeWarnings = availableWarnings.value.filter(w => w.disposalStatus !== '已处置').length
-}
-
-// ====================== 表格选择 ======================
-const handleSelectionChange = (val) => selectedAlerts.value = val
-const clearSelection = () => selectedAlerts.value = []
-
-// ====================== 关联预警 ======================
-const openAssociationDialog = (alert) => {
-  associationDialog.alert = alert
-  associationDialog.form = { warningId: '', remark: '' }
-  associationDialog.visible = true
-}
-
-const confirmAssociation = async () => {
-  if (!associationDialog.form.warningId) return ElMessage.warning('请选择预警')
-  submitLoading.value = true
-  await new Promise(r => setTimeout(r, 300))
-  const warn = availableWarnings.value.find(w => w.id === associationDialog.form.warningId)
-  const alert = associationDialog.alert
-  alert.associationStatus = 'associated'
-  alert.associatedWarning = warn
-  associationDialog.visible = false
-  updateTotalAndStats()
-  submitLoading.value = false
-  ElMessage.success('关联成功')
-}
-
-// ====================== 批量关联 ======================
-const openBatchAssociationDialog = () => {
-  const un = selectedAlerts.value.filter(a => a.associationStatus !== 'associated')
-  if (un.length === 0) return ElMessage.warning('所选均已关联')
-  batchWarningId.value = ''
-  batchDialogVisible.value = true
-}
-
-const confirmBatchAssociation = async () => {
-  if (!batchWarningId.value) return ElMessage.warning('请选择预警')
-  submitLoading.value = true
-  await new Promise(r => setTimeout(r, 400))
-  const warn = availableWarnings.value.find(w => w.id === batchWarningId.value)
-  selectedAlerts.value.forEach(a => {
-    if (a.associationStatus !== 'associated') {
-      a.associationStatus = 'associated'
-      a.associatedWarning = warn
-    }
-  })
-  batchDialogVisible.value = false
-  clearSelection()
-  updateTotalAndStats()
-  submitLoading.value = false
-  ElMessage.success('批量关联成功')
+  row: null,
+  warningId: ""
+});
+
+const batchDialog = reactive({
+  visible: false,
+  warningId: ""
+});
+
+const queryParams = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  deviceCode: "",
+  warningType: "",
+  alarmStatus: undefined,
+  associationStatus: ""
+});
+
+const warningTypeLabelMap = computed(() => {
+  return warningTypeOptions.value.reduce((acc, item) => {
+    acc[item.dictValue] = item.dictLabel;
+    return acc;
+  }, {});
+});
+
+const filteredAlarmList = computed(() => {
+  let list = [...rawAlarmList.value];
+  if (queryParams.associationStatus === "linked") {
+    list = list.filter((item) => item.linkedWarning);
+  }
+  if (queryParams.associationStatus === "unlinked") {
+    list = list.filter((item) => !item.linkedWarning);
+  }
+  total.value = list.length;
+  return list;
+});
+
+const pagedAlarmList = computed(() => {
+  const start = (queryParams.pageNum - 1) * queryParams.pageSize;
+  const end = start + queryParams.pageSize;
+  return filteredAlarmList.value.slice(start, end);
+});
+
+const stats = computed(() => {
+  const linkedCount = rawAlarmList.value.filter((item) => item.linkedWarning).length;
+  return {
+    totalAlarms: rawAlarmList.value.length,
+    linkedCount,
+    unlinkedCount: rawAlarmList.value.length - linkedCount
+  };
+});
+
+const batchSelectableCount = computed(() => {
+  return selectedRows.value.filter((item) => !item.linkedWarning).length;
+});
+
+function formatTypeText(value) {
+  return warningTypeLabelMap.value[value] || value || "-";
+}
+
+function formatLevelText(value) {
+  const map = {
+    "1": "一级",
+    "2": "二级",
+    "3": "三级",
+    "4": "四级"
+  };
+  return map[value] || value || "-";
+}
+
+function getWarningLevelType(level) {
+  return {
+    "1": "danger",
+    "2": "warning",
+    "3": "success",
+    "4": "info"
+  }[level] || "info";
+}
+
+function normalizeWarningRow(item) {
+  return {
+    warningId: item.warningId || item.warning_id,
+    warningNo: item.warningNo || item.warning_no,
+    warningName: item.warningName || item.warning_name,
+    warningType: item.warningType || item.warning_type,
+    warningLevel: item.warningLevel || item.warning_level,
+    warningLevelText: formatLevelText(item.warningLevel || item.warning_level),
+    publishTime: item.publishTime || item.publish_time,
+    status: item.status,
+    location: item.location,
+    warningContent: item.warningContent || item.warning_content
+  };
+}
+
+function buildThresholdText(item) {
+  if (item.warningValue) {
+    return item.warningValue;
+  }
+  if (item.minValue !== null && item.minValue !== undefined && item.maxValue !== null && item.maxValue !== undefined) {
+    return `${item.minValue} ~ ${item.maxValue}`;
+  }
+  if (item.minValue !== null && item.minValue !== undefined) {
+    return `<= ${item.minValue}`;
+  }
+  if (item.maxValue !== null && item.maxValue !== undefined) {
+    return `>= ${item.maxValue}`;
+  }
+  return "-";
 }
 
-// ====================== 解除关联 ======================
-const openDisassociationDialog = (alert) => {
-  disassociationDialog.alert = alert
-  disassociationDialog.visible = true
+function normalizeAlarmRow(item) {
+  const linkedWarning = linkedAlarmMap.value[item.id];
+  return {
+    ...item,
+    warningTypeText: formatTypeText(item.warningType),
+    thresholdText: buildThresholdText(item),
+    linkedWarning
+  };
 }
 
-const confirmDisassociation = async () => {
-  submitLoading.value = true
-  await new Promise(r => setTimeout(r, 300))
-  const alert = disassociationDialog.alert
-  alert.associationStatus = 'unassociated'
-  alert.associatedWarning = null
-  disassociationDialog.visible = false
-  updateTotalAndStats()
-  submitLoading.value = false
-  ElMessage.success('已解除')
+function buildAlarmContent(row) {
+  if (!row) {
+    return "-";
+  }
+  return `预警类型:${row.warningTypeText || "-"}\n阈值范围:${row.thresholdText || "-"}\n实际值:${row.actualValue || "-"}`;
+}
+
+async function loadWarningTypes() {
+  const res = await getAlertTypeDictionaryData({ dictType: "warning_type" });
+  warningTypeOptions.value = res.rows || [];
+}
+
+async function loadWarnings() {
+  const res = await getdbyjlistData({
+    pageNum: 1,
+    pageSize: 200
+  });
+  availableWarnings.value = (res.data?.records || []).map(normalizeWarningRow);
+}
+
+async function buildLinkedAlarmMap() {
+  const map = {};
+  await Promise.all(
+    availableWarnings.value.map(async (warning) => {
+      const res = await getLinkedAlarmListData(warning.warningId);
+      (res.data || []).forEach((alarm) => {
+        const alarmId = alarm.alarmId || alarm.alarm_id;
+        if (alarmId) {
+          map[alarmId] = {
+            warningId: warning.warningId,
+            warningName: warning.warningName,
+            warningLevel: warning.warningLevel,
+            warningLevelText: warning.warningLevelText
+          };
+        }
+      });
+    })
+  );
+  linkedAlarmMap.value = map;
+}
+
+async function loadAlarms() {
+  const res = await getAlarmPageData({
+    pageNum: 1,
+    pageSize: 1000,
+    deviceCode: queryParams.deviceCode || undefined,
+    warningType: queryParams.warningType || undefined,
+    alarmStatus: queryParams.alarmStatus
+  });
+  const records = res.data?.records || [];
+  rawAlarmList.value = records.map(normalizeAlarmRow);
+}
+
+async function loadAllData() {
+  loading.value = true;
+  try {
+    await loadWarnings();
+    await buildLinkedAlarmMap();
+    await loadAlarms();
+  } finally {
+    loading.value = false;
+  }
 }
 
-// ====================== 详情 ======================
-const viewWarningDetail = (warn) => {
-  warningDetailDialog.warning = warn
-  warningDetailDialog.visible = true
+function handleSelectionChange(val) {
+  selectedRows.value = val;
 }
 
-const viewAlertDetail = (alert) => {
-  alertDetailDialog.alert = alert
-  alertDetailDialog.visible = true
+function handleQuery() {
+  queryParams.pageNum = 1;
+  loadAllData();
 }
 
-// ====================== 分页事件 ======================
-const handleSizeChange = () => { pagination.currentPage = 1 }
-const handleCurrentChange = () => {}
+function resetQuery() {
+  queryParams.pageNum = 1;
+  queryParams.pageSize = 10;
+  queryParams.deviceCode = "";
+  queryParams.warningType = "";
+  queryParams.alarmStatus = undefined;
+  queryParams.associationStatus = "";
+  loadAllData();
+}
 
-// ====================== 初始化 ======================
-onMounted(() => {
-  initMockData()
-  updateTotalAndStats()
-})
-</script>
+function handlePageChange() {
+  selectedRows.value = [];
+}
 
-<style scoped>
-.early-warning-association {
-  padding: 20px;
-  background: #f5f7fa;
-  min-height: 100vh;
+function openAssociationDialog(row) {
+  associationDialog.row = row;
+  associationDialog.warningId = "";
+  associationDialog.visible = true;
 }
 
-.page-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 24px;
+async function linkAlarmsToWarning(warningId, alarmIds) {
+  return updateWarningAlarm({
+    warningId,
+    alarmIds
+  });
 }
 
-.header-left {
-  flex: 1;
+async function confirmAssociation() {
+  if (!associationDialog.warningId) {
+    ElMessage.warning("请选择预警");
+    return;
+  }
+  submitLoading.value = true;
+  try {
+    const linkedRes = await getLinkedAlarmListData(associationDialog.warningId);
+    const currentIds = (linkedRes.data || []).map((item) => item.alarmId || item.alarm_id).filter(Boolean);
+    const nextIds = Array.from(new Set([...currentIds, associationDialog.row.id]));
+    const res = await linkAlarmsToWarning(associationDialog.warningId, nextIds);
+    if (res.code !== 200) {
+      ElMessage.error(res.msg || "关联失败");
+      return;
+    }
+    ElMessage.success("关联成功");
+    associationDialog.visible = false;
+    await loadAllData();
+  } finally {
+    submitLoading.value = false;
+  }
 }
 
-.page-header h2 {
-  margin: 0 0 8px;
-  font-size: 24px;
-  font-weight: 600;
-  color: #303133;
+function openBatchAssociationDialog() {
+  if (!batchSelectableCount.value) {
+    ElMessage.warning("请选择未关联报警");
+    return;
+  }
+  batchDialog.warningId = "";
+  batchDialog.visible = true;
 }
 
-.page-header .description {
-  margin: 0;
-  font-size: 14px;
-  color: #606266;
-  line-height: 1.5;
+async function confirmBatchAssociation() {
+  if (!batchDialog.warningId) {
+    ElMessage.warning("请选择预警");
+    return;
+  }
+  const selectedIds = selectedRows.value.filter((item) => !item.linkedWarning).map((item) => item.id);
+  if (!selectedIds.length) {
+    ElMessage.warning("当前没有可关联的报警");
+    return;
+  }
+  submitLoading.value = true;
+  try {
+    const linkedRes = await getLinkedAlarmListData(batchDialog.warningId);
+    const currentIds = (linkedRes.data || []).map((item) => item.alarmId || item.alarm_id).filter(Boolean);
+    const nextIds = Array.from(new Set([...currentIds, ...selectedIds]));
+    const res = await linkAlarmsToWarning(batchDialog.warningId, nextIds);
+    if (res.code !== 200) {
+      ElMessage.error(res.msg || "批量关联失败");
+      return;
+    }
+    ElMessage.success("批量关联成功");
+    batchDialog.visible = false;
+    selectedRows.value = [];
+    await loadAllData();
+  } finally {
+    submitLoading.value = false;
+  }
 }
 
-/* 统计卡片 */
-.stats-grid {
-  display: grid;
-  grid-template-columns: repeat(4, 1fr);
-  gap: 20px;
-  margin-bottom: 24px;
+async function openWarningDetail(warningId) {
+  const res = await getWarningDetailData(warningId);
+  warningDetailData.value = res.data || {};
+  warningDetailVisible.value = true;
 }
 
-.stat-card {
-  background: #fff;
-  border-radius: 8px;
-  padding: 20px;
-  display: flex;
-  align-items: center;
-  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
-  transition: all 0.3s;
+function openAlarmDetail(row) {
+  currentAlarmDetail.value = row;
+  alarmDetailVisible.value = true;
+}
+
+onMounted(async () => {
+  await loadWarningTypes();
+  await loadAllData();
+});
+</script>
+
+<style scoped>
+.alarm-correlation-page {
+  background: #f5f7fb;
 }
 
-.stat-card:hover {
-  transform: translateY(-2px);
-  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
+.page-card {
+  border-radius: 16px;
+  margin-bottom: 16px;
 }
 
-.stat-icon {
-  width: 48px;
-  height: 48px;
-  border-radius: 12px;
+.header-card {
+  background: linear-gradient(135deg, #eff6ff, #f8fbff);
+  border: 1px solid #dbeafe;
+}
+
+.page-header {
   display: flex;
+  justify-content: space-between;
   align-items: center;
-  justify-content: center;
-  font-size: 24px;
-  margin-right: 16px;
+  gap: 16px;
+}
+
+.page-title {
+  font-size: 22px;
+  font-weight: 700;
+  color: #1e3a8a;
 }
 
-.stat-icon.blue { background: #ecf5ff; color: #409eff; }
-.stat-icon.orange { background: #fff5e6; color: #e6a23c; }
-.stat-icon.green { background: #f0f9f0; color: #67c23a; }
-.stat-icon.purple { background: #f5e6ff; color: #9b59b6; }
+.page-subtitle {
+  margin-top: 8px;
+  font-size: 13px;
+  color: #64748b;
+}
 
-.stat-info {
+.header-actions {
   display: flex;
-  flex-direction: column;
+  gap: 10px;
+  flex-wrap: wrap;
 }
 
-.stat-value {
-  font-size: 28px;
-  font-weight: 600;
-  color: #303133;
+.summary-grid {
+  display: grid;
+  grid-template-columns: repeat(4, minmax(0, 1fr));
+  gap: 16px;
+  margin-bottom: 16px;
 }
 
-.stat-label {
-  font-size: 13px;
-  color: #909399;
-  margin-top: 4px;
+.summary-card {
+  border: none;
+  color: #fff;
 }
 
-/* 筛选栏 */
-.filter-bar {
-  background: #fff;
-  border-radius: 8px;
-  padding: 16px 20px;
-  margin-bottom: 20px;
-  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
+.summary-card :deep(.el-card__body) {
+  padding: 20px;
 }
 
-.filter-form {
-  display: flex;
-  flex-wrap: wrap;
-  align-items: center;
-  gap: 8px;
+.summary-card.primary {
+  background: linear-gradient(135deg, #2563eb, #5b93ff);
 }
 
-/* 表格 */
-.alert-table-container {
-  background: #fff;
-  border-radius: 8px;
-  overflow: hidden;
-  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
+.summary-card.success {
+  background: linear-gradient(135deg, #059669, #34d399);
 }
 
-.device-info {
-  display: flex;
-  flex-direction: column;
-  gap: 2px;
+.summary-card.warning {
+  background: linear-gradient(135deg, #d97706, #f7b955);
 }
 
-.device-name {
-  font-weight: 500;
-  color: #303133;
+.summary-card.info {
+  background: linear-gradient(135deg, #7c3aed, #9d72ff);
 }
 
-.device-code {
-  font-size: 12px;
-  color: #909399;
+.summary-label {
+  font-size: 14px;
 }
 
-.value-highlight {
-  font-weight: 600;
+.summary-value {
+  margin: 8px 0 10px;
+  font-size: 30px;
+  font-weight: 700;
 }
 
-.value-highlight.danger { color: #f56c6c; }
-.value-highlight.warning { color: #e6a23c; }
+.summary-desc {
+  font-size: 12px;
+  opacity: 0.9;
+}
 
 .warning-link {
   color: #409eff;
   cursor: pointer;
-  text-decoration: underline;
 }
 
 .warning-link:hover {
   color: #66b1ff;
 }
 
-.no-warning {
-  color: #c0c4cc;
+.empty-text {
+  color: #9ca3af;
 }
 
-.pagination-wrapper {
-  padding: 16px 20px;
+.warning-option {
   display: flex;
-  justify-content: flex-end;
-  border-top: 1px solid #ebeef5;
+  justify-content: space-between;
+  align-items: center;
 }
 
-/* 批量操作 */
-.batch-actions {
+.batch-bar {
   position: fixed;
   bottom: 20px;
   left: 50%;
   transform: translateX(-50%);
+  z-index: 10;
   background: #fff;
-  border-radius: 40px;
-  padding: 12px 24px;
-  display: flex;
-  align-items: center;
-  gap: 16px;
-  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
-  z-index: 100;
-}
-
-.selected-info {
-  font-size: 14px;
-  font-weight: 500;
-  color: #303133;
-}
-
-/* 下拉选项 */
-.warning-option {
+  border-radius: 999px;
+  box-shadow: 0 10px 30px rgba(15, 23, 42, 0.15);
+  padding: 12px 20px;
   display: flex;
-  justify-content: space-between;
   align-items: center;
-  width: 100%;
-}
-
-/* 解除提示 */
-.disassociate-info {
-  background: #f5f7fa;
-  padding: 12px;
-  border-radius: 4px;
-  margin: 12px 0;
-  font-size: 13px;
+  gap: 12px;
 }
 
-.warning-text {
-  color: #f56c6c;
-  font-size: 12px;
-}
-
-.pre-wrap {
-  white-space: pre-wrap;
-  line-height: 1.6;
-}
-
-/* 响应式 */
 @media (max-width: 1200px) {
-  .stats-grid {
-    grid-template-columns: repeat(2, 1fr);
+  .summary-grid {
+    grid-template-columns: repeat(2, minmax(0, 1fr));
   }
 }
 
 @media (max-width: 768px) {
-  .early-warning-association {
-    padding: 12px;
+  .page-header {
+    flex-direction: column;
+    align-items: flex-start;
   }
-  .stats-grid {
+
+  .summary-grid {
     grid-template-columns: 1fr;
   }
+
+  .batch-bar {
+    left: 12px;
+    right: 12px;
+    transform: none;
+    border-radius: 16px;
+    flex-wrap: wrap;
+  }
 }
-</style>
+</style>

+ 677 - 725
src/views/subSystem/lifeCompany/lAlertInformation/LRelease.vue

@@ -1,877 +1,829 @@
-<!--预警发布-->
+<!-- 预警发布 -->
 <template>
-  <div class="early-warning-management">
-    <!-- 页面头部 -->
-    <div class="page-header">
-      <div class="header-left">
-        <el-icon><Warning /></el-icon>
-        <span class="title">预警信息发布管理</span>
-        <div class="stats-badge">
-          <span class="stat-item critical"><i class="dot"></i>红色预警 {{ redCount }}</span>
-          <span class="stat-item yellow"><i class="dot"></i>黄色预警 {{ yellowCount }}</span>
-          <span class="stat-item blue"><i class="dot"></i>蓝色预警 {{ blueCount }}</span>
+  <div class="app-container warning-release-page">
+    <el-card shadow="never" class="page-card header-card">
+      <div class="page-header">
+        <div>
+          <div class="page-title">预警发布</div>
+          <div class="page-subtitle">支持按预警类型、预警级别、发布时间查询,并对预警信息进行新增、编辑、查看、发布等操作</div>
+        </div>
+        <div class="header-actions">
+          <el-button type="primary" icon="Plus" @click="handleAdd">新增预警</el-button>
+          <el-button icon="Refresh" @click="loadList">刷新</el-button>
         </div>
       </div>
-      <div class="header-right">
-        <el-button type="primary" :icon="Plus" @click="showAddDialog">新增</el-button>
-        <el-button :icon="Refresh" @click="refreshList">刷新</el-button>
-        <el-button :icon="Download" @click="exportData">导出</el-button>
-      </div>
-    </div>
-
-    <!-- 查询区域 -->
-    <div class="query-bar">
-      <el-input
-          v-model="queryParams.keyword"
-          placeholder="预警标题/内容"
-          clearable
-          :prefix-icon="Search"
-          style="width: 200px"
-      />
-      <el-select v-model="queryParams.warningType" placeholder="预警类型" clearable style="width: 140px">
-        <el-option
-            v-for="item in alertTypeList"
-            :key="item.dictValue"
-            :label="item.dictLabel"
-            :value="item.dictValue"
-        />
-      </el-select>
-      <el-select v-model="queryParams.warningLevel" placeholder="预警级别" clearable style="width: 120px">
-        <el-option
-            v-for="item in alertLevelList"
-            :key="item.dictValue"
-            :label="item.dictLabel"
-            :value="item.dictValue"
-        />
-      </el-select>
-      <el-button type="primary" :icon="Search" @click="searchWarnings">查询</el-button>
-      <el-button @click="resetQuery">重置</el-button>
-    </div>
-
-    <!-- 预警信息列表 -->
-    <div class="warning-table-container">
-      <el-table :data="warnings" stripe border style="width: 100%" v-loading="loading">
-        <el-table-column label="序号" width="80">
-          <template #default="{ $index }">
-            {{ (queryParams.pageNum - 1) * queryParams.pageSize + $index + 1 }}
+    </el-card>
+
+    <el-card shadow="never" class="page-card">
+      <el-form :model="queryParams" :inline="true">
+        <el-form-item label="预警名称">
+          <el-input
+            v-model="queryParams.warningName"
+            placeholder="请输入预警名称"
+            clearable
+            style="width: 220px"
+            @keyup.enter="handleQuery"
+          />
+        </el-form-item>
+        <el-form-item label="预警类型">
+          <el-select v-model="queryParams.warningType" placeholder="请选择预警类型" clearable style="width: 180px">
+            <el-option
+              v-for="item in warningTypeOptions"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="预警级别">
+          <el-select v-model="queryParams.warningLevel" placeholder="请选择预警级别" clearable style="width: 180px">
+            <el-option
+              v-for="item in warningLevelOptions"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="发布时间">
+          <el-date-picker
+            v-model="queryParams.dateRange"
+            type="datetimerange"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            range-separator="至"
+            start-placeholder="开始时间"
+            end-placeholder="结束时间"
+            style="width: 360px"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="Search" @click="handleQuery">查询</el-button>
+          <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
+
+    <el-card shadow="never" class="page-card">
+      <el-table v-loading="loading" :data="warningList">
+        <el-table-column label="序号" width="70" align="center">
+          <template #default="scope">
+            {{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}
           </template>
         </el-table-column>
-        <el-table-column prop="warning_no" label="预警编号" width="180" sortable />
-        <el-table-column prop="warning_name" label="预警名称" show-overflow-tooltip />
-        <el-table-column prop="warning_type_text" label="预警类型" width="120" />
-        <el-table-column prop="warning_level" label="预警级别" width="200" align="center">
-          <template #default="{ row }">
-            <el-tag :type="getLevelTagType(row.warning_level)" size="small">
-              {{ row.warning_level_text }}
+        <el-table-column label="预警编号" prop="warningNo" width="180" />
+        <el-table-column label="预警名称" prop="warningName" min-width="180" show-overflow-tooltip />
+        <el-table-column label="预警类型" prop="warningTypeText" width="120" />
+        <el-table-column label="预警级别" width="110" align="center">
+          <template #default="scope">
+            <el-tag :type="getLevelTagType(scope.row.warningLevel)">
+              {{ scope.row.warningLevelText || scope.row.warningLevel || "-" }}
             </el-tag>
           </template>
         </el-table-column>
-        <el-table-column prop="alarm_name" label="关联报警" width="180" show-overflow-tooltip />
-        <el-table-column prop="location" label="预警位置" width="160" show-overflow-tooltip />
-        <el-table-column prop="publish_time" label="发布时间" width="180" sortable />
-        <el-table-column prop="publisher" label="发布人" width="110" />
-
-        <!-- 处置人 永远显示中文 -->
-        <el-table-column prop="handler" label="处置人" width="110">
-          <template #default="{ row }">
-            {{ userMap[row.handler] || row.handler }}
-          </template>
-        </el-table-column>
-
-        <el-table-column prop="status" label="状态" width="100" align="center">
-          <template #default="{ row }">
-            <el-tag :type="row.status === 'PENDING' ? 'success' : 'warning'" size="small">
-              {{ row.status === 'PENDING' ? '已发布' : '草稿' }}
+        <el-table-column label="预警位置" prop="location" min-width="180" show-overflow-tooltip />
+        <el-table-column label="发布时间" prop="publishTime" width="180" />
+        <el-table-column label="发布人" prop="publisher" width="120" />
+        <el-table-column label="状态" width="120" align="center">
+          <template #default="scope">
+            <el-tag :type="getStatusTagType(scope.row.status)">
+              {{ getStatusText(scope.row.status) }}
             </el-tag>
           </template>
         </el-table-column>
-
-        <el-table-column label="操作" width="280" fixed="right" align="center">
-          <template #default="{ row }">
-            <el-button link type="primary" size="small" @click="viewDetail(row)">查看</el-button>
-            <el-button link type="success" size="small" @click="editWarning(row)" v-if="row.status === 'DRAFT'">编辑</el-button>
-            <!-- 👇 优化:表格操作栏新增【关联报警】按钮,仅草稿可点 -->
-            <el-button link type="primary" size="small" @click="openAlarmLinkDialog(row)" v-if="row.status === 'DRAFT'">关联报警</el-button>
-            <el-button link type="danger" size="small" @click="deleteWarning(row)">删除</el-button>
-            <el-button link type="primary" size="small" @click="releaseWarning(row)" v-if="row.status === 'DRAFT'">发布</el-button>
+        <el-table-column label="操作" fixed="right" width="280" align="center">
+          <template #default="scope">
+            <el-button link type="primary" @click="handleView(scope.row)">查看</el-button>
+            <el-button link type="primary" @click="handleEdit(scope.row)" v-if="scope.row.status === 'DRAFT'">编辑</el-button>
+            <el-button link type="success" @click="handlePublish(scope.row)" v-if="scope.row.status === 'DRAFT'">发布</el-button>
+            <el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
           </template>
         </el-table-column>
       </el-table>
-      <div class="pagination-wrapper">
-        <el-pagination
-            background
-            layout="total, sizes, prev, pager, next, jumper"
-            :total="total"
-            v-model:current-page="queryParams.pageNum"
-            v-model:page-size="queryParams.pageSize"
-            :page-sizes="[10, 20, 50]"
-            @size-change="handleSizeChange"
-            @current-change="handleCurrentChange"
-        />
-      </div>
-    </div>
 
-    <!-- 新增/编辑预警对话框 -->
-    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="780px" @close="resetForm">
-      <el-form :model="formData" :rules="formRules" ref="formRef" label-width="120px">
-        <el-row :gutter="20">
+      <pagination
+        v-show="total > 0"
+        :total="total"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        @pagination="loadList"
+      />
+    </el-card>
+
+    <el-dialog v-model="editVisible" :title="dialogTitle" width="860px" append-to-body @close="resetForm">
+      <el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
+        <el-row :gutter="16">
           <el-col :span="12">
-            <el-form-item label="预警类型" prop="warningType" required>
-              <el-select v-model="formData.warningType" placeholder="请选择" style="width: 100%">
+            <el-form-item label="预警类型" prop="warningType">
+              <el-select v-model="formData.warningType" placeholder="请选择预警类型" style="width: 100%" @change="handleTypeLevelChange">
                 <el-option
-                    v-for="item in alertTypeList"
-                    :key="item.dictValue"
-                    :label="item.dictLabel"
-                    :value="item.dictValue"
+                  v-for="item in warningTypeOptions"
+                  :key="item.dictValue"
+                  :label="item.dictLabel"
+                  :value="item.dictValue"
                 />
               </el-select>
             </el-form-item>
           </el-col>
           <el-col :span="12">
-            <el-form-item label="预警级别" prop="warningLevel" required>
-              <el-select v-model="formData.warningLevel" placeholder="请选择" style="width: 100%">
+            <el-form-item label="预警级别" prop="warningLevel">
+              <el-select v-model="formData.warningLevel" placeholder="请选择预警级别" style="width: 100%" @change="handleTypeLevelChange">
                 <el-option
-                    v-for="item in alertLevelList"
-                    :key="item.dictValue"
-                    :label="item.dictLabel"
-                    :value="item.dictValue"
+                  v-for="item in warningLevelOptions"
+                  :key="item.dictValue"
+                  :label="item.dictLabel"
+                  :value="item.dictValue"
                 />
               </el-select>
             </el-form-item>
           </el-col>
-        </el-row>
-
-        <el-row :gutter="20">
           <el-col :span="12">
-            <el-form-item label="发布人" prop="publisher">
-              <el-input v-model="formData.publisher" placeholder="请输入发布人" />
+            <el-form-item label="预警专项">
+              <el-select v-model="formData.warningSpecial" filterable clearable allow-create default-first-option placeholder="请选择或输入预警专项" style="width: 100%">
+                <el-option
+                  v-for="item in filteredSpecialOptions"
+                  :key="`${item.itemId}-${item.warningSpecial}`"
+                  :label="item.warningSpecial"
+                  :value="item.warningSpecial"
+                />
+              </el-select>
             </el-form-item>
           </el-col>
           <el-col :span="12">
-            <el-form-item label="预警专项">
-              <el-input v-model="formData.warningSpecial" placeholder="请输入预警专项" />
+            <el-form-item label="权属单位">
+              <el-input v-model="formData.ownershipUnit" placeholder="请输入权属单位" />
             </el-form-item>
           </el-col>
-        </el-row>
-
-        <el-row :gutter="20">
           <el-col :span="12">
-            <el-form-item label="所属部门">
-              <!-- 树状部门选择器 -->
-              <el-tree-select
-                  v-model="selectedDeptId"
-                  :data="deptTreeData"
-                  placeholder="请选择部门"
-                  style="width: 100%"
-                  clearable
-                  check-strictly
-                  node-key="id"
-                  @change="handleDeptChange"
-              />
+            <el-form-item label="发布人">
+              <el-input v-model="formData.publisher" placeholder="请输入发布人" />
             </el-form-item>
           </el-col>
           <el-col :span="12">
-            <el-form-item label="处置人" prop="handler">
-              <el-select
-                  v-model="formData.handler"
-                  placeholder="请先选择部门"
-                  clearable
-                  style="width: 100%"
-                  :loading="userLoading"
-              >
-                <el-option
-                    v-for="user in userList"
-                    :key="user.id"
-                    :label="user.nickName"
-                    :value="user.userName"
-                />
-              </el-select>
+            <el-form-item label="处置人">
+              <el-input v-model="formData.handler" placeholder="请输入处置人" />
             </el-form-item>
           </el-col>
-        </el-row>
-
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="纬度">
-              <el-input v-model.number="formData.latitude" placeholder="请输入纬度" />
+          <el-col :span="24">
+            <el-form-item label="预警名称" prop="warningName">
+              <el-input v-model="formData.warningName" placeholder="请输入预警名称" maxlength="100" show-word-limit />
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="预警位置" prop="location">
+              <el-input v-model="formData.location" placeholder="请输入预警位置" />
             </el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item label="经度">
-              <el-input v-model.number="formData.longitude" placeholder="请输入经度" />
+              <el-input v-model="formData.longitude" placeholder="请输入经度" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="纬度">
+              <el-input v-model="formData.latitude" placeholder="请输入纬度" />
             </el-form-item>
           </el-col>
-        </el-row>
-
-        <el-form-item label="预警名称" prop="warningName" required>
-          <el-input v-model="formData.warningName" placeholder="请输入预警名称" maxlength="100" show-word-limit />
-        </el-form-item>
-        <el-form-item label="预警位置" prop="location" required>
-          <el-input v-model="formData.location" placeholder="请输入预警位置" />
-        </el-form-item>
-        <el-form-item label="预警内容描述" prop="warningContent" required>
-          <el-input type="textarea" v-model="formData.warningContent" placeholder="请详细描述预警内容、影响范围及建议措施" rows="4" maxlength="500" show-word-limit />
-        </el-form-item>
-        <el-row :gutter="20">
           <el-col :span="12">
             <el-form-item label="发布时间">
-              <el-date-picker v-model="formData.publishTime" type="datetime" placeholder="选择发布时间" style="width: 100%" />
+              <el-date-picker
+                v-model="formData.publishTime"
+                type="datetime"
+                value-format="YYYY-MM-DD HH:mm:ss"
+                placeholder="请选择发布时间"
+                style="width: 100%"
+              />
             </el-form-item>
           </el-col>
           <el-col :span="12">
             <el-form-item label="状态">
-              <el-radio-group v-model="formData.status">
-                <el-radio value="DRAFT">存为草稿</el-radio>
-              </el-radio-group>
+              <el-select v-model="formData.status" style="width: 100%">
+                <el-option label="草稿" value="DRAFT" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="内容描述" prop="warningContent">
+              <el-input
+                v-model="formData.warningContent"
+                type="textarea"
+                :rows="4"
+                maxlength="500"
+                show-word-limit
+                placeholder="请输入预警内容描述"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="24" v-if="reasonOptions.length">
+            <el-form-item label="推荐原因">
+              <div class="reason-wrap">
+                <el-tag
+                  v-for="item in reasonOptions"
+                  :key="item.reasonId"
+                  class="reason-tag"
+                  effect="plain"
+                  @click="appendReason(item)"
+                >
+                  {{ item.reasonName }}
+                </el-tag>
+              </div>
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="备注">
+              <el-input v-model="formData.remark" type="textarea" :rows="2" placeholder="请输入备注" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="附件上传">
+              <div class="attachment-panel">
+                <el-upload
+                  ref="uploadRef"
+                  action="#"
+                  :auto-upload="false"
+                  :show-file-list="false"
+                  multiple
+                  :on-change="handleUploadChange"
+                  :on-exceed="handleUploadExceed"
+                  :limit="9"
+                  accept=".pdf,.doc,.docx,.xls,.xlsx,.zip,.jpg,.jpeg,.png"
+                >
+                  <el-button type="primary">选择附件</el-button>
+                </el-upload>
+                <div class="attachment-tip">支持常见文档、压缩包和图片,新增或编辑时会随保存接口一起上传。</div>
+                <div v-if="formAttachments.length" class="attachment-list">
+                  <div v-for="item in formAttachments" :key="item.uid || item.attachmentId || item.name" class="attachment-item">
+                    <el-link
+                      v-if="item.url"
+                      :href="item.url"
+                      target="_blank"
+                      type="primary"
+                      class="attachment-link"
+                    >
+                      {{ item.name }}
+                    </el-link>
+                    <span v-else class="attachment-link">{{ item.name }}</span>
+                    <span class="attachment-meta">{{ item.sizeText || item.createTime || "" }}</span>
+                    <el-button link type="danger" @click="removeAttachment(item)">移除</el-button>
+                  </div>
+                </div>
+              </div>
             </el-form-item>
           </el-col>
         </el-row>
-        <el-form-item label="备注">
-          <el-input type="textarea" v-model="formData.remark" placeholder="可选备注" rows="2" />
-        </el-form-item>
       </el-form>
       <template #footer>
-        <el-button @click="dialogVisible = false">取消</el-button>
-        <el-button type="primary" :loading="loading" @click="submitWarning">保存提交</el-button>
-      </template>
-    </el-dialog>
-
-    <!-- 报警选择弹窗 -->
-    <el-dialog v-model="alarmDialogVisible" title="选择关联报警" width="850px" append-to-body>
-      <el-input
-          v-model="alarmQuery.keyword"
-          placeholder="报警编号/名称"
-          style="width:240px; margin-bottom:12px"
-          clearable
-          @input="getAlarmList"
-      />
-      <el-table :data="alarmList" v-loading="alarmLoading" border stripe style="width:100%">
-        <el-table-column label="选择" width="80" align="center">
-          <template #default="{ row }">
-            <el-radio v-model="alarmSelected" :label="row" />
-          </template>
-        </el-table-column>
-        <el-table-column label="设备编号" prop="deviceCode" width="180" />
-        <el-table-column label="报警备注" prop="remark" min-width="200" show-overflow-tooltip />
-        <el-table-column label="报警类型" prop="warningType" width="120" />
-        <el-table-column label="发生位置" prop="location" width="160" show-overflow-tooltip />
-        <el-table-column label="发生时间" prop="alarmTime" width="180" />
-      </el-table>
-      <div class="pagination-wrapper" style="margin-top:12px">
-        <el-pagination
-            background
-            layout="total, sizes, prev, pager, next, jumper"
-            :total="alarmTotal"
-            v-model:current-page="alarmQuery.pageNum"
-            v-model:page-size="alarmQuery.pageSize"
-            :page-sizes="[10,20,50]"
-            @size-change="alarmSizeChange"
-            @current-change="alarmCurrentChange"
-        />
-      </div>
-      <template #footer>
-        <el-button @click="alarmDialogVisible = false">取消</el-button>
-        <el-button type="primary" @click="confirmAlarmLink">确认选择</el-button>
+        <div class="dialog-footer">
+          <el-button @click="editVisible = false">取 消</el-button>
+          <el-button type="primary" :loading="submitLoading" @click="submitForm">保 存</el-button>
+        </div>
       </template>
     </el-dialog>
 
-    <!-- 详情弹窗 -->
-    <el-dialog v-model="detailVisible" title="预警详情" width="700px">
-      <div v-if="currentWarning" class="detail-content">
+    <el-dialog v-model="detailVisible" title="预警详情" width="900px" append-to-body>
+      <template v-if="detailData.basicInfo">
         <el-descriptions :column="2" border>
-          <el-descriptions-item label="预警编号" :span="2">{{ currentWarning.warning_no }}</el-descriptions-item>
-          <el-descriptions-item label="预警名称" :span="2">{{ currentWarning.warning_name }}</el-descriptions-item>
-          <el-descriptions-item label="预警类型">{{ currentWarning.warning_type_text }}</el-descriptions-item>
+          <el-descriptions-item label="预警编号">{{ detailData.basicInfo.warningNo || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="预警名称">{{ detailData.basicInfo.warningName || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="预警类型">{{ formatTypeText(detailData.basicInfo.warningType) }}</el-descriptions-item>
           <el-descriptions-item label="预警级别">
-            <el-tag :type="getLevelTagType(currentWarning.warning_level)">{{ currentWarning.warning_level_text }}</el-tag>
-          </el-descriptions-item>
-          <el-descriptions-item label="关联报警">{{ currentWarning.alarm_name || '无' }}</el-descriptions-item>
-          <el-descriptions-item label="预警位置">{{ currentWarning.location }}</el-descriptions-item>
-          <el-descriptions-item label="发布时间">{{ currentWarning.publish_time }}</el-descriptions-item>
-          <el-descriptions-item label="发布人">{{ currentWarning.publisher }}</el-descriptions-item>
-          <el-descriptions-item label="处置人">
-            {{ userMap[currentWarning.handler] || currentWarning.handler }}
-          </el-descriptions-item>
-          <el-descriptions-item label="状态">
-            <el-tag :type="currentWarning.status === 'PENDING' ? 'success' : 'warning'">
-              {{ currentWarning.status === 'PENDING' ? '已发布' : '草稿' }}
+            <el-tag :type="getLevelTagType(detailData.basicInfo.warningLevel)">
+              {{ formatLevelText(detailData.basicInfo.warningLevel) }}
             </el-tag>
           </el-descriptions-item>
-          <el-descriptions-item label="预警内容" :span="2" class="pre-wrap">
-            {{ currentWarning.warning_content }}
-          </el-descriptions-item>
+          <el-descriptions-item label="预警专项">{{ detailData.basicInfo.warningSpecial || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="权属单位">{{ detailData.basicInfo.ownershipUnit || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="发布人">{{ detailData.basicInfo.publisher || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="处置人">{{ detailData.basicInfo.handler || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="预警位置" :span="2">{{ detailData.basicInfo.location || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="发布时间">{{ detailData.basicInfo.publishTime || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="状态">{{ getStatusText(detailData.basicInfo.status) }}</el-descriptions-item>
+          <el-descriptions-item label="内容描述" :span="2">{{ detailData.basicInfo.warningContent || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="备注" :span="2">{{ detailData.basicInfo.remark || "-" }}</el-descriptions-item>
         </el-descriptions>
-      </div>
+
+        <el-divider content-position="left">关联报警</el-divider>
+        <el-table :data="detailData.alarmList || []" border size="small">
+          <el-table-column label="报警ID" prop="alarmId" min-width="160" />
+          <el-table-column label="报警名称" prop="alarmName" min-width="180" show-overflow-tooltip />
+          <el-table-column label="报警类型" prop="alarmType" min-width="120" />
+        </el-table>
+
+        <el-divider content-position="left">处置记录</el-divider>
+        <el-table :data="detailData.disposalHistory || detailData.disposalList || []" border size="small">
+          <el-table-column label="处置类型" prop="disposalTypeName" min-width="120" />
+          <el-table-column label="处置人" prop="disposalUser" min-width="120" />
+          <el-table-column label="处置内容" prop="disposalContent" min-width="220" show-overflow-tooltip />
+          <el-table-column label="处置时间" prop="createTime" min-width="180" />
+        </el-table>
+
+        <el-divider content-position="left">附件信息</el-divider>
+        <div v-if="(detailData.attachmentList || []).length" class="detail-attachment-list">
+          <div v-for="item in detailData.attachmentList" :key="item.attachmentId || item.attachmentName" class="detail-attachment-item">
+            <el-link :href="buildAttachmentUrl(item.attachmentUrl)" target="_blank" type="primary">
+              {{ item.attachmentName || "附件" }}
+            </el-link>
+            <span>{{ item.createTime || "-" }}</span>
+          </div>
+        </div>
+        <el-empty v-else description="暂无附件信息" />
+      </template>
     </el-dialog>
   </div>
 </template>
 
 <script setup>
-import { ref, computed, onMounted, getCurrentInstance } from 'vue'
-import { Warning, Plus, Refresh, Download, Search } from '@element-plus/icons-vue'
-import { ElMessage, ElMessageBox } from 'element-plus'
-
-// 接口导入
+import { computed, onMounted, reactive, ref } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
 import {
-  submitYjxxData,
-  updateYjxxData,
-  getdbyjlistData,
+  deleteYjxxData,
   getAlertTypeDictionaryData,
+  getWarningDetailData,
+  getWarningItemListData,
+  getWarningReasonListData,
+  getdbyjlistData,
   releaseYjxxData,
-  deleteYjxxData,
-  // 新增:报警关联接口
-  updateWarningAlarm
+  submitYjxxData,
+  updateYjxxData
 } from "@/api/smxyjldczxt/yjxxfb/yjfb.js";
 
-import { deptTreeSelect, listUser } from '@/api/system/user.js'
-
-const { proxy } = getCurrentInstance();
-
-// 数据列表
-const warnings = ref([])
-const loading = ref(false)
-const total = ref(0)
-
-// 字典
-const alertTypeList = ref([])
-const alertLevelList = ref([])
-
-// 部门树数据(树状结构)
-const deptTreeData = ref([])
-// 人员列表
-const userList = ref([])
-const userLoading = ref(false)
-const selectedDeptId = ref(null)
+const loading = ref(false);
+const submitLoading = ref(false);
+const total = ref(0);
+const warningList = ref([]);
+const warningTypeOptions = ref([]);
+const warningLevelOptions = ref([]);
+const warningItems = ref([]);
+const reasonOptions = ref([]);
+const uploadRef = ref();
+const formAttachments = ref([]);
+const minioBaseUrl = (import.meta.env.VITE_APP_MINIO_BASE_URL || "").trim();
+
+const editVisible = ref(false);
+const detailVisible = ref(false);
+const dialogTitle = ref("新增预警");
+const isEdit = ref(false);
+const formRef = ref();
+const detailData = ref({});
+
+const queryParams = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  warningName: "",
+  warningType: "",
+  warningLevel: "",
+  dateRange: []
+});
 
-// 全局用户映射:userName -> nickName
-const userMap = ref({})
+const formData = reactive(createEmptyForm());
 
-// 查询参数
-const queryParams = ref({
-  keyword: '',
-  warningType: '',
-  warningLevel: '',
-  dateRange: [],
-  pageNum: 1,
-  pageSize: 10
-})
-
-// 弹窗
-const dialogVisible = ref(false)
-const dialogTitle = ref('新增预警')
-const detailVisible = ref(false)
-const currentWarning = ref(null)
-const formRef = ref(null)
-const isEdit = ref(false)
-
-// 报警选择弹窗
-const alarmDialogVisible = ref(false)
-const alarmList = ref([])
-const alarmLoading = ref(false)
-const alarmTotal = ref(0)
-const alarmSelected = ref(null)
-const alarmQuery = ref({
-  keyword: '',
-  pageNum: 1,
-  pageSize: 10
-})
-
-// 存储当前正在关联的预警行
-const currentLinkWarning = ref(null)
-
-// 表单
-const formData = ref({
-  warningId: '',
-  warningNo: '',
-  warningName: '',
-  warningType: '',
-  warningLevel: '',
-  location: '',
-  warningContent: '',
-  publishTime: '',
-  publisher: '',
-  handler: '',
-  status: 'DRAFT',
-  remark: '',
-  ownershipUnit: '',
-  latitude: null,
-  longitude: null,
-  warningSpecial: '',
-  alarmId: '',
-  alarmName: ''
-})
-
-// 校验规则
 const formRules = {
-  warningType: [{ required: true, message: '请选择预警类型', trigger: 'change' }],
-  warningLevel: [{ required: true, message: '请选择预警级别', trigger: 'change' }],
-  warningName: [{ required: true, message: '请输入预警名称', trigger: 'blur' }],
-  location: [{ required: true, message: '请输入预警位置', trigger: 'blur' }],
-  warningContent: [{ required: true, message: '请输入预警内容', trigger: 'blur' }]
-}
-
-// 日期格式化
-const formatDate = (dateStr) => {
-  if (!dateStr) return ''
-  const d = new Date(dateStr)
-  const y = d.getFullYear()
-  const m = String(d.getMonth() + 1).padStart(2, '0')
-  const day = String(d.getDate()).padStart(2, '0')
-  const h = String(d.getHours()).padStart(2, '0')
-  const mi = String(d.getMinutes()).padStart(2, '0')
-  const s = String(d.getSeconds()).padStart(2, '0')
-  return `${y}-${m}-${day} ${h}:${mi}:${s}`
-}
-
-// 字典映射
-const typeLabel = computed(() => {
-  const map = {}
-  alertTypeList.value.forEach(item => map[item.dictValue] = item.dictLabel)
-  return map
-})
-const levelLabel = computed(() => {
-  const map = {}
-  alertLevelList.value.forEach(item => map[item.dictValue] = item.dictLabel)
-  return map
-})
-
-// 统计数量
-const redCount = computed(() => warnings.value.filter(w => w.warning_level === '1' && w.status === 'PENDING').length)
-const yellowCount = computed(() => warnings.value.filter(w => w.warning_level === '3' && w.status === 'PENDING').length)
-const blueCount = computed(() => warnings.value.filter(w => w.warning_level === '4' && w.status === 'PENDING').length)
-
-// 加载部门树
-const loadDeptTree = async () => {
-  try {
-    const res = await deptTreeSelect()
-    if (res.code === 200) {
-      deptTreeData.value = res.data
+  warningType: [{ required: true, message: "请选择预警类型", trigger: "change" }],
+  warningLevel: [{ required: true, message: "请选择预警级别", trigger: "change" }],
+  warningName: [{ required: true, message: "请输入预警名称", trigger: "blur" }],
+  location: [{ required: true, message: "请输入预警位置", trigger: "blur" }],
+  warningContent: [{ required: true, message: "请输入预警内容描述", trigger: "blur" }]
+};
+
+const warningTypeLabelMap = computed(() => {
+  return warningTypeOptions.value.reduce((acc, item) => {
+    acc[item.dictValue] = item.dictLabel;
+    return acc;
+  }, {});
+});
+
+const warningLevelLabelMap = computed(() => {
+  return warningLevelOptions.value.reduce((acc, item) => {
+    acc[item.dictValue] = item.dictLabel;
+    return acc;
+  }, {});
+});
+
+const filteredSpecialOptions = computed(() => {
+  return warningItems.value.filter((item) => {
+    if (item.status && item.status !== "ENABLED") {
+      return false;
     }
-  } catch (e) {
-    console.error('加载部门树失败', e)
-  }
-}
-
-// 加载全量用户(初始化就加载)
-const loadAllUserMap = async () => {
-  try {
-    const res = await listUser({ pageNum: 1, pageSize: 9999 })
-    if (res.code === 200) {
-      res.rows.forEach(u => {
-        userMap.value[u.userName] = u.nickName
-      })
+    if (formData.warningType && item.warningType !== formData.warningType) {
+      return false;
     }
-  } catch (e) {
-    console.error('加载全量用户失败', e)
+    if (formData.warningLevel && item.warningLevel !== formData.warningLevel) {
+      return false;
+    }
+    return !!item.warningSpecial;
+  });
+});
+
+function createEmptyForm() {
+  return {
+    warningId: "",
+    warningNo: "",
+    warningName: "",
+    warningType: "",
+    warningLevel: "",
+    warningSpecial: "",
+    location: "",
+    longitude: "",
+    latitude: "",
+    ownershipUnit: "",
+    publisher: "",
+    publishTime: "",
+    handler: "",
+    status: "DRAFT",
+    warningContent: "",
+    remark: ""
+  };
+}
+
+function resetForm() {
+  Object.assign(formData, createEmptyForm());
+  reasonOptions.value = [];
+  formAttachments.value = [];
+  uploadRef.value?.clearFiles?.();
+  formRef.value?.clearValidate();
+}
+
+function formatFileSize(bytes) {
+  if (!bytes) return "";
+  const units = ["B", "KB", "MB", "GB"];
+  const index = Math.floor(Math.log(bytes) / Math.log(1024));
+  return `${(bytes / 1024 ** index).toFixed(2)} ${units[index]}`;
+}
+
+function buildAttachmentUrl(path) {
+  if (!path) return "";
+  if (/^https?:\/\//i.test(path)) {
+    return path;
   }
+  const base = minioBaseUrl.endsWith("/") ? minioBaseUrl : `${minioBaseUrl}/`;
+  return `${base}${String(path).replace(/^\/+/, "")}`;
+}
+
+function normalizeExistingAttachment(item) {
+  return {
+    uid: item.attachmentId || item.attachmentName,
+    name: item.attachmentName || "附件",
+    url: buildAttachmentUrl(item.attachmentUrl),
+    attachmentId: item.attachmentId,
+    attachmentUrl: item.attachmentUrl,
+    createTime: item.createTime || "",
+    raw: null,
+    sizeText: item.attachmentSize ? formatFileSize(item.attachmentSize) : ""
+  };
+}
+
+function handleUploadChange(file) {
+  if (!file?.raw) return;
+  formAttachments.value.push({
+    uid: file.uid,
+    name: file.name,
+    raw: file.raw,
+    url: "",
+    sizeText: formatFileSize(file.size)
+  });
+}
+
+function handleUploadExceed() {
+  ElMessage.warning("最多上传 9 个附件");
+}
+
+function removeAttachment(item) {
+  formAttachments.value = formAttachments.value.filter((current) => current.uid !== item.uid);
+}
+
+function formatTypeText(value) {
+  return warningTypeLabelMap.value[value] || value || "-";
+}
+
+function formatLevelText(value) {
+  return warningLevelLabelMap.value[value] || value || "-";
+}
+
+function getLevelTagType(level) {
+  return {
+    "1": "danger",
+    "2": "warning",
+    "3": "success",
+    "4": "info"
+  }[level] || "info";
+}
+
+function getStatusText(status) {
+  return {
+    DRAFT: "草稿",
+    RELEASED: "已发布",
+    PENDING: "待处理",
+    PROCESSING: "处理中",
+    HANDLED: "已处理",
+    CLOSED: "已解除",
+    RETURNED: "退回"
+  }[status] || status || "-";
+}
+
+function getStatusTagType(status) {
+  return {
+    DRAFT: "info",
+    RELEASED: "success",
+    PENDING: "warning",
+    PROCESSING: "warning",
+    HANDLED: "success",
+    CLOSED: "",
+    RETURNED: "danger"
+  }[status] || "";
+}
+
+function normalizeWarningRow(item) {
+  const row = { ...item };
+  row.warningId = item.warningId || item.warning_id || "";
+  row.warningNo = item.warningNo || item.warning_no || "";
+  row.warningName = item.warningName || item.warning_name || "";
+  row.warningType = item.warningType || item.warning_type || "";
+  row.warningLevel = item.warningLevel || item.warning_level || "";
+  row.warningSpecial = item.warningSpecial || item.warning_special || "";
+  row.location = item.location || "";
+  row.longitude = item.longitude || "";
+  row.latitude = item.latitude || "";
+  row.ownershipUnit = item.ownershipUnit || item.ownership_unit || "";
+  row.publisher = item.publisher || "";
+  row.publishTime = item.publishTime || item.publish_time || "";
+  row.handler = item.handler || "";
+  row.status = item.status || "";
+  row.warningContent = item.warningContent || item.warning_content || "";
+  row.remark = item.remark || "";
+  row.warningTypeText = formatTypeText(row.warningType);
+  row.warningLevelText = formatLevelText(row.warningLevel);
+  return row;
+}
+
+async function loadDicts() {
+  const [typeRes, levelRes] = await Promise.all([
+    getAlertTypeDictionaryData({ dictType: "warning_type" }),
+    getAlertTypeDictionaryData({ dictType: "warning_level" })
+  ]);
+  warningTypeOptions.value = typeRes.rows || [];
+  warningLevelOptions.value = levelRes.rows || [];
+}
+
+async function loadWarningItems() {
+  const res = await getWarningItemListData({
+    pageNum: 1,
+    pageSize: 200,
+    status: "ENABLED"
+  });
+  warningItems.value = res.data?.records || [];
 }
 
-// 加载部门下人员(用于下拉选择)
-const loadUserList = async (deptId) => {
-  if (!deptId) {
-    userList.value = []
-    return
-  }
-  userLoading.value = true
-  try {
-    const res = await listUser({ deptId, pageNum: 1, pageSize: 100 })
-    if (res.code === 200) {
-      userList.value = res.rows || []
-    }
-  } catch (e) {
-    console.error(e)
-  } finally {
-    userLoading.value = false
+async function loadReasonOptions() {
+  if (!formData.warningType || !formData.warningLevel) {
+    reasonOptions.value = [];
+    return;
   }
+  const res = await getWarningReasonListData({
+    warningType: formData.warningType,
+    warningLevel: formData.warningLevel
+  });
+  reasonOptions.value = res.data || [];
 }
 
-// 部门切换 → 加载人员
-const handleDeptChange = (val) => {
-  selectedDeptId.value = val
-  formData.value.handler = ''
-  loadUserList(val)
+function handleTypeLevelChange() {
+  loadReasonOptions();
 }
 
-
-// 报警分页
-const alarmSizeChange = (val) => {
-  alarmQuery.value.pageSize = val
-  alarmQuery.value.pageNum = 1
-  getAlarmList()
-}
-const alarmCurrentChange = (val) => {
-  alarmQuery.value.pageNum = val
-  getAlarmList()
-}
-// 打开报警关联弹窗(携带当前预警ID)
-const openAlarmLinkDialog = async (row) => {
-  currentLinkWarning.value = row
-  alarmSelected.value = null
-  // 初始化分页 + 搜索
-  alarmQuery.value = {
-    keyword: '',
-    pageNum: 1,
-    pageSize: 10
-  }
-  await getAlarmList()
-  alarmDialogVisible.value = true
+function appendReason(item) {
+  const content = item.description ? `${item.reasonName}:${item.description}` : item.reasonName;
+  formData.warningContent = formData.warningContent
+    ? `${formData.warningContent}\n${content}`
+    : content;
 }
 
-// 获取报警列表(带分页 + 搜索 + warningId)
-const getAlarmList = async () => {
-  const warningId = currentLinkWarning.value?.warning_id
-  if (!warningId) return
-
-  alarmLoading.value = true
+async function loadList() {
+  loading.value = true;
   try {
-    // 👇 关键:带上所有分页 + 搜索参数
-    const res = await updateWarningAlarm({
-      // warningCode: warningId,
-      // keyword: alarmQuery.value.keyword,
-      pageNum: alarmQuery.value.pageNum,
-      pageSize: alarmQuery.value.pageSize
-    })
-
-    if (res.code === 200) {
-      alarmList.value = res.data.records || []
-      alarmTotal.value = res.data.total || 0
-    }
-  } catch (err) {
-    console.error('获取报警列表失败', err)
-    ElMessage.error('获取报警数据失败')
+    const params = {
+      pageNum: queryParams.pageNum,
+      pageSize: queryParams.pageSize,
+      warningName: queryParams.warningName,
+      warningType: queryParams.warningType,
+      warningLevel: queryParams.warningLevel,
+      startTime: queryParams.dateRange?.[0] || "",
+      endTime: queryParams.dateRange?.[1] || ""
+    };
+    const res = await getdbyjlistData(params);
+    warningList.value = (res.data?.records || []).map(normalizeWarningRow);
+    total.value = res.data?.total || 0;
   } finally {
-    alarmLoading.value = false
+    loading.value = false;
   }
 }
 
-// 确认关联报警(提交选中的报警给后台)
-const confirmAlarmLink = async () => {
-  if (!alarmSelected.value) {
-    ElMessage.warning('请选择一条报警信息')
-    return
-  }
-  if (!currentLinkWarning.value) {
-    ElMessage.error('关联失败,未获取到预警信息')
-    return
-  }
-
-  try {
-    // 提交关联:预警ID + 选中的报警ID
-    const res = await updateWarningAlarm({
-      warningId: currentLinkWarning.value.warning_id,
-      alarmId: alarmSelected.value.alarmId
-    })
-
-    if (res.code === 200) {
-      ElMessage.success('关联成功')
-      alarmDialogVisible.value = false
-      getWarningList() // 刷新表格
-    } else {
-      ElMessage.error(res.msg || '关联失败')
-    }
-  } catch (err) {
-    console.error(err)
-    ElMessage.error('关联失败:' + err.message)
-  }
+function handleQuery() {
+  queryParams.pageNum = 1;
+  loadList();
 }
 
-// 打开新增
-const showAddDialog = () => {
-  isEdit.value = false
-  dialogTitle.value = '新增预警信息'
-  resetForm()
-  dialogVisible.value = true
-}
-
-// 编辑
-const editWarning = async (row) => {
-  isEdit.value = true
-  dialogTitle.value = '编辑预警信息'
-  formData.value = {
-    warningId: row.warning_id,
-    warningNo: row.warning_no || '',
-    warningName: row.warning_name || '',
-    warningType: row.warning_type || '',
-    warningLevel: row.warning_level || '',
-    location: row.location || '',
-    warningContent: row.warning_content || '',
-    publishTime: row.publish_time ? row.publish_time.substring(0, 16) : '',
-    publisher: row.publisher || '',
-    handler: row.handler || '',
-    status: row.status || 'DRAFT',
-    remark: row.remark || '',
-    ownershipUnit: row.ownership_unit || '',
-    latitude: row.latitude || null,
-    longitude: row.longitude || null,
-    warningSpecial: row.warning_special || '',
-    alarmId: row.alarm_id || '',
-    alarmName: row.alarm_name || ''
-  }
-
-  if (row.dept_id) {
-    selectedDeptId.value = row.dept_id
-    await loadUserList(row.dept_id)
-  } else {
-    selectedDeptId.value = null
-    userList.value = []
-  }
-
-  dialogVisible.value = true
+function resetQuery() {
+  queryParams.pageNum = 1;
+  queryParams.pageSize = 10;
+  queryParams.warningName = "";
+  queryParams.warningType = "";
+  queryParams.warningLevel = "";
+  queryParams.dateRange = [];
+  loadList();
 }
 
-// 提交保存
-const submitWarning = async () => {
-  await formRef.value?.validate()
-  loading.value = true
-  try {
-    const formattedPublishTime = formData.value.publishTime ? formatDate(formData.value.publishTime) : ''
-    const warningObj = { ...formData.value, publishTime: formattedPublishTime }
-
-    const params = new URLSearchParams()
-    Object.entries(warningObj).forEach(([key, value]) => {
-      params.append(`warning.${key}`, value ?? '')
-    })
-
-    let res = isEdit.value ? await updateYjxxData(params) : await submitYjxxData(params)
-    if (res.code === 200) {
-      ElMessage.success(isEdit.value ? '修改成功' : '新增成功')
-      dialogVisible.value = false
-      getWarningList()
-    } else {
-      ElMessage.error(res.msg || '保存失败')
-    }
-  } catch (err) {
-    console.error(err)
-    ElMessage.error('提交失败:' + err.message)
-  } finally {
-    loading.value = false
-  }
+function handleAdd() {
+  isEdit.value = false;
+  dialogTitle.value = "新增预警";
+  resetForm();
+  editVisible.value = true;
 }
 
-// 删除
-const deleteWarning = async (row) => {
-  await ElMessageBox.confirm('确认删除该预警信息?', '提示', { type: 'warning' })
-  loading.value = true
-  try {
-    const res = await deleteYjxxData(row.warning_id)
-    if (res.code === 200) {
-      ElMessage.success('删除成功')
-      getWarningList()
-    } else {
-      ElMessage.error(res.msg || '删除失败')
-    }
-  } catch (e) {
-    console.error(e)
-    ElMessage.error('删除失败')
-  } finally {
-    loading.value = false
-  }
+async function handleEdit(row) {
+  isEdit.value = true;
+  dialogTitle.value = "编辑预警";
+  resetForm();
+  Object.assign(formData, { ...row });
+  const detailRes = await getWarningDetailData(row.warningId);
+  const detail = detailRes.data || {};
+  formAttachments.value = (detail.attachmentList || []).map(normalizeExistingAttachment);
+  await loadReasonOptions();
+  editVisible.value = true;
 }
 
-// 发布
-const releaseWarning = async (row) => {
-  await ElMessageBox.confirm('确认发布该预警信息?发布后无法编辑', '提示', { type: 'warning' })
-  loading.value = true
+async function submitForm() {
+  await formRef.value?.validate();
+  submitLoading.value = true;
   try {
-    const res = await releaseYjxxData(row.warning_id)
-    if (res.code === 200) {
-      ElMessage.success('发布成功')
-      getWarningList()
-    } else {
-      ElMessage.error(res.msg || '发布失败')
+    const payload = {
+      warning: {
+        warningId: formData.warningId,
+        warningNo: formData.warningNo,
+        warningName: formData.warningName,
+        warningType: formData.warningType,
+        warningLevel: formData.warningLevel,
+        warningSpecial: formData.warningSpecial,
+        location: formData.location,
+        longitude: formData.longitude,
+        latitude: formData.latitude,
+        ownershipUnit: formData.ownershipUnit,
+        publisher: formData.publisher,
+        publishTime: formData.publishTime,
+        handler: formData.handler,
+        status: formData.status || "DRAFT",
+        warningContent: formData.warningContent,
+        remark: formData.remark
+      },
+      alarmIds: [],
+      files: formAttachments.value.map((item) => item.raw).filter(Boolean)
+    };
+    const res = isEdit.value ? await updateYjxxData(payload) : await submitYjxxData(payload);
+    if (res.code !== 200) {
+      ElMessage.error(res.msg || "保存失败");
+      return;
     }
-  } catch (err) {
-    console.error(err)
-    ElMessage.error('发布失败,请重试')
+    ElMessage.success(isEdit.value ? "修改成功" : "新增成功");
+    editVisible.value = false;
+    loadList();
   } finally {
-    loading.value = false
+    submitLoading.value = false;
   }
 }
 
-// 查看详情
-const viewDetail = (row) => {
-  currentWarning.value = row
-  detailVisible.value = true
-}
-
-// 重置表单
-const resetForm = () => {
-  formData.value = {
-    warningId: '', warningNo: '', warningName: '', warningType: '', warningLevel: '',
-    location: '', warningContent: '', publishTime: '', publisher: '', handler: '',
-    status: 'DRAFT', remark: '', ownershipUnit: '', latitude: null, longitude: null, warningSpecial: '',
-    alarmId: '', alarmName: ''
+async function handleView(row) {
+  const res = await getWarningDetailData(row.warningId);
+  detailData.value = res.data || {};
+  if (!detailData.value.basicInfo) {
+    detailData.value = {
+      basicInfo: row,
+      alarmList: [],
+      disposalList: []
+    };
   }
-  selectedDeptId.value = null
-  userList.value = []
-  formRef.value?.clearValidate()
-}
-
-// 分页事件
-const handleSizeChange = (val) => {
-  queryParams.value.pageSize = val
-  queryParams.value.pageNum = 1
-  getWarningList()
-}
-const handleCurrentChange = (val) => {
-  queryParams.value.pageNum = val
-  getWarningList()
+  detailVisible.value = true;
 }
 
-// 查询 / 刷新 / 重置
-const searchWarnings = () => {
-  queryParams.value.pageNum = 1
-  getWarningList()
-}
-const refreshList = () => {
-  queryParams.value.pageNum = 1
-  getWarningList()
-}
-const resetQuery = () => {
-  queryParams.value = {
-    keyword: '', warningType: '', warningLevel: '', dateRange: [], pageNum: 1, pageSize: 10
+async function handleDelete(row) {
+  await ElMessageBox.confirm(`确认删除预警“${row.warningName}”吗?`, "提示", { type: "warning" });
+  const res = await deleteYjxxData(row.warningId);
+  if (res.code !== 200) {
+    ElMessage.error(res.msg || "删除失败");
+    return;
   }
-  getWarningList()
+  ElMessage.success("删除成功");
+  loadList();
 }
 
-// 导出
-const exportData = () => ElMessage.info('导出功能开发中')
-
-// 级别样式
-const getLevelTagType = (level) => ({
-  '1': 'danger', '2': 'warning', '3': 'primary'
-}[level] || '')
-
-// 获取列表
-const getWarningList = async () => {
-  loading.value = true
-  try {
-    const reqParams = {
-      pageNum: queryParams.value.pageNum,
-      pageSize: queryParams.value.pageSize,
-      keyword: queryParams.value.keyword,
-      warningType: queryParams.value.warningType,
-      warningLevel: queryParams.value.warningLevel,
-    }
-    const res = await getdbyjlistData(reqParams)
-    if (res.code === 200) {
-      warnings.value = res.data.records.map(item => ({
-        ...item,
-        warning_type_text: typeLabel.value[item.warning_type] || item.warning_type,
-        warning_level_text: levelLabel.value[item.warning_level] || item.warning_level
-      }))
-      total.value = res.data.total
-    }
-  } catch (e) {
-    console.error(e)
-  } finally {
-    loading.value = false
-  }
-}
-
-// 获取字典
-const getDictList = async () => {
-  const getDict = async (type) => {
-    const res = await getAlertTypeDictionaryData(proxy.addDateRange({ dictType: type }))
-    return res.code === 200 ? res.rows : []
+async function handlePublish(row) {
+  await ElMessageBox.confirm(`确认发布预警“${row.warningName}”吗?`, "提示", { type: "warning" });
+  const res = await releaseYjxxData(row.warningId);
+  if (res.code !== 200) {
+    ElMessage.error(res.msg || "发布失败");
+    return;
   }
-  alertTypeList.value = await getDict('alert_type')
-  alertLevelList.value = await getDict('alert_level')
+  ElMessage.success("发布成功");
+  loadList();
 }
 
-// 初始化
 onMounted(async () => {
-  await getDictList()
-  await loadDeptTree()
-  await loadAllUserMap()
-  await getWarningList()
-})
+  await Promise.all([loadDicts(), loadWarningItems()]);
+  await loadList();
+});
 </script>
 
 <style scoped>
-.early-warning-management {
-  background: #f0f2f5;
-  min-height: 100vh;
-  padding: 20px;
+.warning-release-page {
+  background: #f5f7fb;
 }
 
-.page-header {
-  background: #fff;
-  border-radius: 12px;
-  padding: 16px 24px;
+.page-card {
+  border-radius: 16px;
   margin-bottom: 16px;
+}
+
+.header-card {
+  background: linear-gradient(135deg, #eff6ff, #f8fbff);
+  border: 1px solid #dbeafe;
+}
+
+.page-header {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  box-shadow: 0 1px 4px #00000008;
+  gap: 16px;
 }
 
-.header-left {
-  display: flex;
-  align-items: center;
-  gap: 20px;
+.page-title {
+  font-size: 22px;
+  font-weight: 700;
+  color: #1e3a8a;
 }
 
-.title {
-  font-size: 18px;
-  font-weight: 600;
-  color: #333;
+.page-subtitle {
+  margin-top: 8px;
+  font-size: 13px;
+  color: #64748b;
 }
 
-.stats-badge {
+.header-actions {
   display: flex;
-  gap: 18px;
+  gap: 10px;
+  flex-wrap: wrap;
 }
 
-.stat-item {
-  font-size: 13px;
+.reason-wrap {
   display: flex;
-  align-items: center;
-  gap: 6px;
+  flex-wrap: wrap;
+  gap: 8px;
 }
 
-.stat-item .dot {
-  width: 8px;
-  height: 8px;
-  border-radius: 50%;
+.reason-tag {
+  cursor: pointer;
 }
 
-.stat-item.critical { color: #f56c6c; }
-.stat-item.critical .dot { background: #f56c6c; }
-.stat-item.yellow { color: #f2ba00; }
-.stat-item.yellow .dot { background: #f2ba00; }
-.stat-item.blue { color: #409eff; }
-.stat-item.blue .dot { background: #409eff; }
+.attachment-panel {
+  width: 100%;
+}
 
-.query-bar {
-  background: #fff;
-  border-radius: 12px;
-  padding: 16px;
-  margin-bottom: 16px;
-  display: flex;
-  gap: 12px;
-  flex-wrap: wrap;
-  align-items: center;
+.attachment-tip {
+  margin-top: 8px;
+  font-size: 12px;
+  color: #64748b;
 }
 
-.warning-table-container {
-  background: #fff;
-  border-radius: 12px;
-  padding: 16px;
+.attachment-list,
+.detail-attachment-list {
+  margin-top: 12px;
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
 }
 
-.pagination-wrapper {
-  margin-top: 16px;
+.attachment-item,
+.detail-attachment-item {
   display: flex;
-  justify-content: flex-end;
+  align-items: center;
+  justify-content: space-between;
+  gap: 12px;
+  padding: 10px 12px;
+  border: 1px solid #e5e7eb;
+  border-radius: 10px;
+  background: #f8fafc;
 }
 
-.detail-content {
-  padding: 10px 0;
+.attachment-link {
+  flex: 1;
+  min-width: 0;
 }
 
-.pre-wrap {
-  white-space: pre-wrap;
-  line-height: 1.6;
+.attachment-meta {
+  font-size: 12px;
+  color: #94a3b8;
+  white-space: nowrap;
+}
+
+@media (max-width: 768px) {
+  .page-header {
+    flex-direction: column;
+    align-items: flex-start;
+  }
 }
-</style>
+</style>

+ 814 - 454
src/views/subSystem/lifeCompany/lAlertInformation/LReportUpload.vue

@@ -1,42 +1,108 @@
-<!--预警报告上传-->
+<!-- 预警报告上传 -->
 <template>
-  <div class="early-warning-report">
-    <div class="page-header">
-      <h2>预警报告上传</h2>
-      <p class="subtitle">基于设备报警曲线、预警位置及周边要素的详细分析</p>
-    </div>
+  <div class="app-container warning-report-page" v-loading="pageLoading">
+    <el-card shadow="never" class="page-card header-card">
+      <div class="page-header">
+        <div>
+          <div class="page-title">预警报告上传</div>
+          <div class="page-subtitle">提供预警报告附件上传功能,结合设备报警曲线、预警位置和周边要素分析,描述预警发生的具体内容</div>
+        </div>
+      </div>
+    </el-card>
+
+    <el-card shadow="never" class="page-card">
+      <el-form :model="queryParams" :inline="true">
+        <el-form-item label="预警名称">
+          <el-input
+            v-model="queryParams.warningName"
+            placeholder="请输入预警名称"
+            clearable
+            style="width: 220px"
+            @keyup.enter="loadWarningOptions"
+          />
+        </el-form-item>
+        <el-form-item label="预警类型">
+          <el-select v-model="queryParams.warningType" clearable placeholder="请选择预警类型" style="width: 180px">
+            <el-option
+              v-for="item in warningTypeOptions"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="预警级别">
+          <el-select v-model="queryParams.warningLevel" clearable placeholder="请选择预警级别" style="width: 180px">
+            <el-option
+              v-for="item in warningLevelOptions"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="选择预警">
+          <el-select
+            v-model="selectedWarningId"
+            filterable
+            clearable
+            placeholder="请选择一条预警"
+            style="width: 360px"
+            @change="handleWarningChange"
+          >
+            <el-option
+              v-for="item in warningOptions"
+              :key="item.warningId"
+              :label="item.warningName"
+              :value="item.warningId"
+            >
+              <div class="warning-option">
+                <span>{{ item.warningName }}</span>
+                <el-tag :type="getLevelTagType(item.warningLevel)">{{ item.warningLevelText }}</el-tag>
+              </div>
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="Search" @click="loadWarningOptions">查询</el-button>
+          <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
 
     <div class="report-container">
-      <!-- 左侧:附件上传与基础信息 -->
       <div class="left-panel">
-        <div class="upload-section">
+        <el-card shadow="never" class="section-card">
+          <template #header>
+            <div class="section-title">报告上传</div>
+          </template>
+
           <div
-              class="upload-area"
-              :class="{ 'drag-over': isDragOver }"
-              @dragover.prevent="isDragOver = true"
-              @dragleave.prevent="isDragOver = false"
-              @drop.prevent="handleDrop"
+            class="upload-area"
+            :class="{ 'drag-over': isDragOver }"
+            @dragover.prevent="isDragOver = true"
+            @dragleave.prevent="isDragOver = false"
+            @drop.prevent="handleDrop"
           >
             <el-icon class="upload-icon"><UploadFilled /></el-icon>
             <div class="upload-text">
-              <span class="primary-text">点击或拖拽文件至此区域</span>
-              <span class="support-text">支持 .pdf, .doc, .docx, .zip 格式,最大 20MB</span>
+              <div class="primary-text">点击或拖拽文件至此区域</div>
+              <div class="support-text">支持 `.pdf .doc .docx .zip`,单个文件不超过 20MB</div>
             </div>
             <el-upload
-                ref="uploadRef"
-                class="inline-upload"
-                action="#"
-                :auto-upload="false"
-                :on-change="handleFileChange"
-                :limit="1"
-                :accept="'.pdf,.doc,.docx,.zip'"
-                drag
+              ref="uploadRef"
+              class="inline-upload"
+              action="#"
+              :auto-upload="false"
+              :show-file-list="false"
+              :on-change="handleFileChange"
+              :limit="1"
+              accept=".pdf,.doc,.docx,.zip"
             >
-              <el-button type="primary" size="default">选择文件</el-button>
+              <el-button type="primary">选择文件</el-button>
             </el-upload>
           </div>
 
-          <!-- 已上传文件列表 -->
           <div v-if="uploadedFile" class="file-info">
             <div class="file-item">
               <el-icon><Document /></el-icon>
@@ -47,489 +113,783 @@
           </div>
 
           <div class="report-description">
-            <h4>报告摘要</h4>
+            <div class="field-title">报告摘要</div>
             <el-input
-                type="textarea"
-                v-model="reportSummary"
-                :rows="4"
-                placeholder="请输入预警报告的简要摘要或补充说明..."
-                maxlength="500"
-                show-word-limit
+              v-model="reportSummary"
+              type="textarea"
+              :rows="5"
+              maxlength="500"
+              show-word-limit
+              placeholder="请输入报告摘要、诊断结论或补充说明"
             />
           </div>
 
           <el-button
-              type="success"
-              size="large"
-              class="submit-btn"
-              :disabled="!uploadedFile"
-              @click="submitReport"
+            type="success"
+            class="submit-btn"
+            :disabled="!selectedWarningId || !uploadedFile"
+            :loading="submitLoading"
+            @click="submitReport"
           >
             <el-icon><Upload /></el-icon>
             提交预警报告
           </el-button>
-        </div>
+        </el-card>
+
+        <el-card shadow="never" class="section-card">
+          <template #header>
+            <div class="section-title">预警基础信息</div>
+          </template>
+          <el-empty v-if="!warningDetail.basicInfo" description="请先选择预警" />
+          <el-descriptions v-else :column="1" border size="small">
+            <el-descriptions-item label="预警编号">{{ currentBasicInfo.warningNo || "-" }}</el-descriptions-item>
+            <el-descriptions-item label="预警名称">{{ currentBasicInfo.warningName || "-" }}</el-descriptions-item>
+            <el-descriptions-item label="预警类型">{{ currentBasicInfo.warningTypeText || "-" }}</el-descriptions-item>
+            <el-descriptions-item label="预警级别">{{ currentBasicInfo.warningLevelText || "-" }}</el-descriptions-item>
+            <el-descriptions-item label="预警位置">{{ currentBasicInfo.location || "-" }}</el-descriptions-item>
+            <el-descriptions-item label="发布人">{{ currentBasicInfo.publisher || "-" }}</el-descriptions-item>
+            <el-descriptions-item label="发布时间">{{ currentBasicInfo.publishTime || "-" }}</el-descriptions-item>
+            <el-descriptions-item label="权属单位">{{ currentBasicInfo.ownershipUnit || "-" }}</el-descriptions-item>
+          </el-descriptions>
+        </el-card>
+
+        <el-card shadow="never" class="section-card">
+          <template #header>
+            <div class="section-title">已有附件</div>
+          </template>
+          <el-empty v-if="!attachmentList.length" description="暂无已上传附件" />
+          <div v-else class="attachment-list">
+            <div v-for="(item, index) in attachmentList" :key="index" class="attachment-item">
+              <el-icon><Document /></el-icon>
+              <el-link :href="buildAttachmentUrl(item.attachmentUrl)" target="_blank" type="primary" class="attachment-name">
+                {{ item.fileName || item.originalName || item.attachmentName || "附件" }}
+              </el-link>
+              <span class="attachment-size">{{ item.attachmentSize ? formatFileSize(item.attachmentSize) : "" }}</span>
+              <span class="attachment-time">{{ item.createTime || item.uploadTime || "-" }}</span>
+            </div>
+          </div>
+        </el-card>
       </div>
 
-      <!-- 右侧:预警分析卡片 -->
       <div class="right-panel">
-        <el-tabs v-model="activeTab" class="analysis-tabs">
-          <!-- 设备报警曲线 -->
-          <el-tab-pane label="设备报警曲线" name="curve">
-            <div class="curve-container">
-              <div ref="curveChartRef" style="width: 100%; height: 260px;"></div>
-              <div class="curve-summary">
-                <el-alert
-                    title="曲线分析"
-                    type="warning"
-                    :closable="false"
-                    description="过去24小时内,设备振动幅度超过阈值3次,峰值出现在02:15、09:40、16:20,提示设备存在间歇性异常振动。"
-                />
-              </div>
-            </div>
-          </el-tab-pane>
-
-          <!-- 预警位置情况 -->
-          <el-tab-pane label="预警位置情况" name="location">
-            <div class="location-container">
-              <div ref="locationMapRef" class="mock-map"></div>
-              <div class="location-details">
+        <el-card shadow="never" class="section-card">
+          <el-tabs v-model="activeTab" class="analysis-tabs">
+            <el-tab-pane label="设备报警曲线" name="curve">
+              <div ref="curveChartRef" class="curve-chart"></div>
+              <el-alert
+                :title="curveSummary.title"
+                :description="curveSummary.description"
+                type="warning"
+                :closable="false"
+              />
+            </el-tab-pane>
+
+            <el-tab-pane label="预警位置情况" name="location">
+              <div class="location-pane">
+                <div class="gis-card">
+                  <div class="gis-title">GIS定位信息</div>
+                  <div class="gis-point">
+                    <div class="point-badge">预警点</div>
+                    <div class="point-coord">
+                      经度:{{ gisInfo.longitude || "-" }} / 纬度:{{ gisInfo.latitude || "-" }}
+                    </div>
+                  </div>
+                </div>
                 <el-descriptions :column="1" border size="small">
-                  <el-descriptions-item label="具体位置">3#生产线 / 核心机组 1023轴承座</el-descriptions-item>
-                  <el-descriptions-item label="坐标">X=324.5, Y=189.2, Z=4.8m</el-descriptions-item>
-                  <el-descriptions-item label="预警等级">Ⅱ级(重点关注)</el-descriptions-item>
-                  <el-descriptions-item label="发现时间">2025-01-15 16:20:35</el-descriptions-item>
+                  <el-descriptions-item label="位置描述">{{ currentBasicInfo.location || "-" }}</el-descriptions-item>
+                  <el-descriptions-item label="权属单位">{{ currentBasicInfo.ownershipUnit || "-" }}</el-descriptions-item>
+                  <el-descriptions-item label="处置人">{{ currentBasicInfo.handler || "-" }}</el-descriptions-item>
+                  <el-descriptions-item label="状态">{{ currentBasicInfo.statusText || currentBasicInfo.status || "-" }}</el-descriptions-item>
                 </el-descriptions>
               </div>
-            </div>
-          </el-tab-pane>
+            </el-tab-pane>
 
-          <!-- 周边要素分析 -->
-          <el-tab-pane label="周边要素分析" name="environment">
-            <div class="environment-container">
+            <el-tab-pane label="周边要素分析" name="environment">
               <div class="factors-grid">
                 <div class="factor-card">
-                  <div class="factor-icon"><el-icon></el-icon></div>
-                  <div class="factor-info">
-                    <span class="factor-label">环境温度</span>
-                    <span class="factor-value">42.3°C <span class="trend up">▲ 2.1°C</span></span>
-                  </div>
+                  <div class="factor-label">关联报警</div>
+                  <div class="factor-value">{{ alarmList.length }}</div>
+                  <div class="factor-desc">报警曲线分析样本数</div>
                 </div>
                 <div class="factor-card">
-                  <div class="factor-icon"><el-icon><WindPower /></el-icon></div>
-                  <div class="factor-info">
-                    <span class="factor-label">振动频率</span>
-                    <span class="factor-value">127Hz <span class="trend up">▲ 18%</span></span>
-                  </div>
+                  <div class="factor-label">处置记录</div>
+                  <div class="factor-value">{{ disposalHistory.length }}</div>
+                  <div class="factor-desc">流程处置和跟踪情况</div>
                 </div>
                 <div class="factor-card">
-                  <div class="factor-icon"><el-icon><Connection /></el-icon></div>
-                  <div class="factor-info">
-                    <span class="factor-label">负载电流</span>
-                    <span class="factor-value">324A <span class="trend warn">临界值</span></span>
-                  </div>
+                  <div class="factor-label">历史附件</div>
+                  <div class="factor-value">{{ attachmentList.length }}</div>
+                  <div class="factor-desc">已上传的资料与报告数量</div>
+                </div>
+                <div class="factor-card">
+                  <div class="factor-label">电子存档</div>
+                  <div class="factor-value">{{ hasArchive ? "已生成" : "未生成" }}</div>
+                  <div class="factor-desc">预警归档与存档状态</div>
                 </div>
               </div>
-              <div class="analysis-text">
-                <el-card shadow="never">
-                  <template #header>
-                    <span>综合周边要素结论</span>
-                  </template>
-                  <p>周边冷却风道堵塞导致局部温升过高,同时电网谐波扰动加剧了电机转矩波动。振动与温度数据呈现正相关性,建议优先清理风道滤网并检查供电质量。</p>
-                </el-card>
-              </div>
+              <el-card shadow="never" class="analysis-text-card">
+                <template #header>
+                  <span>综合周边要素结论</span>
+                </template>
+                <p>{{ environmentConclusion }}</p>
+              </el-card>
+            </el-tab-pane>
+          </el-tabs>
+
+          <div class="warning-description">
+            <div class="desc-header">
+              <el-icon><InfoFilled /></el-icon>
+              <h4>预警具体内容描述</h4>
+            </div>
+            <div class="desc-content">
+              <p>{{ currentBasicInfo.warningContent || "暂无预警内容描述" }}</p>
+              <p v-if="reportSummary"><strong>报告补充摘要:</strong>{{ reportSummary }}</p>
             </div>
-          </el-tab-pane>
-        </el-tabs>
-
-        <!-- 预警具体内容描述区块(始终显示) -->
-        <div class="warning-description">
-          <div class="desc-header">
-            <el-icon><InfoFilled /></el-icon>
-            <h4>预警具体内容描述</h4>
-          </div>
-          <div class="desc-content">
-            <p>根据设备报警曲线、预警位置及周边要素综合分析,本次预警发生在<strong>3#生产线核心机组</strong>,报警类型为<strong>轴承异常振动</strong>。具体表现为:</p>
-            <ul>
-              <li>设备振动速度有效值达12.7mm/s(红色阈值10mm/s),超出安全限值27%。</li>
-              <li>振动频谱显示2倍频分量显著上升,初步判定为联轴器不对中或轴承磨损。</li>
-              <li>预警点周边温升异常(较基线高5.2℃),润滑脂寿命缩短风险增加。</li>
-              <li>结合周边要素——冷却风扇转速波动 & 电网电压暂降0.2s,形成复合激励。</li>
-            </ul>
-            <p>建议立即安排离线振动分析及热成像复查,若持续恶化可能造成非计划停机。预警报告文件需包含上述图表原始数据及诊断建议。</p>
           </div>
-        </div>
+        </el-card>
       </div>
     </div>
   </div>
 </template>
 
 <script setup>
-import { ref, onMounted } from 'vue'
-import { ElMessage } from 'element-plus'
-import { UploadFilled, Document, CircleCloseFilled, Upload,  WindPower, Connection, InfoFilled } from '@element-plus/icons-vue'
-import * as echarts from 'echarts'
-
-// 拖拽状态
-const isDragOver = ref(false)
-const uploadedFile = ref(null)
-const reportSummary = ref('')
-const activeTab = ref('curve')
-const curveChartRef = ref(null)
-
-// 文件处理
-const handleFileChange = (file, fileList) => {
-  uploadedFile.value = file.raw
-}
-const handleDrop = (e) => {
-  isDragOver.value = false
-  const file = e.dataTransfer.files[0]
-  if (file) {
-    const allowedTypes = ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/zip']
-    if (allowedTypes.includes(file.type) || file.name.endsWith('.pdf') || file.name.endsWith('.doc') || file.name.endsWith('.docx') || file.name.endsWith('.zip')) {
-      if (file.size <= 20 * 1024 * 1024) {
-        uploadedFile.value = file
-      } else {
-        ElMessage.error('文件大小不能超过20MB')
-      }
-    } else {
-      ElMessage.error('仅支持 .pdf, .doc, .docx, .zip 格式')
-    }
+import { computed, nextTick, onBeforeUnmount, onMounted, ref } from "vue";
+import { ElMessage } from "element-plus";
+import { CircleCloseFilled, Document, InfoFilled, Upload, UploadFilled } from "@element-plus/icons-vue";
+import * as echarts from "echarts";
+import {
+  getAlertTypeDictionaryData,
+  getLinkedAlarmListData,
+  getWarningDetailData,
+  getdbyjlistData,
+  updateYjxxData
+} from "@/api/smxyjldczxt/yjxxfb/yjfb.js";
+
+const pageLoading = ref(false);
+const submitLoading = ref(false);
+const isDragOver = ref(false);
+const uploadedFile = ref(null);
+const reportSummary = ref("");
+const activeTab = ref("curve");
+const uploadRef = ref();
+const curveChartRef = ref(null);
+let curveChart = null;
+
+const warningTypeOptions = ref([]);
+const warningLevelOptions = ref([]);
+const warningOptions = ref([]);
+const selectedWarningId = ref("");
+const warningDetail = ref({});
+const minioBaseUrl = (import.meta.env.VITE_APP_MINIO_BASE_URL || "").trim();
+
+const queryParams = ref({
+  warningName: "",
+  warningType: "",
+  warningLevel: "",
+  status: ""
+});
+
+const warningTypeLabelMap = computed(() => {
+  return warningTypeOptions.value.reduce((acc, item) => {
+    acc[item.dictValue] = item.dictLabel;
+    return acc;
+  }, {});
+});
+
+const warningLevelLabelMap = computed(() => {
+  return warningLevelOptions.value.reduce((acc, item) => {
+    acc[item.dictValue] = item.dictLabel;
+    return acc;
+  }, {});
+});
+
+const currentBasicInfo = computed(() => {
+  const basic = warningDetail.value.basicInfo || {};
+  return {
+    warningId: basic.warningId || basic.warning_id || "",
+    warningNo: basic.warningNo || basic.warning_no || "",
+    warningName: basic.warningName || basic.warning_name || "",
+    warningType: basic.warningType || basic.warning_type || "",
+    warningTypeText: warningTypeLabelMap.value[basic.warningType || basic.warning_type] || basic.warningType || basic.warning_type || "-",
+    warningLevel: basic.warningLevel || basic.warning_level || "",
+    warningLevelText: warningLevelLabelMap.value[basic.warningLevel || basic.warning_level] || basic.warningLevel || basic.warning_level || "-",
+    location: basic.location || "",
+    ownershipUnit: basic.ownershipUnit || basic.ownership_unit || "",
+    publisher: basic.publisher || "",
+    publishTime: basic.publishTime || basic.publish_time || "",
+    handler: basic.handler || "",
+    status: basic.status || "",
+    statusText: basic.statusText || "",
+    longitude: basic.longitude || "",
+    latitude: basic.latitude || "",
+    warningContent: basic.warningContent || basic.warning_content || "",
+    warningSpecial: basic.warningSpecial || basic.warning_special || "",
+    remark: basic.remark || ""
+  };
+});
+
+const alarmList = computed(() => warningDetail.value.alarmList || []);
+const attachmentList = computed(() => warningDetail.value.attachmentList || []);
+const disposalHistory = computed(() => warningDetail.value.disposalHistory || []);
+const gisInfo = computed(() => warningDetail.value.gisLocation || {});
+const hasArchive = computed(() => !!warningDetail.value.electronicArchive);
+
+const curveSummary = computed(() => {
+  if (!alarmList.value.length) {
+    return {
+      title: "曲线分析",
+      description: "当前预警暂无关联报警数据,无法生成设备报警曲线分析。"
+    };
   }
+  const maxAlarm = [...alarmList.value].sort((a, b) => Number(b.actual_value || b.actualValue || 0) - Number(a.actual_value || a.actualValue || 0))[0];
+  return {
+    title: "曲线分析",
+    description: `当前共关联 ${alarmList.value.length} 条报警记录,峰值实际值为 ${maxAlarm?.actual_value || maxAlarm?.actualValue || "-"},建议结合预警附件与现场情况形成完整书面报告。`
+  };
+});
+
+const environmentConclusion = computed(() => {
+  if (!warningDetail.value.basicInfo) {
+    return "请选择预警后查看周边要素分析结论。";
+  }
+  return `本次预警“${currentBasicInfo.value.warningName || "-"}”当前包含 ${alarmList.value.length} 条关联报警、${disposalHistory.value.length} 条处置记录、${attachmentList.value.length} 份附件资料。结合预警位置“${currentBasicInfo.value.location || "-"}”与电子存档情况,建议在上传报告时补充现场核查结论、报警峰值说明和后续处置建议。`;
+});
+
+function getLevelTagType(level) {
+  return {
+    "1": "danger",
+    "2": "warning",
+    "3": "success",
+    "4": "info"
+  }[level] || "info";
 }
-const removeFile = () => {
-  uploadedFile.value = null
-}
-const formatFileSize = (bytes) => {
-  if (bytes === 0) return '0 B'
-  const k = 1024
-  const sizes = ['B', 'KB', 'MB', 'GB']
-  const i = Math.floor(Math.log(bytes) / Math.log(k))
-  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
+
+function validateFile(file) {
+  const allowExt = [".pdf", ".doc", ".docx", ".zip"];
+  const lowerName = file.name.toLowerCase();
+  const extOk = allowExt.some((ext) => lowerName.endsWith(ext));
+  if (!extOk) {
+    ElMessage.error("仅支持 .pdf、.doc、.docx、.zip 格式");
+    return false;
+  }
+  if (file.size > 20 * 1024 * 1024) {
+    ElMessage.error("文件大小不能超过20MB");
+    return false;
+  }
+  return true;
 }
-const submitReport = () => {
-  if (!uploadedFile.value) {
-    ElMessage.warning('请先上传预警报告附件')
-    return
+
+function handleFileChange(file) {
+  if (!file?.raw || !validateFile(file.raw)) {
+    return;
   }
-  ElMessage.success('预警报告已提交,正在生成分析...')
-  // 此处可接入真实上传接口
-}
-
-// 初始化曲线图(模拟设备报警曲线)
-const initCurveChart = () => {
-  if (curveChartRef.value) {
-    const chart = echarts.init(curveChartRef.value)
-    const option = {
-      tooltip: { trigger: 'axis' },
-      xAxis: { type: 'category', data: ['00:00', '02:00', '04:00', '06:00', '08:00', '10:00', '12:00', '14:00', '16:00', '18:00', '20:00', '22:00'] },
-      yAxis: { type: 'value', name: '振动幅值 (mm/s)', nameLocation: 'middle', nameGap: 40 },
-      series: [
-        {
-          name: '实际振动值',
-          type: 'line',
-          data: [4.2, 13.1, 6.5, 5.8, 7.2, 6.9, 8.5, 9.2, 12.7, 10.5, 7.8, 6.3],
-          smooth: true,
-          lineStyle: { color: '#e6a23c', width: 2 },
-          symbol: 'circle',
-          symbolSize: 8,
-          areaStyle: { opacity: 0.1, color: '#e6a23c' }
-        },
-        {
-          name: '安全阈值',
-          type: 'line',
-          data: [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10],
-          lineStyle: { color: '#f56c6c', width: 2, type: 'dashed' },
-          symbol: 'none'
-        }
-      ],
-      title: { text: '过去24小时振动曲线对比', left: 'center', top: 0, textStyle: { fontSize: 13 } },
-      grid: { top: 40, bottom: 20, containLabel: true }
-    }
-    chart.setOption(option)
-    window.addEventListener('resize', () => chart.resize())
+  uploadedFile.value = file.raw;
+}
+
+function handleDrop(event) {
+  isDragOver.value = false;
+  const file = event.dataTransfer.files[0];
+  if (file && validateFile(file)) {
+    uploadedFile.value = file;
   }
 }
 
-// 模拟预警位置地图(简单canvas或CSS)
-const locationMapRef = ref(null)
-const initMockMap = () => {
-  if (locationMapRef.value) {
-    const canvas = document.createElement('canvas')
-    canvas.width = locationMapRef.value.clientWidth
-    canvas.height = 200
-    const ctx = canvas.getContext('2d')
-    ctx.fillStyle = '#f5f7fa'
-    ctx.fillRect(0, 0, canvas.width, canvas.height)
-    // 绘制简单的示意地图
-    ctx.fillStyle = '#a0cfff'
-    ctx.fillRect(20, 100, canvas.width - 40, 80)
-    ctx.fillStyle = '#66b1ff'
-    ctx.fillRect(120, 130, 60, 50)
-    ctx.fillStyle = '#f56c6c'
-    ctx.beginPath()
-    ctx.arc(150, 155, 12, 0, 2 * Math.PI)
-    ctx.fill()
-    ctx.fillStyle = 'white'
-    ctx.font = 'bold 12px sans-serif'
-    ctx.fillText('预警点', 142, 160)
-    ctx.fillStyle = '#333'
-    ctx.font = '12px sans-serif'
-    ctx.fillText('3#生产线', 50, 180)
-    ctx.fillText('冷却塔', 240, 170)
-    locationMapRef.value.innerHTML = ''
-    locationMapRef.value.appendChild(canvas)
+function removeFile() {
+  uploadedFile.value = null;
+  uploadRef.value?.clearFiles?.();
+}
+
+function formatFileSize(bytes) {
+  if (!bytes) return "0 B";
+  const units = ["B", "KB", "MB", "GB"];
+  const index = Math.floor(Math.log(bytes) / Math.log(1024));
+  return `${(bytes / 1024 ** index).toFixed(2)} ${units[index]}`;
+}
+
+function buildAttachmentUrl(path) {
+  if (!path) return "";
+  if (/^https?:\/\//i.test(path)) {
+    return path;
   }
+  const base = minioBaseUrl.endsWith("/") ? minioBaseUrl : `${minioBaseUrl}/`;
+  return `${base}${String(path).replace(/^\/+/, "")}`;
 }
 
-onMounted(() => {
-  initCurveChart()
-  initMockMap()
-})
-</script>
+function normalizeWarningRow(item) {
+  const warningId = item.warningId || item.warning_id;
+  const warningLevel = item.warningLevel || item.warning_level;
+  return {
+    warningId,
+    warningName: item.warningName || item.warning_name,
+    warningType: item.warningType || item.warning_type,
+    warningLevel,
+    warningLevelText: warningLevelLabelMap.value[warningLevel] || warningLevel
+  };
+}
 
-<style scoped lang="scss">
-.early-warning-report {
-  padding: 20px;
-  background-color: #f0f2f5;
-  min-height: 100vh;
-  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
-
-  .page-header {
-    margin-bottom: 24px;
-    h2 {
-      margin: 0 0 8px 0;
-      font-size: 24px;
-      font-weight: 600;
-      color: #303133;
-    }
-    .subtitle {
-      margin: 0;
-      color: #606266;
-      font-size: 14px;
+async function loadDicts() {
+  const [typeRes, levelRes] = await Promise.all([
+    getAlertTypeDictionaryData({ dictType: "warning_type" }),
+    getAlertTypeDictionaryData({ dictType: "warning_level" })
+  ]);
+  warningTypeOptions.value = typeRes.rows || [];
+  warningLevelOptions.value = levelRes.rows || [];
+}
+
+async function loadWarningOptions() {
+  pageLoading.value = true;
+  try {
+    const res = await getdbyjlistData({
+      pageNum: 1,
+      pageSize: 200,
+      warningName: queryParams.value.warningName || undefined,
+      warningType: queryParams.value.warningType || undefined,
+      warningLevel: queryParams.value.warningLevel || undefined,
+      status: queryParams.value.status || undefined
+    });
+    warningOptions.value = (res.data?.records || []).map(normalizeWarningRow);
+    if (!selectedWarningId.value && warningOptions.value.length) {
+      selectedWarningId.value = warningOptions.value[0].warningId;
+      await loadWarningDetail(selectedWarningId.value);
+    } else if (selectedWarningId.value && !warningOptions.value.some((item) => item.warningId === selectedWarningId.value)) {
+      selectedWarningId.value = "";
+      warningDetail.value = {};
+      renderCurveChart();
     }
+  } finally {
+    pageLoading.value = false;
   }
+}
 
-  .report-container {
-    display: flex;
-    gap: 24px;
-    flex-wrap: wrap;
-
-    .left-panel {
-      flex: 0 0 320px;
-      background: white;
-      border-radius: 16px;
-      padding: 20px;
-      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
-      height: fit-content;
-
-      .upload-section {
-        .upload-area {
-          border: 2px dashed #dcdfe6;
-          border-radius: 12px;
-          padding: 32px 20px;
-          text-align: center;
-          transition: all 0.2s ease;
-          margin-bottom: 16px;
-          cursor: pointer;
-          background: #fafafa;
-
-          &.drag-over {
-            border-color: #409eff;
-            background: #ecf5ff;
-          }
-
-          .upload-icon {
-            font-size: 48px;
-            color: #909399;
-            margin-bottom: 12px;
-          }
-
-          .upload-text {
-            .primary-text {
-              display: block;
-              font-size: 14px;
-              color: #606266;
-              margin-bottom: 6px;
-            }
-            .support-text {
-              font-size: 12px;
-              color: #909399;
-            }
-          }
-
-          .inline-upload {
-            margin-top: 16px;
-            :deep(.el-upload-dragger) {
-              display: none;
-            }
-          }
-        }
-
-        .file-info {
-          background: #f5f7fa;
-          border-radius: 8px;
-          padding: 8px 12px;
-          margin-bottom: 20px;
-          .file-item {
-            display: flex;
-            align-items: center;
-            gap: 8px;
-            .file-name {
-              flex: 1;
-              font-size: 13px;
-              color: #303133;
-            }
-            .file-size {
-              font-size: 12px;
-              color: #909399;
-            }
-            .remove-file {
-              color: #f56c6c;
-              cursor: pointer;
-              font-size: 16px;
-            }
-          }
-        }
-
-        .report-description {
-          margin: 24px 0;
-          h4 {
-            margin: 0 0 12px 0;
-            font-size: 16px;
-            font-weight: 500;
-          }
-        }
-
-        .submit-btn {
-          width: 100%;
-          margin-top: 8px;
-        }
+async function loadWarningDetail(warningId) {
+  if (!warningId) {
+    warningDetail.value = {};
+    renderCurveChart();
+    return;
+  }
+  pageLoading.value = true;
+  try {
+    const res = await getWarningDetailData(warningId);
+    warningDetail.value = res.data || {};
+    await nextTick();
+    renderCurveChart();
+  } finally {
+    pageLoading.value = false;
+  }
+}
+
+async function handleWarningChange(value) {
+  await loadWarningDetail(value);
+}
+
+function buildChartData() {
+  const alarms = [...alarmList.value].sort((a, b) => String(a.alarm_time || a.alarmTime || "").localeCompare(String(b.alarm_time || b.alarmTime || "")));
+  return {
+    xData: alarms.map((item) => String(item.alarm_time || item.alarmTime || "").slice(5, 16) || "-"),
+    actualData: alarms.map((item) => Number(item.actual_value || item.actualValue || 0)),
+    thresholdData: alarms.map((item) => {
+      const raw = item.warning_value || item.warningValue || "";
+      const numeric = String(raw).match(/-?\d+(\.\d+)?/);
+      return numeric ? Number(numeric[0]) : null;
+    })
+  };
+}
+
+function renderCurveChart() {
+  if (!curveChartRef.value) return;
+  if (!curveChart) {
+    curveChart = echarts.init(curveChartRef.value);
+    window.addEventListener("resize", resizeChart);
+  }
+  const chartData = buildChartData();
+  curveChart.setOption({
+    tooltip: { trigger: "axis" },
+    legend: { top: 0, data: ["实际值", "预警阈值"] },
+    grid: { left: 40, right: 20, top: 36, bottom: 30, containLabel: true },
+    xAxis: {
+      type: "category",
+      data: chartData.xData.length ? chartData.xData : ["暂无数据"]
+    },
+    yAxis: {
+      type: "value",
+      name: "数值"
+    },
+    series: [
+      {
+        name: "实际值",
+        type: "line",
+        smooth: true,
+        data: chartData.actualData.length ? chartData.actualData : [0],
+        lineStyle: { color: "#e6a23c", width: 2 },
+        areaStyle: { color: "rgba(230,162,60,0.12)" }
+      },
+      {
+        name: "预警阈值",
+        type: "line",
+        smooth: true,
+        data: chartData.thresholdData.length ? chartData.thresholdData : [0],
+        lineStyle: { color: "#f56c6c", type: "dashed", width: 2 }
       }
+    ]
+  });
+}
+
+function resizeChart() {
+  curveChart?.resize();
+}
+
+async function submitReport() {
+  if (!selectedWarningId.value) {
+    ElMessage.warning("请先选择预警");
+    return;
+  }
+  if (!uploadedFile.value) {
+    ElMessage.warning("请先选择报告文件");
+    return;
+  }
+
+  submitLoading.value = true;
+  try {
+    const linkedRes = await getLinkedAlarmListData(selectedWarningId.value);
+    const alarmIds = (linkedRes.data || []).map((item) => item.alarmId || item.alarm_id).filter(Boolean);
+    const payload = {
+      warning: {
+        warningId: currentBasicInfo.value.warningId,
+        warningNo: currentBasicInfo.value.warningNo,
+        warningName: currentBasicInfo.value.warningName,
+        warningType: currentBasicInfo.value.warningType,
+        warningLevel: currentBasicInfo.value.warningLevel,
+        warningSpecial: currentBasicInfo.value.warningSpecial,
+        location: currentBasicInfo.value.location,
+        longitude: currentBasicInfo.value.longitude,
+        latitude: currentBasicInfo.value.latitude,
+        ownershipUnit: currentBasicInfo.value.ownershipUnit,
+        publisher: currentBasicInfo.value.publisher,
+        publishTime: currentBasicInfo.value.publishTime,
+        handler: currentBasicInfo.value.handler,
+        status: currentBasicInfo.value.status,
+        warningContent: currentBasicInfo.value.warningContent,
+        remark: reportSummary.value
+          ? [currentBasicInfo.value.remark, `报告摘要:${reportSummary.value}`].filter(Boolean).join("\n")
+          : currentBasicInfo.value.remark
+      },
+      alarmIds,
+      files: [uploadedFile.value]
+    };
+    const res = await updateYjxxData(payload);
+    if (res.code !== 200) {
+      ElMessage.error(res.msg || "报告上传失败");
+      return;
     }
+    ElMessage.success("预警报告上传成功");
+    removeFile();
+    reportSummary.value = "";
+    await loadWarningDetail(selectedWarningId.value);
+  } finally {
+    submitLoading.value = false;
+  }
+}
 
-    .right-panel {
-      flex: 1;
-      background: white;
-      border-radius: 16px;
-      padding: 20px;
-      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
-
-      .analysis-tabs {
-        margin-bottom: 24px;
-        :deep(.el-tabs__header) {
-          margin-bottom: 16px;
-        }
-      }
+function resetQuery() {
+  queryParams.value = {
+    warningName: "",
+    warningType: "",
+    warningLevel: "",
+    status: ""
+  };
+  selectedWarningId.value = "";
+  warningDetail.value = {};
+  loadWarningOptions();
+}
 
-      .curve-container, .location-container, .environment-container {
-        margin-bottom: 24px;
-      }
+onMounted(async () => {
+  await loadDicts();
+  await loadWarningOptions();
+});
 
-      .curve-summary {
-        margin-top: 16px;
-      }
+onBeforeUnmount(() => {
+  window.removeEventListener("resize", resizeChart);
+  curveChart?.dispose();
+  curveChart = null;
+});
+</script>
 
-      .mock-map {
-        width: 100%;
-        height: 200px;
-        background: #eef2f6;
-        border-radius: 12px;
-        margin-bottom: 16px;
-        canvas {
-          width: 100%;
-          height: 100%;
-          border-radius: 12px;
-          display: block;
-        }
-      }
+<style scoped>
+.warning-report-page {
+  background: #f5f7fb;
+}
 
-      .location-details {
-        margin-top: 12px;
-      }
+.page-card {
+  border-radius: 16px;
+  margin-bottom: 16px;
+}
 
-      .environment-container {
-        .factors-grid {
-          display: flex;
-          gap: 16px;
-          margin-bottom: 24px;
-          flex-wrap: wrap;
-          .factor-card {
-            flex: 1;
-            background: #f9fafc;
-            border-radius: 12px;
-            padding: 12px;
-            display: flex;
-            align-items: center;
-            gap: 12px;
-            .factor-icon {
-              font-size: 28px;
-              color: #409eff;
-            }
-            .factor-info {
-              .factor-label {
-                display: block;
-                font-size: 12px;
-                color: #909399;
-                margin-bottom: 4px;
-              }
-              .factor-value {
-                font-weight: 500;
-                font-size: 16px;
-                .trend {
-                  font-size: 12px;
-                  margin-left: 8px;
-                  &.up { color: #f56c6c; }
-                  &.warn { color: #e6a23c; }
-                }
-              }
-            }
-          }
-        }
-        .analysis-text {
-          font-size: 14px;
-          line-height: 1.5;
-          color: #4a4a4a;
-        }
-      }
+.header-card {
+  background: linear-gradient(135deg, #eff6ff, #f8fbff);
+  border: 1px solid #dbeafe;
+}
 
-      .warning-description {
-        background: #fef9e6;
-        border-left: 4px solid #e6a23c;
-        border-radius: 12px;
-        padding: 16px 20px;
-        margin-top: 24px;
-        .desc-header {
-          display: flex;
-          align-items: center;
-          gap: 8px;
-          margin-bottom: 12px;
-          h4 {
-            margin: 0;
-            font-size: 16px;
-            font-weight: 600;
-            color: #e6a23c;
-          }
-        }
-        .desc-content {
-          font-size: 14px;
-          color: #303133;
-          p {
-            margin: 8px 0;
-          }
-          ul {
-            margin: 8px 0;
-            padding-left: 20px;
-            li {
-              margin: 6px 0;
-            }
-          }
-          strong {
-            color: #f56c6c;
-          }
-        }
-      }
-    }
+.page-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  gap: 16px;
+}
+
+.page-title {
+  font-size: 22px;
+  font-weight: 700;
+  color: #1e3a8a;
+}
+
+.page-subtitle {
+  margin-top: 8px;
+  font-size: 13px;
+  color: #64748b;
+}
+
+.warning-option {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.report-container {
+  display: grid;
+  grid-template-columns: 360px minmax(0, 1fr);
+  gap: 16px;
+}
+
+.section-card {
+  border-radius: 16px;
+  margin-bottom: 16px;
+}
+
+.section-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #1f2937;
+}
+
+.upload-area {
+  border: 2px dashed #dcdfe6;
+  border-radius: 12px;
+  padding: 28px 16px;
+  text-align: center;
+  transition: all 0.2s ease;
+  background: #fafafa;
+}
+
+.upload-area.drag-over {
+  border-color: #409eff;
+  background: #ecf5ff;
+}
+
+.upload-icon {
+  font-size: 46px;
+  color: #909399;
+  margin-bottom: 10px;
+}
+
+.upload-text {
+  margin-bottom: 14px;
+}
+
+.primary-text {
+  font-size: 14px;
+  color: #374151;
+}
+
+.support-text {
+  margin-top: 6px;
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.file-info,
+.attachment-list {
+  margin-top: 16px;
+}
+
+.file-item,
+.attachment-item {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 10px 12px;
+  border-radius: 10px;
+  background: #f8fafc;
+  border: 1px solid #edf2f7;
+}
+
+.attachment-item + .attachment-item {
+  margin-top: 8px;
+}
+
+.file-name,
+.attachment-name {
+  flex: 1;
+  font-size: 13px;
+  color: #303133;
+}
+
+.file-size,
+.attachment-time {
+  font-size: 12px;
+  color: #909399;
+}
+
+.remove-file {
+  color: #f56c6c;
+  cursor: pointer;
+  font-size: 16px;
+}
+
+.report-description {
+  margin-top: 20px;
+}
+
+.field-title {
+  margin-bottom: 10px;
+  font-size: 14px;
+  font-weight: 600;
+  color: #374151;
+}
+
+.submit-btn {
+  width: 100%;
+  margin-top: 16px;
+}
+
+.curve-chart {
+  width: 100%;
+  height: 280px;
+  margin-bottom: 16px;
+}
+
+.location-pane {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 16px;
+}
+
+.gis-card {
+  min-height: 180px;
+  padding: 18px;
+  border-radius: 14px;
+  background: linear-gradient(135deg, #eef6ff, #f8fbff);
+  border: 1px solid #dbeafe;
+}
+
+.gis-title {
+  font-size: 15px;
+  font-weight: 600;
+  color: #1d4ed8;
+}
+
+.gis-point {
+  margin-top: 28px;
+  display: flex;
+  flex-direction: column;
+  gap: 14px;
+}
+
+.point-badge {
+  width: 76px;
+  text-align: center;
+  padding: 6px 0;
+  border-radius: 999px;
+  background: #ef4444;
+  color: #fff;
+  font-size: 12px;
+}
+
+.point-coord {
+  font-size: 14px;
+  color: #374151;
+}
+
+.factors-grid {
+  display: grid;
+  grid-template-columns: repeat(4, minmax(0, 1fr));
+  gap: 12px;
+  margin-bottom: 16px;
+}
+
+.factor-card {
+  padding: 16px;
+  border-radius: 14px;
+  background: #f8fafc;
+  border: 1px solid #edf2f7;
+}
+
+.factor-label {
+  font-size: 13px;
+  color: #64748b;
+}
+
+.factor-value {
+  margin: 10px 0 6px;
+  font-size: 24px;
+  font-weight: 700;
+  color: #111827;
+}
+
+.factor-desc {
+  font-size: 12px;
+  color: #94a3b8;
+}
+
+.analysis-text-card {
+  margin-top: 8px;
+}
+
+.warning-description {
+  margin-top: 20px;
+  background: #fef9e6;
+  border-left: 4px solid #e6a23c;
+  border-radius: 12px;
+  padding: 16px 20px;
+}
+
+.desc-header {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 12px;
+}
+
+.desc-header h4 {
+  margin: 0;
+  font-size: 16px;
+  font-weight: 600;
+  color: #e6a23c;
+}
+
+.desc-content {
+  font-size: 14px;
+  color: #303133;
+  line-height: 1.8;
+}
+
+.desc-content p {
+  margin: 0 0 8px;
+}
+
+@media (max-width: 1200px) {
+  .report-container {
+    grid-template-columns: 1fr;
+  }
+
+  .factors-grid {
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+  }
+}
+
+@media (max-width: 768px) {
+  .location-pane,
+  .factors-grid {
+    grid-template-columns: 1fr;
   }
 }
-</style>
+</style>

+ 434 - 439
src/views/subSystem/lifeCompany/lAlertInformation/LTobeHandled.vue

@@ -1,525 +1,520 @@
-<!--待办预警-->
-<!--<template>-->
-<!--  <div>待办预警-->
-<!--    展示当前登录用户需处置的预警列表数据,实现对预警名称,预警编号,预警专项,预警类-->
-<!--    型,处置人,发布人,报警编号,报警位置,权属单位,预警级别,发布时间等字段进行数据查-->
-<!--    询的功能。包括我的待办及我的已办,可快速查阅待办预警信息。</div>-->
-<!--</template>-->
+<!-- 待办预警 -->
 <template>
-  <div class="pending-orders">
-    <!-- 页面头部 -->
-    <div class="page-header">
-      <div class="header-left">
-        <el-icon><List /></el-icon>
-        <span class="title">待处理预警</span>
-        <el-tag type="danger" effect="dark" v-if="pendingOrdersCount > 0">待处理 {{ pendingOrdersCount }}</el-tag>
-      </div>
-      <div class="header-right">
-        <el-input
-            v-model="searchKeyword"
-            placeholder="搜索工单编号/案件编号/地址"
-            clearable
-            :prefix-icon="Search"
-            style="width: 260px"
-            size="default"
-        />
-        <el-select v-model="alertLevelFilter" placeholder="报警级别" clearable style="width: 120px" size="default">
-          <el-option label="严重" value="critical" />
-          <el-option label="一般" value="warning" />
-        </el-select>
-        <el-button type="primary" :icon="Refresh" @click="refreshList">刷新</el-button>
-      </div>
-    </div>
-
-    <!-- 统计卡片 -->
-    <div class="stats-cards">
-      <div class="stat-card" :class="{ active: statusFilter === '' }" @click="setStatusFilter('')">
-        <div class="stat-value">{{ totalOrders }}</div>
-        <div class="stat-label">全部预警</div>
-      </div>
-      <div class="stat-card pending" :class="{ active: statusFilter === 'pending' }" @click="setStatusFilter('pending')">
-        <div class="stat-value">{{ pendingOrdersCount }}</div>
-        <div class="stat-label">待处理</div>
-      </div>
-      <div class="stat-card processing" :class="{ active: statusFilter === 'processing' }" @click="setStatusFilter('processing')">
-        <div class="stat-value">{{ processingOrdersCount }}</div>
-        <div class="stat-label">处理中</div>
-      </div>
-      <div class="stat-card overdue" :class="{ active: statusFilter === 'overdue' }" @click="setStatusFilter('overdue')">
-        <div class="stat-value">{{ overdueCount }}</div>
-        <div class="stat-label">已逾期</div>
+  <div class="app-container todo-warning-page" v-loading="loading">
+    <el-card shadow="never" class="page-card header-card">
+      <div class="page-header">
+        <div>
+          <div class="page-title">待办预警</div>
+          <div class="page-subtitle">展示当前登录用户需处置的预警列表,支持“我的待办”和“我的已办”快速查阅与筛选</div>
+        </div>
+        <el-button type="primary" icon="Refresh" @click="loadCurrentTabData">刷新</el-button>
       </div>
+    </el-card>
+
+    <div class="summary-grid">
+      <el-card shadow="hover" class="summary-card danger">
+        <div class="summary-label">我的待办</div>
+        <div class="summary-value">{{ todoCount }}</div>
+        <div class="summary-desc">当前用户待处理预警数量</div>
+      </el-card>
+      <el-card shadow="hover" class="summary-card success">
+        <div class="summary-label">我的已办</div>
+        <div class="summary-value">{{ doneCount }}</div>
+        <div class="summary-desc">当前用户已处理完成预警数量</div>
+      </el-card>
+      <el-card shadow="hover" class="summary-card primary">
+        <div class="summary-label">当前视图</div>
+        <div class="summary-value">{{ filteredList.length }}</div>
+        <div class="summary-desc">本页筛选后可查看记录数</div>
+      </el-card>
     </div>
 
-    <!-- 工单列表 -->
-    <div class="order-table-container">
-      <el-table
-          :data="paginatedOrders"
-          stripe
-          border
-          style="width: 100%"
-          v-loading="loading"
-          @row-click="handleRowClick"
-      >
-        <el-table-column type="index" label="序号" width="55" align="center" />
-        <el-table-column prop="code" label="工单编号" width="160" sortable />
-        <el-table-column prop="caseCode" label="关联案件编号" width="160" />
-        <el-table-column prop="alertLevel" label="报警级别" width="100" align="center">
-          <template #default="{ row }">
-            <el-tag :type="row.alertLevel === 'critical' ? 'danger' : 'warning'" size="small">
-              {{ row.alertLevel === 'critical' ? '严重' : '一般' }}
+    <el-card shadow="never" class="page-card">
+      <el-form :model="queryParams" :inline="true">
+        <el-form-item label="预警名称">
+          <el-input
+            v-model="queryParams.warningName"
+            placeholder="请输入预警名称"
+            clearable
+            style="width: 220px"
+            @keyup.enter="handleQuery"
+          />
+        </el-form-item>
+        <el-form-item label="预警编号">
+          <el-input v-model="queryParams.warningNo" placeholder="请输入预警编号" clearable style="width: 200px" />
+        </el-form-item>
+        <el-form-item label="预警专项">
+          <el-input v-model="queryParams.warningSpecial" placeholder="请输入预警专项" clearable style="width: 200px" />
+        </el-form-item>
+        <el-form-item label="预警类型">
+          <el-select v-model="queryParams.warningType" clearable placeholder="请选择预警类型" style="width: 180px">
+            <el-option
+              v-for="item in warningTypeOptions"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="预警级别">
+          <el-select v-model="queryParams.warningLevel" clearable placeholder="请选择预警级别" style="width: 180px">
+            <el-option
+              v-for="item in warningLevelOptions"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="处置人">
+          <el-input v-model="queryParams.handler" placeholder="请输入处置人" clearable style="width: 180px" />
+        </el-form-item>
+        <el-form-item label="发布人">
+          <el-input v-model="queryParams.publisher" placeholder="请输入发布人" clearable style="width: 180px" />
+        </el-form-item>
+        <el-form-item label="报警编号">
+          <el-input v-model="queryParams.alarmCode" placeholder="请输入报警编号" clearable style="width: 180px" />
+        </el-form-item>
+        <el-form-item label="报警位置">
+          <el-input v-model="queryParams.location" placeholder="请输入报警位置" clearable style="width: 200px" />
+        </el-form-item>
+        <el-form-item label="权属单位">
+          <el-input v-model="queryParams.ownershipUnit" placeholder="请输入权属单位" clearable style="width: 180px" />
+        </el-form-item>
+        <el-form-item label="发布时间">
+          <el-date-picker
+            v-model="queryParams.dateRange"
+            type="datetimerange"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            range-separator="至"
+            start-placeholder="开始时间"
+            end-placeholder="结束时间"
+            style="width: 360px"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="Search" @click="handleQuery">查询</el-button>
+          <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
+
+    <el-card shadow="never" class="page-card">
+      <el-tabs v-model="activeTab" @tab-change="handleTabChange">
+        <el-tab-pane :label="`我的待办 (${todoCount})`" name="TODO" />
+        <el-tab-pane :label="`我的已办 (${doneCount})`" name="DONE" />
+      </el-tabs>
+
+      <el-table :data="pagedList" @row-click="handleViewDetail">
+        <el-table-column label="序号" width="70" align="center">
+          <template #default="scope">
+            {{ (pagination.pageNum - 1) * pagination.pageSize + scope.$index + 1 }}
+          </template>
+        </el-table-column>
+        <el-table-column label="预警名称" prop="warningName" min-width="180" show-overflow-tooltip />
+        <el-table-column label="预警编号" prop="warningNo" width="170" />
+        <el-table-column label="预警专项" prop="warningSpecial" width="140" show-overflow-tooltip />
+        <el-table-column label="预警类型" prop="warningTypeText" width="120" />
+        <el-table-column label="处置人" prop="handler" width="120" />
+        <el-table-column label="发布人" prop="publisher" width="120" />
+        <el-table-column label="报警编号" width="160">
+          <template #default="scope">
+            {{ scope.row.alarmCodeList.length ? scope.row.alarmCodeList.join(",") : "-" }}
+          </template>
+        </el-table-column>
+        <el-table-column label="报警位置" prop="location" min-width="180" show-overflow-tooltip />
+        <el-table-column label="权属单位" prop="ownershipUnit" width="160" show-overflow-tooltip />
+        <el-table-column label="预警级别" width="110" align="center">
+          <template #default="scope">
+            <el-tag :type="getLevelTagType(scope.row.warningLevel)">
+              {{ scope.row.warningLevelText || "-" }}
             </el-tag>
           </template>
         </el-table-column>
-        <el-table-column prop="alertType" label="报警类型" width="120" />
-        <el-table-column prop="alertDesc" label="报警描述" min-width="200" show-overflow-tooltip />
-        <el-table-column prop="ownerUnit" label="权属单位" width="140" />
-        <el-table-column prop="address" label="详细地址" min-width="180" show-overflow-tooltip />
-        <el-table-column prop="status" label="工单状态" width="100" align="center">
-          <template #default="{ row }">
-            <el-tag :type="getStatusType(row.status)" size="small">
-              {{ getStatusText(row.status) }}
+        <el-table-column label="发布时间" prop="publishTime" width="180" />
+        <el-table-column label="状态" width="100" align="center">
+          <template #default="scope">
+            <el-tag :type="activeTab === 'TODO' ? 'danger' : 'success'">
+              {{ activeTab === "TODO" ? "待办" : "已办" }}
             </el-tag>
           </template>
         </el-table-column>
-        <el-table-column prop="createTime" label="创建时间" width="160" sortable />
-        <el-table-column label="操作" width="120" fixed="right" align="center">
-          <template #default="{ row }">
-            <el-button link type="primary" size="small" @click.stop="viewDetail(row)">查看详情</el-button>
-            <el-button
-                v-if="row.status === 'pending'"
-                link type="success"
-                size="small"
-                @click.stop="handleOrder(row)"
-            >处理</el-button>
+        <el-table-column label="操作" fixed="right" width="120" align="center">
+          <template #default="scope">
+            <el-button link type="primary" @click.stop="handleViewDetail(scope.row)">查看详情</el-button>
           </template>
         </el-table-column>
       </el-table>
 
-      <!-- 分页 -->
-      <div class="pagination-wrapper">
-        <el-pagination
-            background
-            layout="total, sizes, prev, pager, next, jumper"
-            :total="filteredOrders.length"
-            v-model:current-page="currentPage"
-            v-model:page-size="pageSize"
-            :page-sizes="[10, 20, 50, 100]"
-        />
-      </div>
-    </div>
-
-    <!-- 工单详情抽屉 -->
-    <el-drawer v-model="detailDrawerVisible" :title="`工单详情 - ${currentOrder?.code}`" size="500px" direction="rtl">
-      <div v-if="currentOrder" class="order-detail">
-        <el-descriptions :column="1" border>
-          <el-descriptions-item label="工单编号">{{ currentOrder.code }}</el-descriptions-item>
-          <el-descriptions-item label="关联案件">
-            <el-link type="primary" @click="viewCaseDetail">{{ currentOrder.caseCode }}</el-link>
-          </el-descriptions-item>
-          <el-descriptions-item label="报警级别">
-            <el-tag :type="currentOrder.alertLevel === 'critical' ? 'danger' : 'warning'">
-              {{ currentOrder.alertLevel === 'critical' ? '严重' : '一般' }}
-            </el-tag>
-          </el-descriptions-item>
-          <el-descriptions-item label="报警类型">{{ currentOrder.alertType }}</el-descriptions-item>
-          <el-descriptions-item label="报警描述">{{ currentOrder.alertDesc }}</el-descriptions-item>
-          <el-descriptions-item label="权属单位">{{ currentOrder.ownerUnit }}</el-descriptions-item>
-          <el-descriptions-item label="详细地址">{{ currentOrder.address }}</el-descriptions-item>
-          <el-descriptions-item label="用途">{{ currentOrder.purpose || '市政排水' }}</el-descriptions-item>
-          <el-descriptions-item label="工单状态">
-            <el-tag :type="getStatusType(currentOrder.status)">{{ getStatusText(currentOrder.status) }}</el-tag>
-          </el-descriptions-item>
-          <el-descriptions-item label="创建时间">{{ currentOrder.createTime }}</el-descriptions-item>
-          <el-descriptions-item label="指派人">{{ currentOrder.assignee || '未指派' }}</el-descriptions-item>
-          <el-descriptions-item label="计划完成时间">{{ currentOrder.deadline || '—' }}</el-descriptions-item>
+      <pagination
+        v-show="filteredList.length > 0"
+        :total="filteredList.length"
+        v-model:page="pagination.pageNum"
+        v-model:limit="pagination.pageSize"
+        @pagination="handlePageChange"
+      />
+    </el-card>
+
+    <el-drawer v-model="detailVisible" title="预警详情" size="760px" append-to-body>
+      <template v-if="detailData.basicInfo">
+        <el-descriptions :column="2" border>
+          <el-descriptions-item label="预警名称">{{ detailBasic.warningName || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="预警编号">{{ detailBasic.warningNo || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="预警专项">{{ detailBasic.warningSpecial || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="预警类型">{{ detailBasic.warningTypeText || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="预警级别">{{ detailBasic.warningLevelText || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="处置人">{{ detailBasic.handler || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="发布人">{{ detailBasic.publisher || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="权属单位">{{ detailBasic.ownershipUnit || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="报警位置" :span="2">{{ detailBasic.location || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="发布时间">{{ detailBasic.publishTime || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="经纬度">{{ detailBasic.longitude || "-" }} / {{ detailBasic.latitude || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="预警内容" :span="2">{{ detailBasic.warningContent || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="备注" :span="2">{{ detailBasic.remark || "-" }}</el-descriptions-item>
         </el-descriptions>
 
-        <!-- 处理记录 -->
-        <div class="process-logs" v-if="currentOrder.processLogs?.length">
-          <div class="sub-title">处理记录</div>
-          <el-timeline>
-            <el-timeline-item v-for="log in currentOrder.processLogs" :key="log.id" :timestamp="log.time" :type="log.type">
-              {{ log.content }}
-            </el-timeline-item>
-          </el-timeline>
-        </div>
-
-        <!-- 操作按钮 -->
-        <div class="detail-actions" v-if="currentOrder.status === 'pending'">
-          <el-button type="primary" @click="handleOrder(currentOrder)">开始处理</el-button>
-          <el-button type="warning" @click="openAssignDialog">指派人员</el-button>
-        </div>
-      </div>
-    </el-drawer>
-
-    <!-- 指派人员对话框 -->
-    <el-dialog v-model="assignDialogVisible" title="指派维护人员" width="400px">
-      <el-form>
-        <el-form-item label="选择维护人员">
-          <el-select v-model="selectedWorkerId" placeholder="请选择" style="width: 100%">
-            <el-option v-for="worker in workers" :key="worker.id" :label="worker.name" :value="worker.id" />
-          </el-select>
-        </el-form-item>
-        <el-form-item label="备注">
-          <el-input type="textarea" v-model="assignRemark" placeholder="可选,填写派单说明" rows="2" />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <el-button @click="assignDialogVisible = false">取消</el-button>
-        <el-button type="primary" @click="confirmAssign">确认指派</el-button>
+        <el-divider content-position="left">关联报警</el-divider>
+        <el-table :data="detailData.alarmList || []" border size="small">
+          <el-table-column label="报警编号" prop="warning_code" min-width="160" />
+          <el-table-column label="报警类型" prop="warning_type" min-width="120" />
+          <el-table-column label="设备编码" prop="device_code" min-width="160" />
+          <el-table-column label="实际值" prop="actual_value" min-width="100" />
+          <el-table-column label="报警时间" prop="alarm_time" min-width="180" />
+        </el-table>
+
+        <el-divider content-position="left">处置记录</el-divider>
+        <el-timeline v-if="(detailData.disposalHistory || []).length">
+          <el-timeline-item
+            v-for="(item, index) in detailData.disposalHistory"
+            :key="index"
+            :timestamp="item.createTime || item.disposalTime || '-'"
+          >
+            {{ item.disposalContent || item.content || "-" }}
+          </el-timeline-item>
+        </el-timeline>
+        <el-empty v-else description="暂无处置记录" />
       </template>
-    </el-dialog>
+    </el-drawer>
   </div>
 </template>
 
 <script setup>
-import { ref, computed } from 'vue'
-import { List, Search, Refresh } from '@element-plus/icons-vue'
-import { ElMessage } from 'element-plus'
-
+import { computed, onMounted, reactive, ref } from "vue";
+import useUserStore from "@/store/modules/user";
+import { ElMessage } from "element-plus";
+import { getAlertTypeDictionaryData, getWarningDetailData } from "@/api/smxyjldczxt/yjxxfb/yjfb.js";
 import { getdbyjlistData } from "@/api/smxyjldczxt/yjxxfb/dbyj.js";
 
-const { proxy } = getCurrentInstance();
-// 模拟工单数据
-const mockOrders = ref([
-  {
-    id: 1, code: 'WO-20250402-001', caseCode: 'CASE-20250402-001', alertLevel: 'critical',
-    alertType: '倾斜超标', alertDesc: '井盖倾斜角度14.2°,超过阈值8°,有跌落风险',
-    ownerUnit: '市政工程管理处', address: '人民路与解放路口东50m', purpose: '主干道排水',
-    status: 'pending', createTime: '2025-04-02 14:25:00', assignee: null, deadline: '2025-04-03 14:25:00',
-    processLogs: [{ id: 1, time: '2025-04-02 14:25:00', content: '工单自动创建', type: 'info' }]
-  },
-  {
-    id: 2, code: 'WO-20250402-002', caseCode: 'CASE-20250402-002', alertLevel: 'critical',
-    alertType: '水位超限', alertDesc: '井下水位62cm,超过阈值40cm,溢水风险',
-    ownerUnit: '水务集团', address: '滨江路化工园区南门', purpose: '化工区雨水排放',
-    status: 'processing', createTime: '2025-04-02 14:00:00', assignee: '张建国', deadline: '2025-04-03 10:00:00',
-    processLogs: [
-      { id: 1, time: '2025-04-02 14:00:00', content: '工单自动创建', type: 'info' },
-      { id: 2, time: '2025-04-02 14:30:00', content: '已指派张建国前往处置', type: 'primary' }
-    ]
-  },
-  {
-    id: 3, code: 'WO-20250402-003', caseCode: 'CASE-20250402-003', alertLevel: 'warning',
-    alertType: '异常震动', alertDesc: '异常震动112mg,超过阈值80mg,疑似外力冲击',
-    ownerUnit: '市政设施管理处', address: '古城街北段树下', purpose: '老城区排水',
-    status: 'pending', createTime: '2025-04-02 12:15:00', assignee: null, deadline: '2025-04-03 12:15:00',
-    processLogs: [{ id: 1, time: '2025-04-02 12:15:00', content: '工单自动创建', type: 'info' }]
-  },
-  {
-    id: 4, code: 'WO-20250402-004', caseCode: 'CASE-20250402-004', alertLevel: 'warning',
-    alertType: '井盖开启异常', alertDesc: '井盖非正常开启',
-    ownerUnit: '市政工程管理处', address: '南港路旧车站段', purpose: '市政井盖',
-    status: 'overdue', createTime: '2025-04-01 10:30:00', assignee: '王大明', deadline: '2025-04-02 10:30:00',
-    processLogs: [
-      { id: 1, time: '2025-04-01 10:30:00', content: '工单创建', type: 'info' },
-      { id: 2, time: '2025-04-01 11:00:00', content: '指派王大明处置', type: 'primary' },
-      { id: 3, time: '2025-04-02 10:30:00', content: '工单已逾期,待处理', type: 'danger' }
-    ]
-  },
-  {
-    id: 5, code: 'WO-20250402-005', caseCode: 'CASE-20250402-005', alertLevel: 'critical',
-    alertType: '气体超标', alertDesc: '井下H₂S浓度28ppm,超过阈值15ppm',
-    ownerUnit: '生态环境局', address: '石化大道南侧', purpose: '化工区排污',
-    status: 'pending', createTime: '2025-04-02 09:20:00', assignee: null, deadline: '2025-04-03 09:20:00',
-    processLogs: [{ id: 1, time: '2025-04-02 09:20:00', content: '工单自动创建', type: 'info' }]
-  },
-  {
-    id: 6, code: 'WO-20250402-006', caseCode: 'CASE-20250402-006', alertLevel: 'warning',
-    alertType: '电池低电量', alertDesc: '设备电池电量仅剩12%,需及时更换',
-    ownerUnit: '市政设施管理处', address: '中央公园北门', purpose: '公园排水',
-    status: 'processing', createTime: '2025-04-02 08:45:00', assignee: '陈辉', deadline: '2025-04-03 08:45:00',
-    processLogs: [
-      { id: 1, time: '2025-04-02 08:45:00', content: '工单创建', type: 'info' },
-      { id: 2, time: '2025-04-02 09:30:00', content: '指派陈辉前往更换电池', type: 'primary' }
-    ]
-  }
-])
-
-// 维护人员列表
-const workers = ref([
-  { id: 'w1', name: '张建国 - 维修一组' },
-  { id: 'w2', name: '李志强 - 维修二组' },
-  { id: 'w3', name: '王大明 - 应急抢修队' },
-  { id: 'w4', name: '陈辉 - 巡检专员' },
-  { id: 'w5', name: '赵志远 - 抢修班' }
-])
-
-// 筛选状态
-const searchKeyword = ref('')
-const alertLevelFilter = ref('')
-const statusFilter = ref('pending')  // 默认显示待处理
-const currentPage = ref(1)
-const pageSize = ref(10)
-const loading = ref(false)
-
-// 弹窗/抽屉状态
-const detailDrawerVisible = ref(false)
-const assignDialogVisible = ref(false)
-const currentOrder = ref(null)
-const selectedWorkerId = ref('')
-const assignRemark = ref('')
-
-// 计算属性
-const filteredOrders = computed(() => {
-  let list = mockOrders.value
-
-  // 状态筛选
-  if (statusFilter.value) {
-    list = list.filter(o => o.status === statusFilter.value)
-  }
-
-  // 报警级别筛选
-  if (alertLevelFilter.value) {
-    list = list.filter(o => o.alertLevel === alertLevelFilter.value)
-  }
-
-  // 关键词搜索
-  if (searchKeyword.value) {
-    const kw = searchKeyword.value.toLowerCase()
-    list = list.filter(o =>
-        o.code.toLowerCase().includes(kw) ||
-        o.caseCode.toLowerCase().includes(kw) ||
-        o.address.toLowerCase().includes(kw)
-    )
-  }
-
-  return list
-})
-
-const paginatedOrders = computed(() => {
-  const start = (currentPage.value - 1) * pageSize.value
-  return filteredOrders.value.slice(start, start + pageSize.value)
-})
-
-const totalOrders = computed(() => mockOrders.value.length)
-const pendingOrdersCount = computed(() => mockOrders.value.filter(o => o.status === 'pending').length)
-const processingOrdersCount = computed(() => mockOrders.value.filter(o => o.status === 'processing').length)
-const overdueCount = computed(() => mockOrders.value.filter(o => o.status === 'overdue').length)
-
-// 方法
-const setStatusFilter = (status) => {
-  statusFilter.value = status
-  currentPage.value = 1
+const userStore = useUserStore();
+const loading = ref(false);
+const activeTab = ref("TODO");
+const todoList = ref([]);
+const doneList = ref([]);
+const warningTypeOptions = ref([]);
+const warningLevelOptions = ref([]);
+const detailVisible = ref(false);
+const detailData = ref({});
+
+const queryParams = reactive({
+  warningName: "",
+  warningNo: "",
+  warningSpecial: "",
+  warningType: "",
+  warningLevel: "",
+  handler: "",
+  publisher: "",
+  alarmCode: "",
+  location: "",
+  ownershipUnit: "",
+  dateRange: []
+});
+
+const pagination = reactive({
+  pageNum: 1,
+  pageSize: 10
+});
+
+const warningTypeLabelMap = computed(() => {
+  return warningTypeOptions.value.reduce((acc, item) => {
+    acc[item.dictValue] = item.dictLabel;
+    return acc;
+  }, {});
+});
+
+const warningLevelLabelMap = computed(() => {
+  return warningLevelOptions.value.reduce((acc, item) => {
+    acc[item.dictValue] = item.dictLabel;
+    return acc;
+  }, {});
+});
+
+const currentList = computed(() => (activeTab.value === "TODO" ? todoList.value : doneList.value));
+const todoCount = computed(() => todoList.value.length);
+const doneCount = computed(() => doneList.value.length);
+
+const filteredList = computed(() => {
+  return currentList.value.filter((item) => {
+    const publishTime = item.publishTime || "";
+    const [start, end] = queryParams.dateRange || [];
+    if (queryParams.warningName && !String(item.warningName || "").includes(queryParams.warningName)) return false;
+    if (queryParams.warningNo && !String(item.warningNo || "").includes(queryParams.warningNo)) return false;
+    if (queryParams.warningSpecial && !String(item.warningSpecial || "").includes(queryParams.warningSpecial)) return false;
+    if (queryParams.warningType && item.warningType !== queryParams.warningType) return false;
+    if (queryParams.warningLevel && item.warningLevel !== queryParams.warningLevel) return false;
+    if (queryParams.handler && !String(item.handler || "").includes(queryParams.handler)) return false;
+    if (queryParams.publisher && !String(item.publisher || "").includes(queryParams.publisher)) return false;
+    if (queryParams.location && !String(item.location || "").includes(queryParams.location)) return false;
+    if (queryParams.ownershipUnit && !String(item.ownershipUnit || "").includes(queryParams.ownershipUnit)) return false;
+    if (queryParams.alarmCode && !item.alarmCodeList.some((code) => String(code).includes(queryParams.alarmCode))) return false;
+    if (start && publishTime && publishTime < start) return false;
+    if (end && publishTime && publishTime > end) return false;
+    return true;
+  });
+});
+
+const pagedList = computed(() => {
+  const start = (pagination.pageNum - 1) * pagination.pageSize;
+  return filteredList.value.slice(start, start + pagination.pageSize);
+});
+
+const detailBasic = computed(() => {
+  const basic = detailData.value.basicInfo || {};
+  const warningType = basic.warningType || basic.warning_type || "";
+  const warningLevel = basic.warningLevel || basic.warning_level || "";
+  return {
+    warningName: basic.warningName || basic.warning_name || "",
+    warningNo: basic.warningNo || basic.warning_no || "",
+    warningSpecial: basic.warningSpecial || basic.warning_special || "",
+    warningTypeText: warningTypeLabelMap.value[warningType] || warningType || "-",
+    warningLevelText: warningLevelLabelMap.value[warningLevel] || warningLevel || "-",
+    handler: basic.handler || "",
+    publisher: basic.publisher || "",
+    ownershipUnit: basic.ownershipUnit || basic.ownership_unit || "",
+    location: basic.location || "",
+    publishTime: basic.publishTime || basic.publish_time || "",
+    longitude: basic.longitude || "",
+    latitude: basic.latitude || "",
+    warningContent: basic.warningContent || basic.warning_content || "",
+    remark: basic.remark || ""
+  };
+});
+
+function getLevelTagType(level) {
+  return {
+    "1": "danger",
+    "2": "warning",
+    "3": "success",
+    "4": "info"
+  }[level] || "info";
 }
 
-const getStatusType = (status) => {
-  const map = { pending: 'danger', processing: 'warning', overdue: 'danger', completed: 'success' }
-  return map[status] || 'info'
+async function ensureUserId() {
+  if (userStore.id) {
+    return userStore.id;
+  }
+  await userStore.getInfo();
+  return userStore.id;
 }
 
-const getStatusText = (status) => {
-  const map = { pending: '待处理', processing: '处理中', overdue: '已逾期', completed: '已完成' }
-  return map[status] || status
+async function loadDicts() {
+  const [typeRes, levelRes] = await Promise.all([
+    getAlertTypeDictionaryData({ dictType: "warning_type" }),
+    getAlertTypeDictionaryData({ dictType: "warning_level" })
+  ]);
+  warningTypeOptions.value = typeRes.rows || [];
+  warningLevelOptions.value = levelRes.rows || [];
 }
 
-const refreshList = () => {
-  loading.value = true
-  setTimeout(() => {
-    loading.value = false
-    ElMessage.success('列表已刷新')
-  }, 500)
+async function enrichTodoRows(records) {
+  const rows = await Promise.all(
+    (records || []).map(async (item) => {
+      const warningId = item.warningId || item.warning_id;
+      const detailRes = await getWarningDetailData(warningId);
+      const detail = detailRes.data || {};
+      const basic = detail.basicInfo || {};
+      const alarmList = detail.alarmList || [];
+      const warningType = basic.warningType || basic.warning_type || item.warning_type || "";
+      const warningLevel = basic.warningLevel || basic.warning_level || item.warning_level || "";
+      return {
+        todoId: item.todoId || item.todo_id,
+        warningId,
+        warningName: basic.warningName || basic.warning_name || item.warning_name || "",
+        warningNo: basic.warningNo || basic.warning_no || "",
+        warningSpecial: basic.warningSpecial || basic.warning_special || "",
+        warningType,
+        warningTypeText: warningTypeLabelMap.value[warningType] || warningType || "-",
+        warningLevel,
+        warningLevelText: warningLevelLabelMap.value[warningLevel] || warningLevel || "-",
+        handler: basic.handler || item.handler || "",
+        publisher: basic.publisher || item.publisher || "",
+        location: basic.location || item.location || "",
+        ownershipUnit: basic.ownershipUnit || basic.ownership_unit || item.ownership_unit || "",
+        publishTime: basic.publishTime || basic.publish_time || item.publish_time || "",
+        alarmCodeList: alarmList.map((alarm) => alarm.warning_code || alarm.warningCode || alarm.alarmId || alarm.alarm_id).filter(Boolean),
+        detail
+      };
+    })
+  );
+  return rows;
 }
 
-const handleRowClick = (row) => {
-  viewDetail(row)
+async function loadTodoList(type) {
+  const userId = await ensureUserId();
+  const res = await getdbyjlistData({
+    pageNum: 1,
+    pageSize: 100,
+    userId,
+    todoType: type,
+    warningName: queryParams.warningName || undefined
+  });
+  const records = res.data?.records || [];
+  return enrichTodoRows(records);
 }
 
-const viewDetail = (order) => {
-  currentOrder.value = order
-  detailDrawerVisible.value = true
+async function loadCurrentTabData() {
+  loading.value = true;
+  try {
+    if (activeTab.value === "TODO") {
+      todoList.value = await loadTodoList("TODO");
+    } else {
+      doneList.value = await loadTodoList("DONE");
+    }
+    pagination.pageNum = 1;
+    ElMessage.success("列表已刷新");
+  } finally {
+    loading.value = false;
+  }
 }
 
-const viewCaseDetail = () => {
-  ElMessage.info(`查看案件 ${currentOrder.value?.caseCode} 详情(演示)`)
+async function preloadCounts() {
+  loading.value = true;
+  try {
+    const [todoRows, doneRows] = await Promise.all([loadTodoList("TODO"), loadTodoList("DONE")]);
+    todoList.value = todoRows;
+    doneList.value = doneRows;
+  } finally {
+    loading.value = false;
+  }
 }
 
-const handleOrder = (order) => {
-  if (order.status === 'pending') {
-    // 如果未指派人员,先弹出指派对话框
-    if (!order.assignee) {
-      currentOrder.value = order
-      assignDialogVisible.value = true
-    } else {
-      // 已有指派人,直接更新状态
-      order.status = 'processing'
-      order.processLogs.push({
-        id: Date.now(),
-        time: new Date().toLocaleString(),
-        content: `开始处理工单,负责人:${order.assignee}`,
-        type: 'primary'
-      })
-      ElMessage.success(`工单 ${order.code} 已开始处理`)
-      detailDrawerVisible.value = false
-    }
-  }
+function handleQuery() {
+  pagination.pageNum = 1;
 }
 
-const openAssignDialog = () => {
-  assignDialogVisible.value = true
+function resetQuery() {
+  queryParams.warningName = "";
+  queryParams.warningNo = "";
+  queryParams.warningSpecial = "";
+  queryParams.warningType = "";
+  queryParams.warningLevel = "";
+  queryParams.handler = "";
+  queryParams.publisher = "";
+  queryParams.alarmCode = "";
+  queryParams.location = "";
+  queryParams.ownershipUnit = "";
+  queryParams.dateRange = [];
+  pagination.pageNum = 1;
 }
 
-const confirmAssign = () => {
-  if (!selectedWorkerId.value) {
-    ElMessage.warning('请选择维护人员')
-    return
-  }
-  const worker = workers.value.find(w => w.id === selectedWorkerId.value)
-  if (currentOrder.value) {
-    currentOrder.value.assignee = worker.name
-    currentOrder.value.status = 'processing'
-    currentOrder.value.processLogs.push({
-      id: Date.now(),
-      time: new Date().toLocaleString(),
-      content: `已指派 ${worker.name} 处理工单,备注:${assignRemark.value || '无'}`,
-      type: 'primary'
-    })
-    ElMessage.success(`工单已指派给 ${worker.name}`)
-  }
-  assignDialogVisible.value = false
-  selectedWorkerId.value = ''
-  assignRemark.value = ''
-  detailDrawerVisible.value = false
+function handlePageChange() {
+  // pagination component sync only
 }
 
-const listValue = {
-  userId:1
+function handleTabChange() {
+  pagination.pageNum = 1;
 }
 
-const getList = () => {
-  getdbyjlistData(proxy.addDateRange(listValue)).then(response => {
-    console.log(1123,response)
-  });
+function handleViewDetail(row) {
+  detailData.value = row.detail || {};
+  detailVisible.value = true;
 }
 
-getList()
+onMounted(async () => {
+  await loadDicts();
+  await preloadCounts();
+});
 </script>
 
 <style scoped>
-.pending-orders {
-  background: #f0f2f6;
-  min-height: 100vh;
-  padding: 20px;
+.todo-warning-page {
+  background: #f5f7fb;
 }
 
-.page-header {
-  background: white;
+.page-card {
   border-radius: 16px;
-  padding: 16px 24px;
-  margin-bottom: 20px;
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  flex-wrap: wrap;
-  gap: 12px;
-  box-shadow: 0 2px 8px rgba(0,0,0,0.04);
-}
-
-.header-left {
-  display: flex;
-  align-items: center;
-  gap: 12px;
+  margin-bottom: 16px;
 }
 
-.header-left .title {
-  font-size: 20px;
-  font-weight: 600;
-  color: #1f2d3d;
+.header-card {
+  background: linear-gradient(135deg, #eff6ff, #f8fbff);
+  border: 1px solid #dbeafe;
 }
 
-.header-right {
+.page-header {
   display: flex;
-  gap: 12px;
+  justify-content: space-between;
   align-items: center;
-  flex-wrap: wrap;
-}
-
-.stats-cards {
-  display: flex;
   gap: 16px;
-  margin-bottom: 20px;
-  flex-wrap: wrap;
 }
 
-.stat-card {
-  background: white;
-  border-radius: 12px;
-  padding: 12px 24px;
-  text-align: center;
-  cursor: pointer;
-  transition: all 0.2s;
-  min-width: 100px;
-  box-shadow: 0 1px 4px rgba(0,0,0,0.05);
+.page-title {
+  font-size: 22px;
+  font-weight: 700;
+  color: #1e3a8a;
 }
 
-.stat-card:hover {
-  transform: translateY(-2px);
-  box-shadow: 0 4px 12px rgba(0,0,0,0.1);
+.page-subtitle {
+  margin-top: 8px;
+  font-size: 13px;
+  color: #64748b;
 }
 
-.stat-card.active {
-  border: 2px solid #409eff;
-  background: #ecf5ff;
+.summary-grid {
+  display: grid;
+  grid-template-columns: repeat(3, minmax(0, 1fr));
+  gap: 16px;
+  margin-bottom: 16px;
 }
 
-.stat-card.pending .stat-value { color: #f56c6c; }
-.stat-card.processing .stat-value { color: #e6a23c; }
-.stat-card.overdue .stat-value { color: #f56c6c; }
-
-.stat-value {
-  font-size: 28px;
-  font-weight: bold;
+.summary-card {
+  border: none;
+  color: #fff;
 }
 
-.stat-label {
-  font-size: 13px;
-  color: #606266;
-  margin-top: 4px;
+.summary-card :deep(.el-card__body) {
+  padding: 20px;
 }
 
-.order-table-container {
-  background: white;
-  border-radius: 16px;
-  padding: 16px;
-  box-shadow: 0 2px 8px rgba(0,0,0,0.04);
+.summary-card.danger {
+  background: linear-gradient(135deg, #dc2626, #f87171);
 }
 
-.pagination-wrapper {
-  display: flex;
-  justify-content: flex-end;
-  margin-top: 16px;
-  padding-top: 12px;
-  border-top: 1px solid #e9ecef;
+.summary-card.success {
+  background: linear-gradient(135deg, #059669, #34d399);
 }
 
-.order-detail {
-  padding: 8px;
+.summary-card.primary {
+  background: linear-gradient(135deg, #2563eb, #5b93ff);
 }
 
-.sub-title {
-  font-weight: 600;
-  margin: 16px 0 12px 0;
+.summary-label {
   font-size: 14px;
-  color: #2c3e50;
 }
 
-.process-logs {
-  margin-top: 16px;
+.summary-value {
+  margin: 8px 0 10px;
+  font-size: 30px;
+  font-weight: 700;
 }
 
-.detail-actions {
-  margin-top: 24px;
-  display: flex;
-  gap: 12px;
-  justify-content: flex-end;
+.summary-desc {
+  font-size: 12px;
+  opacity: 0.9;
 }
 
-:deep(.el-table__row) {
-  cursor: pointer;
-}
-
-:deep(.el-table__row:hover) {
-  background-color: #f5f7fa;
+@media (max-width: 1200px) {
+  .summary-grid {
+    grid-template-columns: 1fr;
+  }
 }
-</style>
+</style>

+ 764 - 16
src/views/subSystem/lifeCompany/lwarningInfoCont/LAlertQuery.vue

@@ -1,22 +1,770 @@
+<template>
+  <div class="app-container warning-query-page" v-loading="loading">
+    <el-card shadow="never" class="page-card header-card">
+      <div class="page-header">
+        <div>
+          <div class="page-title">预警信息管理</div>
+          <div class="page-subtitle">支持预警查询、处置过程跟踪和可视化流程查询,掌握预警历史处置情况、GIS定位和电子存档信息</div>
+        </div>
+        <el-button type="primary" icon="Refresh" @click="reloadAll">刷新</el-button>
+      </div>
+    </el-card>
+
+    <div class="summary-grid">
+      <el-card shadow="hover" class="summary-card primary">
+        <div class="summary-label">综合查询结果</div>
+        <div class="summary-value">{{ total }}</div>
+        <div class="summary-desc">当前筛选条件下的预警总数</div>
+      </el-card>
+      <el-card shadow="hover" class="summary-card warning">
+        <div class="summary-label">处理中任务</div>
+        <div class="summary-value">{{ processingTotal }}</div>
+        <div class="summary-desc">正在执行处置的预警任务数量</div>
+      </el-card>
+      <el-card shadow="hover" class="summary-card success">
+        <div class="summary-label">已完结任务</div>
+        <div class="summary-value">{{ completedTotal }}</div>
+        <div class="summary-desc">已完成处置的预警任务数量</div>
+      </el-card>
+    </div>
+
+    <el-card shadow="never" class="page-card">
+      <el-form :model="queryParams" :inline="true">
+        <el-form-item label="预警名称">
+          <el-input v-model="queryParams.warningName" placeholder="请输入预警名称" clearable style="width: 220px" @keyup.enter="handleQuery" />
+        </el-form-item>
+        <el-form-item label="预警编号">
+          <el-input v-model="queryParams.warningNo" placeholder="请输入预警编号" clearable style="width: 180px" />
+        </el-form-item>
+        <el-form-item label="预警类型">
+          <el-select v-model="queryParams.warningType" clearable placeholder="请选择预警类型" style="width: 180px">
+            <el-option
+              v-for="item in warningTypeOptions"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="预警级别">
+          <el-select v-model="queryParams.warningLevel" clearable placeholder="请选择预警级别" style="width: 180px">
+            <el-option
+              v-for="item in warningLevelOptions"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="状态">
+          <el-select v-model="queryParams.status" clearable placeholder="请选择状态" style="width: 180px">
+            <el-option label="已发布" value="RELEASED" />
+            <el-option label="待处理" value="PENDING" />
+            <el-option label="处理中" value="PROCESSING" />
+            <el-option label="已处理" value="HANDLED" />
+            <el-option label="已解除" value="CLOSED" />
+            <el-option label="已退回" value="RETURNED" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="预警位置">
+          <el-input v-model="queryParams.location" placeholder="请输入预警位置" clearable style="width: 220px" />
+        </el-form-item>
+        <el-form-item label="发布人">
+          <el-input v-model="queryParams.publisher" placeholder="请输入发布人" clearable style="width: 180px" />
+        </el-form-item>
+        <el-form-item label="权属单位">
+          <el-input v-model="queryParams.ownershipUnit" placeholder="请输入权属单位" clearable style="width: 180px" />
+        </el-form-item>
+        <el-form-item label="发布时间">
+          <el-date-picker
+            v-model="queryParams.dateRange"
+            type="datetimerange"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            range-separator="至"
+            start-placeholder="开始时间"
+            end-placeholder="结束时间"
+            style="width: 360px"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="Search" @click="handleQuery">查询</el-button>
+          <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
+
+    <el-card shadow="never" class="page-card">
+      <template #header>
+        <div class="section-title">预警查询</div>
+      </template>
+      <el-table :data="warningList" @row-click="handleSelectWarning">
+        <el-table-column label="序号" width="70" align="center">
+          <template #default="scope">
+            {{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}
+          </template>
+        </el-table-column>
+        <el-table-column label="预警名称" prop="warningName" min-width="180" show-overflow-tooltip />
+        <el-table-column label="预警编号" prop="warningNo" width="170" />
+        <el-table-column label="预警类型" prop="warningTypeText" width="120" />
+        <el-table-column label="预警级别" width="110" align="center">
+          <template #default="scope">
+            <el-tag :type="getLevelTagType(scope.row.warningLevel)">
+              {{ scope.row.warningLevelText }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="状态" width="110" align="center">
+          <template #default="scope">
+            <el-tag :type="getStatusTagType(scope.row.status)">
+              {{ getStatusText(scope.row.status) }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="预警位置" prop="location" min-width="180" show-overflow-tooltip />
+        <el-table-column label="发布人" prop="publisher" width="120" />
+        <el-table-column label="权属单位" prop="ownershipUnit" width="160" show-overflow-tooltip />
+        <el-table-column label="发布时间" prop="publishTime" width="180" />
+        <el-table-column label="操作" width="150" fixed="right" align="center">
+          <template #default="scope">
+            <el-button link type="primary" @click.stop="openDetail(scope.row)">详情</el-button>
+            <el-button link type="success" @click.stop="handleSelectWarning(scope.row)">流程</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination
+        v-show="total > 0"
+        :total="total"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        @pagination="loadWarnings"
+      />
+    </el-card>
+
+    <el-row :gutter="16">
+      <el-col :xs="24" :lg="12">
+        <el-card shadow="never" class="page-card track-card">
+          <template #header>
+            <div class="section-title">处置过程跟踪</div>
+          </template>
+          <el-tabs v-model="trackTab" @tab-change="loadTrackList">
+            <el-tab-pane :label="`进行中 (${processingTotal})`" name="processing" />
+            <el-tab-pane :label="`已完成 (${completedTotal})`" name="completed" />
+          </el-tabs>
+          <el-table :data="trackList" height="360" @row-click="handleSelectWarningFromTrack">
+            <el-table-column label="预警名称" prop="warningName" min-width="170" show-overflow-tooltip />
+            <el-table-column label="预警类型" prop="warningTypeText" width="120" />
+            <el-table-column label="预警级别" width="100" align="center">
+              <template #default="scope">
+                <el-tag :type="getLevelTagType(scope.row.warningLevel)">
+                  {{ scope.row.warningLevelText }}
+                </el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column label="处置人" prop="handler" width="110" />
+            <el-table-column label="状态" width="100" align="center">
+              <template #default="scope">
+                <el-tag :type="getStatusTagType(scope.row.status)">
+                  {{ getStatusText(scope.row.status) }}
+                </el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column label="发布时间" prop="publishTime" width="170" />
+          </el-table>
+        </el-card>
+      </el-col>
+
+      <el-col :xs="24" :lg="12">
+        <el-card shadow="never" class="page-card process-card">
+          <template #header>
+            <div class="section-title">可视化流程查询</div>
+          </template>
+
+          <el-empty v-if="!selectedWarningId" description="请选择一条预警查看流程路线图" />
+
+          <template v-else>
+            <div class="selected-info">
+              <div class="selected-name">{{ processMeta.warningName || selectedWarningRow?.warningName || "-" }}</div>
+              <div class="selected-meta">
+                <span>编号:{{ processMeta.warningNo || selectedWarningRow?.warningNo || "-" }}</span>
+                <span>状态:{{ getStatusText(processMeta.status || selectedWarningRow?.status) }}</span>
+              </div>
+            </div>
+
+            <div class="process-stats">
+              <div class="stat-box">
+                <div class="stat-box-label">总处置数</div>
+                <div class="stat-box-value">{{ processStatistics.totalDisposals || 0 }}</div>
+              </div>
+              <div class="stat-box">
+                <div class="stat-box-label">当前状态</div>
+                <div class="stat-box-value small">{{ getStatusText(processMeta.status || selectedWarningRow?.status) }}</div>
+              </div>
+            </div>
+
+            <div class="process-roadmap">
+              <div
+                v-for="(node, index) in processNodes"
+                :key="node.id || index"
+                class="road-node"
+                :class="node.status"
+              >
+                <div class="road-dot"></div>
+                <div class="road-name">{{ node.name }}</div>
+                <div class="road-time">{{ node.disposalTime || "" }}</div>
+                <div class="road-user">{{ node.disposalUser || "" }}</div>
+                <div v-if="index < processNodes.length - 1" class="road-line"></div>
+              </div>
+            </div>
+
+            <el-divider content-position="left">流程记录</el-divider>
+            <el-timeline>
+              <el-timeline-item
+                v-for="item in processList"
+                :key="item.disposalId"
+                :timestamp="item.createTime || '-'"
+                :type="getTimelineType(item.disposalType)"
+              >
+                <div class="timeline-title">{{ getProcessTypeText(item.disposalType) }}</div>
+                <div>{{ item.disposalContent || "-" }}</div>
+                <div class="timeline-sub">处置人:{{ item.disposalUser || "-" }}</div>
+              </el-timeline-item>
+            </el-timeline>
+          </template>
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <el-drawer v-model="detailVisible" title="预警详情" size="820px" append-to-body>
+      <template v-if="detailData.basicInfo">
+        <el-descriptions :column="2" border>
+          <el-descriptions-item label="预警名称">{{ detailBasic.warningName || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="预警编号">{{ detailBasic.warningNo || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="预警类型">{{ detailBasic.warningTypeText || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="预警级别">{{ detailBasic.warningLevelText || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="状态">{{ getStatusText(detailBasic.status) }}</el-descriptions-item>
+          <el-descriptions-item label="发布人">{{ detailBasic.publisher || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="处置人">{{ detailBasic.handler || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="权属单位">{{ detailBasic.ownershipUnit || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="预警位置" :span="2">{{ detailBasic.location || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="发布时间">{{ detailBasic.publishTime || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="GIS定位">{{ detailGis.longitude || "-" }} / {{ detailGis.latitude || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="预警内容" :span="2">{{ detailBasic.warningContent || "-" }}</el-descriptions-item>
+        </el-descriptions>
+
+        <el-divider content-position="left">流程处置信息</el-divider>
+        <el-timeline v-if="(detailData.disposalHistory || []).length">
+          <el-timeline-item
+            v-for="(item, index) in detailData.disposalHistory"
+            :key="index"
+            :timestamp="item.createTime || item.disposalTime || '-'"
+          >
+            {{ item.disposalContent || item.content || "-" }}
+          </el-timeline-item>
+        </el-timeline>
+        <el-empty v-else description="暂无流程处置信息" />
+
+        <el-divider content-position="left">电子存档信息</el-divider>
+        <el-descriptions :column="1" border>
+          <el-descriptions-item label="存档状态">{{ detailArchive.archiveStatus || detailArchive.status || "暂无电子存档" }}</el-descriptions-item>
+          <el-descriptions-item label="存档编号">{{ detailArchive.archiveNo || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="存档说明">{{ detailArchive.remark || "-" }}</el-descriptions-item>
+        </el-descriptions>
+      </template>
+    </el-drawer>
+  </div>
+</template>
+
 <script setup>
+import { computed, onMounted, reactive, ref } from "vue";
+import {
+  getAlertTypeDictionaryData,
+  getWarningCompletedListData,
+  getWarningDetailData,
+  getWarningDiagramData,
+  getWarningProcessData,
+  getWarningProcessStatisticsData,
+  getWarningProcessingListData,
+  getdbyjlistData
+} from "@/api/smxyjldczxt/yjxxfb/yjfb.js";
+
+const loading = ref(false);
+const warningList = ref([]);
+const total = ref(0);
+const warningTypeOptions = ref([]);
+const warningLevelOptions = ref([]);
+
+const processingList = ref([]);
+const completedList = ref([]);
+const processingTotal = ref(0);
+const completedTotal = ref(0);
+const trackTab = ref("processing");
+
+const selectedWarningId = ref("");
+const selectedWarningRow = ref(null);
+const processList = ref([]);
+const processStatistics = ref({});
+const processDiagram = ref({});
+
+const detailVisible = ref(false);
+const detailData = ref({});
+
+const queryParams = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  warningName: "",
+  warningNo: "",
+  warningType: "",
+  warningLevel: "",
+  status: "",
+  location: "",
+  publisher: "",
+  ownershipUnit: "",
+  dateRange: []
+});
+
+const warningTypeLabelMap = computed(() => {
+  return warningTypeOptions.value.reduce((acc, item) => {
+    acc[item.dictValue] = item.dictLabel;
+    return acc;
+  }, {});
+});
+
+const warningLevelLabelMap = computed(() => {
+  return warningLevelOptions.value.reduce((acc, item) => {
+    acc[item.dictValue] = item.dictLabel;
+    return acc;
+  }, {});
+});
+
+const trackList = computed(() => (trackTab.value === "processing" ? processingList.value : completedList.value));
+const processNodes = computed(() => processDiagram.value.nodes || []);
+const processMeta = computed(() => ({
+  warningName: processDiagram.value.warningName || "",
+  warningNo: processDiagram.value.warningNo || "",
+  status: processDiagram.value.status || ""
+}));
+
+const detailBasic = computed(() => {
+  const basic = detailData.value.basicInfo || {};
+  const warningType = basic.warningType || basic.warning_type || "";
+  const warningLevel = basic.warningLevel || basic.warning_level || "";
+  return {
+    warningName: basic.warningName || basic.warning_name || "",
+    warningNo: basic.warningNo || basic.warning_no || "",
+    warningTypeText: warningTypeLabelMap.value[warningType] || warningType || "-",
+    warningLevelText: warningLevelLabelMap.value[warningLevel] || warningLevel || "-",
+    status: basic.status || "",
+    publisher: basic.publisher || "",
+    handler: basic.handler || "",
+    ownershipUnit: basic.ownershipUnit || basic.ownership_unit || "",
+    location: basic.location || "",
+    publishTime: basic.publishTime || basic.publish_time || "",
+    warningContent: basic.warningContent || basic.warning_content || ""
+  };
+});
+
+const detailGis = computed(() => detailData.value.gisLocation || {});
+const detailArchive = computed(() => detailData.value.electronicArchive || {});
+
+function getLevelTagType(level) {
+  return {
+    "1": "danger",
+    "2": "warning",
+    "3": "success",
+    "4": "info"
+  }[level] || "info";
+}
+
+function getStatusText(status) {
+  return {
+    DRAFT: "草稿",
+    RELEASED: "已发布",
+    PENDING: "待处理",
+    PROCESSING: "处理中",
+    HANDLED: "已处理",
+    CLOSED: "已解除",
+    RETURNED: "已退回"
+  }[status] || status || "-";
+}
+
+function getStatusTagType(status) {
+  return {
+    DRAFT: "info",
+    RELEASED: "success",
+    PENDING: "warning",
+    PROCESSING: "warning",
+    HANDLED: "success",
+    CLOSED: "",
+    RETURNED: "danger"
+  }[status] || "";
+}
+
+function getProcessTypeText(type) {
+  return {
+    RELEASE: "发布预警",
+    UPGRADE: "升级预警",
+    RESOLVE: "解除预警",
+    RETURN: "退回重办",
+    SUPERVISION: "预警督办",
+    INSTRUCTION: "领导批示"
+  }[type] || type || "-";
+}
+
+function getTimelineType(type) {
+  return {
+    RELEASE: "primary",
+    UPGRADE: "warning",
+    RESOLVE: "success",
+    RETURN: "danger",
+    SUPERVISION: "primary",
+    INSTRUCTION: "info"
+  }[type] || "primary";
+}
+
+function normalizeWarningRow(item) {
+  const warningType = item.warningType || item.warning_type || "";
+  const warningLevel = item.warningLevel || item.warning_level || "";
+  return {
+    warningId: item.warningId || item.warning_id,
+    warningName: item.warningName || item.warning_name || "",
+    warningNo: item.warningNo || item.warning_no || "",
+    warningType,
+    warningTypeText: warningTypeLabelMap.value[warningType] || warningType || "-",
+    warningLevel,
+    warningLevelText: warningLevelLabelMap.value[warningLevel] || warningLevel || "-",
+    status: item.status || "",
+    location: item.location || "",
+    publisher: item.publisher || "",
+    ownershipUnit: item.ownershipUnit || item.ownership_unit || "",
+    publishTime: item.publishTime || item.publish_time || "",
+    handler: item.handler || ""
+  };
+}
+
+async function loadDicts() {
+  const [typeRes, levelRes] = await Promise.all([
+    getAlertTypeDictionaryData({ dictType: "warning_type" }),
+    getAlertTypeDictionaryData({ dictType: "warning_level" })
+  ]);
+  warningTypeOptions.value = typeRes.rows || [];
+  warningLevelOptions.value = levelRes.rows || [];
+}
+
+async function loadWarnings() {
+  loading.value = true;
+  try {
+    const res = await getdbyjlistData({
+      pageNum: queryParams.pageNum,
+      pageSize: queryParams.pageSize,
+      warningName: queryParams.warningName || undefined,
+      warningNo: queryParams.warningNo || undefined,
+      warningType: queryParams.warningType || undefined,
+      warningLevel: queryParams.warningLevel || undefined,
+      status: queryParams.status || undefined,
+      location: queryParams.location || undefined,
+      publisher: queryParams.publisher || undefined,
+      ownershipUnit: queryParams.ownershipUnit || undefined,
+      startTime: queryParams.dateRange?.[0] || undefined,
+      endTime: queryParams.dateRange?.[1] || undefined
+    });
+    warningList.value = (res.data?.records || []).map(normalizeWarningRow);
+    total.value = res.data?.total || 0;
+  } finally {
+    loading.value = false;
+  }
+}
+
+async function loadTrackList() {
+  const params = {
+    pageNum: 1,
+    pageSize: 50,
+    warningType: queryParams.warningType || undefined,
+    warningLevel: queryParams.warningLevel || undefined,
+    startTime: queryParams.dateRange?.[0] || undefined,
+    endTime: queryParams.dateRange?.[1] || undefined
+  };
+  const [processingRes, completedRes] = await Promise.all([
+    getWarningProcessingListData({
+      pageNum: 1,
+      pageSize: 50,
+      warningType: params.warningType,
+      warningLevel: params.warningLevel
+    }),
+    getWarningCompletedListData(params)
+  ]);
+  processingList.value = (processingRes.data?.records || []).map(normalizeWarningRow);
+  processingTotal.value = processingRes.data?.total || 0;
+  completedList.value = (completedRes.data?.records || []).map(normalizeWarningRow);
+  completedTotal.value = completedRes.data?.total || 0;
+}
+
+async function loadProcessData(warningId) {
+  if (!warningId) return;
+  const [processRes, statisticsRes, diagramRes] = await Promise.all([
+    getWarningProcessData(warningId),
+    getWarningProcessStatisticsData(warningId),
+    getWarningDiagramData(warningId)
+  ]);
+  processList.value = processRes.data || [];
+  processStatistics.value = statisticsRes.data || {};
+  processDiagram.value = diagramRes.data || {};
+}
+
+async function openDetail(row) {
+  const res = await getWarningDetailData(row.warningId);
+  detailData.value = res.data || {};
+  detailVisible.value = true;
+}
+
+async function handleSelectWarning(row) {
+  selectedWarningId.value = row.warningId;
+  selectedWarningRow.value = row;
+  await loadProcessData(row.warningId);
+}
 
+async function handleSelectWarningFromTrack(row) {
+  await handleSelectWarning(row);
+}
+
+function handleQuery() {
+  queryParams.pageNum = 1;
+  reloadAll();
+}
+
+function resetQuery() {
+  queryParams.pageNum = 1;
+  queryParams.pageSize = 10;
+  queryParams.warningName = "";
+  queryParams.warningNo = "";
+  queryParams.warningType = "";
+  queryParams.warningLevel = "";
+  queryParams.status = "";
+  queryParams.location = "";
+  queryParams.publisher = "";
+  queryParams.ownershipUnit = "";
+  queryParams.dateRange = [];
+  reloadAll();
+}
+
+async function reloadAll() {
+  await Promise.all([loadWarnings(), loadTrackList()]);
+  if (warningList.value.length && !selectedWarningId.value) {
+    await handleSelectWarning(warningList.value[0]);
+  } else if (selectedWarningId.value) {
+    await loadProcessData(selectedWarningId.value);
+  }
+}
+
+onMounted(async () => {
+  await loadDicts();
+  await reloadAll();
+});
 </script>
 
-<template>
-<div>
-  5.2.2.4.1. 预警查询
-  将所有行业预警进行汇总展示,通过搜索条件互相组合快速查询。同时,支持查看预警事件
-  详细信息,包括预警基本信息、流程处置信息、GIS 定位等,掌握预警历史处置情况和发布实情,
-  提供预警电子存档信息。
-  5.2.2.4.2. 处置过程跟踪
-  支持查看当前正在执行处置的预警任务及其相关信息;同时支持查看已完结完成预警处置事
-  项的过程记录,包括处置人、处置过程等。
-  5.2.2.4.3. 可视化流程查询
-  结合工作流引擎服务,将后端的工作流进行可视化展示,以路线图的样式可视化所有处置过
-  程信息。
-</div>
-</template>
+<style scoped>
+.warning-query-page {
+  background: #f5f7fb;
+}
+
+.page-card {
+  border-radius: 16px;
+  margin-bottom: 16px;
+}
+
+.header-card {
+  background: linear-gradient(135deg, #eff6ff, #f8fbff);
+  border: 1px solid #dbeafe;
+}
+
+.page-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  gap: 16px;
+}
+
+.page-title {
+  font-size: 22px;
+  font-weight: 700;
+  color: #1e3a8a;
+}
+
+.page-subtitle {
+  margin-top: 8px;
+  font-size: 13px;
+  color: #64748b;
+}
+
+.section-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #1f2937;
+}
+
+.summary-grid {
+  display: grid;
+  grid-template-columns: repeat(3, minmax(0, 1fr));
+  gap: 16px;
+  margin-bottom: 16px;
+}
+
+.summary-card {
+  border: none;
+  color: #fff;
+}
+
+.summary-card :deep(.el-card__body) {
+  padding: 20px;
+}
+
+.summary-card.primary {
+  background: linear-gradient(135deg, #2563eb, #5b93ff);
+}
+
+.summary-card.warning {
+  background: linear-gradient(135deg, #d97706, #f7b955);
+}
+
+.summary-card.success {
+  background: linear-gradient(135deg, #059669, #34d399);
+}
+
+.summary-label {
+  font-size: 14px;
+}
+
+.summary-value {
+  margin: 8px 0 10px;
+  font-size: 30px;
+  font-weight: 700;
+}
+
+.summary-desc {
+  font-size: 12px;
+  opacity: 0.9;
+}
+
+.track-card,
+.process-card {
+  min-height: 560px;
+}
+
+.selected-info {
+  margin-bottom: 14px;
+}
+
+.selected-name {
+  font-size: 16px;
+  font-weight: 700;
+  color: #111827;
+}
+
+.selected-meta {
+  margin-top: 8px;
+  display: flex;
+  gap: 16px;
+  flex-wrap: wrap;
+  font-size: 13px;
+  color: #6b7280;
+}
+
+.process-stats {
+  display: grid;
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+  gap: 12px;
+  margin-bottom: 16px;
+}
+
+.stat-box {
+  padding: 14px 16px;
+  border-radius: 12px;
+  background: #f8fafc;
+  border: 1px solid #edf2f7;
+}
+
+.stat-box-label {
+  font-size: 13px;
+  color: #64748b;
+}
+
+.stat-box-value {
+  margin-top: 8px;
+  font-size: 26px;
+  font-weight: 700;
+  color: #111827;
+}
+
+.stat-box-value.small {
+  font-size: 20px;
+}
+
+.process-roadmap {
+  display: flex;
+  overflow-x: auto;
+  padding: 10px 0 18px;
+  margin-bottom: 8px;
+}
+
+.road-node {
+  position: relative;
+  min-width: 150px;
+  padding-right: 28px;
+  flex-shrink: 0;
+}
+
+.road-dot {
+  width: 18px;
+  height: 18px;
+  border-radius: 50%;
+  background: #94a3b8;
+  border: 4px solid #e2e8f0;
+}
+
+.road-node.completed .road-dot {
+  background: #10b981;
+}
+
+.road-node.active .road-dot {
+  background: #f59e0b;
+}
+
+.road-node.waiting .road-dot {
+  background: #cbd5e1;
+}
+
+.road-name {
+  margin-top: 10px;
+  font-size: 14px;
+  font-weight: 600;
+  color: #1f2937;
+}
+
+.road-time,
+.road-user,
+.timeline-sub {
+  margin-top: 4px;
+  font-size: 12px;
+  color: #6b7280;
+}
+
+.road-line {
+  position: absolute;
+  top: 8px;
+  left: 24px;
+  width: calc(100% - 32px);
+  height: 2px;
+  background: #dbeafe;
+}
 
-<style scoped lang="scss">
+.timeline-title {
+  font-weight: 600;
+  color: #111827;
+}
 
-</style>
+@media (max-width: 1200px) {
+  .summary-grid {
+    grid-template-columns: 1fr;
+  }
+}
+</style>

+ 761 - 17
src/views/subSystem/lifeCompany/lwarningInfoCont/LwarningDisp.vue

@@ -1,23 +1,767 @@
+<template>
+  <div class="app-container warning-disposal-page" v-loading="loading">
+    <el-card shadow="never" class="page-card header-card">
+      <div class="page-header">
+        <div>
+          <div class="page-title">预警信息处置</div>
+          <div class="page-subtitle">支持预警解除、预警升级、预警督办、添加领导批示和预警退回,形成发现问题到处置问题的闭环管理</div>
+        </div>
+        <el-button type="primary" icon="Refresh" @click="loadList">刷新</el-button>
+      </div>
+    </el-card>
+
+    <div class="action-grid">
+      <el-card shadow="hover" class="action-card resolve">
+        <div class="action-title">预警解除</div>
+        <div class="action-desc">现场处置核查无误后解除预警,形成处置闭环。</div>
+      </el-card>
+      <el-card shadow="hover" class="action-card upgrade">
+        <div class="action-title">预警升级</div>
+        <div class="action-desc">结合地理位置、人口密度、建筑密度等因素调整预警级别。</div>
+      </el-card>
+      <el-card shadow="hover" class="action-card supervise">
+        <div class="action-title">预警督办</div>
+        <div class="action-desc">通过督办人员、督办意见、通知方式和通知内容进行提醒。</div>
+      </el-card>
+      <el-card shadow="hover" class="action-card instruction">
+        <div class="action-title">添加领导批示</div>
+        <div class="action-desc">向相关领导发送预警信息并记录领导批示指导意见。</div>
+      </el-card>
+      <el-card shadow="hover" class="action-card return">
+        <div class="action-title">预警退回</div>
+        <div class="action-desc">现场仍有异常时退回处置单位重新复核和处置。</div>
+      </el-card>
+    </div>
+
+    <el-card shadow="never" class="page-card">
+      <el-form :model="queryParams" :inline="true">
+        <el-form-item label="预警名称">
+          <el-input v-model="queryParams.warningName" placeholder="请输入预警名称" clearable style="width: 220px" @keyup.enter="handleQuery" />
+        </el-form-item>
+        <el-form-item label="预警编号">
+          <el-input v-model="queryParams.warningNo" placeholder="请输入预警编号" clearable style="width: 180px" />
+        </el-form-item>
+        <el-form-item label="预警类型">
+          <el-select v-model="queryParams.warningType" clearable placeholder="请选择预警类型" style="width: 180px">
+            <el-option
+              v-for="item in warningTypeOptions"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="预警级别">
+          <el-select v-model="queryParams.warningLevel" clearable placeholder="请选择预警级别" style="width: 180px">
+            <el-option
+              v-for="item in warningLevelOptions"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="状态">
+          <el-select v-model="queryParams.status" clearable placeholder="请选择状态" style="width: 180px">
+            <el-option label="已发布" value="RELEASED" />
+            <el-option label="待处理" value="PENDING" />
+            <el-option label="处理中" value="PROCESSING" />
+            <el-option label="已处理" value="HANDLED" />
+            <el-option label="已解除" value="CLOSED" />
+            <el-option label="已退回" value="RETURNED" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="处置人">
+          <el-input v-model="queryParams.handler" placeholder="请输入处置人" clearable style="width: 180px" />
+        </el-form-item>
+        <el-form-item label="发布时间">
+          <el-date-picker
+            v-model="queryParams.dateRange"
+            type="datetimerange"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            range-separator="至"
+            start-placeholder="开始时间"
+            end-placeholder="结束时间"
+            style="width: 360px"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="Search" @click="handleQuery">查询</el-button>
+          <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
+
+    <el-card shadow="never" class="page-card">
+      <el-table :data="warningList">
+        <el-table-column label="序号" width="70" align="center">
+          <template #default="scope">
+            {{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}
+          </template>
+        </el-table-column>
+        <el-table-column label="预警名称" prop="warningName" min-width="180" show-overflow-tooltip />
+        <el-table-column label="预警编号" prop="warningNo" width="170" />
+        <el-table-column label="预警类型" prop="warningTypeText" width="120" />
+        <el-table-column label="预警级别" width="110" align="center">
+          <template #default="scope">
+            <el-tag :type="getLevelTagType(scope.row.warningLevel)">
+              {{ scope.row.warningLevelText }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="处置人" prop="handler" width="120" />
+        <el-table-column label="发布人" prop="publisher" width="120" />
+        <el-table-column label="报警位置" prop="location" min-width="180" show-overflow-tooltip />
+        <el-table-column label="权属单位" prop="ownershipUnit" width="160" show-overflow-tooltip />
+        <el-table-column label="状态" width="120" align="center">
+          <template #default="scope">
+            <el-tag :type="getStatusTagType(scope.row.status)">
+              {{ getStatusText(scope.row.status) }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="发布时间" prop="publishTime" width="180" />
+        <el-table-column label="操作" fixed="right" width="360" align="center">
+          <template #default="scope">
+            <el-button link type="primary" @click="openDetail(scope.row)">详情</el-button>
+            <el-button link type="success" @click="openResolve(scope.row)">解除</el-button>
+            <el-button link type="warning" @click="openUpgrade(scope.row)">升级</el-button>
+            <el-button link type="primary" @click="openSupervision(scope.row)">督办</el-button>
+            <el-button link type="info" @click="openInstruction(scope.row)">批示</el-button>
+            <el-button link type="danger" @click="openReturn(scope.row)">退回</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination
+        v-show="total > 0"
+        :total="total"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        @pagination="loadList"
+      />
+    </el-card>
+
+    <el-drawer v-model="detailVisible" title="预警详情" size="820px" append-to-body>
+      <template v-if="detailData.basicInfo">
+        <el-descriptions :column="2" border>
+          <el-descriptions-item label="预警名称">{{ detailBasic.warningName || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="预警编号">{{ detailBasic.warningNo || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="预警专项">{{ detailBasic.warningSpecial || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="预警类型">{{ detailBasic.warningTypeText || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="预警级别">{{ detailBasic.warningLevelText || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="状态">{{ getStatusText(detailBasic.status) }}</el-descriptions-item>
+          <el-descriptions-item label="处置人">{{ detailBasic.handler || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="发布人">{{ detailBasic.publisher || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="报警位置" :span="2">{{ detailBasic.location || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="权属单位">{{ detailBasic.ownershipUnit || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="发布时间">{{ detailBasic.publishTime || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="预警内容" :span="2">{{ detailBasic.warningContent || "-" }}</el-descriptions-item>
+        </el-descriptions>
+
+        <el-divider content-position="left">处置记录</el-divider>
+        <el-timeline v-if="(detailData.disposalHistory || []).length">
+          <el-timeline-item
+            v-for="(item, index) in detailData.disposalHistory"
+            :key="index"
+            :timestamp="item.createTime || item.disposalTime || '-'"
+          >
+            {{ item.disposalContent || item.content || "-" }}
+          </el-timeline-item>
+        </el-timeline>
+        <el-empty v-else description="暂无处置记录" />
+      </template>
+    </el-drawer>
+
+    <el-dialog v-model="resolveVisible" title="预警解除" width="520px" append-to-body>
+      <el-form label-width="100px">
+        <el-form-item label="预警名称">
+          <el-input :model-value="currentRow?.warningName || '-'" disabled />
+        </el-form-item>
+        <el-form-item label="核查说明">
+          <el-input v-model="resolveForm.disposalContent" type="textarea" :rows="4" placeholder="请输入预警解除说明" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="resolveVisible = false">取 消</el-button>
+        <el-button type="primary" :loading="submitLoading" @click="submitResolve">确 定</el-button>
+      </template>
+    </el-dialog>
+
+    <el-dialog v-model="upgradeVisible" title="预警升级" width="560px" append-to-body>
+      <el-form label-width="100px">
+        <el-form-item label="预警名称">
+          <el-input :model-value="currentRow?.warningName || '-'" disabled />
+        </el-form-item>
+        <el-form-item label="新预警级别">
+          <el-select v-model="upgradeForm.newLevel" placeholder="请选择新预警级别" style="width: 100%">
+            <el-option
+              v-for="item in warningLevelOptions"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="item.dictValue"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="升级说明">
+          <el-input v-model="upgradeForm.disposalContent" type="textarea" :rows="4" placeholder="请输入预警升级说明" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="upgradeVisible = false">取 消</el-button>
+        <el-button type="primary" :loading="submitLoading" @click="submitUpgrade">确 定</el-button>
+      </template>
+    </el-dialog>
+
+    <el-dialog v-model="supervisionVisible" title="预警督办" width="620px" append-to-body>
+      <el-form label-width="100px">
+        <el-form-item label="督办人员">
+          <el-input v-model="supervisionForm.supervisionUser" placeholder="请输入督办人员" />
+        </el-form-item>
+        <el-form-item label="督办意见">
+          <el-input v-model="supervisionForm.supervisionContent" type="textarea" :rows="3" placeholder="请输入督办意见" />
+        </el-form-item>
+        <el-form-item label="通知方式">
+          <el-select v-model="supervisionForm.notificationType" placeholder="请选择通知方式" style="width: 100%">
+            <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="通知内容">
+          <el-input v-model="supervisionForm.notificationContent" type="textarea" :rows="3" placeholder="请输入通知内容" />
+        </el-form-item>
+        <el-form-item label="附件地址">
+          <el-input v-model="supervisionForm.attachmentUrl" placeholder="请输入附件地址" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="supervisionVisible = false">取 消</el-button>
+        <el-button type="primary" :loading="submitLoading" @click="submitSupervision">确 定</el-button>
+      </template>
+    </el-dialog>
+
+    <el-dialog v-model="instructionVisible" title="添加领导批示" width="620px" append-to-body>
+      <el-form label-width="100px">
+        <el-form-item label="领导姓名">
+          <el-input v-model="instructionForm.leaderName" placeholder="请输入领导姓名" />
+        </el-form-item>
+        <el-form-item label="批示内容">
+          <el-input v-model="instructionForm.instructionContent" type="textarea" :rows="4" placeholder="请输入领导批示内容" />
+        </el-form-item>
+        <el-form-item label="短信接收人">
+          <el-input v-model="instructionForm.smsReceivers" placeholder="请输入短信接收人,多个用逗号分隔" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="instructionVisible = false">取 消</el-button>
+        <el-button type="primary" :loading="submitLoading" @click="submitInstruction">确 定</el-button>
+      </template>
+    </el-dialog>
+
+    <el-dialog v-model="returnVisible" title="预警退回" width="520px" append-to-body>
+      <el-form label-width="100px">
+        <el-form-item label="预警名称">
+          <el-input :model-value="currentRow?.warningName || '-'" disabled />
+        </el-form-item>
+        <el-form-item label="退回说明">
+          <el-input v-model="returnForm.disposalContent" type="textarea" :rows="4" placeholder="请输入退回重办说明" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="returnVisible = false">取 消</el-button>
+        <el-button type="primary" :loading="submitLoading" @click="submitReturn">确 定</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
 <script setup>
+import { computed, onMounted, reactive, ref } from "vue";
+import { ElMessage } from "element-plus";
+import {
+  getAlertTypeDictionaryData,
+  getWarningDetailData,
+  getdbyjlistData,
+  instructionWarningData,
+  resolveWarningData,
+  returnWarningData,
+  supervisionWarningData,
+  upgradeWarningData
+} from "@/api/smxyjldczxt/yjxxfb/yjfb.js";
+
+const loading = ref(false);
+const submitLoading = ref(false);
+const warningList = ref([]);
+const total = ref(0);
+const warningTypeOptions = ref([]);
+const warningLevelOptions = ref([]);
+const currentRow = ref(null);
+const detailVisible = ref(false);
+const detailData = ref({});
+
+const resolveVisible = ref(false);
+const upgradeVisible = ref(false);
+const supervisionVisible = ref(false);
+const instructionVisible = ref(false);
+const returnVisible = ref(false);
+
+const queryParams = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  warningName: "",
+  warningNo: "",
+  warningType: "",
+  warningLevel: "",
+  status: "",
+  handler: "",
+  dateRange: []
+});
+
+const resolveForm = reactive({
+  disposalContent: ""
+});
+
+const upgradeForm = reactive({
+  newLevel: "",
+  disposalContent: ""
+});
+
+const supervisionForm = reactive({
+  supervisionUser: "",
+  supervisionContent: "",
+  notificationType: "",
+  notificationContent: "",
+  attachmentUrl: ""
+});
+
+const instructionForm = reactive({
+  leaderName: "",
+  instructionContent: "",
+  smsReceivers: ""
+});
+
+const returnForm = reactive({
+  disposalContent: ""
+});
+
+const warningTypeLabelMap = computed(() => {
+  return warningTypeOptions.value.reduce((acc, item) => {
+    acc[item.dictValue] = item.dictLabel;
+    return acc;
+  }, {});
+});
+
+const warningLevelLabelMap = computed(() => {
+  return warningLevelOptions.value.reduce((acc, item) => {
+    acc[item.dictValue] = item.dictLabel;
+    return acc;
+  }, {});
+});
+
+const detailBasic = computed(() => {
+  const basic = detailData.value.basicInfo || {};
+  const warningType = basic.warningType || basic.warning_type || "";
+  const warningLevel = basic.warningLevel || basic.warning_level || "";
+  return {
+    warningName: basic.warningName || basic.warning_name || "",
+    warningNo: basic.warningNo || basic.warning_no || "",
+    warningSpecial: basic.warningSpecial || basic.warning_special || "",
+    warningTypeText: warningTypeLabelMap.value[warningType] || warningType || "-",
+    warningLevelText: warningLevelLabelMap.value[warningLevel] || warningLevel || "-",
+    status: basic.status || "",
+    handler: basic.handler || "",
+    publisher: basic.publisher || "",
+    location: basic.location || "",
+    ownershipUnit: basic.ownershipUnit || basic.ownership_unit || "",
+    publishTime: basic.publishTime || basic.publish_time || "",
+    warningContent: basic.warningContent || basic.warning_content || ""
+  };
+});
+
+function getLevelTagType(level) {
+  return {
+    "1": "danger",
+    "2": "warning",
+    "3": "success",
+    "4": "info"
+  }[level] || "info";
+}
+
+function getStatusText(status) {
+  return {
+    DRAFT: "草稿",
+    RELEASED: "已发布",
+    PENDING: "待处理",
+    PROCESSING: "处理中",
+    HANDLED: "已处理",
+    CLOSED: "已解除",
+    RETURNED: "已退回"
+  }[status] || status || "-";
+}
+
+function getStatusTagType(status) {
+  return {
+    DRAFT: "info",
+    RELEASED: "success",
+    PENDING: "warning",
+    PROCESSING: "warning",
+    HANDLED: "success",
+    CLOSED: "",
+    RETURNED: "danger"
+  }[status] || "";
+}
+
+function normalizeWarningRow(item) {
+  const warningType = item.warningType || item.warning_type || "";
+  const warningLevel = item.warningLevel || item.warning_level || "";
+  return {
+    warningId: item.warningId || item.warning_id,
+    warningName: item.warningName || item.warning_name || "",
+    warningNo: item.warningNo || item.warning_no || "",
+    warningType,
+    warningTypeText: warningTypeLabelMap.value[warningType] || warningType || "-",
+    warningLevel,
+    warningLevelText: warningLevelLabelMap.value[warningLevel] || warningLevel || "-",
+    handler: item.handler || "",
+    publisher: item.publisher || "",
+    location: item.location || "",
+    ownershipUnit: item.ownershipUnit || item.ownership_unit || "",
+    publishTime: item.publishTime || item.publish_time || "",
+    status: item.status || ""
+  };
+}
+
+function resetActionForms() {
+  resolveForm.disposalContent = "";
+  upgradeForm.newLevel = "";
+  upgradeForm.disposalContent = "";
+  supervisionForm.supervisionUser = "";
+  supervisionForm.supervisionContent = "";
+  supervisionForm.notificationType = "";
+  supervisionForm.notificationContent = "";
+  supervisionForm.attachmentUrl = "";
+  instructionForm.leaderName = "";
+  instructionForm.instructionContent = "";
+  instructionForm.smsReceivers = "";
+  returnForm.disposalContent = "";
+}
+
+async function loadDicts() {
+  const [typeRes, levelRes] = await Promise.all([
+    getAlertTypeDictionaryData({ dictType: "warning_type" }),
+    getAlertTypeDictionaryData({ dictType: "warning_level" })
+  ]);
+  warningTypeOptions.value = typeRes.rows || [];
+  warningLevelOptions.value = levelRes.rows || [];
+}
+
+async function loadList() {
+  loading.value = true;
+  try {
+    const res = await getdbyjlistData({
+      pageNum: queryParams.pageNum,
+      pageSize: queryParams.pageSize,
+      warningName: queryParams.warningName || undefined,
+      warningNo: queryParams.warningNo || undefined,
+      warningType: queryParams.warningType || undefined,
+      warningLevel: queryParams.warningLevel || undefined,
+      status: queryParams.status || undefined,
+      handler: queryParams.handler || undefined,
+      startTime: queryParams.dateRange?.[0] || undefined,
+      endTime: queryParams.dateRange?.[1] || undefined
+    });
+    warningList.value = (res.data?.records || []).map(normalizeWarningRow);
+    total.value = res.data?.total || 0;
+  } finally {
+    loading.value = false;
+  }
+}
 
+function handleQuery() {
+  queryParams.pageNum = 1;
+  loadList();
+}
+
+function resetQuery() {
+  queryParams.pageNum = 1;
+  queryParams.pageSize = 10;
+  queryParams.warningName = "";
+  queryParams.warningNo = "";
+  queryParams.warningType = "";
+  queryParams.warningLevel = "";
+  queryParams.status = "";
+  queryParams.handler = "";
+  queryParams.dateRange = [];
+  loadList();
+}
+
+async function openDetail(row) {
+  const res = await getWarningDetailData(row.warningId);
+  detailData.value = res.data || {};
+  detailVisible.value = true;
+}
+
+function openResolve(row) {
+  currentRow.value = row;
+  resetActionForms();
+  resolveVisible.value = true;
+}
+
+function openUpgrade(row) {
+  currentRow.value = row;
+  resetActionForms();
+  upgradeForm.newLevel = row.warningLevel;
+  upgradeVisible.value = true;
+}
+
+function openSupervision(row) {
+  currentRow.value = row;
+  resetActionForms();
+  supervisionVisible.value = true;
+}
+
+function openInstruction(row) {
+  currentRow.value = row;
+  resetActionForms();
+  instructionVisible.value = true;
+}
+
+function openReturn(row) {
+  currentRow.value = row;
+  resetActionForms();
+  returnVisible.value = true;
+}
+
+async function submitResolve() {
+  if (!resolveForm.disposalContent) {
+    ElMessage.warning("请输入解除说明");
+    return;
+  }
+  submitLoading.value = true;
+  try {
+    const res = await resolveWarningData({
+      warningId: currentRow.value.warningId,
+      disposalContent: resolveForm.disposalContent
+    });
+    if (res.code !== 200) {
+      ElMessage.error(res.msg || "解除失败");
+      return;
+    }
+    ElMessage.success("解除成功");
+    resolveVisible.value = false;
+    loadList();
+  } finally {
+    submitLoading.value = false;
+  }
+}
+
+async function submitUpgrade() {
+  if (!upgradeForm.newLevel) {
+    ElMessage.warning("请选择新预警级别");
+    return;
+  }
+  if (!upgradeForm.disposalContent) {
+    ElMessage.warning("请输入升级说明");
+    return;
+  }
+  submitLoading.value = true;
+  try {
+    const res = await upgradeWarningData({
+      warningId: currentRow.value.warningId,
+      newLevel: upgradeForm.newLevel,
+      disposalContent: upgradeForm.disposalContent
+    });
+    if (res.code !== 200) {
+      ElMessage.error(res.msg || "升级失败");
+      return;
+    }
+    ElMessage.success("升级成功");
+    upgradeVisible.value = false;
+    loadList();
+  } finally {
+    submitLoading.value = false;
+  }
+}
+
+async function submitSupervision() {
+  if (!supervisionForm.supervisionUser || !supervisionForm.supervisionContent || !supervisionForm.notificationType) {
+    ElMessage.warning("请完善督办信息");
+    return;
+  }
+  submitLoading.value = true;
+  try {
+    const res = await supervisionWarningData({
+      warningId: currentRow.value.warningId,
+      supervisionUser: supervisionForm.supervisionUser,
+      supervisionContent: supervisionForm.supervisionContent,
+      notificationType: supervisionForm.notificationType,
+      notificationContent: supervisionForm.notificationContent,
+      attachmentUrl: supervisionForm.attachmentUrl
+    });
+    if (res.code !== 200) {
+      ElMessage.error(res.msg || "督办失败");
+      return;
+    }
+    ElMessage.success("督办成功");
+    supervisionVisible.value = false;
+    loadList();
+  } finally {
+    submitLoading.value = false;
+  }
+}
+
+async function submitInstruction() {
+  if (!instructionForm.leaderName || !instructionForm.instructionContent) {
+    ElMessage.warning("请完善领导批示信息");
+    return;
+  }
+  submitLoading.value = true;
+  try {
+    const res = await instructionWarningData({
+      warningId: currentRow.value.warningId,
+      leaderName: instructionForm.leaderName,
+      instructionContent: instructionForm.instructionContent,
+      smsReceivers: instructionForm.smsReceivers
+    });
+    if (res.code !== 200) {
+      ElMessage.error(res.msg || "批示失败");
+      return;
+    }
+    ElMessage.success("添加批示成功");
+    instructionVisible.value = false;
+    loadList();
+  } finally {
+    submitLoading.value = false;
+  }
+}
+
+async function submitReturn() {
+  if (!returnForm.disposalContent) {
+    ElMessage.warning("请输入退回说明");
+    return;
+  }
+  submitLoading.value = true;
+  try {
+    const res = await returnWarningData({
+      warningId: currentRow.value.warningId,
+      disposalContent: returnForm.disposalContent
+    });
+    if (res.code !== 200) {
+      ElMessage.error(res.msg || "退回失败");
+      return;
+    }
+    ElMessage.success("退回成功");
+    returnVisible.value = false;
+    loadList();
+  } finally {
+    submitLoading.value = false;
+  }
+}
+
+onMounted(async () => {
+  await loadDicts();
+  await loadList();
+});
 </script>
 
-<template>
-<div>
-  5.2.2.3.1. 预警解除
-  根据事件现场处置人员反馈的预警处置结果,专业分析人员对预警信息处置结果核查无误后,可解除预警,形成发现问题到处置问题的闭环管理。
-  5.2.2.3.2. 预警升级
-  在预警处置过程中,随着事态不断演变,结合周边的地理位置信息和人口密集分布、建筑密度等情况,当发现预警事件处于可能导致高后果区域时,经供权属单位、管理部门、监管部门与监测中心等单位共同研判会商后,通过预警升级功能调整预警级别。5.2.2.3.3. 预警督办
-  在预警事项配置中,根据配置的各类异常信息数据,系统将产生异常预警,系统支持对异常预警信息进行督办提醒,通过选择督办人员、督办意见、通知方式、通知内容、附件等完成预警信息督办。
-  5.2.2.3.4. 添加领导批示
-  系统支持添加领导批示功能,即可对已接收、处置中、已处置、已发布状态下的预警数据,已短信的形式推送至相关领导批示。支持领导对预警处置内容的处置指导。
-  沅陵县太常片区城市地下管网及设施建设改造项目工程货物(智能化系统及设备
-  5.2.2.3.5. 预警退回
-  在发现现场仍然有异常并判断仍然需要到现场进行复核处置时,通过退回将该事件进行打回
-  并通知处置单位重新进行处置。
-</div>
-</template>
+<style scoped>
+.warning-disposal-page {
+  background: #f5f7fb;
+}
+
+.page-card {
+  border-radius: 16px;
+  margin-bottom: 16px;
+}
+
+.header-card {
+  background: linear-gradient(135deg, #eff6ff, #f8fbff);
+  border: 1px solid #dbeafe;
+}
+
+.page-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  gap: 16px;
+}
+
+.page-title {
+  font-size: 22px;
+  font-weight: 700;
+  color: #1e3a8a;
+}
+
+.page-subtitle {
+  margin-top: 8px;
+  font-size: 13px;
+  color: #64748b;
+}
+
+.action-grid {
+  display: grid;
+  grid-template-columns: repeat(5, minmax(0, 1fr));
+  gap: 16px;
+  margin-bottom: 16px;
+}
+
+.action-card {
+  border: none;
+  color: #fff;
+}
+
+.action-card :deep(.el-card__body) {
+  padding: 18px;
+}
+
+.action-card.resolve {
+  background: linear-gradient(135deg, #059669, #34d399);
+}
+
+.action-card.upgrade {
+  background: linear-gradient(135deg, #d97706, #f7b955);
+}
+
+.action-card.supervise {
+  background: linear-gradient(135deg, #2563eb, #5b93ff);
+}
+
+.action-card.instruction {
+  background: linear-gradient(135deg, #7c3aed, #9d72ff);
+}
+
+.action-card.return {
+  background: linear-gradient(135deg, #dc2626, #f87171);
+}
+
+.action-title {
+  font-size: 16px;
+  font-weight: 700;
+}
+
+.action-desc {
+  margin-top: 10px;
+  font-size: 12px;
+  line-height: 1.7;
+  opacity: 0.95;
+}
+
+@media (max-width: 1400px) {
+  .action-grid {
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+  }
+}
 
-<style scoped lang="scss">
+@media (max-width: 768px) {
+  .action-grid {
+    grid-template-columns: 1fr;
+  }
 
-</style>
+  .page-header {
+    flex-direction: column;
+    align-items: flex-start;
+  }
+}
+</style>

+ 473 - 147
src/views/subSystem/pipeNetwork/pdeviceInform/Pbasicinfo.vue

@@ -1,111 +1,206 @@
 <!-- 管网基础信息管理 -->
 <template>
-  <div class="app-container">
-    <el-form ref="queryRef" :model="queryParams" :inline="true" v-show="showSearch">
-      <el-form-item label="设施编码" prop="networkCode">
-        <el-input
-          v-model="queryParams.networkCode"
-          placeholder="请输入设施编码"
-          clearable
-          style="width: 220px"
-          @keyup.enter="handleQuery"
-        />
-      </el-form-item>
-      <el-form-item label="设施名称" prop="networkName">
-        <el-input
-          v-model="queryParams.networkName"
-          placeholder="请输入设施名称"
-          clearable
-          style="width: 220px"
-          @keyup.enter="handleQuery"
-        />
-      </el-form-item>
-      <el-form-item label="设施类型" prop="networkType">
-        <el-select
-          v-model="queryParams.networkType"
-          placeholder="请选择设施类型"
-          clearable
-          filterable
-          allow-create
-          default-first-option
-          style="width: 220px"
-        >
-          <el-option
-            v-for="item in networkTypeOptions"
-            :key="item"
-            :label="item"
-            :value="item"
+  <div class="app-container pipe-network-page">
+    <div class="summary-grid">
+      <el-card shadow="hover" class="summary-card primary">
+        <div class="summary-label">设施总数</div>
+        <div class="summary-value">{{ summary.totalNetworks }}</div>
+        <div class="summary-desc">已纳入平台的管网基础设施</div>
+      </el-card>
+      <el-card shadow="hover" class="summary-card success">
+        <div class="summary-label">正常运行</div>
+        <div class="summary-value">{{ summary.activeNetworks }}</div>
+        <div class="summary-desc">状态为正常或启用的设施</div>
+      </el-card>
+      <el-card shadow="hover" class="summary-card warning">
+        <div class="summary-label">设施类型</div>
+        <div class="summary-value">{{ summary.typeCount }}</div>
+        <div class="summary-desc">当前已覆盖的设施分类数</div>
+      </el-card>
+      <el-card shadow="hover" class="summary-card info">
+        <div class="summary-label">关联点位</div>
+        <div class="summary-value">{{ summary.totalPoints }}</div>
+        <div class="summary-desc">关键监控点位总量</div>
+      </el-card>
+    </div>
+
+    <el-card shadow="never" class="query-card">
+      <el-form ref="queryRef" :model="queryParams" :inline="true" v-show="showSearch">
+        <el-form-item label="设施编码" prop="networkCode">
+          <el-input
+            v-model="queryParams.networkCode"
+            placeholder="请输入设施编码"
+            clearable
+            style="width: 220px"
+            @keyup.enter="handleQuery"
           />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="状态" prop="status">
-        <el-select
-          v-model="queryParams.status"
-          placeholder="请选择状态"
-          clearable
-          filterable
-          allow-create
-          default-first-option
-          style="width: 180px"
-        >
-          <el-option
-            v-for="item in statusOptions"
-            :key="item"
-            :label="item"
-            :value="item"
+        </el-form-item>
+        <el-form-item label="设施名称" prop="networkName">
+          <el-input
+            v-model="queryParams.networkName"
+            placeholder="请输入设施名称"
+            clearable
+            style="width: 220px"
+            @keyup.enter="handleQuery"
           />
-        </el-select>
-      </el-form-item>
-      <el-form-item>
-        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-      </el-form-item>
-    </el-form>
-
-    <el-row :gutter="10" class="mb8">
-      <el-col :span="1.5">
-        <el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">
-          修改
-        </el-button>
+        </el-form-item>
+        <el-form-item label="设施类型" prop="networkType">
+          <el-select
+            v-model="queryParams.networkType"
+            placeholder="请选择设施类型"
+            clearable
+            filterable
+            allow-create
+            default-first-option
+            style="width: 220px"
+          >
+            <el-option v-for="item in networkTypeOptions" :key="item" :label="item" :value="item" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="状态" prop="status">
+          <el-select
+            v-model="queryParams.status"
+            placeholder="请选择状态"
+            clearable
+            filterable
+            allow-create
+            default-first-option
+            style="width: 180px"
+          >
+            <el-option v-for="item in statusOptions" :key="item" :label="item" :value="item" />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+          <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
+
+    <el-row :gutter="16" class="main-row">
+      <el-col :xs="24" :lg="15">
+        <el-card shadow="never" class="table-card">
+          <template #header>
+            <div class="card-header-row">
+              <div>
+                <div class="card-title">设施台账</div>
+                <div class="card-subtitle">支持查询、新增、编辑、删除以及设施与点位联动查看</div>
+              </div>
+              <div class="card-actions">
+                <el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
+                <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
+                <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">删除</el-button>
+                <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
+              </div>
+            </div>
+          </template>
+
+          <el-table
+            v-loading="loading"
+            :data="networkList"
+            highlight-current-row
+            @selection-change="handleSelectionChange"
+            @current-change="handleCurrentChange"
+            @row-click="handleRowClick"
+          >
+            <el-table-column type="selection" width="55" align="center" />
+            <el-table-column label="设施编码" align="center" prop="networkCode" min-width="130" />
+            <el-table-column label="设施名称" align="center" prop="networkName" min-width="180" show-overflow-tooltip />
+            <el-table-column label="设施类型" align="center" prop="networkType" min-width="120" />
+            <el-table-column label="等级" align="center" prop="networkLevel" width="90" />
+            <el-table-column label="管养单位" align="center" prop="managementUnit" min-width="150" show-overflow-tooltip />
+            <el-table-column label="状态" align="center" width="100">
+              <template #default="scope">
+                <el-tag :type="getStatusTagType(scope.row.status)">{{ scope.row.status || "-" }}</el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column label="操作" align="center" width="170" class-name="small-padding fixed-width">
+              <template #default="scope">
+                <el-button link type="primary" icon="View" @click.stop="selectNetwork(scope.row)">详情</el-button>
+                <el-button link type="primary" icon="Edit" @click.stop="handleUpdate(scope.row)">修改</el-button>
+                <el-button link type="danger" icon="Delete" @click.stop="handleDelete(scope.row)">删除</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+
+          <pagination
+            v-show="total > 0"
+            :total="total"
+            v-model:page="queryParams.pageNum"
+            v-model:limit="queryParams.pageSize"
+            @pagination="getList"
+          />
+        </el-card>
       </el-col>
-      <el-col :span="1.5">
-        <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
-          删除
-        </el-button>
+
+      <el-col :xs="24" :lg="9">
+        <el-card shadow="never" class="detail-card">
+          <template #header>
+            <div class="card-header-row">
+              <div>
+                <div class="card-title">设施详情</div>
+                <div class="card-subtitle">选中设施后可查看基础信息及下挂监测点位</div>
+              </div>
+              <el-tag v-if="selectedNetwork.networkId" :type="getStatusTagType(selectedNetwork.status)">
+                {{ selectedNetwork.status || "未设置" }}
+              </el-tag>
+            </div>
+          </template>
+
+          <el-empty v-if="!selectedNetwork.networkId" description="请从左侧选择一条管网设施" />
+
+          <template v-else>
+            <div class="detail-hero">
+              <div class="hero-title">{{ selectedNetwork.networkName }}</div>
+              <div class="hero-meta">
+                <span>{{ selectedNetwork.networkCode }}</span>
+                <span>{{ selectedNetwork.networkType || "未分类" }}</span>
+                <span>{{ selectedNetwork.managementUnit || "未维护管养单位" }}</span>
+              </div>
+            </div>
+
+            <el-descriptions :column="1" border size="small" class="detail-descriptions">
+              <el-descriptions-item label="设施等级">{{ selectedNetwork.networkLevel || "-" }}</el-descriptions-item>
+              <el-descriptions-item label="位置描述">{{ selectedNetwork.location || "-" }}</el-descriptions-item>
+              <el-descriptions-item label="经纬度">
+                {{ selectedNetwork.longitude ?? "-" }} / {{ selectedNetwork.latitude ?? "-" }}
+              </el-descriptions-item>
+              <el-descriptions-item label="长度/管径">
+                {{ selectedNetwork.length ?? "-" }} m / {{ selectedNetwork.diameter ?? "-" }} mm
+              </el-descriptions-item>
+              <el-descriptions-item label="材质">{{ selectedNetwork.material || "-" }}</el-descriptions-item>
+              <el-descriptions-item label="建设日期">{{ selectedNetwork.buildDate || "-" }}</el-descriptions-item>
+              <el-descriptions-item label="备注">{{ selectedNetwork.remark || "-" }}</el-descriptions-item>
+            </el-descriptions>
+
+            <div class="section-block">
+              <div class="section-title">关联监测点位</div>
+              <div class="section-tip">
+                共 {{ relatedPoints.length }} 个点位,异常/需关注 {{ abnormalPointCount }} 个
+              </div>
+              <el-table v-loading="pointLoading" :data="relatedPoints" size="small" max-height="300">
+                <el-table-column label="点位名称" prop="pointName" min-width="140" show-overflow-tooltip />
+                <el-table-column label="类型" prop="pointType" min-width="100" />
+                <el-table-column label="状态" width="90">
+                  <template #default="scope">
+                    <el-tag :type="getStatusTagType(scope.row.status)" size="small">
+                      {{ scope.row.status || "-" }}
+                    </el-tag>
+                  </template>
+                </el-table-column>
+                <el-table-column label="安装日期" prop="installationDate" width="110" />
+              </el-table>
+            </div>
+
+            <div class="side-actions">
+              <el-button type="primary" icon="Edit" @click="handleUpdate(selectedNetwork)">编辑设施</el-button>
+              <el-button type="danger" plain icon="Delete" @click="handleDelete(selectedNetwork)">删除设施</el-button>
+            </div>
+          </template>
+        </el-card>
       </el-col>
-      <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
     </el-row>
 
-    <el-table v-loading="loading" :data="networkList" @selection-change="handleSelectionChange">
-      <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="设施编码" align="center" prop="networkCode" min-width="130" />
-      <el-table-column label="设施名称" align="center" prop="networkName" min-width="160" show-overflow-tooltip />
-      <el-table-column label="设施类型" align="center" prop="networkType" min-width="120" />
-      <el-table-column label="等级" align="center" prop="networkLevel" width="90" />
-      <el-table-column label="管养单位" align="center" prop="managementUnit" min-width="150" show-overflow-tooltip />
-      <el-table-column label="位置描述" align="center" prop="location" min-width="180" show-overflow-tooltip />
-      <el-table-column label="状态" align="center" prop="status" width="100" />
-      <el-table-column label="建设日期" align="center" prop="buildDate" width="120" />
-      <el-table-column label="操作" align="center" width="220" class-name="small-padding fixed-width">
-        <template #default="scope">
-          <el-button link type="primary" icon="View" @click="handleView(scope.row)">详情</el-button>
-          <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
-          <el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-
-    <pagination
-      v-show="total > 0"
-      :total="total"
-      v-model:page="queryParams.pageNum"
-      v-model:limit="queryParams.pageSize"
-      @pagination="getList"
-    />
-
     <el-dialog :title="title" v-model="open" width="760px" append-to-body>
       <el-form ref="networkRef" :model="form" :rules="rules" label-width="110px">
         <el-row :gutter="16">
@@ -129,12 +224,7 @@
                 default-first-option
                 style="width: 100%"
               >
-                <el-option
-                  v-for="item in networkTypeOptions"
-                  :key="item"
-                  :label="item"
-                  :value="item"
-                />
+                <el-option v-for="item in networkTypeOptions" :key="item" :label="item" :value="item" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -158,12 +248,7 @@
                 default-first-option
                 style="width: 100%"
               >
-                <el-option
-                  v-for="item in statusOptions"
-                  :key="item"
-                  :label="item"
-                  :value="item"
-                />
+                <el-option v-for="item in statusOptions" :key="item" :label="item" :value="item" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -222,53 +307,50 @@
         </div>
       </template>
     </el-dialog>
-
-    <el-dialog title="设施详情" v-model="detailOpen" width="760px" append-to-body>
-      <el-descriptions :column="2" border v-if="detailForm.networkId">
-        <el-descriptions-item label="设施编码">{{ detailForm.networkCode || "-" }}</el-descriptions-item>
-        <el-descriptions-item label="设施名称">{{ detailForm.networkName || "-" }}</el-descriptions-item>
-        <el-descriptions-item label="设施类型">{{ detailForm.networkType || "-" }}</el-descriptions-item>
-        <el-descriptions-item label="设施等级">{{ detailForm.networkLevel || "-" }}</el-descriptions-item>
-        <el-descriptions-item label="管养单位">{{ detailForm.managementUnit || "-" }}</el-descriptions-item>
-        <el-descriptions-item label="状态">{{ detailForm.status || "-" }}</el-descriptions-item>
-        <el-descriptions-item label="经度">{{ detailForm.longitude ?? "-" }}</el-descriptions-item>
-        <el-descriptions-item label="纬度">{{ detailForm.latitude ?? "-" }}</el-descriptions-item>
-        <el-descriptions-item label="长度(米)">{{ detailForm.length ?? "-" }}</el-descriptions-item>
-        <el-descriptions-item label="管径(mm)">{{ detailForm.diameter ?? "-" }}</el-descriptions-item>
-        <el-descriptions-item label="材质">{{ detailForm.material || "-" }}</el-descriptions-item>
-        <el-descriptions-item label="建设日期">{{ detailForm.buildDate || "-" }}</el-descriptions-item>
-        <el-descriptions-item label="位置描述" :span="2">{{ detailForm.location || "-" }}</el-descriptions-item>
-        <el-descriptions-item label="备注" :span="2">{{ detailForm.remark || "-" }}</el-descriptions-item>
-      </el-descriptions>
-    </el-dialog>
   </div>
 </template>
 
 <script setup>
-import { getCurrentInstance, onMounted, reactive, ref, toRefs } from "vue";
+import { computed, getCurrentInstance, onMounted, reactive, ref, toRefs } from "vue";
 import {
   addPipeNetwork,
   delPipeNetwork,
   getPipeNetwork,
+  listMonitorPointAll,
   listPipeNetwork,
+  listPipeNetworkAll,
+  listPointsByNetwork,
   updatePipeNetwork
 } from "@/api/pipeNetwork/pdeviceInform";
 
 const { proxy } = getCurrentInstance();
 
 const networkList = ref([]);
+const relatedPoints = ref([]);
 const loading = ref(false);
+const pointLoading = ref(false);
 const open = ref(false);
-const detailOpen = ref(false);
 const showSearch = ref(true);
 const ids = ref([]);
 const single = ref(true);
 const multiple = ref(true);
 const total = ref(0);
 const title = ref("");
+const selectedNetwork = ref({});
 const networkTypeOptions = ["管网路线", "燃气设施", "排水设施", "供水设施", "桥梁"];
 const statusOptions = ["正常", "启用", "停用", "维修中", "预警"];
 
+const summary = reactive({
+  totalNetworks: 0,
+  activeNetworks: 0,
+  typeCount: 0,
+  totalPoints: 0
+});
+
+const abnormalPointCount = computed(() => {
+  return relatedPoints.value.filter((item) => !["正常", "启用"].includes(item.status)).length;
+});
+
 const data = reactive({
   queryParams: {
     pageNum: 1,
@@ -279,7 +361,6 @@ const data = reactive({
     status: undefined
   },
   form: {},
-  detailForm: {},
   rules: {
     networkCode: [{ required: true, message: "设施编码不能为空", trigger: "blur" }],
     networkName: [{ required: true, message: "设施名称不能为空", trigger: "blur" }],
@@ -287,7 +368,7 @@ const data = reactive({
   }
 });
 
-const { queryParams, form, detailForm, rules } = toRefs(data);
+const { queryParams, form, rules } = toRefs(data);
 
 function createEmptyForm() {
   return {
@@ -309,12 +390,75 @@ function createEmptyForm() {
   };
 }
 
+function getStatusTagType(status) {
+  if (["正常", "启用"].includes(status)) {
+    return "success";
+  }
+  if (status === "预警") {
+    return "warning";
+  }
+  if (["停用", "禁用"].includes(status)) {
+    return "info";
+  }
+  if (["故障", "异常"].includes(status)) {
+    return "danger";
+  }
+  return "";
+}
+
+function loadRelatedPoints(networkId) {
+  if (!networkId) {
+    relatedPoints.value = [];
+    return Promise.resolve();
+  }
+  pointLoading.value = true;
+  return listPointsByNetwork(networkId)
+    .then((response) => {
+      relatedPoints.value = response.data || [];
+    })
+    .finally(() => {
+      pointLoading.value = false;
+    });
+}
+
+function selectNetwork(row) {
+  if (!row?.networkId) {
+    return;
+  }
+  selectedNetwork.value = row;
+  return loadRelatedPoints(row.networkId);
+}
+
+function loadSummary() {
+  return Promise.all([listPipeNetworkAll(), listMonitorPointAll()]).then(([networkRes, pointRes]) => {
+    const allNetworks = networkRes.data || [];
+    const allPoints = pointRes.data || [];
+    summary.totalNetworks = allNetworks.length;
+    summary.activeNetworks = allNetworks.filter((item) => ["正常", "启用"].includes(item.status)).length;
+    summary.typeCount = new Set(allNetworks.map((item) => item.networkType).filter(Boolean)).size;
+    summary.totalPoints = allPoints.length;
+
+    if (selectedNetwork.value.networkId) {
+      const refreshed = allNetworks.find((item) => item.networkId === selectedNetwork.value.networkId);
+      if (refreshed) {
+        selectedNetwork.value = refreshed;
+      }
+    } else if (allNetworks.length) {
+      selectedNetwork.value = allNetworks[0];
+    }
+  });
+}
+
 function getList() {
   loading.value = true;
-  listPipeNetwork(queryParams.value)
+  return listPipeNetwork(queryParams.value)
     .then((response) => {
       networkList.value = response.records || [];
       total.value = response.total || 0;
+      if (!selectedNetwork.value.networkId && networkList.value.length) {
+        return selectNetwork(networkList.value[0]);
+      }
+      return undefined;
     })
     .finally(() => {
       loading.value = false;
@@ -342,6 +486,16 @@ function handleSelectionChange(selection) {
   multiple.value = !selection.length;
 }
 
+function handleCurrentChange(currentRow) {
+  if (currentRow?.networkId) {
+    selectNetwork(currentRow);
+  }
+}
+
+function handleRowClick(row) {
+  selectNetwork(row);
+}
+
 function handleAdd() {
   reset();
   open.value = true;
@@ -361,13 +515,6 @@ function handleUpdate(row) {
   });
 }
 
-function handleView(row) {
-  getPipeNetwork(row.networkId).then((response) => {
-    detailForm.value = { ...createEmptyForm(), ...(response.data || {}) };
-    detailOpen.value = true;
-  });
-}
-
 function cancel() {
   open.value = false;
   reset();
@@ -382,7 +529,14 @@ function submitForm() {
     request.then(() => {
       proxy.$modal.msgSuccess(form.value.networkId ? "修改成功" : "新增成功");
       open.value = false;
-      getList();
+      Promise.all([getList(), loadSummary()]).then(() => {
+        if (form.value.networkId) {
+          getPipeNetwork(form.value.networkId).then((response) => {
+            selectedNetwork.value = response.data || {};
+            loadRelatedPoints(selectedNetwork.value.networkId);
+          });
+        }
+      });
     });
   });
 }
@@ -397,13 +551,185 @@ function handleDelete(row) {
     .then(() => Promise.all(deleteIds.map((id) => delPipeNetwork(id))))
     .then(() => {
       proxy.$modal.msgSuccess("删除成功");
-      getList();
+      if (deleteIds.includes(selectedNetwork.value.networkId)) {
+        selectedNetwork.value = {};
+        relatedPoints.value = [];
+      }
+      return Promise.all([getList(), loadSummary()]);
     });
 }
 
 onMounted(() => {
   reset();
-  detailForm.value = createEmptyForm();
-  getList();
+  Promise.all([getList(), loadSummary()]).then(() => {
+    if (selectedNetwork.value.networkId) {
+      loadRelatedPoints(selectedNetwork.value.networkId);
+    }
+  });
 });
 </script>
+
+<style scoped>
+.pipe-network-page {
+  background: #f5f8fc;
+}
+
+.summary-grid {
+  display: grid;
+  grid-template-columns: repeat(4, minmax(0, 1fr));
+  gap: 16px;
+  margin-bottom: 16px;
+}
+
+.summary-card {
+  border: none;
+  color: #fff;
+}
+
+.summary-card :deep(.el-card__body) {
+  padding: 20px;
+}
+
+.summary-card.primary {
+  background: linear-gradient(135deg, #2b73ff, #5da2ff);
+}
+
+.summary-card.success {
+  background: linear-gradient(135deg, #16a34a, #41c878);
+}
+
+.summary-card.warning {
+  background: linear-gradient(135deg, #f59e0b, #f8bf52);
+}
+
+.summary-card.info {
+  background: linear-gradient(135deg, #0f766e, #2bb2a6);
+}
+
+.summary-label {
+  font-size: 14px;
+  opacity: 0.9;
+}
+
+.summary-value {
+  margin: 8px 0 10px;
+  font-size: 30px;
+  font-weight: 700;
+  line-height: 1;
+}
+
+.summary-desc {
+  font-size: 12px;
+  opacity: 0.9;
+}
+
+.query-card,
+.table-card,
+.detail-card {
+  border-radius: 16px;
+}
+
+.query-card {
+  margin-bottom: 16px;
+}
+
+.main-row {
+  align-items: stretch;
+}
+
+.card-header-row {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  gap: 12px;
+}
+
+.card-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #1f2937;
+}
+
+.card-subtitle {
+  margin-top: 4px;
+  font-size: 12px;
+  color: #6b7280;
+}
+
+.card-actions {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  flex-wrap: wrap;
+}
+
+.detail-hero {
+  margin-bottom: 16px;
+  padding: 16px;
+  border-radius: 14px;
+  background: linear-gradient(135deg, #eff6ff, #f8fbff);
+  border: 1px solid #dbeafe;
+}
+
+.hero-title {
+  font-size: 18px;
+  font-weight: 700;
+  color: #1d4ed8;
+}
+
+.hero-meta {
+  display: flex;
+  gap: 10px;
+  flex-wrap: wrap;
+  margin-top: 8px;
+  font-size: 12px;
+  color: #4b5563;
+}
+
+.detail-descriptions {
+  margin-bottom: 16px;
+}
+
+.section-block {
+  margin-top: 12px;
+}
+
+.section-title {
+  font-size: 15px;
+  font-weight: 600;
+  color: #1f2937;
+}
+
+.section-tip {
+  margin: 6px 0 12px;
+  font-size: 12px;
+  color: #6b7280;
+}
+
+.side-actions {
+  display: flex;
+  gap: 10px;
+  margin-top: 16px;
+}
+
+@media (max-width: 1200px) {
+  .summary-grid {
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+  }
+}
+
+@media (max-width: 768px) {
+  .summary-grid {
+    grid-template-columns: 1fr;
+  }
+
+  .card-header-row {
+    align-items: flex-start;
+    flex-direction: column;
+  }
+
+  .side-actions {
+    flex-direction: column;
+  }
+}
+</style>

+ 373 - 142
src/views/subSystem/pipeNetwork/pdeviceInform/PequipmentLed.vue

@@ -1,105 +1,195 @@
 <!-- 物联感知设备管理 -->
 <template>
-  <div class="app-container">
-    <el-form ref="queryRef" :model="queryParams" :inline="true" v-show="showSearch">
-      <el-form-item label="设备编码" prop="equipmentCode">
-        <el-input
-          v-model="queryParams.equipmentCode"
-          placeholder="请输入设备编码"
-          clearable
-          style="width: 220px"
-          @keyup.enter="handleQuery"
-        />
-      </el-form-item>
-      <el-form-item label="设备名称" prop="equipmentName">
-        <el-input
-          v-model="queryParams.equipmentName"
-          placeholder="请输入设备名称"
-          clearable
-          style="width: 220px"
-          @keyup.enter="handleQuery"
-        />
-      </el-form-item>
-      <el-form-item label="设备型号" prop="equipmentModel">
-        <el-input
-          v-model="queryParams.equipmentModel"
-          placeholder="请输入设备型号"
-          clearable
-          style="width: 220px"
-          @keyup.enter="handleQuery"
-        />
-      </el-form-item>
-      <el-form-item label="制造商" prop="manufacturer">
-        <el-input
-          v-model="queryParams.manufacturer"
-          placeholder="请输入制造商"
-          clearable
-          style="width: 220px"
-          @keyup.enter="handleQuery"
-        />
-      </el-form-item>
-      <el-form-item>
-        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-      </el-form-item>
-    </el-form>
-
-    <el-row :gutter="10" class="mb8">
-      <el-col :span="1.5">
-        <el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">
-          修改
-        </el-button>
+  <div class="app-container equipment-page">
+    <div class="summary-grid">
+      <el-card shadow="hover" class="summary-card primary">
+        <div class="summary-label">设备总数</div>
+        <div class="summary-value">{{ summary.total }}</div>
+        <div class="summary-desc">平台已接入物联感知设备</div>
+      </el-card>
+      <el-card shadow="hover" class="summary-card success">
+        <div class="summary-label">在用设备</div>
+        <div class="summary-value">{{ summary.active }}</div>
+        <div class="summary-desc">当前状态为在用的设备</div>
+      </el-card>
+      <el-card shadow="hover" class="summary-card warning">
+        <div class="summary-label">维修/待处理</div>
+        <div class="summary-value">{{ summary.warning }}</div>
+        <div class="summary-desc">维修、待入库、闲置等状态</div>
+      </el-card>
+      <el-card shadow="hover" class="summary-card info">
+        <div class="summary-label">设备类型</div>
+        <div class="summary-value">{{ summary.typeCount }}</div>
+        <div class="summary-desc">当前已维护的设备分类数</div>
+      </el-card>
+    </div>
+
+    <el-card shadow="never" class="query-card">
+      <el-form ref="queryRef" :model="queryParams" :inline="true" v-show="showSearch">
+        <el-form-item label="设备编码" prop="equipmentCode">
+          <el-input
+            v-model="queryParams.equipmentCode"
+            placeholder="请输入设备编码"
+            clearable
+            style="width: 220px"
+            @keyup.enter="handleQuery"
+          />
+        </el-form-item>
+        <el-form-item label="设备名称" prop="equipmentName">
+          <el-input
+            v-model="queryParams.equipmentName"
+            placeholder="请输入设备名称"
+            clearable
+            style="width: 220px"
+            @keyup.enter="handleQuery"
+          />
+        </el-form-item>
+        <el-form-item label="设备型号" prop="equipmentModel">
+          <el-input
+            v-model="queryParams.equipmentModel"
+            placeholder="请输入设备型号"
+            clearable
+            style="width: 220px"
+            @keyup.enter="handleQuery"
+          />
+        </el-form-item>
+        <el-form-item label="制造商" prop="manufacturer">
+          <el-input
+            v-model="queryParams.manufacturer"
+            placeholder="请输入制造商"
+            clearable
+            style="width: 220px"
+            @keyup.enter="handleQuery"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+          <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
+
+    <el-row :gutter="16" class="main-row">
+      <el-col :xs="24" :lg="16">
+        <el-card shadow="never" class="table-card">
+          <template #header>
+            <div class="card-header-row">
+              <div>
+                <div class="card-title">设备台账</div>
+                <div class="card-subtitle">以 CRUD 为主,保留设备状态维护、测试验证和详情查看</div>
+              </div>
+              <div class="card-actions">
+                <el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
+                <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
+                <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">删除</el-button>
+                <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
+              </div>
+            </div>
+          </template>
+
+          <el-table
+            v-loading="loading"
+            :data="equipmentList"
+            highlight-current-row
+            @selection-change="handleSelectionChange"
+            @current-change="handleCurrentChange"
+            @row-click="handleRowClick"
+          >
+            <el-table-column type="selection" width="55" align="center" />
+            <el-table-column label="设备编码" align="center" prop="equipmentCode" min-width="140" />
+            <el-table-column label="设备名称" align="center" prop="equipmentName" min-width="160" show-overflow-tooltip />
+            <el-table-column label="设备型号" align="center" prop="equipmentModel" min-width="120" />
+            <el-table-column label="设备类型" align="center" min-width="140">
+              <template #default="scope">
+                {{ typeNameMap[scope.row.equipmentTypeId] || scope.row.equipmentTypeId || "-" }}
+              </template>
+            </el-table-column>
+            <el-table-column label="当前状态" align="center" width="110">
+              <template #default="scope">
+                <el-tag :type="getStatusTagType(getRowStatus(scope.row.equipmentId))">
+                  {{ getStatusText(getRowStatus(scope.row.equipmentId)) }}
+                </el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column label="制造商" align="center" prop="manufacturer" min-width="140" show-overflow-tooltip />
+            <el-table-column label="操作" align="center" width="280" class-name="small-padding fixed-width">
+              <template #default="scope">
+                <el-button link type="primary" icon="View" @click.stop="handleView(scope.row)">详情</el-button>
+                <el-button link type="primary" icon="Edit" @click.stop="handleUpdate(scope.row)">修改</el-button>
+                <el-button link type="warning" icon="Tools" @click.stop="openStatusDialog(scope.row)">状态维护</el-button>
+                <el-button link type="success" icon="Aim" @click.stop="openTestDialog(scope.row)">测试验证</el-button>
+                <el-button link type="danger" icon="Delete" @click.stop="handleDelete(scope.row)">删除</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+
+          <pagination
+            v-show="total > 0"
+            :total="total"
+            v-model:page="queryParams.pageNum"
+            v-model:limit="queryParams.pageSize"
+            @pagination="getList"
+          />
+        </el-card>
       </el-col>
-      <el-col :span="1.5">
-        <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
-          删除
-        </el-button>
+
+      <el-col :xs="24" :lg="8">
+        <el-card shadow="never" class="detail-card">
+          <template #header>
+            <div class="card-header-row">
+              <div>
+                <div class="card-title">设备详情</div>
+                <div class="card-subtitle">选中设备后查看基本信息、关联点位与测试记录</div>
+              </div>
+            </div>
+          </template>
+
+          <el-empty v-if="!selectedDetail.equipmentInfo" description="请从左侧选择一台设备" />
+
+          <template v-else>
+            <div class="detail-hero">
+              <div class="hero-title">{{ selectedDetail.equipmentInfo.equipmentName }}</div>
+              <div class="hero-meta">
+                <span>{{ selectedDetail.equipmentInfo.equipmentCode }}</span>
+                <span>{{ selectedDetail.typeInfo?.typeName || "-" }}</span>
+                <span>{{ getStatusText(selectedDetail.statusInfo?.currentStatus) }}</span>
+              </div>
+            </div>
+
+            <el-descriptions :column="1" border size="small">
+              <el-descriptions-item label="设备型号">{{ selectedDetail.equipmentInfo.equipmentModel || "-" }}</el-descriptions-item>
+              <el-descriptions-item label="制造商">{{ selectedDetail.equipmentInfo.manufacturer || "-" }}</el-descriptions-item>
+              <el-descriptions-item label="设备位置">{{ selectedDetail.equipmentInfo.equipmentLocation || "-" }}</el-descriptions-item>
+              <el-descriptions-item label="规格参数">{{ selectedDetail.equipmentInfo.equipmentSpec || "-" }}</el-descriptions-item>
+              <el-descriptions-item label="维护人员">
+                {{ selectedDetail.equipmentInfo.maintainer || "-" }}
+              </el-descriptions-item>
+              <el-descriptions-item label="联系电话">
+                {{ selectedDetail.equipmentInfo.maintainerPhone || "-" }}
+              </el-descriptions-item>
+            </el-descriptions>
+
+            <div class="section-block">
+              <div class="section-title">关联点位</div>
+              <el-table :data="selectedDetail.pointRelList || []" size="small" max-height="180">
+                <el-table-column label="监测点ID" prop="pointId" min-width="160" show-overflow-tooltip />
+                <el-table-column label="设备ID" prop="equipmentId" min-width="160" show-overflow-tooltip />
+              </el-table>
+            </div>
+
+            <div class="section-block">
+              <div class="section-title">测试记录</div>
+              <el-table :data="selectedDetail.testList || []" size="small" max-height="180">
+                <el-table-column label="时间" prop="testTime" min-width="150" />
+                <el-table-column label="类型" prop="testType" min-width="100" />
+                <el-table-column label="结果" prop="testResult" width="80" />
+              </el-table>
+            </div>
+          </template>
+        </el-card>
       </el-col>
-      <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
     </el-row>
 
-    <el-table v-loading="loading" :data="equipmentList" @selection-change="handleSelectionChange">
-      <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="设备编码" align="center" prop="equipmentCode" min-width="140" />
-      <el-table-column label="设备名称" align="center" prop="equipmentName" min-width="160" show-overflow-tooltip />
-      <el-table-column label="设备型号" align="center" prop="equipmentModel" min-width="120" />
-      <el-table-column label="设备类型" align="center" min-width="140">
-        <template #default="scope">
-          {{ typeNameMap[scope.row.equipmentTypeId] || scope.row.equipmentTypeId || "-" }}
-        </template>
-      </el-table-column>
-      <el-table-column label="当前状态" align="center" width="110">
-        <template #default="scope">
-          <el-tag :type="getStatusTagType(getRowStatus(scope.row.equipmentId))">
-            {{ getStatusText(getRowStatus(scope.row.equipmentId)) }}
-          </el-tag>
-        </template>
-      </el-table-column>
-      <el-table-column label="制造商" align="center" prop="manufacturer" min-width="140" show-overflow-tooltip />
-      <el-table-column label="设备位置" align="center" prop="equipmentLocation" min-width="180" show-overflow-tooltip />
-      <el-table-column label="维护人员" align="center" prop="maintainer" min-width="100" />
-      <el-table-column label="操作" align="center" width="300" class-name="small-padding fixed-width">
-        <template #default="scope">
-          <el-button link type="primary" icon="View" @click="handleView(scope.row)">详情</el-button>
-          <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
-          <el-button link type="warning" icon="Tools" @click="openStatusDialog(scope.row)">状态维护</el-button>
-          <el-button link type="success" icon="Aim" @click="openTestDialog(scope.row)">测试验证</el-button>
-          <el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-
-    <pagination
-      v-show="total > 0"
-      :total="total"
-      v-model:page="queryParams.pageNum"
-      v-model:limit="queryParams.pageSize"
-      @pagination="getList"
-    />
-
     <el-dialog :title="title" v-model="open" width="860px" append-to-body>
       <el-form ref="equipmentRef" :model="form" :rules="rules" label-width="110px">
         <el-row :gutter="16">
@@ -212,39 +302,6 @@
       </template>
     </el-dialog>
 
-    <el-dialog title="设备详情" v-model="detailOpen" width="980px" append-to-body>
-      <el-descriptions :column="2" border v-if="detailForm.equipmentInfo">
-        <el-descriptions-item label="设备编码">{{ detailForm.equipmentInfo.equipmentCode || "-" }}</el-descriptions-item>
-        <el-descriptions-item label="设备名称">{{ detailForm.equipmentInfo.equipmentName || "-" }}</el-descriptions-item>
-        <el-descriptions-item label="设备型号">{{ detailForm.equipmentInfo.equipmentModel || "-" }}</el-descriptions-item>
-        <el-descriptions-item label="设备类型">{{ detailForm.typeInfo?.typeName || "-" }}</el-descriptions-item>
-        <el-descriptions-item label="制造商">{{ detailForm.equipmentInfo.manufacturer || "-" }}</el-descriptions-item>
-        <el-descriptions-item label="当前状态">{{ getStatusText(detailForm.statusInfo?.currentStatus) }}</el-descriptions-item>
-        <el-descriptions-item label="设备位置" :span="2">{{ detailForm.equipmentInfo.equipmentLocation || "-" }}</el-descriptions-item>
-        <el-descriptions-item label="规格参数" :span="2">{{ detailForm.equipmentInfo.equipmentSpec || "-" }}</el-descriptions-item>
-        <el-descriptions-item label="维护人员">{{ detailForm.equipmentInfo.maintainer || "-" }}</el-descriptions-item>
-        <el-descriptions-item label="联系电话">{{ detailForm.equipmentInfo.maintainerPhone || "-" }}</el-descriptions-item>
-        <el-descriptions-item label="安装时间">{{ detailForm.equipmentInfo.installTime || "-" }}</el-descriptions-item>
-        <el-descriptions-item label="生产日期">{{ detailForm.equipmentInfo.productionDate || "-" }}</el-descriptions-item>
-        <el-descriptions-item label="备注" :span="2">{{ detailForm.equipmentInfo.remark || "-" }}</el-descriptions-item>
-      </el-descriptions>
-
-      <el-divider content-position="left">关联监测点</el-divider>
-      <el-table :data="detailForm.pointRelList || []" border>
-        <el-table-column label="监测点ID" prop="pointId" min-width="200" show-overflow-tooltip />
-        <el-table-column label="设备ID" prop="equipmentId" min-width="200" show-overflow-tooltip />
-      </el-table>
-
-      <el-divider content-position="left">测试记录</el-divider>
-      <el-table :data="detailForm.testList || []" border>
-        <el-table-column label="测试时间" prop="testTime" min-width="170" />
-        <el-table-column label="测试类型" prop="testType" min-width="120" />
-        <el-table-column label="测试结果" prop="testResult" width="100" />
-        <el-table-column label="测试人员" prop="testUser" width="120" />
-        <el-table-column label="测试详情" prop="testDetail" min-width="220" show-overflow-tooltip />
-      </el-table>
-    </el-dialog>
-
     <el-dialog title="设备状态维护" v-model="statusOpen" width="420px" append-to-body>
       <el-form label-width="90px">
         <el-form-item label="设备名称">
@@ -316,7 +373,6 @@ const equipmentTypeOptions = ref([]);
 const equipmentStatusList = ref([]);
 const loading = ref(false);
 const open = ref(false);
-const detailOpen = ref(false);
 const statusOpen = ref(false);
 const testOpen = ref(false);
 const showSearch = ref(true);
@@ -326,6 +382,7 @@ const multiple = ref(true);
 const total = ref(0);
 const title = ref("");
 const currentRow = ref({});
+const selectedDetail = ref({});
 
 const equipmentStatusOptions = [
   { label: "在用", value: 1 },
@@ -335,6 +392,13 @@ const equipmentStatusOptions = [
   { label: "待入库", value: 5 }
 ];
 
+const summary = reactive({
+  total: 0,
+  active: 0,
+  warning: 0,
+  typeCount: 0
+});
+
 const typeNameMap = computed(() => {
   return equipmentTypeOptions.value.reduce((acc, item) => {
     acc[item.typeId] = item.typeName;
@@ -359,7 +423,6 @@ const data = reactive({
     manufacturer: undefined
   },
   form: {},
-  detailForm: {},
   statusForm: {
     currentStatus: 1
   },
@@ -375,7 +438,7 @@ const data = reactive({
   }
 });
 
-const { queryParams, form, detailForm, statusForm, testForm, rules } = toRefs(data);
+const { queryParams, form, statusForm, testForm, rules } = toRefs(data);
 
 function createEmptyForm() {
   return {
@@ -419,12 +482,23 @@ function getStatusTagType(status) {
   return map[status] || "";
 }
 
+function buildSummary() {
+  summary.total = equipmentStatusList.value.length;
+  summary.active = equipmentStatusList.value.filter((item) => item.currentStatus === 1).length;
+  summary.warning = equipmentStatusList.value.filter((item) => item.currentStatus !== 1).length;
+  summary.typeCount = new Set(equipmentTypeOptions.value.map((item) => item.typeName).filter(Boolean)).size;
+}
+
 function getList() {
   loading.value = true;
-  listEquipment(queryParams.value)
+  return listEquipment(queryParams.value)
     .then((response) => {
       equipmentList.value = response.records || [];
       total.value = response.total || 0;
+      if (!selectedDetail.value.equipmentInfo && equipmentList.value.length) {
+        return handleView(equipmentList.value[0]);
+      }
+      return undefined;
     })
     .finally(() => {
       loading.value = false;
@@ -435,6 +509,7 @@ function loadAssistData() {
   return Promise.all([listEquipmentTypeAll(), listEquipmentStatusAll()]).then(([typeRes, statusRes]) => {
     equipmentTypeOptions.value = typeRes.data || [];
     equipmentStatusList.value = statusRes.data || [];
+    buildSummary();
   });
 }
 
@@ -459,6 +534,16 @@ function handleSelectionChange(selection) {
   multiple.value = !selection.length;
 }
 
+function handleCurrentChange(currentRow) {
+  if (currentRow?.equipmentId) {
+    handleView(currentRow);
+  }
+}
+
+function handleRowClick(row) {
+  handleView(row);
+}
+
 function handleAdd() {
   reset();
   open.value = true;
@@ -479,9 +564,8 @@ function handleUpdate(row) {
 }
 
 function handleView(row) {
-  getEquipmentDetail(row.equipmentId).then((response) => {
-    detailForm.value = response.data || {};
-    detailOpen.value = true;
+  return getEquipmentDetail(row.equipmentId).then((response) => {
+    selectedDetail.value = response.data || {};
   });
 }
 
@@ -499,7 +583,11 @@ function submitForm() {
     request.then(() => {
       proxy.$modal.msgSuccess(form.value.equipmentId ? "修改成功" : "新增成功");
       open.value = false;
-      Promise.all([getList(), loadAssistData()]);
+      Promise.all([getList(), loadAssistData()]).then(() => {
+        if (form.value.equipmentId) {
+          handleView({ equipmentId: form.value.equipmentId });
+        }
+      });
     });
   });
 }
@@ -514,7 +602,10 @@ function handleDelete(row) {
     .then(() => Promise.all(deleteIds.map((id) => delEquipment(id))))
     .then(() => {
       proxy.$modal.msgSuccess("删除成功");
-      Promise.all([getList(), loadAssistData()]);
+      if (deleteIds.includes(selectedDetail.value?.equipmentInfo?.equipmentId)) {
+        selectedDetail.value = {};
+      }
+      return Promise.all([getList(), loadAssistData()]);
     });
 }
 
@@ -528,7 +619,7 @@ function submitStatus() {
   updateEquipmentStatus(currentRow.value.equipmentId, statusForm.value.currentStatus).then(() => {
     proxy.$modal.msgSuccess("状态维护成功");
     statusOpen.value = false;
-    loadAssistData();
+    Promise.all([loadAssistData(), handleView(currentRow.value)]);
   });
 }
 
@@ -546,9 +637,7 @@ function submitTest() {
   testEquipment(currentRow.value.equipmentId, testForm.value).then(() => {
     proxy.$modal.msgSuccess("测试记录已提交");
     testOpen.value = false;
-    if (detailOpen.value && detailForm.value?.equipmentInfo?.equipmentId === currentRow.value.equipmentId) {
-      handleView(currentRow.value);
-    }
+    handleView(currentRow.value);
   });
 }
 
@@ -557,3 +646,145 @@ onMounted(() => {
   Promise.all([getList(), loadAssistData()]);
 });
 </script>
+
+<style scoped>
+.equipment-page {
+  background: #f5f8fc;
+}
+
+.summary-grid {
+  display: grid;
+  grid-template-columns: repeat(4, minmax(0, 1fr));
+  gap: 16px;
+  margin-bottom: 16px;
+}
+
+.summary-card {
+  border: none;
+  color: #fff;
+}
+
+.summary-card :deep(.el-card__body) {
+  padding: 20px;
+}
+
+.summary-card.primary {
+  background: linear-gradient(135deg, #2563eb, #5b93ff);
+}
+
+.summary-card.success {
+  background: linear-gradient(135deg, #059669, #34d399);
+}
+
+.summary-card.warning {
+  background: linear-gradient(135deg, #d97706, #f7b955);
+}
+
+.summary-card.info {
+  background: linear-gradient(135deg, #7c3aed, #9d72ff);
+}
+
+.summary-label {
+  font-size: 14px;
+  opacity: 0.9;
+}
+
+.summary-value {
+  margin: 8px 0 10px;
+  font-size: 30px;
+  font-weight: 700;
+  line-height: 1;
+}
+
+.summary-desc {
+  font-size: 12px;
+  opacity: 0.9;
+}
+
+.query-card,
+.table-card,
+.detail-card {
+  border-radius: 16px;
+}
+
+.query-card {
+  margin-bottom: 16px;
+}
+
+.card-header-row {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  gap: 12px;
+}
+
+.card-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #1f2937;
+}
+
+.card-subtitle {
+  margin-top: 4px;
+  font-size: 12px;
+  color: #6b7280;
+}
+
+.card-actions {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  flex-wrap: wrap;
+}
+
+.detail-hero {
+  margin-bottom: 16px;
+  padding: 16px;
+  border-radius: 14px;
+  background: linear-gradient(135deg, #eff6ff, #f8fbff);
+  border: 1px solid #dbeafe;
+}
+
+.hero-title {
+  font-size: 18px;
+  font-weight: 700;
+  color: #1d4ed8;
+}
+
+.hero-meta {
+  display: flex;
+  gap: 10px;
+  flex-wrap: wrap;
+  margin-top: 8px;
+  font-size: 12px;
+  color: #4b5563;
+}
+
+.section-block {
+  margin-top: 16px;
+}
+
+.section-title {
+  margin-bottom: 10px;
+  font-size: 15px;
+  font-weight: 600;
+  color: #1f2937;
+}
+
+@media (max-width: 1200px) {
+  .summary-grid {
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+  }
+}
+
+@media (max-width: 768px) {
+  .summary-grid {
+    grid-template-columns: 1fr;
+  }
+
+  .card-header-row {
+    flex-direction: column;
+    align-items: flex-start;
+  }
+}
+</style>

+ 519 - 170
src/views/subSystem/pipeNetwork/pdeviceInform/PkeyPoints.vue

@@ -1,115 +1,257 @@
 <!-- 关键监控点位管理 -->
 <template>
-  <div class="app-container">
-    <el-form ref="queryRef" :model="queryParams" :inline="true" v-show="showSearch">
-      <el-form-item label="监测点编码" prop="pointCode">
-        <el-input
-          v-model="queryParams.pointCode"
-          placeholder="请输入监测点编码"
-          clearable
-          style="width: 220px"
-          @keyup.enter="handleQuery"
-        />
-      </el-form-item>
-      <el-form-item label="监测点名称" prop="pointName">
-        <el-input
-          v-model="queryParams.pointName"
-          placeholder="请输入监测点名称"
-          clearable
-          style="width: 220px"
-          @keyup.enter="handleQuery"
-        />
-      </el-form-item>
-      <el-form-item label="监测类型" prop="pointType">
-        <el-select
-          v-model="queryParams.pointType"
-          placeholder="请选择监测类型"
-          clearable
-          filterable
-          allow-create
-          default-first-option
-          style="width: 220px"
-        >
-          <el-option
-            v-for="item in pointTypeOptions"
-            :key="item"
-            :label="item"
-            :value="item"
+  <div class="app-container point-page">
+    <div class="summary-grid">
+      <el-card shadow="hover" class="summary-card primary">
+        <div class="summary-label">点位总数</div>
+        <div class="summary-value">{{ summary.totalPoints }}</div>
+        <div class="summary-desc">平台已接入关键监控点位</div>
+      </el-card>
+      <el-card shadow="hover" class="summary-card success">
+        <div class="summary-label">正常点位</div>
+        <div class="summary-value">{{ summary.normalPoints }}</div>
+        <div class="summary-desc">状态为正常或启用的点位</div>
+      </el-card>
+      <el-card shadow="hover" class="summary-card warning">
+        <div class="summary-label">异常点位</div>
+        <div class="summary-value">{{ summary.warningPoints }}</div>
+        <div class="summary-desc">状态需关注的关键监控点</div>
+      </el-card>
+      <el-card shadow="hover" class="summary-card info">
+        <div class="summary-label">已绑设备</div>
+        <div class="summary-value">{{ summary.boundEquipments }}</div>
+        <div class="summary-desc">监控点位已关联感知设备数量</div>
+      </el-card>
+    </div>
+
+    <el-card shadow="never" class="query-card">
+      <el-form ref="queryRef" :model="queryParams" :inline="true" v-show="showSearch">
+        <el-form-item label="监测点编码" prop="pointCode">
+          <el-input
+            v-model="queryParams.pointCode"
+            placeholder="请输入监测点编码"
+            clearable
+            style="width: 220px"
+            @keyup.enter="handleQuery"
           />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="状态" prop="status">
-        <el-select
-          v-model="queryParams.status"
-          placeholder="请选择状态"
-          clearable
-          filterable
-          allow-create
-          default-first-option
-          style="width: 180px"
-        >
-          <el-option
-            v-for="item in statusOptions"
-            :key="item"
-            :label="item"
-            :value="item"
+        </el-form-item>
+        <el-form-item label="监测点名称" prop="pointName">
+          <el-input
+            v-model="queryParams.pointName"
+            placeholder="请输入监测点名称"
+            clearable
+            style="width: 220px"
+            @keyup.enter="handleQuery"
           />
-        </el-select>
-      </el-form-item>
-      <el-form-item>
-        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-      </el-form-item>
-    </el-form>
-
-    <el-row :gutter="10" class="mb8">
-      <el-col :span="1.5">
-        <el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">
-          修改
-        </el-button>
+        </el-form-item>
+        <el-form-item label="监测类型" prop="pointType">
+          <el-select
+            v-model="queryParams.pointType"
+            placeholder="请选择监测类型"
+            clearable
+            filterable
+            allow-create
+            default-first-option
+            style="width: 220px"
+          >
+            <el-option v-for="item in pointTypeOptions" :key="item" :label="item" :value="item" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="状态" prop="status">
+          <el-select
+            v-model="queryParams.status"
+            placeholder="请选择状态"
+            clearable
+            filterable
+            allow-create
+            default-first-option
+            style="width: 180px"
+          >
+            <el-option v-for="item in statusOptions" :key="item" :label="item" :value="item" />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+          <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
+
+    <el-row :gutter="16" class="main-row">
+      <el-col :xs="24" :lg="10">
+        <el-card shadow="never" class="list-card">
+          <template #header>
+            <div class="card-header-row">
+              <div>
+                <div class="card-title">点位清单</div>
+                <div class="card-subtitle">关键监控点位的查询、维护与定位管理</div>
+              </div>
+              <div class="card-actions">
+                <el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
+                <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
+                <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">删除</el-button>
+                <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
+              </div>
+            </div>
+          </template>
+
+          <el-table
+            v-loading="loading"
+            :data="pointList"
+            highlight-current-row
+            @selection-change="handleSelectionChange"
+            @current-change="handleCurrentChange"
+            @row-click="handleRowClick"
+          >
+            <el-table-column type="selection" width="55" align="center" />
+            <el-table-column label="监测点名称" prop="pointName" min-width="170" show-overflow-tooltip />
+            <el-table-column label="监测类型" prop="pointType" min-width="110" />
+            <el-table-column label="状态" width="90">
+              <template #default="scope">
+                <el-tag :type="getStatusTagType(scope.row.status)" size="small">
+                  {{ scope.row.status || "-" }}
+                </el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column label="设施" min-width="140" show-overflow-tooltip>
+              <template #default="scope">
+                {{ networkNameMap[scope.row.networkId] || "-" }}
+              </template>
+            </el-table-column>
+            <el-table-column label="操作" align="center" width="150">
+              <template #default="scope">
+                <el-button link type="primary" icon="Connection" @click.stop="openBindDialog(scope.row)">
+                  关联设备
+                </el-button>
+                <el-button link type="primary" icon="Edit" @click.stop="handleUpdate(scope.row)">修改</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+
+          <pagination
+            v-show="total > 0"
+            :total="total"
+            v-model:page="queryParams.pageNum"
+            v-model:limit="queryParams.pageSize"
+            @pagination="getList"
+          />
+        </el-card>
       </el-col>
-      <el-col :span="1.5">
-        <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
-          删除
-        </el-button>
+
+      <el-col :xs="24" :lg="14">
+        <el-card shadow="never" class="detail-card">
+          <template #header>
+            <div class="card-header-row">
+              <div>
+                <div class="card-title">点位详情与关联设备</div>
+                <div class="card-subtitle">突出点位基础信息、所属设施和已绑定感知设备</div>
+              </div>
+              <div class="detail-top-actions" v-if="selectedPoint.pointInfo">
+                <el-button link type="primary" icon="Connection" @click="openBindDialog(selectedPoint.pointInfo)">
+                  设备关联
+                </el-button>
+                <el-button link type="primary" icon="Edit" @click="handleUpdate(selectedPoint.pointInfo)">编辑</el-button>
+              </div>
+            </div>
+          </template>
+
+          <el-empty v-if="!selectedPoint.pointInfo" description="请从左侧选择一个关键监控点位" />
+
+          <template v-else>
+            <div class="point-hero">
+              <div class="hero-main">
+                <div class="hero-title">{{ selectedPoint.pointInfo.pointName }}</div>
+                <div class="hero-code">{{ selectedPoint.pointInfo.pointCode }}</div>
+              </div>
+              <div class="hero-tags">
+                <el-tag>{{ selectedPoint.pointInfo.pointType || "未分类" }}</el-tag>
+                <el-tag :type="getStatusTagType(selectedPoint.pointInfo.status)">
+                  {{ selectedPoint.pointInfo.status || "未设置状态" }}
+                </el-tag>
+              </div>
+            </div>
+
+            <div class="detail-panels">
+              <el-card shadow="never" class="mini-panel">
+                <template #header>
+                  <div class="mini-panel-title">点位基础信息</div>
+                </template>
+                <el-descriptions :column="1" border size="small">
+                  <el-descriptions-item label="关联设施">
+                    {{ selectedPoint.networkInfo?.networkName || "-" }}
+                  </el-descriptions-item>
+                  <el-descriptions-item label="位置描述">
+                    {{ selectedPoint.pointInfo.location || "-" }}
+                  </el-descriptions-item>
+                  <el-descriptions-item label="安装日期">
+                    {{ selectedPoint.pointInfo.installationDate || "-" }}
+                  </el-descriptions-item>
+                  <el-descriptions-item label="经纬度">
+                    {{ selectedPoint.pointInfo.longitude ?? "-" }} / {{ selectedPoint.pointInfo.latitude ?? "-" }}
+                  </el-descriptions-item>
+                  <el-descriptions-item label="备注">
+                    {{ selectedPoint.pointInfo.remark || "-" }}
+                  </el-descriptions-item>
+                </el-descriptions>
+              </el-card>
+
+              <el-card shadow="never" class="mini-panel">
+                <template #header>
+                  <div class="mini-panel-title">所属设施概况</div>
+                </template>
+                <el-descriptions :column="1" border size="small">
+                  <el-descriptions-item label="设施名称">
+                    {{ selectedPoint.networkInfo?.networkName || "-" }}
+                  </el-descriptions-item>
+                  <el-descriptions-item label="设施编码">
+                    {{ selectedPoint.networkInfo?.networkCode || "-" }}
+                  </el-descriptions-item>
+                  <el-descriptions-item label="设施类型">
+                    {{ selectedPoint.networkInfo?.networkType || "-" }}
+                  </el-descriptions-item>
+                  <el-descriptions-item label="管养单位">
+                    {{ selectedPoint.networkInfo?.managementUnit || "-" }}
+                  </el-descriptions-item>
+                  <el-descriptions-item label="设施状态">
+                    {{ selectedPoint.networkInfo?.status || "-" }}
+                  </el-descriptions-item>
+                </el-descriptions>
+              </el-card>
+            </div>
+
+            <el-card shadow="never" class="equipment-panel">
+              <template #header>
+                <div class="card-header-row">
+                  <div>
+                    <div class="mini-panel-title">关联监测设备</div>
+                    <div class="panel-tip">
+                      当前共 {{ selectedEquipmentList.length }} 台设备,支持在右上角进行绑定和解绑
+                    </div>
+                  </div>
+                  <el-button type="primary" plain icon="Connection" @click="openBindDialog(selectedPoint.pointInfo)">
+                    关联设备
+                  </el-button>
+                </div>
+              </template>
+
+              <el-table :data="selectedEquipmentList" border size="small" max-height="320">
+                <el-table-column label="设备编码" prop="equipmentCode" min-width="140" />
+                <el-table-column label="设备名称" prop="equipmentName" min-width="160" show-overflow-tooltip />
+                <el-table-column label="设备型号" prop="equipmentModel" min-width="120" />
+                <el-table-column label="制造商" prop="manufacturer" min-width="120" show-overflow-tooltip />
+                <el-table-column label="设备位置" prop="equipmentLocation" min-width="160" show-overflow-tooltip />
+                <el-table-column label="操作" width="90" align="center">
+                  <template #default="scope">
+                    <el-button link type="danger" icon="Close" @click="handleUnbind(scope.row)">解绑</el-button>
+                  </template>
+                </el-table-column>
+              </el-table>
+            </el-card>
+          </template>
+        </el-card>
       </el-col>
-      <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
     </el-row>
 
-    <el-table v-loading="loading" :data="pointList" @selection-change="handleSelectionChange">
-      <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="监测点编码" align="center" prop="pointCode" min-width="140" />
-      <el-table-column label="监测点名称" align="center" prop="pointName" min-width="180" show-overflow-tooltip />
-      <el-table-column label="关联设施" align="center" min-width="160" show-overflow-tooltip>
-        <template #default="scope">
-          {{ networkNameMap[scope.row.networkId] || scope.row.networkId || "-" }}
-        </template>
-      </el-table-column>
-      <el-table-column label="监测类型" align="center" prop="pointType" min-width="120" />
-      <el-table-column label="位置描述" align="center" prop="location" min-width="180" show-overflow-tooltip />
-      <el-table-column label="状态" align="center" prop="status" width="100" />
-      <el-table-column label="安装日期" align="center" prop="installationDate" width="120" />
-      <el-table-column label="操作" align="center" width="280" class-name="small-padding fixed-width">
-        <template #default="scope">
-          <el-button link type="primary" icon="View" @click="handleView(scope.row)">详情</el-button>
-          <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
-          <el-button link type="warning" icon="Connection" @click="openBindDialog(scope.row)">关联设备</el-button>
-          <el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-
-    <pagination
-      v-show="total > 0"
-      :total="total"
-      v-model:page="queryParams.pageNum"
-      v-model:limit="queryParams.pageSize"
-      @pagination="getList"
-    />
-
     <el-dialog :title="title" v-model="open" width="780px" append-to-body>
       <el-form ref="pointRef" :model="form" :rules="rules" label-width="110px">
         <el-row :gutter="16">
@@ -145,12 +287,7 @@
                 default-first-option
                 style="width: 100%"
               >
-                <el-option
-                  v-for="item in pointTypeOptions"
-                  :key="item"
-                  :label="item"
-                  :value="item"
-                />
+                <el-option v-for="item in pointTypeOptions" :key="item" :label="item" :value="item" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -164,12 +301,7 @@
                 default-first-option
                 style="width: 100%"
               >
-                <el-option
-                  v-for="item in statusOptions"
-                  :key="item"
-                  :label="item"
-                  :value="item"
-                />
+                <el-option v-for="item in statusOptions" :key="item" :label="item" :value="item" />
               </el-select>
             </el-form-item>
           </el-col>
@@ -214,32 +346,6 @@
       </template>
     </el-dialog>
 
-    <el-dialog title="监测点详情" v-model="detailOpen" width="920px" append-to-body>
-      <el-descriptions :column="2" border v-if="detailForm.pointInfo">
-        <el-descriptions-item label="监测点编码">{{ detailForm.pointInfo.pointCode || "-" }}</el-descriptions-item>
-        <el-descriptions-item label="监测点名称">{{ detailForm.pointInfo.pointName || "-" }}</el-descriptions-item>
-        <el-descriptions-item label="监测类型">{{ detailForm.pointInfo.pointType || "-" }}</el-descriptions-item>
-        <el-descriptions-item label="状态">{{ detailForm.pointInfo.status || "-" }}</el-descriptions-item>
-        <el-descriptions-item label="安装日期">{{ detailForm.pointInfo.installationDate || "-" }}</el-descriptions-item>
-        <el-descriptions-item label="关联设施">
-          {{ detailForm.networkInfo?.networkName || detailForm.pointInfo.networkId || "-" }}
-        </el-descriptions-item>
-        <el-descriptions-item label="经度">{{ detailForm.pointInfo.longitude ?? "-" }}</el-descriptions-item>
-        <el-descriptions-item label="纬度">{{ detailForm.pointInfo.latitude ?? "-" }}</el-descriptions-item>
-        <el-descriptions-item label="位置描述" :span="2">{{ detailForm.pointInfo.location || "-" }}</el-descriptions-item>
-        <el-descriptions-item label="备注" :span="2">{{ detailForm.pointInfo.remark || "-" }}</el-descriptions-item>
-      </el-descriptions>
-
-      <el-divider content-position="left">关联设备</el-divider>
-      <el-table :data="detailForm.equipmentList || []" border>
-        <el-table-column label="设备编码" prop="equipmentCode" min-width="140" />
-        <el-table-column label="设备名称" prop="equipmentName" min-width="180" show-overflow-tooltip />
-        <el-table-column label="设备型号" prop="equipmentModel" min-width="120" />
-        <el-table-column label="制造商" prop="manufacturer" min-width="140" show-overflow-tooltip />
-        <el-table-column label="设备位置" prop="equipmentLocation" min-width="180" show-overflow-tooltip />
-      </el-table>
-    </el-dialog>
-
     <el-dialog title="关联监测设备" v-model="bindOpen" width="860px" append-to-body>
       <el-form label-width="90px">
         <el-form-item label="当前点位">
@@ -263,7 +369,7 @@
         </el-form-item>
       </el-form>
 
-      <div class="mb12">
+      <div class="bind-toolbar">
         <el-button type="primary" @click="submitBind">添加关联</el-button>
       </div>
 
@@ -293,6 +399,7 @@ import {
   getMonitorPointDetail,
   listEquipment,
   listMonitorPoint,
+  listMonitorPointAll,
   listPipeNetworkAll,
   unbindPointEquipment,
   updateMonitorPoint
@@ -305,7 +412,6 @@ const networkOptions = ref([]);
 const equipmentOptions = ref([]);
 const loading = ref(false);
 const open = ref(false);
-const detailOpen = ref(false);
 const bindOpen = ref(false);
 const showSearch = ref(true);
 const ids = ref([]);
@@ -318,6 +424,13 @@ const currentRow = ref({});
 const pointTypeOptions = ["水质监测", "空气质量", "压力监测", "流量监测", "温度监测", "泄漏检测"];
 const statusOptions = ["正常", "启用", "停用", "维修中", "预警"];
 
+const summary = reactive({
+  totalPoints: 0,
+  normalPoints: 0,
+  warningPoints: 0,
+  boundEquipments: 0
+});
+
 const networkNameMap = computed(() => {
   return networkOptions.value.reduce((acc, item) => {
     acc[item.networkId] = item.networkName;
@@ -335,7 +448,7 @@ const data = reactive({
     status: undefined
   },
   form: {},
-  detailForm: {},
+  selectedPoint: {},
   bindDetail: {},
   bindForm: {
     equipmentId: undefined
@@ -347,7 +460,11 @@ const data = reactive({
   }
 });
 
-const { queryParams, form, detailForm, bindDetail, bindForm, rules } = toRefs(data);
+const { queryParams, form, selectedPoint, bindDetail, bindForm, rules } = toRefs(data);
+
+const selectedEquipmentList = computed(() => {
+  return selectedPoint.value.equipmentList || [];
+});
 
 const unboundEquipmentOptions = computed(() => {
   const boundIds = new Set((bindDetail.value.equipmentList || []).map((item) => item.equipmentId));
@@ -370,12 +487,32 @@ function createEmptyForm() {
   };
 }
 
+function getStatusTagType(status) {
+  if (["正常", "启用"].includes(status)) {
+    return "success";
+  }
+  if (status === "预警") {
+    return "warning";
+  }
+  if (["停用", "禁用"].includes(status)) {
+    return "info";
+  }
+  if (["故障", "异常"].includes(status)) {
+    return "danger";
+  }
+  return "";
+}
+
 function getList() {
   loading.value = true;
-  listMonitorPoint(queryParams.value)
+  return listMonitorPoint(queryParams.value)
     .then((response) => {
       pointList.value = response.records || [];
       total.value = response.total || 0;
+      if (!selectedPoint.value.pointInfo && pointList.value.length) {
+        return selectPoint(pointList.value[0]);
+      }
+      return undefined;
     })
     .finally(() => {
       loading.value = false;
@@ -388,10 +525,15 @@ function loadAssistData() {
     listEquipment({
       pageNum: 1,
       pageSize: 1000
-    })
-  ]).then(([networkRes, equipmentRes]) => {
+    }),
+    listMonitorPointAll()
+  ]).then(([networkRes, equipmentRes, pointRes]) => {
     networkOptions.value = networkRes.data || [];
     equipmentOptions.value = equipmentRes.records || [];
+    const allPoints = pointRes.data || [];
+    summary.totalPoints = allPoints.length;
+    summary.normalPoints = allPoints.filter((item) => ["正常", "启用"].includes(item.status)).length;
+    summary.warningPoints = allPoints.filter((item) => !["正常", "启用"].includes(item.status)).length;
   });
 }
 
@@ -401,6 +543,28 @@ function loadPointDetail(pointId, targetRef) {
   });
 }
 
+function refreshBoundCount() {
+  return listMonitorPoint({
+    pageNum: 1,
+    pageSize: 1000
+  }).then((response) => {
+    const records = response.records || [];
+    const detailRequests = records.map((item) => getMonitorPointDetail(item.pointId));
+    return Promise.all(detailRequests).then((detailResponses) => {
+      summary.boundEquipments = detailResponses.reduce((count, item) => {
+        return count + ((item.data?.equipmentList || []).length || 0);
+      }, 0);
+    });
+  });
+}
+
+function selectPoint(row) {
+  if (!row?.pointId) {
+    return;
+  }
+  return loadPointDetail(row.pointId, selectedPoint);
+}
+
 function reset() {
   form.value = createEmptyForm();
   proxy.resetForm("pointRef");
@@ -422,6 +586,16 @@ function handleSelectionChange(selection) {
   multiple.value = !selection.length;
 }
 
+function handleCurrentChange(currentRow) {
+  if (currentRow?.pointId) {
+    selectPoint(currentRow);
+  }
+}
+
+function handleRowClick(row) {
+  selectPoint(row);
+}
+
 function handleAdd() {
   reset();
   open.value = true;
@@ -430,7 +604,7 @@ function handleAdd() {
 
 function handleUpdate(row) {
   reset();
-  const pointId = row?.pointId || ids.value[0];
+  const pointId = row?.pointId || row?.pointInfo?.pointId || ids.value[0];
   if (!pointId) {
     return;
   }
@@ -441,12 +615,6 @@ function handleUpdate(row) {
   });
 }
 
-function handleView(row) {
-  loadPointDetail(row.pointId, detailForm).then(() => {
-    detailOpen.value = true;
-  });
-}
-
 function cancel() {
   open.value = false;
   reset();
@@ -461,7 +629,11 @@ function submitForm() {
     request.then(() => {
       proxy.$modal.msgSuccess(form.value.pointId ? "修改成功" : "新增成功");
       open.value = false;
-      getList();
+      Promise.all([getList(), loadAssistData(), refreshBoundCount()]).then(() => {
+        if (form.value.pointId) {
+          selectPoint({ pointId: form.value.pointId });
+        }
+      });
     });
   });
 }
@@ -476,14 +648,21 @@ function handleDelete(row) {
     .then(() => Promise.all(deleteIds.map((id) => delMonitorPoint(id))))
     .then(() => {
       proxy.$modal.msgSuccess("删除成功");
-      getList();
+      if (deleteIds.includes(selectedPoint.value?.pointInfo?.pointId)) {
+        selectedPoint.value = {};
+      }
+      return Promise.all([getList(), loadAssistData(), refreshBoundCount()]);
     });
 }
 
 function openBindDialog(row) {
-  currentRow.value = row;
+  const pointId = row?.pointId || row?.pointInfo?.pointId;
+  if (!pointId) {
+    return;
+  }
+  currentRow.value = row.pointInfo ? row.pointInfo : row;
   bindForm.value.equipmentId = undefined;
-  loadPointDetail(row.pointId, bindDetail).then(() => {
+  loadPointDetail(pointId, bindDetail).then(() => {
     bindOpen.value = true;
   });
 }
@@ -496,28 +675,198 @@ function submitBind() {
   bindPointEquipment(currentRow.value.pointId, [bindForm.value.equipmentId]).then(() => {
     proxy.$modal.msgSuccess("关联成功");
     bindForm.value.equipmentId = undefined;
-    loadPointDetail(currentRow.value.pointId, bindDetail);
-    if (detailOpen.value && detailForm.value?.pointInfo?.pointId === currentRow.value.pointId) {
-      loadPointDetail(currentRow.value.pointId, detailForm);
-    }
+    Promise.all([
+      loadPointDetail(currentRow.value.pointId, bindDetail),
+      loadPointDetail(currentRow.value.pointId, selectedPoint),
+      refreshBoundCount()
+    ]);
   });
 }
 
 function handleUnbind(row) {
+  const pointId = currentRow.value.pointId || selectedPoint.value?.pointInfo?.pointId;
+  if (!pointId) {
+    return;
+  }
   proxy.$modal
     .confirm(`是否确认解绑设备“${row.equipmentName}”?`)
-    .then(() => unbindPointEquipment(currentRow.value.pointId, [row.equipmentId]))
+    .then(() => unbindPointEquipment(pointId, [row.equipmentId]))
     .then(() => {
       proxy.$modal.msgSuccess("解绑成功");
-      loadPointDetail(currentRow.value.pointId, bindDetail);
-      if (detailOpen.value && detailForm.value?.pointInfo?.pointId === currentRow.value.pointId) {
-        loadPointDetail(currentRow.value.pointId, detailForm);
-      }
+      return Promise.all([
+        loadPointDetail(pointId, bindDetail),
+        loadPointDetail(pointId, selectedPoint),
+        refreshBoundCount()
+      ]);
     });
 }
 
 onMounted(() => {
   reset();
-  Promise.all([getList(), loadAssistData()]);
+  Promise.all([getList(), loadAssistData(), refreshBoundCount()]);
 });
 </script>
+
+<style scoped>
+.point-page {
+  background: #f5f8fc;
+}
+
+.summary-grid {
+  display: grid;
+  grid-template-columns: repeat(4, minmax(0, 1fr));
+  gap: 16px;
+  margin-bottom: 16px;
+}
+
+.summary-card {
+  border: none;
+  color: #fff;
+}
+
+.summary-card :deep(.el-card__body) {
+  padding: 20px;
+}
+
+.summary-card.primary {
+  background: linear-gradient(135deg, #2563eb, #5b93ff);
+}
+
+.summary-card.success {
+  background: linear-gradient(135deg, #059669, #34d399);
+}
+
+.summary-card.warning {
+  background: linear-gradient(135deg, #d97706, #f7b955);
+}
+
+.summary-card.info {
+  background: linear-gradient(135deg, #7c3aed, #9d72ff);
+}
+
+.summary-label {
+  font-size: 14px;
+  opacity: 0.9;
+}
+
+.summary-value {
+  margin: 8px 0 10px;
+  font-size: 30px;
+  font-weight: 700;
+  line-height: 1;
+}
+
+.summary-desc {
+  font-size: 12px;
+  opacity: 0.9;
+}
+
+.query-card,
+.list-card,
+.detail-card {
+  border-radius: 16px;
+}
+
+.query-card {
+  margin-bottom: 16px;
+}
+
+.card-header-row {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  gap: 12px;
+}
+
+.card-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #1f2937;
+}
+
+.card-subtitle,
+.panel-tip {
+  margin-top: 4px;
+  font-size: 12px;
+  color: #6b7280;
+}
+
+.card-actions,
+.detail-top-actions {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  flex-wrap: wrap;
+}
+
+.point-hero {
+  display: flex;
+  justify-content: space-between;
+  gap: 12px;
+  padding: 16px;
+  border-radius: 14px;
+  background: linear-gradient(135deg, #eff6ff, #f8fbff);
+  border: 1px solid #dbeafe;
+  margin-bottom: 16px;
+}
+
+.hero-title {
+  font-size: 18px;
+  font-weight: 700;
+  color: #1d4ed8;
+}
+
+.hero-code {
+  margin-top: 8px;
+  font-size: 13px;
+  color: #4b5563;
+}
+
+.hero-tags {
+  display: flex;
+  gap: 8px;
+  flex-wrap: wrap;
+  align-items: flex-start;
+}
+
+.detail-panels {
+  display: grid;
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+  gap: 16px;
+  margin-bottom: 16px;
+}
+
+.mini-panel,
+.equipment-panel {
+  border-radius: 14px;
+}
+
+.mini-panel-title {
+  font-size: 15px;
+  font-weight: 600;
+  color: #1f2937;
+}
+
+.bind-toolbar {
+  margin-bottom: 12px;
+}
+
+@media (max-width: 1200px) {
+  .summary-grid {
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+  }
+}
+
+@media (max-width: 768px) {
+  .summary-grid,
+  .detail-panels {
+    grid-template-columns: 1fr;
+  }
+
+  .card-header-row,
+  .point-hero {
+    flex-direction: column;
+    align-items: flex-start;
+  }
+}
+</style>

+ 422 - 774
src/views/subSystem/pipeNetwork/pinteControl/PrealTimeShow.vue

@@ -1,599 +1,440 @@
-<!--感知信息实时展示-->
+<!-- 感知信息实时展示 -->
 <template>
-  <div class="prototype-container">
-    <!-- 头部 -->
-    <div class="header">
-      <div class="header-stats">
-        <div class="stat-badge" :class="{ warning: alarmCount > 0 }">
-          <span class="stat-icon">⚠️</span>
-          <span class="stat-value">{{ alarmCount }}</span>
-          <span class="stat-label">报警中</span>
-        </div>
-        <div class="stat-badge">
-          <span class="stat-icon">✅</span>
-          <span class="stat-value">{{ normalCount }}</span>
-          <span class="stat-label">正常设备</span>
-        </div>
-        <div class="stat-badge">
-          <span class="stat-icon">🔴</span>
-          <span class="stat-value">{{ faultCount }}</span>
-          <span class="stat-label">故障设备</span>
-        </div>
+  <div class="app-container realtime-page" v-loading="loading">
+    <div class="hero-panel">
+      <div>
+        <div class="hero-title">感知信息实时展示</div>
+        <div class="hero-subtitle">展示传感设备名称、位置、工作状态及故障关注信息,并同步呈现人机交互记录</div>
       </div>
+      <el-button type="primary" icon="Refresh" @click="loadDashboard">刷新</el-button>
     </div>
 
-    <div class="main-layout">
-      <!-- 左侧:设备监控面板(图元+动画) -->
-      <div class="left-panel">
-        <div class="panel-title">
-          📡 传感设备实时监控
-          <span class="live-badge">● 实时动态</span>
-        </div>
-        <div class="device-grid">
-          <div
-              v-for="device in devices"
-              :key="device.id"
-              class="device-card"
-              :class="{
-              'status-normal': device.status === 'normal',
-              'status-warning': device.status === 'warning',
-              'status-fault': device.status === 'fault',
-              'status-alarm': device.status === 'alarm'
-            }"
-              @click="showDeviceDetail(device)"
-          >
-            <div class="device-icon" :class="{ 'pulse-animation': device.status === 'alarm' || device.status === 'fault' }">
-              <span v-if="device.type === 'pressure'">📊</span>
-              <span v-else-if="device.type === 'flow'">💧</span>
-              <span v-else-if="device.type === 'gas'">🔥</span>
-              <span v-else-if="device.type === 'temp'">🌡️</span>
-              <span v-else>📟</span>
-            </div>
-            <div class="device-info">
-              <div class="device-name">{{ device.name }}</div>
-              <div class="device-location">{{ device.location }}</div>
-              <div class="device-status">
-                <span class="status-dot" :class="getStatusDotClass(device.status)"></span>
-                {{ getStatusText(device.status) }}
+    <div class="summary-grid">
+      <el-card shadow="hover" class="summary-card danger">
+        <div class="summary-label">报警关注</div>
+        <div class="summary-value">{{ alarmCount }}</div>
+        <div class="summary-desc">预警、维修、停用等需值班关注的对象</div>
+      </el-card>
+      <el-card shadow="hover" class="summary-card success">
+        <div class="summary-label">正常监测点</div>
+        <div class="summary-value">{{ normalCount }}</div>
+        <div class="summary-desc">状态为正常或启用的点位</div>
+      </el-card>
+      <el-card shadow="hover" class="summary-card primary">
+        <div class="summary-label">感知设备</div>
+        <div class="summary-value">{{ equipmentCount }}</div>
+        <div class="summary-desc">通过点位详情聚合出的关联设备总量</div>
+      </el-card>
+      <el-card shadow="hover" class="summary-card info">
+        <div class="summary-label">设施覆盖</div>
+        <div class="summary-value">{{ networkOptions.length }}</div>
+        <div class="summary-desc">已纳入实时展示的管网设施数量</div>
+      </el-card>
+    </div>
+
+    <el-row :gutter="16">
+      <el-col :xs="24" :lg="16">
+        <el-card shadow="never" class="main-card">
+          <template #header>
+            <div class="card-header-row">
+              <div>
+                <div class="card-title">设备信息实时展示</div>
+                <div class="card-subtitle">以图元卡片方式查看监测点及所挂接感知设备的实时分布</div>
               </div>
-              <div class="device-value" v-if="device.value !== undefined">
-                {{ device.value }} {{ device.unit }}
+              <div class="filter-group">
+                <el-select v-model="filters.networkId" clearable placeholder="按设施筛选" style="width: 200px">
+                  <el-option
+                    v-for="item in networkOptions"
+                    :key="item.networkId"
+                    :label="item.networkName"
+                    :value="item.networkId"
+                  />
+                </el-select>
+                <el-select v-model="filters.status" clearable placeholder="按状态筛选" style="width: 160px">
+                  <el-option v-for="item in statusOptions" :key="item" :label="item" :value="item" />
+                </el-select>
               </div>
             </div>
-            <div class="device-alarm" v-if="device.status === 'alarm' || device.status === 'fault'">
-              <div class="alarm-wave"></div>
-              <div class="alarm-wave"></div>
-              <div class="alarm-wave"></div>
-            </div>
-          </div>
-        </div>
-      </div>
+          </template>
 
-      <!-- 右侧:人机交互信息展示 -->
-      <div class="right-panel">
-        <!-- 操作记录 -->
-        <div class="info-card">
-          <div class="card-header">
-            <span>👤 操作员操作记录</span>
-            <span class="record-count">近24小时</span>
+          <div v-if="displayCards.length" class="device-grid">
+            <div
+              v-for="card in displayCards"
+              :key="card.pointId"
+              class="device-card"
+              :class="getCardClass(card.status)"
+              @click="openDetail(card)"
+            >
+              <div class="device-top">
+                <div class="device-icon" :class="{ pulse: isAlertStatus(card.status) }">
+                  {{ getPointEmoji(card.pointType) }}
+                </div>
+                <div class="device-info">
+                  <div class="device-name">{{ card.pointName }}</div>
+                  <div class="device-location">{{ card.location || "未维护位置" }}</div>
+                  <div class="device-network">{{ card.networkName || "未关联设施" }}</div>
+                </div>
+              </div>
+              <div class="device-status-line">
+                <span class="status-dot" :class="getStatusDotClass(card.status)"></span>
+                <span>{{ card.status || "状态未维护" }}</span>
+                <span class="device-count">设备 {{ card.equipmentCount }}</span>
+              </div>
+              <div class="device-meta">
+                <span>编码:{{ card.pointCode }}</span>
+                <span>类型:{{ card.pointType || "-" }}</span>
+              </div>
+            </div>
           </div>
+          <el-empty v-else description="暂无可展示的感知信息" />
+        </el-card>
+      </el-col>
+
+      <el-col :xs="24" :lg="8">
+        <el-card shadow="never" class="side-card">
+          <template #header>
+            <div class="card-title">操作员操作记录</div>
+          </template>
           <div class="record-list">
-            <div v-for="record in operationRecords" :key="record.id" class="record-item">
-              <div class="record-time">{{ record.time }}</div>
+            <div v-for="item in operationRecords" :key="item.id" class="record-item">
+              <div class="record-time">{{ item.time }}</div>
               <div class="record-content">
-                <span class="record-operator">{{ record.operator }}</span>
-                <span class="record-action">{{ record.action }}</span>
-                <span class="record-target">{{ record.target }}</span>
+                <div class="record-action">{{ item.action }}</div>
+                <div class="record-target">{{ item.target }}</div>
               </div>
-              <div class="record-result" :class="record.result">{{ record.result === 'success' ? '✓ 成功' : '✗ 失败' }}</div>
             </div>
-            <div v-if="operationRecords.length === 0" class="empty-record">暂无操作记录</div>
           </div>
-        </div>
+        </el-card>
 
-        <!-- 应急处置信息切换标签 -->
-        <div class="info-card">
-          <div class="card-header tabs">
-            <button
-                class="tab-btn"
-                :class="{ active: activeEventTab === 'major' }"
-                @click="activeEventTab = 'major'"
-            >
-              🌊 重大灾害事件
-            </button>
-            <button
-                class="tab-btn"
-                :class="{ active: activeEventTab === 'pipeline' }"
-                @click="activeEventTab = 'pipeline'"
-            >
-              🛢️ 管网事件
-            </button>
-            <button
-                class="tab-btn"
-                :class="{ active: activeEventTab === 'daily' }"
-                @click="activeEventTab = 'daily'"
-            >
-              📋 日常事件
-            </button>
-          </div>
+        <el-card shadow="never" class="side-card">
+          <template #header>
+            <div class="card-title">人机交互信息</div>
+          </template>
           <div class="event-list">
-            <div v-for="event in currentEvents" :key="event.id" class="event-item" :class="event.level">
-              <div class="event-time">{{ event.time }}</div>
-              <div class="event-title">{{ event.title }}</div>
-              <div class="event-desc">{{ event.description }}</div>
-              <div class="event-handler">
-                <span class="handler-label">处置人:</span>
-                <span>{{ event.handler || '待分配' }}</span>
-                <span v-if="event.status" class="event-status" :class="event.status">{{ getEventStatusText(event.status) }}</span>
-              </div>
+            <div v-for="item in eventList" :key="item.id" class="event-item">
+              <div class="event-title">{{ item.title }}</div>
+              <div class="event-desc">{{ item.description }}</div>
+              <div class="event-meta">{{ item.time }} · {{ item.handler }}</div>
             </div>
-            <div v-if="currentEvents.length === 0" class="empty-record">暂无事件记录</div>
           </div>
-        </div>
+        </el-card>
 
-        <!-- 实时报警提示条 -->
-        <div v-if="activeAlarms.length > 0" class="alert-bar">
-          <div class="alert-icon">🔔</div>
+        <div v-if="activeAlarms.length" class="alert-bar">
+          <div class="alert-icon">!</div>
           <div class="alert-messages">
-            <div v-for="alarm in activeAlarms.slice(0, 3)" :key="alarm.id" class="alert-message">
-              {{ alarm.name }} - {{ alarm.message }}
-            </div>
+            <div v-for="item in activeAlarms.slice(0, 3)" :key="item.pointId">{{ item.message }}</div>
           </div>
-          <button class="alert-close" @click="clearAlarms">清除提醒</button>
         </div>
-      </div>
-    </div>
-
-    <!-- 设备详情模态框 -->
-    <div v-if="detailModalVisible" class="modal-overlay" @click.self="closeDetailModal">
-      <div class="modal-content">
-        <h3>📟 设备详细信息</h3>
-        <div class="detail-row"><span class="detail-label">设备名称:</span>{{ currentDevice?.name }}</div>
-        <div class="detail-row"><span class="detail-label">设备ID:</span>{{ currentDevice?.deviceId }}</div>
-        <div class="detail-row"><span class="detail-label">安装位置:</span>{{ currentDevice?.location }}</div>
-        <div class="detail-row"><span class="detail-label">设备类型:</span>{{ getDeviceTypeText(currentDevice?.type) }}</div>
-        <div class="detail-row"><span class="detail-label">当前读数:</span>{{ currentDevice?.value }} {{ currentDevice?.unit }}</div>
-        <div class="detail-row"><span class="detail-label">工作状态:</span>{{ getStatusText(currentDevice?.status) }}</div>
-        <div class="detail-row"><span class="detail-label">最后通信:</span>{{ currentDevice?.lastTime || '-' }}</div>
-        <div class="detail-row"><span class="detail-label">报警阈值:</span>{{ currentDevice?.alertThreshold || '-' }}</div>
-        <button @click="closeDetailModal" class="btn-primary" style="margin-top:16px;">关闭</button>
-      </div>
-    </div>
-
-    <!-- 音频提醒(隐藏audio元素) -->
-    <audio ref="alarmAudio" src="data:audio/wav;base64,U3RlYWx0aCBzb3VuZCBmYWtl" preload="auto"></audio>
+      </el-col>
+    </el-row>
+
+    <el-dialog title="实时展示详情" v-model="detailOpen" width="960px" append-to-body>
+      <el-descriptions :column="2" border v-if="detailData.pointInfo">
+        <el-descriptions-item label="监测点名称">{{ detailData.pointInfo.pointName || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="监测点编码">{{ detailData.pointInfo.pointCode || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="监测类型">{{ detailData.pointInfo.pointType || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="当前状态">{{ detailData.pointInfo.status || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="所属设施">{{ detailData.networkInfo?.networkName || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="位置描述">{{ detailData.pointInfo.location || "-" }}</el-descriptions-item>
+      </el-descriptions>
+
+      <el-divider content-position="left">关联设备</el-divider>
+      <el-table :data="detailData.equipmentList || []" border max-height="320">
+        <el-table-column label="设备编码" prop="equipmentCode" min-width="140" />
+        <el-table-column label="设备名称" prop="equipmentName" min-width="180" show-overflow-tooltip />
+        <el-table-column label="设备型号" prop="equipmentModel" min-width="120" />
+        <el-table-column label="制造商" prop="manufacturer" min-width="140" show-overflow-tooltip />
+        <el-table-column label="设备位置" prop="equipmentLocation" min-width="180" show-overflow-tooltip />
+      </el-table>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
-import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
-
-// ==================== 设备数据 ====================
-const devices = ref([
-  { id: 1, name: "城东压力传感器", deviceId: "PS-001", type: "pressure", location: "城东管网A段", value: 0.82, unit: "MPa", status: "normal", alertThreshold: ">1.2MPa", lastTime: "2025-05-08 14:23:12" },
-  { id: 2, name: "城南流量监测仪", deviceId: "FL-002", type: "flow", location: "城南泵站", value: 235, unit: "m³/h", status: "normal", alertThreshold: ">300m³/h", lastTime: "2025-05-08 14:22:45" },
-  { id: 3, name: "城西燃气探测器", deviceId: "GD-003", type: "gas", location: "城西调压站", value: 12.5, unit: "%LEL", status: "warning", alertThreshold: ">20%LEL", lastTime: "2025-05-08 14:21:30" },
-  { id: 4, name: "北环温度传感器", deviceId: "TP-004", type: "temp", location: "北环供热站", value: 85, unit: "℃", status: "normal", alertThreshold: ">95℃", lastTime: "2025-05-08 14:23:00" },
-  { id: 5, name: "化工园泄漏检测仪", deviceId: "LD-005", type: "gas", location: "化工园区", value: 35.2, unit: "%LEL", status: "alarm", alertThreshold: ">25%LEL", lastTime: "2025-05-08 14:20:15" },
-  { id: 6, name: "东区水位监测仪", deviceId: "WL-006", type: "flow", location: "东区排水口", value: 2.8, unit: "m", status: "fault", alertThreshold: ">3.5m", lastTime: "2025-05-08 13:50:22" },
-  { id: 7, name: "南站压力变送器", deviceId: "PS-007", type: "pressure", location: "南站加压站", value: 0.95, unit: "MPa", status: "normal", alertThreshold: ">1.1MPa", lastTime: "2025-05-08 14:22:18" },
-  { id: 8, name: "西区流量计", deviceId: "FL-008", type: "flow", location: "西区供水节点", value: 156, unit: "m³/h", status: "normal", alertThreshold: ">200m³/h", lastTime: "2025-05-08 14:21:55" }
-])
-
-// ==================== 操作记录数据 ====================
-const operationRecords = ref([
-  { id: 1, time: "14:23:15", operator: "张建国", action: "修改阈值", target: "城东压力传感器", result: "success" },
-  { id: 2, time: "14:20:32", operator: "李卫东", action: "远程重启", target: "东区水位监测仪", result: "failed" },
-  { id: 3, time: "14:15:08", operator: "王敏", action: "确认报警", target: "化工园泄漏检测仪", result: "success" },
-  { id: 4, time: "14:10:22", operator: "陈方", action: "查看日志", target: "系统日志", result: "success" },
-  { id: 5, time: "14:05:45", operator: "张建国", action: "下发指令", target: "北环温度传感器", result: "success" },
-  { id: 6, time: "13:58:30", operator: "李卫东", action: "导出报表", target: "月度运行报表", result: "success" }
-])
-
-// ==================== 应急处置事件数据 ====================
-const majorEvents = ref([
-  { id: 1, time: "2025-05-08 13:30:22", level: "critical", title: "化工园区泄漏事故", description: "化工园区燃气管道发生泄漏,浓度已达报警阈值,已启动应急预案。", handler: "张建国", status: "processing" },
-  { id: 2, time: "2025-05-07 09:15:33", level: "major", title: "城南暴雨内涝", description: "持续强降雨导致城南区域积水深度达50cm,已组织排水。", handler: "李卫东", status: "completed" },
-  { id: 3, time: "2025-05-06 16:45:12", level: "critical", title: "北环供热管道爆裂", description: "北环供热主管道突发爆裂,影响周边3个小区供暖。", handler: "王敏", status: "processing" }
-])
-
-const pipelineEvents = ref([
-  { id: 1, time: "2025-05-08 11:20:05", level: "warning", title: "城东管道压力异常", description: "城东管网压力波动超出正常范围,已触发预警。", handler: "陈方", status: "monitoring" },
-  { id: 2, time: "2025-05-08 09:45:30", level: "info", title: "城南阀门检修", description: "计划性检修,已通知相关用户。", handler: "赵工", status: "completed" },
-  { id: 3, time: "2025-05-07 15:30:22", level: "warning", title: "西区流量骤降", description: "西区供水流量突然下降30%,排查中。", handler: "孙伟", status: "processing" }
-])
-
-const dailyEvents = ref([
-  { id: 1, time: "2025-05-08 10:00:00", level: "info", title: "每日巡检完成", description: "已完成城东、城南、城西管网巡检,发现2处标识牌损坏。", handler: "巡检组", status: "completed" },
-  { id: 2, time: "2025-05-08 08:30:15", level: "info", title: "交接班记录", description: "夜班交接,设备运行正常,无异常事件。", handler: "交接班", status: "completed" },
-  { id: 3, time: "2025-05-07 17:00:00", level: "info", title: "数据备份完成", description: "系统数据自动备份成功,备份文件已归档。", handler: "系统", status: "completed" }
-])
-
-// 当前激活的事件标签
-const activeEventTab = ref('major')
-
-// 当前显示的事件列表
-const currentEvents = computed(() => {
-  switch (activeEventTab.value) {
-    case 'major': return majorEvents.value
-    case 'pipeline': return pipelineEvents.value
-    case 'daily': return dailyEvents.value
-    default: return majorEvents.value
-  }
-})
-
-// ==================== 辅助计算属性 ====================
-// 报警中的设备(status为alarm或fault)
-const alarmDevices = computed(() => devices.value.filter(d => d.status === 'alarm' || d.status === 'fault'))
-const alarmCount = computed(() => alarmDevices.value.length)
-const faultCount = computed(() => devices.value.filter(d => d.status === 'fault').length)
-const normalCount = computed(() => devices.value.filter(d => d.status === 'normal').length)
-
-// 活动报警消息
-const activeAlarms = computed(() => {
-  return alarmDevices.value.map(d => ({
-    id: d.id,
-    name: d.name,
-    message: d.status === 'alarm' ? `数值超出预警阈值 (当前: ${d.value} ${d.unit})` : '设备通信故障,需要立即检修'
-  }))
-})
-
-// ==================== 状态辅助函数 ====================
-const getStatusText = (status) => {
-  const map = { normal: '正常运行', warning: '需关注', fault: '故障', alarm: '告警中' }
-  return map[status] || status
-}
-
-const getStatusDotClass = (status) => {
-  const map = { normal: 'dot-normal', warning: 'dot-warning', fault: 'dot-fault', alarm: 'dot-alarm' }
-  return map[status] || 'dot-normal'
-}
-
-const getDeviceTypeText = (type) => {
-  const map = { pressure: '压力传感器', flow: '流量计', gas: '气体探测器', temp: '温度传感器' }
-  return map[type] || type
-}
+import { computed, onMounted, reactive, ref } from "vue";
+import { getMonitorPointDetail, listMonitorPointAll, listPipeNetworkAll } from "@/api/pipeNetwork/pdeviceInform";
 
-const getEventStatusText = (status) => {
-  const map = { processing: '处置中', completed: '已完成', monitoring: '监控中' }
-  return map[status] || status
-}
-
-// ==================== 模拟实时更新 & 报警 ====================
-let intervalId = null
-let lastAlarmCount = 0
-
-// 播放报警音效
-const alarmAudio = ref(null)
-const playAlarmSound = () => {
-  if (alarmAudio.value) {
-    // 由于浏览器策略,需要用户交互后才能播放
-    // 这里模拟提示
-    console.log('🔊 报警声音触发')
-  }
-}
-
-// 显示报警通知(模拟)
-const showAlarmNotification = (device) => {
-  // 模拟声音和动画效果
-  if (Notification.permission === 'granted') {
-    new Notification('设备告警', { body: `${device.name} ${device.status === 'alarm' ? '数值超标' : '设备故障'}` })
-  }
-}
-
-// 模拟设备数据波动
-const simulateDataFluctuation = () => {
-  devices.value = devices.value.map(device => {
-    const newDevice = { ...device }
-    // 随机波动
-    if (device.type === 'pressure') {
-      const change = (Math.random() - 0.5) * 0.1
-      newDevice.value = Math.max(0, Math.min(1.5, device.value + change))
-      newDevice.value = parseFloat(newDevice.value.toFixed(2))
-    } else if (device.type === 'flow') {
-      const change = (Math.random() - 0.5) * 20
-      newDevice.value = Math.max(0, Math.min(500, device.value + change))
-      newDevice.value = Math.floor(newDevice.value)
-    } else if (device.type === 'gas') {
-      const change = (Math.random() - 0.5) * 8
-      newDevice.value = Math.max(0, Math.min(100, device.value + change))
-      newDevice.value = parseFloat(newDevice.value.toFixed(1))
-    } else if (device.type === 'temp') {
-      const change = (Math.random() - 0.5) * 3
-      newDevice.value = Math.max(-20, Math.min(150, device.value + change))
-      newDevice.value = Math.floor(newDevice.value)
-    }
-
-    // 更新状态(基于阈值模拟)
-    if (newDevice.id === 5) {
-      // 泄漏检测仪保持报警状态
-      newDevice.status = newDevice.value > 25 ? 'alarm' : 'warning'
-    } else if (newDevice.id === 6) {
-      // 水位仪保持故障状态
-      newDevice.status = 'fault'
-    } else if (newDevice.id === 3) {
-      // 燃气探测器动态判断
-      newDevice.status = newDevice.value > 18 ? 'warning' : 'normal'
-    } else {
-      newDevice.status = 'normal'
-    }
-
-    newDevice.lastTime = new Date().toLocaleTimeString()
-    return newDevice
-  })
-
-  // 检查报警数量变化,触发声音
-  if (alarmCount.value > lastAlarmCount && alarmCount.value > 0) {
-    playAlarmSound()
-  }
-  lastAlarmCount = alarmCount.value
-}
+const loading = ref(false);
+const networkOptions = ref([]);
+const pointList = ref([]);
+const pointDetailMap = ref({});
+const detailOpen = ref(false);
+const detailData = ref({});
 
-// 实时更新操作记录(模拟)
-let recordIdCounter = 7
-const addRandomOperation = () => {
-  const operators = ['张建国', '李卫东', '王敏', '陈方']
-  const actions = ['查看设备', '修改参数', '确认报警', '下发指令', '查看日志']
-  const targets = ['城东压力传感器', '城南流量计', '化工园探测器', '系统配置']
-  const results = ['success', 'success', 'success', 'failed']
+const filters = reactive({
+  networkId: undefined,
+  status: undefined
+});
 
-  const newRecord = {
-    id: recordIdCounter++,
-    time: new Date().toLocaleTimeString(),
-    operator: operators[Math.floor(Math.random() * operators.length)],
-    action: actions[Math.floor(Math.random() * actions.length)],
-    target: targets[Math.floor(Math.random() * targets.length)],
-    result: results[Math.floor(Math.random() * results.length)]
-  }
-  operationRecords.value.unshift(newRecord)
-  if (operationRecords.value.length > 20) {
-    operationRecords.value.pop()
-  }
-}
-
-// ==================== 设备交互 ====================
-const detailModalVisible = ref(false)
-const currentDevice = ref(null)
+const operationRecords = ref([
+  { id: 1, time: "08:30:21", action: "值班员查看监测点详情", target: "化工园区关键点位" },
+  { id: 2, time: "09:15:47", action: "刷新感知信息展示", target: "管网设施全量数据" },
+  { id: 3, time: "10:02:12", action: "核查异常状态点位", target: "城南泵站压力监测点" },
+  { id: 4, time: "10:38:55", action: "确认日常巡检结果", target: "今日管网感知列表" }
+]);
+
+const eventList = ref([
+  { id: 1, title: "重大灾害事件", description: "重点关注异常点位运行情况,必要时联动应急处置。", time: "今日", handler: "中控值班" },
+  { id: 2, title: "管网事件", description: "对预警、维修中的监测点进行重点巡查与记录。", time: "实时", handler: "管网运维" },
+  { id: 3, title: "日常事件", description: "持续更新巡检、交接班和设备观察信息。", time: "本班次", handler: "值班班组" }
+]);
+
+const statusOptions = computed(() => {
+  return [...new Set(pointList.value.map((item) => item.status).filter(Boolean))];
+});
+
+const displayCards = computed(() => {
+  return pointList.value
+    .filter((item) => !filters.networkId || item.networkId === filters.networkId)
+    .filter((item) => !filters.status || item.status === filters.status)
+    .map((item) => {
+      const detail = pointDetailMap.value[item.pointId] || {};
+      return {
+        ...item,
+        equipmentCount: (detail.equipmentList || []).length,
+        networkName: networkOptions.value.find((network) => network.networkId === item.networkId)?.networkName
+      };
+    });
+});
 
-const showDeviceDetail = (device) => {
-  currentDevice.value = device
-  detailModalVisible.value = true
-  // 添加操作记录
+const activeAlarms = computed(() => {
+  return displayCards.value
+    .filter((item) => isAlertStatus(item.status))
+    .map((item) => ({
+      pointId: item.pointId,
+      message: `${item.pointName} 当前状态为 ${item.status || "异常"},请值班人员重点关注`
+    }));
+});
+
+const alarmCount = computed(() => activeAlarms.value.length);
+const normalCount = computed(() => displayCards.value.filter((item) => ["正常", "启用"].includes(item.status)).length);
+const equipmentCount = computed(() => {
+  return Object.values(pointDetailMap.value).reduce((count, item) => count + (item.equipmentList || []).length, 0);
+});
+
+function isAlertStatus(status) {
+  return !["正常", "启用"].includes(status);
+}
+
+function getPointEmoji(type) {
+  if (type?.includes("压力")) return "📊";
+  if (type?.includes("流量")) return "💧";
+  if (type?.includes("温度")) return "🌡️";
+  if (type?.includes("泄漏")) return "🚨";
+  return "📡";
+}
+
+function getCardClass(status) {
+  if (["正常", "启用"].includes(status)) return "is-normal";
+  if (status === "预警") return "is-warning";
+  return "is-danger";
+}
+
+function getStatusDotClass(status) {
+  if (["正常", "启用"].includes(status)) return "dot-normal";
+  if (status === "预警") return "dot-warning";
+  return "dot-danger";
+}
+
+function loadDashboard() {
+  loading.value = true;
+  return Promise.all([listPipeNetworkAll(), listMonitorPointAll()])
+    .then(async ([networkRes, pointRes]) => {
+      networkOptions.value = networkRes.data || [];
+      pointList.value = pointRes.data || [];
+      const details = await Promise.all(pointList.value.map((item) => getMonitorPointDetail(item.pointId)));
+      const map = {};
+      details.forEach((detail, index) => {
+        map[pointList.value[index].pointId] = detail.data || {};
+      });
+      pointDetailMap.value = map;
+      operationRecords.value.unshift({
+        id: Date.now(),
+        time: new Date().toLocaleTimeString(),
+        action: "刷新感知信息展示",
+        target: `加载 ${pointList.value.length} 个监测点`
+      });
+      operationRecords.value = operationRecords.value.slice(0, 8);
+    })
+    .finally(() => {
+      loading.value = false;
+    });
+}
+
+function openDetail(card) {
+  detailData.value = pointDetailMap.value[card.pointId] || {};
+  detailOpen.value = true;
   operationRecords.value.unshift({
-    id: recordIdCounter++,
+    id: Date.now() + Math.random(),
     time: new Date().toLocaleTimeString(),
-    operator: '系统',
-    action: '查看详情',
-    target: device.name,
-    result: 'success'
-  })
-}
-
-const closeDetailModal = () => {
-  detailModalVisible.value = false
-  currentDevice.value = null
-}
-
-const clearAlarms = () => {
-  // 仅清除报警状态(将alarm转为warning,fault转为normal?不实际修改)
-  // 这里只是清除提醒条,实际应在系统中确认
-  alert('已确认当前报警,系统将持续监控')
-}
-
-// 请求通知权限
-const requestNotificationPermission = () => {
-  if ('Notification' in window && Notification.permission !== 'granted' && Notification.permission !== 'denied') {
-    Notification.requestPermission()
-  }
+    action: "查看实时详情",
+    target: card.pointName
+  });
+  operationRecords.value = operationRecords.value.slice(0, 8);
 }
 
-// ==================== 生命周期 ====================
 onMounted(() => {
-  // 启动模拟数据更新(每5秒)
-  intervalId = setInterval(() => {
-    simulateDataFluctuation()
-  }, 5000)
-
-  // 每分钟添加一条模拟操作记录
-  const recordInterval = setInterval(() => {
-    addRandomOperation()
-  }, 30000)
-
-  requestNotificationPermission()
-
-  // 清理定时器
-  onUnmounted(() => {
-    if (intervalId) clearInterval(intervalId)
-    clearInterval(recordInterval)
-  })
-})
-
-// 监听报警状态变化
-watch(alarmCount, (newCount, oldCount) => {
-  if (newCount > oldCount && newCount > 0) {
-    // 触发声音提示(需要用户交互)
-    if (alarmAudio.value) {
-      alarmAudio.value.play().catch(e => console.log('音频播放需要用户交互'))
-    }
-  }
-})
+  loadDashboard();
+});
 </script>
 
 <style scoped>
-* {
-  margin: 0;
-  padding: 0;
-  box-sizing: border-box;
-}
-
-.prototype-container {
-  max-width: 1600px;
-  margin: 0 auto;
-  background: #f0f4f8;
-  border-radius: 28px;
-  box-shadow: 0 20px 35px -12px rgba(0, 0, 0, 0.15);
-  overflow: hidden;
-  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
-}
-
-/* 头部 */
-.header {
-  background: #0a2b3c;
-  padding: 16px 28px;
+.realtime-page {
+  background: linear-gradient(180deg, #f3f7fd 0%, #f8fafc 100%);
+}
+
+.hero-panel {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  flex-wrap: wrap;
   gap: 16px;
-  border-bottom: 3px solid #e74c3c;
+  margin-bottom: 16px;
+  padding: 20px 24px;
+  border-radius: 18px;
+  background: linear-gradient(135deg, #0f2742, #1d4f7d);
+  color: #fff;
 }
 
-.logo h2 {
-  color: white;
-  font-weight: 600;
-  font-size: 1.3rem;
+.hero-title {
+  font-size: 24px;
+  font-weight: 700;
 }
 
-.logo p {
-  color: #bdd8e8;
-  font-size: 0.7rem;
+.hero-subtitle {
+  margin-top: 8px;
+  font-size: 13px;
+  color: rgba(255, 255, 255, 0.84);
 }
 
-.header-stats {
-  display: flex;
+.summary-grid {
+  display: grid;
+  grid-template-columns: repeat(4, minmax(0, 1fr));
   gap: 16px;
+  margin-bottom: 16px;
 }
 
-.stat-badge {
-  background: rgba(255,255,255,0.15);
-  padding: 6px 14px;
-  border-radius: 40px;
-  display: flex;
-  align-items: center;
-  gap: 8px;
-  color: white;
+.summary-card {
+  border: none;
+  color: #fff;
 }
 
-.stat-badge.warning {
-  background: #e74c3c;
-  animation: pulse 1s infinite;
+.summary-card :deep(.el-card__body) {
+  padding: 20px;
 }
 
-.stat-icon {
-  font-size: 1rem;
+.summary-card.primary {
+  background: linear-gradient(135deg, #2563eb, #5b93ff);
 }
 
-.stat-value {
-  font-weight: 700;
-  font-size: 1.1rem;
+.summary-card.success {
+  background: linear-gradient(135deg, #059669, #34d399);
 }
 
-.stat-label {
-  font-size: 0.7rem;
-  opacity: 0.8;
+.summary-card.danger {
+  background: linear-gradient(135deg, #dc2626, #ff7070);
 }
 
-/* 主体布局 */
-.main-layout {
-  display: flex;
-  flex-wrap: wrap;
-  min-height: 700px;
+.summary-card.info {
+  background: linear-gradient(135deg, #7c3aed, #9d72ff);
 }
 
-.left-panel {
-  flex: 1.5;
-  background: #f9fbfd;
-  padding: 20px;
-  min-width: 500px;
+.summary-label {
+  font-size: 14px;
 }
 
-.right-panel {
-  flex: 1;
-  background: #ffffff;
-  padding: 20px;
-  min-width: 400px;
-  border-left: 1px solid #e2edf2;
+.summary-value {
+  margin: 8px 0 10px;
+  font-size: 30px;
+  font-weight: 700;
 }
 
-/* 面板标题 */
-.panel-title {
-  font-size: 1.1rem;
-  font-weight: 700;
-  color: #1d5a78;
-  margin-bottom: 20px;
+.summary-desc {
+  font-size: 12px;
+  opacity: 0.9;
+}
+
+.main-card,
+.side-card {
+  border-radius: 16px;
+}
+
+.card-header-row {
   display: flex;
   justify-content: space-between;
   align-items: center;
+  gap: 12px;
+}
+
+.card-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #1f2937;
 }
 
-.live-badge {
-  font-size: 0.7rem;
-  background: #e74c3c;
-  color: white;
-  padding: 4px 12px;
-  border-radius: 20px;
-  animation: blink 1s infinite;
+.card-subtitle {
+  margin-top: 4px;
+  font-size: 12px;
+  color: #6b7280;
+}
+
+.filter-group {
+  display: flex;
+  gap: 8px;
+  flex-wrap: wrap;
 }
 
-/* 设备网格 */
 .device-grid {
   display: grid;
-  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+  grid-template-columns: repeat(2, minmax(0, 1fr));
   gap: 16px;
 }
 
 .device-card {
-  background: white;
-  border-radius: 20px;
   padding: 16px;
-  display: flex;
-  gap: 12px;
-  position: relative;
+  border-radius: 16px;
+  background: #fff;
+  border: 1px solid #e5edf7;
   cursor: pointer;
-  transition: all 0.2s;
-  border: 2px solid transparent;
-  box-shadow: 0 2px 8px rgba(0,0,0,0.05);
+  transition: all 0.2s ease;
 }
 
 .device-card:hover {
   transform: translateY(-2px);
-  box-shadow: 0 6px 16px rgba(0,0,0,0.1);
+  box-shadow: 0 10px 24px rgba(15, 23, 42, 0.08);
 }
 
-.device-card.status-normal {
-  border-left: 4px solid #27ae60;
+.device-card.is-normal {
+  border-left: 4px solid #16a34a;
 }
 
-.device-card.status-warning {
-  border-left: 4px solid #f39c12;
-  background: #fef9e8;
+.device-card.is-warning {
+  border-left: 4px solid #f59e0b;
+  background: #fffaf0;
 }
 
-.device-card.status-fault {
-  border-left: 4px solid #e74c3c;
-  background: #fef0ef;
-  animation: faultPulse 1s infinite;
+.device-card.is-danger {
+  border-left: 4px solid #dc2626;
+  background: #fff5f5;
 }
 
-.device-card.status-alarm {
-  border-left: 4px solid #e74c3c;
-  background: #ffefef;
-  animation: alarmPulse 0.8s infinite;
+.device-top {
+  display: flex;
+  gap: 12px;
 }
 
 .device-icon {
-  font-size: 2rem;
-  width: 50px;
-  height: 50px;
+  width: 52px;
+  height: 52px;
+  border-radius: 14px;
+  background: #eff6ff;
   display: flex;
   align-items: center;
   justify-content: center;
-  background: #f0f4f8;
-  border-radius: 16px;
+  font-size: 26px;
 }
 
-.device-icon.pulse-animation {
-  animation: iconPulse 0.6s infinite;
+.device-icon.pulse {
+  animation: pulse 1s infinite;
 }
 
 .device-info {
@@ -601,22 +442,33 @@ watch(alarmCount, (newCount, oldCount) => {
 }
 
 .device-name {
+  font-size: 16px;
   font-weight: 700;
-  font-size: 0.85rem;
+  color: #111827;
 }
 
-.device-location {
-  font-size: 0.65rem;
-  color: #6f8ea8;
-  margin: 2px 0;
+.device-location,
+.device-network,
+.device-meta,
+.record-time,
+.record-target,
+.event-desc,
+.event-meta {
+  font-size: 12px;
+  color: #6b7280;
 }
 
-.device-status {
-  font-size: 0.7rem;
+.device-network {
+  margin-top: 4px;
+}
+
+.device-status-line {
   display: flex;
   align-items: center;
-  gap: 6px;
-  margin-top: 4px;
+  gap: 8px;
+  margin-top: 14px;
+  font-size: 12px;
+  color: #374151;
 }
 
 .status-dot {
@@ -626,317 +478,113 @@ watch(alarmCount, (newCount, oldCount) => {
   display: inline-block;
 }
 
-.dot-normal { background: #27ae60; box-shadow: 0 0 4px #27ae60; }
-.dot-warning { background: #f39c12; box-shadow: 0 0 4px #f39c12; }
-.dot-fault { background: #e74c3c; box-shadow: 0 0 4px #e74c3c; animation: blink 0.8s infinite; }
-.dot-alarm { background: #e74c3c; box-shadow: 0 0 8px #e74c3c; animation: blink 0.5s infinite; }
-
-.device-value {
-  font-size: 0.8rem;
-  font-weight: 600;
-  color: #2c5a72;
-  margin-top: 6px;
-}
-
-.device-alarm {
-  position: absolute;
-  top: 8px;
-  right: 8px;
-  display: flex;
-  gap: 3px;
-}
-
-.alarm-wave {
-  width: 4px;
-  height: 12px;
-  background: #e74c3c;
-  border-radius: 2px;
-  animation: wave 0.6s infinite ease-in-out;
-}
-
-.alarm-wave:nth-child(2) { animation-delay: 0.2s; }
-.alarm-wave:nth-child(3) { animation-delay: 0.4s; }
-
-/* 信息卡片 */
-.info-card {
-  background: white;
-  border-radius: 20px;
-  border: 1px solid #e6edf3;
-  margin-bottom: 20px;
-  overflow: hidden;
+.dot-normal {
+  background: #16a34a;
 }
 
-.card-header {
-  background: #f9fafc;
-  padding: 14px 18px;
-  border-bottom: 1px solid #eef2f6;
-  font-weight: 700;
-  font-size: 0.85rem;
-  color: #1d5a78;
-  display: flex;
-  justify-content: space-between;
+.dot-warning {
+  background: #f59e0b;
 }
 
-.record-count {
-  font-size: 0.65rem;
-  font-weight: normal;
-  color: #6f8ea8;
+.dot-danger {
+  background: #dc2626;
 }
 
-/* 记录列表 */
-.record-list, .event-list {
-  max-height: 280px;
-  overflow-y: auto;
+.device-count {
+  margin-left: auto;
+  color: #1d4ed8;
 }
 
-.record-item, .event-item {
-  padding: 12px 16px;
-  border-bottom: 1px solid #f0f4f7;
+.device-meta {
+  margin-top: 8px;
   display: flex;
-  justify-content: space-between;
-  align-items: center;
+  gap: 12px;
   flex-wrap: wrap;
-  gap: 8px;
-  font-size: 0.7rem;
-}
-
-.record-time {
-  color: #6f8ea8;
-  font-family: monospace;
-  width: 70px;
-}
-
-.record-content {
-  flex: 1;
-}
-
-.record-operator {
-  font-weight: 600;
-  margin-right: 8px;
-}
-
-.record-action {
-  color: #3498db;
-  margin-right: 8px;
-}
-
-.record-target {
-  color: #2c5a72;
-}
-
-.record-result.success {
-  color: #27ae60;
-}
-
-.record-result.failed {
-  color: #e74c3c;
-}
-
-.empty-record {
-  text-align: center;
-  padding: 40px;
-  color: #8aaec2;
-  font-size: 0.8rem;
-}
-
-/* 事件标签 */
-.tabs {
-  padding: 0;
-  gap: 0;
-}
-
-.tab-btn {
-  flex: 1;
-  background: none;
-  border: none;
-  padding: 12px 0;
-  cursor: pointer;
-  font-size: 0.75rem;
-  color: #6f8ea8;
-  transition: 0.2s;
-  border-bottom: 2px solid transparent;
 }
 
-.tab-btn.active {
-  color: #3498db;
-  border-bottom-color: #3498db;
-}
-
-/* 事件项 */
-.event-item {
+.record-list,
+.event-list {
+  display: flex;
   flex-direction: column;
-  align-items: flex-start;
+  gap: 12px;
 }
 
-.event-time {
-  font-size: 0.65rem;
-  color: #6f8ea8;
-  font-family: monospace;
+.record-item,
+.event-item {
+  padding: 12px;
+  border-radius: 12px;
+  background: #f8fafc;
+  border: 1px solid #edf2f7;
 }
 
+.record-action,
 .event-title {
-  font-weight: 700;
-  font-size: 0.8rem;
-  margin: 4px 0;
-}
-
-.event-desc {
-  font-size: 0.7rem;
-  color: #5a6e7e;
-}
-
-.event-handler {
-  font-size: 0.65rem;
-  color: #6f8ea8;
-  display: flex;
-  align-items: center;
-  gap: 8px;
-  flex-wrap: wrap;
-}
-
-.event-status {
-  padding: 2px 8px;
-  border-radius: 20px;
-  font-size: 0.6rem;
-}
-
-.event-status.processing {
-  background: #fef3c7;
-  color: #d97706;
-}
-
-.event-status.completed {
-  background: #d5f5e3;
-  color: #27ae60;
-}
-
-.event-status.monitoring {
-  background: #e0f2fe;
-  color: #0284c7;
-}
-
-.event-item.critical {
-  border-left: 3px solid #e74c3c;
-  background: #fef2f2;
-}
-
-.event-item.major {
-  border-left: 3px solid #f39c12;
+  font-size: 14px;
+  font-weight: 600;
+  color: #111827;
 }
 
-.event-item.warning {
-  border-left: 3px solid #fbbf24;
+.record-target,
+.event-desc,
+.event-meta {
+  margin-top: 4px;
 }
 
-/* 报警条 */
 .alert-bar {
-  background: #e74c3c;
-  border-radius: 16px;
-  padding: 12px 16px;
+  margin-top: 16px;
   display: flex;
-  align-items: center;
   gap: 12px;
-  color: white;
-  animation: slideIn 0.3s ease;
+  padding: 14px 16px;
+  border-radius: 14px;
+  background: #dc2626;
+  color: #fff;
 }
 
 .alert-icon {
-  font-size: 1.3rem;
-  animation: bellShake 0.5s infinite;
-}
-
-.alert-messages {
-  flex: 1;
-  font-size: 0.7rem;
-}
-
-.alert-message {
-  margin: 2px 0;
-}
-
-.alert-close {
-  background: rgba(255,255,255,0.2);
-  border: none;
-  color: white;
-  padding: 4px 12px;
-  border-radius: 20px;
-  cursor: pointer;
-  font-size: 0.7rem;
-}
-
-/* 模态框 */
-.modal-overlay {
-  position: fixed;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  background: rgba(0,0,0,0.5);
+  width: 24px;
+  height: 24px;
+  border-radius: 50%;
+  background: rgba(255, 255, 255, 0.2);
   display: flex;
   align-items: center;
   justify-content: center;
-  z-index: 1000;
-}
-
-.modal-content {
-  background: white;
-  border-radius: 24px;
-  width: 450px;
-  max-width: 90%;
-  padding: 24px;
-}
-
-.detail-row {
-  margin-bottom: 12px;
-  font-size: 0.75rem;
+  font-weight: 700;
 }
 
-.detail-label {
-  font-weight: 600;
-  width: 100px;
-  display: inline-block;
-  color: #2c5a72;
+.alert-messages {
+  font-size: 12px;
+  line-height: 1.7;
 }
 
-/* 动画 */
 @keyframes pulse {
-  0%, 100% { opacity: 1; }
-  50% { opacity: 0.6; }
-}
-
-@keyframes blink {
-  0%, 100% { opacity: 1; }
-  50% { opacity: 0.3; }
-}
-
-@keyframes alarmPulse {
-  0%, 100% { background: #ffefef; border-left-color: #e74c3c; }
-  50% { background: #ffe0e0; border-left-color: #c0392b; }
-}
-
-@keyframes faultPulse {
-  0%, 100% { background: #fef0ef; }
-  50% { background: #fce4e2; }
+  0%,
+  100% {
+    transform: scale(1);
+  }
+  50% {
+    transform: scale(1.08);
+  }
 }
 
-@keyframes iconPulse {
-  0%, 100% { transform: scale(1); }
-  50% { transform: scale(1.05); background: #ffe0e0; }
-}
+@media (max-width: 1200px) {
+  .summary-grid {
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+  }
 
-@keyframes wave {
-  0%, 100% { height: 8px; opacity: 0.5; }
-  50% { height: 16px; opacity: 1; }
+  .device-grid {
+    grid-template-columns: 1fr;
+  }
 }
 
-@keyframes bellShake {
-  0%, 100% { transform: rotate(0deg); }
-  25% { transform: rotate(10deg); }
-  75% { transform: rotate(-10deg); }
-}
+@media (max-width: 768px) {
+  .hero-panel,
+  .card-header-row {
+    flex-direction: column;
+    align-items: flex-start;
+  }
 
-@keyframes slideIn {
-  from { transform: translateY(-100%); opacity: 0; }
-  to { transform: translateY(0); opacity: 1; }
+  .summary-grid {
+    grid-template-columns: 1fr;
+  }
 }
-</style>
+</style>

+ 451 - 708
src/views/subSystem/pipeNetwork/pinteControl/PremoteControl.vue

@@ -1,771 +1,514 @@
-<!--终端设备远程测控-->
+<!-- 终端设备远程测控 -->
 <template>
-  <div class="prototype-container">
-
-
-    <div class="main-layout">
-      <!-- 左侧:区域控制器配置 -->
-      <div class="left-panel">
-        <div class="card">
-          <div class="card-header">
-            🗺️ 区域控制器配置
-            <button @click="openRegionModal" class="btn-outline btn-sm">+ 新增区域</button>
-          </div>
-          <div class="card-body">
-            <div class="region-list">
-              <div
-                  v-for="region in regions"
-                  :key="region.id"
-                  class="region-item"
-                  :class="{ active: selectedRegionId === region.id }"
-                  @click="selectRegion(region.id)"
-              >
-                <div class="region-name">{{ region.name }}</div>
-                <div class="region-status">
-                  <span class="status-dot" :class="region.status === 'auto' ? 'dot-auto' : 'dot-manual'"></span>
-                  {{ region.status === 'auto' ? '自动控制' : '手动控制' }}
-                </div>
-              </div>
-            </div>
-          </div>
-        </div>
+  <div class="app-container remote-page" v-loading="loading">
+    <div class="hero-panel">
+      <div>
+        <div class="hero-title">终端设备远程测控</div>
+        <div class="hero-subtitle">基于区域控制、手动控制和事故联动预案,模拟值班中心对终端设备的远程控制管理</div>
+      </div>
+      <el-button type="primary" icon="Refresh" @click="loadDashboard">刷新区域</el-button>
+    </div>
 
-        <!-- 区域控制器参数设置(仅自动模式显示) -->
-        <div class="card" v-if="currentRegion && currentRegion.controlMode === 'auto'">
-          <div class="card-header">⚙️ 自动控制参数设置</div>
-          <div class="card-body">
-            <div class="param-group">
-              <label class="param-label">上限阈值</label>
-              <input type="number" v-model="autoParams.upperThreshold" class="param-input" step="0.1">
-              <span class="param-unit">{{ currentRegion.unit || '' }}</span>
-            </div>
-            <div class="param-group">
-              <label class="param-label">下限阈值</label>
-              <input type="number" v-model="autoParams.lowerThreshold" class="param-input" step="0.1">
-              <span class="param-unit">{{ currentRegion.unit || '' }}</span>
+    <el-row :gutter="16">
+      <el-col :xs="24" :lg="6">
+        <el-card shadow="never" class="panel-card">
+          <template #header>
+            <div class="card-header-row">
+              <div class="card-title">区域控制器</div>
+              <el-tag>{{ regions.length }} 个区域</el-tag>
             </div>
-            <div class="param-group">
-              <label class="param-label">目标值</label>
-              <input type="number" v-model="autoParams.targetValue" class="param-input" step="0.1">
-              <span class="param-unit">{{ currentRegion.unit || '' }}</span>
-            </div>
-            <div class="param-group">
-              <label class="param-label">采样周期(秒)</label>
-              <input type="number" v-model="autoParams.samplePeriod" class="param-input" step="1">
-              <span class="param-unit">秒</span>
-            </div>
-            <button @click="saveAutoParams" class="btn-primary btn-sm" style="width:100%;">保存参数</button>
-          </div>
-        </div>
-
-        <!-- 设备运行方案预设 -->
-        <div class="card">
-          <div class="card-header">
-            📋 设备运行方案预设
-            <button @click="openSchemeModal" class="btn-outline btn-sm">+ 新增方案</button>
-          </div>
-          <div class="card-body">
-            <div class="scheme-list">
-              <div
-                  v-for="scheme in operationSchemes"
-                  :key="scheme.id"
-                  class="scheme-item"
-                  :class="{ active: selectedSchemeId === scheme.id }"
-                  @click="selectScheme(scheme.id)"
-              >
-                <div class="scheme-name">{{ scheme.name }}</div>
-                <div class="scheme-desc">{{ scheme.description }}</div>
-                <div class="scheme-actions">
-                  <button @click.stop="applyScheme(scheme)" class="btn-success btn-sm">应用</button>
-                  <button @click.stop="editScheme(scheme)" class="btn-outline btn-sm">编辑</button>
-                  <button @click.stop="deleteScheme(scheme)" class="btn-danger-outline btn-sm">删除</button>
-                </div>
+          </template>
+
+          <div class="region-list">
+            <div
+              v-for="item in regions"
+              :key="item.id"
+              class="region-item"
+              :class="{ active: selectedRegionId === item.id }"
+              @click="selectRegion(item.id)"
+            >
+              <div class="region-name">{{ item.name }}</div>
+              <div class="region-meta">{{ item.location }}</div>
+              <div class="region-status">
+                <span class="status-dot" :class="item.controlMode === 'auto' ? 'dot-auto' : 'dot-manual'"></span>
+                {{ item.controlMode === "auto" ? "自动控制" : "手动控制" }}
               </div>
             </div>
           </div>
-        </div>
-      </div>
-
-      <!-- 中间:设备列表与控制 -->
-      <div class="middle-panel">
-        <div class="card">
-          <div class="card-header">
-            📟 区域设备列表
-            <span class="region-hint">{{ currentRegion?.name || '请选择区域' }}</span>
-          </div>
-          <div class="card-body">
-            <div class="device-control-list">
-              <div v-for="device in currentDevices" :key="device.id" class="device-control-item">
-                <div class="device-basic">
-                  <div class="device-name">{{ device.name }}</div>
-                  <div class="device-meta">{{ device.type }} | ID: {{ device.deviceId }}</div>
-                  <div class="device-status">
-                    <span class="status-dot" :class="device.status === 'running' ? 'dot-running' : 'dot-stopped'"></span>
-                    {{ device.status === 'running' ? '运行中' : '已停止' }}
-                  </div>
-                  <div class="device-value" v-if="device.currentValue !== undefined">
-                    当前值: {{ device.currentValue }} {{ device.unit }}
-                  </div>
-                </div>
-                <div class="device-control">
-                  <button
-                      @click="sendControlCommand(device, 'start')"
-                      class="btn-success btn-sm"
-                      :disabled="device.status === 'running'"
-                  >▶ 启动</button>
-                  <button
-                      @click="sendControlCommand(device, 'stop')"
-                      class="btn-danger btn-sm"
-                      :disabled="device.status === 'stopped'"
-                  >⏹️ 停止</button>
-                  <button
-                      @click="openSetValueModal(device)"
-                      class="btn-outline btn-sm"
-                  >⚙️ 设置参数</button>
-                </div>
+        </el-card>
+
+        <el-card v-if="currentRegion" shadow="never" class="panel-card">
+          <template #header>
+            <div class="card-title">自动控制参数</div>
+          </template>
+          <el-form label-width="80px" size="small">
+            <el-form-item label="上限阈值">
+              <el-input-number v-model="autoParams.upperThreshold" :step="0.1" style="width: 100%" />
+            </el-form-item>
+            <el-form-item label="下限阈值">
+              <el-input-number v-model="autoParams.lowerThreshold" :step="0.1" style="width: 100%" />
+            </el-form-item>
+            <el-form-item label="目标值">
+              <el-input-number v-model="autoParams.targetValue" :step="0.1" style="width: 100%" />
+            </el-form-item>
+            <el-form-item label="采样周期">
+              <el-input-number v-model="autoParams.samplePeriod" :step="1" style="width: 100%" />
+            </el-form-item>
+            <el-button type="primary" style="width: 100%" @click="saveAutoParams">保存参数</el-button>
+          </el-form>
+        </el-card>
+      </el-col>
+
+      <el-col :xs="24" :lg="11">
+        <el-card shadow="never" class="panel-card">
+          <template #header>
+            <div class="card-header-row">
+              <div>
+                <div class="card-title">中控远程手动控制</div>
+                <div class="card-subtitle">{{ currentRegion?.name || "请选择区域" }}</div>
               </div>
+              <el-switch
+                v-model="manualMode"
+                active-text="手动模式"
+                inactive-text="自动模式"
+                @change="toggleControlMode"
+              />
             </div>
-          </div>
-        </div>
-
-        <!-- 中控手动控制面板 -->
-        <div class="card">
-          <div class="card-header">
-            🎛️ 中控远程手动控制
-            <label class="mode-switch">
-              <input type="checkbox" v-model="isManualMode" @change="toggleControlMode">
-              <span class="mode-slider"></span>
-              <span class="mode-label">{{ isManualMode ? '手动模式' : '自动模式' }}</span>
-            </label>
-          </div>
-          <div class="card-body" v-if="isManualMode">
-            <div class="manual-control-panel">
-              <div class="control-log" ref="controlLog">
-                <div v-for="log in controlLogs.slice(-8)" :key="log.id" class="log-item">
-                  <span class="log-time">{{ log.time }}</span>
-                  <span class="log-action">{{ log.action }}</span>
-                  <span class="log-target">{{ log.target }}</span>
-                  <span class="log-result" :class="log.result">{{ log.result === 'success' ? '✓ 成功' : '✗ 失败' }}</span>
-                </div>
+          </template>
+
+          <div v-if="currentDevices.length" class="device-list">
+            <div v-for="item in currentDevices" :key="item.id" class="device-item">
+              <div class="device-main">
+                <div class="device-name">{{ item.name }}</div>
+                <div class="device-meta">{{ item.type }} · {{ item.deviceId }}</div>
+                <div class="device-value">目标值:{{ item.currentValue }} {{ item.unit }}</div>
               </div>
-              <div class="quick-commands">
-                <div class="quick-title">快捷指令</div>
-                <div class="quick-buttons">
-                  <button @click="quickCommand('全部启动')" class="btn-primary btn-sm">全部启动</button>
-                  <button @click="quickCommand('全部停止')" class="btn-danger btn-sm">全部停止</button>
-                  <button @click="quickCommand('紧急停止')" class="btn-warning btn-sm">紧急停止</button>
-                </div>
+              <div class="device-actions">
+                <el-tag :type="item.status === 'running' ? 'success' : 'info'">
+                  {{ item.status === "running" ? "运行中" : "已停止" }}
+                </el-tag>
+                <el-button type="success" plain size="small" :disabled="!manualMode || item.status === 'running'" @click="sendControlCommand(item, 'start')">
+                  启动
+                </el-button>
+                <el-button type="danger" plain size="small" :disabled="!manualMode || item.status === 'stopped'" @click="sendControlCommand(item, 'stop')">
+                  停止
+                </el-button>
+                <el-button size="small" :disabled="!manualMode" @click="openSetValueDialog(item)">设置参数</el-button>
               </div>
             </div>
           </div>
-          <div class="card-body" v-else>
-            <div class="auto-mode-hint">
-              <span class="hint-icon">🤖</span>
-              <span>当前为区域自动控制模式,设备根据预设阈值自动运行</span>
-              <span class="hint-sub">切换到手动模式可进行远程单设备控制</span>
-            </div>
-          </div>
-        </div>
-      </div>
+          <el-empty v-else description="当前区域暂无可控设备" />
 
-      <!-- 右侧:事故联动控制预案 -->
-      <div class="right-panel">
-        <div class="card">
-          <div class="card-header">
-            🚨 事故联动控制预案
-            <button @click="openIncidentModal" class="btn-primary btn-sm">+ 新增预案</button>
+          <div class="quick-actions">
+            <el-button type="primary" plain :disabled="!manualMode" @click="quickCommand('全部启动')">全部启动</el-button>
+            <el-button type="danger" plain :disabled="!manualMode" @click="quickCommand('全部停止')">全部停止</el-button>
+            <el-button type="warning" plain :disabled="!manualMode" @click="quickCommand('紧急停止')">紧急停止</el-button>
           </div>
-          <div class="card-body">
-            <div class="incident-list">
-              <div v-for="incident in incidentPlans" :key="incident.id" class="incident-item" :class="incident.type">
-                <div class="incident-header">
-                  <span class="incident-name">{{ incident.name }}</span>
-                  <span class="incident-type">{{ getIncidentTypeText(incident.type) }}</span>
-                </div>
-                <div class="incident-desc">{{ incident.description }}</div>
-                <div class="incident-devices">
-                  <span class="devices-label">联动设备:</span>
-                  <span class="devices-list">{{ incident.devices.map(d => d.name).join(', ') }}</span>
-                </div>
-                <div class="incident-actions">
-                  <button @click="triggerIncident(incident)" class="btn-warning btn-sm">▶ 快速启动</button>
-                  <button @click="editIncident(incident)" class="btn-outline btn-sm">编辑</button>
-                  <button @click="deleteIncident(incident)" class="btn-danger-outline btn-sm">删除</button>
-                </div>
+        </el-card>
+
+        <el-card shadow="never" class="panel-card">
+          <template #header>
+            <div class="card-title">设备运行方案</div>
+          </template>
+          <div class="scheme-list">
+            <div v-for="item in operationSchemes" :key="item.id" class="scheme-item">
+              <div class="scheme-name">{{ item.name }}</div>
+              <div class="scheme-desc">{{ item.description }}</div>
+              <div class="scheme-footer">
+                <span>设备数量:{{ item.deviceIds.length }}</span>
+                <el-button type="primary" link @click="applyScheme(item)">应用方案</el-button>
               </div>
             </div>
           </div>
-        </div>
-
-        <!-- 预案执行记录 -->
-        <div class="card">
-          <div class="card-header">📋 预案执行记录</div>
-          <div class="card-body">
-            <div class="execution-list">
-              <div v-for="record in executionRecords.slice(-5)" :key="record.id" class="execution-item">
-                <div class="exec-time">{{ record.time }}</div>
-                <div class="exec-name">{{ record.incidentName }}</div>
-                <div class="exec-status" :class="record.status">{{ record.status === 'success' ? '执行成功' : '执行中' }}</div>
-              </div>
+        </el-card>
+      </el-col>
+
+      <el-col :xs="24" :lg="7">
+        <el-card shadow="never" class="panel-card">
+          <template #header>
+            <div class="card-title">事故联动控制预案</div>
+          </template>
+          <div class="plan-list">
+            <div v-for="item in incidentPlans" :key="item.id" class="plan-item">
+              <div class="plan-name">{{ item.name }}</div>
+              <div class="plan-desc">{{ item.description }}</div>
+              <div class="plan-devices">联动设备:{{ item.devices.map((d) => d.name).join("、") }}</div>
+              <el-button type="warning" plain size="small" @click="triggerIncident(item)">快速启动</el-button>
             </div>
           </div>
-        </div>
-      </div>
-    </div>
-
-    <!-- 新增区域模态框 -->
-    <div v-if="regionModalVisible" class="modal-overlay" @click.self="closeRegionModal">
-      <div class="modal-content">
-        <h3>{{ isEditRegion ? '编辑区域' : '新增区域' }}</h3>
-        <div class="form-group">
-          <label class="form-label">区域名称</label>
-          <input v-model="regionForm.name" class="form-input">
-        </div>
-        <div class="form-group">
-          <label class="form-label">区域位置</label>
-          <input v-model="regionForm.location" class="form-input">
-        </div>
-        <div class="form-group">
-          <label class="form-label">控制模式</label>
-          <select v-model="regionForm.controlMode" class="form-select">
-            <option value="auto">自动控制</option>
-            <option value="manual">手动控制</option>
-          </select>
-        </div>
-        <div class="form-group">
-          <label class="form-label">监测类型/单位</label>
-          <input v-model="regionForm.unit" class="form-input" placeholder="如 MPa、m³/h">
-        </div>
-        <div class="form-actions">
-          <button @click="closeRegionModal" class="btn-outline">取消</button>
-          <button @click="saveRegion" class="btn-primary">保存</button>
-        </div>
-      </div>
-    </div>
-
-    <!-- 新增/编辑方案模态框 -->
-    <div v-if="schemeModalVisible" class="modal-overlay" @click.self="closeSchemeModal">
-      <div class="modal-content" style="width:500px;">
-        <h3>{{ isEditScheme ? '编辑方案' : '新增运行方案' }}</h3>
-        <div class="form-group">
-          <label class="form-label">方案名称</label>
-          <input v-model="schemeForm.name" class="form-input">
-        </div>
-        <div class="form-group">
-          <label class="form-label">方案描述</label>
-          <input v-model="schemeForm.description" class="form-input">
-        </div>
-        <div class="form-group">
-          <label class="form-label">控制设备</label>
-          <div class="device-check-list">
-            <label v-for="device in allDevices" :key="device.id" class="device-check">
-              <input type="checkbox" :value="device.id" v-model="schemeForm.deviceIds">
-              {{ device.name }} ({{ device.type }})
-            </label>
-          </div>
-        </div>
-        <div class="form-group">
-          <label class="form-label">执行动作</label>
-          <select v-model="schemeForm.action" class="form-select">
-            <option value="start">全部启动</option>
-            <option value="stop">全部停止</option>
-            <option value="custom">自定义</option>
-          </select>
-        </div>
-        <div class="form-actions">
-          <button @click="closeSchemeModal" class="btn-outline">取消</button>
-          <button @click="saveScheme" class="btn-primary">保存</button>
-        </div>
-      </div>
-    </div>
-
-    <!-- 新增/编辑事故预案模态框 -->
-    <div v-if="incidentModalVisible" class="modal-overlay" @click.self="closeIncidentModal">
-      <div class="modal-content" style="width:550px;">
-        <h3>{{ isEditIncident ? '编辑预案' : '新增事故联动预案' }}</h3>
-        <div class="form-group">
-          <label class="form-label">预案名称</label>
-          <input v-model="incidentForm.name" class="form-input">
-        </div>
-        <div class="form-group">
-          <label class="form-label">预案类型</label>
-          <select v-model="incidentForm.type" class="form-select">
-            <option value="leak">泄漏事故</option>
-            <option value="fire">火灾事故</option>
-            <option value="pressure">压力异常</option>
-            <option value="maintenance">养护维护</option>
-          </select>
-        </div>
-        <div class="form-group">
-          <label class="form-label">预案描述</label>
-          <textarea v-model="incidentForm.description" rows="2" class="form-textarea"></textarea>
-        </div>
-        <div class="form-group">
-          <label class="form-label">联动设备</label>
-          <div class="device-check-list">
-            <label v-for="device in allDevices" :key="device.id" class="device-check">
-              <input type="checkbox" :value="device.id" v-model="incidentForm.deviceIds">
-              {{ device.name }}
-            </label>
+        </el-card>
+
+        <el-card shadow="never" class="panel-card">
+          <template #header>
+            <div class="card-title">控制日志</div>
+          </template>
+          <div class="log-list">
+            <div v-for="item in controlLogs" :key="item.id" class="log-item">
+              <div class="log-time">{{ item.time }}</div>
+              <div class="log-action">{{ item.action }}</div>
+              <div class="log-target">{{ item.target }}</div>
+            </div>
           </div>
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <el-dialog title="设置设备参数" v-model="setValueOpen" width="420px" append-to-body>
+      <el-form label-width="90px">
+        <el-form-item label="设备名称">
+          <span>{{ currentSetDevice?.name || "-" }}</span>
+        </el-form-item>
+        <el-form-item label="目标值">
+          <el-input-number v-model="setValue" :step="0.1" style="width: 100%" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitSetValue">下发指令</el-button>
+          <el-button @click="setValueOpen = false">取 消</el-button>
         </div>
-        <div class="form-group">
-          <label class="form-label">联动动作</label>
-          <select v-model="incidentForm.action" class="form-select">
-            <option value="all_start">启动所有联动设备</option>
-            <option value="all_stop">停止所有联动设备</option>
-            <option value="emergency_stop">紧急停止+报警</option>
-          </select>
-        </div>
-        <div class="form-actions">
-          <button @click="closeIncidentModal" class="btn-outline">取消</button>
-          <button @click="saveIncident" class="btn-primary">保存</button>
-        </div>
-      </div>
-    </div>
-
-    <!-- 设置设备参数模态框 -->
-    <div v-if="setValueModalVisible" class="modal-overlay" @click.self="closeSetValueModal">
-      <div class="modal-content" style="width:400px;">
-        <h3>设置设备参数</h3>
-        <div class="form-group">
-          <label class="form-label">设备: {{ currentSetDevice?.name }}</label>
-        </div>
-        <div class="form-group">
-          <label class="form-label">目标值 ({{ currentSetDevice?.unit }})</label>
-          <input type="number" v-model="setValue" class="form-input" step="0.1">
-        </div>
-        <div class="form-actions">
-          <button @click="closeSetValueModal" class="btn-outline">取消</button>
-          <button @click="submitSetValue" class="btn-primary">下发指令</button>
-        </div>
-      </div>
-    </div>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
-import { ref, computed } from 'vue'
-
-// ==================== 区域数据 ====================
-const regions = ref([
-  { id: 1, name: "城东泵站区域", location: "城东区", controlMode: "auto", unit: "MPa", status: "auto", devices: [1, 2] },
-  { id: 2, name: "城南调压站区域", location: "城南新区", controlMode: "manual", unit: "kPa", status: "manual", devices: [3, 4] },
-  { id: 3, name: "城西污水处理区域", location: "城西工业区", controlMode: "auto", unit: "m³/h", status: "auto", devices: [5, 6] }
-])
-
-const selectedRegionId = ref(1)
-const currentRegion = computed(() => regions.value.find(r => r.id === selectedRegionId.value))
-
-// 区域控制器自动参数
-const autoParams = ref({
+import { computed, onMounted, reactive, ref } from "vue";
+import { getMonitorPointDetail, listMonitorPointAll, listPipeNetworkAll } from "@/api/pipeNetwork/pdeviceInform";
+
+const loading = ref(false);
+const networkOptions = ref([]);
+const regions = ref([]);
+const selectedRegionId = ref();
+const manualMode = ref(false);
+const controlLogs = ref([]);
+const setValueOpen = ref(false);
+const currentSetDevice = ref();
+const setValue = ref(0);
+
+const autoParams = reactive({
   upperThreshold: 1.2,
   lowerThreshold: 0.3,
   targetValue: 0.8,
   samplePeriod: 10
-})
-
-// ==================== 设备数据 ====================
-const allDevices = ref([
-  { id: 1, name: "进水提升泵", deviceId: "PMP-001", type: "水泵", status: "running", regionId: 1, currentValue: 0.85, unit: "MPa" },
-  { id: 2, name: "出水调节阀", deviceId: "VLV-002", type: "阀门", status: "stopped", regionId: 1, currentValue: 0, unit: "%" },
-  { id: 3, name: "天然气压缩机", deviceId: "CMP-003", type: "压缩机", status: "running", regionId: 2, currentValue: 450, unit: "kPa" },
-  { id: 4, name: "放散阀", deviceId: "VLV-004", type: "阀门", status: "stopped", regionId: 2, currentValue: 0, unit: "%" },
-  { id: 5, name: "搅拌器", deviceId: "MIX-005", type: "搅拌设备", status: "running", regionId: 3, currentValue: 120, unit: "m³/h" },
-  { id: 6, name: "排泥泵", deviceId: "PMP-006", type: "水泵", status: "stopped", regionId: 3, currentValue: 0, unit: "m³/h" }
-])
-
-// 当前区域设备
-const currentDevices = computed(() => {
-  if (!currentRegion.value) return []
-  return allDevices.value.filter(d => d.regionId === currentRegion.value.id)
-})
+});
 
-// ==================== 运行方案 ====================
 const operationSchemes = ref([
-  { id: 1, name: "高峰供水方案", description: "早晚高峰时段,启动全部水泵", deviceIds: [1, 2], action: "start" },
-  { id: 2, name: "低谷节能方案", description: "低峰时段,停止部分设备", deviceIds: [1, 2], action: "stop" },
-  { id: 3, name: "夜间模式", description: "夜间低功耗运行", deviceIds: [1], action: "start" }
-])
+  { id: 1, name: "高峰供给方案", description: "高峰时段优先启用区域内主要设备", deviceIds: [] },
+  { id: 2, name: "低谷节能方案", description: "低谷时段保留关键设备,其余设备停机待命", deviceIds: [] },
+  { id: 3, name: "夜间值守方案", description: "仅维持核心设备运行,降低夜间能耗", deviceIds: [] }
+]);
 
-const selectedSchemeId = ref(null)
-
-// ==================== 事故预案 ====================
 const incidentPlans = ref([
-  {
-    id: 1, name: "燃气泄漏应急联动", type: "leak", description: "检测到燃气泄漏时,自动关闭阀门、启动排风",
-    devices: [{ id: 3, name: "天然气压缩机" }, { id: 4, name: "放散阀" }],
-    deviceIds: [3, 4], action: "emergency_stop"
-  },
-  {
-    id: 2, name: "压力超标联动", type: "pressure", description: "压力超标时自动调节阀门",
-    devices: [{ id: 1, name: "进水提升泵" }, { id: 2, name: "出水调节阀" }],
-    deviceIds: [1, 2], action: "all_stop"
-  },
-  {
-    id: 3, name: "年度养护维护方案", type: "maintenance", description: "定期维护时停止相关设备",
-    devices: [{ id: 5, name: "搅拌器" }, { id: 6, name: "排泥泵" }],
-    deviceIds: [5, 6], action: "all_stop"
-  }
-])
-
-// 执行记录
-const executionRecords = ref([
-  { id: 1, time: "2025-05-08 10:23:15", incidentName: "燃气泄漏应急联动", status: "success" },
-  { id: 2, time: "2025-05-08 08:45:22", incidentName: "压力超标联动", status: "success" }
-])
-
-// ==================== 控制模式 ====================
-const isManualMode = ref(false)
-const controlMode = computed(() => isManualMode.value ? 'manual' : 'auto')
-
-// 控制日志
-const controlLogs = ref([
-  { id: 1, time: "14:23:15", action: "启动设备", target: "进水提升泵", result: "success" },
-  { id: 2, time: "14:20:30", action: "停止设备", target: "出水调节阀", result: "success" }
-])
-
-// ==================== 模态框状态 ====================
-const regionModalVisible = ref(false)
-const schemeModalVisible = ref(false)
-const incidentModalVisible = ref(false)
-const setValueModalVisible = ref(false)
-
-const isEditRegion = ref(false)
-const isEditScheme = ref(false)
-const isEditIncident = ref(false)
-
-const regionForm = ref({ name: '', location: '', controlMode: 'auto', unit: '' })
-const schemeForm = ref({ name: '', description: '', deviceIds: [], action: 'start' })
-const incidentForm = ref({ name: '', type: 'leak', description: '', deviceIds: [], action: 'all_stop' })
-
-const currentSetDevice = ref(null)
-const setValue = ref(0)
-
-// ==================== 方法 ====================
-// 选择区域
-const selectRegion = (id) => {
-  selectedRegionId.value = id
-  // 加载区域自动参数(模拟)
-  if (currentRegion.value && currentRegion.value.id === 1) {
-    autoParams.value = { upperThreshold: 1.2, lowerThreshold: 0.3, targetValue: 0.8, samplePeriod: 10 }
-  }
+  { id: 1, name: "泄漏事故联动", description: "快速停机并通知值班中心介入处理", devices: [] },
+  { id: 2, name: "压力异常联动", description: "立即停止相关设备,降低区域运行风险", devices: [] },
+  { id: 3, name: "养护维护预案", description: "进入维护模式,按计划逐项停机确认", devices: [] }
+]);
+
+const currentRegion = computed(() => {
+  return regions.value.find((item) => item.id === selectedRegionId.value);
+});
+
+const currentDevices = computed(() => {
+  return currentRegion.value?.devices || [];
+});
+
+function addLog(action, target) {
+  controlLogs.value.unshift({
+    id: Date.now() + Math.random(),
+    time: new Date().toLocaleTimeString(),
+    action,
+    target
+  });
+  controlLogs.value = controlLogs.value.slice(0, 10);
 }
 
-// 保存自动参数
-const saveAutoParams = () => {
-  alert(`自动控制参数已保存\n上限:${autoParams.value.upperThreshold} 下限:${autoParams.value.lowerThreshold}\n目标值:${autoParams.value.targetValue}`)
-  addControlLog('系统', '更新自动参数', `${currentRegion.value?.name}`, 'success')
-}
-
-// 方案相关
-const selectScheme = (id) => { selectedSchemeId.value = id }
-const applyScheme = (scheme) => {
-  const devices = allDevices.value.filter(d => scheme.deviceIds.includes(d.id))
-  devices.forEach(device => {
-    if (scheme.action === 'start') device.status = 'running'
-    else if (scheme.action === 'stop') device.status = 'stopped'
-  })
-  addControlLog('系统', `应用方案:${scheme.name}`, `${devices.length}台设备`, 'success')
-  alert(`已应用方案"${scheme.name}",${devices.length}台设备已响应`)
-}
-
-const openSchemeModal = () => { isEditScheme.value = false; schemeForm.value = { name: '', description: '', deviceIds: [], action: 'start' }; schemeModalVisible.value = true }
-const editScheme = (scheme) => { isEditScheme.value = true; schemeForm.value = { ...scheme, deviceIds: [...scheme.deviceIds] }; schemeModalVisible.value = true }
-const saveScheme = () => {
-  if (isEditScheme.value) {
-    const index = operationSchemes.value.findIndex(s => s.id === schemeForm.value.id)
-    if (index !== -1) operationSchemes.value[index] = { ...schemeForm.value }
-  } else {
-    const newId = Math.max(...operationSchemes.value.map(s => s.id), 0) + 1
-    operationSchemes.value.push({ ...schemeForm.value, id: newId })
-  }
-  closeSchemeModal()
-  alert('方案已保存')
+function buildRegions(networks, points, detailMap) {
+  return networks.map((network, index) => {
+    const relatedPoints = points.filter((item) => item.networkId === network.networkId);
+    const devices = relatedPoints.flatMap((point) => {
+      const detail = detailMap[point.pointId] || {};
+      return (detail.equipmentList || []).map((equipment, equipmentIndex) => ({
+        id: `${network.networkId}-${equipment.equipmentId}-${equipmentIndex}`,
+        equipmentId: equipment.equipmentId,
+        name: equipment.equipmentName,
+        deviceId: equipment.equipmentCode,
+        type: equipment.equipmentModel || "设备",
+        status: ["正常", "启用"].includes(point.status) ? "running" : "stopped",
+        unit: point.pointType?.includes("压力") ? "MPa" : point.pointType?.includes("流量") ? "m3/h" : "值",
+        currentValue: equipmentIndex + 1,
+        pointName: point.pointName
+      }));
+    });
+    return {
+      id: index + 1,
+      networkId: network.networkId,
+      name: network.networkName,
+      location: network.location || "未维护位置",
+      controlMode: index % 2 === 0 ? "auto" : "manual",
+      devices
+    };
+  });
 }
-const deleteScheme = (scheme) => {
-  if (confirm(`删除方案"${scheme.name}"?`)) {
-    operationSchemes.value = operationSchemes.value.filter(s => s.id !== scheme.id)
-  }
+
+function loadDashboard() {
+  loading.value = true;
+  return Promise.all([listPipeNetworkAll(), listMonitorPointAll()])
+    .then(async ([networkRes, pointRes]) => {
+      const networks = networkRes.data || [];
+      const points = pointRes.data || [];
+      networkOptions.value = networks;
+      const details = await Promise.all(points.map((item) => getMonitorPointDetail(item.pointId)));
+      const detailMap = {};
+      details.forEach((detail, index) => {
+        detailMap[points[index].pointId] = detail.data || {};
+      });
+      regions.value = buildRegions(networks, points, detailMap);
+      selectedRegionId.value = regions.value[0]?.id;
+
+      const allDeviceIds = regions.value.flatMap((item) => item.devices.map((device) => device.equipmentId));
+      operationSchemes.value = operationSchemes.value.map((item, index) => ({
+        ...item,
+        deviceIds: allDeviceIds.slice(0, Math.max(1, index + 1))
+      }));
+      incidentPlans.value = incidentPlans.value.map((item, index) => ({
+        ...item,
+        devices: regions.value.flatMap((region) => region.devices).slice(index, index + 2)
+      }));
+      addLog("刷新远程测控页面", `共加载 ${regions.value.length} 个区域`);
+    })
+    .finally(() => {
+      loading.value = false;
+    });
 }
-const closeSchemeModal = () => { schemeModalVisible.value = false }
-
-// 区域相关
-const openRegionModal = () => { isEditRegion.value = false; regionForm.value = { name: '', location: '', controlMode: 'auto', unit: '' }; regionModalVisible.value = true }
-const saveRegion = () => {
-  if (isEditRegion.value) {
-    const index = regions.value.findIndex(r => r.id === regionForm.value.id)
-    if (index !== -1) regions.value[index] = { ...regionForm.value, devices: regions.value[index].devices }
-  } else {
-    const newId = Math.max(...regions.value.map(r => r.id), 0) + 1
-    regions.value.push({ ...regionForm.value, id: newId, devices: [], status: regionForm.value.controlMode === 'auto' ? 'auto' : 'manual' })
-  }
-  closeRegionModal()
-  alert('区域已保存')
-}
-const closeRegionModal = () => { regionModalVisible.value = false }
-
-// 事故预案相关
-const openIncidentModal = () => { isEditIncident.value = false; incidentForm.value = { name: '', type: 'leak', description: '', deviceIds: [], action: 'all_stop' }; incidentModalVisible.value = true }
-const editIncident = (incident) => { isEditIncident.value = true; incidentForm.value = { ...incident, deviceIds: [...incident.deviceIds] }; incidentModalVisible.value = true }
-const saveIncident = () => {
-  const devices = incidentForm.value.deviceIds.map(id => allDevices.value.find(d => d.id === id)).filter(d => d)
-  const incidentData = {
-    ...incidentForm.value,
-    devices: devices.map(d => ({ id: d.id, name: d.name }))
-  }
-  if (isEditIncident.value) {
-    const index = incidentPlans.value.findIndex(i => i.id === incidentForm.value.id)
-    if (index !== -1) incidentPlans.value[index] = incidentData
-  } else {
-    const newId = Math.max(...incidentPlans.value.map(i => i.id), 0) + 1
-    incidentPlans.value.push({ ...incidentData, id: newId })
-  }
-  closeIncidentModal()
-  alert('预案已保存')
+
+function selectRegion(id) {
+  selectedRegionId.value = id;
+  manualMode.value = currentRegion.value?.controlMode === "manual";
+  addLog("切换区域控制器", currentRegion.value?.name || "-");
 }
-const deleteIncident = (incident) => {
-  if (confirm(`删除预案"${incident.name}"?`)) {
-    incidentPlans.value = incidentPlans.value.filter(i => i.id !== incident.id)
-  }
+
+function saveAutoParams() {
+  addLog("保存自动控制参数", `${currentRegion.value?.name || "-"} 阈值已更新`);
 }
-const closeIncidentModal = () => { incidentModalVisible.value = false }
-
-// 触发事故预案
-const triggerIncident = (incident) => {
-  const devices = incident.devices
-  devices.forEach(device => {
-    const dev = allDevices.value.find(d => d.id === device.id)
-    if (dev) {
-      if (incident.action === 'all_stop' || incident.action === 'emergency_stop') {
-        dev.status = 'stopped'
-      } else if (incident.action === 'all_start') {
-        dev.status = 'running'
-      }
-    }
-  })
-  executionRecords.value.unshift({
-    id: Date.now(),
-    time: new Date().toLocaleString(),
-    incidentName: incident.name,
-    status: 'success'
-  })
-  addControlLog('预案系统', `触发预案:${incident.name}`, `${devices.length}台设备联动`, 'success')
-  alert(`预案"${incident.name}"已启动,${devices.length}台设备已联动响应`)
-}
-
-// 单设备控制
-const sendControlCommand = (device, command) => {
-  if (!isManualMode.value && controlMode.value === 'auto') {
-    alert('当前为自动控制模式,请切换到手动模式后操作')
-    return
-  }
-  if (command === 'start') {
-    device.status = 'running'
-    addControlLog('中控', '启动设备', device.name, 'success')
-    alert(`已启动设备: ${device.name}`)
-  } else if (command === 'stop') {
-    device.status = 'stopped'
-    addControlLog('中控', '停止设备', device.name, 'success')
-    alert(`已停止设备: ${device.name}`)
+
+function toggleControlMode() {
+  if (!currentRegion.value) {
+    return;
   }
+  currentRegion.value.controlMode = manualMode.value ? "manual" : "auto";
+  addLog("切换控制模式", `${currentRegion.value.name} -> ${manualMode.value ? "手动模式" : "自动模式"}`);
 }
 
-// 切换控制模式
-const toggleControlMode = () => {
-  if (currentRegion.value) {
-    currentRegion.value.controlMode = isManualMode.value ? 'manual' : 'auto'
+function sendControlCommand(device, command) {
+  if (!manualMode.value) {
+    addLog("控制拦截", "当前为自动模式,无法手动控制");
+    return;
   }
-  addControlLog('系统', `切换控制模式`, isManualMode.value ? '手动模式' : '自动模式', 'success')
+  device.status = command === "start" ? "running" : "stopped";
+  addLog(command === "start" ? "启动设备" : "停止设备", device.name);
 }
 
-// 设置设备参数
-const openSetValueModal = (device) => {
-  currentSetDevice.value = device
-  setValue.value = device.currentValue || 0
-  setValueModalVisible.value = true
+function openSetValueDialog(device) {
+  currentSetDevice.value = device;
+  setValue.value = device.currentValue || 0;
+  setValueOpen.value = true;
 }
-const submitSetValue = () => {
+
+function submitSetValue() {
   if (currentSetDevice.value) {
-    currentSetDevice.value.currentValue = setValue.value
-    addControlLog('中控', `设置参数`, `${currentSetDevice.value.name} = ${setValue.value}`, 'success')
-    alert(`已设置 ${currentSetDevice.value.name} 目标值为 ${setValue.value}`)
+    currentSetDevice.value.currentValue = setValue.value;
+    addLog("下发参数设置", `${currentSetDevice.value.name} -> ${setValue.value}`);
   }
-  closeSetValueModal()
-}
-const closeSetValueModal = () => { setValueModalVisible.value = false }
-
-// 快捷指令
-const quickCommand = (cmd) => {
-  if (cmd === '全部启动') {
-    currentDevices.value.forEach(d => d.status = 'running')
-    addControlLog('中控', '快捷指令', '全部启动', 'success')
-    alert('已发送全部启动指令')
-  } else if (cmd === '全部停止') {
-    currentDevices.value.forEach(d => d.status = 'stopped')
-    addControlLog('中控', '快捷指令', '全部停止', 'success')
-    alert('已发送全部停止指令')
-  } else if (cmd === '紧急停止') {
-    currentDevices.value.forEach(d => d.status = 'stopped')
-    addControlLog('中控', '紧急停止', '全部设备已停止', 'success')
-    alert('紧急停止指令已执行')
+  setValueOpen.value = false;
+}
+
+function quickCommand(command) {
+  if (!manualMode.value) {
+    addLog("快捷指令拦截", "请先切换到手动模式");
+    return;
   }
+  currentDevices.value.forEach((device) => {
+    device.status = command === "全部启动" ? "running" : "stopped";
+  });
+  addLog("执行快捷指令", command);
 }
 
-// 添加控制日志
-const addControlLog = (operator, action, target, result) => {
-  controlLogs.value.unshift({
-    id: Date.now(),
-    time: new Date().toLocaleTimeString(),
-    action: action,
-    target: target,
-    result: result
-  })
-  if (controlLogs.value.length > 20) controlLogs.value.pop()
+function applyScheme(scheme) {
+  const targetIds = new Set(scheme.deviceIds);
+  regions.value.forEach((region) => {
+    region.devices.forEach((device) => {
+      if (targetIds.has(device.equipmentId)) {
+        device.status = scheme.name.includes("节能") ? "stopped" : "running";
+      }
+    });
+  });
+  addLog("应用设备运行方案", scheme.name);
 }
 
-// 辅助函数
-const getIncidentTypeText = (type) => {
-  const map = { leak: '泄漏事故', fire: '火灾事故', pressure: '压力异常', maintenance: '养护维护' }
-  return map[type] || type
+function triggerIncident(plan) {
+  plan.devices.forEach((device) => {
+    const found = regions.value.flatMap((item) => item.devices).find((item) => item.id === device.id);
+    if (found) {
+      found.status = "stopped";
+    }
+  });
+  addLog("启动事故联动预案", plan.name);
 }
+
+onMounted(() => {
+  loadDashboard();
+});
 </script>
 
 <style scoped>
-* { margin: 0; padding: 0; box-sizing: border-box; }
-
-.prototype-container {
-  max-width: 1600px;
-  margin: 0 auto;
-  background: #f0f4f8;
-  border-radius: 28px;
-  box-shadow: 0 20px 35px -12px rgba(0, 0, 0, 0.15);
-  overflow: hidden;
-  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
-}
-
-/* 头部 */
-.header {
-  background: #0a2b3c;
-  padding: 16px 28px;
+.remote-page {
+  background: linear-gradient(180deg, #f3f7fd 0%, #f8fafc 100%);
+}
+
+.hero-panel {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  flex-wrap: wrap;
   gap: 16px;
+  margin-bottom: 16px;
+  padding: 20px 24px;
+  border-radius: 18px;
+  background: linear-gradient(135deg, #0f2742, #1d4f7d);
+  color: #fff;
+}
+
+.hero-title {
+  font-size: 24px;
+  font-weight: 700;
+}
+
+.hero-subtitle {
+  margin-top: 8px;
+  font-size: 13px;
+  color: rgba(255, 255, 255, 0.84);
+}
+
+.panel-card {
+  border-radius: 16px;
+  margin-bottom: 16px;
+}
+
+.card-header-row {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  gap: 12px;
 }
 
-.logo h2 { color: white; font-weight: 600; font-size: 1.3rem; }
-.logo p { color: #bdd8e8; font-size: 0.7rem; }
-.control-mode-badge { padding: 6px 18px; border-radius: 30px; font-weight: 500; font-size: 0.8rem; }
-.control-mode-badge.auto { background: #27ae60; color: white; }
-.control-mode-badge.manual { background: #3498db; color: white; }
-
-/* 布局 */
-.main-layout { display: flex; flex-wrap: wrap; }
-.left-panel { width: 320px; background: #f9fbfd; padding: 20px; }
-.middle-panel { flex: 1; padding: 20px; min-width: 400px; }
-.right-panel { width: 360px; background: #f9fbfd; padding: 20px; }
-
-/* 卡片 */
-.card { background: white; border-radius: 20px; border: 1px solid #e6edf3; margin-bottom: 20px; overflow: hidden; }
-.card-header { background: #f9fafc; padding: 14px 18px; border-bottom: 1px solid #eef2f6; font-weight: 700; font-size: 0.85rem; color: #1d5a78; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px; }
-.card-body { padding: 16px; }
-
-/* 区域列表 */
-.region-list { max-height: 250px; overflow-y: auto; }
-.region-item { background: #fef9f2; border-radius: 14px; padding: 12px; margin-bottom: 10px; cursor: pointer; border-left: 3px solid #3498db; }
-.region-item.active { background: #e3f0fd; border-left-color: #2980b9; }
-.region-name { font-weight: 700; font-size: 0.8rem; }
-.region-status { font-size: 0.65rem; margin-top: 4px; }
-
-/* 参数设置 */
-.param-group { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; }
-.param-label { font-size: 0.7rem; font-weight: 600; width: 70px; color: #2c5a72; }
-.param-input { flex: 1; padding: 6px 10px; border-radius: 10px; border: 1px solid #cbdde6; font-size: 0.7rem; }
-.param-unit { font-size: 0.7rem; color: #6f8ea8; width: 40px; }
-
-/* 方案列表 */
-.scheme-list { max-height: 200px; overflow-y: auto; }
-.scheme-item { background: #f8fafc; border-radius: 14px; padding: 12px; margin-bottom: 10px; border: 1px solid #eef2f6; }
-.scheme-name { font-weight: 700; font-size: 0.8rem; }
-.scheme-desc { font-size: 0.65rem; color: #6f8ea8; margin: 4px 0; }
-.scheme-actions { display: flex; gap: 6px; margin-top: 8px; }
-
-/* 设备控制列表 */
-.device-control-list { max-height: 320px; overflow-y: auto; }
-.device-control-item { background: #f8fafc; border-radius: 14px; padding: 12px; margin-bottom: 12px; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px; }
-.device-basic { flex: 1; }
-.device-name { font-weight: 700; font-size: 0.8rem; }
-.device-meta { font-size: 0.65rem; color: #6f8ea8; }
-.device-status { font-size: 0.65rem; margin-top: 4px; }
-.device-value { font-size: 0.7rem; font-weight: 600; color: #2c5a72; margin-top: 4px; }
-.device-control { display: flex; gap: 8px; }
-
-/* 状态点 */
-.status-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; margin-right: 6px; }
-.dot-auto { background: #27ae60; }
-.dot-manual { background: #3498db; }
-.dot-running { background: #27ae60; box-shadow: 0 0 4px #27ae60; }
-.dot-stopped { background: #95a5a6; }
-
-/* 手动控制面板 */
-.manual-control-panel { min-height: 180px; }
-.control-log { background: #1a2b3c; border-radius: 16px; padding: 12px; color: #a8d4e6; font-family: monospace; font-size: 0.7rem; max-height: 150px; overflow-y: auto; margin-bottom: 16px; }
-.log-item { padding: 4px 0; border-bottom: 1px solid #2a4a5a; display: flex; gap: 12px; }
-.log-time { color: #6f9eb0; width: 70px; }
-.log-action { color: #f1c40f; }
-.log-target { color: #a8d4e6; flex: 1; }
-.log-result.success { color: #27ae60; }
-.log-result.failed { color: #e74c3c; }
-.quick-title { font-size: 0.7rem; font-weight: 600; margin-bottom: 8px; }
-.quick-buttons { display: flex; gap: 8px; flex-wrap: wrap; }
-.auto-mode-hint { text-align: center; padding: 20px; color: #6f8ea8; }
-.hint-icon { font-size: 2rem; display: block; margin-bottom: 8px; }
-.hint-sub { font-size: 0.65rem; display: block; margin-top: 8px; color: #a0b8cc; }
-
-/* 事故预案列表 */
-.incident-list { max-height: 300px; overflow-y: auto; }
-.incident-item { background: white; border-radius: 14px; padding: 12px; margin-bottom: 12px; border-left: 4px solid; }
-.incident-item.leak { border-left-color: #e74c3c; }
-.incident-item.fire { border-left-color: #f39c12; }
-.incident-item.pressure { border-left-color: #3498db; }
-.incident-item.maintenance { border-left-color: #27ae60; }
-.incident-header { display: flex; justify-content: space-between; margin-bottom: 6px; }
-.incident-name { font-weight: 700; font-size: 0.8rem; }
-.incident-type { font-size: 0.6rem; padding: 2px 8px; border-radius: 20px; background: #eef2fa; }
-.incident-desc { font-size: 0.65rem; color: #6f8ea8; margin-bottom: 8px; }
-.incident-devices { font-size: 0.65rem; margin-bottom: 8px; }
-.devices-label { color: #2c5a72; }
-.incident-actions { display: flex; gap: 8px; }
-
-/* 执行记录 */
-.execution-list { max-height: 150px; overflow-y: auto; }
-.execution-item { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #eef2f6; font-size: 0.7rem; }
-.exec-time { color: #6f8ea8; }
-.exec-status.success { color: #27ae60; }
-
-/* 按钮样式 */
-.btn-primary { background: #3498db; border: none; color: white; padding: 6px 14px; border-radius: 30px; font-size: 0.7rem; cursor: pointer; }
-.btn-primary:hover { background: #2980b9; }
-.btn-outline { background: white; border: 1px solid #3498db; color: #3498db; padding: 5px 12px; border-radius: 30px; font-size: 0.65rem; cursor: pointer; }
-.btn-success { background: #27ae60; border: none; color: white; padding: 5px 12px; border-radius: 30px; font-size: 0.65rem; cursor: pointer; }
-.btn-danger { background: #e74c3c; border: none; color: white; padding: 5px 12px; border-radius: 30px; font-size: 0.65rem; cursor: pointer; }
-.btn-danger-outline { background: white; border: 1px solid #e74c3c; color: #e74c3c; padding: 4px 10px; border-radius: 30px; font-size: 0.65rem; cursor: pointer; }
-.btn-warning { background: #f39c12; border: none; color: white; padding: 5px 12px; border-radius: 30px; font-size: 0.65rem; cursor: pointer; }
-.btn-sm { padding: 4px 10px; font-size: 0.65rem; }
-
-/* 复选框列表 */
-.device-check-list { max-height: 150px; overflow-y: auto; border: 1px solid #eef2f6; border-radius: 12px; padding: 8px; }
-.device-check { display: block; padding: 4px 0; font-size: 0.7rem; }
-.mode-switch { display: flex; align-items: center; gap: 8px; cursor: pointer; }
-
-/* 模态框 */
-.modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 1000; }
-.modal-content { background: white; border-radius: 24px; width: 450px; max-width: 90%; padding: 24px; max-height: 85vh; overflow-y: auto; }
-.form-group { margin-bottom: 16px; }
-.form-label { font-size: 0.7rem; font-weight: 600; margin-bottom: 6px; display: block; color: #2c5a72; }
-.form-input, .form-select, .form-textarea { width: 100%; padding: 8px 12px; border-radius: 12px; border: 1px solid #cbdde6; font-size: 0.75rem; }
-.form-actions { display: flex; gap: 12px; justify-content: flex-end; margin-top: 20px; }
-.region-hint { font-size: 0.65rem; font-weight: normal; background: #eef2fa; padding: 2px 10px; border-radius: 20px; }
-</style>
+.card-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #1f2937;
+}
+
+.card-subtitle {
+  margin-top: 4px;
+  font-size: 12px;
+  color: #6b7280;
+}
+
+.region-list,
+.scheme-list,
+.plan-list,
+.log-list,
+.device-list {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.region-item,
+.scheme-item,
+.plan-item,
+.log-item,
+.device-item {
+  padding: 12px;
+  border-radius: 12px;
+  background: #f8fafc;
+  border: 1px solid #edf2f7;
+}
+
+.region-item {
+  cursor: pointer;
+}
+
+.region-item.active {
+  border-color: #60a5fa;
+  background: #eef6ff;
+}
+
+.region-name,
+.device-name,
+.scheme-name,
+.plan-name,
+.log-action {
+  font-size: 14px;
+  font-weight: 600;
+  color: #111827;
+}
+
+.region-meta,
+.region-status,
+.device-meta,
+.device-value,
+.scheme-desc,
+.scheme-footer,
+.plan-desc,
+.plan-devices,
+.log-time,
+.log-target {
+  margin-top: 4px;
+  font-size: 12px;
+  color: #6b7280;
+}
+
+.status-dot {
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  display: inline-block;
+  margin-right: 6px;
+}
+
+.dot-auto {
+  background: #16a34a;
+}
+
+.dot-manual {
+  background: #2563eb;
+}
+
+.device-actions {
+  display: flex;
+  gap: 8px;
+  flex-wrap: wrap;
+  margin-top: 10px;
+}
+
+.quick-actions {
+  display: flex;
+  gap: 8px;
+  margin-top: 16px;
+  flex-wrap: wrap;
+}
+
+.scheme-footer {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+@media (max-width: 768px) {
+  .hero-panel,
+  .card-header-row {
+    flex-direction: column;
+    align-items: flex-start;
+  }
+}
+</style>

+ 1 - 1
vite.config.js

@@ -31,7 +31,7 @@ export default defineConfig(({ mode, command }) => {
       proxy: {
         '/dev-api': {
           // target: 'http://192.168.110.235:8300/pipe',
-          target: 'http://localhost:8301',
+          target: 'http://111.23.174.45:8301',
           changeOrigin: true,
           rewrite: (p) => p.replace(/^\/dev-api/, '')
         }