✨ feat: 图表
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user