3 Commits

Author SHA1 Message Date
sjl
cd33151920 数据查询历史趋势 2025-12-10 15:19:26 +08:00
sjl
0e0b753126 Merge branch 'master' of http://192.168.1.22:3000/ClientApps/pqs-9100_client 2025-12-10 11:17:25 +08:00
sjl
61ee760f52 趋势图 2025-12-10 11:17:19 +08:00
4 changed files with 483 additions and 3 deletions

View File

@@ -8,7 +8,9 @@
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
// import echarts from './echarts'
import * as echarts from 'echarts' // 全引入
// import 'echarts/lib/component/dataZoom'
import 'echarts-gl'
import 'echarts-liquidfill'
import 'echarts/lib/component/dataZoom'
const color = [
'var(--el-color-primary)',
@@ -39,11 +41,13 @@ const resizeHandler = () => {
})
}
const initChart = () => {
if (!props.isInterVal && !props.pieInterVal) {
chart?.dispose()
}
// chart?.dispose()
chart = echarts.init(chartRef.value as HTMLDivElement)
const options = {
title: {
left: 'center',

View File

@@ -0,0 +1,72 @@
const dataProcessing = (arr: any[]) => {
return arr
.filter(item => typeof item === 'number' || (typeof item === 'string' && !isNaN(parseFloat(item))))
.map(item => (typeof item === 'number' ? item : parseFloat(item)))
}
const calculateValue = (o: number, value: number, num: number, isMin: boolean) => {
if (value === 0) {
return 0
} else if (value > 0 && Math.abs(value) < 1 && isMin == true) {
return 0
} else if (value > -1 && value < 0 && isMin == false) {
return 0
}
let base
if (Math.abs(o) >= 100) {
base = 100
} else if (Math.abs(o) >= 10) {
base = 10
} else if (Math.abs(o) >= 1) {
base = 1
} else {
const multiple = 1 / 0.1
base = Math.ceil(Math.abs(o) * multiple) / multiple
}
let calculatedValue
if (isMin) {
if (value < 0) {
calculatedValue = value + num * value
} else {
calculatedValue = value - num * value
}
} else {
if (value < 0) {
calculatedValue = value - num * value
} else {
calculatedValue = value + num * value
}
}
if (base === 0.1) {
return parseFloat(calculatedValue.toFixed(1))
} else if (isMin) {
return Math.floor(calculatedValue / base) * base
} else {
return Math.ceil(calculatedValue / base) * base
}
}
// 处理y轴最大最小值
export const yMethod = (arr: any) => {
const num = 0.2
const numList = dataProcessing(arr)
let maxValue = 0
let minValue = 0
let max = 0
let min = 0
maxValue = Math.max(...numList)
minValue = Math.min(...numList)
const o = maxValue - minValue == 0 ? maxValue : maxValue - minValue
min = calculateValue(o, minValue, num, true)
max = calculateValue(o, maxValue, num, false)
return [min, max]
}

View File

@@ -0,0 +1,397 @@
<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] : ''
str += `${marker}${el.seriesName.split('(')[0]}${el.value[1]}${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: 1,
splitLine: {
show: false
},
...(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(2))
}
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' : '',
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
})
}
}
// 监听 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>

View File

@@ -173,6 +173,12 @@
:currentScriptTypeName="currentScriptTypeName"
/>
</el-tab-pane>
<el-tab-pane label="历史趋势" name="chartTab">
<CompareDataCheckChart
v-if="activeTab === 'chartTab'"
:tableData="rawTableData.length == 0 ? [] : currentRawTableData"
/>
</el-tab-pane>
</el-tabs>
</div>
</div>
@@ -185,6 +191,7 @@ import { dialogBig } from '@/utils/elementBind'
import { computed, reactive, ref } from 'vue'
import CompareDataCheckResultTable from './compareDataCheckResultTable.vue'
import CompareDataCheckRawDataTable from './compareDataCheckRawDataTable.vue'
import CompareDataCheckChart from './compareDataCheckChart.vue'
import { CheckData } from '@/api/check/interface'
import { useCheckStore } from '@/stores/modules/check'
import { Histogram, Postcard } from '@element-plus/icons-vue'
@@ -269,6 +276,7 @@ const currentRawTableData = computed(() => {
})
const open = async (row: any, chnNum: string, deviceId: string | null, source: number) => {
activeTab.value = 'resultTab'
isWaveData.value = false
pattern.value = dictStore.getDictData('Pattern').find(item => item.name === modeStore.currentMode)?.id ?? '' //获取数据字典中对应的id
rowList.value = {}
@@ -405,7 +413,6 @@ const getBasicInformation = async (scriptType: any) => {
})
formContent.dataRule = res.data.dataRule
console.log('formContent.dataRule',formContent.dataRule)
formContent.deviceName = res.data.deviceName
formContent.errorSysId = res.data.errorSysId
chnMapList.value = res.data.chnMap
@@ -521,7 +528,7 @@ const getResults = async (code: any) => {
// 判断是否为录波数据请求
const isWaveDataRequest = code === 'wave_data' || isWaveData.value
console.log(checkStore.plan)
getContrastResult({
planId: checkStore.plan.id,
scriptType: rowList.value.scriptType,