<template>
|
<div class="app-container">
|
<div class="page-header">
|
<div>
|
<h2>能耗实时监控</h2>
|
<p class="subtitle">展示本地已同步的用电数据(定时任务每小时同步)</p>
|
</div>
|
<div class="header-actions">
|
<el-tag :type="autoRefresh ? 'success' : 'info'" size="small">
|
{{ autoRefresh ? "自动刷新中" : "已暂停" }}
|
</el-tag>
|
<span class="update-time">更新:{{ lastUpdateTime }}</span>
|
<el-switch v-model="autoRefresh" active-text="自动刷新" @change="toggleAutoRefresh" />
|
<el-button type="primary" :loading="loading" @click="loadData">
|
<el-icon><Refresh /></el-icon>
|
立即刷新
|
</el-button>
|
</div>
|
</div>
|
|
<el-card class="yesterday-banner" v-loading="yesterdayLoading" shadow="hover">
|
<div class="yesterday-row">
|
<div>
|
<div class="yesterday-title">昨日总用电({{ getYesterdayDayPicker() }})</div>
|
<div class="yesterday-value">{{ yesterdaySummary.totalConsumption ?? 0 }} <span>kWh</span></div>
|
</div>
|
<div class="yesterday-stats">
|
<span>平均 {{ yesterdaySummary.avgConsumption ?? 0 }} kWh</span>
|
<span>最大 {{ yesterdaySummary.maxConsumption ?? 0 }} kWh</span>
|
<span>最小 {{ yesterdaySummary.minConsumption ?? 0 }} kWh</span>
|
</div>
|
</div>
|
</el-card>
|
|
<el-row :gutter="16" class="monitor-cards">
|
<el-col :span="8">
|
<el-card shadow="hover">
|
<div class="monitor-card">
|
<div class="monitor-title">当前小时用电</div>
|
<div class="monitor-value">
|
{{ currentHourConsumption }}
|
<span class="unit">kWh</span>
|
</div>
|
</div>
|
</el-card>
|
</el-col>
|
<el-col :span="8">
|
<el-card shadow="hover">
|
<div class="monitor-card">
|
<div class="monitor-title">近24小时累计</div>
|
<div class="monitor-value">
|
{{ totalConsumption }}
|
<span class="unit">kWh</span>
|
</div>
|
</div>
|
</el-card>
|
</el-col>
|
<el-col :span="8">
|
<el-card shadow="hover">
|
<div class="monitor-card">
|
<div class="monitor-title">平均小时用电</div>
|
<div class="monitor-value">
|
{{ avgConsumption }}
|
<span class="unit">kWh</span>
|
</div>
|
</div>
|
</el-card>
|
</el-col>
|
</el-row>
|
|
<el-card class="chart-card">
|
<template #header>
|
<div class="card-header">
|
<span>近24小时用电趋势</span>
|
<el-radio-group v-model="chartType" size="small" @change="renderChart">
|
<el-radio-button value="line">折线图</el-radio-button>
|
<el-radio-button value="bar">柱状图</el-radio-button>
|
</el-radio-group>
|
</div>
|
</template>
|
<div ref="chartRef" class="chart-container"></div>
|
</el-card>
|
|
<el-card class="table-card">
|
<template #header>
|
<span>实时采集明细</span>
|
</template>
|
<el-table v-loading="loading" :data="records" border stripe max-height="320">
|
<el-table-column label="时间" min-width="160">
|
<template #default="{ row }">
|
{{ parseTimeKey(row.timeKey, "hour") }}
|
</template>
|
</el-table-column>
|
<el-table-column prop="meterId" label="电表ID" width="100" />
|
<el-table-column prop="totalConsumption" label="用电量(kWh)" width="120" />
|
<el-table-column prop="startTime" label="开始时间" min-width="160" />
|
<el-table-column prop="endTime" label="结束时间" min-width="160" />
|
<el-table-column prop="endReading" label="当前读数" min-width="160" show-overflow-tooltip />
|
</el-table>
|
</el-card>
|
</div>
|
</template>
|
|
<script setup>
|
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
|
import { Refresh } from "@element-plus/icons-vue";
|
import * as echarts from "echarts";
|
import {
|
summaryStatisticEle,
|
getYesterdaySummary,
|
getYesterdayDayPicker,
|
parseTimeKey,
|
getRecentHourRange,
|
} from "@/api/energyManagement/statisticEle.js";
|
|
const loading = ref(false);
|
const yesterdayLoading = ref(false);
|
const yesterdaySummary = ref({});
|
const autoRefresh = ref(true);
|
const lastUpdateTime = ref("-");
|
const chartType = ref("line");
|
const records = ref([]);
|
const chartRecords = ref([]);
|
const chartRef = ref(null);
|
let chartInstance = null;
|
let refreshTimer = null;
|
|
const totalConsumption = computed(() => {
|
const total = chartRecords.value.reduce(
|
(sum, item) => sum + (item.totalConsumption || 0),
|
0
|
);
|
return total.toFixed(2);
|
});
|
|
const avgConsumption = computed(() => {
|
if (!chartRecords.value.length) return "0.00";
|
return (Number(totalConsumption.value) / chartRecords.value.length).toFixed(2);
|
});
|
|
const currentHourConsumption = computed(() => {
|
if (!chartRecords.value.length) return "0.00";
|
const latest = chartRecords.value[chartRecords.value.length - 1];
|
return (latest.totalConsumption || 0).toFixed(2);
|
});
|
|
async function loadYesterday() {
|
yesterdayLoading.value = true;
|
try {
|
const res = await getYesterdaySummary();
|
yesterdaySummary.value = res.data || {};
|
} finally {
|
yesterdayLoading.value = false;
|
}
|
}
|
|
async function loadData() {
|
loading.value = true;
|
try {
|
const { startTime, endTime } = getRecentHourRange(24);
|
const res = await summaryStatisticEle({
|
dimension: "hour",
|
startTime,
|
endTime,
|
});
|
records.value = res.data?.records || [];
|
chartRecords.value = res.data?.chartRecords || [];
|
lastUpdateTime.value = new Date().toLocaleString();
|
renderChart();
|
} finally {
|
loading.value = false;
|
}
|
}
|
|
function renderChart() {
|
if (!chartRef.value) return;
|
if (!chartInstance) {
|
chartInstance = echarts.init(chartRef.value);
|
}
|
const labels = chartRecords.value.map((item) => parseTimeKey(item.timeKey, "hour"));
|
const values = chartRecords.value.map((item) => item.totalConsumption || 0);
|
chartInstance.setOption({
|
tooltip: { trigger: "axis" },
|
grid: { left: 50, right: 30, top: 30, bottom: 60 },
|
xAxis: {
|
type: "category",
|
data: labels,
|
axisLabel: { rotate: 35, fontSize: 11 },
|
},
|
yAxis: {
|
type: "value",
|
name: "kWh",
|
},
|
series: [
|
{
|
name: "用电量",
|
type: chartType.value,
|
data: values,
|
smooth: true,
|
areaStyle: chartType.value === "line" ? { opacity: 0.15 } : undefined,
|
itemStyle: { color: "#409EFF" },
|
barMaxWidth: 40,
|
},
|
],
|
});
|
}
|
|
function startAutoRefresh() {
|
stopAutoRefresh();
|
refreshTimer = setInterval(loadData, 60 * 1000);
|
}
|
|
function stopAutoRefresh() {
|
if (refreshTimer) {
|
clearInterval(refreshTimer);
|
refreshTimer = null;
|
}
|
}
|
|
function toggleAutoRefresh(val) {
|
if (val) {
|
startAutoRefresh();
|
} else {
|
stopAutoRefresh();
|
}
|
}
|
|
function handleResize() {
|
chartInstance?.resize();
|
}
|
|
onMounted(() => {
|
loadYesterday();
|
loadData();
|
startAutoRefresh();
|
window.addEventListener("resize", handleResize);
|
});
|
|
onBeforeUnmount(() => {
|
stopAutoRefresh();
|
window.removeEventListener("resize", handleResize);
|
chartInstance?.dispose();
|
chartInstance = null;
|
});
|
</script>
|
|
<style scoped>
|
.page-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: flex-start;
|
margin-bottom: 16px;
|
}
|
.page-header h2 {
|
margin: 0 0 4px;
|
font-size: 20px;
|
}
|
.subtitle {
|
margin: 0;
|
color: #909399;
|
font-size: 13px;
|
}
|
.header-actions {
|
display: flex;
|
align-items: center;
|
gap: 12px;
|
}
|
.update-time {
|
font-size: 13px;
|
color: #909399;
|
}
|
.yesterday-banner {
|
margin-bottom: 16px;
|
}
|
.yesterday-row {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
}
|
.yesterday-title {
|
font-size: 14px;
|
color: #909399;
|
margin-bottom: 6px;
|
}
|
.yesterday-value {
|
font-size: 32px;
|
font-weight: 600;
|
color: #409eff;
|
}
|
.yesterday-value span {
|
font-size: 14px;
|
font-weight: 400;
|
color: #909399;
|
}
|
.yesterday-stats {
|
display: flex;
|
gap: 20px;
|
font-size: 13px;
|
color: #606266;
|
}
|
.monitor-cards {
|
margin-bottom: 16px;
|
}
|
.monitor-card {
|
text-align: center;
|
padding: 8px 0;
|
}
|
.monitor-title {
|
color: #909399;
|
font-size: 14px;
|
margin-bottom: 12px;
|
}
|
.monitor-value {
|
font-size: 32px;
|
font-weight: 600;
|
color: #303133;
|
}
|
.monitor-value .unit {
|
font-size: 14px;
|
font-weight: 400;
|
color: #909399;
|
margin-left: 4px;
|
}
|
.chart-card {
|
margin-bottom: 16px;
|
}
|
.card-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
}
|
.chart-container {
|
width: 100%;
|
height: 400px;
|
}
|
</style>
|