From 7835baa4f22d5ff39b95f5cb05aefc9d3ceb67ae Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期六, 05 九月 2026 11:45:35 +0800
Subject: [PATCH] 对齐测试控制台数据与实时状态

---
 src/components/ConsoleDataListPanel.vue |   49 ++++++++++++++++++-------------------------------
 1 files changed, 18 insertions(+), 31 deletions(-)

diff --git a/src/components/ConsoleDataListPanel.vue b/src/components/ConsoleDataListPanel.vue
index fb23b3e..2e1d887 100644
--- a/src/components/ConsoleDataListPanel.vue
+++ b/src/components/ConsoleDataListPanel.vue
@@ -5,7 +5,8 @@
 import * as XLSX from 'xlsx'
 import { Download, EditPen, Refresh, Search, UploadFilled, Clock, Check } from '@element-plus/icons-vue'
 import { getPersistedTestCycles, getPersistedTestRecords, getPersistedTestSteps, type PersistedTestCycle, type PersistedTestRecord, type PersistedTestStep } from '@/api/tests'
-import { submitConsoleDataAction, type ConsoleDataType } from '@/api/consoleData'
+import { consoleImportColumns, submitConsoleDataAction, type ConsoleDataType } from '@/api/consoleData'
+import { formatBusinessTime as formatTime } from '@/utils/businessTime'
 
 type DataTab = 'realtime' | 'step' | 'cycle'
 interface ConsoleCell {
@@ -48,6 +49,8 @@
   positivePotential: string
   negativePotential: string
   extensionMeasurements: string
+  sourceIdentity: string
+  collectorDeviceTime: string
   chargeCapacity: string
   dischargeCapacity: string
   efficiency: string
@@ -109,19 +112,11 @@
           { label: '放电能量(Wh)', value: 'dischargeEnergy' },
         ],
 )
-function formatTime(value?: string | null) {
-  return value
-    ? value
-        .replace('T', ' ')
-        .replace('Z', '')
-        .replace(/\.\d{3,6}$/, '')
-    : '-'
-}
 function emptyRow(base: Partial<DataRow>): DataRow {
-  return { id: '', sourceType: 'CABINET', metricCode: '-', metricValue: '-', unit: '-', collectorCode: '-', channelCode: '-', recordNo: '', batteryNo: '', cycleNo: 0, stepNo: 0, stepName: '', actualStepLabel: '-', sequenceId: 0, recordedAt: '', absoluteTime: '', serverReceivedAt: '', stepDuration: '-', startVoltage: '-', endVoltage: '-', chargePeakVoltage: '-', dischargeValleyVoltage: '-', voltage: '-', current: '-', capacity: '-', energy: '-', temperature: '-', positivePotential: '-', negativePotential: '-', extensionMeasurements: '-', chargeCapacity: '-', dischargeCapacity: '-', efficiency: '-', chargeEnergy: '-', dischargeEnergy: '-', ...base }
+  return { sourceIdentity: '-', collectorDeviceTime: '-', id: '', sourceType: 'CABINET', metricCode: '-', metricValue: '-', unit: '-', collectorCode: '-', channelCode: '-', recordNo: '', batteryNo: '', cycleNo: 0, stepNo: 0, stepName: '', actualStepLabel: '-', sequenceId: 0, recordedAt: '', absoluteTime: '', serverReceivedAt: '', stepDuration: '-', startVoltage: '-', endVoltage: '-', chargePeakVoltage: '-', dischargeValleyVoltage: '-', voltage: '-', current: '-', capacity: '-', energy: '-', temperature: '-', positivePotential: '-', negativePotential: '-', extensionMeasurements: '-', chargeCapacity: '-', dischargeCapacity: '-', efficiency: '-', chargeEnergy: '-', dischargeEnergy: '-', ...base }
 }
 function mapRecord(record: PersistedTestRecord): DataRow {
-  const deviceTime = formatTime(record.deviceTime || record.recordedAt)
+  const deviceTime = formatTime(record.deviceTime ?? record.recordedAt)
   const identity = record.stableIdentity || [record.adapterId, record.devtype, record.deviceId, record.subdevid, record.channelId, record.sequenceId].join(':')
   const collector = record.sourceType === 'COLLECTOR'
   const snapshot = record.sourceType === 'SNAPSHOT'
@@ -132,10 +127,10 @@
   if (snapshot && record.extensionMeasurementsJson) {
     try {
       const parsed = typeof record.extensionMeasurementsJson === 'string' ? JSON.parse(record.extensionMeasurementsJson) : record.extensionMeasurementsJson
-      extensionMeasurements = Object.entries(parsed as Record<string, any>).map(([code, value]) => `${code}=${value?.value ?? value?.metricValue ?? '-'}${value?.unit ? ` ${value.unit}` : ''}`).join('; ') || '-'
+      extensionMeasurements = Object.entries(parsed as Record<string, { value?: unknown; metricValue?: unknown; unit?: string; channelCode?: string }>).map(([code, value]) => `${code}${value?.channelCode ? ` (${value.channelCode})` : ''}=${value?.value ?? value?.metricValue ?? '-'}${value?.unit ? ` ${value.unit}` : ''}`).join('; ') || '-'
     } catch { extensionMeasurements = String(record.extensionMeasurementsJson) }
   }
-  return emptyRow({ id: identity, sourceType: record.sourceType || 'CABINET', metricCode: record.metricCode || '-', metricValue: extensionValue, unit: record.unit || '-', collectorCode: record.collectorCode || '-', channelCode: record.channelCode || '-', recordNo: record.recordNo, batteryNo: record.batteryNo || '-', cycleNo: Number(record.cycleNo || 0), stepNo: Number(record.stepNo || 0), stepName: collector ? '-' : (record.stepName || '-'), sequenceId: Number(record.sequenceId || record.cabinetSequenceId || 0), recordedAt: deviceTime, absoluteTime: deviceTime, serverReceivedAt: formatTime(record.serverReceivedAt), voltage: collector ? '-' : (record.voltage == null ? '-' : Number(record.voltage).toFixed(4)), current: collector ? '-' : (record.current == null ? '-' : Number(record.current).toFixed(4)), capacity: collector ? '-' : (record.capacity == null ? '-' : Number(record.capacity).toFixed(4)), energy: collector ? '-' : (record.energy == null ? '-' : Number(record.energy).toFixed(4)), temperature: snapshot ? (record.temperature == null ? '-' : Number(record.temperature).toFixed(4)) : extension('TEMPERATURE'), positivePotential: snapshot ? (record.positivePotential == null ? '-' : Number(record.positivePotential).toFixed(4)) : extension('POSITIVE_POTENTIAL'), negativePotential: snapshot ? (record.negativePotential == null ? '-' : Number(record.negativePotential).toFixed(4)) : extension('NEGATIVE_POTENTIAL'), extensionMeasurements })
+  return emptyRow({ sourceIdentity: record.sourceIdentity || '-', collectorDeviceTime: formatTime(record.collectorDeviceTime), id: identity, sourceType: record.sourceType || 'CABINET', metricCode: record.metricCode || '-', metricValue: extensionValue, unit: record.unit || '-', collectorCode: record.collectorCode || String(record.collectorId ?? '-'), channelCode: record.channelCode || '-', recordNo: record.recordNo, batteryNo: record.batteryNo || '-', cycleNo: Number(record.cycleNo || 0), stepNo: Number(record.stepNo || 0), stepName: collector ? '-' : (record.stepName || '-'), sequenceId: Number(record.sequenceId || record.cabinetSequenceId || 0), recordedAt: deviceTime, absoluteTime: deviceTime, serverReceivedAt: formatTime(record.serverReceivedAt), voltage: collector ? '-' : (record.voltage == null ? '-' : Number(record.voltage).toFixed(4)), current: collector ? '-' : (record.current == null ? '-' : Number(record.current).toFixed(4)), capacity: collector ? '-' : (record.capacity == null ? '-' : Number(record.capacity).toFixed(4)), energy: collector ? '-' : (record.energy == null ? '-' : Number(record.energy).toFixed(4)), temperature: snapshot ? (record.temperature == null ? '-' : Number(record.temperature).toFixed(4)) : extension('TEMPERATURE'), positivePotential: snapshot ? (record.positivePotential == null ? '-' : Number(record.positivePotential).toFixed(4)) : extension('POSITIVE_POTENTIAL'), negativePotential: snapshot ? (record.negativePotential == null ? '-' : Number(record.negativePotential).toFixed(4)) : extension('NEGATIVE_POTENTIAL'), extensionMeasurements })
 }
 function mapStep(record: PersistedTestStep): DataRow {
   const startedAt = formatTime(record.recordedAt)
@@ -151,22 +146,19 @@
   loading.value = true
   loadError.value = ''
   try {
-    if (props.receptionStatus === 'NOT_STARTED') {
-      rows.value = []
-      stepRows.value = []
-      cycleRows.value = []
-      return
-    }
     const [records, steps, cycles] = await Promise.all([getPersistedTestRecords(props.testId), getPersistedTestSteps(props.testId), getPersistedTestCycles(props.testId)])
     // 后端查询接口已经返回当前有效数据,前端不再读取覆盖表或拼接伪造行。
     rows.value = records.map(mapRecord).sort((a, b) => b.absoluteTime.localeCompare(a.absoluteTime) || b.sequenceId - a.sequenceId)
     stepRows.value = steps.map(mapStep)
     cycleRows.value = cycles.map(mapCycle)
+    currentPage.value = Math.min(currentPage.value, Math.max(1, Math.ceil(filteredRows.value.length / pageSize.value)))
+    return true
   } catch (error) {
     rows.value = []
     stepRows.value = []
     cycleRows.value = []
     loadError.value = error instanceof Error ? error.message : '测试数据加载失败'
+    return false
   } finally {
     loading.value = false
   }
@@ -221,14 +213,8 @@
 function goToRevisions() {
   router.push({ path: '/data-revision', query: { testId: String(props.testId), dataType: dataType.value } })
 }
-function templateHeaders() {
-  return activeTab.value === 'cycle' ? ['记录编号', '电池号', '循环序号', '充放电容量(Ah)', '效率(%)', '能量(Wh)'] : activeTab.value === 'step' ? ['记录编号', '电池号', '循环序号', '工步序号', '工步', '工步时间', '容量(Ah)', '能量(Wh)', '起止电压(V)', '充电峰值电压(V)', '放电谷值电压(V)'] : ['记录编号', '电池号', '设备时间', '循环序号', '工步序号', '工步名称', '记录序号', '服务器接收时间', '电压(V)', '电流(A)', '容量(Ah)', '能量(Wh)']
-}
-function realtimeTemplateHeaders() {
-  return [...templateHeaders(), '温度(℃)', '正电位(V)', '负电位(V)']
-}
 function downloadTemplate() {
-  const worksheet = XLSX.utils.aoa_to_sheet([activeTab.value === 'realtime' ? realtimeTemplateHeaders() : templateHeaders()])
+  const worksheet = XLSX.utils.aoa_to_sheet([Object.values(consoleImportColumns[dataType.value])])
   const workbook = XLSX.utils.book_new()
   XLSX.utils.book_append_sheet(workbook, worksheet, `${activeTab.value}模板`)
   XLSX.writeFile(workbook, `测试${activeTab.value}数据导入模板.xlsx`)
@@ -329,9 +315,7 @@
   }
   try {
     await submitConsoleDataAction(props.testId, { dataType: dataType.value, operationType: 'DELETE', scopeSummary: `物理删除${ids.length}条${activeTab.value}数据`, identitySummary: JSON.stringify(physicalIds), changesSummary: null, reason: result.value.trim() })
-    if (activeTab.value === 'realtime') rows.value = rows.value.filter(row => !ids.includes(row.id))
-    else if (activeTab.value === 'step') stepRows.value = stepRows.value.filter(row => !physicalIds.includes(row.id))
-    else cycleRows.value = cycleRows.value.filter(row => !ids.includes(row.id))
+    await loadRecords()
     selectedIds.value = new Set()
     ElMessage.success(`已从当前有效视图移除 ${ids.length} 条数据`)
   } catch (error) {
@@ -342,6 +326,8 @@
   if (activeTab.value === tab) return
   if (editing.value) cancelBatchEdit()
   activeTab.value = tab
+  selectedIds.value = new Set()
+  void router.replace({ query: { ...route.query, tab: 'data-list', dataType: dataType.value } })
   currentPage.value = 1
   // 切换数据子页时主动同步一次,除此之外不进行后台轮询。
   void loadRecords()
@@ -349,7 +335,8 @@
 function escapeCsv(value: unknown) {
   return `"${String(value ?? '').replaceAll('"', '""')}"`
 }
-function downloadCsv() {
+async function downloadCsv() {
+  if (!await loadRecords()) return
   if (!filteredRows.value.length) {
     ElMessage.warning('没有可下载的数据')
     return
@@ -500,7 +487,7 @@
               <td>{{ row.sequenceId }}</td>
               <td>{{ row.recordedAt }}</td>
               <td>{{ row.serverReceivedAt }}</td>
-              <td>{{ row.sourceType === 'COLLECTOR' ? `采集器 ${row.collectorCode}` : row.sourceType === 'SNAPSHOT' ? '检测柜 + 采集器' : '检测柜' }}<small v-if="row.sourceType === 'COLLECTOR'">{{ row.channelCode }}</small></td>
+              <td :title="row.sourceIdentity">{{ row.sourceType === 'COLLECTOR' ? `采集器 ${row.collectorCode}` : row.sourceType === 'SNAPSHOT' ? '检测柜 + 采集器' : '检测柜' }}<small>{{ row.sourceIdentity }}</small><small v-if="row.sourceType === 'SNAPSHOT'">采集器 {{ row.collectorCode }} · {{ row.collectorDeviceTime }}</small><small v-if="row.sourceType === 'COLLECTOR'">{{ row.channelCode }}</small><small v-if="row.extensionMeasurements !== '-'">{{ row.extensionMeasurements }}</small></td>
               <td><el-input v-if="editing && draftRows[row.id]" v-model="draftRows[row.id].temperature" size="small" /><span v-else>{{ row.temperature }}</span></td>
               <td><el-input v-if="editing && draftRows[row.id]" v-model="draftRows[row.id].positivePotential" size="small" /><span v-else>{{ row.positivePotential }}</span></td>
               <td><el-input v-if="editing && draftRows[row.id]" v-model="draftRows[row.id].negativePotential" size="small" /><span v-else>{{ row.negativePotential }}</span></td>

--
Gitblit v1.7.1