Jelajahi Sumber

设备告警改造

learshaw 4 bulan lalu
induk
melakukan
352c9b4f33

+ 64 - 29
ems-ui-cloud/src/api/alarm/alarm-info.js

@@ -1,4 +1,3 @@
-import {get} from '@/api/commonApi';
 import request from '@/utils/request';
 
 // 查询能源设施告警列表
@@ -44,40 +43,76 @@ export function delAlarmInfo(id) {
   })
 }
 
-export const fetchAlarmIndex = (params) => {
-  return get('/alarm-info/alarm/type/index', params);
-};
+// ===== 统计接口 - 改用 request 方法 =====
 
-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 function fetchAlarmIndex(params) {
+  return request({
+    url: '/ems/alarm-info/alarm/type/index',
+    method: 'get',
+    params: params
+  })
+}
 
+export function fetchAlarmIndexDay(params) {
+  return request({
+    url: '/ems/alarm-info/alarm/type/index/day',
+    method: 'get',
+    params: params
+  })
+}
 
-export const fetchAlarmIndexYear = (params) => {
-  return get('/alarm-info/alarm/type/index/year', params);
-};
+export function fetchAlarmIndexMonth(params) {
+  return request({
+    url: '/ems/alarm-info/alarm/type/index/month',
+    method: 'get',
+    params: params
+  })
+}
 
-export const fetchSubSysIndexDay = (params) => {
-  return get('/alarm-info/subsys/index/day', params);
-};
+export function fetchAlarmIndexYear(params) {
+  return request({
+    url: '/ems/alarm-info/alarm/type/index/year',
+    method: 'get',
+    params: params
+  })
+}
 
-export const fetchSubSysIndexMonth = (params) => {
-  return get('/alarm-info/subsys/index/month', params);
-};
+export function fetchSubSysIndexDay(params) {
+  return request({
+    url: '/ems/alarm-info/subsys/index/day',
+    method: 'get',
+    params: params
+  })
+}
 
+export function fetchSubSysIndexMonth(params) {
+  return request({
+    url: '/ems/alarm-info/subsys/index/month',
+    method: 'get',
+    params: params
+  })
+}
 
-export const fetchSubSysIndexYear = (params) => {
-  return get('/alarm-info/subsys/index/year', params);
-};
+export function fetchSubSysIndexYear(params) {
+  return request({
+    url: '/ems/alarm-info/subsys/index/year',
+    method: 'get',
+    params: params
+  })
+}
 
-export const fetchCntHandled = (params) => {
-  return get('/alarm-info/cnt/handled', params);
-};
+export function fetchCntHandled(params) {
+  return request({
+    url: '/ems/alarm-info/cnt/handled',
+    method: 'get',
+    params: params
+  })
+}
 
-export const fetchCntDateAlarmType = (params) => {
-  return get('/alarm-info/cnt/date/alarm/type', params);
-};
+export function fetchCntDateAlarmType(params) {
+  return request({
+    url: '/ems/alarm-info/cnt/date/alarm/type',
+    method: 'get',
+    params: params
+  })
+}

+ 0 - 10
ems-ui-cloud/src/api/device/device.js

@@ -117,16 +117,6 @@ export function getNewIndex(params) {
   })
 }
 
-//查询设备下的设备部件
-export function listByDevice(params) {
-  return request({
-    url: '/ems/device/component/listByDevice',
-    method: 'get',
-    params
-  })
-}
-
-
 export function listDeviceType(params) {
   return request({
     url: '/ems/device/type/online',

+ 10 - 10
ems-ui-cloud/src/views/adapter/nhjc/index.vue

@@ -316,6 +316,7 @@
         </el-tab-pane>
 
         <!-- 楼控设备标签页 - 已改造 -->
+        <!-- 楼控设备标签页 - 已改造 -->
         <el-tab-pane label="楼控设备" name="baDevices">
           <div class="tab-content">
             <!-- 设备类型选择 -->
@@ -340,7 +341,7 @@
               stripe
               v-loading="baDeviceLoading"
               style="margin-top: 15px;">
-              <el-table-column type="expand">
+              <el-table-column type="expand" width="50">
                 <template slot-scope="props">
                   <div class="ba-device-detail">
                     <el-tabs v-model="props.row.detailTab">
@@ -364,8 +365,8 @@
                           border
                           size="small"
                           v-if="getBaDeviceAttrs(props.row.deviceCode, 'State').length > 0">
-                          <el-table-column prop="attrName" label="属性名称" width="180"></el-table-column>
-                          <el-table-column label="属性值" width="150">
+                          <el-table-column prop="attrName" label="属性名称" width="200"></el-table-column>
+                          <el-table-column label="属性值" min-width="180">
                             <template slot-scope="scope">
                               <el-tag v-if="scope.row.attrValueType === 'Enum' && scope.row.attrValueName" size="small">
                                 {{ scope.row.attrValueName }}
@@ -375,8 +376,7 @@
                               </span>
                             </template>
                           </el-table-column>
-                          <el-table-column prop="attrUnit" label="单位" width="100"></el-table-column>
-                          <el-table-column prop="updateTime" label="更新时间" width="160"></el-table-column>
+                          <el-table-column prop="updateTime" label="更新时间" width="180"></el-table-column>
                         </el-table>
                         <el-empty v-else description="暂无状态数据"></el-empty>
                       </el-tab-pane>
@@ -384,10 +384,10 @@
                   </div>
                 </template>
               </el-table-column>
-              <el-table-column prop="deviceCode" label="设备代码" width="180"></el-table-column>
-              <el-table-column prop="deviceName" label="设备名称" min-width="200"></el-table-column>
-              <el-table-column prop="location" label="安装位置" min-width="150"></el-table-column>
-              <el-table-column prop="areaCode" label="区域" width="100">
+              <el-table-column prop="deviceCode" label="设备代码" width="160" show-overflow-tooltip></el-table-column>
+              <el-table-column prop="deviceName" label="设备名称" width="200" show-overflow-tooltip></el-table-column>
+              <el-table-column prop="location" label="安装位置" width="180" show-overflow-tooltip></el-table-column>
+              <el-table-column prop="areaCode" label="区域" width="100" align="center">
                 <template slot-scope="scope">
                   {{ scope.row.areaCode && scope.row.areaCode.includes('3001') ? '北区' : '南区' }}
                 </template>
@@ -400,7 +400,7 @@
                 </template>
               </el-table-column>
               <!-- 新增操作列 -->
-              <el-table-column label="操作" width="200" align="center">
+              <el-table-column label="操作" min-width="220" align="center">
                 <template slot-scope="scope">
                   <!-- 获取当前设备模型的能力列表 -->
                   <template v-if="getBaDeviceAbilities(scope.row.deviceModel).length > 0">

+ 0 - 1
ems-ui-cloud/src/views/devmgr/warn/DevcWarning/img/icon_waring.svg

@@ -1 +0,0 @@
-<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1723023867267" class="icon" viewBox="0 0 1031 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10780" xmlns:xlink="http://www.w3.org/1999/xlink" width="48.328125" height="48"><path d="M518.4 678.4c-19.2 0-32-12.8-32-32L486.4 384c0-19.2 12.8-32 32-32S550.4 364.8 550.4 384l0 262.4C550.4 659.2 537.6 678.4 518.4 678.4z" fill="#ffffff" p-id="10781"></path><path d="M518.4 774.4m-44.8 0a0.7 0.7 0 1 0 89.6 0 0.7 0.7 0 1 0-89.6 0Z" fill="#ffffff" p-id="10782"></path><path d="M992 960C992 960 992 960 992 960l-960 0c-12.8 0-19.2-6.4-25.6-19.2-6.4-12.8-6.4-19.2 0-32l480-832c12.8-19.2 44.8-19.2 57.6 0l480 825.6c6.4 6.4 6.4 12.8 6.4 19.2C1024 947.2 1011.2 960 992 960zM89.6 896l851.2 0L512 160 89.6 896z" fill="#ffffff" p-id="10783"></path></svg>

+ 0 - 61
ems-ui-cloud/src/views/devmgr/warn/DevcWarning/index.scss

@@ -1,61 +0,0 @@
-@import "src/assets/styles";
-
-.device-waring-container {
-  flex-grow: 1;
-  width: 100%;
-
-  .index-content {
-    display: flex;
-    align-items: center;
-    justify-content: center;
-
-    .index-icon {
-      width: 50px;
-      height: 50px;
-      border-radius: 50px;
-      background: url(./img/icon_waring.svg) no-repeat 50% 40%;
-      background-size: 50%;
-      margin-right: 14px;
-
-      &.index-emergent {
-        background-color: rgba(255, 0, 0, 1);
-      }
-
-      &.index-minor {
-        background-color: rgba(255, 165, 0, 1);
-      }
-
-      &.index-warning {
-        background-color: rgba(255, 165, 0, 1);
-      }
-
-      &.index-important {
-        background-color: rgb(255, 78, 78);
-      }
-    }
-
-    .index-info {
-      display: flex;
-      align-items: center;
-      flex-direction: column;
-      justify-content: space-between;
-
-      .index-label {
-        font-family: 'PingFangSC-Regular', 'PingFang SC', sans-serif;
-        color: #515a6e;
-        font-size: 20px;
-        margin-bottom: 10px;
-      }
-
-      .index {
-        font-size: 10px;
-
-        .index-num {
-          font-size: 20px;
-          margin-right: 10px;
-        }
-      }
-    }
-  }
-}
-

+ 0 - 120
ems-ui-cloud/src/views/devmgr/warn/DevcWarning/index.vue

@@ -1,120 +0,0 @@
-<template>
-  <Block title="告警总数">
-    <template v-slot:main>
-      <div class="device-waring-container">
-        <el-row type="flex" style="height: 50%">
-          <el-col class="flex-col" :span="12">
-            <div class="index-content">
-              <div class="index-icon index-emergent"></div>
-              <div class="index-info">
-                <div class="index-label">
-                  <dict-tag :options="dict.type.alarm_type" value="3"></dict-tag>
-                </div>
-                <div class="index">
-                  <span class="index-num">{{ alarmIndex['3'] || 0 }}</span>
-                  次
-                </div>
-              </div>
-            </div>
-          </el-col>
-          <el-col class="flex-col" :span="12">
-            <div class="index-content">
-              <div class="index-icon index-important"></div>
-              <div class="index-info">
-                <div class="index-label">
-                  <dict-tag :options="dict.type.alarm_type" value="2"></dict-tag>
-                </div>
-                <div class="index">
-                  <span class="index-num">{{ alarmIndex['2'] || 0 }}</span>
-                  次
-                </div>
-              </div>
-            </div>
-          </el-col>
-        </el-row>
-        <el-row type="flex" style="height: 50%">
-          <el-col class="flex-col" :span="12">
-            <div class="index-content">
-              <div class="index-icon index-minor"></div>
-              <div class="index-info">
-                <div class="index-label">
-                  <dict-tag :options="dict.type.alarm_type" value="1"></dict-tag>
-                </div>
-                <div class="index">
-                  <span class="index-num">{{ alarmIndex['1'] || 0 }}</span>
-                  次
-                </div>
-              </div>
-            </div>
-          </el-col>
-          <el-col class="flex-col" :span="12">
-            <div class="index-content">
-              <div class="index-icon index-warning"></div>
-              <div class="index-info">
-                <div class="index-label">
-                  <dict-tag :options="dict.type.alarm_type" value="5"></dict-tag>
-                </div>
-                <div class="index">
-                  <span class="index-num">{{ alarmIndex['5'] || 0 }}</span>
-                  次
-                </div>
-              </div>
-            </div>
-          </el-col>
-        </el-row>
-      </div>
-    </template>
-  </Block>
-</template>
-
-<script>
-import {fetchAlarmIndex, fetchCntDateAlarmType} from '@/api/alarm/alarm-info';
-import {ApiCode} from '@/api/apiEmums';
-import Block from '@/components/Block/block.vue';
-
-export default {
-  components: {Block},
-  props: {
-    areaCode: {
-      type: String,
-      default: '',
-    },
-  },
-  name: 'DeviceWaring',
-  dicts: ['alarm_type'],
-  data() {
-    return {
-      alarmIndex: {},
-    };
-  },
-  computed: {},
-  watch: {
-    areaCode(val) {
-      this.initData();
-    },
-  },
-  mounted() {
-    this.initData();
-  },
-  created() {
-  },
-  methods: {
-    async initData() {
-      const {
-        code,
-        data,
-      } = await fetchCntDateAlarmType({
-        areaCode: this.areaCode,
-      });
-      let result = {};
-      if (ApiCode.SUCCESS === code && data && data.length > 0) {
-        data.forEach(item => {
-          result[item.alarmType] = item.cnt;
-        });
-      }
-      this.alarmIndex = result;
-    },
-  },
-};
-</script>
-<style src="./index.scss" lang="scss"/>

+ 625 - 173
ems-ui-cloud/src/views/devmgr/warn/index.vue

@@ -1,226 +1,678 @@
 <template>
-  <div class="app-container power-index-content">
-    <el-row type="flex" :gutter="20">
-      <el-col :span="24">
-        <div class="gl-filters">
-          <SwitchTag
-              :ds="areaTag"
-              :def-tag="defArea"
-              :tagClick="onSwitchTagClick"
+  <div class="app-container device-alarm-content">
+    <el-row :gutter="10">
+      <!-- 左侧区域树 -->
+      <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"
+          />
+        </div>
+        <div class="head-container">
+          <el-tree
+            ref="tree"
+            :data="areaOptions"
+            :default-expand-all="true"
+            :expand-on-click-node="false"
+            :filter-node-method="filterNode"
+            node-key="id"
+            highlight-current
+            @node-click="handleNodeClick"
           />
         </div>
       </el-col>
-    </el-row>
-    <el-row type="flex" :gutter="20" style="margin-top: 20px">
-      <el-col :span="12">
-        <BarChartBlock
-            title="设备数量统计"
-            :opt-cfg="devcNum"
-        />
-      </el-col>
-      <el-col :span="12">
-        <PieChartBlock title="设备状态统计" :opt-cfg="devcStat"></PieChartBlock>
-      </el-col>
-    </el-row>
-    <el-row type="flex" :gutter="20" style="margin-top: 20px">
-      <el-col :span="12">
-        <PieChartBlock title="供应商占比" :opt-cfg="supplyerIndex"></PieChartBlock>
-      </el-col>
-      <el-col :span="12">
-        <LineChartBlock title="故障发生情况" :opt-cfg="devcFault" />
+
+      <!-- 右侧内容区 -->
+      <el-col :span="20" :xs="24">
+        <!-- KPI卡片区 -->
+        <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>
+            </div>
+          </el-col>
+        </el-row>
+
+        <!-- 第一行图表 -->
+        <el-row :gutter="20" style="margin-top: 20px">
+          <el-col :span="12">
+            <PieChartBlock title="设备状态分布" :opt-cfg="deviceStatusChart" />
+          </el-col>
+          <el-col :span="12">
+            <LineChartBlock title="告警趋势分析" :opt-cfg="alarmTrendChart">
+              <template v-slot:filters>
+                <SwitchTag
+                  :ds="dateTypeOptions"
+                  :defTag="dateType"
+                  :tagClick="onDateTypeSwitch"
+                />
+              </template>
+            </LineChartBlock>
+          </el-col>
+        </el-row>
+
+        <!-- 第二行 -->
+        <el-row :gutter="20" style="margin-top: 20px">
+          <el-col :span="14">
+            <BlockTable title="实时告警列表" :table-data="realTimeAlarmData">
+              <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">
+                  <template slot-scope="scope">
+                    <el-tag :type="getAlarmTypeTag(scope.row.alarmType)" size="small">
+                      {{ getAlarmTypeName(scope.row.alarmType) }}
+                    </el-tag>
+                  </template>
+                </el-table-column>
+                <el-table-column label="状态" align="center" width="100">
+                  <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) }}
+                        </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>
+                  </template>
+                </el-table-column>
+              </template>
+            </BlockTable>
+          </el-col>
+          <el-col :span="10">
+            <PieChartBlock title="告警类型分布" :opt-cfg="alarmTypeChart" />
+          </el-col>
+        </el-row>
       </el-col>
     </el-row>
   </div>
 </template>
 
 <script>
-
-import { ApiCode } from '@/api/apiEmums';
-import { get } from '@/api/commonApi';
-import BarChartBlock from '@/components/Block/charts/BarChartBlock.vue';
+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 * as areaApi from '../../../api/basecfg/area';
-import LineChartBlock from '../../../components/Block/charts/LineChartBlock.vue';
-import SwitchTag from '../../../components/SwitchTag/index.vue';
+import dayjs from 'dayjs';
 
 export default {
+  name: 'DeviceAlarm',
   components: {
-    BarChartBlock,
-    PieChartBlock,
+    BlockTable,
     LineChartBlock,
+    PieChartBlock,
     SwitchTag,
   },
   data() {
     return {
-      areaTag: [],
-      defArea: {},
-      devcStat: {
+      // 区域筛选
+      areaName: '',
+      areaOptions: [],
+      areaCode: '', // 默认为空字符串,表示全部
+
+      // 时间维度
+      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: [
-              {
-                value: 8,
-                name: '故障',
-              },
-              {
-                value: 75,
-                name: '运行',
-              },
-              {
-                value: 2,
-                name: '未运行',
-              },
-            ],
+            data: [],
           },
         ],
       },
-      supplyerIndex: {
-        series: [
-          {
-            type: 'pie',
-            radius: ['0%', '70%'],
-            data: [
-              {
-                value: 7,
-                name: '西门子',
-              },
-              {
-                value: 6,
-                name: '施耐德',
-              },
-              {
-                value: 9,
-                name: '通用电器',
-              },
-              {
-                value: 14,
-                name: '飞利浦',
-              },
-              {
-                value: 18,
-                name: '霍尼韦尔',
-              },
-            ],
-          },
-        ],
-      },
-      devcNum: {
-        categories: ['空调', '灯具', '杀菌设备', '出纳机', '感应器'],
-        series: [
-          {
-            name: '故障',
-            data: [1, 0, 0, 1, 1],
-          },
-          {
-            name: '运行',
-            data: [20, 109, 20, 20, 10],
-          },
-          {
-            name: '未运行',
-            data: [1, 2, 4, 0, 1],
-          },
-        ],
-      },
-      devcFault: {
-        unit:"  ",
+
+      // 告警趋势
+      alarmTrendChart: {
+        unit: '',
         xAxis: {
           type: 'category',
-          data: [
-            '1月',
-            '2月',
-            '03月',
-            '04月',
-            '05月',
-            '06月',
-            '07月',
-            '08月',
-          ],
+          data: [],
         },
+        series: [],
+      },
+
+      // 告警类型分布
+      alarmTypeChart: {
         series: [
           {
-            data: [
-              73, 57, 37, 40, 37, 40, 73, 57,
-            ],
-            type: 'line',
-            areaStyle: {
-              color: '#d7e4fc',
-              emphasis: {
-                color: '#6093f5',
-              },
-            },
+            type: 'pie',
+            radius: ['0%', '70%'],
+            data: [],
           },
         ],
       },
+
+      // 实时告警数据
+      realTimeAlarmData: [],
     };
   },
-  mounted() {
-    this.initData();
+  watch: {
+    areaName(val) {
+      this.$refs.tree.filter(val);
+    },
   },
-  created() {
+  mounted() {
+    this.getAreaTree();
   },
   methods: {
-    onSwitchTagClick(item) {
-      console.log(item);
-    },
-    async initData() {
-      const {
-        rows,
-        total,
-      } = await areaApi.listArea({
-        pageNum: 1,
-        pageSize: 10,
-      });
-      if (rows.length > 0) {
-        rows.forEach(item => {
-          this.areaTag.push({
-            val: item.areaCode,
-            text: item.areaName,
-          });
+    // 获取区域树
+    async getAreaTree() {
+      try {
+        const response = await areaTreeSelect('0', 1);
+        this.areaOptions = [
+          {
+            id: '',
+            label: '全部',
+            children: [],
+          },
+        ].concat(response.data || []);
+
+        // 区域树加载完成后再加载数据
+        this.$nextTick(() => {
+          this.loadAllData();
         });
-        this.defArea = this.areaTag[0];
+      } catch (error) {
+        console.error('获取区域树失败:', error);
+        this.$message.error('获取区域树失败');
       }
-      this.queryCharts();
     },
-    queryCharts() {
-      this.getPvList();
+
+    // 筛选节点
+    filterNode(value, data) {
+      if (!value) return true;
+      return data.label.indexOf(value) !== -1;
     },
-    async getPvList() {
-      const {
-        data,
-        code,
-      } = await get('/prod/list/prod/this/day/index', {
-        areaCode: this.defArea.val,
-      });
-      const result = {};
-      if (ApiCode.SUCCESS !== code || !data || data.length < 1) {
-        return null;
+
+    // 区域切换
+    handleNodeClick(data) {
+      this.areaCode = data.id;
+      this.loadAllData();
+    },
+
+    // 时间维度切换
+    async onDateTypeSwitch(item) {
+      this.dateType = item;
+      await this.loadAlarmTrend();
+    },
+
+    // 构建查询参数
+    buildQueryParams(extraParams = {}) {
+      const params = {
+        areaCode: this.areaCode || '', // 总是传递 areaCode,空字符串表示全部
+        ...extraParams
+      };
+      return params;
+    },
+
+    // 加载所有数据
+    async loadAllData() {
+      try {
+        await Promise.all([
+          this.loadKpiData(),
+          this.loadDeviceStatus(),
+          this.loadAlarmTrend(),
+          this.loadRealTimeAlarm(),
+          this.loadAlarmTypeDistribution(),
+        ]);
+      } catch (error) {
+        console.error('加载数据失败:', error);
       }
-      const xAxis = DateTool.getTime(60);
-      data.forEach(item => {
-        const {
-          elecQuantity,
-          timeIndex,
-        } = item;
-        result[timeIndex] = {
-          elecQuantity,
+    },
+
+    // 加载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',
+          },
+        ];
+      } 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: '离线' },
+              ],
+            },
+          ],
         };
-      });
-      const chartData = [];
-      xAxis.forEach((item, index) => {
-        const timeIndex = index + 1;
-        if (result[timeIndex]) {
-          chartData.push(result[timeIndex].elecQuantity);
+      } catch (error) {
+        console.error('加载设备状态失败:', error);
+      }
+    },
+
+    // 加载告警趋势
+    async loadAlarmTrend() {
+      try {
+        let xAxis = [];
+        let data = [];
+
+        const params = this.buildQueryParams();
+
+        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 {
-          chartData.push(0);
+          const res = await fetchAlarmIndexYear(params);
+          data = res.data || [];
+          xAxis = DateTool.getMonthsOfYearAgo();
         }
+
+        const series = this.transformAlarmTrendData(data, xAxis);
+
+        this.alarmTrendChart = {
+          unit: '',
+          xAxis: {
+            type: 'category',
+            data: xAxis,
+          },
+          series,
+        };
+      } catch (error) {
+        console.error('加载告警趋势失败:', error);
+      }
+    },
+
+    // 转换告警趋势数据
+    transformAlarmTrendData(data, xAxis) {
+      const dayGroup = _.groupBy(data, 'alarmType');
+      const series = [];
+
+      const alarmTypeMap = {
+        1: '一般告警',
+        2: '重要告警',
+        3: '紧急告警',
+        4: '恢复告警',
+        5: '诊断告警',
+        6: '其他告警',
+      };
+
+      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] || '未知',
+          type: 'line',
+          smooth: true,
+          data: seriesData,
+        });
       });
-      this.pvData.xAxis.data = xAxis;
-      this.pvData.series[0].data = chartData;
+
+      return series;
+    },
+
+    // 加载实时告警
+    async loadRealTimeAlarm() {
+      try {
+        const { rows } = await listAlarmInfo(
+          this.buildQueryParams({
+            pageNum: 1,
+            pageSize: 10,
+            alarmStateList: [0, 1],
+          })
+        );
+
+        this.realTimeAlarmData = rows || [];
+      } catch (error) {
+        console.error('加载实时告警失败:', error);
+      }
+    },
+
+    // 加载告警类型分布
+    async loadAlarmTypeDistribution() {
+      try {
+        const endTime = dayjs().format('YYYY-MM-DD HH:mm:ss');
+        const startTime = dayjs().startOf('year').format('YYYY-MM-DD HH:mm:ss'); // 当年1月1日 00:00:00
+
+        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,
+            },
+          ],
+        };
+      } catch (error) {
+        console.error('加载告警类型分布失败:', error);
+      }
+    },
+
+    // 告警状态变更
+    async handleAlarmStateChange(command, row) {
+      try {
+        await updateAlarmInfo({
+          id: row.id,
+          alarmState: command,
+        });
+
+        this.$message.success('告警状态更新成功');
+        await this.loadRealTimeAlarm();
+        await this.loadKpiData();
+      } catch (error) {
+        console.error('更新告警状态失败:', error);
+        this.$message.error('告警状态更新失败');
+      }
+    },
+
+    // 获取告警类型名称
+    getAlarmTypeName(type) {
+      const typeMap = {
+        1: '一般',
+        2: '重要',
+        3: '紧急',
+        4: '恢复',
+        5: '诊断',
+        6: '其他',
+      };
+      return typeMap[type] || '未知';
+    },
+
+    // 获取告警类型标签
+    getAlarmTypeTag(type) {
+      const tagMap = {
+        1: 'info',
+        2: 'warning',
+        3: 'danger',
+        4: 'success',
+        5: '',
+        6: 'info',
+      };
+      return tagMap[type] || 'info';
+    },
+
+    // 获取告警状态名称
+    getAlarmStateName(state) {
+      const stateMap = {
+        0: '新增',
+        1: '处理中',
+        2: '已处置',
+        3: '已消散',
+      };
+      return stateMap[state] || '未知';
+    },
+
+    // 获取告警状态标签
+    getAlarmStateTag(state) {
+      const tagMap = {
+        0: 'danger',
+        1: 'warning',
+        2: 'success',
+        3: 'info',
+      };
+      return tagMap[state] || 'info';
     },
   },
 };
 </script>
-<style src="./index.scss" lang="scss" />
+
+<style scoped lang="scss">
+@import '@/assets/styles/variables.scss';
+
+.device-alarm-content {
+  background: #fff;
+  padding: 20px;
+
+  // KPI卡片样式
+  .kpi-cards {
+    .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-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-content {
+        flex: 1;
+
+        .kpi-value {
+          font-size: 28px;
+          font-weight: bold;
+          line-height: 1.2;
+          margin-bottom: 5px;
+        }
+
+        .kpi-label {
+          font-size: 14px;
+          color: #666;
+        }
+      }
+
+      &.kpi-primary {
+        .kpi-icon {
+          background: rgba(64, 158, 255, 0.1);
+          color: #409eff;
+        }
+
+        .kpi-value {
+          color: #409eff;
+        }
+      }
+
+      &.kpi-success {
+        .kpi-icon {
+          background: rgba(103, 194, 58, 0.1);
+          color: #67c23a;
+        }
+
+        .kpi-value {
+          color: #67c23a;
+        }
+      }
+
+      &.kpi-danger {
+        .kpi-icon {
+          background: rgba(245, 108, 108, 0.1);
+          color: #f56c6c;
+        }
+
+        .kpi-value {
+          color: #f56c6c;
+        }
+      }
+
+      &.kpi-warning {
+        .kpi-icon {
+          background: rgba(230, 162, 60, 0.1);
+          color: #e6a23c;
+        }
+
+        .kpi-value {
+          color: #e6a23c;
+        }
+      }
+    }
+  }
+
+  // 区域树样式
+  .head-container {
+    background: #f5f7fa;
+    padding: 10px;
+    border-radius: 4px;
+  }
+
+  // 下拉链接样式
+  .el-dropdown-link {
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+}
+</style>

+ 0 - 307
ems-ui-cloud/src/views/devmgr/warn/warn.vue

@@ -1,307 +0,0 @@
-<template>
-  <div class="app-container power-index-content">
-    <el-row :gutter="10">
-      <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"/>
-        </div>
-        <div class="head-container">
-          <el-tree ref="tree" :data="areaOptions" :default-expand-all="true" :expand-on-click-node="false"
-                   :filter-node-method="filterNode" node-key="id" highlight-current @node-click="handleNodeClick"/>
-        </div>
-      </el-col>
-      <el-col :span="20" :xs="24">
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <DeviceWaring :areaCode="areaCode"/>
-          </el-col>
-          <el-col :span="12">
-            <LineChartBlock title="历史告警变化" :opt-cfg="devcWarningHis">
-              <template v-slot:filters>
-                <SwitchTag
-                    :ds="[{ val: 'year', text: '按年' }, { val: 'month', text: '按月' }, { val: 'day', text: '按日' }]"
-                    :defTag="dateType" :tagClick="onDateTypeSwitch"/>
-              </template>
-            </LineChartBlock>
-          </el-col>
-        </el-row>
-        <el-row type="flex" :gutter="20" style="margin-top: 20px">
-          <el-col :span="12">
-            <BlockTable title="实时告警" :table-data="realTimeTableData">
-              <template v-slot:columns>
-                <el-table-column type="index" label="序号" align="center"/>
-                <el-table-column prop="subSystemName" label="子系统" align="center"/>
-                <el-table-column prop="objName" label="对象名称" align="center"/>
-                <el-table-column prop="alarmMsg" label="描述" align="center"/>
-                <el-table-column prop="alarmTime" label="时间" align="center"/>
-                <el-table-column label="操作" align="center" width="100">
-                  <template slot-scope="scope">
-                    <el-dropdown @command="(cmd) => handleCommand(cmd, scope.row)"
-                                 v-if="![ALARM_STATE.dissolved.value, ALARM_STATE.dissolved.value].includes(scope.row.alarmState)">
-                      <span class="el-dropdown-link">
-                        <dict-tag :options="dict.type.alarm_state" :value="scope.row.alarmState"></dict-tag>
-                        <i class="el-icon-arrow-down el-icon--right"></i>
-                      </span>
-                      <el-dropdown-menu slot="dropdown">
-                        <el-dropdown-item :command="ALARM_STATE.disposing.code"
-                                          v-if="scope.row.alarmState === ALARM_STATE.new.value">开始处理
-                        </el-dropdown-item>
-                        <el-dropdown-item :command="ALARM_STATE.disposed.code">已处理</el-dropdown-item>
-                        <el-dropdown-item :command="ALARM_STATE.dissolved.code">已消散</el-dropdown-item>
-                      </el-dropdown-menu>
-                    </el-dropdown>
-                  </template>
-                </el-table-column>
-              </template>
-            </BlockTable>
-          </el-col>
-          <el-col :span="12">
-            <BarChartBlock title="告警统计报表" :opt-cfg="subSysIndex">
-              <template v-slot:filters>
-                <SwitchTag
-                    :ds="[{ val: 'year', text: '按年' }, { val: 'month', text: '按月' }, { val: 'day', text: '按日' }]"
-                    :defTag="dateType" :tagClick="onSubSysDateTypeSwitch"/>
-              </template>
-            </BarChartBlock>
-          </el-col>
-        </el-row>
-      </el-col>
-    </el-row>
-
-  </div>
-</template>
-
-<script>
-
-import {
-  fetchAlarmIndexDay,
-  fetchAlarmIndexMonth,
-  fetchAlarmIndexYear,
-  fetchSubSysIndexDay,
-  fetchSubSysIndexMonth,
-  fetchSubSysIndexYear,
-  listAlarmInfo,
-  updateAlarmInfo,
-} from '@/api/alarm/alarm-info';
-import {ApiCode} from '@/api/apiEmums';
-import Block from '@/components/Block/block.vue';
-import BlockTable from '@/components/Block/BlockTable/index.vue';
-import BarChartBlock from '@/components/Block/charts/BarChartBlock.vue';
-import PieChartBlock from '@/components/Block/charts/PieChartBlock.vue';
-import {ALARM_STATE} from '@/enums/alarm';
-import {DateTool} from '@/utils/DateTool';
-import DeviceWaring from '@/views/devmgr/warn/DevcWarning/index.vue';
-import dayjs from 'dayjs';
-import {areaTreeSelect} from '@/api/basecfg/area'
-import LineChartBlock from '../../../components/Block/charts/LineChartBlock.vue';
-import SwitchTag from '../../../components/SwitchTag/index.vue';
-
-export default {
-  dicts: ['alarm_type', 'alarm_state'],
-  components: {
-    Tag,
-    BlockTable,
-    DeviceWaring,
-    Block,
-    BarChartBlock,
-    PieChartBlock,
-    LineChartBlock,
-    SwitchTag,
-  },
-  data() {
-    return {
-      startRecTime: '',
-      endRecTime: '',
-      areaName: undefined,
-      areaOptions: [],
-      ALARM_STATE,
-      dateType: {val: 'year'},
-      areaCode: '',
-      devcWarningHis: {
-        unit: '  ',
-        xAxis: {
-          type: 'category',
-          data: [],
-        },
-        series: [],
-      },
-      subSysIndex: {
-        unit: '  ',
-        xAxis: {
-          type: 'category',
-          data: [],
-        },
-        series: [],
-      },
-      realTimeTableData: [],
-    };
-  },
-  watch: {
-    // 根据名称筛选区域树
-    areaName(val) {
-      this.$refs.tree.filter(val)
-    }
-  },
-  async mounted() {
-    await this.getAreaTreeByTag('0', 1)
-    this.queryCharts()
-    this.getRecentSevenDays();
-  },
-  methods: {
-    /**计算近7天的时间范围*/
-    getRecentSevenDays() {
-      const endRecTime = dayjs(); // 当前时间
-      const startRecTime = endRecTime.subtract(7, 'days'); // 7天前的时间
-      this.startRecTime = startRecTime.format('YYYY-MM-DD HH:mm:ss');
-      this.endRecTime = endRecTime.format('YYYY-MM-DD HH:mm:ss');
-    },
-    /** 查询区域树结构 */
-    async getAreaTreeByTag(areaCode, layer) {
-      await areaTreeSelect(areaCode, layer).then(response => {
-        this.areaOptions = [{
-          id: '-1',
-          label: '全部',
-          children: []
-        }].concat(response.data)
-        this.areaCode = '-1'
-      })
-    },
-    // 筛选节点
-    filterNode(value, data) {
-      if (!value) return true
-      return data.label.indexOf(value) !== -1
-    },
-    handleNodeClick(data, node) {
-      this.areaCode = data.id
-      this.queryCharts()
-    },
-    async onDateTypeSwitch(item) {
-      if (item.val === 'day') {
-        const xaxis = DateTool.getTime(60);
-        const {data} = await fetchAlarmIndexDay({
-          areaCode: this.areaCode,
-        });
-        const series = this.toSeries(data, xaxis);
-        this.devcWarningHis.xAxis.data = xaxis;
-        this.devcWarningHis.series = series;
-        return;
-      }
-      if (item.val === 'month') {
-        const xaxis = DateTool.getDayOfRange(
-            dayjs().subtract(1, 'month'), dayjs(), DateTool.DateFormat.YYYY_MM_DD);
-        const {data} = await fetchAlarmIndexMonth({areaCode: this.areaCode});
-        const series = this.toSeries(data, xaxis);
-        this.devcWarningHis.xAxis.data = xaxis;
-        this.devcWarningHis.series = series;
-        return;
-      }
-      const xaxis = DateTool.getMonthsOfYearAgo();
-      const {data} = await fetchAlarmIndexYear({areaCode: this.areaCode});
-      const series = this.toSeries(data, xaxis);
-      this.devcWarningHis.xAxis.data = xaxis;
-      this.devcWarningHis.series = series;
-    },
-    async onSubSysDateTypeSwitch(item) {
-      if (item.val === 'day') {
-        const xaxis = DateTool.getTime(60);
-        const {data} = await fetchSubSysIndexDay({
-          areaCode: this.areaCode,
-        });
-        const series = this.subSysIndexToSeries(data, xaxis);
-        this.subSysIndex.xAxis.data = xaxis;
-        this.subSysIndex.series = series;
-        return;
-      }
-      if (item.val === 'month') {
-        const xaxis = DateTool.getDayOfRange(
-            dayjs().subtract(1, 'month'), dayjs(), DateTool.DateFormat.YYYY_MM_DD);
-        const {data} = await fetchSubSysIndexMonth({areaCode: this.areaCode});
-        const series = this.subSysIndexToSeries(data, xaxis);
-        this.subSysIndex.xAxis.data = xaxis;
-        this.subSysIndex.series = series;
-        return;
-      }
-      const xaxis = DateTool.getMonthsOfYearAgo();
-      const {data} = await fetchSubSysIndexYear({areaCode: this.areaCode});
-      const series = this.subSysIndexToSeries(data, xaxis);
-      this.subSysIndex.xAxis.data = xaxis;
-      this.subSysIndex.series = series;
-    },
-    toSeries(data, xaxis) {
-      const dayGroup = _.groupBy(data, 'alarmType');
-      const series = [];
-      Object.keys(dayGroup).forEach((alarmType) => {
-        let ds = {};
-        let typeName = this.selectDictLabel(this.dict.type.alarm_type, alarmType);
-        dayGroup[alarmType].forEach(item => {
-          ds[item.dateIndex] = item.cnt;
-        });
-        let seriesData = [];
-        xaxis.forEach((item) => seriesData.push(ds[item] || 0));
-        series.push({
-          name: typeName,
-          type: 'line',
-          smooth: true,
-          data: seriesData,
-        });
-      });
-      return series;
-    },
-
-    subSysIndexToSeries(data, xaxis) {
-      const dayGroup = _.groupBy(data, 'systemCode');
-      const series = [];
-      Object.keys(dayGroup).forEach((subSysCode) => {
-        let ds = {};
-        let systemName = dayGroup[subSysCode][0].systemName;
-        dayGroup[subSysCode].forEach(item => {
-          ds[item.dateIndex] = item.cnt;
-        });
-        let seriesData = [];
-        xaxis.forEach((item) => seriesData.push(ds[item] || 0));
-        series.push({
-          name: systemName,
-          type: 'bar',
-          data: seriesData,
-        });
-      });
-      return series;
-    },
-    queryCharts() {
-      this.onDateTypeSwitch(this.dateType);
-      this.onSubSysDateTypeSwitch(this.dateType);
-      this.getRealTimeAlarm();
-    },
-    async getRealTimeAlarm() {
-      let result = [];
-      const {
-        code,
-        rows,
-      } = await listAlarmInfo({
-        pageNum: 1,
-        pageSize: 10,
-        areaCode: this.areaCode,
-        alarmStateList: [
-          ALARM_STATE.new.value, ALARM_STATE.disposing.value,
-        ],
-        startRecTime: this.startRecTime,
-        endRecTime: this.endRecTime,
-      });
-      if (ApiCode.SUCCESS === code && rows && rows.length > 0) {
-        result = rows;
-      }
-      this.realTimeTableData = result;
-    },
-    async handleCommand(command, data) {
-      await updateAlarmInfo({
-        id: data.id,
-        alarmState: ALARM_STATE[command].value,
-      });
-      await this.getRealTimeAlarm();
-    },
-  },
-};
-</script>
-<style src="./index.scss" scoped lang="scss"/>