feat: 图表

This commit is contained in:
砂糖
2025-09-30 10:01:16 +08:00
parent 6c483ad6f5
commit 8b31f9bf84
13 changed files with 1287 additions and 142 deletions

View File

@@ -0,0 +1,382 @@
<template>
<div class="trends-container">
<!-- 月度能耗图表区域 -->
<el-row class="chart-group">
<el-col :span="24">
<div class="chart-header">
<h3>月度能耗趋势</h3>
<div class="chart-type-btn-group">
<el-button :type="monthChartType === 'bar' ? 'primary' : 'default'" @click="toggleMonthChartType('bar')"
:disabled="!hasMonthData">
柱状图
</el-button>
<el-button :type="monthChartType === 'line' ? 'primary' : 'default'" @click="toggleMonthChartType('line')"
:disabled="!hasMonthData">
折线图
</el-button>
</div>
</div>
<div class="chart-wrapper">
<div ref="monthChart" class="chart-container"></div>
</div>
</el-col>
</el-row>
<!-- 日度能耗图表区域 -->
<el-row class="chart-group">
<el-col :span="24">
<div class="chart-header">
<h3>日度能耗趋势</h3>
<div class="chart-type-btn-group">
<el-button :type="dayChartType === 'bar' ? 'primary' : 'default'" @click="toggleDayChartType('bar')"
:disabled="!hasDayData">
柱状图
</el-button>
<el-button :type="dayChartType === 'line' ? 'primary' : 'default'" @click="toggleDayChartType('line')"
:disabled="!hasDayData">
折线图
</el-button>
</div>
</div>
<div class="chart-wrapper">
<div ref="dayChart" class="chart-container"></div>
</div>
</el-col>
</el-row>
</div>
</template>
<script>
import * as echarts from 'echarts'
import { getRecentEnergySummary } from '@/api/ems/dashboard/timer'
export default {
name: 'RecentTrends',
data() {
return {
monthData: [], // 原始月度数据
dayData: [], // 原始日度数据
completedMonthData: [], // 补全后的月度数据
completedDayData: [], // 补全后的日度数据
monthChartType: 'bar',
dayChartType: 'bar',
monthChart: null,
dayChart: null,
hasMonthData: false, // 标记是否有月度数据
hasDayData: false // 标记是否有日度数据
}
},
props: {
unit: {
type: String,
required: true
},
energyName: {
type: String,
required: true
},
energyType: {
type: String,
required: true
},
locationId: {
type: String,
required: false
},
deviceId: {
type: String,
required: false
}
},
mounted() {
const dayChartDOM = this.$refs.dayChart
console.log(dayChartDOM, this.$refs)
if (!dayChartDOM) return
this.dayChart = echarts.init(dayChartDOM)
const monthChartDOM = this.$refs.monthChart
if (!monthChartDOM) return
this.monthChart = echarts.init(monthChartDOM)
this.fetchMonthData()
this.fetchDayData()
window.addEventListener('resize', this.handleWindowResize)
},
beforeDestroy() {
this.destroyCharts()
window.removeEventListener('resize', this.handleWindowResize)
},
methods: {
// 销毁图表实例
destroyCharts() {
if (this.monthChart) {
this.monthChart.dispose()
this.monthChart = null
}
if (this.dayChart) {
this.dayChart.dispose()
this.dayChart = null
}
},
// 数据请求
fetchMonthData() {
const year = new Date().getFullYear()
getRecentEnergySummary({ year, energyType: this.energyType, locationId: this.locationId, deviceId: this.deviceId })
.then(res => {
this.monthData = res.data || []
console.log(this.monthData)
// 检查是否有有效数据
this.hasMonthData = Array.isArray(this.monthData) && this.monthData.length > 0
this.completedMonthData = this.completeMonthData(this.monthData)
this.initMonthChart()
})
},
fetchDayData() {
const date = new Date()
const month = date.getFullYear() + '-' + (date.getMonth() + 1).toString().padStart(2, '0')
getRecentEnergySummary({ month, energyType: this.energyType, locationId: this.locationId, deviceId: this.deviceId })
.then(res => {
this.dayData = res.data || []
console.log(this.dayData)
this.completedDayData = this.completeDayData(this.dayData)
this.initDayChart()
})
},
// 补全月度数据 - 固定显示12个月份一整年
completeMonthData(data) {
this.hasMonthData = Array.isArray(this.monthData) && this.monthData.length > 0
if (!this.hasMonthData) {
const result = []
for (let i = 0; i < 12; i++) {
result.push({ month: i + 1, totalConsumption: 0 })
}
return result
}
const result = []
for (let i = 0; i < 12; i++) {
// 查找data中month{year}-{month}中{month}为i+1的数据
const item = data.find(item => item.month?.split('-')[1].padStart(2, '0') == i + 1)
console.log(item, i + 1, data)
if (item) {
result.push(item)
} else {
result.push({ month: i + 1, totalConsumption: 0 })
}
}
return result
},
// 补全日度数据 - 按照当月实际天数补齐
completeDayData(data) {
this.hasDayData = Array.isArray(this.dayData) && this.dayData.length > 0
// 本月天数
const today = new Date()
const dayCount = new Date(today.getFullYear(), today.getMonth() + 1, 0).getDate()
if (!this.hasDayData) {
const result = []
for (let i = 0; i < dayCount; i++) {
result.push({ day: i + 1, totalConsumption: 0 })
}
return result
}
// 补全数据
const result = []
for (let i = 0; i < dayCount; i++) {
const item = data.find(item => item.day?.split('-')[2].padStart(2, '0') == i + 1)
if (item) {
result.push(item)
} else {
result.push({ day: i + 1, totalConsumption: 0 })
}
}
return result;
},
// 初始化月度图表
initMonthChart() {
if (!this.hasMonthData) return
console.log(this.completedMonthData)
const xData = this.completedMonthData.map(item => item.month)
const yData = this.completedMonthData.map(item => item.totalConsumption)
const option = {
tooltip: {
trigger: 'axis',
formatter: '{b}: {c} ' + this.unit
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: xData,
axisLabel: {
rotate: 15
}
},
yAxis: {
type: 'value',
name: '能耗 (' + this.unit + ')',
min: 0
},
series: [
{
name: '总能耗',
type: this.monthChartType,
data: yData,
itemStyle: {
color: this.monthChartType === 'bar' ? '#409EFF' : '#67C23A'
},
markPoint: {
data: [
{ type: 'max', name: '最大值', itemStyle: { color: '#F56C6C' } },
{ type: 'min', name: '最小值', itemStyle: { color: '#909399' } }
],
label: {
formatter: '{b}: {c} ' + this.unit
}
},
smooth: this.monthChartType === 'line'
}
]
}
console.log(option)
this.monthChart.setOption(option)
},
// 初始化日度图表
initDayChart() {
if (!this.hasDayData) return
const xData = this.completedDayData.map(item => item.day)
const yData = this.completedDayData.map(item => item.totalConsumption)
const option = {
tooltip: {
trigger: 'axis',
formatter: '{b}: {c} ' + this.unit
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: xData,
axisLabel: {
rotate: 15
}
},
yAxis: {
type: 'value',
name: '能耗 (' + this.unit + ')',
min: 0
},
series: [
{
name: '总能耗',
type: this.dayChartType,
data: yData,
itemStyle: {
color: this.dayChartType === 'bar' ? '#409EFF' : '#67C23A'
},
markPoint: {
data: [
{ type: 'max', name: '最大值', itemStyle: { color: '#F56C6C' } },
{ type: 'min', name: '最小值', itemStyle: { color: '#909399' } }
],
label: {
formatter: '{b}: {c} ' + this.unit
}
},
smooth: this.dayChartType === 'line'
}
]
}
this.dayChart.setOption(option)
},
// 切换图表类型
toggleMonthChartType(type) {
this.monthChartType = type
this.initMonthChart()
},
toggleDayChartType(type) {
this.dayChartType = type
this.initDayChart()
},
// 窗口大小调整
handleWindowResize() {
if (this.monthChart) this.monthChart.resize()
if (this.dayChart) this.dayChart.resize()
},
// 对外暴露的刷新方法
refresh() {
this.fetchMonthData()
this.fetchDayData()
}
}
}
</script>
<style scoped>
.trends-container {
padding: 20px;
}
.chart-group {
margin-bottom: 30px;
}
.chart-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.chart-type-btn-group button {
margin-left: 10px;
}
.chart-wrapper {
width: 100%;
height: 300px;
border: 1px solid #eee;
border-radius: 4px;
position: relative;
}
.chart-container {
width: 100%;
height: 100%;
padding: 10px;
}
.no-data {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
</style>