learshaw преди 2 месеца
родител
ревизия
5e629e9e8d

+ 0 - 83
ems-ui-cloud/src/api/alarm/alarm-info.js

@@ -1,83 +0,0 @@
-import {get} from '@/api/commonApi';
-import request from '@/utils/request';
-
-// 查询能源设施告警列表
-export function listAlarmInfo(query) {
-  return request({
-    url: '/ems/alarm-info/list',
-    method: 'get',
-    params: query
-  })
-}
-
-// 查询能源设施告警详细
-export function getAlarmInfo(id) {
-  return request({
-    url: '/ems/alarm-info/' + id,
-    method: 'get'
-  })
-}
-
-// 新增能源设施告警
-export function addAlarmInfo(data) {
-  return request({
-    url: '/ems/alarm-info',
-    method: 'post',
-    data: data
-  })
-}
-
-// 修改能源设施告警
-export function updateAlarmInfo(data) {
-  return request({
-    url: '/ems/alarm-info',
-    method: 'put',
-    data: data
-  })
-}
-
-// 删除能源设施告警
-export function delAlarmInfo(id) {
-  return request({
-    url: '/ems/alarm-info/' + id,
-    method: 'delete'
-  })
-}
-
-export const fetchAlarmIndex = (params) => {
-  return get('/alarm-info/alarm/type/index', params);
-};
-
-export const fetchAlarmIndexDay = (params) => {
-  return get('/alarm-info/alarm/type/index/day', params);
-};
-
-export const fetchAlarmIndexMonth = (params) => {
-  return get('/alarm-info/alarm/type/index/month', params);
-};
-
-
-export const fetchAlarmIndexYear = (params) => {
-  return get('/alarm-info/alarm/type/index/year', params);
-};
-
-export const fetchSubSysIndexDay = (params) => {
-  return get('/alarm-info/subsys/index/day', params);
-};
-
-export const fetchSubSysIndexMonth = (params) => {
-  return get('/alarm-info/subsys/index/month', params);
-};
-
-
-export const fetchSubSysIndexYear = (params) => {
-  return get('/alarm-info/subsys/index/year', params);
-};
-
-export const fetchCntHandled = (params) => {
-  return get('/alarm-info/cnt/handled', params);
-};
-
-export const fetchCntDateAlarmType = (params) => {
-  return get('/alarm-info/cnt/date/alarm/type', params);
-};

+ 1103 - 364
ems-ui-cloud/src/views/analysis/report/statement-warn.vue

@@ -1,112 +1,267 @@
 <template>
-  <div class="app-container">
+  <div class="app-container alarm-report-container">
     <el-row :gutter="20">
       <!-- 左侧树形区域 -->
       <el-col :span="5" :xs="24">
-        <div class="head-container">
-          <el-input
-            v-model="areaName"
-            placeholder="请输入区域名称"
-            clearable
-            size="small"
-            prefix-icon="el-icon-search"
-            style="margin-bottom: 20px"
-            @input="filterTree"
-          />
-        </div>
-        <div class="head-container tree-container">
-          <el-tree
-            ref="tree"
-            :data="areaOptions"
-            :props="defaultProps"
-            :expand-on-click-node="false"
-            :filter-node-method="filterNode"
-            node-key="id"
-            :default-expanded-keys="defaultExpandedKeys"
-            highlight-current
-            @node-click="handleNodeClick"
-          >
-            <span class="custom-tree-node" slot-scope="{ node, data }">
-              <span class="tree-label">
-                <i :class="getTreeIcon(data)" class="tree-icon"></i>
-                {{ node.label }}
+        <div class="tree-panel">
+          <div class="tree-search">
+            <el-input
+              v-model="areaName"
+              placeholder="搜索区域..."
+              clearable
+              size="small"
+              prefix-icon="el-icon-search"
+              @input="filterTree"
+            />
+          </div>
+          <div class="tree-content">
+            <el-tree
+              ref="tree"
+              :data="areaOptions"
+              :props="defaultProps"
+              :expand-on-click-node="false"
+              :filter-node-method="filterNode"
+              node-key="id"
+              :default-expanded-keys="defaultExpandedKeys"
+              highlight-current
+              @node-click="handleNodeClick"
+            >
+              <span class="custom-tree-node" slot-scope="{ node, data }">
+                <span class="tree-label">
+                  <i :class="getTreeIcon(data)" class="tree-icon"></i>
+                  {{ node.label }}
+                </span>
+                <el-badge
+                  v-if="areaAlarmCounts[data.id] > 0"
+                  :value="areaAlarmCounts[data.id]"
+                  :max="99"
+                  class="tree-badge"
+                />
               </span>
-              <el-tag
-                v-if="data.facsCategory === 'E'"
-                size="mini"
-                effect="plain"
-                class="tree-tag"
-              >
-                光伏
-              </el-tag>
-            </span>
-          </el-tree>
+            </el-tree>
+          </div>
         </div>
       </el-col>
 
       <!-- 右侧内容区域 -->
       <el-col :span="19" :xs="24">
         <div class="content-wrapper">
-          <!-- 标题区域 -->
+          <!-- 顶部统计卡片 -->
+          <div class="stats-row">
+            <div class="stat-card total">
+              <div class="stat-icon">
+                <i class="el-icon-bell"></i>
+              </div>
+              <div class="stat-info">
+                <div class="stat-value">{{ statsData.totalCount || 0 }}</div>
+                <div class="stat-label">告警总数</div>
+              </div>
+              <div class="stat-trend" v-if="statsData.todayCount > 0">
+                今日 +{{ statsData.todayCount }}
+              </div>
+            </div>
+            <div class="stat-card active" @click="filterByStatus(0)">
+              <div class="stat-icon">
+                <i class="el-icon-warning"></i>
+              </div>
+              <div class="stat-info">
+                <div class="stat-value">{{ statsData.activeCount || 0 }}</div>
+                <div class="stat-label">活动告警</div>
+              </div>
+              <div class="stat-pulse" v-if="statsData.activeCount > 0"></div>
+            </div>
+            <div class="stat-card urgent" @click="filterByLevel(3)">
+              <div class="stat-icon">
+                <i class="el-icon-s-opportunity"></i>
+              </div>
+              <div class="stat-info">
+                <div class="stat-value">{{ statsData.urgentCount || 0 }}</div>
+                <div class="stat-label">紧急告警</div>
+              </div>
+            </div>
+            <div class="stat-card handled">
+              <div class="stat-icon">
+                <i class="el-icon-circle-check"></i>
+              </div>
+              <div class="stat-info">
+                <div class="stat-value">{{ statsData.handleRate || 0 }}%</div>
+                <div class="stat-label">处理率</div>
+              </div>
+              <div class="stat-progress">
+                <div class="progress-bar" :style="{ width: (statsData.handleRate || 0) + '%' }"></div>
+              </div>
+            </div>
+          </div>
+
+          <!-- 标题与筛选区域 -->
           <div class="content-header">
             <div class="header-left">
               <h3 class="page-title">
-                <i class="el-icon-warning-outline"></i>
-                告警信息【{{ selectedLabel }}】
+                <i class="el-icon-document"></i>
+                告警报表
+                <span class="area-tag">{{ selectedLabel }}</span>
               </h3>
             </div>
+            <div class="header-right">
+              <el-radio-group v-model="timeRange" size="small" @change="handleTimeRangeChange">
+                <el-radio-button label="today">今日</el-radio-button>
+                <el-radio-button label="week">本周</el-radio-button>
+                <el-radio-button label="month">本月</el-radio-button>
+                <el-radio-button label="custom">自定义</el-radio-button>
+              </el-radio-group>
+            </div>
           </div>
 
           <!-- 查询条件区域 -->
           <div class="search-container">
-            <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
-              <el-form-item label="日期" prop="date">
+            <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="80px">
+              <el-form-item label="时间范围" prop="dateRange" v-show="timeRange === 'custom'">
                 <el-date-picker
-                  clearable
-                  v-model="queryParams.date"
-                  type="date"
-                  value-format="yyyy-MM-dd"
-                  placeholder="请选择日期"
-                >
-                </el-date-picker>
+                  v-model="dateRange"
+                  type="datetimerange"
+                  range-separator="至"
+                  start-placeholder="开始时间"
+                  end-placeholder="结束时间"
+                  value-format="yyyy-MM-dd HH:mm:ss"
+                  :default-time="['00:00:00', '23:59:59']"
+                  style="width: 340px"
+                />
+              </el-form-item>
+              <el-form-item label="告警级别" prop="alarmLevel">
+                <el-select v-model="queryParams.alarmLevel" placeholder="全部级别" clearable style="width: 120px">
+                  <el-option v-for="item in ALARM_LEVEL_OPTIONS" :key="item.value" :label="item.label" :value="item.value">
+                    <span :style="{ color: item.color }">
+                      <i class="el-icon-warning-outline"></i> {{ item.label }}
+                    </span>
+                  </el-option>
+                </el-select>
+              </el-form-item>
+              <el-form-item label="告警状态" prop="alarmStatus">
+                <el-select v-model="queryParams.alarmStatus" placeholder="全部状态" clearable style="width: 120px">
+                  <el-option v-for="item in ALARM_STATUS_OPTIONS" :key="item.value" :label="item.label" :value="item.value" />
+                </el-select>
+              </el-form-item>
+              <el-form-item label="子系统" prop="subsystemCode">
+                <el-select v-model="queryParams.subsystemCode" placeholder="全部子系统" clearable style="width: 140px">
+                  <el-option v-for="item in subsystemOptions" :key="item.systemCode"
+                             :label="item.systemName" :value="item.systemCode" />
+                </el-select>
               </el-form-item>
               <el-form-item>
-                <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
-                <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
-                <el-button
-                  type="warning"
-                  plain
-                  icon="el-icon-download"
-                  size="mini"
-                  @click="handleExport"
-                >
-                  导出
-                </el-button>
+                <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
+                <el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
+                <el-button type="warning" plain icon="el-icon-download" @click="handleExport">导出</el-button>
               </el-form-item>
             </el-form>
           </div>
 
+          <!-- 图表区域 -->
+          <div class="chart-row">
+            <div class="chart-card">
+              <div class="chart-header">
+                <span class="chart-title">
+                  <i class="el-icon-pie-chart"></i> 级别分布
+                </span>
+              </div>
+              <div ref="levelChart" class="chart-body"></div>
+            </div>
+            <div class="chart-card">
+              <div class="chart-header">
+                <span class="chart-title">
+                  <i class="el-icon-data-line"></i> 告警趋势
+                </span>
+                <el-radio-group v-model="trendGranularity" size="mini" @change="loadTrendData">
+                  <el-radio-button label="day">按时</el-radio-button>
+                  <el-radio-button label="month">按天</el-radio-button>
+                </el-radio-group>
+              </div>
+              <div ref="trendChart" class="chart-body"></div>
+            </div>
+            <div class="chart-card">
+              <div class="chart-header">
+                <span class="chart-title">
+                  <i class="el-icon-s-data"></i> 子系统分布
+                </span>
+              </div>
+              <div ref="subsystemChart" class="chart-body"></div>
+            </div>
+          </div>
+
           <!-- 数据表格 -->
           <div class="table-container">
-            <el-table v-loading="loading" :data="pvAlarmHList" stripe>
-              <el-table-column label="时间" align="center" prop="time">
+            <div class="table-header">
+              <span class="table-title">
+                <i class="el-icon-tickets"></i> 告警明细
+              </span>
+              <span class="table-total">共 {{ total }} 条记录</span>
+            </div>
+            <el-table v-loading="loading" :data="alarmList" stripe border
+                      :row-class-name="tableRowClassName" @row-click="handleRowClick">
+              <el-table-column label="告警级别" align="center" width="100">
+                <template slot-scope="scope">
+                  <el-tag :type="getAlarmLevelInfo(scope.row.alarmLevel).type" size="small" effect="dark">
+                    {{ getAlarmLevelInfo(scope.row.alarmLevel).label }}
+                  </el-tag>
+                </template>
+              </el-table-column>
+              <el-table-column label="告警状态" align="center" width="100">
                 <template slot-scope="scope">
-                  <span class="time-text">{{ scope.row.alarmTime }}</span>
+                  <el-tag :type="getAlarmStatusInfo(scope.row.alarmStatus).type" size="small" effect="light">
+                    {{ getAlarmStatusInfo(scope.row.alarmStatus).label }}
+                  </el-tag>
                 </template>
               </el-table-column>
-              <el-table-column label="区域名称" align="center" prop="areaName">
+              <el-table-column label="告警时间" align="center" width="160" prop="alarmTime">
                 <template slot-scope="scope">
-                  <el-tag type="info" size="mini">{{ scope.row.areaName }}</el-tag>
+                  <span class="time-text">
+                    <i class="el-icon-time"></i>
+                    {{ formatTime(scope.row.alarmTime) }}
+                  </span>
                 </template>
               </el-table-column>
-              <el-table-column label="子系统名称" align="center" prop="subSystemName">
+              <el-table-column label="区域" align="center" width="120" prop="areaName" show-overflow-tooltip>
                 <template slot-scope="scope">
-                  <span class="system-text">{{ scope.row.subSystemName }}</span>
+                  <el-tag type="info" size="mini">{{ scope.row.areaName || '-' }}</el-tag>
                 </template>
               </el-table-column>
-              <el-table-column label="告警内容" align="center" prop="alarmMsg">
+              <el-table-column label="子系统" align="center" width="120" prop="subsystemName" show-overflow-tooltip>
                 <template slot-scope="scope">
-                  <span class="alarm-msg">{{ scope.row.alarmMsg }}</span>
+                  <span class="subsystem-text">{{ scope.row.subsystemName || '-' }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="告警目标" min-width="150" prop="targetName" show-overflow-tooltip>
+                <template slot-scope="scope">
+                  <div class="target-cell">
+                    <i :class="getTargetTypeIcon(scope.row.targetType)" class="target-icon"></i>
+                    <span>{{ scope.row.targetName || scope.row.targetCode }}</span>
+                  </div>
+                </template>
+              </el-table-column>
+              <el-table-column label="告警内容" min-width="200" prop="alarmMsg" show-overflow-tooltip>
+                <template slot-scope="scope">
+                  <span class="alarm-msg">{{ scope.row.alarmMsg || formatAlarmContent(scope.row) }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="位置" width="150" prop="location" show-overflow-tooltip>
+                <template slot-scope="scope">
+                  <span class="location-text" v-if="scope.row.location">
+                    <i class="el-icon-location-outline"></i>
+                    {{ scope.row.location }}
+                  </span>
+                  <span v-else>-</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="处理状态" align="center" width="120">
+                <template slot-scope="scope">
+                  <template v-if="scope.row.alarmStatus >= 3">
+                    <span class="handled-info">
+                      <i class="el-icon-check"></i>
+                      {{ scope.row.resolveBy || '系统' }}
+                    </span>
+                  </template>
+                  <template v-else>
+                    <span class="pending-info">待处理</span>
+                  </template>
                 </template>
               </el-table-column>
             </el-table>
@@ -119,149 +274,215 @@
               @pagination="getList"
             />
           </div>
-
-          <!-- 添加或修改节能计量日对话框 -->
-          <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
-            <el-form ref="form" :model="form" :rules="rules" label-width="80px">
-              <el-form-item label="园区代码" prop="areaCode">
-                <el-input v-model="form.areaCode" placeholder="请输入园区代码"/>
-              </el-form-item>
-              <el-form-item label="日期" prop="date">
-                <el-date-picker
-                  clearable
-                  v-model="form.date"
-                  type="date"
-                  value-format="yyyy-MM-dd"
-                  placeholder="请选择日期"
-                >
-                </el-date-picker>
-              </el-form-item>
-              <el-form-item label="节电(千瓦时)" prop="elecEcoQuantity">
-                <el-input v-model="form.elecEcoQuantity" placeholder="请输入节电"/>
-              </el-form-item>
-              <el-form-item label="节电金额(元)" prop="elecEcoCost">
-                <el-input v-model="form.elecEcoCost" placeholder="请输入节电金额"/>
-              </el-form-item>
-              <el-form-item label="节水量 (吨)" prop="waterEcoQuantity">
-                <el-input v-model="form.waterEcoQuantity" placeholder="请输入节水量"/>
-              </el-form-item>
-              <el-form-item label="节水金额(元)" prop="waterEcoCost">
-                <el-input v-model="form.waterEcoCost" placeholder="请输入节水金额"/>
-              </el-form-item>
-            </el-form>
-            <div slot="footer" class="dialog-footer">
-              <el-button type="primary" @click="submitForm">确 定</el-button>
-              <el-button @click="cancel">取 消</el-button>
-            </div>
-          </el-dialog>
         </div>
       </el-col>
     </el-row>
+
+    <!-- 告警详情抽屉 -->
+    <el-drawer
+      title="告警详情"
+      :visible.sync="detailVisible"
+      size="500px"
+      direction="rtl"
+    >
+      <div class="detail-content" v-if="currentAlarm">
+        <div class="detail-header" :class="'level-' + currentAlarm.alarmLevel">
+          <el-tag :type="getAlarmLevelInfo(currentAlarm.alarmLevel).type" size="medium" effect="dark">
+            {{ getAlarmLevelInfo(currentAlarm.alarmLevel).label }}
+          </el-tag>
+          <el-tag :type="getAlarmStatusInfo(currentAlarm.alarmStatus).type" size="medium" effect="light" style="margin-left: 8px">
+            {{ getAlarmStatusInfo(currentAlarm.alarmStatus).label }}
+          </el-tag>
+        </div>
+        <el-descriptions :column="1" border size="small" class="detail-desc">
+          <el-descriptions-item label="告警ID">
+            <code>{{ currentAlarm.alarmId }}</code>
+          </el-descriptions-item>
+          <el-descriptions-item label="告警时间">{{ currentAlarm.alarmTime }}</el-descriptions-item>
+          <el-descriptions-item label="告警目标">{{ currentAlarm.targetName }}</el-descriptions-item>
+          <el-descriptions-item label="告警内容">{{ currentAlarm.alarmMsg }}</el-descriptions-item>
+          <el-descriptions-item label="位置">{{ currentAlarm.location || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="子系统">{{ currentAlarm.subsystemName || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="触发规则">{{ currentAlarm.ruleName || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="告警值">
+            <code v-if="currentAlarm.attrValue">{{ currentAlarm.attrValue }}</code>
+            <span v-else>-</span>
+          </el-descriptions-item>
+          <el-descriptions-item label="阈值">{{ currentAlarm.thresholdValue || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="持续时长" v-if="currentAlarm.duration">
+            {{ formatDuration(currentAlarm.duration) }}
+          </el-descriptions-item>
+          <el-descriptions-item label="确认人" v-if="currentAlarm.confirmBy">
+            {{ currentAlarm.confirmBy }} ({{ currentAlarm.confirmTime }})
+          </el-descriptions-item>
+          <el-descriptions-item label="解决人" v-if="currentAlarm.resolveBy">
+            {{ currentAlarm.resolveBy }} ({{ currentAlarm.resolveTime }})
+          </el-descriptions-item>
+          <el-descriptions-item label="解决备注" v-if="currentAlarm.resolveRemark">
+            {{ currentAlarm.resolveRemark }}
+          </el-descriptions-item>
+        </el-descriptions>
+      </div>
+    </el-drawer>
   </div>
 </template>
 
 <script>
-import { addEmsEcoD, delEmsEcoD, getEmsEcoD, updateEmsEcoD } from "@/api/ems/EmsEcoD"
-import { listAlarmInfo } from "@/api/alarm/alarm-info"
-import { areaTreeByFacsCategory } from "@/api/basecfg/area"
+import * as echarts from 'echarts'
+import { listAlarm, getAlarmStatsOverview, countAlarmByLevel, countAlarmByTrend, countAlarmBySubsystem, exportAlarm } from '@/api/alarm/alarm'
+import { areaTreeByFacsCategory } from '@/api/basecfg/area'
+import { listSubsystem } from '@/api/adapter/subsystem'
+import { parseTime } from '@/utils/ruoyi'
+
+// 告警级别配置
+const ALARM_LEVEL_OPTIONS = [
+  { value: 1, label: '一般', color: '#409EFF', type: 'primary' },
+  { value: 2, label: '重要', color: '#E6A23C', type: 'warning' },
+  { value: 3, label: '紧急', color: '#F56C6C', type: 'danger' }
+]
+
+// 告警状态配置
+const ALARM_STATUS_OPTIONS = [
+  { value: 0, label: '活动', color: '#F56C6C', type: 'danger' },
+  { value: 1, label: '已确认', color: '#E6A23C', type: 'warning' },
+  { value: 2, label: '处置中', color: '#409EFF', type: 'primary' },
+  { value: 3, label: '已解决', color: '#67C23A', type: 'success' },
+  { value: 4, label: '已关闭', color: '#909399', type: 'info' },
+  { value: 5, label: '已恢复', color: '#67C23A', type: 'success' }
+]
+
+// 目标类型图标
+const TARGET_TYPE_ICONS = {
+  0: 'el-icon-location',
+  1: 'el-icon-office-building',
+  2: 'el-icon-cpu'
+}
 
 export default {
-  name: "AlarmList",
+  name: 'AlarmReport',
   data() {
     return {
-      areaName: undefined,
+      areaName: '',
       defaultProps: {
-        children: "children",
-        label: "label"
+        children: 'children',
+        label: 'label'
       },
       defaultExpandedKeys: [],
       selectedLabel: '全部',
-      // 表单参数
       areaOptions: [],
-      facsCategory: 'E',
-      facsSubCategory: '',
-      // 遮罩层
-      loading: true,
-      // 选中数组
-      ids: [],
-      // 非单个禁用
-      single: true,
-      // 非多个禁用
-      multiple: true,
-      // 显示搜索条件
-      showSearch: true,
-      // 总条数
+      areaAlarmCounts: {},
+      loading: false,
       total: 0,
-      // 节能计量日表格数据
-      EmsEcoDList: [],
-      pvAlarmHList: [],
-      // 弹出层标题
-      title: "",
-      // 是否显示弹出层
-      open: false,
-      // 查询参数
+      alarmList: [],
+      timeRange: 'today',
+      trendGranularity: 'day',
+      dateRange: [],
       queryParams: {
         pageNum: 1,
         pageSize: 10,
         areaCode: null,
-        alarmDate: null,
+        alarmLevel: null,
+        alarmStatus: null,
+        subsystemCode: null,
+        startRecTime: null,
+        endRecTime: null
       },
-      // 表单参数
-      form: {},
-      // 表单校验
-      rules: {
-        areaCode: [
-          { required: true, message: "园区代码不能为空", trigger: "blur" }
-        ],
-        alarmDate: [
-          { required: true, message: "日期不能为空", trigger: "blur" }
-        ],
-      }
+      statsData: {
+        totalCount: 0,
+        activeCount: 0,
+        urgentCount: 0,
+        handleRate: 0,
+        todayCount: 0
+      },
+      subsystemOptions: [],
+      ALARM_LEVEL_OPTIONS,
+      ALARM_STATUS_OPTIONS,
+      // 图表实例
+      charts: {
+        level: null,
+        trend: null,
+        subsystem: null
+      },
+      // 详情抽屉
+      detailVisible: false,
+      currentAlarm: null
     }
   },
   async created() {
+    await this.loadSubsystems()
     await this.getAreaList()
+    this.initTimeRange()
     this.getList()
+    this.loadStats()
+  },
+  mounted() {
+    this.$nextTick(() => {
+      this.initCharts()
+      this.loadChartData()
+    })
+    window.addEventListener('resize', this.resizeCharts)
+  },
+  beforeDestroy() {
+    window.removeEventListener('resize', this.resizeCharts)
+    this.disposeCharts()
   },
   watch: {
-    // 根据名称筛选区域树
     areaName(val) {
       this.$refs.tree.filter(val)
     }
   },
   methods: {
+    // 获取告警级别信息
+    getAlarmLevelInfo(level) {
+      return ALARM_LEVEL_OPTIONS.find(o => o.value === level) || { label: '未知', color: '#909399', type: 'info' }
+    },
+
+    // 获取告警状态信息
+    getAlarmStatusInfo(status) {
+      return ALARM_STATUS_OPTIONS.find(o => o.value === status) || { label: '未知', color: '#909399', type: 'info' }
+    },
+
+    // 获取目标类型图标
+    getTargetTypeIcon(type) {
+      return TARGET_TYPE_ICONS[type] || 'el-icon-question'
+    },
+
+    // 加载子系统
+    async loadSubsystems() {
+      try {
+        const res = await listSubsystem({})
+        this.subsystemOptions = res.rows || res.data || []
+      } catch (e) {
+        console.error('加载子系统失败', e)
+      }
+    },
+
     // 查询区域列表
     async getAreaList() {
-      await areaTreeByFacsCategory(this.facsCategory, this.facsSubCategory, false).then(response => {
+      try {
+        const response = await areaTreeByFacsCategory('', '', false)
         this.areaOptions = [{
           id: '-1',
           label: '全部',
           children: response.data || []
         }]
-
-        // 设置默认展开第一级
         this.defaultExpandedKeys = ['-1']
-
-        // 默认选中全部
         this.$nextTick(() => {
           if (this.$refs.tree) {
             this.$refs.tree.setCurrentKey('-1')
           }
         })
-
         this.selectedLabel = '全部'
         this.queryParams.areaCode = '-1'
-      })
+      } catch (e) {
+        console.error('加载区域树失败', e)
+      }
     },
 
     // 获取树节点图标
     getTreeIcon(data) {
-      if (data.facsCategory === 'E') {
-        return 'el-icon-sunny'
-      }
-      if (data.id === '-1') {
-        return 'el-icon-s-home'
-      }
+      if (data.id === '-1') return 'el-icon-s-home'
+      if (data.children && data.children.length > 0) return 'el-icon-folder-opened'
       return 'el-icon-office-building'
     },
 
@@ -270,314 +491,832 @@ export default {
       this.$refs.tree.filter(this.areaName)
     },
 
-    /** 查询节能计量日列表 */
+    // 筛选节点
+    filterNode(value, data) {
+      if (!value) return true
+      return data.label.indexOf(value) !== -1
+    },
+
+    // 节点单击事件
+    handleNodeClick(data) {
+      this.queryParams.areaCode = data.id
+      this.selectedLabel = data.label
+      this.handleQuery()
+      this.loadStats()
+      this.loadChartData()
+    },
+
+    // 初始化时间范围
+    initTimeRange() {
+      this.handleTimeRangeChange(this.timeRange)
+    },
+
+    // 时间范围变化
+    handleTimeRangeChange(val) {
+      const now = new Date()
+      let start, end
+
+      switch (val) {
+        case 'today':
+          start = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0)
+          end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59)
+          break
+        case 'week':
+          const dayOfWeek = now.getDay() || 7
+          start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - dayOfWeek + 1, 0, 0, 0)
+          end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59)
+          break
+        case 'month':
+          start = new Date(now.getFullYear(), now.getMonth(), 1, 0, 0, 0)
+          end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59)
+          break
+        case 'custom':
+          return
+      }
+
+      this.dateRange = [parseTime(start, '{y}-{m}-{d} {h}:{i}:{s}'), parseTime(end, '{y}-{m}-{d} {h}:{i}:{s}')]
+      this.queryParams.startRecTime = this.dateRange[0]
+      this.queryParams.endRecTime = this.dateRange[1]
+      this.handleQuery()
+      this.loadStats()
+      this.loadChartData()
+    },
+
+    // 查询列表
     getList() {
       this.loading = true
-      listAlarmInfo(this.queryParams).then(response => {
-        this.pvAlarmHList = response.rows
-        this.total = response.total
+      if (this.dateRange && this.dateRange.length === 2) {
+        this.queryParams.startRecTime = this.dateRange[0]
+        this.queryParams.endRecTime = this.dateRange[1]
+      }
+
+      listAlarm(this.queryParams).then(res => {
+        this.alarmList = res.rows || []
+        this.total = res.total || 0
+      }).finally(() => {
         this.loading = false
       })
     },
 
-    // 取消按钮
-    cancel() {
-      this.open = false
-      this.reset()
-    },
-
-    // 表单重置
-    reset() {
-      this.form = {
-        id: null,
-        areaCode: null,
-        date: null
+    // 加载统计数据
+    async loadStats() {
+      try {
+        const query = {
+          areaCode: this.queryParams.areaCode,
+          startRecTime: this.queryParams.startRecTime,
+          endRecTime: this.queryParams.endRecTime
+        }
+        const res = await getAlarmStatsOverview(query)
+        const data = res.data || {}
+        this.statsData = {
+          totalCount: data.totalCount || 0,
+          activeCount: data.activeCount || 0,
+          urgentCount: data.urgentCount || 0,
+          handleRate: data.handleRate?.rate || 0,
+          todayCount: data.todayCount || 0
+        }
+      } catch (e) {
+        console.error('加载统计失败', e)
       }
-      this.resetForm("form")
     },
 
-    /** 搜索按钮操作 */
+    // 搜索
     handleQuery() {
       this.queryParams.pageNum = 1
       this.getList()
     },
 
-    /** 重置按钮操作 */
+    // 重置
     resetQuery() {
-      this.resetForm("queryForm")
+      this.timeRange = 'today'
+      this.dateRange = []
+      this.resetForm('queryForm')
+      this.initTimeRange()
+    },
+
+    // 按状态筛选
+    filterByStatus(status) {
+      this.queryParams.alarmStatus = status
       this.handleQuery()
     },
 
-    // 多选框选中数据
-    handleSelectionChange(selection) {
-      this.ids = selection.map(item => item.id)
-      this.single = selection.length !== 1
-      this.multiple = !selection.length
+    // 按级别筛选
+    filterByLevel(level) {
+      this.queryParams.alarmLevel = level
+      this.handleQuery()
     },
 
-    /** 新增按钮操作 */
-    handleAdd() {
-      this.reset()
-      this.open = true
-      this.title = "添加节能计量日"
+    // 表格行样式
+    tableRowClassName({ row }) {
+      if (row.alarmStatus === 0) {
+        if (row.alarmLevel === 3) return 'urgent-row'
+        if (row.alarmLevel === 2) return 'important-row'
+        return 'active-row'
+      }
+      return ''
     },
 
-    /** 修改按钮操作 */
-    handleUpdate(row) {
-      this.reset()
-      const id = row.id || this.ids
-      getEmsEcoD(id).then(response => {
-        this.form = response.data
-        this.open = true
-        this.title = "修改节能计量日"
-      })
+    // 行点击
+    handleRowClick(row) {
+      this.currentAlarm = row
+      this.detailVisible = true
     },
 
-    /** 提交按钮 */
-    submitForm() {
-      this.$refs["form"].validate(valid => {
-        if (valid) {
-          if (this.form.id != null) {
-            updateEmsEcoD(this.form).then(response => {
-              this.$modal.msgSuccess("修改成功")
-              this.open = false
-              this.getList()
-            })
-          } else {
-            addEmsEcoD(this.form).then(response => {
-              this.$modal.msgSuccess("新增成功")
-              this.open = false
-              this.getList()
-            })
-          }
-        }
-      })
+    // 格式化时间
+    formatTime(time) {
+      return parseTime(time, '{y}-{m}-{d} {h}:{i}')
+    },
+
+    // 格式化告警内容
+    formatAlarmContent(row) {
+      const attrName = row.attrName || row.attrKey || '属性'
+      return `${row.targetName || '设备'} ${attrName}异常`
     },
 
-    /** 删除按钮操作 */
-    handleDelete(row) {
-      const ids = row.id || this.ids
-      this.$modal.confirm('是否确认删除节能计量日编号为"' + ids + '"的数据项?').then(function () {
-        return delEmsEcoD(ids)
-      }).then(() => {
-        this.getList()
-        this.$modal.msgSuccess("删除成功")
-      }).catch(() => {})
+    // 格式化持续时长
+    formatDuration(seconds) {
+      if (!seconds) return ''
+      if (seconds < 60) return `${seconds}秒`
+      if (seconds < 3600) return `${Math.floor(seconds / 60)}分钟`
+      if (seconds < 86400) return `${Math.floor(seconds / 3600)}小时`
+      return `${Math.floor(seconds / 86400)}天`
     },
 
-    /** 导出按钮操作 */
+    // 导出
     handleExport() {
-      this.download('ems/alarm-info/export', {
+      this.download('/ems/alarm/export', {
         ...this.queryParams
-      }, `alarm_${new Date().getTime()}.xlsx`)
+      }, `alarm_report_${new Date().getTime()}.xlsx`)
     },
 
-    // 筛选节点
-    filterNode(value, data) {
-      if (!value) return true
-      return data.label.indexOf(value) !== -1
+    // ==================== 图表相关 ====================
+
+    // 初始化图表
+    initCharts() {
+      this.charts.level = echarts.init(this.$refs.levelChart)
+      this.charts.trend = echarts.init(this.$refs.trendChart)
+      this.charts.subsystem = echarts.init(this.$refs.subsystemChart)
     },
 
-    // 节点单击事件
-    handleNodeClick(data) {
-      this.queryParams.areaCode = data.id
-      this.selectedLabel = data.label
-      this.getList()
+    // 销毁图表
+    disposeCharts() {
+      Object.values(this.charts).forEach(chart => {
+        chart && chart.dispose()
+      })
+    },
+
+    // 调整图表大小
+    resizeCharts() {
+      Object.values(this.charts).forEach(chart => {
+        chart && chart.resize()
+      })
+    },
+
+    // 加载图表数据
+    async loadChartData() {
+      await Promise.all([
+        this.loadLevelChart(),
+        this.loadTrendData(),
+        this.loadSubsystemChart()
+      ])
+    },
+
+    // 加载级别分布图
+    async loadLevelChart() {
+      try {
+        const query = {
+          areaCode: this.queryParams.areaCode,
+          startRecTime: this.queryParams.startRecTime,
+          endRecTime: this.queryParams.endRecTime
+        }
+        const res = await countAlarmByLevel(query)
+        const data = (res.data || []).map(item => {
+          const levelInfo = ALARM_LEVEL_OPTIONS.find(o => o.value === item.alarmLevel)
+          return {
+            name: levelInfo ? levelInfo.label : `级别${item.alarmLevel}`,
+            value: item.cnt,
+            itemStyle: { color: levelInfo ? levelInfo.color : '#909399' }
+          }
+        })
+
+        const option = {
+          tooltip: {
+            trigger: 'item',
+            formatter: '{b}: {c} ({d}%)'
+          },
+          legend: {
+            orient: 'vertical',
+            right: 10,
+            top: 'center'
+          },
+          series: [{
+            type: 'pie',
+            radius: ['45%', '70%'],
+            center: ['40%', '50%'],
+            avoidLabelOverlap: true,
+            itemStyle: {
+              borderRadius: 6,
+              borderColor: '#fff',
+              borderWidth: 2
+            },
+            label: {
+              show: true,
+              formatter: '{b}\n{c}'
+            },
+            data: data
+          }]
+        }
+
+        this.charts.level && this.charts.level.setOption(option, true)
+      } catch (e) {
+        console.error('加载级别分布失败', e)
+      }
+    },
+
+    // 加载趋势图
+    async loadTrendData() {
+      try {
+        const query = {
+          areaCode: this.queryParams.areaCode,
+          startRecTime: this.queryParams.startRecTime,
+          endRecTime: this.queryParams.endRecTime
+        }
+        const res = await countAlarmByTrend(query, this.trendGranularity)
+        const trendData = res.data || []
+
+        const xData = trendData.map(item => item.timeIndex)
+        const yData = trendData.map(item => item.cnt)
+
+        const option = {
+          tooltip: {
+            trigger: 'axis',
+            axisPointer: { type: 'cross' }
+          },
+          grid: {
+            left: '3%',
+            right: '4%',
+            bottom: '3%',
+            top: '10%',
+            containLabel: true
+          },
+          xAxis: {
+            type: 'category',
+            boundaryGap: false,
+            data: xData,
+            axisLine: { lineStyle: { color: '#dcdfe6' } },
+            axisLabel: { color: '#606266', fontSize: 10 }
+          },
+          yAxis: {
+            type: 'value',
+            axisLine: { show: false },
+            axisTick: { show: false },
+            splitLine: { lineStyle: { color: '#ebeef5', type: 'dashed' } },
+            axisLabel: { color: '#909399' }
+          },
+          series: [{
+            name: '告警数',
+            type: 'line',
+            smooth: true,
+            symbol: 'circle',
+            symbolSize: 6,
+            itemStyle: { color: '#f56c6c' },
+            areaStyle: {
+              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                { offset: 0, color: 'rgba(245, 108, 108, 0.3)' },
+                { offset: 1, color: 'rgba(245, 108, 108, 0.05)' }
+              ])
+            },
+            data: yData
+          }]
+        }
+
+        this.charts.trend && this.charts.trend.setOption(option, true)
+      } catch (e) {
+        console.error('加载趋势数据失败', e)
+      }
+    },
+
+    // 加载子系统分布图
+    async loadSubsystemChart() {
+      try {
+        const query = {
+          areaCode: this.queryParams.areaCode,
+          startRecTime: this.queryParams.startRecTime,
+          endRecTime: this.queryParams.endRecTime
+        }
+        const res = await countAlarmBySubsystem(query)
+        const data = res.data || []
+
+        const xData = data.map(item => item.subsystemName || item.subsystemCode || '未知')
+        const yData = data.map(item => item.cnt)
+
+        const option = {
+          tooltip: {
+            trigger: 'axis',
+            axisPointer: { type: 'shadow' }
+          },
+          grid: {
+            left: '3%',
+            right: '4%',
+            bottom: '3%',
+            top: '10%',
+            containLabel: true
+          },
+          xAxis: {
+            type: 'category',
+            data: xData,
+            axisLine: { lineStyle: { color: '#dcdfe6' } },
+            axisLabel: {
+              color: '#606266',
+              fontSize: 10,
+              rotate: xData.length > 4 ? 30 : 0
+            }
+          },
+          yAxis: {
+            type: 'value',
+            axisLine: { show: false },
+            axisTick: { show: false },
+            splitLine: { lineStyle: { color: '#ebeef5', type: 'dashed' } },
+            axisLabel: { color: '#909399' }
+          },
+          series: [{
+            name: '告警数',
+            type: 'bar',
+            barWidth: '50%',
+            itemStyle: {
+              borderRadius: [4, 4, 0, 0],
+              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                { offset: 0, color: '#409eff' },
+                { offset: 1, color: '#79bbff' }
+              ])
+            },
+            data: yData
+          }]
+        }
+
+        this.charts.subsystem && this.charts.subsystem.setOption(option, true)
+      } catch (e) {
+        console.error('加载子系统分布失败', e)
+      }
     }
   }
 }
 </script>
 
 <style lang="scss" scoped>
-.app-container {
+.alarm-report-container {
   padding: 20px;
-  background: #f5f7fa;
+  background: linear-gradient(135deg, #f5f7fa 0%, #e8ecf1 100%);
   min-height: calc(100vh - 84px);
+}
 
-  .head-container {
-    background: #fff;
-    padding: 15px;
-    border-radius: 8px;
-    margin-bottom: 15px;
-    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+// 左侧树面板
+.tree-panel {
+  background: #fff;
+  border-radius: 12px;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+  overflow: hidden;
 
-    &.tree-container {
-      max-height: calc(100vh - 280px);
-      overflow-y: auto;
+  .tree-search {
+    padding: 16px;
+    border-bottom: 1px solid #f0f2f5;
+    background: linear-gradient(135deg, #fafbfc 0%, #f5f7fa 100%);
 
-      &::-webkit-scrollbar {
-        width: 6px;
-      }
+    ::v-deep .el-input__inner {
+      border-radius: 20px;
+      background: #fff;
+      border: 1px solid #e4e7ed;
+      transition: all 0.3s;
 
-      &::-webkit-scrollbar-track {
-        background: #f1f1f1;
-        border-radius: 3px;
+      &:focus {
+        border-color: #409eff;
+        box-shadow: 0 0 0 3px rgba(64, 158, 255, 0.1);
       }
+    }
+  }
 
-      &::-webkit-scrollbar-thumb {
-        background: #c1c1c1;
-        border-radius: 3px;
-      }
+  .tree-content {
+    max-height: calc(100vh - 240px);
+    overflow-y: auto;
+    padding: 8px;
 
-      &::-webkit-scrollbar-thumb:hover {
-        background: #a8a8a8;
-      }
+    &::-webkit-scrollbar {
+      width: 6px;
+    }
 
-      ::v-deep .el-tree {
-        background: transparent;
+    &::-webkit-scrollbar-track {
+      background: transparent;
+    }
 
-        .el-tree-node__content {
-          height: 40px;
-          padding: 0 8px;
-          transition: all 0.3s;
+    &::-webkit-scrollbar-thumb {
+      background: #dcdfe6;
+      border-radius: 3px;
+    }
 
-          &:hover {
-            background-color: #f5f7fa;
-          }
-        }
+    ::v-deep .el-tree {
+      background: transparent;
 
-        .el-tree-node.is-current > .el-tree-node__content {
-          background-color: #ecf5ff;
-          color: #409eff;
+      .el-tree-node__content {
+        height: 44px;
+        border-radius: 8px;
+        margin: 2px 0;
+        transition: all 0.3s;
+
+        &:hover {
+          background: #f5f7fa;
         }
+      }
+
+      .el-tree-node.is-current > .el-tree-node__content {
+        background: linear-gradient(135deg, #ecf5ff 0%, #e6f1fc 100%);
+        color: #409eff;
+      }
 
-        .custom-tree-node {
-          flex: 1;
+      .custom-tree-node {
+        flex: 1;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        padding-right: 8px;
+
+        .tree-label {
           display: flex;
           align-items: center;
-          justify-content: space-between;
-          font-size: 14px;
-
-          .tree-label {
-            display: flex;
-            align-items: center;
-
-            .tree-icon {
-              margin-right: 8px;
-              font-size: 16px;
-              color: #909399;
-              transition: color 0.3s;
-            }
-          }
 
-          .tree-tag {
+          .tree-icon {
             margin-right: 8px;
+            font-size: 16px;
+            color: #909399;
           }
         }
 
-        .el-tree-node.is-current .tree-icon {
-          color: #409eff;
+        .tree-badge {
+          ::v-deep .el-badge__content {
+            background: #f56c6c;
+          }
         }
       }
     }
   }
+}
+
+// 右侧内容
+.content-wrapper {
+  .stats-row {
+    display: grid;
+    grid-template-columns: repeat(4, 1fr);
+    gap: 16px;
+    margin-bottom: 20px;
+  }
 
-  .content-wrapper {
+  .stat-card {
     background: #fff;
-    border-radius: 8px;
+    border-radius: 12px;
     padding: 20px;
-    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
-    min-height: calc(100vh - 160px);
+    display: flex;
+    align-items: center;
+    position: relative;
+    overflow: hidden;
+    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.06);
+    cursor: pointer;
+    transition: all 0.3s;
 
-    .content-header {
+    &:hover {
+      transform: translateY(-4px);
+      box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
+    }
+
+    &::before {
+      content: '';
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 4px;
+      height: 100%;
+    }
+
+    &.total::before { background: linear-gradient(180deg, #667eea 0%, #764ba2 100%); }
+    &.active::before { background: linear-gradient(180deg, #f56c6c 0%, #e6a23c 100%); }
+    &.urgent::before { background: linear-gradient(180deg, #f56c6c 0%, #ff7875 100%); }
+    &.handled::before { background: linear-gradient(180deg, #67c23a 0%, #95d475 100%); }
+
+    .stat-icon {
+      width: 52px;
+      height: 52px;
+      border-radius: 12px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      margin-right: 16px;
+
+      i { font-size: 26px; color: #fff; }
+    }
+
+    &.total .stat-icon { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
+    &.active .stat-icon { background: linear-gradient(135deg, #f56c6c 0%, #e6a23c 100%); }
+    &.urgent .stat-icon { background: linear-gradient(135deg, #f56c6c 0%, #ff7875 100%); }
+    &.handled .stat-icon { background: linear-gradient(135deg, #67c23a 0%, #95d475 100%); }
+
+    .stat-info {
+      flex: 1;
+
+      .stat-value {
+        font-size: 28px;
+        font-weight: 700;
+        color: #303133;
+        line-height: 1.2;
+      }
+
+      .stat-label {
+        font-size: 13px;
+        color: #909399;
+        margin-top: 4px;
+      }
+    }
+
+    .stat-trend {
+      position: absolute;
+      top: 12px;
+      right: 12px;
+      font-size: 11px;
+      color: #67c23a;
+      background: rgba(103, 194, 58, 0.1);
+      padding: 2px 8px;
+      border-radius: 10px;
+    }
+
+    .stat-pulse {
+      position: absolute;
+      top: 12px;
+      right: 12px;
+      width: 10px;
+      height: 10px;
+      background: #f56c6c;
+      border-radius: 50%;
+      animation: pulse 1.5s infinite;
+    }
+
+    .stat-progress {
+      position: absolute;
+      bottom: 0;
+      left: 0;
+      right: 0;
+      height: 4px;
+      background: #f5f7fa;
+
+      .progress-bar {
+        height: 100%;
+        background: linear-gradient(90deg, #67c23a 0%, #95d475 100%);
+        border-radius: 0 2px 2px 0;
+        transition: width 0.6s ease;
+      }
+    }
+  }
+
+  @keyframes pulse {
+    0%, 100% { opacity: 1; transform: scale(1); }
+    50% { opacity: 0.5; transform: scale(1.2); }
+  }
+
+  // 标题区域
+  .content-header {
+    background: #fff;
+    border-radius: 12px;
+    padding: 16px 20px;
+    margin-bottom: 16px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.06);
+
+    .page-title {
+      margin: 0;
+      font-size: 18px;
+      font-weight: 600;
+      color: #303133;
+      display: flex;
+      align-items: center;
+
+      i {
+        margin-right: 8px;
+        color: #409eff;
+      }
+
+      .area-tag {
+        margin-left: 12px;
+        font-size: 13px;
+        font-weight: 400;
+        color: #909399;
+        background: #f5f7fa;
+        padding: 4px 12px;
+        border-radius: 12px;
+      }
+    }
+  }
+
+  // 搜索区域
+  .search-container {
+    background: #fff;
+    border-radius: 12px;
+    padding: 16px 20px;
+    margin-bottom: 16px;
+    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.06);
+
+    ::v-deep .el-form-item {
+      margin-bottom: 0;
+      margin-right: 16px;
+    }
+  }
+
+  // 图表区域
+  .chart-row {
+    display: grid;
+    grid-template-columns: 1fr 1.5fr 1fr;
+    gap: 16px;
+    margin-bottom: 16px;
+  }
+
+  .chart-card {
+    background: #fff;
+    border-radius: 12px;
+    padding: 16px;
+    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.06);
+
+    .chart-header {
       display: flex;
       justify-content: space-between;
       align-items: center;
-      margin-bottom: 20px;
-      padding-bottom: 15px;
-      border-bottom: 2px solid #f0f2f5;
+      margin-bottom: 12px;
+      padding-bottom: 12px;
+      border-bottom: 1px solid #f0f2f5;
 
-      .page-title {
-        margin: 0;
-        font-size: 20px;
+      .chart-title {
+        font-size: 14px;
         font-weight: 600;
         color: #303133;
 
         i {
-          margin-right: 8px;
-          color: #e6a23c;
+          margin-right: 6px;
+          color: #409eff;
         }
       }
     }
 
-    .search-container {
-      background: #f9fbfd;
-      border-radius: 8px;
-      padding: 15px;
-      margin-bottom: 20px;
+    .chart-body {
+      height: 220px;
+    }
+  }
 
-      ::v-deep .el-form {
-        .el-form-item {
-          margin-bottom: 0;
-          margin-right: 15px;
+  // 表格区域
+  .table-container {
+    background: #fff;
+    border-radius: 12px;
+    padding: 20px;
+    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.06);
 
-          .el-form-item__label {
-            font-weight: 500;
-            color: #606266;
-          }
-        }
+    .table-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 16px;
+      padding-bottom: 12px;
+      border-bottom: 1px solid #f0f2f5;
 
-        .el-button {
-          padding: 7px 15px;
+      .table-title {
+        font-size: 15px;
+        font-weight: 600;
+        color: #303133;
+
+        i {
+          margin-right: 6px;
+          color: #409eff;
         }
       }
+
+      .table-total {
+        font-size: 13px;
+        color: #909399;
+      }
     }
 
-    .table-container {
-      ::v-deep .el-table {
-        .el-table__header {
-          th {
-            background-color: #f5f7fa;
-            color: #606266;
-            font-weight: 600;
-            font-size: 14px;
-          }
-        }
+    ::v-deep .el-table {
+      .urgent-row {
+        background: rgba(245, 108, 108, 0.08) !important;
+      }
 
-        .el-table__body {
-          .time-text {
-            font-weight: 500;
-            color: #606266;
-          }
+      .important-row {
+        background: rgba(230, 162, 60, 0.08) !important;
+      }
 
-          .system-text {
-            color: #409eff;
-            font-weight: 500;
-          }
+      .active-row {
+        background: rgba(64, 158, 255, 0.05) !important;
+      }
 
-          .alarm-msg {
-            color: #e6a23c;
-            font-weight: 500;
-          }
+      .time-text {
+        color: #606266;
+        i { margin-right: 4px; color: #909399; }
+      }
+
+      .subsystem-text {
+        color: #409eff;
+        font-weight: 500;
+      }
+
+      .target-cell {
+        display: flex;
+        align-items: center;
+
+        .target-icon {
+          margin-right: 6px;
+          color: #409eff;
         }
       }
+
+      .alarm-msg {
+        color: #e6a23c;
+      }
+
+      .location-text {
+        color: #909399;
+        font-size: 12px;
+        i { margin-right: 2px; }
+      }
+
+      .handled-info {
+        color: #67c23a;
+        font-size: 12px;
+        i { margin-right: 2px; }
+      }
+
+      .pending-info {
+        color: #f56c6c;
+        font-size: 12px;
+      }
+    }
+  }
+}
+
+// 详情抽屉
+.detail-content {
+  padding: 20px;
+
+  .detail-header {
+    padding: 16px;
+    border-radius: 8px;
+    margin-bottom: 20px;
+
+    &.level-1 { background: linear-gradient(135deg, rgba(64, 158, 255, 0.1) 0%, rgba(64, 158, 255, 0.05) 100%); }
+    &.level-2 { background: linear-gradient(135deg, rgba(230, 162, 60, 0.1) 0%, rgba(230, 162, 60, 0.05) 100%); }
+    &.level-3 { background: linear-gradient(135deg, rgba(245, 108, 108, 0.1) 0%, rgba(245, 108, 108, 0.05) 100%); }
+  }
+
+  .detail-desc {
+    ::v-deep .el-descriptions-item__label {
+      width: 90px;
+      font-weight: 500;
+    }
+
+    code {
+      background: #f5f7fa;
+      padding: 2px 8px;
+      border-radius: 4px;
+      font-family: 'Monaco', 'Menlo', monospace;
+      font-size: 12px;
+      color: #409eff;
+    }
+  }
+}
+
+// 响应式
+@media (max-width: 1400px) {
+  .content-wrapper {
+    .stats-row {
+      grid-template-columns: repeat(2, 1fr);
+    }
+
+    .chart-row {
+      grid-template-columns: 1fr 1fr;
     }
   }
 }
 
-// 响应式布局
 @media (max-width: 768px) {
-  .app-container {
+  .alarm-report-container {
     padding: 10px;
+  }
 
-    .el-col {
-      margin-bottom: 20px;
+  .content-wrapper {
+    .stats-row {
+      grid-template-columns: 1fr;
     }
 
-    .content-wrapper {
-      .content-header {
-        flex-direction: column;
-        align-items: flex-start;
-      }
+    .chart-row {
+      grid-template-columns: 1fr;
+    }
 
-      .search-container {
-        ::v-deep .el-form {
-          .el-form-item {
-            display: block;
-            margin-bottom: 10px;
-          }
-        }
-      }
+    .content-header {
+      flex-direction: column;
+      gap: 12px;
     }
   }
 }

+ 909 - 437
ems-ui-cloud/src/views/devmgr/warn/index.vue

@@ -1,168 +1,303 @@
 <template>
-  <div class="app-container device-alarm-content">
+  <div class="app-container device-alarm-dashboard">
     <el-row :gutter="20">
+      <!-- 左侧区域树 -->
       <el-col :span="4" :xs="24">
-        <div class="head-container">
-          <el-input
-            v-model="areaName"
-            placeholder="请输入服务区名称"
-            clearable
-            size="small"
-            prefix-icon="el-icon-search"
-            style="margin-bottom: 20px"
-            @input="filterTree"
-          />
-        </div>
-        <div class="head-container tree-container">
-          <el-tree
-            ref="tree"
-            :data="areaOptions"
-            :props="defaultProps"
-            :expand-on-click-node="false"
-            :filter-node-method="filterNode"
-            node-key="id"
-            :default-expanded-keys="defaultExpandedKeys"
-            highlight-current
-            @node-click="handleNodeClick"
-          >
-            <span class="custom-tree-node" slot-scope="{ node, data }">
-              <span class="tree-label">
-                <i :class="getTreeIcon(data)" class="tree-icon"></i>
-                {{ node.label }}
+        <div class="tree-panel">
+          <div class="tree-search">
+            <el-input
+              v-model="areaName"
+              placeholder="搜索服务区..."
+              clearable
+              size="small"
+              prefix-icon="el-icon-search"
+              @input="filterTree"
+            />
+          </div>
+          <div class="tree-content">
+            <el-tree
+              ref="tree"
+              :data="areaOptions"
+              :props="defaultProps"
+              :expand-on-click-node="false"
+              :filter-node-method="filterNode"
+              node-key="id"
+              :default-expanded-keys="defaultExpandedKeys"
+              highlight-current
+              @node-click="handleNodeClick"
+            >
+              <span class="custom-tree-node" slot-scope="{ node, data }">
+                <span class="tree-label">
+                  <i :class="getTreeIcon(data)" class="tree-icon"></i>
+                  {{ node.label }}
+                </span>
+                <el-badge
+                  v-if="areaAlarmCounts[data.id] > 0"
+                  :value="areaAlarmCounts[data.id]"
+                  :max="99"
+                  class="area-badge"
+                />
               </span>
-            </span>
-          </el-tree>
+            </el-tree>
+          </div>
         </div>
       </el-col>
 
+      <!-- 右侧主内容 -->
       <el-col :span="20" :xs="24">
-        <el-row :gutter="20" class="kpi-cards">
-          <el-col :span="6" v-for="(item, index) in kpiData" :key="index">
-            <div class="kpi-card" :class="'kpi-' + item.type">
-              <div class="kpi-icon">
-                <i :class="item.icon"></i>
-              </div>
-              <div class="kpi-content">
-                <div class="kpi-value">{{ item.value }}</div>
-                <div class="kpi-label">{{ item.label }}</div>
-              </div>
+        <!-- KPI 统计卡片 -->
+        <div class="kpi-row">
+          <div class="kpi-card total">
+            <div class="kpi-icon">
+              <i class="el-icon-monitor"></i>
             </div>
-          </el-col>
-        </el-row>
+            <div class="kpi-info">
+              <div class="kpi-value">{{ kpiData.deviceTotal }}</div>
+              <div class="kpi-label">设备总数</div>
+            </div>
+            <div class="kpi-decoration"></div>
+          </div>
+          <div class="kpi-card online">
+            <div class="kpi-icon">
+              <i class="el-icon-success"></i>
+            </div>
+            <div class="kpi-info">
+              <div class="kpi-value">{{ kpiData.onlineCount }}</div>
+              <div class="kpi-label">在线设备</div>
+            </div>
+            <div class="kpi-rate" v-if="kpiData.deviceTotal > 0">
+              {{ ((kpiData.onlineCount / kpiData.deviceTotal) * 100).toFixed(1) }}%
+            </div>
+          </div>
+          <div class="kpi-card offline">
+            <div class="kpi-icon">
+              <i class="el-icon-warning"></i>
+            </div>
+            <div class="kpi-info">
+              <div class="kpi-value">{{ kpiData.offlineCount }}</div>
+              <div class="kpi-label">离线设备</div>
+            </div>
+            <div class="kpi-pulse" v-if="kpiData.offlineCount > 0"></div>
+          </div>
+          <div class="kpi-card alarm">
+            <div class="kpi-icon">
+              <i class="el-icon-bell"></i>
+            </div>
+            <div class="kpi-info">
+              <div class="kpi-value">{{ kpiData.activeAlarmCount }}</div>
+              <div class="kpi-label">待处理告警</div>
+            </div>
+            <div class="kpi-urgent" v-if="kpiData.urgentCount > 0">
+              <i class="el-icon-warning-outline"></i>
+              {{ kpiData.urgentCount }} 紧急
+            </div>
+          </div>
+        </div>
 
-        <el-row :gutter="20" style="margin-top: 20px">
+        <!-- 图表第一行 -->
+        <el-row :gutter="20" class="chart-row">
           <el-col :span="12">
-            <div class="dashboard-card">
-              <PieChartBlock title="设备状态分布" :opt-cfg="deviceStatusChart" style="height: 100%"/>
+            <div class="chart-card">
+              <div class="card-header">
+                <span class="card-title">
+                  <i class="el-icon-pie-chart"></i> 设备状态分布
+                </span>
+              </div>
+              <div ref="deviceStatusChart" class="chart-body"></div>
             </div>
           </el-col>
           <el-col :span="12">
-            <div class="dashboard-card">
-              <LineChartBlock title="告警趋势分析" :opt-cfg="alarmTrendChart" style="height: 100%">
-                <template v-slot:filters>
-                  <SwitchTag
-                    :ds="dateTypeOptions"
-                    :defTag="dateType"
-                    :tagClick="onDateTypeSwitch"
-                  />
-                </template>
-              </LineChartBlock>
+            <div class="chart-card">
+              <div class="card-header">
+                <span class="card-title">
+                  <i class="el-icon-data-line"></i> 告警趋势分析
+                </span>
+                <el-radio-group v-model="trendType" size="mini" @change="loadAlarmTrend">
+                  <el-radio-button label="day">按时</el-radio-button>
+                  <el-radio-button label="month">按天</el-radio-button>
+                </el-radio-group>
+              </div>
+              <div ref="alarmTrendChart" class="chart-body"></div>
             </div>
           </el-col>
         </el-row>
 
-        <el-row :gutter="20" style="margin-top: 20px">
+        <!-- 图表第二行:实时告警 + 告警级别分布 -->
+        <el-row :gutter="20" class="chart-row">
           <el-col :span="14">
-            <div class="dashboard-card table-card">
-              <BlockTable title="实时告警列表" :table-data="realTimeAlarmData" style="height: 100%">
-                <template v-slot:columns>
-                  <el-table-column type="index" label="序号" align="center" width="60"/>
-                  <el-table-column prop="subSystemName" label="子系统" align="center" width="100"/>
-                  <el-table-column prop="objName" label="设备名称" align="center" show-overflow-tooltip/>
-                  <el-table-column prop="alarmMsg" label="告警描述" align="center" show-overflow-tooltip/>
-                  <el-table-column prop="alarmTime" label="告警时间" align="center" width="160"/>
-                  <el-table-column label="告警类型" align="center" width="100">
+            <div class="chart-card table-card">
+              <div class="card-header">
+                <span class="card-title">
+                  <i class="el-icon-bell"></i> 实时告警列表
+                </span>
+                <span class="table-count" v-if="realTimeAlarmList.length > 0">
+                  显示最新 {{ realTimeAlarmList.length }} 条
+                </span>
+              </div>
+              <div class="table-body">
+                <el-table
+                  v-loading="tableLoading"
+                  :data="realTimeAlarmList"
+                  stripe
+                  size="small"
+                  :row-class-name="tableRowClassName"
+                >
+                  <el-table-column type="index" label="#" width="45" align="center" />
+                  <el-table-column label="级别" width="70" align="center">
                     <template slot-scope="scope">
-                      <el-tag :type="getAlarmTypeTag(scope.row.alarmType)" size="small">
-                        {{ getAlarmTypeName(scope.row.alarmType) }}
+                      <el-tag
+                        :type="getAlarmLevelTag(scope.row.alarmLevel)"
+                        size="mini"
+                        effect="dark"
+                        :class="{ 'blink': scope.row.alarmLevel === 3 }"
+                      >
+                        {{ getAlarmLevelName(scope.row.alarmLevel) }}
                       </el-tag>
                     </template>
                   </el-table-column>
-                  <el-table-column label="状态" align="center" width="100">
+                  <el-table-column label="子系统" width="90" align="center" prop="subsystemName" show-overflow-tooltip />
+                  <el-table-column label="设备名称" min-width="120" prop="targetName" show-overflow-tooltip>
                     <template slot-scope="scope">
-                      <el-dropdown
-                        @command="(cmd) => handleAlarmStateChange(cmd, scope.row)"
-                        v-if="scope.row.alarmState !== 2 && scope.row.alarmState !== 3"
-                      >
-                        <span class="el-dropdown-link">
-                          <el-tag :type="getAlarmStateTag(scope.row.alarmState)" size="small">
-                            {{ getAlarmStateName(scope.row.alarmState) }}
+                      <span class="device-name">{{ scope.row.targetName || scope.row.targetCode }}</span>
+                    </template>
+                  </el-table-column>
+                  <el-table-column label="告警描述" min-width="150" prop="alarmMsg" show-overflow-tooltip>
+                    <template slot-scope="scope">
+                      <span class="alarm-msg">{{ scope.row.alarmMsg || formatAlarmContent(scope.row) }}</span>
+                    </template>
+                  </el-table-column>
+                  <el-table-column label="告警时间" width="140" align="center">
+                    <template slot-scope="scope">
+                      <span class="alarm-time">{{ formatTime(scope.row.alarmTime) }}</span>
+                    </template>
+                  </el-table-column>
+                  <el-table-column label="操作" width="120" align="center" fixed="right">
+                    <template slot-scope="scope">
+                      <template v-if="scope.row.alarmStatus < 3">
+                        <el-dropdown
+                          trigger="click"
+                          @command="(cmd) => handleAlarmAction(cmd, scope.row)"
+                        >
+                          <el-tag
+                            :type="getAlarmStatusTag(scope.row.alarmStatus)"
+                            size="mini"
+                            class="status-dropdown"
+                          >
+                            {{ getAlarmStatusName(scope.row.alarmStatus) }}
+                            <i class="el-icon-arrow-down"></i>
                           </el-tag>
-                          <i class="el-icon-arrow-down el-icon--right"></i>
-                        </span>
-                        <el-dropdown-menu slot="dropdown">
-                          <el-dropdown-item :command="1" v-if="scope.row.alarmState === 0">
-                            开始处理
-                          </el-dropdown-item>
-                          <el-dropdown-item :command="2">已处置</el-dropdown-item>
-                          <el-dropdown-item :command="3">已消散</el-dropdown-item>
-                        </el-dropdown-menu>
-                      </el-dropdown>
-                      <el-tag v-else :type="getAlarmStateTag(scope.row.alarmState)" size="small">
-                        {{ getAlarmStateName(scope.row.alarmState) }}
-                      </el-tag>
+                          <el-dropdown-menu slot="dropdown">
+                            <el-dropdown-item command="confirm" v-if="scope.row.alarmStatus === 0">
+                              <i class="el-icon-check"></i> 确认告警
+                            </el-dropdown-item>
+                            <el-dropdown-item command="handle">
+                              <i class="el-icon-s-tools"></i> 开始处置
+                            </el-dropdown-item>
+                            <el-dropdown-item command="resolve" divided>
+                              <i class="el-icon-circle-check"></i> 标记解决
+                            </el-dropdown-item>
+                            <el-dropdown-item command="close">
+                              <i class="el-icon-circle-close"></i> 关闭告警
+                            </el-dropdown-item>
+                          </el-dropdown-menu>
+                        </el-dropdown>
+                      </template>
+                      <template v-else>
+                        <el-tag :type="getAlarmStatusTag(scope.row.alarmStatus)" size="mini">
+                          {{ getAlarmStatusName(scope.row.alarmStatus) }}
+                        </el-tag>
+                      </template>
                     </template>
                   </el-table-column>
-                </template>
-              </BlockTable>
+                </el-table>
+              </div>
             </div>
           </el-col>
-
           <el-col :span="10">
-            <div class="dashboard-card"
-                 v-if="alarmTypeChart.series[0].data && alarmTypeChart.series[0].data.length > 0"
-            >
-              <PieChartBlock title="告警类型分布" :opt-cfg="alarmTypeChart" style="height: 100%"/>
-            </div>
-
-            <div class="dashboard-card empty-state" v-else>
-              <div class="card-title">告警类型分布</div>
-              <el-empty description="暂无告警类型数据" :image-size="120"></el-empty>
+            <div class="chart-card">
+              <div class="card-header">
+                <span class="card-title">
+                  <i class="el-icon-s-data"></i> 告警级别分布
+                </span>
+                <span class="card-subtitle">本年度</span>
+              </div>
+              <div ref="alarmLevelChart" class="chart-body" v-if="hasAlarmLevelData"></div>
+              <div class="empty-chart" v-else>
+                <el-empty description="暂无告警数据" :image-size="100"></el-empty>
+              </div>
             </div>
           </el-col>
         </el-row>
       </el-col>
     </el-row>
+
+    <!-- 告警处置对话框 -->
+    <el-dialog
+      :title="handleDialogTitle"
+      :visible.sync="handleDialogVisible"
+      width="500px"
+      append-to-body
+    >
+      <el-form ref="handleForm" :model="handleForm" label-width="80px">
+        <el-form-item label="告警目标">
+          <span>{{ currentAlarm.targetName }}</span>
+        </el-form-item>
+        <el-form-item label="告警内容">
+          <span>{{ currentAlarm.alarmMsg }}</span>
+        </el-form-item>
+        <el-form-item :label="handleForm.type === 'resolve' ? '解决说明' : '处置说明'">
+          <el-input
+            v-model="handleForm.content"
+            type="textarea"
+            :rows="3"
+            :placeholder="handleForm.type === 'resolve' ? '请描述解决方案' : '请描述处置措施'"
+          />
+        </el-form-item>
+      </el-form>
+      <div slot="footer">
+        <el-button @click="handleDialogVisible = false">取 消</el-button>
+        <el-button type="primary" @click="submitAlarmHandle" :loading="handleSubmitting">确 定</el-button>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
 <script>
+import * as echarts from 'echarts'
 import { areaTreeSelect } from '@/api/basecfg/area'
-import {
-  listAlarmInfo,
-  updateAlarmInfo,
-  fetchAlarmIndexDay,
-  fetchAlarmIndexMonth,
-  fetchAlarmIndexYear,
-  fetchAlarmIndex
-} from '@/api/alarm/alarm-info'
 import { listDeviceStatus } from '@/api/device/device'
-import BlockTable from '@/components/Block/BlockTable/index.vue'
-import LineChartBlock from '@/components/Block/charts/LineChartBlock.vue'
-import PieChartBlock from '@/components/Block/charts/PieChartBlock.vue'
-import SwitchTag from '@/components/SwitchTag/index.vue'
-import { DateTool } from '@/utils/DateTool'
-import dayjs from 'dayjs'
-import _ from 'lodash'
+import {
+  listAlarm,
+  listActiveAlarm,
+  confirmAlarm,
+  handleAlarm,
+  resolveAlarm,
+  closeAlarm,
+  getAlarmStatsOverview,
+  countAlarmByLevel,
+  countAlarmByTrend
+} from '@/api/alarm/alarm'
+import { parseTime } from '@/utils/ruoyi'
+
+// 告警级别配置 - 适配新模型
+const ALARM_LEVEL_MAP = {
+  1: { name: '一般', tag: 'info', color: '#409EFF' },
+  2: { name: '重要', tag: 'warning', color: '#E6A23C' },
+  3: { name: '紧急', tag: 'danger', color: '#F56C6C' }
+}
+
+// 告警状态配置 - 适配新模型
+const ALARM_STATUS_MAP = {
+  0: { name: '活动', tag: 'danger' },
+  1: { name: '已确认', tag: 'warning' },
+  2: { name: '处置中', tag: 'primary' },
+  3: { name: '已解决', tag: 'success' },
+  4: { name: '已关闭', tag: 'info' },
+  5: { name: '已恢复', tag: 'success' }
+}
 
 export default {
-  name: 'DeviceAlarm',
-  components: {
-    BlockTable,
-    LineChartBlock,
-    PieChartBlock,
-    SwitchTag
-  },
+  name: 'DeviceAlarmDashboard',
   data() {
     return {
       // 区域筛选
@@ -174,28 +309,46 @@ export default {
         children: 'children',
         label: 'label'
       },
+      areaAlarmCounts: {},
+
+      // KPI 数据
+      kpiData: {
+        deviceTotal: 0,
+        onlineCount: 0,
+        offlineCount: 0,
+        activeAlarmCount: 0,
+        urgentCount: 0
+      },
+
+      // 趋势图类型
+      trendType: 'day',
+
+      // 实时告警
+      tableLoading: false,
+      realTimeAlarmList: [],
+
+      // 告警级别分布数据
+      hasAlarmLevelData: false,
+
+      // 图表实例
+      charts: {
+        deviceStatus: null,
+        alarmTrend: null,
+        alarmLevel: null
+      },
 
-      // 时间维度
-      dateType: { val: 'year', text: '按年' },
-      dateTypeOptions: [
-        { val: 'day', text: '按日' },
-        { val: 'month', text: '按月' },
-        { val: 'year', text: '按年' }
-      ],
-
-      // KPI数据
-      kpiData: [
-        { label: '设备总数', value: 0, icon: 'el-icon-monitor', type: 'primary' },
-        { label: '在线设备', value: 0, icon: 'el-icon-circle-check', type: 'success' },
-        { label: '离线设备', value: 0, icon: 'el-icon-warning-outline', type: 'danger' },
-        { label: '待处理告警', value: 0, icon: 'el-icon-bell', type: 'warning' }
-      ],
-
-      // 图表数据初始化
-      deviceStatusChart: { series: [{ type: 'pie', radius: ['50%', '70%'], data: [] }] },
-      alarmTrendChart: { unit: '', xAxis: { type: 'category', data: [] }, series: [] },
-      alarmTypeChart: { series: [{ type: 'pie', radius: ['0%', '70%'], data: [] }] },
-      realTimeAlarmData: []
+      // 处置对话框
+      handleDialogVisible: false,
+      handleDialogTitle: '处置告警',
+      handleSubmitting: false,
+      currentAlarm: {},
+      handleForm: {
+        type: 'handle',
+        content: ''
+      },
+
+      // 刷新定时器
+      refreshTimer: null
     }
   },
   watch: {
@@ -206,11 +359,16 @@ export default {
   mounted() {
     this.getAreaTree()
   },
+  beforeDestroy() {
+    this.stopAutoRefresh()
+    this.disposeCharts()
+    window.removeEventListener('resize', this.resizeCharts)
+  },
   methods: {
+    // ==================== 基础方法 ====================
     getTreeIcon(data) {
-      if (data.id === '-1') {
-        return 'el-icon-s-home'
-      }
+      if (data.id === '-1') return 'el-icon-s-home'
+      if (data.children && data.children.length > 0) return 'el-icon-folder-opened'
       return 'el-icon-office-building'
     },
 
@@ -223,6 +381,40 @@ export default {
       return data.label.indexOf(value) !== -1
     },
 
+    getAlarmLevelName(level) {
+      return ALARM_LEVEL_MAP[level]?.name || '未知'
+    },
+
+    getAlarmLevelTag(level) {
+      return ALARM_LEVEL_MAP[level]?.tag || 'info'
+    },
+
+    getAlarmStatusName(status) {
+      return ALARM_STATUS_MAP[status]?.name || '未知'
+    },
+
+    getAlarmStatusTag(status) {
+      return ALARM_STATUS_MAP[status]?.tag || 'info'
+    },
+
+    formatTime(time) {
+      return parseTime(time, '{m}-{d} {h}:{i}')
+    },
+
+    formatAlarmContent(row) {
+      const attrName = row.attrName || row.attrKey || '属性'
+      return `${row.targetName || '设备'} ${attrName}异常`
+    },
+
+    tableRowClassName({ row }) {
+      if (row.alarmStatus === 0) {
+        if (row.alarmLevel === 3) return 'urgent-row'
+        if (row.alarmLevel === 2) return 'important-row'
+      }
+      return ''
+    },
+
+    // ==================== 数据加载 ====================
     async getAreaTree() {
       try {
         const response = await areaTreeSelect('0', 1)
@@ -235,7 +427,10 @@ export default {
           if (this.$refs.tree) {
             this.$refs.tree.setCurrentKey('-1')
           }
+          this.initCharts()
           this.loadAllData()
+          this.startAutoRefresh()
+          window.addEventListener('resize', this.resizeCharts)
         })
       } catch (error) {
         console.error('获取区域树失败:', error)
@@ -248,15 +443,10 @@ export default {
       this.loadAllData()
     },
 
-    async onDateTypeSwitch(item) {
-      this.dateType = item
-      await this.loadAlarmTrend()
-    },
-
-    buildQueryParams(extraParams = {}) {
+    buildQueryParams(extra = {}) {
       return {
-        areaCode: this.areaCode || '',
-        ...extraParams
+        areaCode: this.areaCode || undefined,
+        ...extra
       }
     },
 
@@ -267,403 +457,685 @@ export default {
           this.loadDeviceStatus(),
           this.loadAlarmTrend(),
           this.loadRealTimeAlarm(),
-          this.loadAlarmTypeDistribution()
+          this.loadAlarmLevelDistribution()
         ])
       } catch (error) {
         console.error('加载数据失败:', error)
       }
     },
 
+    // 加载 KPI 数据
     async loadKpiData() {
       try {
+        // 设备状态
         const deviceRes = await listDeviceStatus(this.buildQueryParams())
         const deviceData = deviceRes.data || {}
-        const alarmRes = await listAlarmInfo(
-          this.buildQueryParams({ pageNum: 1, pageSize: 1, alarmStateList: [0, 1] })
-        )
-
-        this.kpiData = [
-          { label: '设备总数', value: deviceData.total || 0, icon: 'el-icon-monitor', type: 'primary' },
-          { label: '在线设备', value: deviceData.onlineCount || 0, icon: 'el-icon-circle-check', type: 'success' },
-          { label: '离线设备', value: deviceData.offlineCount || 0, icon: 'el-icon-warning-outline', type: 'danger' },
-          { label: '待处理告警', value: alarmRes.total || 0, icon: 'el-icon-bell', type: 'warning' }
-        ]
+
+        // 告警统计 - 使用新接口
+        const alarmRes = await getAlarmStatsOverview(this.buildQueryParams())
+        const alarmData = alarmRes.data || {}
+
+        this.kpiData = {
+          deviceTotal: deviceData.total || 0,
+          onlineCount: deviceData.onlineCount || 0,
+          offlineCount: deviceData.offlineCount || 0,
+          activeAlarmCount: alarmData.activeCount || 0,
+          urgentCount: alarmData.urgentCount || 0
+        }
       } catch (error) {
         console.error('加载KPI数据失败:', error)
       }
     },
 
+    // 加载设备状态分布
     async loadDeviceStatus() {
       try {
         const { data } = await listDeviceStatus(this.buildQueryParams())
-        this.deviceStatusChart = {
-          series: [
-            {
-              type: 'pie',
-              radius: ['50%', '70%'],
-              data: [
-                { value: data.onlineCount || 0, name: '在线' },
-                { value: data.offlineCount || 0, name: '离线' }
-              ]
-            }
-          ]
-        }
+        this.renderDeviceStatusChart(data || {})
       } catch (error) {
         console.error('加载设备状态失败:', error)
       }
     },
 
+    // 加载告警趋势 - 使用新接口
     async loadAlarmTrend() {
       try {
-        let xAxis = []
-        let data = []
         const params = this.buildQueryParams()
+        const res = await countAlarmByTrend(params, this.trendType)
+        const trendData = res.data || []
+        this.renderAlarmTrendChart(trendData)
+      } catch (error) {
+        console.error('加载告警趋势失败:', error)
+      }
+    },
 
-        if (this.dateType.val === 'day') {
-          const res = await fetchAlarmIndexDay(params)
-          data = res.data || []
-          xAxis = DateTool.getTime(24)
-        } else if (this.dateType.val === 'month') {
-          const res = await fetchAlarmIndexMonth(params)
-          data = res.data || []
-          xAxis = DateTool.getDayOfRange(dayjs().subtract(1, 'month'), dayjs(), DateTool.DateFormat.YYYY_MM_DD)
-        } else {
-          const res = await fetchAlarmIndexYear(params)
-          data = res.data || []
-          xAxis = DateTool.getMonthsOfYearAgo()
-        }
+    // 加载实时告警 - 使用新接口
+    async loadRealTimeAlarm() {
+      this.tableLoading = true
+      try {
+        // 使用活动告警接口
+        const res = await listActiveAlarm(this.areaCode)
+        // 只取前6条显示
+        this.realTimeAlarmList = (res.data || []).slice(0, 6)
+      } catch (error) {
+        console.error('加载实时告警失败:', error)
+        this.realTimeAlarmList = []
+      } finally {
+        this.tableLoading = false
+      }
+    },
 
-        const series = this.transformAlarmTrendData(data, xAxis)
-        this.alarmTrendChart = {
-          unit: '',
-          xAxis: { type: 'category', data: xAxis },
-          series
+    // 加载告警级别分布 - 使用新接口
+    async loadAlarmLevelDistribution() {
+      try {
+        const params = this.buildQueryParams()
+        const res = await countAlarmByLevel(params)
+        const levelData = res.data || []
+
+        this.hasAlarmLevelData = levelData.length > 0
+        if (this.hasAlarmLevelData) {
+          this.$nextTick(() => {
+            this.renderAlarmLevelChart(levelData)
+          })
         }
       } catch (error) {
-        console.error('加载告警趋势失败:', error)
+        console.error('加载告警级别分布失败:', error)
+        this.hasAlarmLevelData = false
       }
     },
 
-    transformAlarmTrendData(data, xAxis) {
-      const dayGroup = _.groupBy(data, 'alarmType')
-      const series = []
-      const alarmTypeMap = {
-        1: '一般告警', 2: '重要告警', 3: '紧急告警', 4: '恢复告警', 5: '诊断告警', 6: '其他告警'
+    // ==================== 图表渲染 ====================
+    initCharts() {
+      this.charts.deviceStatus = echarts.init(this.$refs.deviceStatusChart)
+      this.charts.alarmTrend = echarts.init(this.$refs.alarmTrendChart)
+      if (this.$refs.alarmLevelChart) {
+        this.charts.alarmLevel = echarts.init(this.$refs.alarmLevelChart)
       }
+    },
 
-      Object.keys(dayGroup).forEach((alarmType) => {
-        let ds = {}
-        dayGroup[alarmType].forEach((item) => {
-          ds[item.dateIndex] = item.cnt
-        })
-        let seriesData = []
-        xAxis.forEach((item) => seriesData.push(ds[item] || 0))
-        series.push({
-          name: alarmTypeMap[alarmType] || '未知',
+    disposeCharts() {
+      Object.values(this.charts).forEach(chart => {
+        chart && chart.dispose()
+      })
+    },
+
+    resizeCharts() {
+      Object.values(this.charts).forEach(chart => {
+        chart && chart.resize()
+      })
+    },
+
+    renderDeviceStatusChart(data) {
+      const option = {
+        tooltip: {
+          trigger: 'item',
+          formatter: '{b}: {c} ({d}%)'
+        },
+        legend: {
+          orient: 'vertical',
+          right: 20,
+          top: 'center'
+        },
+        color: ['#67C23A', '#F56C6C'],
+        series: [{
+          type: 'pie',
+          radius: ['45%', '70%'],
+          center: ['40%', '50%'],
+          avoidLabelOverlap: true,
+          itemStyle: {
+            borderRadius: 8,
+            borderColor: '#fff',
+            borderWidth: 2
+          },
+          label: {
+            show: true,
+            formatter: '{b}\n{c}台'
+          },
+          emphasis: {
+            label: {
+              show: true,
+              fontSize: 14,
+              fontWeight: 'bold'
+            }
+          },
+          data: [
+            { value: data.onlineCount || 0, name: '在线' },
+            { value: data.offlineCount || 0, name: '离线' }
+          ]
+        }]
+      }
+      this.charts.deviceStatus && this.charts.deviceStatus.setOption(option, true)
+    },
+
+    renderAlarmTrendChart(trendData) {
+      const xData = trendData.map(item => item.timeIndex)
+      const yData = trendData.map(item => item.cnt)
+
+      const option = {
+        tooltip: {
+          trigger: 'axis',
+          axisPointer: { type: 'cross' }
+        },
+        grid: {
+          left: '3%',
+          right: '4%',
+          bottom: '3%',
+          top: '10%',
+          containLabel: true
+        },
+        xAxis: {
+          type: 'category',
+          boundaryGap: false,
+          data: xData,
+          axisLine: { lineStyle: { color: '#dcdfe6' } },
+          axisLabel: { color: '#606266', fontSize: 10 }
+        },
+        yAxis: {
+          type: 'value',
+          axisLine: { show: false },
+          axisTick: { show: false },
+          splitLine: { lineStyle: { color: '#ebeef5', type: 'dashed' } },
+          axisLabel: { color: '#909399' }
+        },
+        series: [{
+          name: '告警数',
           type: 'line',
           smooth: true,
-          data: seriesData
-        })
+          symbol: 'circle',
+          symbolSize: 6,
+          itemStyle: { color: '#f56c6c' },
+          lineStyle: { width: 2 },
+          areaStyle: {
+            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+              { offset: 0, color: 'rgba(245, 108, 108, 0.4)' },
+              { offset: 1, color: 'rgba(245, 108, 108, 0.05)' }
+            ])
+          },
+          data: yData
+        }]
+      }
+      this.charts.alarmTrend && this.charts.alarmTrend.setOption(option, true)
+    },
+
+    renderAlarmLevelChart(levelData) {
+      if (!this.charts.alarmLevel && this.$refs.alarmLevelChart) {
+        this.charts.alarmLevel = echarts.init(this.$refs.alarmLevelChart)
+      }
+
+      const chartData = levelData.map(item => {
+        const levelInfo = ALARM_LEVEL_MAP[item.alarmLevel]
+        return {
+          value: item.cnt,
+          name: levelInfo?.name || `级别${item.alarmLevel}`,
+          itemStyle: { color: levelInfo?.color || '#909399' }
+        }
       })
-      return series
+
+      const option = {
+        tooltip: {
+          trigger: 'item',
+          formatter: '{b}: {c} ({d}%)'
+        },
+        legend: {
+          orient: 'vertical',
+          right: 20,
+          top: 'center'
+        },
+        series: [{
+          type: 'pie',
+          radius: ['0%', '65%'],
+          center: ['40%', '50%'],
+          roseType: 'radius',
+          itemStyle: {
+            borderRadius: 6,
+            borderColor: '#fff',
+            borderWidth: 2
+          },
+          label: {
+            show: true,
+            formatter: '{b}\n{c}条'
+          },
+          data: chartData
+        }]
+      }
+      this.charts.alarmLevel && this.charts.alarmLevel.setOption(option, true)
     },
 
-    async loadRealTimeAlarm() {
+    // ==================== 告警操作 ====================
+    handleAlarmAction(command, row) {
+      this.currentAlarm = row
+
+      switch (command) {
+        case 'confirm':
+          this.doConfirmAlarm(row)
+          break
+        case 'handle':
+          this.handleForm.type = 'handle'
+          this.handleDialogTitle = '处置告警'
+          this.handleForm.content = ''
+          this.handleDialogVisible = true
+          break
+        case 'resolve':
+          this.handleForm.type = 'resolve'
+          this.handleDialogTitle = '解决告警'
+          this.handleForm.content = ''
+          this.handleDialogVisible = true
+          break
+        case 'close':
+          this.doCloseAlarm(row)
+          break
+      }
+    },
+
+    // 确认告警
+    async doConfirmAlarm(row) {
       try {
-        // 【关键修改】将 pageSize 改为 6,以适配 430px 的高度
-        const { rows } = await listAlarmInfo(
-          this.buildQueryParams({ pageNum: 1, pageSize: 6, alarmStateList: [0, 1] })
-        )
-        this.realTimeAlarmData = rows || []
+        await confirmAlarm(row.alarmId, '快速确认')
+        this.$message.success('告警已确认')
+        this.loadRealTimeAlarm()
+        this.loadKpiData()
       } catch (error) {
-        console.error('加载实时告警失败:', error)
+        this.$message.error('确认失败')
       }
     },
 
-    async loadAlarmTypeDistribution() {
+    // 关闭告警
+    async doCloseAlarm(row) {
       try {
-        const endTime = dayjs().format('YYYY-MM-DD HH:mm:ss')
-        const startTime = dayjs().startOf('year').format('YYYY-MM-DD HH:mm:ss')
-        const params = this.buildQueryParams({ startRecTime: startTime, endRecTime: endTime })
-        const { data } = await fetchAlarmIndex(params)
-
-        const alarmTypeMap = {
-          1: '一般告警', 2: '重要告警', 3: '紧急告警', 4: '恢复告警', 5: '诊断告警', 6: '其他告警'
-        }
-        const chartData = (data || []).map((item) => ({
-          value: item.cnt,
-          name: alarmTypeMap[item.alarmType] || '未知'
-        }))
-
-        this.alarmTypeChart = {
-          series: [{ type: 'pie', radius: ['0%', '70%'], data: chartData }]
-        }
+        await this.$confirm('确定关闭此告警吗?', '提示', {
+          type: 'warning'
+        })
+        await closeAlarm(row.alarmId, '快速关闭')
+        this.$message.success('告警已关闭')
+        this.loadRealTimeAlarm()
+        this.loadKpiData()
       } catch (error) {
-        console.error('加载告警类型分布失败:', error)
+        if (error !== 'cancel') {
+          this.$message.error('关闭失败')
+        }
       }
     },
 
-    async handleAlarmStateChange(command, row) {
+    // 提交处置
+    async submitAlarmHandle() {
+      if (!this.handleForm.content) {
+        this.$message.warning('请输入处置说明')
+        return
+      }
+
+      this.handleSubmitting = true
       try {
-        await updateAlarmInfo({ id: row.id, alarmState: command })
-        this.$message.success('告警状态更新成功')
-        await this.loadRealTimeAlarm()
-        await this.loadKpiData()
+        if (this.handleForm.type === 'handle') {
+          await handleAlarm(this.currentAlarm.alarmId, this.handleForm.content, '处置中')
+          this.$message.success('已开始处置')
+        } else if (this.handleForm.type === 'resolve') {
+          await resolveAlarm(this.currentAlarm.alarmId, this.handleForm.content)
+          this.$message.success('已标记解决')
+        }
+        this.handleDialogVisible = false
+        this.loadRealTimeAlarm()
+        this.loadKpiData()
       } catch (error) {
-        this.$message.error('告警状态更新失败')
+        this.$message.error('操作失败')
+      } finally {
+        this.handleSubmitting = false
       }
     },
 
-    getAlarmTypeName(type) {
-      const map = { 1: '一般', 2: '重要', 3: '紧急', 4: '恢复', 5: '诊断', 6: '其他' }
-      return map[type] || '未知'
-    },
-    getAlarmTypeTag(type) {
-      const map = { 1: 'info', 2: 'warning', 3: 'danger', 4: 'success', 5: '', 6: 'info' }
-      return map[type] || 'info'
-    },
-    getAlarmStateName(state) {
-      const map = { 0: '新增', 1: '处理中', 2: '已处置', 3: '已消散' }
-      return map[state] || '未知'
+    // ==================== 自动刷新 ====================
+    startAutoRefresh() {
+      this.refreshTimer = setInterval(() => {
+        this.loadRealTimeAlarm()
+        this.loadKpiData()
+      }, 30000)
     },
-    getAlarmStateTag(state) {
-      const map = { 0: 'danger', 1: 'warning', 2: 'success', 3: 'info' }
-      return map[state] || 'info'
+
+    stopAutoRefresh() {
+      if (this.refreshTimer) {
+        clearInterval(this.refreshTimer)
+        this.refreshTimer = null
+      }
     }
   }
 }
 </script>
 
 <style scoped lang="scss">
-.device-alarm-content {
-  background: #f5f7fa;
+.device-alarm-dashboard {
   padding: 20px;
+  background: linear-gradient(135deg, #f5f7fa 0%, #e8ecf1 100%);
   min-height: calc(100vh - 84px);
+}
 
-  // ---------------------------------------------------------
-  // 核心样式:统一卡片容器
-  // ---------------------------------------------------------
-  .dashboard-card {
-    height: 430px; // 【关键】统一四个板块的高度
-    background: #fff;
-    border-radius: 8px;
-    padding: 15px;
-    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
-    box-sizing: border-box;
-    display: flex;
-    flex-direction: column;
-    overflow: hidden;
+// 左侧树面板
+.tree-panel {
+  background: #fff;
+  border-radius: 12px;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+  overflow: hidden;
 
-    // 空状态样式
-    &.empty-state {
-      .card-title {
-        font-size: 16px;
-        font-weight: bold;
-        color: #333;
-        border-left: 4px solid #409EFF;
-        padding-left: 10px;
-        line-height: 1;
-        margin-bottom: 20px;
-      }
+  .tree-search {
+    padding: 16px;
+    border-bottom: 1px solid #f0f2f5;
 
-      ::v-deep .el-empty {
-        flex: 1;
-        display: flex;
-        flex-direction: column;
-        justify-content: center;
-      }
-    }
+    ::v-deep .el-input__inner {
+      border-radius: 20px;
+      border: 1px solid #e4e7ed;
 
-    // 针对表格卡片的特殊处理
-    &.table-card {
-      // 深度选择器,强行让内部表格适应高度
-      ::v-deep .block-table-container {
-        height: 100%;
-        display: flex;
-        flex-direction: column;
-      }
-
-      ::v-deep .el-table {
-        flex: 1; // 撑满剩余高度
-        height: 0; // 关键:Flex布局下允许自适应缩放
+      &:focus {
+        border-color: #409eff;
       }
     }
   }
 
-  // 左侧树容器
-  .head-container {
-    background: #fff;
-    padding: 15px;
-    border-radius: 8px;
-    margin-bottom: 15px;
-    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+  .tree-content {
+    max-height: calc(100vh - 200px);
+    overflow-y: auto;
+    padding: 8px;
 
-    &.tree-container {
-      max-height: calc(100vh - 280px);
-      overflow-y: auto;
+    &::-webkit-scrollbar {
+      width: 6px;
+    }
 
-      &::-webkit-scrollbar {
-        width: 6px;
-      }
+    &::-webkit-scrollbar-thumb {
+      background: #dcdfe6;
+      border-radius: 3px;
+    }
 
-      &::-webkit-scrollbar-track {
-        background: #f1f1f1;
-        border-radius: 3px;
-      }
+    ::v-deep .el-tree {
+      background: transparent;
 
-      &::-webkit-scrollbar-thumb {
-        background: #c1c1c1;
-        border-radius: 3px;
-      }
+      .el-tree-node__content {
+        height: 44px;
+        border-radius: 8px;
+        margin: 2px 0;
 
-      &::-webkit-scrollbar-thumb:hover {
-        background: #a8a8a8;
+        &:hover {
+          background: #f5f7fa;
+        }
       }
 
-      ::v-deep .el-tree {
-        background: transparent;
-
-        .el-tree-node__content {
-          height: 40px;
-          padding: 0 8px;
-          transition: all 0.3s;
-
-          &:hover {
-            background-color: #f5f7fa;
-          }
-        }
+      .el-tree-node.is-current > .el-tree-node__content {
+        background: linear-gradient(135deg, #ecf5ff 0%, #e6f1fc 100%);
+        color: #409eff;
+      }
 
-        .el-tree-node.is-current > .el-tree-node__content {
-          background-color: #ecf5ff;
-          color: #409eff;
-        }
+      .custom-tree-node {
+        flex: 1;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        padding-right: 8px;
 
-        .custom-tree-node {
-          flex: 1;
+        .tree-label {
           display: flex;
           align-items: center;
-          justify-content: space-between;
-          font-size: 14px;
-
-          .tree-label {
-            display: flex;
-            align-items: center;
-
-            .tree-icon {
-              margin-right: 8px;
-              font-size: 16px;
-              color: #909399;
-              transition: color 0.3s;
-            }
+
+          .tree-icon {
+            margin-right: 8px;
+            font-size: 16px;
+            color: #909399;
           }
         }
 
-        .el-tree-node.is-current .tree-icon {
-          color: #409eff;
+        .area-badge {
+          ::v-deep .el-badge__content {
+            background: #f56c6c;
+          }
         }
       }
     }
   }
+}
 
-  // KPI卡片样式
-  .kpi-cards {
-    margin-bottom: 20px;
-
-    .kpi-card {
-      background: #fff;
-      border-radius: 8px;
-      padding: 20px;
-      display: flex;
-      align-items: center;
-      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
-      transition: all 0.3s;
-      position: relative;
-      overflow: hidden;
-
-      &:hover {
-        transform: translateY(-4px);
-        box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.15);
-      }
+// KPI 卡片
+.kpi-row {
+  display: grid;
+  grid-template-columns: repeat(4, 1fr);
+  gap: 16px;
+  margin-bottom: 20px;
+}
 
-      .kpi-icon {
-        width: 60px;
-        height: 60px;
-        border-radius: 50%;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        font-size: 28px;
-        margin-right: 15px;
-        flex-shrink: 0;
-      }
+.kpi-card {
+  background: #fff;
+  border-radius: 12px;
+  padding: 20px;
+  display: flex;
+  align-items: center;
+  position: relative;
+  overflow: hidden;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.06);
+  cursor: pointer;
+  transition: all 0.3s ease;
+
+  &:hover {
+    transform: translateY(-4px);
+    box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
+  }
 
-      .kpi-content {
-        flex: 1;
+  &::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 4px;
+    height: 100%;
+  }
 
-        .kpi-value {
-          font-size: 28px;
-          font-weight: bold;
-          line-height: 1.2;
-          margin-bottom: 5px;
-        }
+  &.total::before { background: linear-gradient(180deg, #667eea 0%, #764ba2 100%); }
+  &.online::before { background: linear-gradient(180deg, #67c23a 0%, #95d475 100%); }
+  &.offline::before { background: linear-gradient(180deg, #f56c6c 0%, #ff7875 100%); }
+  &.alarm::before { background: linear-gradient(180deg, #e6a23c 0%, #f5af19 100%); }
 
-        .kpi-label {
-          font-size: 14px;
-          color: #666;
-        }
+  .kpi-icon {
+    width: 56px;
+    height: 56px;
+    border-radius: 12px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-right: 16px;
+
+    i { font-size: 26px; color: #fff; }
+  }
+
+  &.total .kpi-icon { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
+  &.online .kpi-icon { background: linear-gradient(135deg, #67c23a 0%, #95d475 100%); }
+  &.offline .kpi-icon { background: linear-gradient(135deg, #f56c6c 0%, #ff7875 100%); }
+  &.alarm .kpi-icon { background: linear-gradient(135deg, #e6a23c 0%, #f5af19 100%); }
+
+  .kpi-info {
+    flex: 1;
+
+    .kpi-value {
+      font-size: 28px;
+      font-weight: 700;
+      color: #303133;
+      line-height: 1.2;
+    }
+
+    .kpi-label {
+      font-size: 13px;
+      color: #909399;
+      margin-top: 4px;
+    }
+  }
+
+  .kpi-rate {
+    position: absolute;
+    top: 12px;
+    right: 12px;
+    font-size: 12px;
+    color: #67c23a;
+    background: rgba(103, 194, 58, 0.1);
+    padding: 2px 8px;
+    border-radius: 10px;
+  }
+
+  .kpi-pulse {
+    position: absolute;
+    top: 12px;
+    right: 12px;
+    width: 10px;
+    height: 10px;
+    background: #f56c6c;
+    border-radius: 50%;
+    animation: pulse 1.5s infinite;
+  }
+
+  .kpi-urgent {
+    position: absolute;
+    top: 12px;
+    right: 12px;
+    font-size: 11px;
+    color: #f56c6c;
+    background: rgba(245, 108, 108, 0.1);
+    padding: 2px 8px;
+    border-radius: 10px;
+
+    i { margin-right: 2px; }
+  }
+
+  .kpi-decoration {
+    position: absolute;
+    right: -20px;
+    bottom: -20px;
+    width: 80px;
+    height: 80px;
+    border-radius: 50%;
+    opacity: 0.1;
+  }
+
+  &.total .kpi-decoration { background: #667eea; }
+}
+
+@keyframes pulse {
+  0%, 100% { opacity: 1; transform: scale(1); }
+  50% { opacity: 0.5; transform: scale(1.2); }
+}
+
+// 图表行
+.chart-row {
+  margin-bottom: 20px;
+}
+
+.chart-card {
+  background: #fff;
+  border-radius: 12px;
+  padding: 16px;
+  height: 380px;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.06);
+  display: flex;
+  flex-direction: column;
+
+  &.table-card {
+    height: 420px;
+  }
+
+  .card-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 12px;
+    padding-bottom: 12px;
+    border-bottom: 1px solid #f0f2f5;
+
+    .card-title {
+      font-size: 15px;
+      font-weight: 600;
+      color: #303133;
+
+      i {
+        margin-right: 6px;
+        color: #409eff;
       }
+    }
 
-      &.kpi-primary {
-        .kpi-icon {
-          background: rgba(64, 158, 255, 0.1);
-          color: #409eff;
-        }
+    .card-subtitle {
+      font-size: 12px;
+      color: #909399;
+    }
 
-        .kpi-value {
-          color: #409eff;
-        }
+    .table-count {
+      font-size: 12px;
+      color: #909399;
+    }
+  }
+
+  .chart-body {
+    flex: 1;
+    min-height: 0;
+  }
+
+  .table-body {
+    flex: 1;
+    overflow: hidden;
+
+    ::v-deep .el-table {
+      height: 100% !important;
+
+      .urgent-row {
+        background: rgba(245, 108, 108, 0.08) !important;
       }
 
-      &.kpi-success {
-        .kpi-icon {
-          background: rgba(103, 194, 58, 0.1);
-          color: #67c23a;
-        }
+      .important-row {
+        background: rgba(230, 162, 60, 0.08) !important;
+      }
 
-        .kpi-value {
-          color: #67c23a;
-        }
+      .device-name {
+        font-weight: 500;
+        color: #303133;
       }
 
-      &.kpi-danger {
-        .kpi-icon {
-          background: rgba(245, 108, 108, 0.1);
-          color: #f56c6c;
-        }
+      .alarm-msg {
+        color: #e6a23c;
+      }
 
-        .kpi-value {
-          color: #f56c6c;
-        }
+      .alarm-time {
+        font-size: 12px;
+        color: #909399;
       }
 
-      &.kpi-warning {
-        .kpi-icon {
-          background: rgba(230, 162, 60, 0.1);
-          color: #e6a23c;
-        }
+      .status-dropdown {
+        cursor: pointer;
 
-        .kpi-value {
-          color: #e6a23c;
-        }
+        i { margin-left: 2px; }
       }
     }
   }
 
-  .el-dropdown-link {
-    cursor: pointer;
+  .empty-chart {
+    flex: 1;
     display: flex;
     align-items: center;
     justify-content: center;
   }
 }
+
+// 闪烁效果
+.blink {
+  animation: blink 1s infinite;
+}
+
+@keyframes blink {
+  0%, 100% { opacity: 1; }
+  50% { opacity: 0.5; }
+}
+
+// 响应式
+@media (max-width: 1400px) {
+  .kpi-row {
+    grid-template-columns: repeat(2, 1fr);
+  }
+}
+
+@media (max-width: 768px) {
+  .device-alarm-dashboard {
+    padding: 10px;
+  }
+
+  .kpi-row {
+    grid-template-columns: 1fr;
+  }
+}
 </style>

+ 142 - 64
ems-ui-cloud/src/views/largeScreen/device/left.vue

@@ -8,7 +8,7 @@
         <div class="dev-ttl">
           <div class="txt-sml txt-clr">设备总数</div>
           <div class="num">
-            <span v-for="value, index in countFormat(deviceTotal, 6)" :key="index">{{ value }}</span>
+            <span v-for="(value, index) in countFormat(deviceTotal, 6)" :key="index">{{ value }}</span>
           </div>
         </div>
       </div>
@@ -48,10 +48,11 @@
 import CusModule from '../components/CusModule.vue';
 import BaseChart from '@/components/BaseChart/index.vue'
 import Pie3d from './pie3d.vue'
-import {mapState} from 'vuex';
-import {listDeviceStatus, listDeviceType} from "@/api/device/device";
-import {fetchCntHandled} from "@/api/alarm/alarm-info";
-import {DateTool} from "@/utils/DateTool";
+import { mapState } from 'vuex';
+import { listDeviceStatus, listDeviceType } from "@/api/device/device";
+// 使用新的告警接口
+import { getAlarmStatsOverview, getAlarmHandleRate } from "@/api/alarm/alarm";
+import { DateTool } from "@/utils/DateTool";
 
 export default {
   name: 'DeviceLeft',
@@ -62,36 +63,31 @@ export default {
       equipList: [
         {
           name: "在线设备",
-          value: 530,
-          unit: "次",
+          value: 0,
+          pct: 0,
+          unit: "台",
           image: require("@/assets/images/device/l2-item1_bg.png"),
         },
-        // {
-        //   name: "故障设备",
-        //   value: 12,
-        //   pct: 30,
-        //   unit: "次",
-        //   image: require("@/assets/images/device/l2-item2_bg.png"),
-        // },
         {
           name: "离线设备",
-          value: 6,
-          unit: "次",
+          value: 0,
+          pct: 0,
+          unit: "台",
           image: require("@/assets/images/device/l2-item3_bg.png"),
         },
       ],
       deal: [
         {
-          name: '今日警',
-          percent: 70,
-          value: 10,
-          dealValue: 7
+          name: '今日警',
+          percent: 0,
+          value: 0,
+          dealValue: 0
         },
         {
-          name: '本月报警',
-          percent: 80,
-          value: 300,
-          dealValue: 240
+          name: '活动告警',
+          percent: 0,
+          value: 0,
+          dealValue: 0
         },
       ]
     };
@@ -120,7 +116,7 @@ export default {
     this.timer && clearInterval(this.timer)
   },
   methods: {
-    //字符串转数组 总数
+    // 字符串转数组 总数
     countFormat(val, total = 0) {
       let str = String(val);
       let numArr = str.split("");
@@ -139,53 +135,136 @@ export default {
         return arr;
       }
     },
+
     getDeviceData() {
       this.cntListDeviceType()
       this.cntListDeviceStatus()
-      this.getCntHandled()
+      this.getAlarmHandleStats()
     },
+
     async cntListDeviceType() {
-      const {data} = await listDeviceType({
-        areaCode: this.areaType
-      })
-      this.pieData = data.map(item => ({
-        name: item.typeName || '其他',
-        value: item.total
-      }))
+      try {
+        const { data } = await listDeviceType({
+          areaCode: this.areaType
+        })
+        this.pieData = (data || []).map(item => ({
+          name: item.typeName || '其他',
+          value: item.total
+        }))
+      } catch (error) {
+        console.error('获取设备类型统计失败:', error)
+      }
     },
+
     async cntListDeviceStatus() {
-      const {data} = await listDeviceStatus({
-        areaCode: this.areaType
-      })
-      const {
-        total,
-        onlineCount
-      } = data
-      const [online, offline] = this.equipList
-      this.deviceTotal = total
-      const onlinePct = (onlineCount / total * 100).toFixed(1)
-      online.pct = onlinePct
-      offline.pct = (100 - onlinePct).toFixed(1)
+      try {
+        const { data } = await listDeviceStatus({
+          areaCode: this.areaType
+        })
+        const { total = 0, onlineCount = 0, offlineCount = 0 } = data || {}
+
+        this.deviceTotal = total
+
+        const [online, offline] = this.equipList
+        online.value = onlineCount
+        offline.value = offlineCount
+
+        if (total > 0) {
+          const onlinePct = (onlineCount / total * 100).toFixed(1)
+          online.pct = onlinePct
+          offline.pct = (100 - parseFloat(onlinePct)).toFixed(1)
+        } else {
+          online.pct = 0
+          offline.pct = 0
+        }
+      } catch (error) {
+        console.error('获取设备状态统计失败:', error)
+      }
     },
-    async getCntHandled() {
-      const {data: thisday = {}} = await fetchCntHandled({
-        areaCode: this.areaType,
-        startRecTime: DateTool.now()
-      })
-
-      const {data: thisMonth = {}} = await fetchCntHandled({
-        areaCode: this.areaType,
-        startRecTime: DateTool.thisMonth()
-      })
-      const [thisDayData, thisMonthData] = this.deal
-      thisMonthData.value = thisMonth.cnt || 0
-      thisMonthData.dealValue = thisMonth.handledCnt || 0
-      thisMonthData.percent = thisMonthData.value && (thisMonthData.dealValue / thisMonthData.value * 100).toFixed(2)
-      thisDayData.value = thisday.cnt || 0
-      thisDayData.dealValue = thisday.handledCnt || 0
-      thisDayData.percent = thisDayData.value && (thisDayData.dealValue / thisDayData.value * 100).toFixed(2)
-      this.deal = [thisDayData, thisMonthData]
+
+    /**
+     * 获取告警处置统计 - 使用新接口
+     *
+     * 新接口 getAlarmStatsOverview 返回:
+     * - totalCount: 告警总数
+     * - activeCount: 活动告警数
+     * - urgentCount: 紧急告警数
+     * - handleRate: { rate: 处理率 }
+     * - todayCount: 今日新增
+     *
+     * 新接口 getAlarmHandleRate 返回:
+     * - total: 总数
+     * - handled: 已处理数
+     * - pending: 待处理数
+     * - rate: 处理率
+     */
+    async getAlarmHandleStats() {
+      try {
+        // 构建基础查询参数
+        const baseParams = {
+          areaCode: this.areaType && this.areaType !== '-1' ? this.areaType : undefined
+        }
+
+        // 获取今日日期范围
+        const today = new Date()
+        const todayStr = this.formatDate(today)
+        const todayParams = {
+          ...baseParams,
+          startRecTime: todayStr + ' 00:00:00',
+          endRecTime: todayStr + ' 23:59:59'
+        }
+
+        // 并行请求:全部统计 + 今日统计
+        const [overallRes, todayRes] = await Promise.all([
+          getAlarmHandleRate(baseParams),      // 全部告警处理率
+          getAlarmHandleRate(todayParams)       // 今日告警处理率
+        ])
+
+        const overallData = overallRes.data || {}
+        const todayData = todayRes.data || {}
+
+        const [todayItem, activeItem] = this.deal
+
+        // 今日告警统计
+        const todayTotal = todayData.total || 0
+        const todayHandled = todayData.handled || 0
+        const todayRate = todayData.rate || 0
+
+        todayItem.value = todayTotal
+        todayItem.dealValue = todayHandled
+        todayItem.percent = todayRate
+
+        // 活动告警统计(使用全部数据)
+        const overallTotal = overallData.total || 0
+        const overallHandled = overallData.handled || 0
+        const overallPending = overallData.pending || 0
+        const overallRate = overallData.rate || 0
+
+        activeItem.name = '活动告警'
+        activeItem.value = overallPending  // 活动告警数 = pending
+        activeItem.dealValue = overallHandled
+        activeItem.percent = overallRate
+
+        this.deal = [todayItem, activeItem]
+      } catch (error) {
+        console.error('获取告警处置统计失败:', error)
+        // 出错时重置数据
+        this.deal = [
+          { name: '今日告警', percent: 0, value: 0, dealValue: 0 },
+          { name: '活动告警', percent: 0, value: 0, dealValue: 0 }
+        ]
+      }
     },
+
+    /**
+     * 格式化日期为 yyyy-MM-dd
+     */
+    formatDate(date) {
+      const year = date.getFullYear()
+      const month = String(date.getMonth() + 1).padStart(2, '0')
+      const day = String(date.getDate()).padStart(2, '0')
+      return `${year}-${month}-${day}`
+    }
   }
 }
 </script>
@@ -296,7 +375,6 @@ export default {
         color: #07e3f9;
         font-size: 18px;
       }
-
     }
   }
 }

+ 327 - 213
ems-ui-cloud/src/views/largeScreen/device/right.vue

@@ -1,68 +1,90 @@
 <template>
   <div>
     <CusModule title="告警信息">
+      <!-- 3D饼图:告警级别分布 -->
       <BaseChart height="200px" width="100%" :option="pieOptions"/>
+
+      <!-- 实时告警列表 -->
       <div class="custom-table">
         <el-table :data="tableData" class="customer-table" style="width: 100%" height="650">
           <el-table-column type="index" label="序号" width="60" align="center"/>
-          <el-table-column prop="type" label="报警类型" align="center">
+          <el-table-column prop="alarmLevel" label="告警级别" width="90" align="center">
             <template #default="{ row }">
-              <dict-tag :options="dict.type.alarm_type" :value="row.alarmType"/>
+              <span :class="['level-tag', 'level-' + row.alarmLevel]">
+                {{ getAlarmLevelName(row.alarmLevel) }}
+              </span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="alarmMsg" label="告警内容" align="center" show-overflow-tooltip>
+            <template #default="{ row }">
+              <span class="alarm-content">{{ row.alarmMsg || formatAlarmContent(row) }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="alarmTime" label="告警时间" width="150" align="center">
+            <template #default="{ row }">
+              <span class="alarm-time">{{ formatTime(row.alarmTime) }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="alarmStatus" label="状态" width="80" align="center">
+            <template #default="{ row }">
+              <span :class="['status-tag', 'status-' + row.alarmStatus]">
+                {{ getAlarmStatusName(row.alarmStatus) }}
+              </span>
             </template>
           </el-table-column>
-          <el-table-column prop="alarmMsg" label="报警内容" align="center"/>
-          <el-table-column prop="alarmTime" label="告警事件" align="center"/>
         </el-table>
-        <el-pagination :current-page="page.pageIndex" :page-size="page.pageSize" :small="'small'" :background="true"
-                       layout="total,  prev, pager, next, jumper" :total="page.total" @size-change="handleSizeChange"
-                       @current-change="handleCurrentChange"/>
+        <el-pagination
+          :current-page="page.pageIndex"
+          :page-size="page.pageSize"
+          :small="'small'"
+          :background="true"
+          layout="total, prev, pager, next, jumper"
+          :total="page.total"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+        />
       </div>
     </CusModule>
   </div>
 </template>
+
 <script>
-import CusModule from '../components/CusModule.vue';
+import CusModule from '../components/CusModule.vue'
 import BaseChart from '@/components/BaseChart/index.vue'
-import {mapState} from 'vuex';
-import {fetchCntDateAlarmType, listAlarmInfo} from "@/api/alarm/alarm-info";
-import {ALARM_STATE} from "@/enums/alarmEnum";
-import {ApiCode} from "@/api/apiEmums";
+import { mapState } from 'vuex'
+import { listAlarm, countAlarmByLevel } from '@/api/alarm/alarm'
+import { parseTime } from '@/utils/ruoyi'
+
+// 告警级别配置 - 适配新模型
+const ALARM_LEVEL_MAP = {
+  1: { name: '一般', color: '#1990FF' },
+  2: { name: '重要', color: '#FFBD1F' },
+  3: { name: '紧急', color: '#FF4949' }
+}
 
+// 告警状态配置 - 适配新模型
+const ALARM_STATUS_MAP = {
+  0: { name: '活动', color: '#FF4949' },
+  1: { name: '已确认', color: '#FFBD1F' },
+  2: { name: '处置中', color: '#1990FF' },
+  3: { name: '已解决', color: '#2fe30f' },
+  4: { name: '已关闭', color: '#909399' },
+  5: { name: '已恢复', color: '#2fe30f' }
+}
+
+// 3D饼图颜色配置 - 按告警级别
 const colorDic = {
-  3: {
-    itemStyle: {
-      color: '#FF4949'
-    },
-  },
-  2: {
-    itemStyle: {
-      color: '#FFBD1F'
-    },
-  },
-  1: {
-    itemStyle: {
-      color: '#1990FF'
-    },
-  },
-  4: {
-    itemStyle: {
-      color: '#2fe30f'
-    },
-  },
-  5: {
-    itemStyle: {
-      color: '#737386'
-    },
-  },
-  6: {
-    itemStyle: {
-      color: 'rgba(232,244,255,0.58)'
-    },
-  }
+  1: { itemStyle: { color: '#1990FF' } },  // 一般 - 蓝色
+  2: { itemStyle: { color: '#FFBD1F' } },  // 重要 - 橙色
+  3: { itemStyle: { color: '#FF4949' } }   // 紧急 - 红色
 }
+
 export default {
   name: 'DeviceRight',
-  dicts: ['alarm_type'],
+  components: {
+    CusModule,
+    BaseChart
+  },
   data() {
     return {
       pieData: [],
@@ -74,273 +96,288 @@ export default {
       }
     }
   },
-  components: {
-    CusModule,
-    BaseChart,
-  },
   computed: {
     ...mapState('userState', ['areaType']),
     pieOptions() {
-      return this.getPie3D(this.pieData, 0)// 可做为调整内环大小 0为实心圆饼图,大于0 小于1 为圆环
+      return this.getPie3D(this.pieData, 0)
     }
   },
   watch: {
     areaType() {
-      this.getPieData()
+      this.loadData()
     }
   },
   mounted() {
-    this.getPieData()
+    this.loadData()
     this.timer && clearInterval(this.timer)
     this.timer = setInterval(() => {
-      this.getPieData()
-    }, 60000 * 3)
+      this.loadData()
+    }, 60000 * 3) // 3分钟刷新
   },
   beforeDestroy() {
     this.timer && clearInterval(this.timer)
   },
   methods: {
+    // ==================== 基础方法 ====================
+    getAlarmLevelName(level) {
+      return ALARM_LEVEL_MAP[level]?.name || '未知'
+    },
+
+    getAlarmStatusName(status) {
+      return ALARM_STATUS_MAP[status]?.name || '未知'
+    },
+
+    formatTime(time) {
+      return parseTime(time, '{m}-{d} {h}:{i}')
+    },
+
+    formatAlarmContent(row) {
+      const attrName = row.attrName || row.attrKey || '属性'
+      return `${row.targetName || '设备'} ${attrName}异常`
+    },
+
+    // ==================== 分页事件 ====================
     handleSizeChange(size) {
       this.page.pageIndex = 1
       this.page.pageSize = size
       this.getRealTimeAlarm()
     },
+
     handleCurrentChange(index) {
       this.page.pageIndex = index
       this.getRealTimeAlarm()
     },
-    getPieData() {
-      this.getDateAlarmType()
+
+    // ==================== 数据加载 ====================
+    loadData() {
+      this.getAlarmLevelDistribution()
       this.getRealTimeAlarm()
     },
+
+    // 获取告警级别分布 - 使用新接口
+    async getAlarmLevelDistribution() {
+      try {
+        const params = {
+          areaCode: this.areaType || undefined
+        }
+        const { data } = await countAlarmByLevel(params)
+
+        if (!data || data.length === 0) {
+          this.pieData = []
+          return
+        }
+
+        // 计算总数
+        const totalValue = data.reduce((acc, cur) => acc + (cur.cnt || 0), 0)
+
+        // 转换为饼图数据格式
+        this.pieData = data.map(item => {
+          const levelInfo = ALARM_LEVEL_MAP[item.alarmLevel]
+          const colorInfo = colorDic[item.alarmLevel] || { itemStyle: { color: '#909399' } }
+          const proportion = totalValue > 0 ? ((item.cnt / totalValue) * 100).toFixed(2) : '0.00'
+
+          return {
+            name: levelInfo?.name || `级别${item.alarmLevel}`,
+            alarmLevel: item.alarmLevel,
+            value: item.cnt || 0,
+            proportion: proportion,
+            ...colorInfo
+          }
+        })
+      } catch (error) {
+        console.error('获取告警级别分布失败:', error)
+        this.pieData = []
+      }
+    },
+
+    // 获取实时告警列表 - 使用新接口
+    async getRealTimeAlarm() {
+      try {
+        const params = {
+          pageNum: this.page.pageIndex,
+          pageSize: this.page.pageSize,
+          areaCode: this.areaType || undefined,
+          // 新模型:活动状态为 0, 1, 2 (活动、已确认、处置中)
+          alarmStatus: undefined // 查询所有状态,或可指定特定状态
+        }
+
+        // 如果只想显示待处理的告警,可以多次查询或后端支持状态列表
+        // 这里查询活动状态的告警
+        const { code, rows, total } = await listAlarm({
+          ...params,
+          // 可以通过时间排序获取最新告警
+        })
+
+        if (code === 200 && rows) {
+          this.tableData = rows
+          this.page.total = total || 0
+        } else {
+          this.tableData = []
+          this.page.total = 0
+        }
+      } catch (error) {
+        console.error('获取实时告警失败:', error)
+        this.tableData = []
+        this.page.total = 0
+      }
+    },
+
+    // ==================== 3D饼图相关方法 ====================
     getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h) {
-      const midRatio = (startRatio + endRatio) / 2;
-      const startRadian = startRatio * Math.PI * 2;
-      const endRadian = endRatio * Math.PI * 2;
-      const midRadian = midRatio * Math.PI * 2;
-      // 如果只有一个扇形,则不实现选中效果。
+      const midRatio = (startRatio + endRatio) / 2
+      const startRadian = startRatio * Math.PI * 2
+      const endRadian = endRatio * Math.PI * 2
+      const midRadian = midRatio * Math.PI * 2
+
       if (startRatio === 0 && endRatio === 1) {
-        isSelected = false;
+        isSelected = false
       }
-      k = typeof k !== 'undefined' ? k : 1 / 3;
-      const offsetX = isSelected ? Math.cos(midRadian) * 0.1 : 0;
-      const offsetY = isSelected ? Math.sin(midRadian) * 0.1 : 0;
-      // 鼠标滑过时外环放大大小
-      const hoverRate = isHovered ? 1.05 : 1;
-      // 返回曲面参数方程
-      return {
-        u: {
-          min: -Math.PI,
-          max: Math.PI * 3,
-          step: Math.PI / 32,
-        },
 
-        v: {
-          min: 0,
-          max: Math.PI * 2,
-          step: Math.PI / 20,
-        },
+      k = typeof k !== 'undefined' ? k : 1 / 3
+      const offsetX = isSelected ? Math.cos(midRadian) * 0.1 : 0
+      const offsetY = isSelected ? Math.sin(midRadian) * 0.1 : 0
+      const hoverRate = isHovered ? 1.05 : 1
 
+      return {
+        u: { min: -Math.PI, max: Math.PI * 3, step: Math.PI / 32 },
+        v: { min: 0, max: Math.PI * 2, step: Math.PI / 20 },
         x(u, v) {
           if (u < startRadian) {
-            return offsetX + Math.cos(startRadian) * (1 + Math.cos(v) * k) * hoverRate;
+            return offsetX + Math.cos(startRadian) * (1 + Math.cos(v) * k) * hoverRate
           }
           if (u > endRadian) {
-            return offsetX + Math.cos(endRadian) * (1 + Math.cos(v) * k) * hoverRate;
+            return offsetX + Math.cos(endRadian) * (1 + Math.cos(v) * k) * hoverRate
           }
-          return offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate;
+          return offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate
         },
-
         y(u, v) {
           if (u < startRadian) {
-            return offsetY + Math.sin(startRadian) * (1 + Math.cos(v) * k) * hoverRate;
+            return offsetY + Math.sin(startRadian) * (1 + Math.cos(v) * k) * hoverRate
           }
           if (u > endRadian) {
-            return offsetY + Math.sin(endRadian) * (1 + Math.cos(v) * k) * hoverRate;
+            return offsetY + Math.sin(endRadian) * (1 + Math.cos(v) * k) * hoverRate
           }
-          return offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate;
+          return offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate
         },
-
         z(u, v) {
           if (u < -Math.PI * 0.5) {
-            return Math.sin(u);
+            return Math.sin(u)
           }
           if (u > Math.PI * 2.5) {
-            return Math.sin(u) * h * 0.1;
+            return Math.sin(u) * h * 0.1
           }
-          // 当前图形的高度是Z根据h(每个value的值决定的)
-          return Math.sin(v) > 0 ? 1 * h * 0.1 : -1;
-        },
-      };
+          return Math.sin(v) > 0 ? 1 * h * 0.1 : -1
+        }
+      }
     },
+
     getPie3D(pieData, internalDiameterRatio) {
-      const series = [];
-      let sumValue = 0;
-      let startValue = 0;
-      let endValue = 0;
-      const legendData = [];
-      const k =
-        typeof internalDiameterRatio !== 'undefined'
-          ? (1 - internalDiameterRatio) / (1 + internalDiameterRatio)
-          : 1 / 3;
+      const series = []
+      let sumValue = 0
+      let startValue = 0
+      let endValue = 0
+
+      const k = typeof internalDiameterRatio !== 'undefined'
+        ? (1 - internalDiameterRatio) / (1 + internalDiameterRatio)
+        : 1 / 3
+
       for (let i = 0; i < pieData.length; i += 1) {
-        sumValue += pieData[i].value;
+        sumValue += pieData[i].value
         const seriesItem = {
           name: typeof pieData[i].name === 'undefined' ? `series${i}` : pieData[i].name,
           type: 'surface',
           parametric: true,
-          wireframe: {
-            show: false,
-          },
+          wireframe: { show: false },
           pieData: pieData[i],
-          pieStatus: {
-            selected: false,
-            hovered: false,
-            k,
-          },
-        };
+          pieStatus: { selected: false, hovered: false, k }
+        }
+
         if (typeof pieData[i].itemStyle !== 'undefined') {
-          const {itemStyle} = pieData[i];
-          // eslint-disable-next-line no-unused-expressions
-          typeof pieData[i].itemStyle.color !== 'undefined' ? (itemStyle.color = pieData[i].itemStyle.color) : null;
-          // eslint-disable-next-line no-unused-expressions
-          typeof pieData[i].itemStyle.opacity !== 'undefined'
-            ? (itemStyle.opacity = pieData[i].itemStyle.opacity)
-            : null;
-
-          seriesItem.itemStyle = itemStyle;
+          const { itemStyle } = pieData[i]
+          if (typeof pieData[i].itemStyle.color !== 'undefined') {
+            itemStyle.color = pieData[i].itemStyle.color
+          }
+          if (typeof pieData[i].itemStyle.opacity !== 'undefined') {
+            itemStyle.opacity = pieData[i].itemStyle.opacity
+          }
+          seriesItem.itemStyle = itemStyle
         }
-        series.push(seriesItem);
+        series.push(seriesItem)
       }
+
       for (let i = 0; i < series.length; i += 1) {
-        endValue = startValue + series[i].pieData.value;
-        series[i].pieData.startRatio = startValue / sumValue;
-        series[i].pieData.endRatio = endValue / sumValue;
+        endValue = startValue + series[i].pieData.value
+        series[i].pieData.startRatio = startValue / sumValue
+        series[i].pieData.endRatio = endValue / sumValue
         series[i].parametricEquation = this.getParametricEquation(
           series[i].pieData.startRatio,
           series[i].pieData.endRatio,
           false,
           false,
           k,
-          10//在此处传入饼图初始高度h
-        );
-        startValue = endValue;
-        legendData.push(series[i].name);
+          10
+        )
+        startValue = endValue
       }
-      // 准备待返回的配置项,把准备好的series 传入。
+
       const option = {
         legend: {
-          orient: "vertical",
+          orient: 'vertical',
           right: '8%',
           top: '22%',
           textStyle: {
-            color: "#fff",
-            fontSize: 14,
+            color: '#fff',
+            fontSize: 14
           },
           icon: 'circle',
           formatter: (name) => {
             if (this.pieData.length) {
-              const item = this.pieData.filter((item) => item.name === name)[0];
-              let str = `${name}(${item.value}) ${item.proportion}%`
-              return str
+              const item = this.pieData.find((item) => item.name === name)
+              if (item) {
+                return `${name}(${item.value}) ${item.proportion}%`
+              }
             }
-          },
+            return name
+          }
         },
         tooltip: {
           formatter: (params) => {
-            let str = `${params.marker}${params.seriesName}:${this.pieData[params.seriesIndex].value}` + '个,占比:' + `${this.pieData[params.seriesIndex].proportion}` + '%'
-            return str;
-
-          },
-        },
-        xAxis3D: {
-          min: -1,
-          max: 1,
-        },
-        yAxis3D: {
-          min: -1,
-          max: 1,
-        },
-        zAxis3D: {
-          min: -1,
-          max: 1,
+            const item = this.pieData[params.seriesIndex]
+            if (item) {
+              return `${params.marker}${params.seriesName}:${item.value}个,占比:${item.proportion}%`
+            }
+            return ''
+          }
         },
+        xAxis3D: { min: -1, max: 1 },
+        yAxis3D: { min: -1, max: 1 },
+        zAxis3D: { min: -1, max: 1 },
         grid3D: {
           show: false,
-          boxHeight: 25,//修改立体饼图的高度
+          boxHeight: 25,
           top: '-10%',
           left: '-20%',
           viewControl: {
-            // 3d效果可以放大、旋转等,
-            alpha: 35,//饼图翻转的程度
+            alpha: 35,
             beta: 30,
             rotateSensitivity: 1,
             zoomSensitivity: 0,
             panSensitivity: 0,
-            autoRotate: true,//是否自动旋转
-            distance: 300,//距离越小看到的饼图越大
-          },
+            autoRotate: true,
+            distance: 300
+          }
         },
-        series,
-      };
-      return option;
-    },
-
-    async getDateAlarmType() {
-      const {data} = await fetchCntDateAlarmType(
-        {
-          areaCode: this.areaType,
-        }
-      )
-      if (!data) {
-        return
+        series
       }
-      const pieData = data.map(item => {
-        return {
-          name: this.dict.label.alarm_type[item.alarmType],
-          alarmType: item.alarmType,
-          value: item.cnt,
-          proportion: item.proportion,
-        }
-      })
 
-
-      let totalValue = pieData.reduce((acc, cur) => acc + cur.value, 0);
-      this.pieData = pieData.map(item => {
-        item.proportion = (item.value / totalValue * 100).toFixed(2)
-        let color = colorDic[item.alarmType] || colorDic['6']
-        return {
-          ...item,
-          ...color
-        }
-      })
-    },
-    async getRealTimeAlarm() {
-      let result = [];
-      const {
-        code,
-        rows,
-        total,
-      } = await listAlarmInfo({
-        pageNum: this.page.pageIndex,
-        pageSize: this.page.pageSize,
-        areaCode: this.areaType,
-        alarmStateList: [
-          ALARM_STATE.new.value, ALARM_STATE.disposing.value,
-        ]
-      });
-      if (ApiCode.SUCCESS === code && rows && rows.length > 0) {
-        result = rows;
-      }
-      this.tableData = result;
-      this.page.total = total
-    },
+      return option
+    }
   }
 }
 </script>
-<style lang='scss' scoped>
-@import url("../index.scss");
 
+<style lang="scss" scoped>
+@import url("../index.scss");
 
 .custom-table {
   position: relative;
@@ -352,7 +389,6 @@ export default {
   }
 
   ::v-deep .el-table th.is-leaf {
-    /* 去除上边框 */
     border: none;
   }
 
@@ -371,7 +407,6 @@ export default {
     background-color: unset !important;
   }
 
-  // 去掉el-table的所有背景颜色以及所有hover的颜色
   ::v-deep.el-table,
   ::v-deep.el-table .el-table__header-wrapper th,
   ::v-deep.el-table--border {
@@ -403,4 +438,83 @@ export default {
 ::v-deep .el-pagination__jump {
   color: #fff;
 }
+
+// 告警级别标签样式
+.level-tag {
+  display: inline-block;
+  padding: 2px 8px;
+  border-radius: 4px;
+  font-size: 12px;
+  font-weight: 500;
+
+  &.level-1 {
+    background: rgba(25, 144, 255, 0.2);
+    color: #1990FF;
+    border: 1px solid rgba(25, 144, 255, 0.5);
+  }
+
+  &.level-2 {
+    background: rgba(255, 189, 31, 0.2);
+    color: #FFBD1F;
+    border: 1px solid rgba(255, 189, 31, 0.5);
+  }
+
+  &.level-3 {
+    background: rgba(255, 73, 73, 0.2);
+    color: #FF4949;
+    border: 1px solid rgba(255, 73, 73, 0.5);
+    animation: blink 1.5s infinite;
+  }
+}
+
+// 告警状态标签样式
+.status-tag {
+  display: inline-block;
+  padding: 2px 6px;
+  border-radius: 4px;
+  font-size: 11px;
+
+  &.status-0 {
+    background: rgba(255, 73, 73, 0.2);
+    color: #FF4949;
+  }
+
+  &.status-1 {
+    background: rgba(255, 189, 31, 0.2);
+    color: #FFBD1F;
+  }
+
+  &.status-2 {
+    background: rgba(25, 144, 255, 0.2);
+    color: #1990FF;
+  }
+
+  &.status-3,
+  &.status-5 {
+    background: rgba(47, 227, 15, 0.2);
+    color: #2fe30f;
+  }
+
+  &.status-4 {
+    background: rgba(144, 147, 153, 0.2);
+    color: #909399;
+  }
+}
+
+// 告警内容样式
+.alarm-content {
+  color: #FFBD1F;
+}
+
+// 告警时间样式
+.alarm-time {
+  font-size: 12px;
+  color: #B3E3E8;
+}
+
+// 闪烁动画
+@keyframes blink {
+  0%, 100% { opacity: 1; }
+  50% { opacity: 0.5; }
+}
 </style>

+ 251 - 24
ems-ui-cloud/src/views/largeScreen/home/right.vue

@@ -76,19 +76,39 @@
       <BaseChart height="350px" width="100%" :option="lineOptions"/>
     </CusModule>
 
+    <!-- 告警信息模块 - 适配新接口 -->
     <CusModule class="module-top" title="告警信息">
+      <template v-slot:title-right>
+        <div class="alarm-summary">
+          <span class="alarm-count" v-if="alarmStats.activeCount > 0">
+            活动告警: <span class="count-value warning">{{ alarmStats.activeCount }}</span>
+          </span>
+          <span class="alarm-count" v-if="alarmStats.urgentCount > 0">
+            紧急: <span class="count-value danger">{{ alarmStats.urgentCount }}</span>
+          </span>
+        </div>
+      </template>
       <div class="seamless-header">
         <div>告警内容</div>
-        <div>告警设备</div>
+        <div>告警目标</div>
         <div>告警时间</div>
       </div>
       <vue-seamless-scroll :data="listData" class="seamless-warp" :class-option="classOption">
-        <div class="seamless-item" v-for="(item, index) in listData" :key="index">
-          <div>{{ item.alarmMsg }}</div>
-          <div>{{ item.objName }}</div>
-          <div>{{ item.alarmTime }}</div>
+        <div class="seamless-item" v-for="(item, index) in listData" :key="index"
+             :class="getAlarmRowClass(item)">
+          <div class="alarm-msg-cell">
+            <span class="alarm-level-dot" :class="getAlarmLevelClass(item.alarmLevel)"></span>
+            {{ item.alarmMsg }}
+          </div>
+          <div>{{ item.targetName }}</div>
+          <div>{{ formatAlarmTime(item.alarmTime) }}</div>
         </div>
       </vue-seamless-scroll>
+      <!-- 无数据提示 -->
+      <div v-if="listData.length === 0" class="no-alarm-data">
+        <i class="el-icon-check"></i>
+        <span>暂无活动告警</span>
+      </div>
     </CusModule>
 
   </div>
@@ -104,8 +124,12 @@ import {qryElecMeterByDate, qryWaterMeterByDate} from "@/api/device/elecMeterH";
 import {DateTool} from "@/utils/DateTool";
 import {array2Map, numToStr} from "@/utils";
 import {listDeviceStatus, listDeviceType} from "@/api/device/device";
-import {listAlarmInfo} from "@/api/alarm/alarm-info";
-import {ALARM_STATE} from "@/enums/alarmEnum";
+// ========== 告警接口适配:使用新接口替换旧接口 ==========
+// 旧接口: import {listAlarmInfo} from "@/api/alarm/alarm-info";
+// 新接口:
+import { listActiveAlarm, getAlarmStatsOverview } from "@/api/alarm/alarm";
+// 旧枚举已不需要: import {ALARM_STATE} from "@/enums/alarmEnum";
+// ========================================================
 import {ApiCode} from "@/api/apiEmums";
 import {listSumCaMeterD} from "@/api/ca/caMeterD";
 import {calcElecProdForecastDateRange} from "@/api/prediction/predictionProd";
@@ -149,6 +173,12 @@ export default {
         waitTime: 1000
       },
       listData: [],
+      // 新增:告警统计数据
+      alarmStats: {
+        activeCount: 0,
+        urgentCount: 0,
+        importantCount: 0
+      },
       lineData: [],
       monthCaEmission: '0.00',
       // 新增:发电趋势预测数据
@@ -417,10 +447,10 @@ export default {
       this.getElecWaterCost()
       this.getDeviceStatus()
       this.cntListDeviceType()
-      this.getRealTimeAlarm()
+      this.getRealTimeAlarm()  // 使用新接口
       this.getListSumCaMeterD()
       this.getMonthCaEmission()
-      this.getElecProdForecast()  // 新增:获取发电趋势预测
+      this.getElecProdForecast()
     },
     async getElecWaterCost() {
       const {data: thisMonthElecMeter} = await qryElecMeterByDate(DateTool.thisMonth(), this.areaType)
@@ -491,24 +521,112 @@ export default {
         value: item.total
       }))
     },
+
+    /**
+     * ========== 告警接口适配 ==========
+     * 原方法使用 listAlarmInfo 接口,现改为使用新接口 listActiveAlarm
+     *
+     * 主要变化:
+     * 1. 接口变更: listAlarmInfo -> listActiveAlarm
+     * 2. 参数变更: 不再需要 alarmStateList 参数,新接口直接返回活动告警
+     * 3. 字段映射: objName -> targetName
+     * 4. 新增告警统计信息展示
+     */
     async getRealTimeAlarm() {
-      let result = [];
-      const {
-        code,
-        rows,
-      } = await listAlarmInfo({
-        pageNum: 1,
-        pageSize: 10,
-        areaCode: this.areaType,
-        alarmStateList: [
-          ALARM_STATE.new.value, ALARM_STATE.disposing.value,
-        ]
-      });
-      if (ApiCode.SUCCESS === code && rows && rows.length > 0) {
-        result = rows;
+      try {
+        // 使用新接口获取活动告警
+        const areaCode = this.areaType === '-1' ? null : this.areaType;
+        const res = await listActiveAlarm(areaCode);
+
+        if (res.code === 200 && res.data && res.data.length > 0) {
+          // 新接口返回的数据结构
+          // 字段映射:旧接口 objName -> 新接口 targetName
+          // 按告警级别和时间排序(紧急优先,最新优先)
+          const sortedData = res.data.sort((a, b) => {
+            // 先按级别降序(3紧急 > 2重要 > 1一般)
+            if (b.alarmLevel !== a.alarmLevel) {
+              return b.alarmLevel - a.alarmLevel;
+            }
+            // 再按时间降序
+            return new Date(b.alarmTime) - new Date(a.alarmTime);
+          });
+
+          // 只取前10条用于滚动展示
+          this.listData = sortedData.slice(0, 10);
+
+          // 统计告警数量
+          this.alarmStats = {
+            activeCount: res.data.length,
+            urgentCount: res.data.filter(a => a.alarmLevel === 3).length,
+            importantCount: res.data.filter(a => a.alarmLevel === 2).length
+          };
+        } else {
+          this.listData = [];
+          this.alarmStats = {
+            activeCount: 0,
+            urgentCount: 0,
+            importantCount: 0
+          };
+        }
+      } catch (error) {
+        console.error('获取实时告警失败:', error);
+        this.listData = [];
+        this.alarmStats = {
+          activeCount: 0,
+          urgentCount: 0,
+          importantCount: 0
+        };
+      }
+    },
+
+    /**
+     * 格式化告警时间显示
+     * 大屏展示时使用更简洁的时间格式
+     */
+    formatAlarmTime(time) {
+      if (!time) return '';
+      const date = new Date(time);
+      const now = new Date();
+      const diff = Math.floor((now - date) / 1000);
+
+      // 1分钟内显示"刚刚"
+      if (diff < 60) return '刚刚';
+      // 1小时内显示"X分钟前"
+      if (diff < 3600) return `${Math.floor(diff / 60)}分钟前`;
+      // 24小时内显示"X小时前"
+      if (diff < 86400) return `${Math.floor(diff / 3600)}小时前`;
+      // 超过24小时显示日期时间
+      const month = String(date.getMonth() + 1).padStart(2, '0');
+      const day = String(date.getDate()).padStart(2, '0');
+      const hours = String(date.getHours()).padStart(2, '0');
+      const minutes = String(date.getMinutes()).padStart(2, '0');
+      return `${month}-${day} ${hours}:${minutes}`;
+    },
+
+    /**
+     * 根据告警级别获取行样式类
+     * 告警级别: 1-一般, 2-重要, 3-紧急
+     */
+    getAlarmRowClass(item) {
+      if (!item || !item.alarmLevel) return '';
+      switch (item.alarmLevel) {
+        case 3: return 'alarm-urgent';
+        case 2: return 'alarm-important';
+        default: return '';
       }
-      this.listData = result;
     },
+
+    /**
+     * 根据告警级别获取圆点样式类
+     */
+    getAlarmLevelClass(level) {
+      switch (level) {
+        case 3: return 'level-urgent';
+        case 2: return 'level-important';
+        default: return 'level-normal';
+      }
+    },
+
     /**
      * 获取近7天碳排放数据(用于图表)
      */
@@ -760,6 +878,39 @@ export default {
   background-position: center;
 }
 
+/* ========== 告警模块样式优化 ========== */
+
+// 告警统计摘要
+.alarm-summary {
+  display: flex;
+  align-items: center;
+  gap: 15px;
+  font-size: 13px;
+
+  .alarm-count {
+    color: #B3E3E8;
+
+    .count-value {
+      font-weight: bold;
+      margin-left: 4px;
+
+      &.warning {
+        color: #E6A23C;
+      }
+
+      &.danger {
+        color: #F56C6C;
+        animation: blink 1s infinite;
+      }
+    }
+  }
+}
+
+@keyframes blink {
+  0%, 100% { opacity: 1; }
+  50% { opacity: 0.5; }
+}
+
 .seamless-header {
   margin-top: 20px;
   display: flex;
@@ -789,11 +940,30 @@ export default {
     align-items: center;
     justify-content: space-between;
     padding: 5px 0;
+    transition: background-color 0.3s;
 
     &:nth-child(odd) {
       background: #000;
     }
 
+    // 紧急告警样式
+    &.alarm-urgent {
+      background: rgba(245, 108, 108, 0.15) !important;
+
+      .alarm-msg-cell {
+        color: #F56C6C;
+      }
+    }
+
+    // 重要告警样式
+    &.alarm-important {
+      background: rgba(230, 162, 60, 0.1) !important;
+
+      .alarm-msg-cell {
+        color: #E6A23C;
+      }
+    }
+
     > div:first-of-type,
     > div:last-of-type {
       flex-basis: 38%;
@@ -803,9 +973,66 @@ export default {
       text-align: center;
       font-size: 13px;
     }
+
+    // 告警内容单元格
+    .alarm-msg-cell {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+
+      .alarm-level-dot {
+        width: 8px;
+        height: 8px;
+        border-radius: 50%;
+        margin-right: 6px;
+        flex-shrink: 0;
+
+        &.level-urgent {
+          background: #F56C6C;
+          box-shadow: 0 0 6px #F56C6C;
+          animation: pulse 1.5s infinite;
+        }
+
+        &.level-important {
+          background: #E6A23C;
+          box-shadow: 0 0 4px #E6A23C;
+        }
+
+        &.level-normal {
+          background: #409EFF;
+        }
+      }
+    }
+  }
+}
+
+@keyframes pulse {
+  0%, 100% {
+    box-shadow: 0 0 6px #F56C6C;
+  }
+  50% {
+    box-shadow: 0 0 12px #F56C6C;
+  }
+}
+
+// 无数据提示
+.no-alarm-data {
+  height: 200px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  color: #67C23A;
+  font-size: 14px;
+
+  i {
+    font-size: 48px;
+    margin-bottom: 10px;
   }
 }
 
+/* ========== 告警模块样式优化结束 ========== */
+
 @keyframes spin {
   from {
     transform: rotate(0deg);

+ 250 - 24
ems-ui-cloud/src/views/largeScreen/source/pv-road-right.vue

@@ -3,22 +3,44 @@
     <CusModule class="module-top" title="发电趋势预测">
       <BaseChart height="350px" width="100%" :option="lineOptions"/>
     </CusModule>
+
+    <!-- 告警信息模块 - 适配新接口 -->
     <CusModule title="告警信息">
+      <template v-slot:title-right>
+        <div class="alarm-summary" v-if="alarmStats.activeCount > 0">
+          <span class="alarm-count">
+            活动: <span class="count-value warning">{{ alarmStats.activeCount }}</span>
+          </span>
+          <span class="alarm-count" v-if="alarmStats.urgentCount > 0">
+            紧急: <span class="count-value danger">{{ alarmStats.urgentCount }}</span>
+          </span>
+        </div>
+      </template>
       <div class="seamless-header">
         <div>告警内容</div>
-        <div>告警设备</div>
+        <div>告警目标</div>
         <div>告警时间</div>
       </div>
       <vue-seamless-scroll :data="listData" class="seamless-warp" :class-option="classOption">
-        <div class="seamless-item" v-for="(item, index) in listData" :key="index">
-          <div>{{ item.alarmMsg }}</div>
-          <div>{{ item.objName }}</div>
-          <div>{{ item.alarmTime }}</div>
+        <div class="seamless-item" v-for="(item, index) in listData" :key="index"
+             :class="getAlarmRowClass(item)">
+          <div class="alarm-msg-cell">
+            <span class="alarm-level-dot" :class="getAlarmLevelClass(item.alarmLevel)"></span>
+            {{ item.alarmMsg }}
+          </div>
+          <div>{{ item.targetName }}</div>
+          <div>{{ formatAlarmTime(item.alarmTime) }}</div>
         </div>
       </vue-seamless-scroll>
+      <!-- 无数据提示 -->
+      <div v-if="listData.length === 0" class="no-alarm-data">
+        <i class="el-icon-check"></i>
+        <span>暂无活动告警</span>
+      </div>
     </CusModule>
   </div>
 </template>
+
 <script>
 import CusModule from '../components/CusModule.vue';
 import BaseChart from '@/components/BaseChart/index.vue'
@@ -28,8 +50,12 @@ import * as echarts from 'echarts'
 import {mapState} from 'vuex';
 import {DateTool} from "@/utils/DateTool";
 import {predictionProdDateRange} from "@/api/screen";
-import {listAlarmInfo} from "@/api/alarm/alarm-info";
-import {ALARM_STATE} from "@/enums/alarmEnum";
+// ========== 告警接口适配:使用新接口替换旧接口 ==========
+// 旧接口: import {listAlarmInfo} from "@/api/alarm/alarm-info";
+// 新接口:
+import { listActiveAlarm } from "@/api/alarm/alarm";
+// 旧枚举已不需要: import {ALARM_STATE} from "@/enums/alarmEnum";
+// ========================================================
 import {ApiCode} from "@/api/apiEmums";
 
 export default {
@@ -48,7 +74,13 @@ export default {
         singleWidth: 0, // 单步运动停止的宽度(默认值0是无缝不停止的滚动) direction => 2/3
         waitTime: 1000 // 单步运动停止
       },
-      listData: []
+      listData: [],
+      // 新增:告警统计数据
+      alarmStats: {
+        activeCount: 0,
+        urgentCount: 0,
+        importantCount: 0
+      }
     }
   },
   components: {
@@ -109,7 +141,7 @@ export default {
             },
             areaStyle: { //区域填充样式
               normal: {
-                //线性渐变,前4个参数分别是x0,y0,x2,y2(范围0~1);相当于图形包围盒中的百分比。如果最后一个参数是‘true’,则该四个值是绝对像素位置。
+                //线性渐变,前4个参数分别是x0,y0,x2,y2(范围0~1);相当于图形包围盒中的百分比。如果最后一个参数是'true',则该四个值是绝对像素位置。
                 color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
                   offset: 0,
                   color: "rgba(81, 139, 152,0.8)"
@@ -165,27 +197,112 @@ export default {
       }))
     },
 
+    /**
+     * ========== 告警接口适配 ==========
+     * 原方法使用 listAlarmInfo 接口,现改为使用新接口 listActiveAlarm
+     *
+     * 主要变化:
+     * 1. 接口变更: listAlarmInfo -> listActiveAlarm
+     * 2. 参数变更: 不再需要 alarmStateList 参数,新接口直接返回活动告警
+     * 3. 字段映射: objName -> targetName
+     * 4. 返回结构: rows -> data
+     */
     async getRealTimeAlarm() {
-      let result = [];
-      const {
-        code,
-        rows,
-      } = await listAlarmInfo({
-        pageNum: 1,
-        pageSize: 20,
-        areaCode: this.areaType,
-        alarmStateList: [
-          ALARM_STATE.new.value, ALARM_STATE.disposing.value,
-        ]
-      });
-      if (ApiCode.SUCCESS === code && rows && rows.length > 0) {
-        result = rows;
+      try {
+        // 使用新接口获取活动告警
+        const areaCode = this.areaType === '-1' ? null : this.areaType;
+        const res = await listActiveAlarm(areaCode);
+
+        if (res.code === 200 && res.data && res.data.length > 0) {
+          // 按告警级别和时间排序(紧急优先,最新优先)
+          const sortedData = res.data.sort((a, b) => {
+            // 先按级别降序(3紧急 > 2重要 > 1一般)
+            if (b.alarmLevel !== a.alarmLevel) {
+              return b.alarmLevel - a.alarmLevel;
+            }
+            // 再按时间降序
+            return new Date(b.alarmTime) - new Date(a.alarmTime);
+          });
+
+          // 取前20条用于滚动展示
+          this.listData = sortedData.slice(0, 20);
+
+          // 统计告警数量
+          this.alarmStats = {
+            activeCount: res.data.length,
+            urgentCount: res.data.filter(a => a.alarmLevel === 3).length,
+            importantCount: res.data.filter(a => a.alarmLevel === 2).length
+          };
+        } else {
+          this.listData = [];
+          this.alarmStats = {
+            activeCount: 0,
+            urgentCount: 0,
+            importantCount: 0
+          };
+        }
+      } catch (error) {
+        console.error('获取实时告警失败:', error);
+        this.listData = [];
+        this.alarmStats = {
+          activeCount: 0,
+          urgentCount: 0,
+          importantCount: 0
+        };
+      }
+    },
+
+    /**
+     * 格式化告警时间显示
+     * 大屏展示时使用更简洁的时间格式
+     */
+    formatAlarmTime(time) {
+      if (!time) return '';
+      const date = new Date(time);
+      const now = new Date();
+      const diff = Math.floor((now - date) / 1000);
+
+      // 1分钟内显示"刚刚"
+      if (diff < 60) return '刚刚';
+      // 1小时内显示"X分钟前"
+      if (diff < 3600) return `${Math.floor(diff / 60)}分钟前`;
+      // 24小时内显示"X小时前"
+      if (diff < 86400) return `${Math.floor(diff / 3600)}小时前`;
+      // 超过24小时显示日期时间
+      const month = String(date.getMonth() + 1).padStart(2, '0');
+      const day = String(date.getDate()).padStart(2, '0');
+      const hours = String(date.getHours()).padStart(2, '0');
+      const minutes = String(date.getMinutes()).padStart(2, '0');
+      return `${month}-${day} ${hours}:${minutes}`;
+    },
+
+    /**
+     * 根据告警级别获取行样式类
+     * 告警级别: 1-一般, 2-重要, 3-紧急
+     */
+    getAlarmRowClass(item) {
+      if (!item || !item.alarmLevel) return '';
+      switch (item.alarmLevel) {
+        case 3: return 'alarm-urgent';
+        case 2: return 'alarm-important';
+        default: return '';
       }
-      this.listData = result;
     },
+
+    /**
+     * 根据告警级别获取圆点样式类
+     */
+    getAlarmLevelClass(level) {
+      switch (level) {
+        case 3: return 'level-urgent';
+        case 2: return 'level-important';
+        default: return 'level-normal';
+      }
+    }
   }
 }
 </script>
+
 <style lang='scss' scoped>
 @import url("../index.scss");
 
@@ -219,6 +336,39 @@ export default {
   }
 }
 
+/* ========== 告警模块样式 ========== */
+
+// 告警统计摘要
+.alarm-summary {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  font-size: 13px;
+
+  .alarm-count {
+    color: #B3E3E8;
+
+    .count-value {
+      font-weight: bold;
+      margin-left: 4px;
+
+      &.warning {
+        color: #E6A23C;
+      }
+
+      &.danger {
+        color: #F56C6C;
+        animation: blink 1s infinite;
+      }
+    }
+  }
+}
+
+@keyframes blink {
+  0%, 100% { opacity: 1; }
+  50% { opacity: 0.5; }
+}
+
 .seamless-header {
   margin-top: 20px;
   display: flex;
@@ -248,11 +398,30 @@ export default {
     align-items: center;
     justify-content: space-between;
     padding: 5px 0;
+    transition: background-color 0.3s;
 
     &:nth-child(odd) {
       background: #000;
     }
 
+    // 紧急告警样式
+    &.alarm-urgent {
+      background: rgba(245, 108, 108, 0.15) !important;
+
+      .alarm-msg-cell {
+        color: #F56C6C;
+      }
+    }
+
+    // 重要告警样式
+    &.alarm-important {
+      background: rgba(230, 162, 60, 0.1) !important;
+
+      .alarm-msg-cell {
+        color: #E6A23C;
+      }
+    }
+
     > div:first-of-type,
     > div:last-of-type {
       flex-basis: 38%;
@@ -262,6 +431,63 @@ export default {
       text-align: center;
       font-size: 13px;
     }
+
+    // 告警内容单元格
+    .alarm-msg-cell {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+
+      .alarm-level-dot {
+        width: 8px;
+        height: 8px;
+        border-radius: 50%;
+        margin-right: 6px;
+        flex-shrink: 0;
+
+        &.level-urgent {
+          background: #F56C6C;
+          box-shadow: 0 0 6px #F56C6C;
+          animation: pulse 1.5s infinite;
+        }
+
+        &.level-important {
+          background: #E6A23C;
+          box-shadow: 0 0 4px #E6A23C;
+        }
+
+        &.level-normal {
+          background: #409EFF;
+        }
+      }
+    }
+  }
+}
+
+@keyframes pulse {
+  0%, 100% {
+    box-shadow: 0 0 6px #F56C6C;
+  }
+  50% {
+    box-shadow: 0 0 12px #F56C6C;
   }
 }
+
+// 无数据提示
+.no-alarm-data {
+  height: 400px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  color: #67C23A;
+  font-size: 14px;
+
+  i {
+    font-size: 48px;
+    margin-bottom: 10px;
+  }
+}
+
+/* ========== 告警模块样式结束 ========== */
 </style>

+ 248 - 23
ems-ui-cloud/src/views/largeScreen/source/right.vue

@@ -1,18 +1,38 @@
 <template>
   <div>
+    <!-- 告警信息模块 - 适配新接口 -->
     <CusModule title="告警信息">
+      <template v-slot:title-right>
+        <div class="alarm-summary" v-if="alarmStats.activeCount > 0">
+          <span class="alarm-count">
+            活动: <span class="count-value warning">{{ alarmStats.activeCount }}</span>
+          </span>
+          <span class="alarm-count" v-if="alarmStats.urgentCount > 0">
+            紧急: <span class="count-value danger">{{ alarmStats.urgentCount }}</span>
+          </span>
+        </div>
+      </template>
       <div class="seamless-header">
         <div>告警内容</div>
-        <div>告警设备</div>
+        <div>告警目标</div>
         <div>告警时间</div>
       </div>
       <vue-seamless-scroll :data="listData" class="seamless-warp" :class-option="classOption">
-        <div class="seamless-item" v-for="(item, index) in listData" :key="index">
-          <div>{{ item.alarmMsg }}</div>
-          <div>{{ item.objName }}</div>
-          <div>{{ item.alarmTime }}</div>
+        <div class="seamless-item" v-for="(item, index) in listData" :key="index"
+             :class="getAlarmRowClass(item)">
+          <div class="alarm-msg-cell">
+            <span class="alarm-level-dot" :class="getAlarmLevelClass(item.alarmLevel)"></span>
+            {{ item.alarmMsg }}
+          </div>
+          <div>{{ item.targetName }}</div>
+          <div>{{ formatAlarmTime(item.alarmTime) }}</div>
         </div>
       </vue-seamless-scroll>
+      <!-- 无数据提示 -->
+      <div v-if="listData.length === 0" class="no-alarm-data">
+        <i class="el-icon-check"></i>
+        <span>暂无活动告警</span>
+      </div>
     </CusModule>
     <!-- 修改:节能减排 -> 碳排放统计(今年) -->
     <CusModule class="module-top" title="碳排放统计(今年)">
@@ -32,6 +52,7 @@
     </CusModule>
   </div>
 </template>
+
 <script>
 import CusModule from '../components/CusModule.vue';
 import BaseChart from '@/components/BaseChart/index.vue'
@@ -41,8 +62,12 @@ import * as echarts from 'echarts'
 import {mapState} from 'vuex';
 import {DateTool} from "@/utils/DateTool";
 import {calcElecProdForecastDateRange} from "@/api/prediction/predictionProd";
-import {listAlarmInfo} from "@/api/alarm/alarm-info";
-import {ALARM_STATE} from "@/enums/alarmEnum";
+// ========== 告警接口适配:使用新接口替换旧接口 ==========
+// 旧接口: import {listAlarmInfo} from "@/api/alarm/alarm-info";
+// 新接口:
+import { listActiveAlarm } from "@/api/alarm/alarm";
+// 旧枚举已不需要: import {ALARM_STATE} from "@/enums/alarmEnum";
+// ========================================================
 import {ApiCode} from "@/api/apiEmums";
 // 新增:引入碳计量API
 import {listSumCaMeterD} from "@/api/ca/caMeterD";
@@ -69,7 +94,13 @@ export default {
         waitTime: 1000 // 单步运动停止
       },
       listData: [],
-      totalElecProdQuantity: '0.00' // 累计预测发电量
+      totalElecProdQuantity: '0.00', // 累计预测发电量
+      // 新增:告警统计数据
+      alarmStats: {
+        activeCount: 0,
+        urgentCount: 0,
+        importantCount: 0
+      }
     }
   },
   components: {
@@ -267,23 +298,59 @@ export default {
       }
     },
 
+    /**
+     * ========== 告警接口适配 ==========
+     * 原方法使用 listAlarmInfo 接口,现改为使用新接口 listActiveAlarm
+     *
+     * 主要变化:
+     * 1. 接口变更: listAlarmInfo -> listActiveAlarm
+     * 2. 参数变更: 不再需要 alarmStateList 参数,新接口直接返回活动告警
+     * 3. 字段映射: objName -> targetName
+     * 4. 返回结构: rows -> data
+     */
     async getRealTimeAlarm() {
-      let result = [];
-      const {
-        code,
-        rows,
-      } = await listAlarmInfo({
-        pageNum: 1,
-        pageSize: 10,
-        areaCode: this.areaType,
-        alarmStateList: [
-          ALARM_STATE.new.value, ALARM_STATE.disposing.value,
-        ]
-      });
-      if (ApiCode.SUCCESS === code && rows && rows.length > 0) {
-        result = rows;
+      try {
+        // 使用新接口获取活动告警
+        const areaCode = this.areaType === '-1' ? null : this.areaType;
+        const res = await listActiveAlarm(areaCode);
+
+        if (res.code === 200 && res.data && res.data.length > 0) {
+          // 按告警级别和时间排序(紧急优先,最新优先)
+          const sortedData = res.data.sort((a, b) => {
+            // 先按级别降序(3紧急 > 2重要 > 1一般)
+            if (b.alarmLevel !== a.alarmLevel) {
+              return b.alarmLevel - a.alarmLevel;
+            }
+            // 再按时间降序
+            return new Date(b.alarmTime) - new Date(a.alarmTime);
+          });
+
+          // 取前10条用于滚动展示
+          this.listData = sortedData.slice(0, 10);
+
+          // 统计告警数量
+          this.alarmStats = {
+            activeCount: res.data.length,
+            urgentCount: res.data.filter(a => a.alarmLevel === 3).length,
+            importantCount: res.data.filter(a => a.alarmLevel === 2).length
+          };
+        } else {
+          this.listData = [];
+          this.alarmStats = {
+            activeCount: 0,
+            urgentCount: 0,
+            importantCount: 0
+          };
+        }
+      } catch (error) {
+        console.error('获取实时告警失败:', error);
+        this.listData = [];
+        this.alarmStats = {
+          activeCount: 0,
+          urgentCount: 0,
+          importantCount: 0
+        };
       }
-      this.listData = result;
     },
 
     /**
@@ -298,10 +365,59 @@ export default {
         return `${parts[1]}-${parts[2]}`;
       }
       return date;
+    },
+
+    /**
+     * 格式化告警时间显示
+     * 大屏展示时使用更简洁的时间格式
+     */
+    formatAlarmTime(time) {
+      if (!time) return '';
+      const date = new Date(time);
+      const now = new Date();
+      const diff = Math.floor((now - date) / 1000);
+
+      // 1分钟内显示"刚刚"
+      if (diff < 60) return '刚刚';
+      // 1小时内显示"X分钟前"
+      if (diff < 3600) return `${Math.floor(diff / 60)}分钟前`;
+      // 24小时内显示"X小时前"
+      if (diff < 86400) return `${Math.floor(diff / 3600)}小时前`;
+      // 超过24小时显示日期时间
+      const month = String(date.getMonth() + 1).padStart(2, '0');
+      const day = String(date.getDate()).padStart(2, '0');
+      const hours = String(date.getHours()).padStart(2, '0');
+      const minutes = String(date.getMinutes()).padStart(2, '0');
+      return `${month}-${day} ${hours}:${minutes}`;
+    },
+
+    /**
+     * 根据告警级别获取行样式类
+     * 告警级别: 1-一般, 2-重要, 3-紧急
+     */
+    getAlarmRowClass(item) {
+      if (!item || !item.alarmLevel) return '';
+      switch (item.alarmLevel) {
+        case 3: return 'alarm-urgent';
+        case 2: return 'alarm-important';
+        default: return '';
+      }
+    },
+
+    /**
+     * 根据告警级别获取圆点样式类
+     */
+    getAlarmLevelClass(level) {
+      switch (level) {
+        case 3: return 'level-urgent';
+        case 2: return 'level-important';
+        default: return 'level-normal';
+      }
     }
   }
 }
 </script>
+
 <style lang='scss' scoped>
 @import url("../index.scss");
 
@@ -335,6 +451,39 @@ export default {
   }
 }
 
+/* ========== 告警模块样式 ========== */
+
+// 告警统计摘要
+.alarm-summary {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  font-size: 13px;
+
+  .alarm-count {
+    color: #B3E3E8;
+
+    .count-value {
+      font-weight: bold;
+      margin-left: 4px;
+
+      &.warning {
+        color: #E6A23C;
+      }
+
+      &.danger {
+        color: #F56C6C;
+        animation: blink 1s infinite;
+      }
+    }
+  }
+}
+
+@keyframes blink {
+  0%, 100% { opacity: 1; }
+  50% { opacity: 0.5; }
+}
+
 .seamless-header {
   margin-top: 20px;
   display: flex;
@@ -364,11 +513,30 @@ export default {
     align-items: center;
     justify-content: space-between;
     padding: 5px 0;
+    transition: background-color 0.3s;
 
     &:nth-child(odd) {
       background: #000;
     }
 
+    // 紧急告警样式
+    &.alarm-urgent {
+      background: rgba(245, 108, 108, 0.15) !important;
+
+      .alarm-msg-cell {
+        color: #F56C6C;
+      }
+    }
+
+    // 重要告警样式
+    &.alarm-important {
+      background: rgba(230, 162, 60, 0.1) !important;
+
+      .alarm-msg-cell {
+        color: #E6A23C;
+      }
+    }
+
     > div:first-of-type,
     > div:last-of-type {
       flex-basis: 38%;
@@ -378,6 +546,63 @@ export default {
       text-align: center;
       font-size: 13px;
     }
+
+    // 告警内容单元格
+    .alarm-msg-cell {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+
+      .alarm-level-dot {
+        width: 8px;
+        height: 8px;
+        border-radius: 50%;
+        margin-right: 6px;
+        flex-shrink: 0;
+
+        &.level-urgent {
+          background: #F56C6C;
+          box-shadow: 0 0 6px #F56C6C;
+          animation: pulse 1.5s infinite;
+        }
+
+        &.level-important {
+          background: #E6A23C;
+          box-shadow: 0 0 4px #E6A23C;
+        }
+
+        &.level-normal {
+          background: #409EFF;
+        }
+      }
+    }
   }
 }
+
+@keyframes pulse {
+  0%, 100% {
+    box-shadow: 0 0 6px #F56C6C;
+  }
+  50% {
+    box-shadow: 0 0 12px #F56C6C;
+  }
+}
+
+// 无数据提示
+.no-alarm-data {
+  height: 200px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  color: #67C23A;
+  font-size: 14px;
+
+  i {
+    font-size: 36px;
+    margin-bottom: 8px;
+  }
+}
+
+/* ========== 告警模块样式结束 ========== */
 </style>

+ 0 - 580
ems-ui-cloud/src/views/mgr/powerdist.vue

@@ -1,580 +0,0 @@
-<template>
-  <div class="powerdist">
-    <div class="powerdist-header">
-      <div>室内环境</div>
-      <div class="powerdist-env">
-        <div>
-          <img src="@/assets/images/mgr/temperature.svg" alt="">
-          <div>
-            <span>温度</span>
-            <span>62.5°C</span>
-          </div>
-        </div>
-        <div>
-          <img src="@/assets/images/mgr/humidity.svg" alt="">
-          <div>
-            <span>湿度</span>
-            <span>77.88RH</span>
-          </div>
-        </div>
-        <div>
-          <img src="@/assets/images/mgr/smoke.svg" alt="">
-          <div>
-            <span>烟感</span>
-            <span>正常</span>
-          </div>
-        </div>
-        <div>
-          <img src="@/assets/images/mgr/water.svg" alt="">
-          <div>
-            <span>水侵</span>
-            <span>正常</span>
-          </div>
-        </div>
-      </div>
-    </div>
-    <div class="powerdist-body">
-      <Panel title="变压器">
-        <div class="transformer">
-          <el-select v-model="transformer" size="mini" @change="getTransformerInfo">
-            <el-option v-for="item in transformerOptions" :key="item.deviceCode" :label="item.deviceName"
-              :value="item.deviceCode"></el-option>
-          </el-select>
-          <div class="transformer-info">
-            <!-- <div class="info-header">
-              <span>A相</span>
-              <span>B相</span>
-              <span>C相</span>
-            </div> -->
-            <img src="@/assets/images/mgr/line.svg" class="lineImg" alt="">
-            <div class="arrowImg">
-              <span>{{ transformerInfo.ratedVoltage }}</span>
-              <img src="@/assets/images/mgr/arrow.svg" alt="">
-            </div>
-            <img class="transformerImg" src="@/assets/images/mgr/transformer.png" alt="">
-          </div>
-          <el-descriptions class="custom-desc" title="设备参数" :column="2" size="mini" border>
-            <el-descriptions-item label="产品型号">{{ transformerInfo.deviceSpec }}</el-descriptions-item>
-            <el-descriptions-item label="额定容量">{{ transformerInfo.ratedCapacity }}</el-descriptions-item>
-            <el-descriptions-item label="额定电压">{{ transformerInfo.ratedVoltage }}</el-descriptions-item>
-            <el-descriptions-item label="额定频率">{{ transformerInfo.ratedFrequency }}</el-descriptions-item>
-            <el-descriptions-item label="相数">{{ transformerInfo.phaseNumber }}</el-descriptions-item>
-            <el-descriptions-item label="生产厂家">{{ transformerInfo.deviceBrand }}</el-descriptions-item>
-          </el-descriptions>
-          <alarm :alarmData="transformerAlarmData" />
-        </div>
-      </Panel>
-      <Panel title="配电柜" background="linear-gradient( 180deg, #F1FEFF 0%, #FFFFFF 100%)">
-        <div class="power">
-          <el-select v-model="powerCode" size="mini" @change="powerChange">
-            <el-option v-for="item in powerList" :key="item.deviceCode" :label="item.deviceName"
-              :value="item.deviceCode"></el-option>
-          </el-select>
-          <div class="power-info">
-            <div>
-              <span>{{ powerInfo.la || '--' }}A</span>
-              <span>A相</span>
-            </div>
-            <div>
-              <span>{{ powerInfo.lb || '--' }}A</span>
-              <span>B相</span>
-            </div>
-            <div>
-              <span>{{ powerInfo.lc || '--' }}A</span>
-              <span>C相</span>
-            </div>
-          </div>
-          <div class="power-data">
-            <div class="power-data-title">实时数据</div>
-            <div class="power-data-body">
-              <div>
-                <div class="data-name">功率因素</div>
-                <div class="data-num">{{ powerInfo.pf || '--' }}</div>
-              </div>
-              <div>
-                <div class="power-data-row">
-                  <div>
-                    <span class="data-name">A相电压</span>
-                    <span class="data-num">{{ powerInfo.ua || '--' }}V</span>
-                  </div>
-                  <div>
-                    <span class="data-name">B相电压</span>
-                    <span class="data-num">{{ powerInfo.ub || '--' }}V</span>
-                  </div>
-                  <div>
-                    <span class="data-name">C相电压</span>
-                    <span class="data-num">{{ powerInfo.uc || '--' }}V</span>
-                  </div>
-                </div>
-                <div class="power-data-row">
-                  <div>
-                    <span class="data-name">A相有功功率</span>
-                    <span class="data-num">{{ powerInfo.pa || '--' }}W</span>
-                  </div>
-                  <div>
-                    <span class="data-name">B相有功功率</span>
-                    <span class="data-num">{{ powerInfo.pb || '--' }}W</span>
-                  </div>
-                  <div>
-                    <span class="data-name">C相有功功率</span>
-                    <span class="data-num">{{ powerInfo.pc || '--' }}W</span>
-                  </div>
-                </div>
-                <div class="power-data-row">
-                  <div>
-                    <span class="data-name">A相无功功率</span>
-                    <span class="data-num">{{ powerInfo.ua || '--' }}W</span>
-                  </div>
-                  <div>
-                    <span class="data-name">B相无功功率</span>
-                    <span class="data-num">{{ powerInfo.ub || '--' }}W</span>
-                  </div>
-                  <div>
-                    <span class="data-name">C相无功功率</span>
-                    <span class="data-num">{{ powerInfo.uc || '--' }}W</span>
-                  </div>
-                </div>
-              </div>
-            </div>
-          </div>
-          <alarm :alarmData="powerAlarmData" @handlerAlarm="handlerPowerAlarm" />
-        </div>
-
-      </Panel>
-      <Panel title="器件" background="linear-gradient( 180deg, #FFFBF1 0%, #FFFFFF 100%)">
-        <div class="switch">
-          <img class="rightArrow" src="@/assets/images/mgr/line2.svg" alt="">
-          <el-table :data="eqptList" style="width: 100%">
-            <el-table-column prop="deviceCode" align="center" width="200" label="设备代码"></el-table-column>
-            <el-table-column prop="deviceName" align="center" width="100" label="设备名称"></el-table-column>
-            <el-table-column prop="deviceBrand" align="center" width="100" label="设备品牌"></el-table-column>
-            <el-table-column prop="location" align="center" width="100" label="安装位置"></el-table-column>
-            <el-table-column prop="psCode" align="center" width="100" label="工艺标识"></el-table-column>
-          </el-table>
-        </div>
-      </Panel>
-    </div>
-  </div>
-</template>
-<script>
-import {listAlarmInfo, updateAlarmInfo} from '@/api/alarm/alarm-info.js'
-import {getByCondition, getDeviceDetail, getObjAttr, getByFlowRel, getNewIndex, listByDevice} from '@/api/device/device.js'
-export default {
-  name: 'Powerdist',
-  data () {
-    return {
-      transformer: 1,
-      transformerOptions: [],
-      transformerInfo: {},
-      transformerAlarmData: [
-        {
-          name: '变压器油温值:90.3℃',
-          type: '过温预警',
-          createTime: '08-25 17:51:35'
-        },
-        {
-          name: '变压器油温值:90.3℃',
-          type: '过温预警',
-          createTime: '08-25 17:51:35'
-        },
-        {
-          name: '变压器油温值:90.3℃',
-          type: '过温预警',
-          createTime: '08-25 17:51:35'
-        },
-        {
-          name: '变压器油温值:90.3℃',
-          type: '过温预警',
-          createTime: '08-25 17:51:35'
-        },
-        {
-          name: '变压器油温值:90.3℃',
-          type: '过温预警',
-          createTime: '08-25 17:51:35'
-        },
-        {
-          name: '变压器油温值:90.3℃',
-          type: '过温预警',
-          createTime: '08-25 17:51:35'
-        }
-      ],
-      powerAlarmData: [],
-      powerCode: '',
-      powerList: [],
-      powerInfo: {},
-      eqptList: []
-    }
-  },
-  components: {
-    Panel: () => import('./components/panel'),
-    alarm: () => import('./components/alarm.vue')
-  },
-  mounted () {
-    this.getTransformerList()
-  },
-  methods: {
-    getPowerAlarmList () {
-      listAlarmInfo({
-        objType: 4,
-        objCode: this.powerCode,
-        alarmStateList: [0, 1]
-      }).then(({rows}) => {
-        this.powerAlarmData = rows.map(item => ({
-          ...item,
-          name: item.objName,
-          type: item.alarmMsg,
-          createTime: item.alarmTime
-        }))
-      })
-    },
-    getPowerList () {
-      getByFlowRel({
-        upstreamObjType: 2,
-        upstreamObjCode: this.transformer,
-        deviceSubCategory: 'W2',
-        psCode: 'AP'
-      }).then(({data}) => {
-        this.powerList = data || []
-        if (data.length) {
-          this.powerCode = data[0].deviceCode
-        }
-        this.powerChange()
-      })
-    },
-    getTransformerList () {
-      getByCondition({
-        deviceSubCategory: 'W2',
-        psCode: 'T'
-      }).then(({code, data}) => {
-        if (code === 200) {
-          this.transformerOptions = data
-          if (data && data.length) {
-            this.transformer = data[0].deviceCode
-          }
-          this.getTransformerInfo()
-        }
-      })
-    },
-    getTransformerInfo () {
-      getDeviceDetail({deviceCode: this.transformer}).then(({code, data}) => {
-        if (code === 200) {
-          this.transformerInfo = data
-          this.getTransformerAttr()
-          this.getPowerList()
-        }
-      })
-    },
-    getTransformerAttr () {
-      getObjAttr({
-        objType: 2,
-        objCode: this.transformer
-      }).then(({code, data}) => {
-        if (code === 200) {
-          if (data.attrs && data.attrs.length) {
-            const attrValuesObj = {};
-            (data.attrValues || []).forEach(item => {
-              attrValuesObj[item.attrKey] = item.attrValue
-            })
-            data.attrs.forEach((item, index) => {
-              this.$set(this.transformerInfo, item.attrKey, (attrValuesObj[item.attrKey] || '--') + item.attrUnit)
-            })
-          }
-        }
-      })
-    },
-    getPowerInfo () {
-      const [{deviceCode, areaCode}] = this.powerList.filter(item => item.deviceCode === this.powerCode)
-      getNewIndex({
-        areaCode: areaCode,
-        objType: '2',
-        objCode: deviceCode
-      }).then(({code, data}) => {
-        if (code === 200) {
-          this.powerInfo = data || {}
-        }
-      })
-    },
-    getEqptList () {
-      getByFlowRel({
-        upstreamObjType: 2,
-        upstreamObjCode:  this.powerCode,
-        deviceSubCategory: 'W2',
-      }).then(({ code, data }) => {
-        if (code === 200) {
-          this.eqptList = data || []
-        }
-      })
-    },
-    powerChange () {
-      this.getPowerInfo()
-      this.getPowerAlarmList()
-      this.getEqptList()
-    },
-    handlerPowerAlarm (row) {
-      updateAlarmInfo({
-        id: row.id,
-        alarmState: '2'
-      }).then(({code}) => {
-        if (code === 200) {
-          this.$modal.msgSuccess('处理成功')
-          this.getPowerAlarmList()
-        } else {
-          this.$modal.msgError('处理失败')
-        }
-      })
-    }
-  }
-}
-</script>
-<style lang='scss' scoped>
-.powerdist {
-  background: #f6f7f9;
-  padding: 10px;
-  min-height: inherit;
-  display: flex;
-  flex-direction: column;
-
-  .powerdist-header {
-    background: linear-gradient(180deg, #f1f5ff 0%, #ffffff 100%);
-    border-radius: 8px;
-    border: 3px solid #ffffff;
-    padding: 20px;
-
-    >div:first-child {
-      font-weight: 500;
-      font-size: 20px;
-      color: #212121;
-    }
-
-    .powerdist-env {
-      display: flex;
-      color: #515a6e;
-      font-size: 14px;
-      justify-content: space-evenly;
-
-      >div {
-        display: flex;
-        align-items: flex-end;
-
-        img {
-          margin-right: 5px;
-          height: 30px;
-        }
-
-        >div {
-          display: flex;
-          flex-direction: column;
-          font-weight: 500;
-          font-size: 18px;
-          color: #405383;
-
-          span:first-child {
-            font-weight: 400;
-            font-size: 14px;
-            color: #848ea6;
-            margin-bottom: 5px;
-          }
-        }
-      }
-    }
-  }
-
-  .powerdist-body {
-    flex: 1;
-    margin-top: 10px;
-    display: flex;
-
-    >div {
-      flex: 1;
-
-      &:not(:first-child) {
-        margin-left: 10px;
-      }
-    }
-
-    .transformer {
-      padding: 10px;
-
-      .transformer-info {
-        text-align: center;
-        margin-top: 50px;
-        position: relative;
-      }
-
-      .transformerImg {
-        height: 150px;
-      }
-
-      .lineImg {
-        position: absolute;
-        width: 275px;
-        right: -38px;
-        top: -20px;
-      }
-
-      .arrowImg {
-        position: absolute;
-        left: 5%;
-        top: 25%;
-
-        img {
-          width: 80px;
-        }
-
-        span {
-          position: absolute;
-          top: 10px;
-          left: 10px;
-        }
-      }
-
-      .info-header {
-        position: absolute;
-        top: -20px;
-        left: 50%;
-        transform: translateX(-50%);
-        font-size: 14px;
-
-        span {
-          margin-left: 5px;
-          font-weight: bold;
-        }
-      }
-
-      .info-right {
-        position: absolute;
-        right: 0;
-        top: 50%;
-        transform: translateY(-50%);
-        font-size: 14px;
-
-        >div {
-          display: flex;
-          align-items: center;
-          margin-top: 5px;
-        }
-      }
-    }
-
-    .custom-desc {
-      ::v-deep .el-descriptions__header {
-        margin-bottom: 5px;
-        font-weight: 600;
-        font-size: 14px;
-        color: #212121;
-      }
-
-      ::v-deep .el-descriptions-item__label {
-        background: #f4f5fa;
-        font-weight: 400;
-        font-size: 12px;
-        color: #212121;
-      }
-    }
-
-    .power {
-      padding: 10px;
-
-      .power-info {
-        display: flex;
-        justify-content: space-between;
-        margin-top: 10px;
-        padding: 0 10px;
-
-        >div {
-          flex: 1;
-          background: url('~@/assets/images/mgr/backA.svg') no-repeat;
-          background-position: center;
-          display: flex;
-          flex-direction: column;
-          justify-content: center;
-          height: 100px;
-          text-align: center;
-          font-weight: 400;
-          font-size: 12px;
-          color: #6a6a6a;
-
-          &:nth-child(2) {
-            background: url('~@/assets/images/mgr/backB.svg') no-repeat;
-            background-position: center;
-            background-size: 70%;
-          }
-
-          &:nth-child(3) {
-            background: url('~@/assets/images/mgr/backC.svg') no-repeat;
-            background-position: center;
-            background-size: 70%;
-          }
-
-          span:last-of-type {
-            margin-top: 5px;
-            font-size: 13px;
-            color: #212121;
-          }
-        }
-      }
-
-      .power-data {
-        margin-top: 20px;
-
-        .power-data-title {
-          background: #f4f5fa;
-          padding: 8px 10px;
-          font-weight: 600;
-          font-size: 14px;
-          color: #212121;
-        }
-
-        .power-data-body {
-          border: 1px solid #f0f1f2;
-          display: flex;
-          align-items: center;
-          padding: 10px 20px;
-          text-align: center;
-
-          >div:last-of-type {
-            flex: 1;
-            margin-left: 30px;
-          }
-
-          .power-data-row {
-            display: flex;
-            justify-content: space-between;
-            margin-top: 20px;
-
-            >div {
-              display: flex;
-              flex-direction: column;
-            }
-          }
-
-          .data-name {
-            font-weight: 400;
-            font-size: 13px;
-            color: #212121;
-          }
-
-          .data-num {
-            margin-top: 4px;
-            font-weight: 400;
-            font-size: 13px;
-            color: #6a6a6a;
-          }
-        }
-      }
-    }
-
-    //.switch {
-    //  padding: 10px;
-    //  position: relative;
-    //
-    //  .rightArrow {
-    //    position: absolute;
-    //    top: 65px;
-    //    left: -20px;
-    //    z-index: 999;
-    //  }
-    //}
-  }
-}
-</style>