From b2f8041203c8eaf9bbba0a0cb439cae4519d29c4 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期四, 03 九月 2026 11:46:00 +0800
Subject: [PATCH] 实现工步图表当前有效数据与测试详情保存
---
src/components/ConsoleChartPanel.vue | 108 +++++++++++++++++++++++++++++-------------------------
1 files changed, 58 insertions(+), 50 deletions(-)
diff --git a/src/components/ConsoleChartPanel.vue b/src/components/ConsoleChartPanel.vue
index db48b69..430c402 100644
--- a/src/components/ConsoleChartPanel.vue
+++ b/src/components/ConsoleChartPanel.vue
@@ -12,7 +12,7 @@
} from '@/utils/compareReportPrint'
import type { CompareMetricKey } from '@/stores/compareReports'
import { useBatteryGroupFields } from '@/stores/batteryGroupFields'
-import type { PersistedTestRecord, TestLogicStepMapping } from '@/api/tests'
+import { saveTestConsoleDetail, type PersistedTestRecord, type TestConsoleDetail, type TestLogicStepMapping } from '@/api/tests'
interface ConsoleCell {
no: string
@@ -25,18 +25,21 @@
}
const props = withDefaults(defineProps<{
+ testId: number
cells: ConsoleCell[]
durationMinutes?: number
testName?: string
batteryGroup?: string
records: PersistedTestRecord[]
logicStepMappings?: TestLogicStepMapping[]
+ consoleDetail?: TestConsoleDetail | null
}>(), {
durationMinutes: 5,
testName: '',
batteryGroup: '',
records: () => [],
logicStepMappings: () => [],
+ consoleDetail: null,
})
use([CanvasRenderer, LineChart, GridComponent, LegendComponent, TooltipComponent])
@@ -50,6 +53,7 @@
const batteryFields = ref(['额定容量', '电流', '内阻'])
const specialFields = ref(['温升'])
const detailFieldValues = reactive<Record<string, Record<string, string>>>({})
+const detailSaving = ref(false)
const templateDialogVisible = ref(false)
const templateMode = ref<'battery' | 'special'>('battery')
const selectedBatteryFields = ref<string[]>([])
@@ -86,41 +90,27 @@
}
const cycleOptions = computed(() => [...new Set(props.records
+ .filter(record => record.sourceType === 'SNAPSHOT')
.map(record => Number(record.cycleNo))
.filter(value => Number.isFinite(value) && value > 0))].sort((a, b) => a - b))
const stepOptions = computed<StepOption[]>(() => {
+ const cycleNo = Number(selectedCycle.value)
+ if (!Number.isFinite(cycleNo) || cycleNo <= 0) return []
const actual = new Map<string, StepOption>()
- const actualAcrossCycles = new Map<number, StepOption>()
- for (const record of props.records) {
- const cycleNo = Number(record.cycleNo)
+ for (const record of props.records.filter(item => item.sourceType === 'SNAPSHOT')) {
const stepNo = Number(record.stepNo)
- if (!Number.isFinite(cycleNo) || !Number.isFinite(stepNo) || cycleNo <= 0 || stepNo <= 0) continue
+ if (Number(record.cycleNo) !== cycleNo || !Number.isFinite(stepNo) || stepNo <= 0) continue
const value = `actual:${cycleNo}:${stepNo}`
if (!actual.has(value)) actual.set(value, { value, label: `${cycleNo}-${stepNo} ${record.stepName || '-'}`, cycleNo, stepNo })
- if (!actualAcrossCycles.has(stepNo)) actualAcrossCycles.set(stepNo, {
- value: `actual:all:${stepNo}`,
- label: `全部循环-${stepNo} ${record.stepName || '-'}`,
- cycleNo: 0,
- stepNo,
- })
}
const logical: StepOption[] = []
- const logicalAcrossCycles: 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 })
- }
- }
for (const mapping of props.logicStepMappings || []) {
- logicalAcrossCycles.push({
- value: `logical:all:${mapping.logicalNo}`,
- label: `全部循环-逻辑${mapping.logicalNo} ${mapping.logicalName}`,
- cycleNo: 0,
- logical: mapping,
- })
+ const hit = props.records.some(record => record.sourceType === 'SNAPSHOT' && Number(record.cycleNo) === cycleNo
+ && Number(record.stepNo) >= mapping.startStepNo && Number(record.stepNo) <= mapping.endStepNo)
+ if (hit) logical.push({ value: `logical:${cycleNo}:${mapping.logicalNo}`, label: `${cycleNo}-逻辑${mapping.logicalNo} ${mapping.logicalName}`, cycleNo, logical: mapping })
}
- return [...actualAcrossCycles.values(), ...actual.values(), ...logicalAcrossCycles, ...logical]
+ return [...actual.values(), ...logical]
})
function ensureDetailFieldValues() {
@@ -137,6 +127,8 @@
watch(() => props.testName, value => {
if (value) experimentName.value = value
})
+
+watch(() => props.consoleDetail, value => applyConsoleDetail(value), { immediate: true, deep: true })
watch(() => props.cells, cells => {
const validCellNos = new Set(cells.map(cell => cell.no))
@@ -158,8 +150,9 @@
watch(cycleOptions, options => {
const current = Number(selectedCycle.value)
- if (selectedCycle.value && !options.includes(current)) selectedCycle.value = ''
-})
+ if (!options.length) selectedCycle.value = ''
+ else if (!options.includes(current)) selectedCycle.value = String(options[0])
+}, { immediate: true })
async function addCustomField() {
try {
@@ -186,6 +179,23 @@
delete detailFieldValues[field]
}
+async function saveConsoleDetail() {
+ detailSaving.value = true
+ try {
+ const detail: TestConsoleDetail = {
+ experimentName: experimentName.value.trim() || null,
+ batteryFields: [...batteryFields.value],
+ specialFields: [...specialFields.value],
+ fieldValues: JSON.parse(JSON.stringify(detailFieldValues)) as Record<string, Record<string, string>>,
+ }
+ const saved = await saveTestConsoleDetail(props.testId, detail)
+ applyConsoleDetail(saved.consoleDetail)
+ ElMessage.success('测试详情已保存')
+ } catch (error) {
+ ElMessage.error(error instanceof Error ? error.message : '测试详情保存失败')
+ } finally { detailSaving.value = false }
+}
+
function openFieldTemplate(mode: 'battery' | 'special') {
templateMode.value = mode
selectedBatteryFields.value = [...batteryFields.value]
@@ -204,26 +214,12 @@
ElMessage.success('字段模板已应用')
}
-async function saveSpecialTemplate() {
- if (!specialFields.value.length) {
- ElMessage.warning('请先添加特殊字段')
- return
- }
- try {
- const result = await ElMessageBox.prompt('请输入特殊字段模板名称', '另存特殊字段模板', {
- confirmButtonText: '保存',
- cancelButtonText: '取消',
- inputPlaceholder: '输入模板名称',
- inputValidator: value => value.trim() ? true : '请输入模板名称',
- })
- ElMessage.success(`已另存为“${result.value.trim()}”`)
- } catch {
- // 用户取消另存。
- }
-}
-
function updateSeriesColor(index: number, color: string | null) {
if (color) seriesColors.value[index] = color
+}
+
+function ensureSelectedCell() {
+ if (!selectedCellNos.value.length && props.cells.length) selectedCellNos.value = [props.cells[0].no]
}
function metricMatches(metric: CompareMetricKey, record: PersistedTestRecord) {
@@ -239,6 +235,16 @@
return false
}
+function applyConsoleDetail(detail: TestConsoleDetail | null | undefined) {
+ if (!detail) return
+ experimentName.value = detail.experimentName || ''
+ if (Array.isArray(detail.batteryFields)) batteryFields.value = [...detail.batteryFields]
+ if (Array.isArray(detail.specialFields)) specialFields.value = [...detail.specialFields]
+ Object.keys(detailFieldValues).forEach(key => delete detailFieldValues[key])
+ Object.entries(detail.fieldValues || {}).forEach(([field, values]) => { detailFieldValues[field] = { ...values } })
+ ensureDetailFieldValues()
+}
+
function metricValue(metric: CompareMetricKey, record: PersistedTestRecord) {
if (metric === 'voltage') return Number(record.voltage)
if (metric === 'current') return Number(record.current)
@@ -249,9 +255,11 @@
function selectedRecords() {
const option = stepOptions.value.find(item => item.value === selectedStep.value)
if (!option) return []
- const cycle = selectedCycle.value ? Number(selectedCycle.value) : option.cycleNo > 0 ? option.cycleNo : null
+ const cycle = Number(selectedCycle.value)
+ if (!Number.isFinite(cycle) || cycle <= 0) return []
return props.records.filter(record => {
- if (cycle !== null && Number(record.cycleNo) !== cycle) return false
+ if (record.sourceType !== 'SNAPSHOT') return false
+ 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
@@ -357,15 +365,15 @@
</tbody>
</table>
</div>
- <div class="detail-actions"><button class="primary-button" type="button" @click="addCustomField"><el-icon><Plus /></el-icon>添加项</button><button class="secondary-button" type="button" @click="openFieldTemplate('battery')">电池组字段模板</button><button class="secondary-button" type="button" @click="openFieldTemplate('special')">选择特殊字段模板</button><button class="secondary-button" type="button" @click="saveSpecialTemplate">另存特殊字段模板</button></div>
+ <div class="detail-actions"><button class="primary-button" type="button" :disabled="detailSaving" @click="saveConsoleDetail"><el-icon><Check /></el-icon>保存详情</button><button class="secondary-button" type="button" @click="addCustomField"><el-icon><Plus /></el-icon>添加项</button><button class="secondary-button" type="button" @click="openFieldTemplate('battery')">电池组字段模板</button><button class="secondary-button" type="button" @click="openFieldTemplate('special')">选择特殊字段模板</button></div>
</section>
<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="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>
+ <label class="step-select"><span>循环</span><el-select v-model="selectedCycle" :disabled="!cycleOptions.length" 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" @change="ensureSelectedCell"><el-checkbox v-for="cell in cells" :key="cell.no" :value="cell.no">{{ cell.no }}</el-checkbox></el-checkbox-group></div>
</div>
</section>
--
Gitblit v1.7.1