feat(HT.UI): 改造曲线代码,原本的曲线代码copy备份,另一种备选方案用good

- 需求:历史曲线,比如说停电3个小时,他就没有数据了,他想把这个停电状态在历史曲线上显示出来,显示0,然后标红吧,就是本次收到数据和上次收到数据时间差1小时以上就标红
master
zch 2 months ago
parent 53f8d5ab06
commit dc79271196

@ -0,0 +1,676 @@
<template>
<div class="app-container">
<el-row :gutter="28">
<el-col :span="4.3" :xs="24">
<div class="head-container">
<el-input
v-model="workUnitName"
placeholder="请输入计量设备名称"
clearable
size="small"
prefix-icon="el-icon-search"
style="margin-bottom: 20px"
/>
</div>
<div class="head-container">
<el-tree
:data="monitorInfoOptions"
:props="monitorProps"
:expand-on-click-node="false"
:filter-node-method="filterNode"
ref="tree"
node-key="id"
default-expand-all
highlight-current
@node-click="handleNodeClick"
/>
</div>
</el-col>
<el-col :span="19" :xs="24">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch"
label-width="100px"
>
<!-- <el-form-item label="计量设备编号" prop="monitorCode">-->
<!-- <el-input-->
<!-- v-model="queryParams.monitorCode"-->
<!-- placeholder="请输入计量设备编号"-->
<!-- clearable-->
<!-- @keyup.enter.native="handleQuery"-->
<!-- />-->
<!-- </el-form-item>-->
<el-form-item label="采集时间">
<date-picker
v-model="daterangeRecordTime"
range
type="datetime"
format="YYYY-MM-DD HH:mm:ss"
value-type="YYYY-MM-DD HH:mm:ss"
:placeholder="['开始时间', '结束时间']"
:lang="lang"
style="width: 340px"
></date-picker>
</el-form-item>
<!-- <el-form-item label="采集日期范围">
<el-date-picker
v-model="daterangeCollectTime"
style="width: 340px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>-->
<!-- <el-form-item label="记录时间范围">
<el-time-picker
v-model="timerangeRecordTime"
style="width: 340px"
value-format="HH:mm:ss"
is-range
range-separator="-"
start-placeholder="开始时间"
end-placeholder="结束时间"
></el-time-picker>
</el-form-item>-->
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<Chart ref="Chart1" class="chart1"/>
<Chart ref="Chart2" class="chart2"/>
<Chart ref="Chart3" class="chart3"/>
</el-col>
</el-row>
</div>
</template>
<script>
import { getMonitorInfoTree } from '@/api/ems/base/baseMonitorInfo'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import { parseTime } from '@/utils/ruoyi'
import Chart from '@/components/Charts/Chart'
import * as echarts from 'echarts'
import {pointSteamInstantList} from "@/api/ems/report/reportPointSteam";
import {listRecordSteamInstant, steamInstantAvg, steamInstantList} from "@/api/ems/record/recordSteamInstant";
import DatePicker from 'vue2-datepicker';
import 'vue2-datepicker/index.css';
import 'vue2-datepicker/locale/zh-cn';
export default {
name: 'currentSteamCurve',
dicts: ['collect_type'],
components: {
Chart,
Treeselect,
DatePicker
},
data() {
return {
//List
baseMonitorInfoOptions: [],
//List
monitorInfoOptions: [],
workUnitName: undefined,
//
selectMonitorName: null,
//
baseMonitorInfoList: [],
//
daterangeCollectTime: [],
//
daterangeRecordTime: [],
//
timerangeRecordTime:[],
//
loading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
dataList: [],
//
title: '',
//
open: false,
//
queryParams: {
pageNum: 1,
pageSize: 10,
monitorCode: null,
monitorName: null,
collectTime: null,
fluxFlow: null,
steamFlow: null,
heatInstantValue: null,
heatTotalValue: null,
temperature: null,
press: null,
density: null,
differencePress: null,
recordTime: null
},
//
form: {},
monitorProps: {
children: 'children',
label: 'label'
},
//
rules: {
/* objId: [
{ required: true, message: '编号不能为空', trigger: 'blur' }
]*/
},
columns: [
{ key: 0, label: `自增标识`, visible: false },
{ key: 1, label: `计量设备编号`, visible: true },
{ key: 2, label: `采集时间`, visible: false },
{ key: 3, label: `瞬时流量`, visible: true },
{ key: 4, label: `累计流量`, visible: true },
{ key: 5, label: `瞬时热量`, visible: true },
{ key: 6, label: `累计热量`, visible: true },
{ key: 7, label: `温度`, visible: false },
{ key: 8, label: `压力`, visible: false },
{ key: 9, label: `密度`, visible: false },
{ key: 10, label: `压力差值`, visible: false },
{ key: 11, label: `记录时间`, visible: true },
{ key: 12, label: `计量设备名称`, visible: true }
],
lang: {
formatLocale: {
firstDayOfWeek: 1
},
monthBeforeYear: false
}
}
},
created() {
const nowDate = new Date();
const today = parseTime(new Date(), '{y}-{m}-{d}')
const lastDate = new Date();
lastDate.setDate(nowDate.getDate() - 1)
const yesterday = parseTime(lastDate, '{y}-{m}-{d}')
console.log(today,yesterday)
this.daterangeRecordTime[0] = yesterday+ ' 08:00:00'
this.daterangeRecordTime[1] = today + ' 08:00:00'
/* this.daterangeCollectTime[0] = parseTime(yesterday, '{y}-{m}-{d}') ;
this.daterangeCollectTime[1] = parseTime(today, '{y}-{m}-{d}') ;
//8
this.timerangeRecordTime[0] = '08:00:00';
this.timerangeRecordTime[1] = '08:00:00';*/
this.getTreeselect()
this.getTreeMonitorInfo()
this.getList()
},
watch: {
//
workUnitName(val) {
this.$refs.tree.filter(val)
}
},
methods: {
/** 转换计量设备信息数据结构 */
normalizer(node) {
if (node.children && !node.children.length) {
delete node.children
}
return {
id: node.monitorCode,
label: node.monitorName,
children: node.children
}
},
/** 查询电实时数据列表 */
getList() {
this.loading = true
this.queryParams.params = {}
/* if (null != this.daterangeCollectTime && '' != this.daterangeCollectTime) {
this.queryParams.params['beginCollectTime'] =this.daterangeCollectTime[0] + ' ' + this.timerangeRecordTime[0];
this.queryParams.params['endCollectTime'] =this.daterangeCollectTime[1] + ' ' + this.timerangeRecordTime[1];
}*/
if (null != this.daterangeRecordTime && '' != this.daterangeRecordTime) {
this.queryParams.params['beginRecordTime'] =this.daterangeRecordTime[0];
this.queryParams.params['endRecordTime'] =this.daterangeRecordTime[1];
}
this.getChart()
},
//
cancel() {
this.open = false
this.reset()
},
//
reset() {
this.form = {
objId: null,
monitorCode: null,
instrumentValue: null,
expend: null,
recordTime: null,
beginTime: null,
endTime: null,
updateFlag: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null
}
this.resetForm('form')
},
/** 搜索按钮操作 */
handleQuery() {
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.queryParams.monitorCode = null
this.resetForm('queryForm')
this.handleQuery()
},
/** 导出按钮操作 */
handleExport() {
this.download('ems/record/recordSteamInstant/export', {
...this.queryParams
}, `recordSteamInstant_${new Date().getTime()}.xlsx`)
},
/** 查询计量设备信息下拉树结构 */
getTreeselect() {
getMonitorInfoTree({ monitorType: 4 }).then(response => {
this.monitorInfoOptions = response.data
})
},
/** 筛选节点 */
filterNode(value, data) {
if (!value) return true
return data.label.indexOf(value) !== -1
},
/** 节点单击事件 */
handleNodeClick(data) {
this.queryParams.monitorCode = data.code
this.selectMonitorName = data.label
this.handleQuery()
},
/** 曲线 */
async getChart() {
let query = JSON.parse(JSON.stringify(this.queryParams))
const {data} = await steamInstantList(query)
let option1 = {
title: {
text: this.selectMonitorName + ' 瞬时流量' + ' (平均值:'+
((data.map(e=>parseFloat(e.fluxFlow)).reduce((a,b)=>a+b,0))/data.length).toFixed(2)+")",
x: 'center',
textStyle: {
fontSize: 15 //
}
},
grid: {
top: '15%',
bottom: '10%',
left: '10%',
right: '3%'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
label: {
show: true
}
}
},
dataZoom: [{
type: 'slider'
}],
legend: {
right: 0
},
xAxis: {
data: data.map(e => e.recordTime),
axisLine: {
show: true, //X线
lineStyle: {
color: '#000000'
}
},
axisTick: {
show: true //X
},
axisLabel: {
show: true,
textStyle: {
color: '#000000' //X
}
}
},
yAxis: [
{
type: 'value',
name: '瞬时流量y',
nameTextStyle: {
color: '#000000'
},
splitLine: {
show: false
},
axisTick: {
show: true
},
axisLine: {
show: true,
lineStyle: {
color: '#000000'
}
},
axisLabel: {
show: true,
textStyle: {
color: '#000000'
}
}
}
],
series: [
{
name: '瞬时流量',
type: 'line',
smooth: true, //线
showAllSymbol: true, //
symbol: 'circle', //
symbolSize: 0, //
data: data.map(e => e.fluxFlow)
},
]
}
let option2 = {
/* 使 filter temperature undefined null
title: {
text: this.selectMonitorName + ' 温度' + ' (平均值:' +
((data.filter(e => e.temperature !== undefined && e.temperature !== null)
.map(e => parseFloat(e.temperature))
.reduce((a, b) => a + b, 0)) /
data.filter(e => e.temperature !== undefined && e.temperature !== null).length).toFixed(2) + ")",
x: 'center'
},*/
title: {
text: this.selectMonitorName + ' 温度' + ' (平均值:'+
((data.map(e=>parseFloat(e.temperature)).reduce((a,b)=>a+b,0))/data.length).toFixed(2)+")",
x: 'center',
textStyle: {
fontSize: 15 //
}
},
grid: {
top: '15%',
bottom: '10%',
left: '10%',
right: '3%'
},
dataZoom: [{
type: 'slider'
}],
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
label: {
show: true
}
}
},
legend: {
right: 0
},
xAxis: {
data: data.map(e => e.recordTime),
axisLine: {
show: true, //X线
lineStyle: {
color: '#000000'
}
},
axisTick: {
show: true //X
},
axisLabel: {
show: true,
textStyle: {
color: '#000000' //X
}
}
},
yAxis: [
{
type: 'value',
name: '温度y',
nameTextStyle: {
color: '#000000'
},
splitLine: {
show: false
},
axisTick: {
show: true
},
axisLine: {
show: true,
lineStyle: {
color: '#000000'
}
},
axisLabel: {
show: true,
textStyle: {
color: '#000000'
}
}
}
],
series: [
{
name: '温度',
type: 'line',
smooth: true, //线
showAllSymbol: true, //
symbol: 'circle', //
symbolSize: 0, //
data: data.map(e => e.temperature)
},
]
}
let option3 = {
title: {
text: this.selectMonitorName + ' 压力' + ' (平均值:'+
((data.map(e=>parseFloat(e.press)).reduce((a,b)=>a+b,0))/data.length).toFixed(2)+")",
x: 'center',
textStyle: {
fontSize: 15 //
}
},
grid: {
top: '15%',
bottom: '10%',
left: '10%',
right: '3%'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
label: {
show: true
}
}
},
dataZoom: [{
type: 'slider'
}],
legend: {
right: 0
},
xAxis: {
data: data.map(e => e.recordTime),
axisLine: {
show: true, //X线
lineStyle: {
color: '#000000'
}
},
axisTick: {
show: true //X
},
axisLabel: {
show: true,
textStyle: {
color: '#000000' //X
}
}
},
yAxis: [
{
type: 'value',
name: '压力y',
nameTextStyle: {
color: '#000000'
},
splitLine: {
show: false
},
axisTick: {
show: true
},
axisLine: {
show: true,
lineStyle: {
color: '#000000'
}
},
axisLabel: {
show: true,
textStyle: {
color: '#000000'
}
}
}
],
series: [
{
name: '压力',
type: 'line',
smooth: true, //线
showAllSymbol: true, //
symbol: 'circle', //
symbolSize: 0, //
data: data.map(e => e.press)
},
]
}
this.$refs.Chart1.setData(option1)
this.$refs.Chart2.setData(option2)
this.$refs.Chart3.setData(option3)
echarts.connect(this.$refs.Chart1.chart, this.$refs.Chart2.chart, this.$refs.Chart3.chart)
/* this.$refs.Chart1.chart.on('datazoom', (e) => {
option.dataZoom[0].start = e.start;
option.dataZoom[0].end = e.end;
this.$refs.Chart1.setData(option);
});*/
this.$refs.Chart1.chart.on('datazoom', (e) => {
option2.dataZoom[0].start = e.start
option2.dataZoom[0].end = e.end
this.$refs.Chart2.setData(option2)
option3.dataZoom[0].start = e.start
option3.dataZoom[0].end = e.end
this.$refs.Chart3.setData(option3)
})
this.$refs.Chart2.chart.on('datazoom', (e) => {
option1.dataZoom[0].start = e.start
option1.dataZoom[0].end = e.end
this.$refs.Chart1.setData(option1)
option3.dataZoom[0].start = e.start
option3.dataZoom[0].end = e.end
this.$refs.Chart3.setData(option3)
})
this.$refs.Chart3.chart.on('datazoom', (e) => {
option2.dataZoom[0].start = e.start
option2.dataZoom[0].end = e.end
this.$refs.Chart2.setData(option2)
option1.dataZoom[0].start = e.start
option1.dataZoom[0].end = e.end
this.$refs.Chart1.setData(option1)
})
}
}
}
</script>
<style scoped >
.chart1 {
width: 50%;
height: 40vh;
display: inline-block;
}
.chart2 {
width: 50%;
height: 40vh;
display: inline-block;
}
.chart3 {
width: 50%;
height: 40vh;
display: inline-block;
}
/* 选中节点的样式 */
/deep/ .el-tree .el-tree-node.is-current > .el-tree-node__content {
background-color: #67C23A !important; /* 选中节点的背景色 */
color: #303133 !important; /* 选中节点的文字颜色 */
padding: 0; /* 清除内边距 */
margin: 0; /* 清除外边距 */
z-index: 1; /* 确保在其他元素之上 */
}
/* 鼠标悬停时的样式 */
.el-tree .el-tree-node__content:hover {
background-color: #e6f7ff; /* 鼠标悬停时的背景色 */
}
</style>

@ -0,0 +1,843 @@
<template>
<div class="app-container">
<el-row :gutter="28">
<el-col :span="4.3" :xs="24">
<div class="head-container">
<el-input
v-model="workUnitName"
placeholder="请输入计量设备名称"
clearable
size="small"
prefix-icon="el-icon-search"
style="margin-bottom: 20px"
/>
</div>
<div class="head-container">
<el-tree
:data="monitorInfoOptions"
:props="monitorProps"
:expand-on-click-node="false"
:filter-node-method="filterNode"
ref="tree"
node-key="id"
default-expand-all
highlight-current
@node-click="handleNodeClick"
/>
</div>
</el-col>
<el-col :span="19" :xs="24">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch"
label-width="100px"
>
<el-form-item label="记录时间">
<date-picker
v-model="daterangeRecordTime"
range
type="datetime"
format="YYYY-MM-DD HH:mm:ss"
value-type="YYYY-MM-DD HH:mm:ss"
:placeholder="['开始时间', '结束时间']"
:lang="lang"
style="width: 340px"
></date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<!-- 图表联动提示 -->
<el-alert
v-if="powerOutageSummary.count > 0"
title="图表已联动,缩放任一图表将同步其它图表"
type="info"
:closable="false"
show-icon
style="margin-bottom: 10px"
/>
<!-- 停电统计信息 -->
<el-card v-if="powerOutageSummary.count > 0" class="power-outage-summary" shadow="hover">
<div slot="header">
<span><i class="el-icon-lightning" style="color: red;"></i> 停电统计</span>
<el-button style="float: right; padding: 3px 0" type="text" @click="exportPowerOutageData">
导出停电记录
</el-button>
</div>
<div class="power-outage-info">
<p><strong>停电次数:</strong> {{ powerOutageSummary.count }} </p>
<p><strong>最长停电:</strong> {{ powerOutageSummary.longestDuration }} 小时</p>
<p><strong>总停电时长:</strong> {{ powerOutageSummary.totalDuration }} 小时</p>
</div>
</el-card>
<!-- 图表区域 -->
<div class="charts-container">
<Chart ref="Chart1" class="chart1"/>
<Chart ref="Chart2" class="chart2"/>
<Chart ref="Chart3" class="chart3"/>
</div>
</el-col>
</el-row>
</div>
</template>
<script>
import { getMonitorInfoTree } from '@/api/ems/base/baseMonitorInfo'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import { parseTime } from '@/utils/ruoyi'
import Chart from '@/components/Charts/Chart'
import * as echarts from 'echarts'
import {pointSteamInstantList} from "@/api/ems/report/reportPointSteam";
import {listRecordSteamInstant, steamInstantAvg, steamInstantList} from "@/api/ems/record/recordSteamInstant";
import DatePicker from 'vue2-datepicker';
import 'vue2-datepicker/index.css';
import 'vue2-datepicker/locale/zh-cn';
export default {
name: 'currentSteamCurve',
dicts: ['collect_type'],
components: {
Chart,
Treeselect,
DatePicker
},
data() {
return {
//List
baseMonitorInfoOptions: [],
//List
monitorInfoOptions: [],
workUnitName: undefined,
//
selectMonitorName: null,
//
baseMonitorInfoList: [],
//
daterangeCollectTime: [],
//
daterangeRecordTime: [],
//
timerangeRecordTime:[],
//
loading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
dataList: [],
//
title: '',
//
open: false,
//
queryParams: {
pageNum: 1,
pageSize: 10,
monitorCode: null,
monitorName: null,
collectTime: null,
fluxFlow: null,
steamFlow: null,
heatInstantValue: null,
heatTotalValue: null,
temperature: null,
press: null,
density: null,
differencePress: null,
recordTime: null
},
//
form: {},
monitorProps: {
children: 'children',
label: 'label'
},
//
rules: {
/* objId: [
{ required: true, message: '编号不能为空', trigger: 'blur' }
]*/
},
columns: [
{ key: 0, label: `自增标识`, visible: false },
{ key: 1, label: `计量设备编号`, visible: true },
{ key: 2, label: `采集时间`, visible: false },
{ key: 3, label: `瞬时流量`, visible: true },
{ key: 4, label: `累计流量`, visible: true },
{ key: 5, label: `瞬时热量`, visible: true },
{ key: 6, label: `累计热量`, visible: true },
{ key: 7, label: `温度`, visible: false },
{ key: 8, label: `压力`, visible: false },
{ key: 9, label: `密度`, visible: false },
{ key: 10, label: `压力差值`, visible: false },
{ key: 11, label: `记录时间`, visible: true },
{ key: 12, label: `计量设备名称`, visible: true }
],
lang: {
formatLocale: {
firstDayOfWeek: 1
},
monthBeforeYear: false
},
//
powerOutageSummary: {
count: 0,
totalDuration: 0,
longestDuration: 0
},
//
powerOutageList: [],
//
samplingThreshold: 1000,
//
baseChartOptions: {}
}
},
created() {
const nowDate = new Date();
const today = parseTime(new Date(), '{y}-{m}-{d}')
const lastDate = new Date();
lastDate.setDate(nowDate.getDate() - 1)
const yesterday = parseTime(lastDate, '{y}-{m}-{d}')
console.log(today,yesterday)
this.daterangeRecordTime[0] = yesterday+ ' 08:00:00'
this.daterangeRecordTime[1] = today + ' 08:00:00'
//
this.initBaseChartOptions()
this.getTreeselect()
this.getTreeMonitorInfo()
this.getList()
},
watch: {
//
workUnitName(val) {
this.$refs.tree.filter(val)
}
},
methods: {
/** 初始化图表基础配置 */
initBaseChartOptions() {
this.baseChartOptions = {
grid: {
top: '15%',
bottom: '10%',
left: '10%',
right: '3%'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
label: {
show: true
}
}
},
dataZoom: [{
type: 'slider'
}],
legend: {
right: 0,
data: ['数据', '停电']
},
xAxis: {
axisLine: {
show: true,
lineStyle: {
color: '#000000'
}
},
axisTick: {
show: true
},
axisLabel: {
show: true,
textStyle: {
color: '#000000'
}
}
},
yAxis: {
type: 'value',
splitLine: {
show: false
},
axisTick: {
show: true
},
axisLine: {
show: true,
lineStyle: {
color: '#000000'
}
},
axisLabel: {
show: true,
textStyle: {
color: '#000000'
}
}
}
}
},
/** 转换计量设备信息数据结构 */
normalizer(node) {
if (node.children && !node.children.length) {
delete node.children
}
return {
id: node.monitorCode,
label: node.monitorName,
children: node.children
}
},
/** 查询电实时数据列表 */
getList() {
this.loading = true
this.queryParams.params = {}
if (null != this.daterangeRecordTime && '' != this.daterangeRecordTime) {
this.queryParams.params['beginRecordTime'] = this.daterangeRecordTime[0];
this.queryParams.params['endRecordTime'] = this.daterangeRecordTime[1];
}
this.getChart()
},
//
cancel() {
this.open = false
this.reset()
},
//
reset() {
this.form = {
objId: null,
monitorCode: null,
instrumentValue: null,
expend: null,
recordTime: null,
beginTime: null,
endTime: null,
updateFlag: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null
}
this.resetForm('form')
},
/** 搜索按钮操作 */
handleQuery() {
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.queryParams.monitorCode = null
this.resetForm('queryForm')
this.handleQuery()
},
/** 导出按钮操作 */
handleExport() {
this.download('ems/record/recordSteamInstant/export', {
...this.queryParams
}, `recordSteamInstant_${new Date().getTime()}.xlsx`)
},
/** 导出停电数据 */
exportPowerOutageData() {
if (this.powerOutageList.length === 0) {
this.$message.warning('没有停电数据可导出');
return;
}
//
const columns = [
{label: '序号', prop: 'index'},
{label: '开始时间', prop: 'startTime'},
{label: '结束时间', prop: 'endTime'},
{label: '持续时间(小时)', prop: 'duration'}
];
let table = '<table border="1" cellspacing="0" cellpadding="5">';
//
table += '<tr>';
columns.forEach(col => {
table += `<th>${col.label}</th>`;
});
table += '</tr>';
//
this.powerOutageList.forEach(item => {
table += '<tr>';
columns.forEach(col => {
table += `<td>${item[col.prop]}</td>`;
});
table += '</tr>';
});
table += '</table>';
// HTML
const deviceName = this.selectMonitorName || '设备';
const fileName = `${deviceName}停电记录_${new Date().getTime()}.xls`;
const blob = new Blob([table], {type: 'application/vnd.ms-excel'});
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = fileName;
link.click();
URL.revokeObjectURL(link.href);
},
/** 查询计量设备信息下拉树结构 */
getTreeselect() {
getMonitorInfoTree({ monitorType: 4 }).then(response => {
this.monitorInfoOptions = response.data
})
},
/** 筛选节点 */
filterNode(value, data) {
if (!value) return true
return data.label.indexOf(value) !== -1
},
/** 节点单击事件 */
handleNodeClick(data) {
this.queryParams.monitorCode = data.code
this.selectMonitorName = data.label
this.handleQuery()
},
/**
* 数据抽样处理当数据量大时进行抽样
* @param {Array} dataArray 原始数据数组
* @returns {Array} 抽样后的数据
*/
sampleData(dataArray) {
if (!dataArray || dataArray.length <= this.samplingThreshold) {
return dataArray;
}
//
const interval = Math.ceil(dataArray.length / this.samplingThreshold);
const sampledData = [];
//
for (let i = 0; i < dataArray.length; i += interval) {
sampledData.push(dataArray[i]);
}
//
if (sampledData[sampledData.length - 1] !== dataArray[dataArray.length - 1]) {
sampledData.push(dataArray[dataArray.length - 1]);
}
return sampledData;
},
/**
* 处理时间间隔大于1小时的数据插入标红的0值点
* @param {Array} originalData 原始数据数组
* @param {string} valueField 数值字段名
* @returns {Object} 处理后的数据和标记点
*/
processDataBreaks(originalData, valueField) {
//
const sampledData = this.sampleData(originalData);
if (!sampledData || sampledData.length < 2) {
return {
processedData: sampledData || [],
timeData: sampledData ? sampledData.map(e => e.recordTime) : [],
valueData: sampledData ? sampledData.map(e => e[valueField]) : [],
markPoints: [],
markAreas: []
}
}
//
const sortedData = [...sampledData].sort((a, b) => {
return new Date(a.recordTime) - new Date(b.recordTime)
})
const processedData = []
const markPoints = []
const markAreas = []
const powerOutages = []
const oneHourMs = 60 * 60 * 1000 // 1
//
for (let i = 0; i < sortedData.length; i++) {
processedData.push(sortedData[i])
//
if (i < sortedData.length - 1) {
const currentTime = new Date(sortedData[i].recordTime).getTime()
const nextTime = new Date(sortedData[i + 1].recordTime).getTime()
const timeDiff = nextTime - currentTime
// 1
if (timeDiff > oneHourMs) {
// 1
const breakTime = new Date(currentTime + 60000)
const breakTimeStr = parseTime(breakTime, '{y}-{m}-{d} {h}:{i}:{s}')
const endBreakTime = new Date(nextTime - 60000)
const endBreakTimeStr = parseTime(endBreakTime, '{y}-{m}-{d} {h}:{i}:{s}')
//
const breakPoint = { ...sortedData[i] }
breakPoint.recordTime = breakTimeStr
breakPoint[valueField] = 0
breakPoint.isBreakPoint = true //
processedData.push(breakPoint)
//
markPoints.push({
value: '停电',
xAxis: breakTimeStr,
yAxis: 0,
symbol: 'path://M11.184 6.6C10.744 5.04 9.252 4 7.5 4s-3.244 1.04-3.684 2.6l-3.755 9.96A.5.5 0 0 0 .5 17h3.882a.5.5 0 0 0 .474-.65l-.333-1h6.954l-.333 1A.5.5 0 0 0 11.618 17H15.5a.5.5 0 0 0 .46-.69l-3.776-9.71zm1.372 8.15l-1.087-2.792A.5.5 0 0 0 11 11.5H4a.5.5 0 0 0-.47.342l-1.087 2.917h-1.3l3.446-9.13C5.819 4.673 6.64 4 7.5 4s1.68.673 1.908 1.63l3.446 9.13h-1.298z',
symbolSize: 30,
symbolOffset: [0, '-50%'],
itemStyle: {
color: 'red'
}
})
//
markAreas.push([
{
xAxis: breakTimeStr,
itemStyle: { color: 'rgba(255, 0, 0, 0.1)' }
},
{
xAxis: endBreakTimeStr,
}
])
//
const durationHours = (timeDiff / (1000 * 60 * 60)).toFixed(2);
powerOutages.push({
startTime: parseTime(new Date(currentTime), '{y}-{m}-{d} {h}:{i}:{s}'),
endTime: parseTime(new Date(nextTime), '{y}-{m}-{d} {h}:{i}:{s}'),
duration: durationHours
});
}
}
}
//
this.updatePowerOutageSummary(powerOutages);
//
const timeData = processedData.map(e => e.recordTime)
const valueData = processedData.map(e => e[valueField])
//
const validData = processedData.filter(item => !item.isBreakPoint)
const validValues = validData.map(e => parseFloat(e[valueField]))
const average = validValues.length > 0 ?
(validValues.reduce((a, b) => a + b, 0) / validValues.length).toFixed(2) : 0
return {
processedData,
timeData,
valueData,
markPoints,
markAreas,
average
}
},
/**
* 更新停电统计信息
* @param {Array} powerOutages 停电记录数组
*/
updatePowerOutageSummary(powerOutages) {
if (!powerOutages || powerOutages.length === 0) {
this.powerOutageSummary = {
count: 0,
totalDuration: 0,
longestDuration: 0
};
this.powerOutageList = [];
return;
}
//
let totalDuration = 0;
let longestDuration = 0;
powerOutages.forEach((outage, index) => {
const duration = parseFloat(outage.duration);
totalDuration += duration;
longestDuration = Math.max(longestDuration, duration);
});
//
this.powerOutageSummary = {
count: powerOutages.length,
totalDuration: totalDuration.toFixed(2),
longestDuration: longestDuration.toFixed(2)
};
//
this.powerOutageList = powerOutages.map((outage, index) => ({
index: index + 1,
startTime: outage.startTime,
endTime: outage.endTime,
duration: outage.duration
}));
},
/**
* 创建图表配置
* @param {String} title 图表标题
* @param {Object} dataResult 处理后的数据结果
* @param {String} name 数据名称
* @param {String} yAxisName Y轴名称
* @param {String} color 图表颜色
* @param {Function} tooltipFormatter 提示格式化函数
* @returns {Object} 图表配置
*/
createChartOption(title, dataResult, name, yAxisName, color, tooltipFormatter) {
const option = JSON.parse(JSON.stringify(this.baseChartOptions));
//
option.title = {
text: this.selectMonitorName + ' ' + title + ' (平均值:' + dataResult.average + ")",
x: 'center',
textStyle: {
fontSize: 15
}
};
// X
option.xAxis.data = dataResult.timeData;
// Y
option.yAxis.name = yAxisName;
option.yAxis.nameTextStyle = {
color: '#000000'
};
//
option.tooltip.formatter = tooltipFormatter || function(params) {
const dataIndex = params[0].dataIndex;
const isBreakPoint = dataResult.processedData[dataIndex] && dataResult.processedData[dataIndex].isBreakPoint;
if (isBreakPoint) {
return '停电<br/>时间: ' + params[0].axisValue;
}
return params[0].seriesName + ': ' + params[0].value + '<br/>时间: ' + params[0].axisValue;
};
//
option.series = [{
name: name,
type: 'line',
smooth: true,
showAllSymbol: true,
symbol: 'circle',
symbolSize: 4,
itemStyle: {
normal: {
color: function(params) {
return dataResult.processedData[params.dataIndex].isBreakPoint ? 'red' : color;
}
}
},
data: dataResult.valueData,
markPoint: {
data: dataResult.markPoints,
symbolSize: 60,
label: {
color: '#000000',
fontSize: 14,
fontWeight: 'bold',
backgroundColor: 'rgba(255, 255, 255, 0.8)',
borderColor: '#ff0000',
borderWidth: 1,
borderRadius: 4,
padding: [4, 8]
}
},
markArea: {
data: dataResult.markAreas,
silent: true
}
}];
return option;
},
/** 曲线 */
async getChart() {
this.loading = true;
let query = JSON.parse(JSON.stringify(this.queryParams))
try {
const {data} = await steamInstantList(query)
//
const fluxFlowResult = this.processDataBreaks(data, 'fluxFlow')
//
const temperatureResult = this.processDataBreaks(data, 'temperature')
//
const pressResult = this.processDataBreaks(data, 'press')
//
const option1 = this.createChartOption(
'瞬时流量',
fluxFlowResult,
'瞬时流量',
'瞬时流量y',
'#5470c6'
);
const option2 = this.createChartOption(
'温度',
temperatureResult,
'温度',
'温度y',
'#91cc75'
);
const option3 = this.createChartOption(
'压力',
pressResult,
'压力',
'压力y',
'#fac858'
);
this.$refs.Chart1.setData(option1)
this.$refs.Chart2.setData(option2)
this.$refs.Chart3.setData(option3)
echarts.connect(this.$refs.Chart1.chart, this.$refs.Chart2.chart, this.$refs.Chart3.chart)
//
this.$refs.Chart1.chart.on('datazoom', (e) => {
option2.dataZoom[0].start = e.start
option2.dataZoom[0].end = e.end
this.$refs.Chart2.setData(option2)
option3.dataZoom[0].start = e.start
option3.dataZoom[0].end = e.end
this.$refs.Chart3.setData(option3)
})
this.$refs.Chart2.chart.on('datazoom', (e) => {
option1.dataZoom[0].start = e.start
option1.dataZoom[0].end = e.end
this.$refs.Chart1.setData(option1)
option3.dataZoom[0].start = e.start
option3.dataZoom[0].end = e.end
this.$refs.Chart3.setData(option3)
})
this.$refs.Chart3.chart.on('datazoom', (e) => {
option2.dataZoom[0].start = e.start
option2.dataZoom[0].end = e.end
this.$refs.Chart2.setData(option2)
option1.dataZoom[0].start = e.start
option1.dataZoom[0].end = e.end
this.$refs.Chart1.setData(option1)
})
} catch (error) {
console.error('获取图表数据失败:', error)
this.$message.error('获取图表数据失败')
} finally {
this.loading = false
}
},
/** 查询树形结构 */
getTreeMonitorInfo() {
getMonitorInfoTree({ monitorType: 4 }).then(response => {
this.baseMonitorInfoOptions = this.handleTree(response.data, "id", "parentId")
})
}
}
}
</script>
<style scoped >
.app-container {
padding: 10px;
}
.charts-container {
margin-top: 10px;
}
.chart1 {
width: 50%;
height: 40vh;
display: inline-block;
}
.chart2 {
width: 50%;
height: 40vh;
display: inline-block;
}
.chart3 {
width: 50%;
height: 40vh;
display: inline-block;
}
.power-outage-summary {
margin-bottom: 10px;
}
.power-outage-info {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
.power-outage-info p {
margin: 5px 10px;
}
/* 选中节点的样式 */
::v-deep .el-tree .el-tree-node.is-current > .el-tree-node__content {
background-color: #67C23A !important; /* 选中节点的背景色 */
color: #303133 !important; /* 选中节点的文字颜色 */
padding: 0; /* 清除内边距 */
margin: 0; /* 清除外边距 */
z-index: 1; /* 确保在其他元素之上 */
}
/* 鼠标悬停时的样式 */
.el-tree .el-tree-node__content:hover {
background-color: #e6f7ff; /* 鼠标悬停时的背景色 */
}
/* 闪电图标 */
.el-icon-lightning:before {
content: "\e6a8"; /* 使用现有的element-ui图标字体 */
}
</style>

@ -31,15 +31,7 @@
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch"
label-width="100px" label-width="100px"
> >
<!-- <el-form-item label="计量设备编号" prop="monitorCode">--> <el-form-item label="记录时间">
<!-- <el-input-->
<!-- v-model="queryParams.monitorCode"-->
<!-- placeholder="请输入计量设备编号"-->
<!-- clearable-->
<!-- @keyup.enter.native="handleQuery"-->
<!-- />-->
<!-- </el-form-item>-->
<el-form-item label="采集时间">
<date-picker <date-picker
v-model="daterangeRecordTime" v-model="daterangeRecordTime"
range range
@ -52,37 +44,43 @@
></date-picker> ></date-picker>
</el-form-item> </el-form-item>
<!-- <el-form-item label="采集日期范围">
<el-date-picker
v-model="daterangeCollectTime"
style="width: 340px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>-->
<!-- <el-form-item label="记录时间范围">
<el-time-picker
v-model="timerangeRecordTime"
style="width: 340px"
value-format="HH:mm:ss"
is-range
range-separator="-"
start-placeholder="开始时间"
end-placeholder="结束时间"
></el-time-picker>
</el-form-item>-->
<el-form-item> <el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button> <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button> <el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<!-- 图表联动提示 -->
<el-alert
v-if="powerOutageSummary.count > 0"
title="图表已联动,缩放任一图表将同步其它图表"
type="info"
:closable="false"
show-icon
style="margin-bottom: 10px"
/>
<!-- 停电统计信息 -->
<el-card v-if="powerOutageSummary.count > 0" class="power-outage-summary" shadow="hover">
<div slot="header">
<span><i class="el-icon-lightning" style="color: red;"></i> 停电统计</span>
<el-button style="float: right; padding: 3px 0" type="text" @click="exportPowerOutageData">
导出停电记录
</el-button>
</div>
<div class="power-outage-info">
<p><strong>停电次数:</strong> {{ powerOutageSummary.count }} </p>
<p><strong>最长停电:</strong> {{ powerOutageSummary.longestDuration }} 小时</p>
<p><strong>总停电时长:</strong> {{ powerOutageSummary.totalDuration }} 小时</p>
</div>
</el-card>
<!-- 图表区域 -->
<div class="charts-container">
<Chart ref="Chart1" class="chart1"/> <Chart ref="Chart1" class="chart1"/>
<Chart ref="Chart2" class="chart2"/> <Chart ref="Chart2" class="chart2"/>
<Chart ref="Chart3" class="chart3"/> <Chart ref="Chart3" class="chart3"/>
</div>
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
@ -194,11 +192,22 @@ export default {
firstDayOfWeek: 1 firstDayOfWeek: 1
}, },
monthBeforeYear: false monthBeforeYear: false
} },
//
powerOutageSummary: {
count: 0,
totalDuration: 0,
longestDuration: 0
},
//
powerOutageList: [],
//
samplingThreshold: 1000,
//
baseChartOptions: {}
} }
}, },
created() { created() {
const nowDate = new Date(); const nowDate = new Date();
const today = parseTime(new Date(), '{y}-{m}-{d}') const today = parseTime(new Date(), '{y}-{m}-{d}')
const lastDate = new Date(); const lastDate = new Date();
@ -209,13 +218,8 @@ export default {
this.daterangeRecordTime[0] = yesterday+ ' 08:00:00' this.daterangeRecordTime[0] = yesterday+ ' 08:00:00'
this.daterangeRecordTime[1] = today + ' 08:00:00' this.daterangeRecordTime[1] = today + ' 08:00:00'
/* this.daterangeCollectTime[0] = parseTime(yesterday, '{y}-{m}-{d}') ; //
this.daterangeCollectTime[1] = parseTime(today, '{y}-{m}-{d}') ; this.initBaseChartOptions()
//8
this.timerangeRecordTime[0] = '08:00:00';
this.timerangeRecordTime[1] = '08:00:00';*/
this.getTreeselect() this.getTreeselect()
this.getTreeMonitorInfo() this.getTreeMonitorInfo()
this.getList() this.getList()
@ -227,6 +231,72 @@ export default {
} }
}, },
methods: { methods: {
/** 初始化图表基础配置 */
initBaseChartOptions() {
this.baseChartOptions = {
grid: {
top: '15%',
bottom: '10%',
left: '10%',
right: '3%'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
label: {
show: true
}
}
},
dataZoom: [{
type: 'slider'
}],
legend: {
right: 0,
data: ['数据', '停电']
},
xAxis: {
axisLine: {
show: true,
lineStyle: {
color: '#000000'
}
},
axisTick: {
show: true
},
axisLabel: {
show: true,
textStyle: {
color: '#000000'
}
}
},
yAxis: {
type: 'value',
splitLine: {
show: false
},
axisTick: {
show: true
},
axisLine: {
show: true,
lineStyle: {
color: '#000000'
}
},
axisLabel: {
show: true,
textStyle: {
color: '#000000'
}
}
}
}
},
/** 转换计量设备信息数据结构 */ /** 转换计量设备信息数据结构 */
normalizer(node) { normalizer(node) {
if (node.children && !node.children.length) { if (node.children && !node.children.length) {
@ -238,19 +308,15 @@ export default {
children: node.children children: node.children
} }
}, },
/** 查询电实时数据列表 */ /** 查询电实时数据列表 */
getList() { getList() {
this.loading = true this.loading = true
this.queryParams.params = {} this.queryParams.params = {}
/* if (null != this.daterangeCollectTime && '' != this.daterangeCollectTime) {
this.queryParams.params['beginCollectTime'] =this.daterangeCollectTime[0] + ' ' + this.timerangeRecordTime[0];
this.queryParams.params['endCollectTime'] =this.daterangeCollectTime[1] + ' ' + this.timerangeRecordTime[1];
}*/
if (null != this.daterangeRecordTime && '' != this.daterangeRecordTime) { if (null != this.daterangeRecordTime && '' != this.daterangeRecordTime) {
this.queryParams.params['beginRecordTime'] =this.daterangeRecordTime[0]; this.queryParams.params['beginRecordTime'] = this.daterangeRecordTime[0];
this.queryParams.params['endRecordTime'] =this.daterangeRecordTime[1]; this.queryParams.params['endRecordTime'] = this.daterangeRecordTime[1];
} }
this.getChart() this.getChart()
}, },
@ -298,6 +364,50 @@ export default {
}, `recordSteamInstant_${new Date().getTime()}.xlsx`) }, `recordSteamInstant_${new Date().getTime()}.xlsx`)
}, },
/** 导出停电数据 */
exportPowerOutageData() {
if (this.powerOutageList.length === 0) {
this.$message.warning('没有停电数据可导出');
return;
}
//
const columns = [
{label: '序号', prop: 'index'},
{label: '开始时间', prop: 'startTime'},
{label: '结束时间', prop: 'endTime'},
{label: '持续时间(小时)', prop: 'duration'}
];
let table = '<table border="1" cellspacing="0" cellpadding="5">';
//
table += '<tr>';
columns.forEach(col => {
table += `<th>${col.label}</th>`;
});
table += '</tr>';
//
this.powerOutageList.forEach(item => {
table += '<tr>';
columns.forEach(col => {
table += `<td>${item[col.prop]}</td>`;
});
table += '</tr>';
});
table += '</table>';
// HTML
const deviceName = this.selectMonitorName || '设备';
const fileName = `${deviceName}停电记录_${new Date().getTime()}.xls`;
const blob = new Blob([table], {type: 'application/vnd.ms-excel'});
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = fileName;
link.click();
URL.revokeObjectURL(link.href);
},
/** 查询计量设备信息下拉树结构 */ /** 查询计量设备信息下拉树结构 */
getTreeselect() { getTreeselect() {
getMonitorInfoTree({ monitorType: 4 }).then(response => { getMonitorInfoTree({ monitorType: 4 }).then(response => {
@ -318,284 +428,346 @@ export default {
this.handleQuery() this.handleQuery()
}, },
/**
* 数据抽样处理当数据量大时进行抽样
* @param {Array} dataArray 原始数据数组
* @returns {Array} 抽样后的数据
*/
sampleData(dataArray) {
if (!dataArray || dataArray.length <= this.samplingThreshold) {
return dataArray;
}
//
const interval = Math.ceil(dataArray.length / this.samplingThreshold);
const sampledData = [];
//
for (let i = 0; i < dataArray.length; i += interval) {
sampledData.push(dataArray[i]);
}
//
if (sampledData[sampledData.length - 1] !== dataArray[dataArray.length - 1]) {
sampledData.push(dataArray[dataArray.length - 1]);
}
return sampledData;
},
/**
* 处理时间间隔大于1小时的数据插入标红的0值点
* @param {Array} originalData 原始数据数组
* @param {string} valueField 数值字段名
* @returns {Object} 处理后的数据和标记点
*/
processDataBreaks(originalData, valueField) {
//
const sampledData = this.sampleData(originalData);
if (!sampledData || sampledData.length < 2) {
return {
processedData: sampledData || [],
timeData: sampledData ? sampledData.map(e => e.recordTime) : [],
valueData: sampledData ? sampledData.map(e => e[valueField]) : [],
markPoints: [],
markAreas: []
}
}
//
const sortedData = [...sampledData].sort((a, b) => {
return new Date(a.recordTime) - new Date(b.recordTime)
})
const processedData = []
const markPoints = []
const markAreas = []
const powerOutages = []
const oneHourMs = 60 * 60 * 1000 // 1
//
for (let i = 0; i < sortedData.length; i++) {
//
processedData.push(sortedData[i])
//
if (i < sortedData.length - 1) {
const currentTime = new Date(sortedData[i].recordTime).getTime()
const nextTime = new Date(sortedData[i + 1].recordTime).getTime()
const timeDiff = nextTime - currentTime
// 1
if (timeDiff > oneHourMs) {
// - 使null线
//
const breakTime1 = new Date(currentTime + 60000)
const breakTime1Str = parseTime(breakTime1, '{y}-{m}-{d} {h}:{i}:{s}')
const nullPoint1 = {
recordTime: breakTime1Str,
[valueField]: null, // 使null0
isBreakPoint: true
}
processedData.push(nullPoint1)
//
const breakTime2 = new Date(nextTime - 60000)
const breakTime2Str = parseTime(breakTime2, '{y}-{m}-{d} {h}:{i}:{s}')
const nullPoint2 = {
recordTime: breakTime2Str,
[valueField]: null, // 使null0
isBreakPoint: true
}
processedData.push(nullPoint2)
const endBreakTime = new Date(nextTime - 60000)
const endBreakTimeStr = parseTime(endBreakTime, '{y}-{m}-{d} {h}:{i}:{s}')
//
markPoints.push({
value: '停电',
xAxis: breakTime1Str,
yAxis: 0,
symbol: 'path://M11.184 6.6C10.744 5.04 9.252 4 7.5 4s-3.244 1.04-3.684 2.6l-3.755 9.96A.5.5 0 0 0 .5 17h3.882a.5.5 0 0 0 .474-.65l-.333-1h6.954l-.333 1A.5.5 0 0 0 11.618 17H15.5a.5.5 0 0 0 .46-.69l-3.776-9.71zm1.372 8.15l-1.087-2.792A.5.5 0 0 0 11 11.5H4a.5.5 0 0 0-.47.342l-1.087 2.917h-1.3l3.446-9.13C5.819 4.673 6.64 4 7.5 4s1.68.673 1.908 1.63l3.446 9.13h-1.298z',
symbolSize: 30,
symbolOffset: [0, '-50%'],
itemStyle: {
color: 'red'
}
})
//
markAreas.push([
{
xAxis: breakTime1Str,
itemStyle: { color: 'rgba(255, 0, 0, 0.1)' }
},
{
xAxis: endBreakTimeStr,
}
])
//
const durationHours = (timeDiff / (1000 * 60 * 60)).toFixed(2);
powerOutages.push({
startTime: parseTime(new Date(currentTime), '{y}-{m}-{d} {h}:{i}:{s}'),
endTime: parseTime(new Date(nextTime), '{y}-{m}-{d} {h}:{i}:{s}'),
duration: durationHours
});
}
}
}
//
this.updatePowerOutageSummary(powerOutages);
//
const timeData = processedData.map(e => e.recordTime)
const valueData = processedData.map(e => e[valueField])
//
const validData = processedData.filter(item => !item.isBreakPoint)
const validValues = validData.map(e => parseFloat(e[valueField]))
const average = validValues.length > 0 ?
(validValues.reduce((a, b) => a + b, 0) / validValues.length).toFixed(2) : 0
return {
processedData,
timeData,
valueData,
markPoints,
markAreas,
average
}
},
/**
* 更新停电统计信息
* @param {Array} powerOutages 停电记录数组
*/
updatePowerOutageSummary(powerOutages) {
if (!powerOutages || powerOutages.length === 0) {
this.powerOutageSummary = {
count: 0,
totalDuration: 0,
longestDuration: 0
};
this.powerOutageList = [];
return;
}
//
let totalDuration = 0;
let longestDuration = 0;
powerOutages.forEach((outage, index) => {
const duration = parseFloat(outage.duration);
totalDuration += duration;
longestDuration = Math.max(longestDuration, duration);
});
//
this.powerOutageSummary = {
count: powerOutages.length,
totalDuration: totalDuration.toFixed(2),
longestDuration: longestDuration.toFixed(2)
};
//
this.powerOutageList = powerOutages.map((outage, index) => ({
index: index + 1,
startTime: outage.startTime,
endTime: outage.endTime,
duration: outage.duration
}));
},
/**
* 创建图表配置
* @param {String} title 图表标题
* @param {Object} dataResult 处理后的数据结果
* @param {String} name 数据名称
* @param {String} yAxisName Y轴名称
* @param {String} color 图表颜色
* @param {Function} tooltipFormatter 提示格式化函数
* @returns {Object} 图表配置
*/
createChartOption(title, dataResult, name, yAxisName, color, tooltipFormatter) {
const option = JSON.parse(JSON.stringify(this.baseChartOptions));
//
option.title = {
text: this.selectMonitorName + ' ' + title + ' (平均值:' + dataResult.average + ")",
x: 'center',
textStyle: {
fontSize: 15
}
};
// X
option.xAxis.data = dataResult.timeData;
// Y
option.yAxis.name = yAxisName;
option.yAxis.nameTextStyle = {
color: '#000000'
};
//
option.tooltip.formatter = tooltipFormatter || function(params) {
const dataIndex = params[0].dataIndex;
const isBreakPoint = dataResult.processedData[dataIndex] && dataResult.processedData[dataIndex].isBreakPoint;
if (isBreakPoint) {
return '停电<br/>时间: ' + params[0].axisValue;
}
return params[0].seriesName + ': ' + params[0].value + '<br/>时间: ' + params[0].axisValue;
};
//
option.series = [{
name: name,
type: 'line',
smooth: false,
showAllSymbol: true,
symbol: 'circle',
symbolSize: 4,
step: 'end',
connectNulls: false,
itemStyle: {
normal: {
color: function(params) {
return dataResult.processedData[params.dataIndex] && dataResult.processedData[params.dataIndex].isBreakPoint ? 'red' : color;
}
}
},
data: dataResult.valueData,
markPoint: {
data: dataResult.markPoints,
symbolSize: 60,
label: {
color: '#000000',
fontSize: 14,
fontWeight: 'bold',
backgroundColor: 'rgba(255, 255, 255, 0.8)',
borderColor: '#ff0000',
borderWidth: 1,
borderRadius: 4,
padding: [4, 8]
}
},
markArea: {
data: dataResult.markAreas,
silent: true
},
markLine: {
silent: true,
symbol: 'none',
lineStyle: {
color: '#ff0000',
width: 2,
type: 'dashed'
},
label: {
show: false
},
data: dataResult.markPoints.map(point => ({
xAxis: point.xAxis
}))
}
}];
// y0
if (!option.yAxis.min) {
option.yAxis.min = function(value) {
// 0
if (dataResult.processedData.some(item => item.isBreakPoint)) {
return 0;
}
return value.min;
};
}
return option;
},
/** 曲线 */ /** 曲线 */
async getChart() { async getChart() {
this.loading = true;
let query = JSON.parse(JSON.stringify(this.queryParams)) let query = JSON.parse(JSON.stringify(this.queryParams))
try {
const {data} = await steamInstantList(query) const {data} = await steamInstantList(query)
let option1 = { //
title: { const fluxFlowResult = this.processDataBreaks(data, 'fluxFlow')
text: this.selectMonitorName + ' 瞬时流量' + ' (平均值:'+ //
((data.map(e=>parseFloat(e.fluxFlow)).reduce((a,b)=>a+b,0))/data.length).toFixed(2)+")", const temperatureResult = this.processDataBreaks(data, 'temperature')
x: 'center', //
textStyle: { const pressResult = this.processDataBreaks(data, 'press')
fontSize: 15 //
}
},
grid: {
top: '15%',
bottom: '10%',
left: '10%',
right: '3%'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
label: {
show: true
}
}
},
dataZoom: [{
type: 'slider'
}],
legend: {
right: 0
},
xAxis: {
data: data.map(e => e.recordTime),
axisLine: {
show: true, //X线
lineStyle: {
color: '#000000'
}
},
axisTick: {
show: true //X
},
axisLabel: {
show: true,
textStyle: {
color: '#000000' //X
}
}
},
yAxis: [
{
type: 'value',
name: '瞬时流量y',
nameTextStyle: {
color: '#000000'
},
splitLine: {
show: false
},
axisTick: {
show: true
},
axisLine: {
show: true,
lineStyle: {
color: '#000000'
}
},
axisLabel: {
show: true,
textStyle: {
color: '#000000'
}
}
}
],
series: [
{
name: '瞬时流量',
type: 'line',
smooth: true, //线
showAllSymbol: true, //
symbol: 'circle', //
symbolSize: 0, //
data: data.map(e => e.fluxFlow)
},
]
}
let option2 = {
/* 使 filter temperature undefined null
title: {
text: this.selectMonitorName + ' 温度' + ' (平均值:' +
((data.filter(e => e.temperature !== undefined && e.temperature !== null)
.map(e => parseFloat(e.temperature))
.reduce((a, b) => a + b, 0)) /
data.filter(e => e.temperature !== undefined && e.temperature !== null).length).toFixed(2) + ")",
x: 'center'
},*/
//
const option1 = this.createChartOption(
'瞬时流量',
fluxFlowResult,
'瞬时流量',
'瞬时流量y',
'#5470c6'
);
title: { const option2 = this.createChartOption(
text: this.selectMonitorName + ' 温度' + ' (平均值:'+ '温度',
((data.map(e=>parseFloat(e.temperature)).reduce((a,b)=>a+b,0))/data.length).toFixed(2)+")", temperatureResult,
x: 'center', '温度',
textStyle: { '温度y',
fontSize: 15 // '#91cc75'
} );
},
grid: {
top: '15%',
bottom: '10%',
left: '10%',
right: '3%'
},
dataZoom: [{ const option3 = this.createChartOption(
type: 'slider' '压力',
}], pressResult,
tooltip: { '压力',
trigger: 'axis', '压力y',
axisPointer: { '#fac858'
type: 'shadow', );
label: {
show: true
}
}
},
legend: {
right: 0
},
xAxis: {
data: data.map(e => e.recordTime),
axisLine: {
show: true, //X线
lineStyle: {
color: '#000000'
}
},
axisTick: {
show: true //X
},
axisLabel: {
show: true,
textStyle: {
color: '#000000' //X
}
}
},
yAxis: [
{
type: 'value',
name: '温度y',
nameTextStyle: {
color: '#000000'
},
splitLine: {
show: false
},
axisTick: {
show: true
},
axisLine: {
show: true,
lineStyle: {
color: '#000000'
}
},
axisLabel: {
show: true,
textStyle: {
color: '#000000'
}
}
}
],
series: [
{
name: '温度',
type: 'line',
smooth: true, //线
showAllSymbol: true, //
symbol: 'circle', //
symbolSize: 0, //
data: data.map(e => e.temperature)
},
]
}
let option3 = {
title: {
text: this.selectMonitorName + ' 压力' + ' (平均值:'+
((data.map(e=>parseFloat(e.press)).reduce((a,b)=>a+b,0))/data.length).toFixed(2)+")",
x: 'center',
textStyle: {
fontSize: 15 //
}
},
grid: {
top: '15%',
bottom: '10%',
left: '10%',
right: '3%'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
label: {
show: true
}
}
},
dataZoom: [{
type: 'slider'
}],
legend: {
right: 0
},
xAxis: {
data: data.map(e => e.recordTime),
axisLine: {
show: true, //X线
lineStyle: {
color: '#000000'
}
},
axisTick: {
show: true //X
},
axisLabel: {
show: true,
textStyle: {
color: '#000000' //X
}
}
},
yAxis: [
{
type: 'value',
name: '压力y',
nameTextStyle: {
color: '#000000'
},
splitLine: {
show: false
},
axisTick: {
show: true
},
axisLine: {
show: true,
lineStyle: {
color: '#000000'
}
},
axisLabel: {
show: true,
textStyle: {
color: '#000000'
}
}
}
],
series: [
{
name: '压力',
type: 'line',
smooth: true, //线
showAllSymbol: true, //
symbol: 'circle', //
symbolSize: 0, //
data: data.map(e => e.press)
},
]
}
this.$refs.Chart1.setData(option1) this.$refs.Chart1.setData(option1)
this.$refs.Chart2.setData(option2) this.$refs.Chart2.setData(option2)
@ -603,12 +775,7 @@ export default {
echarts.connect(this.$refs.Chart1.chart, this.$refs.Chart2.chart, this.$refs.Chart3.chart) echarts.connect(this.$refs.Chart1.chart, this.$refs.Chart2.chart, this.$refs.Chart3.chart)
/* this.$refs.Chart1.chart.on('datazoom', (e) => { //
option.dataZoom[0].start = e.start;
option.dataZoom[0].end = e.end;
this.$refs.Chart1.setData(option);
});*/
this.$refs.Chart1.chart.on('datazoom', (e) => { this.$refs.Chart1.chart.on('datazoom', (e) => {
option2.dataZoom[0].start = e.start option2.dataZoom[0].start = e.start
option2.dataZoom[0].end = e.end option2.dataZoom[0].end = e.end
@ -616,8 +783,8 @@ export default {
option3.dataZoom[0].start = e.start option3.dataZoom[0].start = e.start
option3.dataZoom[0].end = e.end option3.dataZoom[0].end = e.end
this.$refs.Chart3.setData(option3) this.$refs.Chart3.setData(option3)
}) })
this.$refs.Chart2.chart.on('datazoom', (e) => { this.$refs.Chart2.chart.on('datazoom', (e) => {
option1.dataZoom[0].start = e.start option1.dataZoom[0].start = e.start
option1.dataZoom[0].end = e.end option1.dataZoom[0].end = e.end
@ -626,6 +793,7 @@ export default {
option3.dataZoom[0].end = e.end option3.dataZoom[0].end = e.end
this.$refs.Chart3.setData(option3) this.$refs.Chart3.setData(option3)
}) })
this.$refs.Chart3.chart.on('datazoom', (e) => { this.$refs.Chart3.chart.on('datazoom', (e) => {
option2.dataZoom[0].start = e.start option2.dataZoom[0].start = e.start
option2.dataZoom[0].end = e.end option2.dataZoom[0].end = e.end
@ -634,12 +802,31 @@ export default {
option1.dataZoom[0].end = e.end option1.dataZoom[0].end = e.end
this.$refs.Chart1.setData(option1) this.$refs.Chart1.setData(option1)
}) })
} catch (error) {
console.error('获取图表数据失败:', error)
this.$message.error('获取图表数据失败')
} finally {
this.loading = false
}
},
/** 查询树形结构 */
getTreeMonitorInfo() {
getMonitorInfoTree({ monitorType: 4 }).then(response => {
this.baseMonitorInfoOptions = this.handleTree(response.data, "id", "parentId")
})
} }
} }
} }
</script> </script>
<style scoped > <style scoped >
.app-container {
padding: 10px;
}
.charts-container {
margin-top: 10px;
}
.chart1 { .chart1 {
width: 50%; width: 50%;
@ -652,24 +839,43 @@ export default {
height: 40vh; height: 40vh;
display: inline-block; display: inline-block;
} }
.chart3 { .chart3 {
width: 50%; width: 50%;
height: 40vh; height: 40vh;
display: inline-block; display: inline-block;
} }
/* 选中节点的样式 */ .power-outage-summary {
/deep/ .el-tree .el-tree-node.is-current > .el-tree-node__content { margin-bottom: 10px;
}
.power-outage-info {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
.power-outage-info p {
margin: 5px 10px;
}
/* 选中节点的样式 */
::v-deep .el-tree .el-tree-node.is-current > .el-tree-node__content {
background-color: #67C23A !important; /* 选中节点的背景色 */ background-color: #67C23A !important; /* 选中节点的背景色 */
color: #303133 !important; /* 选中节点的文字颜色 */ color: #303133 !important; /* 选中节点的文字颜色 */
padding: 0; /* 清除内边距 */ padding: 0; /* 清除内边距 */
margin: 0; /* 清除外边距 */ margin: 0; /* 清除外边距 */
z-index: 1; /* 确保在其他元素之上 */ z-index: 1; /* 确保在其他元素之上 */
} }
/* 鼠标悬停时的样式 */ /* 鼠标悬停时的样式 */
.el-tree .el-tree-node__content:hover { .el-tree .el-tree-node__content:hover {
background-color: #e6f7ff; /* 鼠标悬停时的背景色 */ background-color: #e6f7ff; /* 鼠标悬停时的背景色 */
} }
/* 闪电图标 */
.el-icon-lightning:before {
content: "\e6a8"; /* 使用现有的element-ui图标字体 */
}
</style> </style>

Loading…
Cancel
Save