430 lines
13 KiB
Vue
430 lines
13 KiB
Vue
<template>
|
||
<!-- 历史趋势数据 -->
|
||
<div class="history_chart">
|
||
<MyEchart ref="historyChart" :options="echartsData"/>
|
||
</div>
|
||
</template>
|
||
<script lang="ts" setup>
|
||
|
||
|
||
import { nextTick, ref, watch } from 'vue'
|
||
import { yMethod } from '@/utils/echartMethod'
|
||
import MyEchart from '@/components/echarts/line/index.vue'
|
||
import { CheckData } from '@/api/check/interface'
|
||
|
||
const prop = defineProps({
|
||
tableData: {
|
||
type: Array as () => CheckData.TableRow[],
|
||
default: []
|
||
},
|
||
})
|
||
|
||
|
||
const color = [
|
||
'var(--el-color-primary)',
|
||
'#07CCCA',
|
||
'#00BFF5',
|
||
'#FFBF00',
|
||
'#77DA63',
|
||
'#D5FF6B',
|
||
'#Ff6600',
|
||
'#FF9100',
|
||
'#5B6E96',
|
||
'#66FFCC',
|
||
'#B3B3B3'
|
||
]
|
||
|
||
const chartsList = ref<any>([])
|
||
|
||
const echartsData = ref<any>(null)
|
||
//初始化趋势图
|
||
const lineStyle = [{ type: 'solid' }, { type: 'dashed' }, { type: 'dotted' }]
|
||
|
||
const setEchart = () => {
|
||
echartsData.value = {}
|
||
// y轴单位数组
|
||
let unitList: any = []
|
||
|
||
let groupedData = chartsList.value.reduce((acc: any, item: any) => {
|
||
let key = ''
|
||
if (item.phase == null) {
|
||
key = item.unit
|
||
} else {
|
||
key = item.anotherName
|
||
}
|
||
|
||
if (!acc[key]) {
|
||
acc[key] = []
|
||
}
|
||
acc[key].push(item)
|
||
return acc
|
||
}, {})
|
||
let result = Object.values(groupedData)
|
||
if (chartsList.value.length > 0) {
|
||
unitList = result.map((item: any) => {
|
||
return item[0].unit
|
||
})
|
||
}
|
||
|
||
echartsData.value = {
|
||
legend: {
|
||
itemWidth: 20,
|
||
itemHeight: 20,
|
||
itemStyle: { opacity: 0 }, //去圆点
|
||
type: 'scroll', // 开启滚动分页
|
||
// orient: 'vertical', // 垂直排列
|
||
top: 5,
|
||
right: 70
|
||
// width: 550,
|
||
// height: 50
|
||
},
|
||
grid: {
|
||
top: '30px',
|
||
},
|
||
tooltip: {
|
||
axisPointer: {
|
||
type: 'cross',
|
||
label: {
|
||
color: '#fff',
|
||
fontSize: 16
|
||
}
|
||
},
|
||
textStyle: {
|
||
color: '#fff',
|
||
fontStyle: 'normal',
|
||
opacity: 0.35,
|
||
fontSize: 14
|
||
},
|
||
backgroundColor: 'rgba(0,0,0,0.55)',
|
||
borderWidth: 0,
|
||
formatter(params: any) {
|
||
const xname = params[0].value[0]
|
||
let str = `${xname}<br>`
|
||
params.forEach((el: any, index: any) => {
|
||
let marker = ''
|
||
|
||
if (el.value[3] == 'dashed') {
|
||
for (let i = 0; i < 3; i++) {
|
||
marker += `<span style="display:inline-block;border: 2px ${el.color} solid;margin-right:5px;width:10px;height:0px;background-color:#ffffff00;"></span>`
|
||
}
|
||
} else {
|
||
marker = `<span style="display:inline-block;border: 2px ${el.color} ${el.value[3]};margin-right:5px;width:40px;height:0px;background-color:#ffffff00;"></span>`
|
||
}
|
||
let unit = el.value[2] ? el.value[2] : ''
|
||
// 格式化数值显示为4位小数
|
||
const value = parseFloat(el.value[1]);
|
||
const formattedValue = value.toFixed(4);
|
||
|
||
str += `${marker}${el.seriesName.split('(')[0]}:${formattedValue}${unit}<br>`
|
||
})
|
||
return str
|
||
}
|
||
},
|
||
color: ['#DAA520', '#2E8B57', '#A52a2a', ...color],
|
||
xAxis: {
|
||
type: 'time',
|
||
axisLabel: {
|
||
formatter: function(value) {
|
||
const date = new Date(value);
|
||
const hours = String(date.getHours()).padStart(2, '0');
|
||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||
const seconds = String(date.getSeconds()).padStart(2, '0');
|
||
return `${hours}:${minutes}:${seconds}`;
|
||
},
|
||
},
|
||
splitNumber: 8
|
||
},
|
||
yAxis: [{}],
|
||
|
||
options: {
|
||
series: []
|
||
}
|
||
}
|
||
|
||
|
||
if (chartsList.value.length > 0) {
|
||
let yData: any = []
|
||
echartsData.value.yAxis = []
|
||
let setList = [...new Set(unitList)]
|
||
|
||
setList.forEach((item: any, index: any) => {
|
||
if (index > 2) {
|
||
echartsData.value.grid.right = (index - 1) * 80
|
||
}
|
||
yData.push([])
|
||
let right = {
|
||
position: 'right',
|
||
offset: (index - 1) * 80
|
||
}
|
||
echartsData.value.yAxis.push({
|
||
name: item,
|
||
yAxisIndex: index,
|
||
splitNumber: 5,
|
||
minInterval: 0,
|
||
splitLine: {
|
||
show: false
|
||
},
|
||
axisLabel: {
|
||
// 添加标签格式化,支持小数显示
|
||
formatter: function(value) {
|
||
return value.toFixed(5);
|
||
}
|
||
},
|
||
...(index > 0 ? right : null)
|
||
})
|
||
})
|
||
let ABCName = [
|
||
...new Set(
|
||
chartsList.value.map((item: any) => {
|
||
return item.anotherName == '电压负序分量'
|
||
? '电压不平衡'
|
||
: item.anotherName == '电压正序分量'
|
||
? '电压不平衡'
|
||
: item.anotherName == '电压零序分量'
|
||
? '电压不平衡'
|
||
: item.anotherName
|
||
})
|
||
)
|
||
]
|
||
result.forEach((item: any, index: any) => {
|
||
let yMethodList: any = []
|
||
|
||
let ABCList = Object.values(
|
||
item.reduce((acc, item) => {
|
||
let key = ''
|
||
if (item.phase == null) {
|
||
key = item.anotherName
|
||
} else {
|
||
key = item.phase
|
||
}
|
||
|
||
if (!acc[key]) {
|
||
acc[key] = []
|
||
}
|
||
acc[key].push(item)
|
||
return acc
|
||
}, {})
|
||
)
|
||
|
||
ABCList.forEach((kk: any) => {
|
||
let colorName = kk[0].phase?.charAt(0).toUpperCase()
|
||
let lineS = ABCName.findIndex(
|
||
item =>
|
||
item ===
|
||
(kk[0].anotherName == '电压负序分量'
|
||
? '电压不平衡'
|
||
: kk[0].anotherName == '电压正序分量'
|
||
? '电压不平衡'
|
||
: kk[0].anotherName == '电压零序分量'
|
||
? '电压不平衡'
|
||
: kk[0].anotherName)
|
||
)
|
||
let seriesList: any = []
|
||
kk.forEach((cc: any) => {
|
||
if (cc.statisticalData !== null) {
|
||
yData[setList.indexOf(kk[0].unit)].push(cc.statisticalData?.toFixed(4))
|
||
}
|
||
|
||
seriesList.push([cc.time, cc.statisticalData, cc.unit, lineStyle[lineS].type])
|
||
})
|
||
|
||
|
||
echartsData.value.options.series.push({
|
||
name: kk[0].phase ? kk[0].phase + '相' + kk[0].anotherName : kk[0].anotherName,
|
||
type: 'line',
|
||
smooth: true,
|
||
color:
|
||
colorName == 'A' ? '#DAA520' : colorName == 'B' ? '#2E8B57' : colorName == 'C' ? '#A52a2a' : '#DAA520',
|
||
symbol: 'none',
|
||
data: seriesList,
|
||
lineStyle: lineStyle[lineS],
|
||
yAxisIndex: setList.indexOf(kk[0].unit)
|
||
})
|
||
})
|
||
})
|
||
// yData.forEach((item: any, index: any) => {
|
||
// let [min, max] = yMethod(item)
|
||
// echartsData.value.yAxis[index].min = min
|
||
// echartsData.value.yAxis[index].max = max
|
||
// })
|
||
let allValues: number[] = [];
|
||
yData.forEach(item => {
|
||
item.forEach((val: string) => {
|
||
allValues.push(val);
|
||
|
||
});
|
||
});
|
||
|
||
// 计算全局最大最小值
|
||
let globalMin = Math.min(...allValues) - 0.0001;
|
||
let globalMax = Math.max(...allValues) +0.0001
|
||
|
||
// 确保最小值不小于0
|
||
globalMin = Math.max(0, globalMin);
|
||
|
||
// 特殊情况:如果所有值都是0
|
||
if (globalMin === 0 && globalMax === 0) {
|
||
globalMax = 1;
|
||
}
|
||
// 为所有Y轴应用相同的范围
|
||
yData.forEach((item: any, index: any) => {
|
||
echartsData.value.yAxis[index].min = globalMin ;
|
||
echartsData.value.yAxis[index].max = globalMax;
|
||
});
|
||
}
|
||
}
|
||
|
||
// 监听 tableData 变化并触发 setEchart
|
||
watch(() => prop.tableData, (newTableData) => {
|
||
// 处理数据转换
|
||
const processedData: any[] = []
|
||
|
||
newTableData.forEach(item => {
|
||
// 处理标准设备数据
|
||
processDeviceData(item, '标准设备', item.uaStdDev, item.ubStdDev, item.ucStdDev, item.utStdDev, item.timeStdDev, item.unit)
|
||
.forEach(data => processedData.push(data));
|
||
// 处理被检设备数据
|
||
processDeviceData(item, '被检设备', item.uaDev, item.ubDev, item.ucDev, item.utDev, item.timeDev, item.unit)
|
||
.forEach(data => processedData.push(data));
|
||
});
|
||
// 更新 chartsList 数据
|
||
chartsList.value = processedData
|
||
// 延迟执行确保 DOM 已经渲染
|
||
nextTick(() => {
|
||
setTimeout(() => {
|
||
setEchart()
|
||
}, 100)
|
||
})
|
||
}, {
|
||
immediate: true, // 立即执行一次
|
||
deep: true // 深度监听
|
||
})
|
||
|
||
|
||
// 处理单个设备的数据
|
||
function processDeviceData(
|
||
item: any,
|
||
deviceType: string,
|
||
aValue: number | null,
|
||
bValue: number | null,
|
||
cValue: number | null,
|
||
tValue: number | null,
|
||
time: string,
|
||
unit: string
|
||
): any[] {
|
||
const result: any[] = [];
|
||
|
||
// 判断各相是否存在有效数据
|
||
const hasA = aValue !== null;
|
||
const hasB = bValue !== null;
|
||
const hasC = cValue !== null;
|
||
const hasT = tValue !== null;
|
||
|
||
// 计算有多少相有数据
|
||
const phaseCount = (hasA ? 1 : 0) + (hasB ? 1 : 0) + (hasC ? 1 : 0);
|
||
// 时间四舍五入到秒
|
||
const roundedTime = roundTimeToSecond(time);
|
||
|
||
if (hasA) {
|
||
result.push({
|
||
anotherName: deviceType,
|
||
phase: phaseCount > 1 ? 'A' : null,
|
||
statisticalData: aValue,
|
||
time: roundedTime,
|
||
unit: unit,
|
||
});
|
||
}
|
||
|
||
if (hasB) {
|
||
result.push({
|
||
anotherName: deviceType,
|
||
phase: phaseCount > 1 ? 'B' : null,
|
||
statisticalData: bValue,
|
||
time: roundedTime,
|
||
unit: unit,
|
||
});
|
||
}
|
||
|
||
if (hasC) {
|
||
result.push({
|
||
anotherName: deviceType,
|
||
phase: phaseCount > 1 ? 'C' : null,
|
||
statisticalData: cValue,
|
||
time: roundedTime,
|
||
unit: unit,
|
||
});
|
||
}
|
||
|
||
if (hasT) {
|
||
result.push({
|
||
anotherName: deviceType,
|
||
phase: null,
|
||
statisticalData: tValue,
|
||
time: roundedTime,
|
||
unit: unit,
|
||
});
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
// 时间四舍五入到秒的辅助函数
|
||
function roundTimeToSecond(timeStr: string): string {
|
||
if (!timeStr) return timeStr;
|
||
|
||
try {
|
||
// 直接使用本地时间解析,避免时区转换问题
|
||
// 替换空格为 T 以符合 ISO 格式
|
||
const isoString = timeStr.replace(' ', 'T');
|
||
const date = new Date(isoString);
|
||
|
||
// 检查日期是否有效
|
||
if (isNaN(date.getTime())) {
|
||
return timeStr;
|
||
}
|
||
|
||
// 获取毫秒部分
|
||
const milliseconds = date.getMilliseconds();
|
||
|
||
// 如果毫秒数大于等于500,则加一秒
|
||
if (milliseconds >= 500) {
|
||
date.setSeconds(date.getSeconds() + 1);
|
||
}
|
||
|
||
// 清除毫秒部分
|
||
date.setMilliseconds(0);
|
||
|
||
// 手动格式化为 YYYY-MM-DD HH:mm:ss 格式(使用本地时间)
|
||
const year = date.getFullYear();
|
||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||
const day = String(date.getDate()).padStart(2, '0');
|
||
const hours = String(date.getHours()).padStart(2, '0');
|
||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||
const seconds = String(date.getSeconds()).padStart(2, '0');
|
||
|
||
const formattedTime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||
|
||
|
||
return formattedTime;
|
||
} catch (error) {
|
||
// 如果解析失败,返回原始时间字符串
|
||
console.error('时间解析错误:', error);
|
||
return timeStr;
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.history_chart {
|
||
width: 100%;
|
||
height: 360px; /* 明确指定高度 */
|
||
margin-top: 0px;
|
||
}
|
||
|
||
/* 或者设置最小高度 */
|
||
.history_chart {
|
||
width: 100%;
|
||
min-height: 300px; /* 确保有最小高度 */
|
||
margin-top: 10px;
|
||
}
|
||
</style> |