From 7835baa4f22d5ff39b95f5cb05aefc9d3ceb67ae Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期六, 05 九月 2026 11:45:35 +0800
Subject: [PATCH] 对齐测试控制台数据与实时状态
---
src/views/ConsoleDataImport.vue | 79 +++++++++++++++------------------------
1 files changed, 31 insertions(+), 48 deletions(-)
diff --git a/src/views/ConsoleDataImport.vue b/src/views/ConsoleDataImport.vue
index a03b625..7d7674e 100644
--- a/src/views/ConsoleDataImport.vue
+++ b/src/views/ConsoleDataImport.vue
@@ -3,9 +3,9 @@
import { ElMessage, ElMessageBox } from 'element-plus'
import { useRoute, useRouter } from 'vue-router'
import * as XLSX from 'xlsx'
-import type { ConsoleDataRow } from '@/stores/consoleTestData'
-import { submitConsoleDataAction, type ConsoleDataType } from '@/api/consoleData'
+import { consoleImportColumns, submitConsoleDataAction, type ConsoleDataType } from '@/api/consoleData'
import { getTestDetail } from '@/api/tests'
+import { formatBusinessTime } from '@/utils/businessTime'
type ImportStage = 'upload' | 'preview' | 'result'
type RawExcelRow = Record<string, unknown>
@@ -14,7 +14,7 @@
sourceRow: number
raw: RawExcelRow
errors: string[]
- data: Omit<ConsoleDataRow, 'id'>
+ data: Record<string, string | number | null>
}
const route = useRoute()
@@ -29,14 +29,14 @@
const stage = ref<ImportStage>('upload')
const dragging = ref(false)
-const requiredHeaders = dataType === 'CYCLE' ? ['记录编号', '电池号', '循环序号', '充电容量(Ah)', '放电容量(Ah)', '充放电效率(%)', '充电能量(Wh)', '放电能量(Wh)'] : dataType === 'STEP' ? ['记录编号', '电池号', '循环序号', '工步序号', '工步', '工步时间', '容量(Ah)', '能量(Wh)', '起始电压(V)', '终止电压(V)', '充电峰值电压(V)', '放电谷值电压(V)'] : ['记录编号', '电池号', '记录时间', '循环序号', '工步序号', '工步名称', '记录序号', '电压(V)', '电流(A)', '容量(Ah)', '能量(Wh)']
-const numericHeaders = requiredHeaders.filter(header => !['记录编号', '电池号', '记录时间', '工步', '工步名称', '工步时间'].includes(header))
+const columns = consoleImportColumns[dataType]
+const requiredHeaders = Object.values(columns)
const validRows = computed(() => validatedRows.value.filter(row => row.errors.length === 0))
const failedRows = computed(() => validatedRows.value.filter(row => row.errors.length > 0))
const knownBatteries = ref<Set<string>>(new Set())
function backToDataList() {
- router.push({ path: `/test-list/${testId}/console`, query: { tab: 'data-list', state: canImport.value ? 'ended' : 'running' } })
+ router.push({ path: `/test-list/${testId}/console`, query: { tab: 'data-list', dataType } })
}
onMounted(async () => {
@@ -53,49 +53,32 @@
return String(row[key] ?? '').trim()
}
-function normalizeImportRow(row: RawExcelRow): RawExcelRow {
- if (dataType === 'REALTIME') return { ...row, '工步时间': row['工步时间'] || '-', '电流(A)': row['电流(A)'], '容量(Ah)': row['容量(Ah)'], '能量(Wh)': row['能量(Wh)'], '温度(℃)': row['温度(℃)'] || '0', '正电位(V)': row['正电位(V)'] || '0', '负电位(V)': row['负电位(V)'] || '0' }
- if (dataType === 'STEP') return { ...row, '记录时间': row['工步时间'], '电流(A)': '0', '容量(Ah)': row['容量(Ah)'], '能量(Wh)': row['能量(Wh)'], '温度(℃)': '0', '正电位(V)': '0', '负电位(V)': '0' }
- return { ...row, '记录时间': '-', '工步时间': '-', '工步名称': '循环统计', '工步序号': '1', '电压(V)': '0', '电流(A)': '0', '容量(Ah)': row['充电容量(Ah)'], '能量(Wh)': row['充电能量(Wh)'], '温度(℃)': '0', '正电位(V)': '0', '负电位(V)': '0' }
-}
-
function validateRow(raw: RawExcelRow, sourceRow: number): ValidatedRow {
const errors: string[] = []
- requiredHeaders.forEach(header => {
- if (!textValue(raw, header)) errors.push(`${header}不能为空`)
- })
- numericHeaders.forEach(header => {
+ const data: ValidatedRow['data'] = { sourceType: dataType === 'REALTIME' ? 'SNAPSHOT' : 'CABINET' }
+ Object.entries(columns).forEach(([field, header]) => {
const value = textValue(raw, header)
- if (value && !Number.isFinite(Number(value))) errors.push(`${header}必须是数字`)
+ if (['recordNo', 'batteryNo', 'stepName'].includes(field)) {
+ data[field] = value
+ if (!value) errors.push(`${header}不能为空`)
+ } else if (field === 'recordedAt') {
+ // Excel 文本按浏览器本地时间解释,提交边界只发送毫秒数。
+ const parsed = /^\d{13}$/.test(value) ? Number(value) : new Date(value).getTime()
+ if (!value || !Number.isFinite(parsed)) errors.push('设备时间无效')
+ data.recordedAt = Number.isFinite(parsed) ? parsed : null
+ data.deviceTime = data.recordedAt
+ data.absoluteTime = data.recordedAt
+ } else if (['cycleNo', 'stepNo', 'sequenceId'].includes(field)) {
+ data[field] = Number(value)
+ if (!value || !Number.isInteger(data[field]) || Number(data[field]) < 1) errors.push(`${header}必须是正整数`)
+ } else {
+ data[field] = !value || value === '-' ? null : Number(value)
+ if (data[field] !== null && !Number.isFinite(data[field])) errors.push(`${header}必须是数字`)
+ }
})
const batteryNo = textValue(raw, '电池号')
if (batteryNo && knownBatteries.value.size && !knownBatteries.value.has(batteryNo)) errors.push('电池号不属于当前测试')
- const cycleNo = Number(textValue(raw, '循环序号'))
- const stepNo = Number(textValue(raw, '工步序号'))
- if (!Number.isInteger(cycleNo) || cycleNo < 1) errors.push('循环序号必须是正整数')
- if (!Number.isInteger(stepNo) || stepNo < 1) errors.push('工步序号必须是正整数')
-
- return {
- sourceRow,
- raw,
- errors: [...new Set(errors)],
- data: {
- recordNo: textValue(raw, '记录编号'),
- batteryNo,
- recordedAt: textValue(raw, '记录时间'),
- stepDuration: textValue(raw, '工步时间'),
- cycleNo: cycleNo || 1,
- stepNo: stepNo || 1,
- stepName: textValue(raw, dataType === 'STEP' ? '工步' : '工步名称'),
- voltage: textValue(raw, '电压(V)'),
- current: textValue(raw, '电流(A)'),
- capacity: textValue(raw, '容量(Ah)'),
- energy: textValue(raw, '能量(Wh)'),
- temperature: textValue(raw, '温度(℃)'),
- positivePotential: textValue(raw, '正电位(V)'),
- negativePotential: textValue(raw, '负电位(V)'),
- },
- }
+ return { sourceRow, raw, errors: [...new Set(errors)], data }
}
async function processFile(file: File) {
@@ -128,9 +111,8 @@
selectedFile.value = file
const seen = new Set<string>()
validatedRows.value = rawRows.map((row, index) => {
- const normalized = normalizeImportRow(row)
- const validated = validateRow(normalized, index + 2)
- const identity = validated.data.recordNo
+ const validated = validateRow(row, index + 2)
+ const identity = String(validated.data.recordNo || '')
if (identity && seen.has(identity)) validated.errors.push('本批次存在重复稳定身份')
if (identity) seen.add(identity)
return validated
@@ -161,6 +143,7 @@
}
async function confirmImport() {
+ if (!validRows.value.length) { ElMessage.warning('没有可导入的有效数据'); return }
if (!canImport.value) {
ElMessage.warning('测试完成后才可以导入数据')
return
@@ -169,7 +152,7 @@
try { const result = await ElMessageBox.prompt('请输入本次导入原因', '提交导入', { inputPlaceholder: '必填,将写入数据修订记录', inputValidator: value => value.trim() ? true : '导入原因不能为空' }); reason = result.value.trim() } catch { return }
const imported = validRows.value.map(item => ({ ...item.data }))
try {
- await submitConsoleDataAction(testId, { dataType, operationType: 'IMPORT', scopeSummary: `导入${validRows.value.length}条${dataType}数据`, identitySummary: JSON.stringify(imported.map(row => row.recordNo)), changesSummary: JSON.stringify(imported), reason, overrides: imported.map(row => ({ stableIdentity: row.recordNo, fieldValues: JSON.stringify(row), deleted: false })) })
+ await submitConsoleDataAction(testId, { dataType, operationType: 'IMPORT', scopeSummary: `导入${validRows.value.length}条${dataType}数据`, identitySummary: JSON.stringify(imported.map(row => row.recordNo)), changesSummary: JSON.stringify(imported), reason, overrides: imported.map(row => ({ stableIdentity: String(row.recordNo), fieldValues: JSON.stringify(row), deleted: false })) })
} catch (error) { ElMessage.error(error instanceof Error ? error.message : '导入提交失败'); return }
stage.value = 'result'
}
@@ -206,7 +189,7 @@
<div v-else-if="stage === 'preview'" class="preview-stage">
<div class="file-summary"><div><el-icon><Document /></el-icon><span><strong>{{ selectedFile?.name }}</strong><small>{{ ((selectedFile?.size || 0) / 1024).toFixed(1) }} KB · 已完成字段校验</small></span></div><button type="button" @click="resetUpload">重新选择</button></div>
<div class="validation-summary"><div><span>总数据</span><strong>{{ validatedRows.length }}</strong></div><div class="success"><span>校验通过</span><strong>{{ validRows.length }}</strong></div><div class="failed"><span>校验失败</span><strong>{{ failedRows.length }}</strong></div></div>
- <div class="preview-table-wrap"><table><thead><tr><th>Excel行号</th><th>电池号</th><th>记录时间</th><th>循环</th><th>工步</th><th>工步名称</th><th>电压(V)</th><th>容量(Ah)</th><th>校验结果</th></tr></thead><tbody><tr v-for="item in validatedRows.slice(0, 12)" :key="item.sourceRow"><td>{{ item.sourceRow }}</td><td>{{ item.data.batteryNo }}</td><td>{{ item.data.recordedAt }}</td><td>{{ item.data.cycleNo }}</td><td>{{ item.data.stepNo }}</td><td>{{ item.data.stepName }}</td><td>{{ item.data.voltage }}</td><td>{{ item.data.capacity }}</td><td><span class="validation-state" :class="item.errors.length ? 'failed' : 'success'">{{ item.errors.length ? item.errors.join(';') : '通过' }}</span></td></tr></tbody></table></div>
+ <div class="preview-table-wrap"><table><thead><tr><th>Excel行号</th><th>电池号</th><th>记录时间</th><th>循环</th><th>工步</th><th>工步名称</th><th>电压(V)</th><th>容量(Ah)</th><th>校验结果</th></tr></thead><tbody><tr v-for="item in validatedRows.slice(0, 12)" :key="item.sourceRow"><td>{{ item.sourceRow }}</td><td>{{ item.data.batteryNo }}</td><td>{{ formatBusinessTime(item.data.recordedAt) }}</td><td>{{ item.data.cycleNo }}</td><td>{{ item.data.stepNo }}</td><td>{{ item.data.stepName }}</td><td>{{ item.data.voltage }}</td><td>{{ item.data.capacity }}</td><td><span class="validation-state" :class="item.errors.length ? 'failed' : 'success'">{{ item.errors.length ? item.errors.join(';') : '通过' }}</span></td></tr></tbody></table></div>
<p v-if="validatedRows.length > 12" class="preview-tip">当前预览前 12 条,共 {{ validatedRows.length }} 条数据</p>
<div class="stage-actions"><button class="primary-button" type="button" :disabled="!canImport" @click="confirmImport"><el-icon><Check /></el-icon>确认导入</button><button class="secondary-button" type="button" @click="resetUpload">返回上传</button></div>
</div>
--
Gitblit v1.7.1