✨ feat: 图表
This commit is contained in:
19
klp-ui/src/api/ems/dashboard/timer.js
Normal file
19
klp-ui/src/api/ems/dashboard/timer.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 近期能耗情况汇总
|
||||
export function getRecentEnergySummary(query) {
|
||||
return request({
|
||||
url: '/ems/energyConsumption/getEnergySummary',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 近期能耗趋势
|
||||
export function getRecentEnergyTrend(query) {
|
||||
return request({
|
||||
url: '/ems/energyConsumption/getEnergyTrend',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
@@ -1051,7 +1051,7 @@ body {
|
||||
// background-color: $--color-background-light;
|
||||
border-radius: 6px 6px 0 0;
|
||||
padding: 0 var(--spacing-base);
|
||||
box-shadow: inset 0 -1px 0 $--border-color-light;
|
||||
// box-shadow: inset 0 -1px 0 $--border-color-light;
|
||||
}
|
||||
|
||||
// 标签项
|
||||
@@ -1128,11 +1128,10 @@ body {
|
||||
|
||||
// 标签内容区
|
||||
.el-tabs__content {
|
||||
padding: var(--spacing-lg);
|
||||
// padding: var(--spacing-lg);
|
||||
background-color: $--metal-gradient-light;
|
||||
border: 1px solid $--border-color-light;
|
||||
border-radius: 0 0 6px 6px;
|
||||
min-height: 100px; // 确保有基本高度
|
||||
// border: 1px solid $--border-color-light;
|
||||
// border-radius: 0 0 6px 6px;
|
||||
}
|
||||
|
||||
// 卡片类型标签页
|
||||
@@ -1150,14 +1149,15 @@ body {
|
||||
.el-tabs__item {
|
||||
border: 1px solid $--border-color-light;
|
||||
border-bottom: none;
|
||||
border-radius: 6px 6px 0 0;
|
||||
// border-radius: 6px 6px 0 0;
|
||||
margin: 0 2px;
|
||||
background-color: $--metal-gradient-light;
|
||||
|
||||
&.is-active {
|
||||
border-color: $--border-color-light;
|
||||
border-bottom-color: $--metal-gradient-light;
|
||||
background-color: $--metal-gradient-light;
|
||||
background-color: $--color-primary;
|
||||
color: $--metal-gradient-light;
|
||||
position: relative;
|
||||
bottom: -1px;
|
||||
}
|
||||
|
||||
@@ -53,8 +53,5 @@ export default {
|
||||
return !this.sidebar.opened;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
console.log(this.sidebarRouters, sidebar);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -104,7 +104,7 @@ export default {
|
||||
filteredMenus() {
|
||||
const filterHidden = (menus) => {
|
||||
return menus
|
||||
.filter(menu => menu.hidden!== true)
|
||||
.filter(menu => menu.hidden !== true)
|
||||
.map(menu => {
|
||||
if (menu.children) {
|
||||
menu.children = filterHidden(menu.children)
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
<template>
|
||||
<el-row>
|
||||
<el-col :span="4">
|
||||
<el-select v-model="energyType" placeholder="请选择能源类型">
|
||||
<el-option v-for="item in energyTypeList" :key="item.energyTypeId" :label="item.name" :value="item.energyTypeId" />
|
||||
</el-select>
|
||||
<div style="padding: 10px;">
|
||||
<el-select v-model="energyType" placeholder="请选择能源类型" @change="refresh">
|
||||
<el-option v-for="item in energyTypeList" :key="item.energyTypeId" :label="item.name" :value="item.energyTypeId" />
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 区域选择,默认展开,最高高度为60%的屏幕尺寸 -->
|
||||
<el-tree @node-click="handleNodeClick" :data="locationList" :props="defaultProps" :default-expand-all="true" :style="{ height: 'calc(50vh - 50px)' }"></el-tree>
|
||||
@@ -17,19 +20,21 @@
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="20">
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane label="环比概况" name="1">
|
||||
</el-tab-pane>
|
||||
<el-col :span="20" v-if="showRight">
|
||||
<el-tabs v-model="activeTab" type="card">
|
||||
<el-tab-pane label="环比概况" name="1"></el-tab-pane>
|
||||
<el-tab-pane label="近期趋势" name="2"></el-tab-pane>
|
||||
<el-tab-pane label="同比分析" name="3"></el-tab-pane>
|
||||
<el-tab-pane label="环比分析" name="4"></el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<Overview v-if="activeTab === '1'" />
|
||||
<RecentTrend v-if="activeTab === '2'" />
|
||||
<YearToYear v-if="activeTab === '3'" />
|
||||
<MonthToMonth v-if="activeTab === '4'" />
|
||||
<Overview ref="overview" v-if="activeTab === '1'" :unit="energyUnit" :energyName="energyName" :energyType="energyType" :locationId="locationId" :deviceId="deviceId" />
|
||||
<RecentTrend ref="recentTrend" v-if="activeTab === '2'" :unit="energyUnit" :energyName="energyName" :energyType="energyType" :locationId="locationId" :deviceId="deviceId" />
|
||||
<YearToYear ref="yearToYear" v-if="activeTab === '3'" :unit="energyUnit" :energyName="energyName" :energyType="energyType" :locationId="locationId" :deviceId="deviceId" />
|
||||
<MonthToMonth ref="monthToMonth" v-if="activeTab === '4'" :unit="energyUnit" :energyName="energyName" :energyType="energyType" :locationId="locationId" :deviceId="deviceId" />
|
||||
</el-col>
|
||||
<el-col :span="20" v-else>
|
||||
<el-empty description="请选择能源类型和区域"></el-empty>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
@@ -39,10 +44,9 @@ import { listEnergyType } from "@/api/ems/energyType";
|
||||
import { listLocation } from "@/api/ems/location";
|
||||
|
||||
import Overview from "./panels/Overview.vue";
|
||||
import RecentTrend from "./panels/RecentTrend.vue";
|
||||
import YearToYear from "./panels/YearToYear.vue";
|
||||
import RecentTrend from "./panels/RecentTrends.vue";
|
||||
import YearToYear from "./panels/YearOnYear.vue";
|
||||
import MonthToMonth from "./panels/MonthToMonth.vue";
|
||||
import Overview from "./panels/Overview.vue";
|
||||
|
||||
export default {
|
||||
name: "Dashboard",
|
||||
@@ -57,6 +61,7 @@ export default {
|
||||
energyTypeList: [],
|
||||
energyType: '',
|
||||
locationId: '',
|
||||
deviceId: '',
|
||||
locationList: [
|
||||
],
|
||||
defaultProps: {
|
||||
@@ -70,11 +75,16 @@ export default {
|
||||
this.getEnergyTypeList();
|
||||
this.getLocationList();
|
||||
},
|
||||
watch: {
|
||||
// 当选择的区域变化时重新获取设备列表
|
||||
locationId: {
|
||||
|
||||
computed: {
|
||||
showRight() {
|
||||
return this.energyType && (this.deviceId || this.locationId);
|
||||
},
|
||||
energyUnit() {
|
||||
return this.energyTypeList.find(item => item.energyTypeId === this.energyType)?.unit;
|
||||
},
|
||||
energyName() {
|
||||
return this.energyTypeList.find(item => item.energyTypeId === this.energyType)?.name;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getEnergyTypeList() {
|
||||
@@ -84,13 +94,27 @@ export default {
|
||||
},
|
||||
getLocationList() {
|
||||
listLocation().then(response => {
|
||||
const data = { locationId: undefined, name: '顶级节点', children: [] };
|
||||
data.children = this.handleTree(response.data, "locationId", "parentId");
|
||||
this.locationList.push(data);
|
||||
this.locationList = this.handleTree(response.data, "locationId", "parentId");
|
||||
});
|
||||
},
|
||||
handleNodeClick(data) {
|
||||
this.locationId = data.locationId;
|
||||
this.deviceId = undefined;
|
||||
this.refresh();
|
||||
},
|
||||
refresh() {
|
||||
if (this.$refs.overview) {
|
||||
this.$refs.overview.refresh();
|
||||
}
|
||||
if (this.$refs.recentTrend) {
|
||||
this.$refs.recentTrend.refresh();
|
||||
}
|
||||
if (this.$refs.yearToYear) {
|
||||
this.$refs.yearToYear.refresh();
|
||||
}
|
||||
if (this.$refs.monthToMonth) {
|
||||
this.$refs.monthToMonth.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
<template>
|
||||
<div class="energy-analysis-container">
|
||||
<!-- 年份选择与查询按钮区域 -->
|
||||
<div class="query-section">
|
||||
<el-input
|
||||
v-model="year"
|
||||
placeholder="请输入年份"
|
||||
style="width: 200px; margin-right: 10px;"
|
||||
/>
|
||||
<el-button type="primary" @click="handleQuery">查询</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 图表区域(含图例、图表类型切换、刷新) -->
|
||||
<div class="chart-section">
|
||||
<el-row :gutter="10" align="middle" style="margin-bottom: 10px;">
|
||||
<!-- 图例 -->
|
||||
<el-col>
|
||||
<div class="legend-item">
|
||||
<span class="legend-dot" style="background-color: #409EFF;"></span>
|
||||
<span>本期</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-dot" style="background-color: #C084FC;"></span>
|
||||
<span>同期</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<!-- 图表类型切换 + 刷新 -->
|
||||
<el-col :offset="18">
|
||||
<el-button-group>
|
||||
<el-button type="text" @click="chartType = 'bar'">柱状图</el-button>
|
||||
<el-button type="text" @click="chartType = 'line'">折线图</el-button>
|
||||
</el-button-group>
|
||||
<el-button icon="el-icon-refresh" type="text" @click="handleRefresh">刷新</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- ECharts 容器 -->
|
||||
<div ref="chartRef" class="echarts-box" />
|
||||
</div>
|
||||
|
||||
<!-- 同比分析表格 -->
|
||||
<div class="table-section">
|
||||
<el-table :data="tableData" border style="width: 100%">
|
||||
<el-table-column prop="periodTime" label="本期时间" />
|
||||
<el-table-column prop="currentEnergy" label="本期能耗(Nm3)" />
|
||||
<el-table-column prop="samePeriodEnergy" label="同比能耗(Nm3)" />
|
||||
<el-table-column prop="yearOnYear" label="同比(%)" />
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import echarts from 'echarts'
|
||||
|
||||
export default {
|
||||
name: 'MonthToMonthAnalysis',
|
||||
props: {
|
||||
energyType: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
locationId: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
deviceId: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
unit: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
energyName: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
year: '2025',
|
||||
chartType: 'bar',
|
||||
tableData: [
|
||||
{ periodTime: '1月', currentEnergy: '--', samePeriodEnergy: '--', yearOnYear: '--' },
|
||||
{ periodTime: '2月', currentEnergy: '--', samePeriodEnergy: '--', yearOnYear: '--' },
|
||||
{ periodTime: '3月', currentEnergy: '--', samePeriodEnergy: '--', yearOnYear: '--' },
|
||||
{ periodTime: '4月', currentEnergy: '--', samePeriodEnergy: '--', yearOnYear: '--' },
|
||||
{ periodTime: '5月', currentEnergy: '--', samePeriodEnergy: '--', yearOnYear: '--' },
|
||||
{ periodTime: '6月', currentEnergy: '--', samePeriodEnergy: '--', yearOnYear: '--' },
|
||||
{ periodTime: '7月', currentEnergy: '--', samePeriodEnergy: '--', yearOnYear: '--' },
|
||||
{ periodTime: '8月', currentEnergy: '--', samePeriodEnergy: '--', yearOnYear: '--' },
|
||||
{ periodTime: '9月', currentEnergy: '--', samePeriodEnergy: '--', yearOnYear: '--' },
|
||||
{ periodTime: '10月', currentEnergy: '--', samePeriodEnergy: '--', yearOnYear: '--' },
|
||||
{ periodTime: '11月', currentEnergy: '--', samePeriodEnergy: '--', yearOnYear: '--' },
|
||||
{ periodTime: '12月', currentEnergy: '--', samePeriodEnergy: '--', yearOnYear: '--' }
|
||||
],
|
||||
chartInstance: null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initChart()
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.chartInstance) {
|
||||
this.chartInstance.dispose()
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
chartType: {
|
||||
handler(newVal) {
|
||||
this.drawChart()
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 初始化图表
|
||||
initChart() {
|
||||
this.chartInstance = echarts.init(this.$refs.chartRef)
|
||||
this.drawChart()
|
||||
// 监听窗口大小变化,调整图表尺寸
|
||||
window.addEventListener('resize', this.handleResize)
|
||||
},
|
||||
|
||||
// 绘制图表
|
||||
drawChart() {
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '本期',
|
||||
type: this.chartType,
|
||||
data: [120, 132, 101, 134, 90, 230, 210, 120, 132, 101, 134, 90],
|
||||
itemStyle: {
|
||||
color: '#409EFF'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '同期',
|
||||
type: this.chartType,
|
||||
data: [90, 110, 120, 110, 120, 140, 130, 90, 110, 120, 110, 120],
|
||||
itemStyle: {
|
||||
color: '#C084FC'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
this.chartInstance.setOption(option)
|
||||
},
|
||||
|
||||
// 处理查询
|
||||
handleQuery() {
|
||||
// 实际项目中这里会调用接口获取数据
|
||||
console.log('查询年份:', this.year)
|
||||
// 模拟数据加载
|
||||
this.tableData = this.tableData.map(item => ({
|
||||
...item,
|
||||
currentEnergy: Math.floor(Math.random() * 1000) + '',
|
||||
samePeriodEnergy: Math.floor(Math.random() * 1000) + '',
|
||||
yearOnYear: (Math.random() * 20 - 10).toFixed(2) + ''
|
||||
}))
|
||||
this.drawChart()
|
||||
},
|
||||
|
||||
// 刷新图表
|
||||
handleRefresh() {
|
||||
this.drawChart()
|
||||
},
|
||||
|
||||
// 处理窗口大小变化
|
||||
handleResize() {
|
||||
if (this.chartInstance) {
|
||||
this.chartInstance.resize()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.energy-analysis-container {
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.query-section {
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.chart-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.echarts-box {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
border: 1px solid #e6e6e6;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: inline-block;
|
||||
margin-right: 20px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.legend-dot {
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.table-section {
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,320 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 今日 vs 昨日 行 -->
|
||||
<div class="trend-row">
|
||||
<div class="data-card">
|
||||
<div class="data-value">{{ data.today }}</div>
|
||||
<div class="data-label">今日</div>
|
||||
</div>
|
||||
<div class="data-card">
|
||||
<div class="data-value">{{ data.yesterday }}</div>
|
||||
<div class="data-label">昨日</div>
|
||||
</div>
|
||||
<div class="trend-card" :class="todayTrendClass">
|
||||
<div class="trend-indicator">
|
||||
<i class="fas" :class="todayTrendIcon"></i>
|
||||
</div>
|
||||
<div class="trend-percent">{{ todayPercent }}</div>
|
||||
<div class="trend-diff">{{ todayDiff }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 本月 vs 上月 行 -->
|
||||
<div class="trend-row">
|
||||
<div class="data-card">
|
||||
<div class="data-value">{{ data.currentMonth }}</div>
|
||||
<div class="data-label">本月</div>
|
||||
</div>
|
||||
<div class="data-card">
|
||||
<div class="data-value">{{ data.lastMonth }}</div>
|
||||
<div class="data-label">上月</div>
|
||||
</div>
|
||||
<div class="trend-card" :class="monthTrendClass">
|
||||
<div class="trend-indicator">
|
||||
<i class="fas" :class="monthTrendIcon"></i>
|
||||
</div>
|
||||
<div class="trend-percent">{{ monthPercent }}</div>
|
||||
<div class="trend-diff">{{ monthDiff }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 本年 vs 去年 行 -->
|
||||
<div class="trend-row">
|
||||
<div class="data-card">
|
||||
<div class="data-value">{{ data.currentYear }}</div>
|
||||
<div class="data-label">本年</div>
|
||||
</div>
|
||||
<div class="data-card">
|
||||
<div class="data-value">{{ data.lastYear }}</div>
|
||||
<div class="data-label">去年</div>
|
||||
</div>
|
||||
<div class="trend-card" :class="yearTrendClass">
|
||||
<div class="trend-indicator">
|
||||
<i class="fas" :class="yearTrendIcon"></i>
|
||||
</div>
|
||||
<div class="trend-percent">{{ yearPercent }}</div>
|
||||
<div class="trend-diff">{{ yearDiff }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "TrendComparison",
|
||||
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
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
data: {} // 存储模拟数据
|
||||
};
|
||||
},
|
||||
created() {
|
||||
// 组件创建时获取模拟数据
|
||||
this.data = this.fetchMockData();
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 模拟API请求,生成六个数值
|
||||
* 生成规则:
|
||||
* - 今日:随机100-500之间的数值
|
||||
* - 昨日:基于今日数值上下浮动20%
|
||||
* - 本月:随机5000-20000之间的数值
|
||||
* - 上月:基于本月数值上下浮动30%
|
||||
* - 本年:随机100000-500000之间的数值
|
||||
* - 去年:基于本年数值上下浮动40%
|
||||
*/
|
||||
fetchMockData() {
|
||||
// 生成今日数据
|
||||
const today = parseFloat((Math.random() * 400 + 100).toFixed(2));
|
||||
|
||||
// 昨日数据:今日数据的80%-120%之间
|
||||
const yesterday = parseFloat((today * (0.8 + Math.random() * 0.4)).toFixed(2));
|
||||
|
||||
// 本月数据
|
||||
const currentMonth = parseFloat((Math.random() * 15000 + 5000).toFixed(2));
|
||||
|
||||
// 上月数据:本月数据的70%-130%之间
|
||||
const lastMonth = parseFloat((currentMonth * (0.7 + Math.random() * 0.6)).toFixed(2));
|
||||
|
||||
// 本年数据
|
||||
const currentYear = parseFloat((Math.random() * 400000 + 100000).toFixed(2));
|
||||
|
||||
// 去年数据:本年数据的60%-140%之间
|
||||
const lastYear = parseFloat((currentYear * (0.6 + Math.random() * 0.8)).toFixed(2));
|
||||
|
||||
return {
|
||||
today,
|
||||
yesterday,
|
||||
currentMonth,
|
||||
lastMonth,
|
||||
currentYear,
|
||||
lastYear
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化数值显示,添加千位分隔符
|
||||
* @param {Number} num 要格式化的数值
|
||||
* @returns {String} 格式化后的字符串
|
||||
*/
|
||||
formatNumber(num) {
|
||||
return num.toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
});
|
||||
},
|
||||
|
||||
// 对外暴露的刷新方法
|
||||
refresh() {
|
||||
this.data = this.fetchMockData();
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// ---- 今日 vs 昨日 计算 ----
|
||||
todayDiff() {
|
||||
const diff = this.data.today - this.data.yesterday;
|
||||
return diff >= 0
|
||||
? `+${this.formatNumber(diff)}`
|
||||
: `${this.formatNumber(diff)}`;
|
||||
},
|
||||
todayPercent() {
|
||||
if (this.data.yesterday === 0) return "0.00%";
|
||||
const percent = ((this.data.today - this.data.yesterday) / this.data.yesterday) * 100;
|
||||
return percent >= 0
|
||||
? `+${percent.toFixed(2)}%`
|
||||
: `${percent.toFixed(2)}%`;
|
||||
},
|
||||
todayTrendClass() {
|
||||
return this.data.today > this.data.yesterday ? "trend-up" : "trend-down";
|
||||
},
|
||||
todayTrendIcon() {
|
||||
return this.data.today > this.data.yesterday ? "fa-arrow-up" : "fa-arrow-down";
|
||||
},
|
||||
|
||||
// ---- 本月 vs 上月 计算 ----
|
||||
monthDiff() {
|
||||
const diff = this.data.currentMonth - this.data.lastMonth;
|
||||
return diff >= 0
|
||||
? `+${this.formatNumber(diff)}`
|
||||
: `${this.formatNumber(diff)}`;
|
||||
},
|
||||
monthPercent() {
|
||||
if (this.data.lastMonth === 0) return "0.00%";
|
||||
const percent = ((this.data.currentMonth - this.data.lastMonth) / this.data.lastMonth) * 100;
|
||||
return percent >= 0
|
||||
? `+${percent.toFixed(2)}%`
|
||||
: `${percent.toFixed(2)}%`;
|
||||
},
|
||||
monthTrendClass() {
|
||||
return this.data.currentMonth > this.data.lastMonth ? "trend-up" : "trend-down";
|
||||
},
|
||||
monthTrendIcon() {
|
||||
return this.data.currentMonth > this.data.lastMonth ? "fa-arrow-up" : "fa-arrow-down";
|
||||
},
|
||||
|
||||
// ---- 本年 vs 去年 计算 ----
|
||||
yearDiff() {
|
||||
const diff = this.data.currentYear - this.data.lastYear;
|
||||
return diff >= 0
|
||||
? `+${this.formatNumber(diff)}`
|
||||
: `${this.formatNumber(diff)}`;
|
||||
},
|
||||
yearPercent() {
|
||||
if (this.data.lastYear === 0) return "0.00%";
|
||||
const percent = ((this.data.currentYear - this.data.lastYear) / this.data.lastYear) * 100;
|
||||
return percent >= 0
|
||||
? `+${percent.toFixed(2)}%`
|
||||
: `${percent.toFixed(2)}%`;
|
||||
},
|
||||
yearTrendClass() {
|
||||
return this.data.currentYear > this.data.lastYear ? "trend-up" : "trend-down";
|
||||
},
|
||||
yearTrendIcon() {
|
||||
return this.data.currentYear > this.data.lastYear ? "fa-arrow-up" : "fa-arrow-down";
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.trend-container {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
max-width: 800px;
|
||||
margin: 20px auto;
|
||||
padding: 20px;
|
||||
background-color: #ffffff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.trend-title {
|
||||
color: #333333;
|
||||
font-size: 18px;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.trend-row {
|
||||
display: flex;
|
||||
margin-bottom: 15px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.trend-row:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.data-card, .trend-card {
|
||||
padding: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.data-card {
|
||||
flex: 1;
|
||||
background-color: #f9fafb;
|
||||
border-right: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.trend-card {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.data-value {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: #111827;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.data-label {
|
||||
font-size: 14px;
|
||||
color: #6b7280;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.trend-indicator {
|
||||
margin-bottom: 5px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.trend-percent {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.trend-diff {
|
||||
font-size: 14px;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
/* 趋势样式 */
|
||||
.trend-up {
|
||||
background-color: rgba(16, 185, 129, 0.05);
|
||||
}
|
||||
|
||||
.trend-up .trend-percent,
|
||||
.trend-up .trend-indicator {
|
||||
color: #10b981; /* 绿色表示上升 */
|
||||
}
|
||||
|
||||
.trend-down {
|
||||
background-color: rgba(239, 68, 68, 0.05);
|
||||
}
|
||||
|
||||
.trend-down .trend-percent,
|
||||
.trend-down .trend-indicator {
|
||||
color: #ef4444; /* 红色表示下降 */
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
同比分析
|
||||
</div>
|
||||
</template>
|
||||
@@ -114,12 +114,12 @@
|
||||
<el-table-column label="消耗量" align="center" prop="consumption" />
|
||||
<el-table-column label="起始时间" align="center" prop="startTime" width="180">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d}') }}</span>
|
||||
<span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="结束时间" align="center" prop="endTime" width="180">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d}') }}</span>
|
||||
<span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="记录人" align="center" prop="recordedBy" />
|
||||
|
||||
@@ -68,85 +68,82 @@
|
||||
icon="el-icon-plus"
|
||||
size="mini"
|
||||
@click="handleAdd"
|
||||
>新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
icon="el-icon-edit"
|
||||
size="mini"
|
||||
:disabled="single"
|
||||
@click="handleUpdate"
|
||||
>修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="el-icon-delete"
|
||||
size="mini"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete"
|
||||
>删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
icon="el-icon-download"
|
||||
size="mini"
|
||||
@click="handleExport"
|
||||
>导出</el-button>
|
||||
>新增设备</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="meterList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="设备编号" align="center" prop="meterCode" />
|
||||
<el-table-column label="能源类型" align="center" prop="energyTypeId">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ getEnergy(scope.row.energyTypeId).name }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="安装位置" align="center" prop="locationId">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ getLocationName(scope.row.locationId) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="设备型号" align="center" prop="model" />
|
||||
<el-table-column label="制造商" align="center" prop="manufacturer" />
|
||||
<el-table-column label="安装日期" align="center" prop="installDate" width="180">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.installDate, '{y}-{m}-{d}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="设备状态" align="center" prop="status" />
|
||||
<el-table-column label="校准日期" align="center" prop="lastCalibrationDate" width="180">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.lastCalibrationDate, '{y}-{m}-{d}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="阈值" align="center" prop="thresholdValue" />
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-edit"
|
||||
@click="handleUpdate(scope.row)"
|
||||
>修改</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 卡片布局 -->
|
||||
<div v-loading="loading" class="card-container">
|
||||
<el-row :gutter="20">
|
||||
<el-col
|
||||
v-for="item in meterList"
|
||||
:key="item.meterId"
|
||||
:xs="24"
|
||||
:sm="12"
|
||||
:md="6"
|
||||
:lg="4"
|
||||
class="card-col"
|
||||
>
|
||||
<el-card class="meter-card" shadow="hover">
|
||||
<div class="card-header">
|
||||
<div class="device-code">{{ item.meterCode }}</div>
|
||||
<div class="device-status">
|
||||
{{ getEnergy(item.energyTypeId).name }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<div class="card-item">
|
||||
<span class="item-label">安装位置:</span>
|
||||
<span class="item-value">{{ getLocationName(item.locationId) }}</span>
|
||||
</div>
|
||||
<div class="card-item">
|
||||
<span class="item-label">设备型号:</span>
|
||||
<span class="item-value">{{ item.model }}</span>
|
||||
</div>
|
||||
<div class="card-item">
|
||||
<span class="item-label">制造商:</span>
|
||||
<span class="item-value">{{ item.manufacturer }}</span>
|
||||
</div>
|
||||
<div class="card-item">
|
||||
<span class="item-label">安装日期:</span>
|
||||
<span class="item-value">{{ parseTime(item.installDate, '{y}-{m}-{d}') }}</span>
|
||||
</div>
|
||||
<div class="card-item">
|
||||
<span class="item-label">校准日期:</span>
|
||||
<span class="item-value">{{ parseTime(item.lastCalibrationDate, '{y}-{m}-{d}') }}</span>
|
||||
</div>
|
||||
<div class="card-item">
|
||||
<span class="item-label">阈值:</span>
|
||||
<span class="item-value">{{ item.thresholdValue }}</span>
|
||||
</div>
|
||||
<div class="card-item">
|
||||
<span class="item-label">备注:</span>
|
||||
<span class="item-value">{{ item.remark || '无' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-actions">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="primary"
|
||||
icon="el-icon-edit"
|
||||
@click="handleUpdate(item)"
|
||||
plain
|
||||
>编辑</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
@click="handleDelete(item)"
|
||||
plain
|
||||
>删除</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<pagination
|
||||
v-show="total>0"
|
||||
@@ -156,7 +153,7 @@
|
||||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<!-- 添加或修改计量设备(阈值移至此处)对话框 -->
|
||||
<!-- 添加或修改计量设备对话框 -->
|
||||
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="设备编号" prop="meterCode">
|
||||
@@ -168,8 +165,7 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="安装位置" prop="locationId">
|
||||
<!-- <el-input v-model="form.locationId" placeholder="请输入安装位置" /> -->
|
||||
<treeselect v-model="form.locationId" :options="locationOptions" :normalizer="normalizer" placeholder="请选择安装位置" />
|
||||
<treeselect v-model="form.locationId" :options="locationOptions" :normalizer="normalizer" placeholder="请选择安装位置" />
|
||||
</el-form-item>
|
||||
<el-form-item label="设备型号" prop="model">
|
||||
<el-input v-model="form.model" placeholder="请输入设备型号" />
|
||||
@@ -229,17 +225,11 @@ export default {
|
||||
buttonLoading: false,
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 选中数组
|
||||
ids: [],
|
||||
// 非单个禁用
|
||||
single: true,
|
||||
// 非多个禁用
|
||||
multiple: true,
|
||||
// 显示搜索条件
|
||||
showSearch: true,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 计量设备(阈值移至此处)表格数据
|
||||
// 计量设备表格数据
|
||||
meterList: [],
|
||||
// 弹出层标题
|
||||
title: "",
|
||||
@@ -263,6 +253,24 @@ export default {
|
||||
form: {},
|
||||
// 表单校验
|
||||
rules: {
|
||||
meterCode: [
|
||||
{ required: true, message: "设备编号不能为空", trigger: "blur" }
|
||||
],
|
||||
energyTypeId: [
|
||||
{ required: true, message: "能源类型不能为空", trigger: "change" }
|
||||
],
|
||||
locationId: [
|
||||
{ required: true, message: "安装位置不能为空", trigger: "change" }
|
||||
],
|
||||
model: [
|
||||
{ required: true, message: "设备型号不能为空", trigger: "blur" }
|
||||
],
|
||||
installDate: [
|
||||
{ required: true, message: "安装日期不能为空", trigger: "change" }
|
||||
],
|
||||
thresholdValue: [
|
||||
{ required: true, message: "阈值不能为空", trigger: "blur" }
|
||||
]
|
||||
}
|
||||
};
|
||||
},
|
||||
@@ -272,7 +280,7 @@ export default {
|
||||
this.getEnergyTypeList();
|
||||
},
|
||||
methods: {
|
||||
/** 查询计量设备(阈值移至此处)列表 */
|
||||
/** 查询计量设备列表 */
|
||||
getList() {
|
||||
this.loading = true;
|
||||
listMeter(this.queryParams).then(response => {
|
||||
@@ -342,28 +350,22 @@ export default {
|
||||
this.resetForm("queryForm");
|
||||
this.handleQuery();
|
||||
},
|
||||
// 多选框选中数据
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.meterId)
|
||||
this.single = selection.length!==1
|
||||
this.multiple = !selection.length
|
||||
},
|
||||
/** 新增按钮操作 */
|
||||
handleAdd() {
|
||||
this.reset();
|
||||
this.open = true;
|
||||
this.title = "添加计量设备(阈值移至此处)";
|
||||
this.title = "添加计量设备";
|
||||
},
|
||||
/** 修改按钮操作 */
|
||||
handleUpdate(row) {
|
||||
this.loading = true;
|
||||
this.reset();
|
||||
const meterId = row.meterId || this.ids
|
||||
const meterId = row.meterId;
|
||||
getMeter(meterId).then(response => {
|
||||
this.loading = false;
|
||||
this.form = response.data;
|
||||
this.open = true;
|
||||
this.title = "修改计量设备(阈值移至此处)";
|
||||
this.title = "修改计量设备";
|
||||
});
|
||||
},
|
||||
/** 提交按钮 */
|
||||
@@ -392,17 +394,38 @@ export default {
|
||||
});
|
||||
},
|
||||
getLocationName(id) {
|
||||
return this.locationList.find(item => item.locationId === id).name ?? '';
|
||||
const location = this.locationList.find(item => item.locationId === id);
|
||||
return location ? location.name : '';
|
||||
},
|
||||
getEnergy(id) {
|
||||
return this.energyTypeList.find(item => item.energyTypeId === id) ?? {};
|
||||
const energy = this.energyTypeList.find(item => item.energyTypeId === id);
|
||||
return energy || { name: '' };
|
||||
},
|
||||
getStatusClass(status) {
|
||||
if (status === '正常') return 'status-normal';
|
||||
if (status === '故障') return 'status-fault';
|
||||
if (status === '维护中') return 'status-maintenance';
|
||||
if (status === '离线') return 'status-offline';
|
||||
return '';
|
||||
},
|
||||
parseTime(time, format) {
|
||||
if (!time) return '';
|
||||
const date = new Date(time);
|
||||
const year = date.getFullYear();
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
||||
const day = date.getDate().toString().padStart(2, '0');
|
||||
|
||||
if (format === '{y}-{m}-{d}') {
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
return `${year}-${month}-${day}`;
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
const meterIds = row.meterId || this.ids;
|
||||
this.$modal.confirm('是否确认删除计量设备(阈值移至此处)编号为"' + meterIds + '"的数据项?').then(() => {
|
||||
const meterId = row.meterId;
|
||||
this.$modal.confirm('是否确认删除计量设备编号为"' + row.meterCode + '"的数据项?').then(() => {
|
||||
this.loading = true;
|
||||
return delMeter(meterIds);
|
||||
return delMeter(meterId);
|
||||
}).then(() => {
|
||||
this.loading = false;
|
||||
this.getList();
|
||||
@@ -411,13 +434,145 @@ export default {
|
||||
}).finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
/** 导出按钮操作 */
|
||||
handleExport() {
|
||||
this.download('ems/meter/export', {
|
||||
...this.queryParams
|
||||
}, `meter_${new Date().getTime()}.xlsx`)
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.card-container {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.card-col {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.meter-card {
|
||||
height: 100%;
|
||||
border-radius: 0px;
|
||||
transition: all 0.3s ease;
|
||||
border: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.meter-card:hover {
|
||||
transform: translateY(-5px);
|
||||
/* box-shadow: 0 10px 20px rgba(0, 0, 0, 0.12); */
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-bottom: 5px;
|
||||
border-bottom: 1px solid #eee;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.device-code {
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
color: #1a73e8;
|
||||
}
|
||||
|
||||
.device-status {
|
||||
padding: 4px 10px;
|
||||
border-radius: 15px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-normal {
|
||||
background-color: #e8f5e9;
|
||||
color: #2e7d32;
|
||||
}
|
||||
|
||||
.status-fault {
|
||||
background-color: #ffebee;
|
||||
color: #c62828;
|
||||
}
|
||||
|
||||
.status-maintenance {
|
||||
background-color: #fff8e1;
|
||||
color: #f57f17;
|
||||
}
|
||||
|
||||
.status-offline {
|
||||
background-color: #f5f5f5;
|
||||
color: #757575;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.card-item {
|
||||
display: flex;
|
||||
margin-bottom: 5px;
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.item-label {
|
||||
font-weight: bold;
|
||||
min-width: 80px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.item-value {
|
||||
flex-grow: 1;
|
||||
word-break: break-word;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-top: 15px;
|
||||
margin-top: 10px;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
.card-actions .el-button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.card-col {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card-item {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.item-label {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.card-actions .el-button {
|
||||
margin-left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.el-card__body {
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -69,7 +69,7 @@
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="订单明细" name="orderList">
|
||||
<order-detail-list :orderId="currentOrder.orderId" />
|
||||
<order-detail-list :orderId="currentOrder.orderId" :editable="false" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="应收明细" name="receivable">
|
||||
<KLPTable v-loading="rightLoading" :data="currentOrder.receivables" empty-text="暂无数据">
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</el-descriptions-item>
|
||||
</el-descriptions> -->
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-row :gutter="10" class="mb8" v-if="editable">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="el-icon-plus" size="mini" :disabled="!canEdit"
|
||||
@click="handleAdd">新增</el-button>
|
||||
@@ -155,6 +155,10 @@ export default {
|
||||
orderId: {
|
||||
type: [String, Number],
|
||||
required: true
|
||||
},
|
||||
editable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
components: {
|
||||
|
||||
Reference in New Issue
Block a user