Преглед изворни кода

feat(views): 添加能耗分析页面

wangcong пре 1 месец
родитељ
комит
a69604af45

+ 6 - 1
src/i18n/locales/zh.json

@@ -72,6 +72,7 @@
     "exportToLocal": "导出到本地",
     "failure": "失败",
     "finishSetup": "完成配置",
+    "group": "组",
     "item": "项",
     "month": "月",
     "needHelp": "需要帮助?",
@@ -122,6 +123,7 @@
     "total": "共",
     "turnOff": "关闭",
     "unimatIoT": "亿维物联",
+    "unit": "单位",
     "updateTime": "更新时间",
     "use": "使用",
     "verification": "验证",
@@ -262,7 +264,10 @@
     "realTimeValue": "实时值",
     "subItemEnergyEfficiency": "分项能效",
     "tenThousandKwh": "万kwh",
-    "tenThousandYuan": "万元"
+    "tenThousandYuan": "万元",
+    "totalElectricityConsumption": "总电量",
+    "totalElectricityCost": "总电费",
+    "yuan": "元"
   },
   "envMonitor": {
     "addArea": "添加区域",

+ 12 - 0
src/styles/global.scss

@@ -229,3 +229,15 @@
   align-items: center;
   justify-content: center;
 }
+
+.echarts-tooltip-electricity {
+  width: 100%;
+  font-weight: bold;
+  border-collapse: collapse;
+
+  th,
+  td {
+    padding-inline: 3px;
+    text-align: left;
+  }
+}

+ 99 - 1
src/views/energy-analysis/EnergyAnalysis.vue

@@ -1,3 +1,101 @@
+<script setup lang="ts">
+import { ref } from 'vue';
+
+import DeviceGroupSelect from '@/components/DeviceGroupSelect.vue';
+
+import EnergyConsumption from './EnergyConsumption.vue';
+import EnergyEfficiency from './EnergyEfficiency.vue';
+
+const activeKey = ref('energyConsumption');
+
+const handleDevGroupChange = (id: number) => {
+  console.log(id);
+};
+</script>
+
 <template>
-  <div>能耗分析</div>
+  <ATabs class="button-tabs-compact" v-model:active-key="activeKey" type="card">
+    <ATabPane key="energyConsumption" :tab="$t('energyAnalysis.energyConsumptionAnalysis')">
+      <EnergyConsumption />
+    </ATabPane>
+    <ATabPane key="energyEfficiency" :tab="$t('energyAnalysis.energyEfficiencyAnalysis')">
+      <EnergyEfficiency />
+    </ATabPane>
+    <!-- <ATabPane key="energySaving" :tab="$t('energyAnalysis.energySavingAssessment')">
+      <EnergySaving />
+    </ATabPane> -->
+    <template #rightExtra>
+      <DeviceGroupSelect @change="handleDevGroupChange" />
+    </template>
+  </ATabs>
 </template>
+
+<style lang="scss" scoped>
+.group-select {
+  width: 192px;
+
+  & + & {
+    margin-left: 16px;
+  }
+}
+
+:deep(.ant-tabs-content-holder) {
+  .ant-card {
+    border-radius: 16px;
+    box-shadow: none;
+
+    + .ant-card {
+      margin-top: 16px;
+    }
+  }
+
+  .ant-card-head-title {
+    padding-left: 10px;
+    font-size: 16px;
+    font-weight: 600;
+    line-height: 24px;
+    color: #333;
+    border-left: 2px solid var(--antd-color-primary);
+  }
+
+  .energy-card-container {
+    display: flex;
+    flex: 1;
+    align-items: center;
+    height: 124px;
+    border-radius: 8px;
+  }
+
+  .energy-card-icon {
+    margin-right: 16px;
+    margin-left: 24px;
+  }
+
+  .energy-card-title {
+    margin-bottom: 2px;
+  }
+
+  .energy-card-value {
+    margin-right: 4px;
+    font-family: DINAlternate;
+    font-size: 30px;
+    font-weight: bold;
+    line-height: 36px;
+    color: #000;
+  }
+
+  .energy-card-unit {
+    font-size: 14px;
+    font-weight: 500;
+    line-height: 22px;
+    color: #000;
+  }
+
+  .energy-card-description {
+    font-size: 14px;
+    font-weight: 500;
+    line-height: 22px;
+    color: #000;
+  }
+}
+</style>

+ 836 - 0
src/views/energy-analysis/EnergyConsumption.vue

@@ -0,0 +1,836 @@
+<script setup lang="ts">
+import { computed, onMounted, ref, watch } from 'vue';
+import VChart from 'vue-echarts';
+import { BarChart, LineChart, PieChart } from 'echarts/charts';
+import { DatasetComponent, GridComponent, LegendComponent, TitleComponent, TooltipComponent } from 'echarts/components';
+import { use } from 'echarts/core';
+import { CanvasRenderer } from 'echarts/renderers';
+
+import SvgIcon from '@/components/SvgIcon.vue';
+import { useRequest } from '@/hooks/request';
+import { useViewVisible } from '@/hooks/view-visible';
+import { t } from '@/i18n';
+import { getElectricityDataStatistics } from '@/api';
+import type { IconfontIcon } from '@/icons/fonts/iconfont';
+
+import cumulativeElectricityBill from '@/assets/img/cumulative-electricity-bill.png';
+import cumulativeElectricityConsumption from '@/assets/img/cumulative-electricity-consumption.png';
+import electricityDailyConsumption from '@/assets/img/electricity-daily-consumption.png';
+import electricitySequentialGrowth from '@/assets/img/electricity-sequential-growth.png';
+
+import { DeviceType } from '../device-work-status/device-card';
+
+import type { SegmentedBaseOption } from 'ant-design-vue/es/segmented/src/segmented';
+import type { Dayjs } from 'dayjs';
+import type { BarSeriesOption, LineSeriesOption, PieSeriesOption } from 'echarts/charts';
+import type {
+  DatasetComponentOption,
+  GridComponentOption,
+  LegendComponentOption,
+  TitleComponentOption,
+  TooltipComponentOption,
+} from 'echarts/components';
+import type { ComposeOption } from 'echarts/core';
+import type { ElectricityStatisticsResult, EnergyCardItem } from '@/types';
+
+type StatisticsValue = 'electricityConsumption' | 'electricityCost';
+
+interface StatisticsTypeItem extends SegmentedBaseOption {
+  value: StatisticsValue;
+  payload: {
+    title: string;
+    icon: IconfontIcon;
+  };
+}
+
+interface EquipmentData {
+  key: string;
+  name: string;
+  type: string;
+  electricity: number;
+  cost: number;
+  dailyElectricity: number;
+  ratio: number;
+  children?: EquipmentData[];
+}
+
+use([
+  CanvasRenderer,
+  BarChart,
+  PieChart,
+  LineChart,
+  GridComponent,
+  TooltipComponent,
+  DatasetComponent,
+  LegendComponent,
+  TitleComponent,
+]);
+
+type EChartsOption = ComposeOption<
+  | TitleComponentOption
+  | LegendComponentOption
+  | TooltipComponentOption
+  | DatasetComponentOption
+  | GridComponentOption
+  | BarSeriesOption
+  | LineSeriesOption
+  | PieSeriesOption
+>;
+
+const { handleRequest } = useRequest();
+const energyConsumeResult = ref<ElectricityStatisticsResult>();
+
+const getEnergyConsumeResult = () => {
+  handleRequest(async () => {
+    energyConsumeResult.value = await getElectricityDataStatistics({
+      deviceGroupId: 7,
+      deviceTypes: [DeviceType.冷水主机, DeviceType.冷却塔, DeviceType.冷却泵, DeviceType.冷冻泵],
+      startTime: '2025-04-01 00:00:00',
+      endTime: '2025-05-01 23:00:00',
+    });
+
+    energyConsumeResult.value.groupQueryVos.forEach((item) => {
+      item.groupName = item.deviceTypeName + t('common.group');
+    });
+
+    energyConsumeResult.value.hisQueryVos.forEach((item) => {
+      item.groupName = item.deviceTypeName + t('common.group');
+    });
+  });
+};
+
+onMounted(() => {
+  getEnergyConsumeResult();
+});
+
+const time = ref<Dayjs>();
+
+const energyCardList = computed<EnergyCardItem[]>(() => {
+  return [
+    {
+      value: energyConsumeResult.value?.cumulativeEnergy ?? '-',
+      unit: t('energyAnalysis.tenThousandKwh'),
+      description: t('energyAnalysis.cumulativeElectricityConsumption'),
+      icon: cumulativeElectricityConsumption,
+      bgColor: '#FFF6E6',
+    },
+    {
+      value: energyConsumeResult.value?.cumulativeBill ?? '-',
+      unit: t('energyAnalysis.tenThousandYuan'),
+      description: t('energyAnalysis.cumulativeElectricityCost'),
+      icon: cumulativeElectricityBill,
+      bgColor: '#EFF1FE',
+    },
+    {
+      value: energyConsumeResult.value?.ringGrowth ?? '-',
+      unit: '%',
+      description: t('energyAnalysis.electricityConsumptionGrowthRate'),
+      icon: electricitySequentialGrowth,
+      bgColor: 'var(--antd-color-primary-opacity-15)',
+    },
+    {
+      value: energyConsumeResult.value?.cumulativeDailyUse ?? '-',
+      unit: t('energyAnalysis.tenThousandKwh'),
+      description: t('energyAnalysis.averageDailyElectricityConsumption'),
+      icon: electricityDailyConsumption,
+      bgColor: 'rgba(104, 194, 58, 0.12)',
+    },
+  ];
+});
+
+const statisticsTypes = ref<StatisticsTypeItem[]>([
+  {
+    value: 'electricityConsumption',
+    payload: {
+      title: t('energyAnalysis.electricityConsumption'),
+      icon: 'electricity-quantity',
+    },
+  },
+  {
+    value: 'electricityCost',
+    payload: {
+      title: t('energyAnalysis.electricityCost'),
+      icon: 'electricity-bill',
+    },
+  },
+]);
+
+const currentStatisticsType = ref<StatisticsValue>('electricityConsumption');
+
+const isElectricityConsume = computed(() => {
+  return currentStatisticsType.value === 'electricityConsumption';
+});
+
+const pieOption = computed<EChartsOption>(() => {
+  const centerValue = isElectricityConsume.value
+    ? energyConsumeResult.value?.cumulativeEnergy
+    : energyConsumeResult.value?.cumulativeBill;
+  const unitText = isElectricityConsume.value
+    ? t('energyAnalysis.tenThousandKwh')
+    : t('energyAnalysis.tenThousandYuan');
+  const legendData = energyConsumeResult.value?.groupQueryVos.map((item) => item.groupName as string);
+  const seriesData = energyConsumeResult.value?.groupQueryVos.map((item) => ({
+    name: item.groupName as string,
+    value: isElectricityConsume.value ? item.totalEnergyW : item.totalBillW,
+  }));
+
+  return {
+    tooltip: {
+      trigger: 'item',
+      // eslint-disable-next-line @typescript-eslint/no-explicit-any
+      formatter(params: any) {
+        return `${params.marker}${params.name}: ${params.value}${unitText}`;
+      },
+    },
+    legend: {
+      orient: 'horizontal',
+      bottom: 0,
+      textStyle: {
+        color: '#000',
+        fontSize: 12,
+      },
+      itemWidth: 8,
+      itemHeight: 8,
+      itemGap: 24,
+      data: legendData,
+    },
+    series: [
+      {
+        type: 'pie',
+        radius: ['40%', '65%'],
+        avoidLabelOverlap: true,
+        label: {
+          show: true,
+          position: 'outside',
+          formatter: '{d}%',
+        },
+        labelLine: {
+          show: true,
+        },
+        data: seriesData,
+        center: ['50%', '50%'],
+      },
+      {
+        type: 'pie',
+        radius: ['0', '30%'],
+        itemStyle: {
+          color: 'transparent',
+        },
+        tooltip: {
+          show: false,
+        },
+        label: {
+          position: 'center',
+          formatter: `{value|${centerValue ?? '-'}}\n{title|${unitText}}`,
+          rich: {
+            value: {
+              fontSize: 30,
+              fontWeight: 'bold',
+              color: '#000',
+              lineHeight: 36,
+            },
+            title: {
+              fontSize: 14,
+              color: '#666',
+              lineHeight: 22,
+            },
+          },
+        },
+        data: [{ value: 1, name: '' }],
+      },
+    ],
+    color: ['#4ECDC4', '#5470C6', '#36A2EB', '#FFC107'],
+  };
+});
+
+const lineOption = computed<EChartsOption>(() => {
+  const unitText = isElectricityConsume.value ? 'kwh' : t('energyAnalysis.yuan');
+  const tooltipTitle = isElectricityConsume.value
+    ? t('energyAnalysis.totalElectricityConsumption')
+    : t('energyAnalysis.totalElectricityCost');
+  const hisQueryVos = energyConsumeResult.value?.hisQueryVos || [];
+  const deviceTypes = hisQueryVos.map((item) => item.groupName as string) || [];
+  const dataset = {
+    source: [['time', ...deviceTypes]],
+  };
+
+  const timeSet = new Set();
+
+  hisQueryVos.forEach((item) => {
+    item.valueList.forEach((valueItem) => {
+      timeSet.add(valueItem.time);
+    });
+  });
+
+  const times = Array.from(timeSet);
+
+  times.forEach((time) => {
+    const row = [time];
+
+    deviceTypes.forEach((deviceType) => {
+      const deviceData = hisQueryVos.find((item) => item.groupName === deviceType);
+      const valueItem = deviceData?.valueList.find((item) => item.time === time);
+      row.push(isElectricityConsume.value ? valueItem?.energy : valueItem?.bill);
+    });
+
+    dataset.source.push(row as string[]);
+  });
+
+  return {
+    title: {
+      text: `${t('common.unit')}: ${unitText}`,
+      textStyle: {
+        color: '#999',
+        fontSize: 10,
+        fontWeight: 400,
+        lineHeight: 14,
+      },
+      left: '7%',
+      top: '9%',
+    },
+    tooltip: {
+      trigger: 'axis',
+      formatter(params) {
+        // eslint-disable-next-line @typescript-eslint/no-explicit-any
+        const tempParms = params as any[];
+        const time = tempParms[0].axisValue;
+        let totalValue = 0;
+
+        tempParms.forEach((param) => {
+          totalValue += param.value[param.encode.y[0]];
+        });
+
+        let tableHtml = '<table class="echarts-tooltip-electricity">';
+        tableHtml += `<tr><th>${time}</th></tr>`;
+        tableHtml += `<tr><td>${tooltipTitle}</td><td>${totalValue}${unitText}</td><td>100.00%</td></tr>`;
+
+        tempParms.forEach((param) => {
+          const value = param.value[param.encode.y[0]];
+          const percent = ((value / totalValue) * 100).toFixed(2) + '%';
+          tableHtml += `<tr><td>${param.marker}${param.seriesName}</td><td>${value}${unitText}</td><td>${percent}</td></tr>`;
+        });
+
+        tableHtml += '</table>';
+        return tableHtml;
+      },
+    },
+    legend: {
+      orient: 'horizontal',
+      bottom: 0,
+      textStyle: {
+        color: '#000',
+        fontSize: 12,
+      },
+      itemWidth: 22,
+      itemHeight: 8,
+      itemGap: 24,
+      data: deviceTypes,
+    },
+    dataset,
+    xAxis: {
+      type: 'category',
+      boundaryGap: true,
+      axisLine: {
+        lineStyle: {
+          type: 'dashed',
+          color: '#EBEBEB',
+        },
+      },
+      axisLabel: {
+        color: '#999',
+      },
+      axisTick: {
+        show: false,
+      },
+    },
+    yAxis: {
+      type: 'value',
+      axisLabel: {
+        color: '#999',
+      },
+      splitLine: {
+        lineStyle: {
+          type: 'dashed',
+          color: '#EBEBEB',
+        },
+      },
+    },
+    series: deviceTypes.map((type) => ({
+      name: type,
+      type: 'line',
+      stack: 'Total',
+      areaStyle: {
+        opacity: 0.3,
+      },
+      symbolSize: 6,
+      encode: {
+        x: 'time',
+        y: type,
+      },
+    })),
+    grid: {
+      left: '4%',
+      right: 0,
+      bottom: 46,
+      top: '17.5%',
+      containLabel: true,
+    },
+    color: ['#4ECDC4', '#5470C6', '#36A2EB', '#FFC107'],
+  };
+});
+
+const deviceAndGroups = computed<EquipmentData[]>(() => {
+  const groupQueryVos = energyConsumeResult.value?.groupQueryVos || [];
+
+  return groupQueryVos.map((item) => ({
+    key: item.deviceType,
+    name: item.groupName as string,
+    type: item.deviceTypeName,
+    electricity: item.totalEnergy,
+    cost: item.totalBill,
+    dailyElectricity: item.totalDailyUse,
+    ratio: item.totalRatio,
+    children: item.valueList?.map((child) => ({
+      key: child.deviceId.toString(),
+      name: child.deviceName,
+      type: item.deviceTypeName,
+      electricity: child.energy,
+      cost: child.bill,
+      dailyElectricity: child.dailyUse,
+      ratio: child.ratio,
+    })),
+  }));
+});
+
+const exportData = () => {
+  return;
+};
+
+const { handleRequest: handleDevElectricRequest } = useRequest();
+const { visible, showView } = useViewVisible();
+const selectedDevice = ref<EquipmentData>();
+const devEnergyConsumeResult = ref<ElectricityStatisticsResult>();
+
+watch(visible, () => {
+  if (visible.value) {
+    getDevElectricityData();
+  }
+});
+
+const viewDevElectricityData = (record: EquipmentData) => {
+  selectedDevice.value = record;
+  showView();
+};
+
+const getDevElectricityData = () => {
+  handleDevElectricRequest(async () => {
+    devEnergyConsumeResult.value = await getElectricityDataStatistics({
+      deviceGroupId: 7,
+      deviceId: selectedDevice.value?.key,
+      startTime: '2025-04-01 00:00:00',
+      endTime: '2025-05-01 23:00:00',
+    });
+  });
+};
+
+const devElectricityOption = computed<EChartsOption>(() => {
+  const hisQueryVos = devEnergyConsumeResult.value?.hisQueryVos[0]?.valueList || [];
+  const times: string[] = [];
+  const energy: number[] = [];
+  const bill: number[] = [];
+
+  hisQueryVos.forEach((item) => {
+    times.push(item.time);
+    energy.push(item.energy);
+    bill.push(item.bill);
+  });
+
+  // 计算 Y 轴的分割段数
+  const yAxisSegments = 5;
+  const energyMax = Math.ceil(Math.max(...energy) / yAxisSegments) * yAxisSegments;
+  const billMax = Math.ceil(Math.max(...bill) / yAxisSegments) * yAxisSegments;
+
+  return {
+    title: [
+      {
+        text: `${t('common.unit')}(kwh)`,
+        textStyle: {
+          color: '#999',
+          fontSize: 12,
+          fontWeight: 400,
+          lineHeight: 17,
+        },
+        left: '6%',
+        top: 0,
+      },
+      {
+        text: `${t('common.unit')}(${t('energyAnalysis.yuan')})`,
+        textStyle: {
+          color: '#999',
+          fontSize: 12,
+          fontWeight: 400,
+          lineHeight: 17,
+        },
+        right: '2%',
+        top: 0,
+      },
+    ],
+    tooltip: {
+      trigger: 'axis',
+      formatter(params) {
+        // eslint-disable-next-line @typescript-eslint/no-explicit-any
+        const tempParms = params as any[];
+        let tooltipStr = `<div>${tempParms[0].name}</div>`;
+
+        tempParms.forEach((param) => {
+          const unit =
+            param.seriesName === t('energyAnalysis.electricityConsumption') ? 'kwh' : t('energyAnalysis.yuan');
+          tooltipStr += `<div>${param.marker} ${param.seriesName}: ${param.value}${unit}</div>`;
+        });
+
+        return tooltipStr;
+      },
+    },
+    legend: [
+      {
+        orient: 'horizontal',
+        bottom: 0,
+        left: 263,
+        textStyle: {
+          color: '#000',
+          fontSize: 12,
+        },
+        itemWidth: 8,
+        itemHeight: 8,
+        itemGap: 24,
+        data: [t('energyAnalysis.electricityConsumption')],
+      },
+      {
+        orient: 'horizontal',
+        bottom: 0,
+        left: 331,
+        textStyle: {
+          color: '#000',
+          fontSize: 12,
+        },
+        itemWidth: 22,
+        itemHeight: 8,
+        itemGap: 24,
+        data: [t('energyAnalysis.electricityCost')],
+      },
+    ],
+    xAxis: {
+      type: 'category',
+      boundaryGap: true,
+      axisLine: {
+        lineStyle: {
+          type: 'dashed',
+          color: '#EBEBEB',
+        },
+      },
+      axisLabel: {
+        color: '#999',
+        fontSize: 10,
+      },
+      axisTick: {
+        show: false,
+      },
+      data: times,
+    },
+    yAxis: [
+      {
+        type: 'value',
+        position: 'left',
+        min: 0,
+        max: energyMax,
+        interval: energyMax / yAxisSegments,
+        axisLabel: {
+          color: '#999',
+          fontSize: 10,
+        },
+        splitLine: {
+          lineStyle: {
+            type: 'dashed',
+            color: '#EBEBEB',
+          },
+        },
+      },
+      {
+        type: 'value',
+        position: 'right',
+        min: 0,
+        max: billMax,
+        interval: billMax / yAxisSegments,
+        axisLabel: {
+          color: '#999',
+          fontSize: 10,
+        },
+        splitLine: {
+          lineStyle: {
+            type: 'dashed',
+            color: '#EBEBEB',
+          },
+        },
+      },
+    ],
+    series: [
+      {
+        name: t('energyAnalysis.electricityConsumption'),
+        type: 'bar',
+        data: energy,
+        barWidth: 24,
+      },
+      {
+        name: t('energyAnalysis.electricityCost'),
+        type: 'line',
+        yAxisIndex: 1,
+        data: bill,
+        symbolSize: 6,
+      },
+    ],
+    grid: {
+      left: '4%',
+      right: 0,
+      bottom: 38,
+      top: 25,
+      containLabel: true,
+    },
+    color: ['#32BAC0', '#5B8FF9'],
+  };
+});
+</script>
+
+<template>
+  <ACard :title="$t('energyAnalysis.energyConsumptionAnalysis')" :bordered="false">
+    <template #extra>
+      <span class="">{{ $t('common.selectTime') }}</span>
+      <ADatePicker v-model:value="time" picker="year" />
+      <AButton @click="exportData">{{ $t('common.export') }}</AButton>
+    </template>
+    <ARow class="energy-card-list" :gutter="[24, 24]">
+      <ACol v-for="(item, index) in energyCardList" :key="index" :xs="24" :sm="24" :md="12" :lg="12" :xl="6" :xxl="6">
+        <div
+          class="energy-card-container"
+          :style="{
+            backgroundColor: item.bgColor,
+          }"
+        >
+          <img class="energy-card-icon" :src="item.icon" />
+          <div>
+            <div class="energy-card-title">
+              <span class="energy-card-value">{{ item.value }}</span>
+              <span class="energy-card-unit">{{ item.unit }}</span>
+            </div>
+            <div class="energy-card-description">{{ item.description }}</div>
+          </div>
+        </div>
+      </ACol>
+    </ARow>
+    <ASegmented v-model:value="currentStatisticsType" :options="statisticsTypes">
+      <template #label="{ payload }">
+        <SvgIcon :name="payload.icon" />
+        <span>{{ payload.title }}</span>
+      </template>
+    </ASegmented>
+    <div class="chart-container">
+      <div class="chart-wrapper chart-pie">
+        <VChart class="chart" :option="pieOption" autoresize />
+      </div>
+      <div class="chart-wrapper chart-line">
+        <VChart class="chart" :option="lineOption" autoresize />
+      </div>
+    </div>
+    <ATable :data-source="deviceAndGroups" :pagination="false">
+      <ATableColumn :title="$t('energyAnalysis.deviceGroupName')" data-index="name" key="name">
+        <template #default="{ record }">
+          <span v-if="record.children">{{ record.name }}</span>
+          <a v-else class="device-name" @click="viewDevElectricityData(record)">
+            {{ record.name }}
+          </a>
+        </template>
+      </ATableColumn>
+      <ATableColumn :title="$t('energyAnalysis.deviceType')" data-index="type" key="type" />
+      <ATableColumn
+        :title="$t('energyAnalysis.electricityConsumptionUnit')"
+        data-index="electricity"
+        key="electricity"
+      />
+      <ATableColumn :title="$t('energyAnalysis.electricityCostUnit')" data-index="cost" key="cost" />
+      <ATableColumn
+        :title="$t('energyAnalysis.dailyElectricityUnit')"
+        data-index="dailyElectricity"
+        key="dailyElectricity"
+      />
+      <ATableColumn :title="$t('energyAnalysis.electricityRatioUnit')" data-index="ratio" key="ratio" />
+    </ATable>
+    <AModal v-model:visible="visible" :title="selectedDevice?.name" :width="920" :footer="null">
+      <!-- <TimeRangeSelect class="device-electricity-select" is-last /> -->
+      <div class="device-electricity-detail">
+        <div class="device-electricity-summary">
+          <div class="device-electricity-item">
+            <span class="device-electricity-value">{{ devEnergyConsumeResult?.cumulativeEnergy ?? '-' }}</span>
+            <span class="device-electricity-label">{{ $t('energyAnalysis.tenThousandKwh') }}</span>
+            <div class="device-electricity-title">
+              <SvgIcon name="electricity-quantity" />
+              <span class="device-electricity-label">{{ $t('energyAnalysis.totalElectricityConsumption') }}</span>
+            </div>
+          </div>
+          <div class="device-electricity-item">
+            <span class="device-electricity-value">{{ devEnergyConsumeResult?.cumulativeBill ?? '-' }}</span>
+            <span class="device-electricity-label">{{ $t('energyAnalysis.tenThousandYuan') }}</span>
+            <div class="device-electricity-title">
+              <SvgIcon name="electricity-bill" />
+              <span class="device-electricity-label">{{ $t('energyAnalysis.totalElectricityCost') }}</span>
+            </div>
+          </div>
+        </div>
+        <VChart class="device-electricity-chart" :option="devElectricityOption" autoresize />
+      </div>
+    </AModal>
+  </ACard>
+</template>
+
+<style lang="scss" scoped>
+.energy-card-list {
+  margin-bottom: 24px;
+}
+
+:deep(.ant-card-body) {
+  .ant-segmented {
+    padding: 4px;
+    color: var(--antd-color-primary);
+    background-color: var(--antd-color-primary-opacity-15);
+    border-radius: 4px;
+
+    .ant-segmented-item-label {
+      display: flex;
+      align-items: center;
+      padding-inline: 12px;
+
+      i {
+        margin-right: 4px;
+        font-size: 16px;
+      }
+    }
+
+    .ant-segmented-item-selected {
+      color: var(--antd-color-primary);
+      box-shadow: none;
+    }
+
+    .ant-segmented-item {
+      + .ant-segmented-item {
+        margin-left: 8px;
+      }
+
+      &:hover:not(.ant-segmented-item-selected, .ant-segmented-item-disabled) {
+        color: var(--antd-color-primary);
+
+        &::after {
+          background-color: var(--antd-color-primary-opacity-15);
+        }
+      }
+    }
+  }
+}
+
+.chart-container {
+  display: flex;
+  margin-bottom: 40px;
+}
+
+.chart-wrapper {
+  height: 318px;
+}
+
+.chart-pie {
+  width: 360px;
+}
+
+.chart-line {
+  width: calc(100% - 360px);
+}
+
+.chart {
+  height: 100%;
+}
+
+.device-name {
+  color: var(--antd-color-primary);
+}
+
+.device-electricity-select {
+  margin-top: 16px;
+}
+
+.device-electricity-detail {
+  display: flex;
+  margin-top: 14px;
+}
+
+.device-electricity-summary {
+  width: 250px;
+  height: 258px;
+  padding: 40px 24px;
+  margin-top: 10px;
+  background: #f5f7fa;
+  border-radius: 8px;
+}
+
+.device-electricity-item {
+  &:first-child {
+    padding-bottom: 25px;
+    border-bottom: 1px dashed #e4e7ed;
+  }
+
+  & + & {
+    margin-top: 24px;
+  }
+}
+
+.device-electricity-value {
+  margin-right: 4px;
+  font-family: DINAlternate;
+  font-size: 30px;
+  font-weight: bold;
+  line-height: 36px;
+  color: #000;
+}
+
+.device-electricity-label {
+  font-size: 14px;
+  font-weight: 500;
+  line-height: 22px;
+  color: #000;
+}
+
+.device-electricity-title {
+  display: flex;
+  align-items: center;
+  margin-top: 4px;
+
+  i {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 24px;
+    height: 24px;
+    margin-right: 8px;
+    font-size: 16px;
+    color: #fff;
+    border-radius: 2px;
+  }
+
+  .svg-icon-electricity-quantity {
+    background-color: var(--antd-color-primary);
+  }
+
+  .svg-icon-electricity-bill {
+    background-color: #1f8ffb;
+  }
+}
+
+.device-electricity-chart {
+  height: 314px;
+}
+</style>