实时推送&绘制特性点

This commit is contained in:
caozehui
2026-05-06 10:53:30 +08:00
parent 71d80e67f1
commit 5ca5d73f98
3 changed files with 138 additions and 268 deletions

View File

@@ -1,4 +1,4 @@
<template>
<template>
<el-card class="dip-chart-card" shadow="never">
<template #header>
<div class="card-header">
@@ -17,9 +17,9 @@
导出数据
</el-button>
<el-button
v-if="!props.autoDrawCurve"
type="primary"
plain
:loading="curveLoading"
class="draw-curve-button"
@click="drawCharacteristicCurve"
>
@@ -41,7 +41,6 @@ import { ElMessage } from 'element-plus'
import { Document, Download } from '@element-plus/icons-vue'
import * as XLSX from 'xlsx'
import MyEchart from '@/components/echarts/line/index.vue'
import { getFreqConverterSCurve } from '@/api/device/freqConverter'
type ChartPointStatus = 'pass' | 'fail'
@@ -62,16 +61,19 @@ const props = defineProps<{
selectedMapping?: Record<string, any> | null
webMsgSend?: any
resultData?: any
autoDrawCurve?: boolean
}>()
const STATUS_COLOR_MAP: Record<ChartPointStatus, string> = {
pass: '#1d4ed8',
fail: '#111111'
pass: '#4e73df',
fail: '#4b4b4b'
}
const CHARACTERISTIC_POINT_COLOR = '#ff4d4f'
const chartPoints = ref<ChartPoint[]>([])
const characteristicCurveData = ref<Array<[number, number]>>([])
const curveLoading = ref(false)
const characteristicCurveVisible = ref(false)
const chartRef = ref<any>(null)
const selectedMappingText = computed(() => {
@@ -90,21 +92,23 @@ const positiveDurations = computed(() => {
})
const xAxisMin = computed(() => {
if (!positiveDurations.value.length) {
return 0.001
}
const minValue = Math.min(...positiveDurations.value)
return Math.min(0.001, Number(minValue.toFixed(3)))
return 0.001
// if (!positiveDurations.value.length) {
// return 0.001
// }
//
// const minValue = Math.min(...positiveDurations.value)
// return Math.min(0.001, Number(minValue.toFixed(3)))
})
const xAxisMax = computed(() => {
if (!positiveDurations.value.length) {
return 60
}
const maxValue = Math.max(...positiveDurations.value)
return Math.max(Number((maxValue * 1.05).toFixed(3)), 60)
return 1000
// if (!positiveDurations.value.length) {
// return 60
// }
//
// const maxValue = Math.max(...positiveDurations.value)
// return Math.max(Number((maxValue * 1.05).toFixed(3)), 60)
})
const sortedChartPoints = computed(() => {
@@ -127,105 +131,34 @@ const sortedCharacteristicCurveData = computed(() => {
})
})
const clampValue = (value: number, min: number, max: number) => {
return Math.min(max, Math.max(min, value))
}
const buildExtendedCurvePoint = (points: Array<[number, number]>, targetDuration: number) => {
if (!points.length || !Number.isFinite(targetDuration) || targetDuration <= 0) {
return null as [number, number] | null
}
const lastPoint = points[points.length - 1]
if (!lastPoint || targetDuration === lastPoint[0]) {
return null as [number, number] | null
}
if (points.length === 1) {
return [targetDuration, lastPoint[1]] as [number, number]
}
const recentDistinctPoints: Array<[number, number]> = []
for (let index = points.length - 1; index >= 0 && recentDistinctPoints.length < 3; index -= 1) {
const point = points[index]
if (recentDistinctPoints.some(item => item[0] === point[0])) {
continue
}
recentDistinctPoints.unshift(point)
}
if (recentDistinctPoints.length < 2) {
return [targetDuration, lastPoint[1]] as [number, number]
}
const toLogDuration = (value: number) => Math.log10(value)
const getSlope = (start: [number, number], end: [number, number]) => {
const deltaX = toLogDuration(end[0]) - toLogDuration(start[0])
if (!Number.isFinite(deltaX) || deltaX === 0) {
return null
}
return (end[1] - start[1]) / deltaX
}
let slope = getSlope(
recentDistinctPoints[recentDistinctPoints.length - 2],
recentDistinctPoints[recentDistinctPoints.length - 1]
)
if (recentDistinctPoints.length >= 3) {
const previousSlope = getSlope(recentDistinctPoints[0], recentDistinctPoints[1])
if (previousSlope !== null && slope !== null) {
slope = previousSlope * 0.35 + slope * 0.65
}
}
if (slope === null) {
return [targetDuration, lastPoint[1]] as [number, number]
}
const deltaX = toLogDuration(targetDuration) - toLogDuration(lastPoint[0])
if (!Number.isFinite(deltaX)) {
return null as [number, number] | null
}
const predictedResidualVoltage = lastPoint[1] + slope * deltaX
const nearbyResidualVoltages = recentDistinctPoints.map(item => item[1])
const minResidualVoltage = Math.max(0, Math.min(...nearbyResidualVoltages) - 15)
const maxResidualVoltage = Math.min(100, Math.max(...nearbyResidualVoltages) + 15)
return [
targetDuration,
Number(clampValue(predictedResidualVoltage, minResidualVoltage, maxResidualVoltage).toFixed(2))
] as [number, number]
}
const inferredCharacteristicCurvePoint = computed(() => {
return buildExtendedCurvePoint(sortedCharacteristicCurveData.value, xAxisMax.value)
})
const solidCharacteristicCurveSeriesData = computed(() => {
return sortedCharacteristicCurveData.value.map(item => [item[0], item[1], 'Backend point'])
})
const dashedCharacteristicCurveSeriesData = computed(() => {
const lastPoint = sortedCharacteristicCurveData.value[sortedCharacteristicCurveData.value.length - 1]
const extensionPoint = inferredCharacteristicCurvePoint.value
if (!lastPoint || !extensionPoint) {
return []
if (!characteristicCurveVisible.value) {
return [] as Array<[number, number, string]>
}
return [
{
value: [lastPoint[0], lastPoint[1], 'Backend point'],
symbol: 'none'
},
{
value: [extensionPoint[0], extensionPoint[1], 'Inferred point']
}
]
return sortedCharacteristicCurveData.value.map(item => [item[0], item[1], '特性曲线'])
})
const characteristicCurvePointSeriesData = computed(() => {
return sortedCharacteristicCurveData.value.map(item => ({
value: [item[0], item[1], '特性点']
}))
})
const passPointSeriesData = computed(() => {
return sortedChartPoints.value
.filter(item => item.status === 'pass')
.map(item => ({
value: [item.duration, item.residualVoltage, getStatusText(item.status)]
}))
})
const failPointSeriesData = computed(() => {
return sortedChartPoints.value
.filter(item => item.status === 'fail')
.map(item => ({
value: [item.duration, item.residualVoltage, getStatusText(item.status)]
}))
})
const hasChartData = computed(() => {
@@ -374,65 +307,6 @@ const normalizePoint = (source: Record<string, any>): ChartPoint | null => {
}
}
const normalizeCurveData = (source: unknown) => {
if (!Array.isArray(source)) {
return [] as Array<[number, number]>
}
return source
.map(item => {
if (Array.isArray(item) && item.length >= 2) {
const duration = Number(item[0])
const residualVoltage = Number(item[1])
if (Number.isFinite(duration) && Number.isFinite(residualVoltage) && duration > 0) {
return [duration, residualVoltage] as [number, number]
}
}
if (item && typeof item === 'object') {
const record = item as Record<string, any>
const tolerant = normalizeTolerantValue(record.tolerant)
if (tolerant !== null && tolerant !== 2) {
return null
}
const duration = normalizeDuration(record)
const residualVoltage = normalizeResidualVoltageValue(record)
if (duration !== null && residualVoltage !== null && duration > 0) {
return [duration, residualVoltage] as [number, number]
}
}
return null
})
.filter((item): item is [number, number] => !!item)
}
const extractCurveData = (payload: any) => {
if (!payload) {
return [] as Array<[number, number]>
}
const candidates = [
payload,
payload?.data,
payload?.data?.records,
payload?.data?.points,
payload?.points,
payload?.records,
payload?.list
]
for (const candidate of candidates) {
const normalized = normalizeCurveData(candidate)
if (normalized.length) {
return normalized
}
}
return [] as Array<[number, number]>
}
const extractCharacteristicCurvePoints = (payload: any) => {
const result: Array<[number, number]> = []
const seen = new Set<string>()
@@ -472,6 +346,22 @@ const extractCharacteristicCurvePoints = (payload: any) => {
return result
}
const mergeCharacteristicCurvePoints = (points: Array<[number, number]>) => {
if (!points.length) {
return
}
const existingPointMap = new Map(
characteristicCurveData.value.map(item => [`${item[0]}|${item[1]}`, item] as const)
)
points.forEach(item => {
existingPointMap.set(`${item[0]}|${item[1]}`, item)
})
characteristicCurveData.value = Array.from(existingPointMap.values())
}
const extractPoints = (payload: any) => {
const result: ChartPoint[] = []
const seen = new Set<string>()
@@ -511,35 +401,20 @@ const extractPoints = (payload: any) => {
return result
}
const drawCharacteristicCurve = async () => {
const freqConverterId = props.selectedMapping?.freqConverterId
if (!freqConverterId) {
ElMessage.warning('未获取到变频器ID')
const updateCharacteristicCurveVisibility = () => {
if (props.autoDrawCurve) {
characteristicCurveVisible.value = characteristicCurveData.value.length > 0
}
}
const drawCharacteristicCurve = () => {
if (!sortedCharacteristicCurveData.value.length) {
characteristicCurveVisible.value = false
ElMessage.warning('暂无特性曲线点')
return
}
curveLoading.value = true
try {
const result = await getFreqConverterSCurve({
converterId: freqConverterId
})
const normalizedCurveData = extractCurveData(result)
if (!normalizedCurveData.length) {
characteristicCurveData.value = []
ElMessage.warning('未获取到特性曲线数据')
return
}
characteristicCurveData.value = normalizedCurveData
} catch (error) {
console.error('draw characteristic curve failed:', error)
characteristicCurveData.value = []
} finally {
curveLoading.value = false
}
characteristicCurveVisible.value = true
}
const downloadChartImage = async () => {
@@ -577,24 +452,24 @@ const exportChartData = () => {
if (sortedChartPoints.value.length) {
const pointSheet = XLSX.utils.json_to_sheet(
sortedChartPoints.value.map((item, index) => ({
'序号': index + 1,
'持续时间_s': item.duration,
'暂降幅值_%': item.residualVoltage,
'状态': getStatusText(item.status)
序号: index + 1,
持续时间_s: item.duration,
残余电压_pct: item.residualVoltage,
状态: getStatusText(item.status)
}))
)
XLSX.utils.book_append_sheet(workbook, pointSheet, '暂降点')
XLSX.utils.book_append_sheet(workbook, pointSheet, '耐受点')
}
if (sortedCharacteristicCurveData.value.length) {
const curveSheet = XLSX.utils.json_to_sheet(
sortedCharacteristicCurveData.value.map((item, index) => ({
'序号': index + 1,
'持续时间_s': item[0],
'暂降幅值_%': item[1]
序号: index + 1,
持续时间_s: item[0],
残余电压_pct: item[1]
}))
)
XLSX.utils.book_append_sheet(workbook, curveSheet, '特性曲线')
XLSX.utils.book_append_sheet(workbook, curveSheet, '特性')
}
const workbookBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' })
@@ -642,7 +517,7 @@ const chartOptions = computed(() => {
legend: {
top: 0,
right: 0,
data: ['特性测试曲线']
data: ['特性曲线', '特性点', '耐受点', '不耐受点']
},
xAxis: {
type: 'log',
@@ -676,7 +551,7 @@ const chartOptions = computed(() => {
},
yAxis: {
type: 'value',
name: '暂降幅值',
name: '残余电压',
min: 0,
max: 100,
interval: 10,
@@ -704,46 +579,45 @@ const chartOptions = computed(() => {
dataZoom: [],
series: [
{
name: '特性测试曲线',
name: '特性曲线',
type: 'line',
smooth: true,
showSymbol: true,
symbolSize: 7,
showSymbol: false,
lineStyle: {
color: '#ff2a2a',
color: CHARACTERISTIC_POINT_COLOR,
width: 3
},
itemStyle: {
color: '#ff2a2a'
color: CHARACTERISTIC_POINT_COLOR
},
data: solidCharacteristicCurveSeriesData.value
},
{
name: '特性曲线延伸',
type: 'line',
smooth: true,
showSymbol: true,
symbolSize: 7,
lineStyle: {
color: '#ff2a2a',
width: 3,
type: 'dashed'
},
itemStyle: {
color: '#ff2a2a'
},
data: dashedCharacteristicCurveSeriesData.value
},
{
name: '暂降点',
name: '特性',
type: 'scatter',
symbolSize: 10,
data: chartPoints.value.map(item => ({
value: [item.duration, item.residualVoltage, getStatusText(item.status)],
itemStyle: {
color: STATUS_COLOR_MAP[item.status]
}
}))
itemStyle: {
color: CHARACTERISTIC_POINT_COLOR
},
data: characteristicCurvePointSeriesData.value
},
{
name: '耐受点',
type: 'scatter',
symbolSize: 10,
itemStyle: {
color: STATUS_COLOR_MAP.pass
},
data: passPointSeriesData.value
},
{
name: '不耐受点',
type: 'scatter',
symbolSize: 10,
itemStyle: {
color: STATUS_COLOR_MAP.fail
},
data: failPointSeriesData.value
}
],
options: {
@@ -752,6 +626,14 @@ const chartOptions = computed(() => {
}
})
watch(
() => props.autoDrawCurve,
newValue => {
characteristicCurveVisible.value = newValue ? characteristicCurveData.value.length > 0 : false
},
{ immediate: true }
)
watch(
() => props.webMsgSend,
newValue => {
@@ -760,29 +642,21 @@ watch(
}
const nextPoints = extractPoints(newValue)
if (!nextPoints.length) {
const nextCurvePoints = extractCharacteristicCurvePoints(newValue)
if (nextCurvePoints.length) {
characteristicCurveData.value = nextCurvePoints
}
return
if (nextPoints.length) {
const existingPointMap = new Map(
chartPoints.value.map(item => [`${item.duration}|${item.residualVoltage}`, item] as const)
)
nextPoints.forEach(item => {
const key = `${item.duration}|${item.residualVoltage}`
existingPointMap.set(key, item)
})
chartPoints.value = Array.from(existingPointMap.values())
}
const existingPointMap = new Map(
chartPoints.value.map(item => [`${item.duration}|${item.residualVoltage}`, item] as const)
)
nextPoints.forEach(item => {
const key = `${item.duration}|${item.residualVoltage}`
existingPointMap.set(key, item)
})
chartPoints.value = Array.from(existingPointMap.values())
const nextCurvePoints = extractCharacteristicCurvePoints(newValue)
if (nextCurvePoints.length) {
characteristicCurveData.value = nextCurvePoints
}
mergeCharacteristicCurvePoints(extractCharacteristicCurvePoints(newValue))
updateCharacteristicCurveVisibility()
},
{ deep: true }
)
@@ -795,10 +669,8 @@ watch(
}
chartPoints.value = extractPoints(newValue)
const nextCurvePoints = extractCharacteristicCurvePoints(newValue)
if (nextCurvePoints.length) {
characteristicCurveData.value = nextCurvePoints
}
characteristicCurveData.value = extractCharacteristicCurvePoints(newValue)
updateCharacteristicCurveVisibility()
},
{ deep: true, immediate: true }
)
@@ -808,6 +680,7 @@ watch(
() => {
chartPoints.value = []
characteristicCurveData.value = []
characteristicCurveVisible.value = false
}
)
</script>
@@ -856,11 +729,6 @@ watch(
color: var(--el-text-color-secondary);
}
.draw-curve-button {
margin-top: -2px;
flex-shrink: 0;
}
.chart-wrapper {
height: 400px;
}

View File

@@ -13,6 +13,7 @@
v-if="dialogVisible && selectedMapping"
:selected-mapping="selectedMapping"
:result-data="resultPayload"
:auto-draw-curve="true"
/>
<el-empty v-else-if="!loading" description="暂无检测结果" />

View File

@@ -30,6 +30,7 @@
:selected-mapping="selectedMapping"
:web-msg-send="webSocketMessage"
:result-data="historyResultData"
:auto-draw-curve="false"
/>
</div>