فهرست منبع

feat(router): 统一首页路由名称并新增预警概览功能

- 将路由名称从 Index 更新为 MainAdminHome 以避免后端菜单路径冲突
- 在面包屑组件中同时支持 Index 和 MainAdminHome 路由名称
- 修改 portal 页面中的 enterBackend 方法默认跳转到 /index
- 更新 index 页面的 script setup 名称为 MainAdminHome
- 在侧边栏 Logo 组件中为收缩和展开状态都添加首页链接
- 新增历史预警概览页面,包含统计卡片、专项分布图表和预警明细表格
- 新增今日预警概况页面,显示未解除、已解除和总量统计数据
- 新增今日未处置预警页面,展示待关注预警清单
- 在预警处置页面添加设备编码筛选和显示功能
- 新增预警仪表板相关 API 接口文件
- 创建预警概览共享工具函数文件
林仔 1 هفته پیش
والد
کامیت
76a4869f75

+ 3 - 4
src/components/HeaderSearch/index.vue

@@ -19,7 +19,7 @@
 
 <script setup>
 import Fuse from 'fuse.js'
-import { getNormalPath } from '@/utils/ruoyi'
+import { joinRoutePath } from '@/utils/ruoyi'
 import { isHttp } from '@/utils/validate'
 import usePermissionStore from '@/store/modules/permission'
 
@@ -88,9 +88,8 @@ function generateRoutes(routes, basePath = '', prefixTitle = []) {
   for (const r of routes) {
     // skip hidden router
     if (r.hidden) { continue }
-    const p = r.path.length > 0 && r.path[0] === '/' ? r.path : '/' + r.path;
     const data = {
-      path: !isHttp(r.path) ? getNormalPath(basePath + p) : r.path,
+      path: !isHttp(r.path) ? joinRoutePath(basePath, r.path) : r.path,
       title: [...prefixTitle]
     }
 
@@ -184,4 +183,4 @@ watch(searchPool, (list) => {
     }
   }
 }
-</style>
+</style>

+ 4 - 3
src/layout/components/Sidebar/SidebarItem.vue

@@ -30,7 +30,7 @@
 <script setup>
 import { isExternal } from '@/utils/validate'
 import AppLink from './Link'
-import { getNormalPath } from '@/utils/ruoyi'
+import { getNormalPath, joinRoutePath } from '@/utils/ruoyi'
 
 const props = defineProps({
   // route object
@@ -85,11 +85,12 @@ function resolvePath(routePath, routeQuery) {
   if (isExternal(props.basePath)) {
     return props.basePath
   }
+  const path = joinRoutePath(props.basePath, routePath)
   if (routeQuery) {
     let query = JSON.parse(routeQuery);
-    return { path: getNormalPath(props.basePath + '/' + routePath), query: query }
+    return { path: getNormalPath(path), query: query }
   }
-  return getNormalPath(props.basePath + '/' + routePath)
+  return getNormalPath(path)
 }
 
 // 修复后的 hasTitle 函数

+ 4 - 4
src/layout/components/TagsView/index.vue

@@ -52,7 +52,7 @@
 
 <script setup>
 import ScrollPane from './ScrollPane'
-import { getNormalPath } from '@/utils/ruoyi'
+import { joinRoutePath } from '@/utils/ruoyi'
 import useTagsViewStore from '@/store/modules/tagsView'
 import useSettingsStore from '@/store/modules/settings'
 import usePermissionStore from '@/store/modules/permission'
@@ -151,7 +151,7 @@ function filterAffixTags(routes, basePath = '') {
   let tags = []
   routes.forEach(route => {
     if (route.meta && route.meta.affix) {
-      const tagPath = getNormalPath(basePath + '/' + route.path)
+      const tagPath = joinRoutePath(basePath, route.path)
       tags.push({
         fullPath: tagPath,
         path: tagPath,
@@ -160,7 +160,7 @@ function filterAffixTags(routes, basePath = '') {
       })
     }
     if (route.children) {
-      const tempTags = filterAffixTags(route.children, route.path)
+      const tempTags = filterAffixTags(route.children, joinRoutePath(basePath, route.path))
       if (tempTags.length >= 1) {
         tags = [...tags, ...tempTags]
       }
@@ -428,4 +428,4 @@ onMounted(() => {
     }
   }
 }
-</style>
+</style>

+ 18 - 6
src/store/modules/permission.js

@@ -4,6 +4,7 @@ import { getRouters } from '@/api/menu'
 import Layout from '@/layout/index'
 import ParentView from '@/components/ParentView'
 import InnerLink from '@/layout/components/InnerLink'
+import { joinRoutePath } from '@/utils/ruoyi'
 
 // 匹配views里面所有的.vue文件
 const modules = import.meta.glob('./../../views/**/*.vue')
@@ -59,7 +60,7 @@ const usePermissionStore = defineStore(
 function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
   return asyncRouterMap.filter(route => {
     if (type && route.children) {
-      route.children = filterChildren(route.children)
+      route.children = filterChildren(route.children, false, route.path)
     }
     if (route.component) {
       // Layout ParentView 组件特殊处理
@@ -83,15 +84,15 @@ function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
   })
 }
 
-function filterChildren(childrenMap, lastRouter = false) {
+function filterChildren(childrenMap, lastRouter = false, rootPath = '') {
   var children = []
   childrenMap.forEach((el, index) => {
     if (el.children && el.children.length) {
       if (el.component === 'ParentView' && !lastRouter) {
         el.children.forEach(c => {
-          c.path = el.path + '/' + c.path
+          c.path = toNestedRoutePath(joinRoutePath(el.path, c.path), rootPath)
           if (c.children && c.children.length) {
-            children = children.concat(filterChildren(c.children, c))
+            children = children.concat(filterChildren(c.children, c, rootPath))
             return
           }
           children.push(c)
@@ -100,9 +101,9 @@ function filterChildren(childrenMap, lastRouter = false) {
       }
     }
     if (lastRouter) {
-      el.path = lastRouter.path + '/' + el.path
+      el.path = toNestedRoutePath(joinRoutePath(lastRouter.path, el.path), rootPath)
       if (el.children && el.children.length) {
-        children = children.concat(filterChildren(el.children, el))
+        children = children.concat(filterChildren(el.children, el, rootPath))
         return
       }
     }
@@ -111,6 +112,17 @@ function filterChildren(childrenMap, lastRouter = false) {
   return children
 }
 
+function toNestedRoutePath(path, rootPath) {
+  const normalizedPath = String(path || '').replace(/^\/+|\/+$/g, '')
+  const normalizedRoot = String(rootPath || '').replace(/^\/+|\/+$/g, '')
+  if (!normalizedRoot) return normalizedPath
+  if (normalizedPath === normalizedRoot) return ''
+  if (normalizedPath.startsWith(`${normalizedRoot}/`)) {
+    return normalizedPath.slice(normalizedRoot.length + 1)
+  }
+  return normalizedPath
+}
+
 // 动态路由遍历,验证是否具备权限
 export function filterDynamicRoutes(routes) {
   const res = []

+ 31 - 0
src/utils/ruoyi.js

@@ -240,6 +240,37 @@ export function getNormalPath(p) {
   return res;
 }
 
+// 菜单数据可能是相对路径,也可能已经包含父级路径;两种格式统一合并。
+export function joinRoutePath(parentPath = '', childPath = '') {
+  const parentRaw = String(parentPath || '')
+  const childRaw = String(childPath || '')
+  const parent = parentRaw.replace(/^\/+|\/+$/g, '').split('/').filter(Boolean)
+  const child = childRaw.replace(/^\/+|\/+$/g, '').split('/').filter(Boolean)
+  if (!parent.length) return child.length ? `/${child.join('/')}` : ''
+  if (!child.length) return `/${parent.join('/')}`
+  if (childRaw.startsWith('/')) return `/${child.join('/')}`
+
+  // Prefer the longest suffix/prefix overlap. This handles mixed menu data such as
+  // parent="subSystem/lifeCompany/lstatistics" and child="lstatistics/trend".
+  const maxOverlap = Math.min(parent.length, child.length)
+  for (let overlap = maxOverlap; overlap > 0; overlap -= 1) {
+    const parentSuffix = parent.slice(parent.length - overlap)
+    const childPrefix = child.slice(0, overlap)
+    if (parentSuffix.every((segment, index) => segment === childPrefix[index])) {
+      return `/${parent.concat(child.slice(overlap)).join('/')}`
+    }
+  }
+
+  // A legacy record can contain the complete path while the immediate parent is
+  // only one segment (for example parent="lstatistics"). Keep that path intact.
+  for (let start = 0; start <= child.length - parent.length; start += 1) {
+    if (parent.every((segment, index) => segment === child[start + index])) {
+      return `/${child.join('/')}`
+    }
+  }
+  return `/${parent.concat(child).join('/')}`
+}
+
 // 验证是否为blob格式
 export function blobValidate(data) {
   return data.type !== 'application/json'

+ 1 - 1
src/views/subSystem/lifeCompany/lOverviewWarning/LTodayNot.vue

@@ -27,7 +27,7 @@
   </div>
 </template>
 
-<script setup>
+<script setup name="WarningTodayUnprocessed">
 import { computed, onMounted, ref } from 'vue'
 import { ElMessage } from 'element-plus'
 import { Refresh, Search } from '@element-plus/icons-vue'

+ 1 - 1
src/views/subSystem/lifeCompany/lOverviewWarning/Lhistory.vue

@@ -24,7 +24,7 @@
   </div>
 </template>
 
-<script setup>
+<script setup name="WarningHistoryOverview">
 import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
 import { ElMessage } from 'element-plus'
 import { Refresh, Search } from '@element-plus/icons-vue'

+ 1 - 1
src/views/subSystem/lifeCompany/lOverviewWarning/Ltoday.vue

@@ -27,7 +27,7 @@
   </div>
 </template>
 
-<script setup>
+<script setup name="WarningTodayOverview">
 import { computed, onMounted, ref } from 'vue'
 import { ElMessage } from 'element-plus'
 import { Refresh, Search } from '@element-plus/icons-vue'

+ 2 - 1
src/views/subSystem/lifeCompany/lOverviewWarning/dashboardShared.js

@@ -72,7 +72,8 @@ export function warningLevelCode(row) {
 
 export function warningStatusText(row) {
   const value = listValue(row, 'status', 'warningStatus', 'warning_status')
-  return warningStatusLabels[String(value)] || value || '未设置'
+  const normalizedValue = String(value || '').toUpperCase()
+  return warningStatusLabels[normalizedValue] || value || '未设置'
 }
 
 export function warningLevelTag(row) {

+ 668 - 609
src/views/subSystem/lifeCompany/lhome/lHome.vue

@@ -1,726 +1,785 @@
 <template>
-  <div class="prototype-container">
-    <!-- 头部区域 -->
-    <div class="app-header">
-      <div class="title-section">
-        <h1>
-          <i class="fas fa-file-alt"></i> 应急预案在线审批机制
-        </h1>
-        <p>领导 · 专家评审修正 | 科学高效 · 快速响应</p>
+  <div class="lifeline-home" v-loading="loading">
+    <header class="home-header">
+      <div class="header-copy">
+        <span class="header-kicker">城市生命线运行态势</span>
+        <h1>预警联动管理首页</h1>
+        <p>汇集供水、排水、燃气与窨井预警,集中掌握风险和处置进度。</p>
       </div>
-      <div class="badge-area">
-        <i class="fas fa-check-circle"></i> 先进性·科学性·高效性
+      <div class="header-tools">
+        <div class="date-block">
+          <strong>{{ currentDateLabel }}</strong>
+          <span>{{ currentTimeLabel }} · 数据更新 {{ lastUpdated || '未更新' }}</span>
+        </div>
+        <el-tooltip content="刷新首页数据" placement="bottom">
+          <el-button circle type="primary" :loading="loading" aria-label="刷新首页数据" @click="loadData">
+            <el-icon v-if="!loading"><Refresh /></el-icon>
+          </el-button>
+        </el-tooltip>
       </div>
-    </div>
-
-    <div class="dashboard-grid">
-      <!-- 左侧面板:审批流程设置 + 预案调整管理 -->
-      <div class="left-panel">
-        <!-- 1. 审批流程设置模块 -->
-        <div class="section-card">
-          <div class="section-header">
-            <h2><i class="fas fa-diagram-project"></i> 审批流程设置</h2>
-            <span class="tag">动态流转</span>
+    </header>
+
+    <el-alert
+      v-if="errorMessage"
+      :title="errorMessage"
+      :type="allRequestsFailed ? 'error' : 'warning'"
+      show-icon
+      closable
+      class="data-alert"
+      @close="errorMessage = ''"
+    >
+      <template #default>
+        <el-button link type="primary" @click="loadData">重新加载</el-button>
+      </template>
+    </el-alert>
+
+    <section class="metric-grid" aria-label="今日预警核心指标">
+      <button
+        v-for="metric in metricCards"
+        :key="metric.key"
+        type="button"
+        class="metric-card"
+        :style="{ '--tone': metric.color, '--tone-soft': metric.softColor }"
+        @click="navigate(metric.routeName)"
+      >
+        <span class="metric-icon"><el-icon><component :is="metric.icon" /></el-icon></span>
+        <span class="metric-content">
+          <span class="metric-label">{{ metric.label }}</span>
+          <strong>{{ formatCount(metric.value) }}</strong>
+          <small>{{ metric.note }}</small>
+        </span>
+        <el-icon class="metric-arrow"><ArrowRight /></el-icon>
+      </button>
+    </section>
+
+    <div class="analysis-grid">
+      <section class="panel trend-panel">
+        <div class="panel-header">
+          <div>
+            <h2>近 7 日预警趋势</h2>
+            <p>四类设施非草稿预警的每日变化</p>
           </div>
-          <div class="section-content">
-            <div class="flow-steps">
-              <div
-                  v-for="(step, idx) in flowSteps"
-                  :key="idx"
-                  class="flow-node"
-                  :class="{ 'active': step.status === 'active', 'pending': step.status === 'pending' }"
-              >
-                <div class="node-icon"><i :class="step.icon"></i></div>
-                <div class="node-info">
-                  <div class="node-title">{{ step.title }}</div>
-                  <div class="node-desc">{{ step.desc }}</div>
-                </div>
-                <div class="node-badge">
-                  <i :class="step.status === 'active' ? 'fas fa-check-circle' : 'fas fa-clock'"></i>
-                  {{ step.statusText }}
-                </div>
-              </div>
-            </div>
-            <div class="add-flow-btn">
-              <i class="fas fa-plus-circle"></i> 自定义审批节点 / 会签设置
-            </div>
-            <div class="helper-text">
-              <i class="fas fa-info-circle"></i> 支持串签、并签、转审,全审批流转记录留痕
-            </div>
+          <el-button link type="primary" @click="navigate('WarningTrend')">
+            趋势分析<el-icon><ArrowRight /></el-icon>
+          </el-button>
+        </div>
+        <div ref="trendChartRef" class="chart-box" />
+        <el-empty v-if="!trendRows.length && !loading" description="近 7 日暂无预警数据" :image-size="74" />
+      </section>
+
+      <section class="panel status-panel">
+        <div class="panel-header">
+          <div>
+            <h2>近 30 日处置状态</h2>
+            <p>各处置阶段预警数量分布</p>
           </div>
+          <el-button link type="primary" @click="navigate('WarningDisposeStatus')">
+            查看分析<el-icon><ArrowRight /></el-icon>
+          </el-button>
         </div>
+        <div ref="statusChartRef" class="chart-box" />
+        <el-empty v-if="!statusRows.length && !loading" description="当前周期暂无状态数据" :image-size="74" />
+      </section>
+    </div>
 
-        <!-- 3. 预案调整管理 -->
-        <div class="section-card">
-          <div class="section-header">
-            <h2><i class="fas fa-sliders-h"></i> 预案调整管理</h2>
-            <i class="fas fa-sync-alt" style="color:#6c86a3;"></i>
-          </div>
-          <div class="section-content">
-            <div
-                v-for="adjust in adjustments"
-                :key="adjust.id"
-                class="adjust-item"
-            >
-              <div class="adjust-title">
-                <span><i class="fas fa-pen-nib"></i> {{ adjust.name }}</span>
-                <span class="status-badge" :class="adjust.statusClass">{{ adjust.statusText }}</span>
-              </div>
-              <div class="adjust-detail">
-                {{ adjust.detail }}
-              </div>
-            </div>
-            <div class="add-flow-btn" style="background:#f1f5f9; margin-top:10px;">
-              <i class="fas fa-edit"></i> 基于审批结果快速调整预案版本
-            </div>
-          </div>
+    <section class="panel facility-panel">
+      <div class="panel-header">
+        <div>
+          <h2>设施预警分布</h2>
+          <p>历史非草稿预警累计 {{ formatCount(facilityTotal) }} 条</p>
         </div>
+        <el-button link type="primary" @click="navigate('WarningHistoryOverview')">
+          历史预警概况<el-icon><ArrowRight /></el-icon>
+        </el-button>
       </div>
-
-      <!-- 右侧面板:预案在线审批 + 预案发布 -->
-      <div class="right-panel">
-        <!-- 2. 预案在线审批模块 -->
-        <div class="section-card">
-          <div class="section-header">
-            <h2><i class="fas fa-check-double"></i> 预案在线审批</h2>
-            <span class="tag">待办{{ pendingCount }}项</span>
+      <div class="facility-grid">
+        <article
+          v-for="facility in facilityStats"
+          :key="facility.value"
+          class="facility-card"
+          :style="{ '--facility-color': facility.color, '--facility-soft': facility.softColor }"
+        >
+          <div class="facility-title">
+            <span class="facility-mark">{{ facility.shortLabel }}</span>
+            <div><strong>{{ facility.label }}</strong><small>历史预警</small></div>
           </div>
-          <div class="section-content">
-            <table class="data-table">
-              <thead>
-              <tr>
-                <th>预案名称</th>
-                <th>审批人员</th>
-                <th>审批记录/意见</th>
-                <th>状态</th>
-                <th>操作</th>
-              </tr>
-              </thead>
-              <tbody>
-              <tr v-for="item in approvalList" :key="item.id">
-                <td>
-                  <strong>{{ item.name }}</strong><br>
-                  <span class="version-text">{{ item.version }}</span>
-                </td>
-                <td>{{ item.approvers }}</td>
-                <td class="comment-text">
-                  <i class="fas fa-comment-dots"></i> {{ item.comment }}
-                </td>
-                <td>
-                  <span class="status-badge" :class="item.statusClass">{{ item.statusText }}</span>
-                </td>
-                <td>
-                  <i class="fas fa-eye btn-icon"></i>
-                  <i class="fas fa-check-circle btn-icon" style="color:#2c7da0;"></i>
-                </td>
-              </tr>
-              </tbody>
-            </table>
-            <div class="helper-text" style="text-align: right; margin-top: 12px;">
-              <i class="fas fa-archive"></i> 完整审批日志可追溯 &nbsp;|&nbsp; 所有意见留痕
-            </div>
+          <div class="facility-value">
+            <strong>{{ formatCount(facility.count) }}</strong>
+            <span>{{ formatPercent(facility.percentage) }}</span>
           </div>
-        </div>
-
-        <!-- 4. 预案发布模块 -->
-        <div class="section-card">
-          <div class="section-header">
-            <h2><i class="fas fa-rocket"></i> 预案发布</h2>
-            <i class="fas fa-globe-asia" style="color:#3083a2;"></i>
-          </div>
-          <div class="section-content">
-            <div class="publish-card">
-              <div class="publish-info">
-                <h4><i class="fas fa-check-circle" style="color:#15803d;"></i> 已发布预案 (应急处置可用)</h4>
-                <p style="font-size:0.8rem; margin-top: 8px;">
-                  {{ publishedPlans.map(p => p.name).join(' · ') || '暂无已发布预案' }}
-                </p>
-              </div>
-              <div class="publish-btn disabled">仅展示已发布</div>
-            </div>
-
-            <div style="margin-top: 20px;">
-              <div class="pending-publish-card">
-                <div>
-                  <i class="fas fa-spinner fa-pulse" style="color:#e6b422;"></i>
-                  <strong>待发布预案(审批完毕)</strong>
-                  <br>
-                  <span class="pending-list">{{ pendingPublish.map(p => p.name).join(' | ') || '无' }}</span>
-                </div>
-                <span class="publish-action" @click="publishPlan">
-                  一键发布 <i class="fas fa-arrow-right"></i>
-                </span>
-              </div>
-            </div>
-
-            <div class="helper-text" style="margin-top: 20px; background:#eef6fb; padding: 12px; border-radius: 18px;">
-              <i class="fas fa-bolt"></i> <strong>快速响应保障:</strong> 已发布的预案将在应急指挥中心实时同步,支持一键启动应急预案、调集资源。
-            </div>
+          <div class="facility-progress"><i :style="{ width: `${facility.percentage}%` }" /></div>
+        </article>
+      </div>
+    </section>
+
+    <div class="operations-grid">
+      <section class="panel warning-panel">
+        <div class="panel-header">
+          <div>
+            <h2>重点未处置预警</h2>
+            <p>按预警级别和等待时长优先展示,共 {{ formatCount(unprocessedTotal) }} 条</p>
           </div>
+          <el-button link type="primary" @click="navigate('WarningTodayUnprocessed')">
+            查看全部<el-icon><ArrowRight /></el-icon>
+          </el-button>
         </div>
-
-        <!-- 评审过程 & 结果记录 -->
-        <div class="section-card" style="margin-bottom:0;">
-          <div class="section-header">
-            <h2><i class="fas fa-clipboard-list"></i> 评审过程 & 结果记录</h2>
-          </div>
-          <div class="section-content">
-            <div style="display: flex; gap: 12px; flex-wrap: wrap;">
-              <div class="record-card" v-for="record in reviewRecords" :key="record.id">
-                <i class="fas fa-calendar-alt"></i> <strong>{{ record.title }}</strong>
-                <div class="record-desc">{{ record.content }}</div>
+        <el-table
+          :data="unprocessedRows"
+          row-key="warningId"
+          class="warning-table"
+          empty-text="当前暂无未处置预警"
+          @row-click="openDetail"
+        >
+          <el-table-column label="级别" width="122">
+            <template #default="{ row }">
+              <el-tag :type="warningLevelTag(row)" effect="light" size="small">
+                {{ warningLevelText(row) }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="预警事项" min-width="190" show-overflow-tooltip>
+            <template #default="{ row }">
+              <div class="warning-name">
+                <i :class="`level-${warningLevelCode(row)}`" />
+                <span>{{ listValue(row, 'warningName', 'warning_name') || '未命名预警' }}</span>
               </div>
-            </div>
-            <div class="helper-text" style="margin-top: 12px;">
-              <i class="fas fa-database"></i> 所有评审节点自动保存至历史版本库,支持追溯、对比、复盘。
-            </div>
+            </template>
+          </el-table-column>
+          <el-table-column label="设施类型" width="110">
+            <template #default="{ row }">{{ warningTypeText(row) }}</template>
+          </el-table-column>
+          <el-table-column label="权属单位" min-width="150" show-overflow-tooltip>
+            <template #default="{ row }">{{ listValue(row, 'ownershipUnit', 'ownership_unit') || '未设置' }}</template>
+          </el-table-column>
+          <el-table-column label="等待时长" width="105">
+            <template #default="{ row }"><span class="waiting-time">{{ waitingText(row) }}</span></template>
+          </el-table-column>
+          <el-table-column label="状态" width="94" align="center">
+            <template #default="{ row }">
+              <el-tag :type="warningStatusTag(row)" effect="plain" size="small">
+                {{ warningStatusText(row) }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" width="84" fixed="right" align="center">
+            <template #default="{ row }">
+              <el-button link type="primary" @click.stop="openDetail(row)">详情</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </section>
+
+      <aside class="panel quick-panel">
+        <div class="panel-header">
+          <div>
+            <h2>常用功能</h2>
+            <p>快速进入预警查询与处置工作</p>
           </div>
         </div>
-      </div>
+        <div class="quick-list">
+          <button
+            v-for="action in quickActions"
+            :key="action.routeName"
+            type="button"
+            class="quick-action"
+            @click="navigate(action.routeName)"
+          >
+            <span class="quick-icon" :style="{ color: action.color, background: action.softColor }">
+              <el-icon><component :is="action.icon" /></el-icon>
+            </span>
+            <span><strong>{{ action.label }}</strong><small>{{ action.note }}</small></span>
+            <el-icon class="quick-arrow"><ArrowRight /></el-icon>
+          </button>
+        </div>
+      </aside>
     </div>
 
-    <!-- 底部信息 -->
-    <div class="footer-bar">
-      <div style="display: flex; gap: 24px;">
-        <span><i class="fas fa-chart-line" style="color:#1e6f5c;"></i> 先进性 · 动态评审机制</span>
-        <span><i class="fas fa-flask"></i> 科学性 · 专家逐条修正</span>
-        <span><i class="fas fa-tachometer-alt"></i> 高效性 · 快速响应闭环</span>
-      </div>
-      <div style="font-size:0.7rem; color:#66809a;">
-        <i class="far fa-clock"></i> 审批流转引擎就绪 · 提升应急处置敏捷度
-      </div>
-    </div>
+    <WarningDetailDrawer v-model="detailVisible" :row="selectedRow" />
   </div>
 </template>
 
 <script setup>
-import { reactive, computed } from 'vue'
-
-// 审批流程步骤配置
-const flowSteps = reactive([
-  {
-    title: '环节 1:预案起草人提交',
-    desc: '审批人: 安全总监 · 权限: 初审/退回',
-    icon: 'fas fa-user-tie',
-    status: 'active',
-    statusText: '已激活'
-  },
-  {
-    title: '环节 2:专家技术评审',
-    desc: '审批人: 专家组(3人) · 权限: 修正建议/通过',
-    icon: 'fas fa-chalkboard-user',
-    status: 'active',
-    statusText: '进行中'
-  },
-  {
-    title: '环节 3:领导层终审',
-    desc: '审批人: 应急总指挥 · 权限: 批准/驳回',
-    icon: 'fas fa-landmark',
-    status: 'pending',
-    statusText: '待触发'
-  },
-  {
-    title: '环节 4:签发与归档',
-    desc: '办公室自动化发布 · 记录存档',
-    icon: 'fas fa-cloud-upload-alt',
-    status: 'pending',
-    statusText: '流程终点'
-  }
-])
+import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
+import { useRouter } from 'vue-router'
+import { ElMessage } from 'element-plus'
+import {
+  ArrowRight,
+  Bell,
+  CircleCheck,
+  Clock,
+  DataAnalysis,
+  Refresh,
+  Setting,
+  TrendCharts,
+  Warning
+} from '@element-plus/icons-vue'
+import * as echarts from 'echarts'
+import {
+  getWarningTodayOverview,
+  pageWarningHistory,
+  pageWarningTodayUnprocessed
+} from '@/api/warning/dashboard'
+import { getDisposeStatusStat, getWarningTrendByDay } from '@/api/warning/statistics'
+import WarningDetailDrawer from '../lOverviewWarning/WarningDetailDrawer.vue'
+import {
+  dashboardData,
+  listValue,
+  pageRecords,
+  pageTotal,
+  warningLevelCode,
+  warningLevelTag,
+  warningLevelText,
+  warningStatusTag,
+  warningStatusText,
+  warningTypeText
+} from '../lOverviewWarning/dashboardShared'
+import { labelOf, responseData, statusLabels } from '../lstatistics/statisticsShared'
+
+const router = useRouter()
+const loading = ref(false)
+const errorMessage = ref('')
+const allRequestsFailed = ref(false)
+const lastUpdated = ref('')
+const now = ref(new Date())
+const todayOverview = ref({})
+const unprocessedRows = ref([])
+const unprocessedTotal = ref(null)
+const trendRows = ref([])
+const statusRows = ref([])
+const detailVisible = ref(false)
+const selectedRow = ref(null)
+const trendChartRef = ref()
+const statusChartRef = ref()
+let trendChart
+let statusChart
+let clockTimer
+let resizeObserver
+
+const facilityDefinitions = [
+  { value: 'WATER_PIPE', label: '供水管网', shortLabel: '供', color: '#2563eb', softColor: '#eff6ff' },
+  { value: 'SEWER_PIPE', label: '排水管网', shortLabel: '排', color: '#0f766e', softColor: '#ecfdf5' },
+  { value: 'GAS_PIPE', label: '燃气管网', shortLabel: '燃', color: '#d97706', softColor: '#fffbeb' },
+  { value: 'MANHOLE', label: '窨井', shortLabel: '井', color: '#64748b', softColor: '#f1f5f9' }
+]
+
+const facilityCounts = ref(Object.fromEntries(facilityDefinitions.map(item => [item.value, null])))
+
+const facilityTotal = computed(() => {
+  const values = Object.values(facilityCounts.value).filter(value => Number.isFinite(value))
+  return values.length ? values.reduce((total, value) => total + value, 0) : null
+})
 
-// 预案调整数据
-const adjustments = reactive([
-  {
-    id: 1,
-    name: '洪涝灾害应急预案 V2.1',
-    statusText: '基于专家意见调整',
-    statusClass: 'secondary',
-    detail: '调整内容: 根据刘宏专家评审意见,优化预警阈值及撤离路线;增加物资调度流程。评审结果: “科学性提升,建议补充应急通讯保障” — 已修正。'
-  },
-  {
-    id: 2,
-    name: '危化品泄漏专项预案',
-    statusText: '修改中',
-    statusClass: 'warning',
-    detail: '根据领导层反馈: 需强化现场指挥架构,新增“紧急疏散图”附件,当前版本迭代至3.0。'
-  },
-  {
-    id: 3,
-    name: '地震应急响应预案',
-    statusText: '审批通过待调整细节',
-    statusClass: '',
-    detail: '最终评审批示: 修正响应分级条件,将震级启动标准细化,已按专家组合议完成修订。'
-  }
-])
+const facilityStats = computed(() => facilityDefinitions.map(item => {
+  const count = facilityCounts.value[item.value]
+  const percentage = Number.isFinite(count) && facilityTotal.value > 0
+    ? Number(((count / facilityTotal.value) * 100).toFixed(1))
+    : 0
+  return { ...item, count, percentage }
+}))
 
-// 审批列表数据
-const approvalList = reactive([
+const metricCards = computed(() => [
   {
-    id: 1,
-    name: '洪涝灾害应急预案',
-    version: 'v2.2',
-    approvers: '专家组: 王宏,李敏,张启\n领导: 陈卫国',
-    comment: '专家李敏: 响应等级描述建议更精确,已采纳。领导陈卫国: 同意专家组意见,补充响应时效。',
-    statusText: '终审中',
-    statusClass: ''
+    key: 'today', label: '今日预警', value: numericValue(todayOverview.value.todayTotal),
+    note: '今日非草稿预警', routeName: 'WarningTodayOverview', icon: Bell,
+    color: '#2563eb', softColor: '#eff6ff'
   },
   {
-    id: 2,
-    name: '危化品泄漏专项预案',
-    version: 'v3.1',
-    approvers: '安全总监: 赵岩\n外部专家: 刘景明',
-    comment: '审批记录: 安全总监初审通过, 专家提出“新增化学洗消流程”修正要求,预案调整中。',
-    statusText: '待修正',
-    statusClass: 'warning'
+    key: 'unresolved', label: '当前未解除', value: numericValue(todayOverview.value.unresolvedCount),
+    note: '截至当前仍在跟进', routeName: 'WarningTodayOverview', icon: Warning,
+    color: '#dc2626', softColor: '#fef2f2'
   },
   {
-    id: 3,
-    name: '公共卫生事件应急预案',
-    version: 'v1.5',
-    approvers: '应急办: 周敏, 疾控专家: 何秀英',
-    comment: '审批意见: 何秀英专家评审通过;领导复核要求更新隔离标准,修正后重新提交审批。',
-    statusText: '二次评审',
-    statusClass: 'secondary'
+    key: 'resolved', label: '今日已解除', value: numericValue(todayOverview.value.resolvedCount),
+    note: '今日完成风险解除', routeName: 'WarningTodayOverview', icon: CircleCheck,
+    color: '#059669', softColor: '#ecfdf5'
   },
   {
-    id: 4,
-    name: '大面积停电应急预案',
-    version: 'v2.0',
-    approvers: '专家组: 电力工程师+总指挥',
-    comment: '审批记录: 全票通过先进性评估,目前进入发布前最终确认环节。',
-    statusText: '审批通过',
-    statusClass: ''
+    key: 'unprocessed', label: '未处置预警', value: unprocessedTotal.value,
+    note: '需要重点督办', routeName: 'WarningTodayUnprocessed', icon: Clock,
+    color: '#d97706', softColor: '#fffbeb'
   }
 ])
 
-// 已发布的预案
-const publishedPlans = reactive([
-  { id: 1, name: '地震应急响应预案 (2025-01-发布)' }
-])
+const quickActions = [
+  { label: '今日预警概况', note: '查看今日警情明细', routeName: 'WarningTodayOverview', icon: Bell, color: '#2563eb', softColor: '#eff6ff' },
+  { label: '今日未处置预警', note: '跟进待处理警情', routeName: 'WarningTodayUnprocessed', icon: Warning, color: '#dc2626', softColor: '#fef2f2' },
+  { label: '历史预警概况', note: '查看设施预警分布', routeName: 'WarningHistoryOverview', icon: DataAnalysis, color: '#0f766e', softColor: '#ecfdf5' },
+  { label: '处置状态分析', note: '分析处置进度', routeName: 'WarningDisposeStatus', icon: TrendCharts, color: '#7c3aed', softColor: '#f5f3ff' },
+  { label: '预警事项管理', note: '维护预警清单配置', routeName: 'WarningItemManage', icon: Setting, color: '#475569', softColor: '#f1f5f9' }
+]
 
-// 待发布预案(审批完毕但尚未发布)
-const pendingPublish = reactive([
-  { id: 4, name: '大面积停电应急预案' },
-  { id: 3, name: '公共卫生事件应急预案' }
-])
+const currentDateLabel = computed(() => new Intl.DateTimeFormat('zh-CN', {
+  year: 'numeric', month: 'long', day: 'numeric', weekday: 'long'
+}).format(now.value))
 
-// 评审记录
-const reviewRecords = reactive([
-  {
-    id: 1,
-    title: '2025-04-10 专家评审会',
-    content: '评审意见:预案结构科学,响应衔接效率高,建议增加辅助决策地图。→ 记录已归档,预案已完成修正。'
-  },
-  {
-    id: 2,
-    title: '领导终审记录',
-    content: '最终结果:同意发布,强调实战演练与预案结合。结果存入档案,提升先进性指标。'
-  }
-])
-
-// 计算待办数量(状态为终审中/待修正/二次评审的数量)
-const pendingCount = computed(() => {
-  return approvalList.filter(item =>
-      item.statusText === '终审中' || item.statusText === '待修正' || item.statusText === '二次评审'
-  ).length
-})
-
-// 发布预案功能:将待发布预案移至已发布
-const publishPlan = () => {
-  if (pendingPublish.length === 0) {
-    alert('当前没有待发布的预案')
-    return
-  }
-  // 将待发布预案添加到已发布列表
-  pendingPublish.forEach(plan => {
-    if (!publishedPlans.some(p => p.name === plan.name)) {
-      publishedPlans.push({ id: plan.id, name: `${plan.name} (刚刚发布)` })
-    }
-  })
-  // 清空待发布列表
-  pendingPublish.splice(0, pendingPublish.length)
-
-  // 同时更新审批列表中的对应预案状态为“已发布”
-  approvalList.forEach(item => {
-    if (item.name === '大面积停电应急预案' || item.name === '公共卫生事件应急预案') {
-      item.statusText = '已发布'
-      item.statusClass = ''
-    }
-  })
+const currentTimeLabel = computed(() => new Intl.DateTimeFormat('zh-CN', {
+  hour: '2-digit', minute: '2-digit', hour12: false
+}).format(now.value))
 
-  // 可选:提示发布成功
-  alert('预案已成功发布,应急处置中可立即启用!')
+function numericValue(value) {
+  if (value === null || value === undefined || value === '') return null
+  const number = Number(value)
+  return Number.isFinite(number) ? number : null
 }
-</script>
 
-<style scoped>
-* {
-  margin: 0;
-  padding: 0;
-  box-sizing: border-box;
+function formatCount(value) {
+  return Number.isFinite(value) ? new Intl.NumberFormat('zh-CN').format(value) : '-'
 }
 
-.prototype-container {
-  max-width: 1440px;
-  margin: 0 auto;
-  background: #ffffff;
-  border-radius: 32px;
-  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.15);
-  overflow: hidden;
-  font-family: 'Inter', 'Nunito', system-ui, -apple-system, sans-serif;
+function formatPercent(value) {
+  return facilityTotal.value > 0 ? `${Number(value || 0).toFixed(1)}%` : '-'
 }
 
-.app-header {
-  background: linear-gradient(135deg, #0f2b3d 0%, #1b4a6e 100%);
-  padding: 24px 32px;
-  color: white;
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  flex-wrap: wrap;
-  gap: 16px;
+function pad(value) {
+  return String(value).padStart(2, '0')
 }
 
-.title-section h1 {
-  font-size: 1.8rem;
-  font-weight: 700;
-  display: flex;
-  align-items: center;
-  gap: 12px;
+function formatApiDate(date) {
+  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
 }
 
-.title-section h1 i {
-  font-size: 1.8rem;
-  color: #ffd966;
+function rangeForDays(days) {
+  const end = new Date()
+  const start = new Date(end)
+  start.setHours(0, 0, 0, 0)
+  start.setDate(start.getDate() - days + 1)
+  return { startTime: formatApiDate(start), endTime: formatApiDate(end) }
 }
 
-.title-section p {
-  font-size: 0.85rem;
-  opacity: 0.85;
-  margin-top: 6px;
+function recentDateKeys(days = 7) {
+  const result = []
+  const cursor = new Date()
+  cursor.setHours(0, 0, 0, 0)
+  cursor.setDate(cursor.getDate() - days + 1)
+  for (let index = 0; index < days; index += 1) {
+    result.push(`${cursor.getFullYear()}-${pad(cursor.getMonth() + 1)}-${pad(cursor.getDate())}`)
+    cursor.setDate(cursor.getDate() + 1)
+  }
+  return result
 }
 
-.badge-area {
-  background: rgba(255, 255, 240, 0.15);
-  backdrop-filter: blur(4px);
-  padding: 8px 20px;
-  border-radius: 40px;
-  font-size: 0.9rem;
-  font-weight: 500;
+function warningCount(row) {
+  return Number(row.warning_count ?? row.warningCount ?? row.status_count ?? row.statusCount ?? 0)
 }
 
-.dashboard-grid {
-  display: grid;
-  grid-template-columns: 1fr 1.4fr;
-  gap: 0;
-  border-top: 1px solid #e9edf2;
+function renderTrendChart() {
+  if (!trendChartRef.value) return
+  trendChart ||= echarts.init(trendChartRef.value)
+  if (!trendRows.value.length) {
+    trendChart.clear()
+    return
+  }
+  const dates = recentDateKeys()
+  const totals = Object.fromEntries(dates.map(date => [date, 0]))
+  trendRows.value.forEach(row => {
+    const date = String(row.stat_date ?? row.statDate ?? '').slice(0, 10)
+    if (date in totals) totals[date] += warningCount(row)
+  })
+  trendChart.setOption({
+    animationDuration: 450,
+    color: ['#2563eb'],
+    tooltip: { trigger: 'axis', valueFormatter: value => `${value} 条` },
+    grid: { left: 48, right: 22, top: 24, bottom: 38 },
+    xAxis: {
+      type: 'category', boundaryGap: false, data: dates.map(date => date.slice(5)),
+      axisLine: { lineStyle: { color: '#d9e1ea' } }, axisLabel: { color: '#64748b' }
+    },
+    yAxis: {
+      type: 'value', minInterval: 1, axisLabel: { color: '#64748b' },
+      splitLine: { lineStyle: { color: '#edf1f5' } }
+    },
+    series: [{
+      name: '预警数量', type: 'line', smooth: true, symbol: 'circle', symbolSize: 7,
+      lineStyle: { width: 3 }, itemStyle: { borderColor: '#ffffff', borderWidth: 2 },
+      areaStyle: { color: 'rgba(37, 99, 235, 0.10)' }, data: dates.map(date => totals[date])
+    }]
+  }, true)
+}
+
+function renderStatusChart() {
+  if (!statusChartRef.value) return
+  statusChart ||= echarts.init(statusChartRef.value)
+  const data = statusRows.value.map(row => ({
+    name: labelOf(statusLabels, row.status), value: warningCount(row), status: String(row.status || '').toUpperCase()
+  })).filter(item => item.value > 0)
+  if (!data.length) {
+    statusChart.clear()
+    return
+  }
+  const colorByStatus = {
+    PENDING: '#dc2626', PROCESSING: '#d97706', RELEASED: '#2563eb',
+    HANDLED: '#0f766e', CLOSED: '#059669', RESOLVED: '#059669'
+  }
+  statusChart.setOption({
+    tooltip: { trigger: 'item', formatter: '{b}<br/>{c} 条({d}%)' },
+    legend: { type: 'scroll', bottom: 0, left: 'center', textStyle: { color: '#64748b' } },
+    series: [{
+      type: 'pie', radius: ['46%', '68%'], center: ['50%', '43%'], avoidLabelOverlap: true,
+      itemStyle: { borderColor: '#ffffff', borderWidth: 3 }, label: { show: false },
+      emphasis: { label: { show: true, fontSize: 14, fontWeight: 600 } },
+      data: data.map(item => ({
+        name: item.name, value: item.value,
+        itemStyle: { color: colorByStatus[item.status] || '#64748b' }
+      }))
+    }]
+  }, true)
+}
+
+function waitingText(row) {
+  const hours = numericValue(listValue(row, 'waitingHours', 'waiting_hours'))
+  if (hours === null) return '-'
+  if (hours < 1) return '< 1 小时'
+  if (hours < 24) return `${hours.toFixed(hours >= 10 ? 0 : 1)} 小时`
+  return `${Math.floor(hours / 24)} 天 ${Math.round(hours % 24)} 小时`
+}
+
+function openDetail(row) {
+  selectedRow.value = row
+  detailVisible.value = true
+}
+
+function navigate(routeName) {
+  const routeCandidates = router.getRoutes().filter(route => route.name === routeName)
+  if (!routeCandidates.length) {
+    ElMessage.warning('当前账号暂无该功能菜单权限')
+    return
+  }
+  // Legacy databases can temporarily expose the same route name at root level
+  // and under the subsystem. Prefer the scoped route when both are available.
+  const preferredRoute = routeCandidates.sort((left, right) => {
+    const leftScoped = left.path.startsWith('/subSystem/') ? 1 : 0
+    const rightScoped = right.path.startsWith('/subSystem/') ? 1 : 0
+    return rightScoped - leftScoped || right.path.length - left.path.length
+  })[0]
+  router.push(preferredRoute.path).catch(() => {})
+}
+
+async function loadData() {
+  if (loading.value) return
+  loading.value = true
+  errorMessage.value = ''
+  const requests = [
+    { key: 'today', label: '今日概况', promise: getWarningTodayOverview() },
+    { key: 'unprocessed', label: '未处置清单', promise: pageWarningTodayUnprocessed({ pageNum: 1, pageSize: 6 }) },
+    { key: 'trend', label: '预警趋势', promise: getWarningTrendByDay(rangeForDays(7)) },
+    { key: 'status', label: '处置状态', promise: getDisposeStatusStat(rangeForDays(30)) },
+    ...facilityDefinitions.map(item => ({
+      key: item.value, label: item.label,
+      promise: pageWarningHistory({ pageNum: 1, pageSize: 1, warningType: item.value })
+    }))
+  ]
+
+  try {
+    const results = await Promise.allSettled(requests.map(item => item.promise))
+    const failedLabels = []
+    let successCount = 0
+    let generatedAt = ''
+
+    results.forEach((result, index) => {
+      const request = requests[index]
+      if (result.status === 'rejected') {
+        failedLabels.push(request.label)
+        return
+      }
+      successCount += 1
+      if (request.key === 'today') {
+        todayOverview.value = dashboardData(result.value, {})
+        generatedAt = todayOverview.value.generatedAt || generatedAt
+      } else if (request.key === 'unprocessed') {
+        const data = dashboardData(result.value, {})
+        unprocessedRows.value = pageRecords(result.value)
+        unprocessedTotal.value = pageTotal(result.value)
+        generatedAt = data.generatedAt || generatedAt
+      } else if (request.key === 'trend') {
+        trendRows.value = responseData(result.value, [])
+      } else if (request.key === 'status') {
+        statusRows.value = responseData(result.value, [])
+      } else {
+        facilityCounts.value = { ...facilityCounts.value, [request.key]: pageTotal(result.value) }
+      }
+    })
+
+    allRequestsFailed.value = successCount === 0
+    if (successCount > 0) lastUpdated.value = generatedAt || formatApiDate(new Date())
+    if (failedLabels.length) errorMessage.value = `${failedLabels.join('、')}加载失败,其他可用数据已正常展示。`
+    await nextTick()
+    renderTrendChart()
+    renderStatusChart()
+  } finally {
+    loading.value = false
+  }
 }
 
-.left-panel {
-  background: #fafcff;
-  border-right: 1px solid #eef2f6;
-  padding: 28px 24px;
+function resizeCharts() {
+  trendChart?.resize()
+  statusChart?.resize()
 }
 
-.right-panel {
-  background: #ffffff;
-  padding: 28px 24px;
-}
+onMounted(() => {
+  clockTimer = window.setInterval(() => { now.value = new Date() }, 30000)
+  resizeObserver = new ResizeObserver(resizeCharts)
+  if (trendChartRef.value) resizeObserver.observe(trendChartRef.value)
+  if (statusChartRef.value) resizeObserver.observe(statusChartRef.value)
+  loadData()
+})
 
-.section-card {
-  background: white;
-  border-radius: 24px;
-  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05), 0 0 0 1px rgba(0, 0, 0, 0.02);
-  margin-bottom: 32px;
-  overflow: hidden;
-}
+onUnmounted(() => {
+  window.clearInterval(clockTimer)
+  resizeObserver?.disconnect()
+  trendChart?.dispose()
+  statusChart?.dispose()
+})
+</script>
 
-.section-header {
-  padding: 18px 24px 12px 24px;
-  border-bottom: 2px solid #f1f5f9;
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  flex-wrap: wrap;
+<style scoped lang="scss">
+.lifeline-home {
+  min-height: calc(100vh - 84px);
+  padding: 20px 22px 28px;
+  background: #f3f6fa;
+  color: #1f2937;
 }
 
-.section-header h2 {
-  font-size: 1.35rem;
-  font-weight: 600;
+.home-header {
   display: flex;
-  align-items: center;
-  gap: 10px;
-  color: #0f2b3d;
-}
-
-.tag {
-  font-size: 12px;
-  background: #eef2ff;
-  padding: 4px 12px;
-  border-radius: 30px;
+  align-items: flex-end;
+  justify-content: space-between;
+  gap: 24px;
+  padding: 2px 2px 18px;
+  border-bottom: 1px solid #dfe6ee;
 }
 
-.section-content {
-  padding: 20px 24px 24px 24px;
-}
+.header-kicker { color: #2563eb; font-size: 12px; font-weight: 700; }
+.header-copy h1 { margin: 6px 0 5px; font-size: 26px; line-height: 1.25; }
+.header-copy p, .panel-header p { margin: 0; color: #718096; }
+.header-tools { display: flex; align-items: center; gap: 14px; }
 
-.flow-steps {
+.date-block {
   display: flex;
+  min-width: 245px;
   flex-direction: column;
-  gap: 16px;
+  align-items: flex-end;
+  gap: 5px;
 }
 
-.flow-node {
-  display: flex;
-  align-items: center;
-  gap: 16px;
-  background: #f8fafc;
-  padding: 14px 18px;
-  border-radius: 20px;
-  border-left: 5px solid #cbd5e1;
-  transition: all 0.2s;
-}
+.date-block strong { font-size: 14px; }
+.date-block span { color: #8492a6; font-size: 12px; }
+.data-alert { margin-top: 16px; }
 
-.flow-node.active {
-  border-left-color: #2c7da0;
-  background: #f1f9fe;
+.metric-grid {
+  display: grid;
+  grid-template-columns: repeat(4, minmax(0, 1fr));
+  gap: 14px;
+  margin: 18px 0 16px;
 }
 
-.node-icon {
-  width: 44px;
-  height: 44px;
-  background: #eef2ff;
-  border-radius: 30px;
+.metric-card {
+  position: relative;
   display: flex;
+  min-width: 0;
+  min-height: 122px;
   align-items: center;
-  justify-content: center;
-  font-size: 1.4rem;
-  color: #1e4a6b;
-}
-
-.node-info {
-  flex: 1;
-}
-
-.node-title {
-  font-weight: 700;
-  font-size: 1rem;
-}
-
-.node-desc {
-  font-size: 0.8rem;
-  color: #475569;
-  margin-top: 4px;
-}
-
-.node-badge {
-  background: #e2e8f0;
-  padding: 4px 12px;
-  border-radius: 50px;
-  font-size: 0.75rem;
-  font-weight: 600;
-  color: #1e293b;
-}
-
-.add-flow-btn {
-  margin-top: 12px;
-  background: white;
-  border: 1px dashed #94a3b8;
-  border-radius: 50px;
-  padding: 10px;
-  text-align: center;
-  color: #2c7da0;
-  font-weight: 500;
-  font-size: 0.85rem;
-  cursor: default;
-}
-
-.data-table {
-  width: 100%;
-  border-collapse: collapse;
-}
-
-.data-table th {
+  gap: 15px;
+  padding: 20px;
+  border: 1px solid #e1e7ef;
+  border-top: 3px solid var(--tone);
+  border-radius: 8px;
+  background: #ffffff;
+  color: inherit;
   text-align: left;
-  padding: 12px 8px 12px 0;
-  font-weight: 600;
-  font-size: 0.8rem;
-  color: #5b6e8c;
-  border-bottom: 1px solid #eef2f6;
-}
-
-.data-table td {
-  padding: 14px 8px 14px 0;
-  border-bottom: 1px solid #f1f5f9;
-  font-size: 0.85rem;
-  vertical-align: middle;
+  cursor: pointer;
+  transition: border-color 0.2s, box-shadow 0.2s, transform 0.2s;
 }
 
-.version-text {
-  font-size: 11px;
-  color: #54708f;
+.metric-card:hover {
+  border-color: var(--tone);
+  box-shadow: 0 8px 20px rgba(31, 41, 55, 0.08);
+  transform: translateY(-1px);
 }
 
-.status-badge {
-  background: #dcfce7;
-  color: #15803d;
-  padding: 4px 10px;
-  border-radius: 30px;
-  font-size: 0.7rem;
-  font-weight: 600;
-  display: inline-block;
+.metric-icon, .quick-icon {
+  display: inline-flex;
+  width: 42px;
+  height: 42px;
+  flex: 0 0 42px;
+  align-items: center;
+  justify-content: center;
+  border-radius: 7px;
 }
 
-.status-badge.warning {
-  background: #fff3e3;
-  color: #b45309;
-}
+.metric-icon { background: var(--tone-soft); color: var(--tone); font-size: 21px; }
+.metric-content { display: flex; min-width: 0; flex: 1; flex-direction: column; }
+.metric-label { color: #64748b; font-size: 13px; }
+.metric-content strong { margin: 4px 0 2px; color: #172033; font-size: 34px; line-height: 1.05; }
 
-.status-badge.secondary {
-  background: #eef2ff;
-  color: #1e40af;
-}
-
-.btn-icon {
-  background: none;
-  border: none;
-  color: #5b6e8c;
-  cursor: default;
-  font-size: 1rem;
-  margin: 0 4px;
+.metric-content small {
+  overflow: hidden;
+  color: #94a3b8;
+  font-size: 12px;
+  text-overflow: ellipsis;
+  white-space: nowrap;
 }
 
-.comment-text {
-  font-size: 0.8rem;
-  max-width: 180px;
-  white-space: normal;
-  word-break: break-word;
-  line-height: 1.4;
-}
+.metric-arrow { color: #b6c0cd; }
 
-.adjust-item {
-  background: #fefce8;
-  border-left: 4px solid #facc15;
-  padding: 14px 16px;
-  border-radius: 16px;
-  margin-bottom: 14px;
-}
-
-.adjust-title {
-  font-weight: 700;
-  display: flex;
-  justify-content: space-between;
-  flex-wrap: wrap;
-  gap: 8px;
-  margin-bottom: 6px;
+.analysis-grid, .operations-grid {
+  display: grid;
+  grid-template-columns: minmax(0, 1.7fr) minmax(320px, 0.8fr);
+  gap: 16px;
+  margin-bottom: 16px;
 }
 
-.adjust-detail {
-  font-size: 0.8rem;
-  color: #3b3f46;
-  margin-top: 8px;
+.panel {
+  min-width: 0;
+  border: 1px solid #e1e7ef;
+  border-radius: 8px;
+  background: #ffffff;
+  box-shadow: 0 2px 8px rgba(31, 41, 55, 0.035);
 }
 
-.publish-card, .pending-publish-card {
-  background: linear-gradient(115deg, #eef6fc 0%, #ffffff 100%);
-  border-radius: 20px;
-  padding: 20px;
+.panel-header {
   display: flex;
-  justify-content: space-between;
+  min-height: 72px;
   align-items: center;
-  flex-wrap: wrap;
+  justify-content: space-between;
   gap: 16px;
+  padding: 17px 20px 14px;
+  border-bottom: 1px solid #edf1f5;
 }
 
-.publish-info h4 {
-  font-weight: 700;
-}
+.panel-header h2 { margin: 0 0 5px; font-size: 17px; line-height: 1.25; }
+.panel-header p { font-size: 12px; }
+.panel-header .el-button { flex: 0 0 auto; }
+.chart-box { width: 100%; height: 292px; }
+.trend-panel, .status-panel { position: relative; min-height: 365px; }
+.trend-panel .el-empty, .status-panel .el-empty { position: absolute; inset: 84px 0 8px; }
+.facility-panel { margin-bottom: 16px; }
 
-.publish-btn {
-  background: #0f2b3d;
-  border: none;
-  padding: 10px 24px;
-  border-radius: 40px;
-  color: white;
-  font-weight: 600;
-  font-size: 0.85rem;
-  cursor: default;
+.facility-grid {
+  display: grid;
+  grid-template-columns: repeat(4, minmax(0, 1fr));
+  gap: 14px;
+  padding: 16px 20px 20px;
 }
 
-.publish-btn.disabled {
-  background: #94a3b8;
-  cursor: not-allowed;
+.facility-card {
+  min-width: 0;
+  padding: 16px;
+  border: 1px solid #e7ebf1;
+  border-radius: 7px;
+  background: #ffffff;
 }
 
-.pending-publish-card {
-  background: #ffffff;
-  border: 1px solid #eef2f6;
+.facility-title, .facility-value {
+  display: flex;
+  align-items: center;
   justify-content: space-between;
+  gap: 10px;
 }
 
-.pending-list {
-  font-size: 12px;
-  color: #2c3e66;
-}
+.facility-title { justify-content: flex-start; }
+.facility-title > div { display: flex; min-width: 0; flex-direction: column; gap: 3px; }
+.facility-title strong { overflow: hidden; font-size: 14px; text-overflow: ellipsis; white-space: nowrap; }
+.facility-title small, .facility-value span { color: #94a3b8; font-size: 12px; }
 
-.publish-action {
-  background: #2c7da0;
-  padding: 6px 18px;
-  border-radius: 40px;
-  color: white;
-  font-size: 0.8rem;
-  font-weight: 600;
-  cursor: pointer;
-  transition: 0.2s;
+.facility-mark {
+  display: inline-flex;
+  width: 36px;
+  height: 36px;
+  flex: 0 0 36px;
+  align-items: center;
+  justify-content: center;
+  border-radius: 7px;
+  background: var(--facility-soft);
+  color: var(--facility-color);
+  font-size: 14px;
+  font-weight: 700;
 }
 
-.publish-action:hover {
-  background: #1f5e7e;
-  transform: scale(0.98);
-}
+.facility-value { margin: 14px 0 10px; align-items: flex-end; }
+.facility-value strong { font-size: 27px; line-height: 1; }
+.facility-progress { height: 5px; overflow: hidden; border-radius: 3px; background: #eef2f6; }
 
-.record-card {
-  flex: 1;
-  background: #f9fafb;
-  border-radius: 20px;
-  padding: 12px;
+.facility-progress i {
+  display: block;
+  height: 100%;
+  border-radius: inherit;
+  background: var(--facility-color);
+  transition: width 0.35s ease;
 }
 
-.record-desc {
-  font-size: 13px;
-  margin-top: 8px;
-  color: #334155;
-}
+.warning-table { width: calc(100% - 32px); margin: 0 16px 16px; cursor: pointer; }
+.warning-name { display: flex; min-width: 0; align-items: center; gap: 8px; }
 
-.helper-text {
-  font-size: 0.7rem;
-  color: #6c86a3;
-  margin-top: 12px;
-  text-align: center;
+.warning-name i {
+  width: 8px;
+  height: 8px;
+  flex: 0 0 8px;
+  border-radius: 50%;
+  background: #94a3b8;
 }
 
-.footer-bar {
-  background: #f9fef9;
-  border-top: 1px solid #e2ecf5;
-  padding: 16px 32px;
+.warning-name i.level-1 { background: #2563eb; }
+.warning-name i.level-2 { background: #eab308; }
+.warning-name i.level-3 { background: #f97316; }
+.warning-name i.level-4 { background: #dc2626; }
+.warning-name span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.waiting-time { color: #b45309; font-weight: 600; }
+.quick-panel { align-self: stretch; }
+.quick-list { display: flex; flex-direction: column; padding: 8px 14px 14px; }
+
+.quick-action {
   display: flex;
-  justify-content: space-between;
+  width: 100%;
+  min-height: 64px;
   align-items: center;
-  flex-wrap: wrap;
-  gap: 10px;
+  gap: 12px;
+  padding: 10px 8px;
+  border: 0;
+  border-bottom: 1px solid #edf1f5;
+  background: transparent;
+  color: inherit;
+  text-align: left;
+  cursor: pointer;
 }
 
+.quick-action:last-child { border-bottom: 0; }
+.quick-action:hover { background: #f8fafc; }
 
-@media (max-width: 980px) {
-  .dashboard-grid {
-    grid-template-columns: 1fr;
-  }
-  .left-panel {
-    border-right: none;
-    border-bottom: 1px solid #eef2f6;
-  }
-  body {
-    padding: 20px;
-  }
+.quick-action > span:nth-child(2) {
+  display: flex;
+  min-width: 0;
+  flex: 1;
+  flex-direction: column;
+  gap: 4px;
 }
-</style>
+
+.quick-action strong, .quick-action small {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.quick-action strong { font-size: 14px; }
+.quick-action small { color: #94a3b8; font-size: 12px; }
+.quick-icon { width: 38px; height: 38px; flex-basis: 38px; font-size: 18px; }
+.quick-arrow { flex: 0 0 auto; color: #b6c0cd; }
+
+@media (max-width: 1180px) {
+  .metric-grid, .facility-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
+  .analysis-grid, .operations-grid { grid-template-columns: 1fr; }
+  .quick-list { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 16px; }
+}
+
+@media (max-width: 680px) {
+  .lifeline-home { padding: 14px 12px 22px; }
+  .home-header { align-items: flex-start; flex-direction: column; }
+  .header-tools { width: 100%; justify-content: space-between; }
+  .date-block { min-width: 0; align-items: flex-start; }
+  .metric-grid, .facility-grid, .quick-list { grid-template-columns: 1fr; }
+  .metric-card { min-height: 110px; }
+  .panel-header { align-items: flex-start; }
+  .chart-box { height: 260px; }
+  .trend-panel, .status-panel { min-height: 335px; }
+  .facility-grid { padding: 14px; }
+}
+</style>

+ 1 - 1
src/views/subSystem/lifeCompany/lstatistics/disposalEfficiency/index.vue

@@ -7,7 +7,7 @@
     <el-card shadow="never" class="table-card"><template #header><div class="card-title"><span>已处置预警明细</span><em>{{ total }} 条</em></div></template><el-table v-loading="loading" :data="rows" stripe><el-table-column prop="warningNo" label="预警编号" min-width="150"/><el-table-column prop="warningName" label="预警事项" min-width="180" show-overflow-tooltip/><el-table-column label="预警专项" min-width="140"><template #default="{row}">{{ row.warning_special || row.warningSpecial || '-' }}</template></el-table-column><el-table-column label="发布时间" min-width="160"><template #default="{row}">{{ row.publish_time || row.publishTime || '-' }}</template></el-table-column><el-table-column label="处置时长" width="120"><template #default="{row}"><b>{{ row.dispose_hours ?? row.disposeHours ?? '-' }}</b> 小时</template></el-table-column><el-table-column label="状态" width="100"><template #default="{row}"><el-tag type="success">{{ row.status === 'CLOSED' ? '已解除' : '已处置' }}</el-tag></template></el-table-column></el-table><pagination v-show="total > 0" v-model:page="query.pageNum" v-model:limit="query.pageSize" :total="total" @pagination="loadPage" /></el-card>
   </div>
 </template>
-<script setup>
+<script setup name="WarningDisposeEfficiency">
 import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
 import { ElMessage } from 'element-plus'
 import { Refresh } from '@element-plus/icons-vue'

+ 1 - 1
src/views/subSystem/lifeCompany/lstatistics/disposalStatus/index.vue

@@ -7,7 +7,7 @@
     <div class="chart-grid"><el-card shadow="never"><template #header><div class="card-title"><span>预警状态分布</span><em>排除草稿</em></div></template><div ref="statusChartRef" class="chart-box" /></el-card><el-card shadow="never"><template #header><div class="card-title"><span>处置动作分布</span><em>按动作类型</em></div></template><div ref="typeChartRef" class="chart-box" /></el-card></div>
   </div>
 </template>
-<script setup>
+<script setup name="WarningDisposeStatus">
 import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
 import { ElMessage } from 'element-plus'
 import { Refresh } from '@element-plus/icons-vue'

+ 3 - 3
src/views/subSystem/lifeCompany/lstatistics/receiveEfficiency/index.vue

@@ -7,15 +7,15 @@
     <el-card shadow="never"><template #header><div class="card-title"><span>接警效率明细</span><em>{{ total }} 条</em></div></template><el-table v-loading="loading" :data="rows" stripe><el-table-column prop="warningNo" label="预警编号" min-width="150"/><el-table-column prop="warningName" label="预警事项" min-width="180" show-overflow-tooltip/><el-table-column label="类型" min-width="120"><template #default="{row}">{{ typeLabel(row.warning_type||row.warningType) }}</template></el-table-column><el-table-column label="首次接警" min-width="160"><template #default="{row}">{{ row.receive_time||row.receiveTime||'-' }}</template></el-table-column><el-table-column label="接警时长" width="120"><template #default="{row}"><b>{{ row.receive_hours??row.receiveHours??'-' }}</b> 小时</template></el-table-column><el-table-column label="状态" width="100"><template #default="{row}"><el-tag>{{ statusLabels[row.status]||row.status }}</el-tag></template></el-table-column></el-table><pagination v-show="total > 0" v-model:page="query.pageNum" v-model:limit="query.pageSize" :total="total" @pagination="loadPage" /></el-card>
   </div>
 </template>
-<script setup>
+<script setup name="WarningReceiveEfficiency">
 import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
 import { ElMessage } from 'element-plus'
 import { Refresh } from '@element-plus/icons-vue'
 import * as echarts from 'echarts'
 import { getReceiveEfficiencyOverview, getReceiveEfficiencyRange, getReceiveEfficiencyType, pageReceiveEfficiency } from '@/api/warning/statistics'
-import { defaultRange, labelOf, rangeParams, responseData, rowsOf, safeNumber, statusLabels, totalOf, warningTypeLabel, warningTypeLabels } from '../statisticsShared'
+import { defaultRange, labelOf, rangeParams, responseData, rowsOf, safeNumber, statusLabels, totalOf, warningTypeLabel, warningTypeOptions, warningTypeLabels } from '../statisticsShared'
 const dateRange=ref(defaultRange());const warningType=ref('');const warningSpecial=ref('');const status=ref('');const loading=ref(false);const errorMessage=ref('');const overview=ref({});const rangeRows=ref([]);const typeRows=ref([]);const rows=ref([]);const total=ref(0);const query=ref({pageNum:1,pageSize:10});const rangeRef=ref();const typeRef=ref();let rangeChart;let typeChart
-const typeOptions=Object.entries(warningTypeLabels).map(([value,label])=>({value,label}));const typeLabel=value=>labelOf(warningTypeLabels,value);const read=(key, fallback='-')=>overview.value[key]??overview.value[key.replace(/_([a-z])/g,(_,c)=>c.toUpperCase())]??fallback;const metrics=computed(()=>[{label:'平均接警时长',value:safeNumber(read('avg_hours'),2),note:'小时'},{label:'最长接警时长',value:safeNumber(read('max_hours'),2),note:'小时'},{label:'最短接警时长',value:safeNumber(read('min_hours'),2),note:'小时'},{label:'有效接警数',value:read('total_count',0),note:'已接收预警'}])
+const typeOptions=warningTypeOptions;const typeLabel=value=>labelOf(warningTypeLabels,value);const read=(key, fallback='-')=>overview.value[key]??overview.value[key.replace(/_([a-z])/g,(_,c)=>c.toUpperCase())]??fallback;const metrics=computed(()=>[{label:'平均接警时长',value:safeNumber(read('avg_hours'),2),note:'小时'},{label:'最长接警时长',value:safeNumber(read('max_hours'),2),note:'小时'},{label:'最短接警时长',value:safeNumber(read('min_hours'),2),note:'小时'},{label:'有效接警数',value:read('total_count',0),note:'已接收预警'}])
 function render(){if(!rangeRef.value||!typeRef.value)return;rangeChart ||= echarts.init(rangeRef.value);typeChart ||= echarts.init(typeRef.value);rangeChart.setOption({tooltip:{trigger:'axis'},grid:{left:46,right:18,top:22,bottom:38},xAxis:{type:'category',data:rangeRows.value.map(r=>r.time_range||r.timeRange)},yAxis:{type:'value',minInterval:1},series:[{type:'bar',barWidth:32,data:rangeRows.value.map(r=>Number(r.warning_count??r.warningCount??0)),itemStyle:{color:'#2563eb',borderRadius:[6,6,0,0]}}]});typeChart.setOption({tooltip:{trigger:'axis'},grid:{left:55,right:18,top:22,bottom:54},xAxis:{type:'category',data:typeRows.value.map(r=>typeLabel(r.warning_type||r.warningType)),axisLabel:{rotate:25}},yAxis:{type:'value',name:'小时'},series:[{type:'bar',barWidth:28,data:typeRows.value.map(r=>Number(r.avg_receive_hours??r.avgReceiveHours??0)),itemStyle:{color:'#dc2626',borderRadius:[6,6,0,0]}}]})}
 async function loadData(){loading.value=true;errorMessage.value='';try{const params={...rangeParams(dateRange.value),warningType:warningType.value||undefined,warningSpecial:warningSpecial.value||undefined,status:status.value||undefined};const [a,b,c]=await Promise.all([getReceiveEfficiencyOverview(params),getReceiveEfficiencyRange(params),getReceiveEfficiencyType(params)]);overview.value=responseData(a,{});rangeRows.value=responseData(b,[]);typeRows.value=responseData(c,[]);await loadPage();await nextTick();render()}catch(error){errorMessage.value=error?.message||'接警效率加载失败';ElMessage.error(errorMessage.value)}finally{loading.value=false}}
 async function loadPage(){const res=await pageReceiveEfficiency({...rangeParams(dateRange.value),warningType:warningType.value||undefined,warningSpecial:warningSpecial.value||undefined,status:status.value||undefined,sortType:'desc',...query.value});rows.value=rowsOf(res).map(row=>({...row,warningNo:row.warningNo??row.warning_no,warningName:row.warningName??row.warning_name}));total.value=totalOf(res)}

+ 12 - 2
src/views/subSystem/lifeCompany/lstatistics/statisticsShared.js

@@ -15,13 +15,22 @@ export const warningTypeLabels = {
   '窨井预警': '窨井'
 }
 
+// 业务范围固定为四类城市生命线设施;别名仅用于兼容历史数据,不在筛选器中重复展示。
+export const warningTypeOptions = [
+  { value: 'WATER_PIPE', label: '供水管网' },
+  { value: 'SEWER_PIPE', label: '排水管网' },
+  { value: 'GAS_PIPE', label: '燃气管网' },
+  { value: 'MANHOLE', label: '窨井' }
+]
+
 export const statusLabels = {
   DRAFT: '草稿',
   PENDING: '待处置',
   PROCESSING: '处置中',
   RELEASED: '已发布',
   HANDLED: '已处置',
-  CLOSED: '已解除'
+  CLOSED: '已解除',
+  RESOLVED: '已解除'
 }
 
 export const disposalTypeLabels = {
@@ -33,7 +42,8 @@ export const disposalTypeLabels = {
 
 export function labelOf(map, value) {
   const legacyTypeLabels = { '1': '供水管网', '2': '排水管网', '3': '燃气管网', '4': '窨井' }
-  return map[value] || (map === warningTypeLabels ? legacyTypeLabels[String(value)] : undefined) || value || '未设置'
+  const normalizedValue = value === undefined || value === null ? '' : String(value).toUpperCase()
+  return map[value] || map[normalizedValue] || (map === warningTypeLabels ? legacyTypeLabels[String(value)] : undefined) || value || '未设置'
 }
 
 export function warningTypeLabel(value) {

+ 1 - 1
src/views/subSystem/lifeCompany/lstatistics/today/index.vue

@@ -4,7 +4,7 @@
     <el-card shadow="never" class="table-card"><template #header><div class="card-title"><span>{{ activeStatus ? statusLabels[activeStatus] : '今日全部预警' }}</span><div><el-button text @click="selectStatus('')">全部</el-button><el-button type="primary" plain @click="loadData">刷新</el-button></div></div></template><el-table v-loading="loading" :data="rows" stripe empty-text="今日暂无预警"><el-table-column prop="warningNo" label="预警编号" min-width="150"/><el-table-column prop="warningName" label="预警事项" min-width="190" show-overflow-tooltip/><el-table-column prop="warningType" label="预警类型" min-width="120"/><el-table-column prop="warningSpecial" label="预警专项" min-width="140"/><el-table-column prop="publishTime" label="发布时间" min-width="165"/><el-table-column label="处置状态" width="110"><template #default="{row}"><el-tag :type="row.status === 'CLOSED' ? 'success' : row.status === 'HANDLED' ? 'primary' : 'warning'">{{ statusLabels[row.status] || row.status }}</el-tag></template></el-table-column></el-table><pagination v-show="total > 0" v-model:page="query.pageNum" v-model:limit="query.pageSize" :total="total" @pagination="loadPage" /></el-card>
   </div>
 </template>
-<script setup>
+<script setup name="WarningToday">
 import { computed, onMounted, ref } from 'vue'
 import { ElMessage } from 'element-plus'
 import { Refresh } from '@element-plus/icons-vue'

+ 1 - 1
src/views/subSystem/lifeCompany/lstatistics/trend/index.vue

@@ -5,7 +5,7 @@
     <el-card shadow="never"><template #header><div class="card-title"><span>{{ granularity === 'day' ? '日趋势' : '月趋势' }}</span><em>{{ rows.length }} 个数据点</em></div></template><div ref="chartRef" class="trend-chart" /><el-empty v-if="!rows.length && !loading" description="当前周期暂无趋势数据" /></el-card>
   </div>
 </template>
-<script setup>
+<script setup name="WarningTrend">
 import { nextTick, onMounted, onUnmounted, ref } from 'vue'
 import { ElMessage } from 'element-plus'
 import { Refresh } from '@element-plus/icons-vue'

+ 1 - 1
src/views/subSystem/warningList/codeGenerate/index.vue

@@ -90,7 +90,7 @@
   </div>
 </template>
 
-<script setup>
+<script setup name="WarningCodeGenerate">
 import { computed, onMounted, reactive, ref } from 'vue'
 import { ElMessage } from 'element-plus'
 import { Check, Connection, CopyDocument, InfoFilled, Key, MagicStick, Warning } from '@element-plus/icons-vue'

+ 1 - 1
src/views/subSystem/warningList/itemDetail/index.vue

@@ -89,7 +89,7 @@
   </div>
 </template>
 
-<script setup>
+<script setup name="WarningItemDetail">
 import { computed, onMounted, reactive, ref } from 'vue'
 import { useRoute } from 'vue-router'
 import { ElMessage } from 'element-plus'

+ 1 - 1
src/views/subSystem/warningList/itemManage/index.vue

@@ -265,7 +265,7 @@
   </div>
 </template>
 
-<script setup>
+<script setup name="WarningItemManage">
 import { onMounted, reactive, ref } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { ArrowDown, Delete, Edit, MagicStick, Plus, Refresh, RefreshRight, Search, View } from '@element-plus/icons-vue'

+ 2 - 2
src/views/subSystem/warningList/itemQuery/index.vue

@@ -157,13 +157,13 @@
       </div>
       <template #footer>
         <el-button @click="detailVisible = false">关闭</el-button>
-        <el-button type="primary" @click="goFullDetail">进入完整详情</el-button>
+        <el-button v-if="router.hasRoute('WarningItemDetail')" type="primary" @click="goFullDetail">进入完整详情</el-button>
       </template>
     </el-drawer>
   </div>
 </template>
 
-<script setup>
+<script setup name="WarningItemQuery">
 import { computed, onMounted, reactive, ref } from 'vue'
 import { useRouter } from 'vue-router'
 import { CircleCheck, CircleClose, Collection, Refresh, RefreshRight, Search, View, Warning } from '@element-plus/icons-vue'

+ 3 - 3
src/views/subSystem/warningList/reasonManage/index.vue

@@ -173,7 +173,7 @@
   </div>
 </template>
 
-<script setup>
+<script setup name="WarningReasonManage">
 import { onMounted, reactive, ref } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { CollectionTag, Grid, Plus, Refresh, Search, Warning } from '@element-plus/icons-vue'
@@ -204,7 +204,7 @@ const warningTypes = ref(fallbackWarningTypes)
 const detail = reactive({})
 const query = reactive({
   pageNum: 1,
-  pageSize: 9,
+  pageSize: 10,
   reasonCode: '',
   reasonName: '',
   warningType: '',
@@ -252,7 +252,7 @@ function handleQuery() {
   loadData()
 }
 function resetQuery() {
-  Object.assign(query, { pageNum: 1, pageSize: 9, reasonCode: '', reasonName: '', warningType: '', warningLevel: '', status: '' })
+  Object.assign(query, { pageNum: 1, pageSize: 10, reasonCode: '', reasonName: '', warningType: '', warningLevel: '', status: '' })
   loadData()
 }
 function selectType(value) {