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