From 7be6615a8906f0db4b4fefa1995dcaa6e3c6cdcb Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期三, 02 九月 2026 20:39:07 +0800
Subject: [PATCH] 让工步图表读取实时有效数据
---
src/components/ConsoleChartPanel.vue | 142 +++++++++++++++++++++++++++++++++--------------
1 files changed, 100 insertions(+), 42 deletions(-)
diff --git a/src/components/ConsoleChartPanel.vue b/src/components/ConsoleChartPanel.vue
index 2ef3740..b432da0 100644
--- a/src/components/ConsoleChartPanel.vue
+++ b/src/components/ConsoleChartPanel.vue
@@ -12,6 +12,7 @@
} from '@/utils/compareReportPrint'
import type { CompareMetricKey } from '@/stores/compareReports'
import { useBatteryGroupFields } from '@/stores/batteryGroupFields'
+import type { PersistedTestRecord, TestLogicStepMapping } from '@/api/tests'
interface ConsoleCell {
no: string
@@ -28,17 +29,22 @@
durationMinutes?: number
testName?: string
batteryGroup?: string
+ records: PersistedTestRecord[]
+ logicStepMappings?: TestLogicStepMapping[]
}>(), {
durationMinutes: 5,
testName: '',
batteryGroup: '',
+ records: () => [],
+ logicStepMappings: () => [],
})
use([CanvasRenderer, LineChart, GridComponent, LegendComponent, TooltipComponent])
const palette = ['#3978f6', '#7d42d8', '#f08a3e', '#23a983', '#e15268', '#1b9ac8', '#b5892f', '#6957c8', '#4f8d44', '#d264a3', '#46748e', '#a95d37']
const seriesColors = ref<string[]>(props.cells.map((_, index) => palette[index % palette.length]))
-const selectedStep = ref('5-21 恒流充电')
+const selectedStep = ref('')
+const selectedCycle = ref('')
const selectedCellNos = ref<string[]>(props.cells.map(cell => cell.no))
const experimentName = ref(props.testName || 'XXX实验')
const batteryFields = ref(['额定容量', '电流', '内阻'])
@@ -69,9 +75,37 @@
循环寿命模板: ['容量保持率', '能量效率', '循环次数', '内阻变化'],
热特性模板: ['最高温度', '温差', '温升速率'],
}
-const stepOptions = ['5-21 恒流充电', '5-22 搁置', '5-23 恒流放电', '5-24 搁置']
-const pointCount = 31
const chartUpdateOptions = { notMerge: true, lazyUpdate: false }
+
+interface StepOption {
+ value: string
+ label: string
+ cycleNo: number
+ stepNo?: number
+ logical?: TestLogicStepMapping
+}
+
+const cycleOptions = computed(() => [...new Set(props.records
+ .map(record => Number(record.cycleNo))
+ .filter(value => Number.isFinite(value) && value > 0))].sort((a, b) => a - b))
+
+const stepOptions = computed<StepOption[]>(() => {
+ const actual = new Map<string, StepOption>()
+ for (const record of props.records) {
+ const cycleNo = Number(record.cycleNo)
+ const stepNo = Number(record.stepNo)
+ if (!Number.isFinite(cycleNo) || !Number.isFinite(stepNo) || cycleNo <= 0 || stepNo <= 0) continue
+ const value = `actual:${cycleNo}:${stepNo}`
+ if (!actual.has(value)) actual.set(value, { value, label: `${cycleNo}-${stepNo} ${record.stepName || '-'}`, cycleNo, stepNo })
+ }
+ const logical: StepOption[] = []
+ for (const cycleNo of cycleOptions.value) {
+ for (const mapping of props.logicStepMappings || []) {
+ logical.push({ value: `logical:${cycleNo}:${mapping.logicalNo}`, label: `${cycleNo}-逻辑${mapping.logicalNo} ${mapping.logicalName}`, cycleNo, logical: mapping })
+ }
+ }
+ return [...actual.values(), ...logical]
+})
function ensureDetailFieldValues() {
detailFields.value.forEach(field => {
@@ -97,6 +131,19 @@
}, { deep: true })
watch(detailFields, ensureDetailFieldValues)
+
+watch(stepOptions, options => {
+ if (!options.length) {
+ selectedStep.value = ''
+ return
+ }
+ if (!options.some(option => option.value === selectedStep.value)) selectedStep.value = options[0].value
+}, { immediate: true })
+
+watch(cycleOptions, options => {
+ const current = Number(selectedCycle.value)
+ if (selectedCycle.value && !options.includes(current)) selectedCycle.value = ''
+})
async function addCustomField() {
try {
@@ -163,48 +210,55 @@
if (color) seriesColors.value[index] = color
}
-function elapsedLabel(index: number) {
- const totalSeconds = Math.round(props.durationMinutes * 60 * index / (pointCount - 1))
- const hours = Math.floor(totalSeconds / 3600)
- const minutes = Math.floor((totalSeconds % 3600) / 60)
- return `${hours}:${String(minutes).padStart(2, '0')}`
+function metricMatches(metric: CompareMetricKey, record: PersistedTestRecord) {
+ if (metric === 'voltage') return record.sourceType !== 'COLLECTOR' && record.voltage != null
+ if (metric === 'current') return record.sourceType !== 'COLLECTOR' && record.current != null
+ if (record.sourceType !== 'COLLECTOR') return false
+ const code = String(record.metricCode || '').toUpperCase()
+ if (metric === 'temperature') return code === 'TEMPERATURE' || code.includes('TEMPERATURE')
+ if (metric === 'positiveVoltage') return code === 'POSITIVE_POTENTIAL' || code.includes('POSITIVE_POTENTIAL')
+ if (metric === 'negativeVoltage') return code === 'NEGATIVE_POTENTIAL' || code.includes('NEGATIVE_POTENTIAL')
+ return false
}
-function baseValue(cell: ConsoleCell, metric: CompareMetricDefinition) {
- if (metric.key === 'voltage') return Number(cell.voltage)
- if (metric.key === 'current') return Number(cell.current) / 1000
- if (metric.key === 'positiveVoltage') return Number(cell.positive)
- if (metric.key === 'negativeVoltage') return Number(cell.negative)
- return Number(cell.temperature)
+function metricValue(metric: CompareMetricKey, record: PersistedTestRecord) {
+ if (metric === 'voltage') return Number(record.voltage)
+ if (metric === 'current') return Number(record.current)
+ return Number(record.metricValue)
}
-function seriesValues(cell: ConsoleCell, metric: CompareMetricDefinition, cellIndex: number) {
- const base = baseValue(cell, metric)
- const isRest = selectedStep.value.includes('搁置')
- const isDischarge = selectedStep.value.includes('放电')
- return Array.from({ length: pointCount }, (_, pointIndex) => {
- const progress = pointIndex / (pointCount - 1)
- const wave = Math.sin(progress * Math.PI * 3 + cellIndex * 0.45)
- const ripple = Math.cos(progress * Math.PI * 7 + cellIndex * 0.18)
- const amplitude = metric.key === 'temperature' ? 1.8 : metric.key === 'current' ? 0.55 : 0.12
- if (isRest) {
- const restBase = metric.key === 'current' ? 0 : base
- const restAmplitude = metric.key === 'temperature' ? 0.35 : metric.key === 'current' ? 0.04 : 0.025
- return Number((restBase + wave * restAmplitude + ripple * restAmplitude * 0.18).toFixed(3))
- }
- const trend = metric.key === 'temperature'
- ? progress * (isDischarge ? 1.2 : 2.6)
- : metric.key === 'current'
- ? progress * (isDischarge ? -0.42 : 0.3)
- : progress * (isDischarge ? -0.3 : 0.22)
- return Number((base + wave * amplitude + ripple * amplitude * 0.22 + trend).toFixed(3))
+function selectedRecords() {
+ const option = stepOptions.value.find(item => item.value === selectedStep.value)
+ if (!option) return []
+ const cycle = selectedCycle.value ? Number(selectedCycle.value) : option.cycleNo
+ return props.records.filter(record => {
+ if (Number(record.cycleNo) !== cycle) return false
+ const stepNo = Number(record.stepNo)
+ if (option.logical) return stepNo >= option.logical.startStepNo && stepNo <= option.logical.endStepNo
+ return stepNo === option.stepNo
})
}
+const hasSelectedRecords = computed(() => selectedRecords().length > 0)
+
+function recordTime(record: PersistedTestRecord) {
+ const value = record.deviceTime || record.recordedAt
+ const parsed = Date.parse(value || '')
+ return Number.isFinite(parsed) ? parsed : null
+}
+
function chartOption(metric: CompareMetricDefinition) {
+ const source = selectedRecords()
const cellIndexes = selectedCells.value.map(cell => props.cells.findIndex(item => item.no === cell.no))
- const seriesData = selectedCells.value.map((cell, index) => seriesValues(cell, metric, Math.max(0, cellIndexes[index])))
- const values = seriesData.flat()
+ const seriesData = selectedCells.value.map(cell => {
+ return source.filter(record => record.batteryNo === cell.no && metricMatches(metric.key, record))
+ .map(record => ({ time: recordTime(record), value: metricValue(metric.key, record) }))
+ .filter(point => point.time !== null && Number.isFinite(point.value))
+ .sort((a, b) => (a.time as number) - (b.time as number))
+ })
+ const timestamps = [...new Set(seriesData.flat().map(point => point.time as number))].sort((a, b) => a - b)
+ const start = timestamps[0]
+ const values = seriesData.flat().map(point => point.value)
const dataMin = values.length ? Math.min(...values) : 0
const dataMax = values.length ? Math.max(...values) : 1
const margin = Math.max((dataMax - dataMin) * 0.16, metric.key === 'temperature' ? 1 : 0.08)
@@ -243,10 +297,10 @@
xAxis: {
type: 'category',
boundaryGap: false,
- name: '时间/h:mm',
+ name: '设备时间',
nameLocation: 'middle',
nameGap: 29,
- data: Array.from({ length: pointCount }, (_, index) => elapsedLabel(index)),
+ data: timestamps.map(value => start == null ? new Date(value).toISOString() : `${((value - start) / 3600000).toFixed(3)} h`),
axisLine: { lineStyle: { color: '#cbd8e4' } },
axisLabel: { color: '#8795a3', fontSize: 10, interval: 4 },
nameTextStyle: { color: '#52606d', fontSize: 10 },
@@ -258,7 +312,10 @@
smooth: true,
showSymbol: false,
lineStyle: { width: 2 },
- data: seriesData[index],
+ data: timestamps.map(timestamp => {
+ const point = seriesData[index].find(item => item.time === timestamp)
+ return point ? point.value : null
+ }),
})),
}
}
@@ -287,7 +344,8 @@
<section class="data-select-panel">
<header class="panel-heading"><div><strong>数据选择</strong><el-icon><ArrowUp /></el-icon></div></header>
<div class="data-select-content">
- <label class="step-select"><span>循环工步</span><el-select v-model="selectedStep"><el-option v-for="item in stepOptions" :key="item" :label="item" :value="item" /></el-select></label>
+ <label class="step-select"><span>循环</span><el-select v-model="selectedCycle" clearable placeholder="跟随工步"><el-option v-for="item in cycleOptions" :key="item" :label="`循环 ${item}`" :value="String(item)" /></el-select></label>
+ <label class="step-select"><span>循环工步</span><el-select v-model="selectedStep" :disabled="!stepOptions.length" placeholder="暂无实际工步"><el-option v-for="item in stepOptions" :key="item.value" :label="item.label" :value="item.value" /></el-select></label>
<div class="battery-select"><span>电池号</span><el-checkbox-group v-model="selectedCellNos"><el-checkbox v-for="cell in cells" :key="cell.no" :value="cell.no">{{ cell.no }}</el-checkbox></el-checkbox-group></div>
</div>
</section>
@@ -305,13 +363,13 @@
</div>
</div>
- <div v-if="selectedCells.length && visibleMetrics.length" class="charts-grid">
+ <div v-if="selectedCells.length && visibleMetrics.length && hasSelectedRecords" class="charts-grid">
<article v-for="metric in visibleMetrics" :key="metric.key" class="chart-card">
<header><strong>{{ metric.title }}</strong></header>
<VChart :key="`${metric.key}-${selectedStep}-${selectedCellNos.join('|')}`" class="metric-chart" :option="chartOption(metric)" :update-options="chartUpdateOptions" autoresize />
</article>
</div>
- <div v-else class="chart-empty">暂无可展示数据</div>
+ <div v-else class="chart-empty">暂无当前测试实时数据</div>
</section>
<el-dialog v-model="templateDialogVisible" :title="templateMode === 'battery' ? '选择电池组字段模板' : '选择特殊字段模板'" width="620px" align-center>
--
Gitblit v1.7.1