diff --git a/src/views/ems/report/currentSteamCurve/copy.vue b/src/views/ems/report/currentSteamCurve/copy.vue new file mode 100644 index 0000000..8ed024a --- /dev/null +++ b/src/views/ems/report/currentSteamCurve/copy.vue @@ -0,0 +1,676 @@ + + + + + \ No newline at end of file diff --git a/src/views/ems/report/currentSteamCurve/good.vue b/src/views/ems/report/currentSteamCurve/good.vue new file mode 100644 index 0000000..9e0fc77 --- /dev/null +++ b/src/views/ems/report/currentSteamCurve/good.vue @@ -0,0 +1,843 @@ + + + + + \ No newline at end of file diff --git a/src/views/ems/report/currentSteamCurve/index.vue b/src/views/ems/report/currentSteamCurve/index.vue index d4c3e7b..ecb828b 100644 --- a/src/views/ems/report/currentSteamCurve/index.vue +++ b/src/views/ems/report/currentSteamCurve/index.vue @@ -31,15 +31,7 @@ - - - - - - - - - + - - - - + 搜索 重置 - - - + + + + + + +
+ 停电统计 + + 导出停电记录 + +
+
+

停电次数: {{ powerOutageSummary.count }} 次

+

最长停电: {{ powerOutageSummary.longestDuration }} 小时

+

总停电时长: {{ powerOutageSummary.totalDuration }} 小时

+
+
+ + +
+ + + +
@@ -194,11 +192,22 @@ export default { 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(); @@ -209,13 +218,8 @@ export default { 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.initBaseChartOptions() this.getTreeselect() this.getTreeMonitorInfo() this.getList() @@ -227,6 +231,72 @@ export default { } }, 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) { @@ -238,19 +308,15 @@ export default { 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.queryParams.params['beginRecordTime'] = this.daterangeRecordTime[0]; + this.queryParams.params['endRecordTime'] = this.daterangeRecordTime[1]; } - this.getChart() }, @@ -297,6 +363,50 @@ export default { ...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 += ''; + columns.forEach(col => { + table += ``; + }); + table += ''; + + // 添加数据行 + this.powerOutageList.forEach(item => { + table += ''; + columns.forEach(col => { + table += ``; + }); + table += ''; + }); + table += '
${col.label}
${item[col.prop]}
'; + + // 创建临时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() { @@ -318,328 +428,405 @@ export default { 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, // 使用null而不是0 + 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, // 使用null而不是0 + 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 '停电
时间: ' + params[0].axisValue; + } + return params[0].seriesName + ': ' + params[0].value + '
时间: ' + 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 + })) + } + }]; + + // 确保y轴包含0点 + 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() { + this.loading = true; let query = JSON.parse(JSON.stringify(this.queryParams)) - const {data} = await steamInstantList(query) + try { + 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' - },*/ + // 处理瞬时流量数据 + 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' + ); - 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.Chart1.setData(option1) 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) - }) + 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") + }) } } }