From 1858334fbc347e284a55b29693a0695ec19694bf Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期三, 02 九月 2026 22:08:34 +0800
Subject: [PATCH] 展示完整实时快照字段

---
 src/api/tests.ts                                |    7 +++
 src/components/ConsoleChartPanel.vue            |    3 +
 tests/test-console-step-chart.contract.test.mjs |    5 ++
 src/components/ConsoleDataListPanel.vue         |   49 ++++++++++++++++++------
 4 files changed, 50 insertions(+), 14 deletions(-)

diff --git a/src/api/tests.ts b/src/api/tests.ts
index 0deaabf..f568da0 100644
--- a/src/api/tests.ts
+++ b/src/api/tests.ts
@@ -326,7 +326,7 @@
   id: number | string
   stableIdentity?: string
   sampleId?: number
-  sourceType?: 'CABINET' | 'COLLECTOR' | string
+  sourceType?: 'CABINET' | 'COLLECTOR' | 'SNAPSHOT' | string
   metricCode?: string
   metricValue?: number | string | null
   unit?: string | null
@@ -340,6 +340,7 @@
   stepName: string
   stepType?: string
   sequenceId: number
+  cabinetSequenceId?: number | null
   recordedAt: string
   absoluteTime: string
   deviceTime?: string
@@ -348,6 +349,10 @@
   current: number | string | null
   capacity: number | string | null
   energy: number | string | null
+  temperature?: number | string | null
+  positivePotential?: number | string | null
+  negativePotential?: number | string | null
+  extensionMeasurementsJson?: string | Record<string, unknown> | null
   deviceId: number
   channelId: number
   adapterId?: number
diff --git a/src/components/ConsoleChartPanel.vue b/src/components/ConsoleChartPanel.vue
index cc3d2a0..db48b69 100644
--- a/src/components/ConsoleChartPanel.vue
+++ b/src/components/ConsoleChartPanel.vue
@@ -227,8 +227,10 @@
 }
 
 function metricMatches(metric: CompareMetricKey, record: PersistedTestRecord) {
+  const snapshot = record.sourceType === 'SNAPSHOT'
   if (metric === 'voltage') return record.sourceType !== 'COLLECTOR' && record.voltage != null
   if (metric === 'current') return record.sourceType !== 'COLLECTOR' && record.current != null
+  if (snapshot) return ({ temperature: record.temperature, positiveVoltage: record.positivePotential, negativeVoltage: record.negativePotential }[metric] != null)
   if (record.sourceType !== 'COLLECTOR') return false
   const code = String(record.metricCode || '').toUpperCase()
   if (metric === 'temperature') return code === 'TEMPERATURE' || code.includes('TEMPERATURE')
@@ -240,6 +242,7 @@
 function metricValue(metric: CompareMetricKey, record: PersistedTestRecord) {
   if (metric === 'voltage') return Number(record.voltage)
   if (metric === 'current') return Number(record.current)
+  if (record.sourceType === 'SNAPSHOT') return Number(({ temperature: record.temperature, positiveVoltage: record.positivePotential, negativeVoltage: record.negativePotential }[metric]))
   return Number(record.metricValue)
 }
 
diff --git a/src/components/ConsoleDataListPanel.vue b/src/components/ConsoleDataListPanel.vue
index aff4b9f..25a42fa 100644
--- a/src/components/ConsoleDataListPanel.vue
+++ b/src/components/ConsoleDataListPanel.vue
@@ -44,6 +44,10 @@
   current: string
   capacity: string
   energy: string
+  temperature: string
+  positivePotential: string
+  negativePotential: string
+  extensionMeasurements: string
   chargeCapacity: string
   dischargeCapacity: string
   efficiency: string
@@ -83,6 +87,9 @@
           { label: '电流(A)', value: 'current' },
           { label: '容量(Ah)', value: 'capacity' },
           { label: '能量(Wh)', value: 'energy' },
+          { label: '温度(℃)', value: 'temperature' },
+          { label: '正电位(V)', value: 'positivePotential' },
+          { label: '负电位(V)', value: 'negativePotential' },
         ]
     : activeTab.value === 'step'
       ? [
@@ -111,13 +118,24 @@
     : '-'
 }
 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: '-', chargeCapacity: '-', dischargeCapacity: '-', efficiency: '-', chargeEnergy: '-', dischargeEnergy: '-', ...base }
+  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 }
 }
 function mapRecord(record: PersistedTestRecord): DataRow {
   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'
-  return emptyRow({ id: identity, sourceType: record.sourceType || 'CABINET', metricCode: record.metricCode || '-', metricValue: record.metricValue == null ? '-' : String(record.metricValue), 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), 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)) })
+  const snapshot = record.sourceType === 'SNAPSHOT'
+  const metricCode = String(record.metricCode || '').toUpperCase()
+  const extensionValue = record.metricValue == null ? '-' : String(record.metricValue)
+  const extension = (name: string) => collector && metricCode.includes(name) ? extensionValue : '-'
+  let extensionMeasurements = '-'
+  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('; ') || '-'
+    } 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 })
 }
 function mapStep(record: PersistedTestStep): DataRow {
   const startedAt = formatTime(record.recordedAt)
@@ -206,8 +224,11 @@
 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([templateHeaders()])
+  const worksheet = XLSX.utils.aoa_to_sheet([activeTab.value === 'realtime' ? realtimeTemplateHeaders() : templateHeaders()])
   const workbook = XLSX.utils.book_new()
   XLSX.utils.book_append_sheet(workbook, worksheet, `${activeTab.value}模板`)
   XLSX.writeFile(workbook, `测试${activeTab.value}数据导入模板.xlsx`)
@@ -326,8 +347,8 @@
     ElMessage.warning('没有可下载的数据')
     return
   }
-  const headers = activeTab.value === 'cycle' ? ['记录编号', '电池', '循环号', '充电容量(Ah)', '放电容量(Ah)', '充放电效率(%)', '充电能量(Wh)', '放电能量(Wh)'] : activeTab.value === 'step' ? ['记录编号', '电池', '循环号', '工步序号', '工步', '工步时间', '容量(Ah)', '能量(Wh)', '起始电压(V)', '终止电压(V)', '充电峰值(V)', '放电谷值(V)'] : ['记录编号', '电池', '循环号', '实际工步号', '实际工步名称', '记录序号', '设备时间', '服务器接收时间', '电压(V)', '电流(A)', '容量(Ah)', '能量(Wh)']
-  const records = activeTab.value === 'cycle' ? filteredRows.value.map(row => [row.recordNo, row.batteryNo, row.cycleNo, row.chargeCapacity, row.dischargeCapacity, row.efficiency, row.chargeEnergy, row.dischargeEnergy]) : activeTab.value === 'step' ? filteredRows.value.map(row => [row.recordNo, row.batteryNo, row.cycleNo, row.stepNo, row.stepName, row.stepDuration, row.capacity, row.energy, row.startVoltage, row.endVoltage, row.chargePeakVoltage, row.dischargeValleyVoltage]) : filteredRows.value.map(row => [row.recordNo, row.batteryNo, row.cycleNo, row.stepNo, row.stepName, row.sequenceId, row.recordedAt, row.serverReceivedAt, row.voltage, row.current, row.capacity, row.energy])
+  const headers = activeTab.value === 'cycle' ? ['记录编号', '电池', '循环号', '充电容量(Ah)', '放电容量(Ah)', '充放电效率(%)', '充电能量(Wh)', '放电能量(Wh)'] : activeTab.value === 'step' ? ['记录编号', '电池', '循环号', '工步序号', '工步', '工步时间', '容量(Ah)', '能量(Wh)', '起始电压(V)', '终止电压(V)', '充电峰值(V)', '放电谷值(V)'] : ['记录编号', '电池', '循环号', '实际工步号', '实际工步名称', '记录序号', '设备时间', '服务器接收时间', '电压(V)', '电流(A)', '容量(Ah)', '能量(Wh)', '温度(℃)', '正电位(V)', '负电位(V)', '其他采集字段']
+  const records = activeTab.value === 'cycle' ? filteredRows.value.map(row => [row.recordNo, row.batteryNo, row.cycleNo, row.chargeCapacity, row.dischargeCapacity, row.efficiency, row.chargeEnergy, row.dischargeEnergy]) : activeTab.value === 'step' ? filteredRows.value.map(row => [row.recordNo, row.batteryNo, row.cycleNo, row.stepNo, row.stepName, row.stepDuration, row.capacity, row.energy, row.startVoltage, row.endVoltage, row.chargePeakVoltage, row.dischargeValleyVoltage]) : filteredRows.value.map(row => [row.recordNo, row.batteryNo, row.cycleNo, row.stepNo, row.stepName, row.sequenceId, row.recordedAt, row.serverReceivedAt, row.voltage, row.current, row.capacity, row.energy, row.temperature, row.positivePotential, row.negativePotential, row.extensionMeasurements])
   const blob = new Blob([`\uFEFF${[headers, ...records].map(item => item.map(escapeCsv).join(',')).join('\n')}`], { type: 'text/csv;charset=utf-8' })
   const url = URL.createObjectURL(blob)
   const anchor = document.createElement('a')
@@ -415,9 +436,10 @@
             <th>设备时间</th>
             <th>服务器接收时间</th>
             <th>来源</th>
-            <th>指标编码</th>
-            <th>值</th>
-            <th>单位</th>
+            <th>温度(℃)</th>
+            <th>正电位(V)</th>
+            <th>负电位(V)</th>
+            <th>其他采集字段</th>
             <th>电压(V)</th>
             <th>电流(A)</th>
             <th>容量(Ah)</th>
@@ -472,10 +494,11 @@
               <td>{{ row.sequenceId }}</td>
               <td>{{ row.recordedAt }}</td>
               <td>{{ row.serverReceivedAt }}</td>
-              <td>{{ row.sourceType === 'COLLECTOR' ? `采集器 ${row.collectorCode}` : '检测柜' }}<small v-if="row.sourceType === 'COLLECTOR'">{{ row.channelCode }}</small></td>
-              <td>{{ row.metricCode }}</td>
-              <td>{{ row.metricValue }}</td>
-              <td>{{ row.unit }}</td>
+              <td>{{ row.sourceType === 'COLLECTOR' ? `采集器 ${row.collectorCode}` : row.sourceType === 'SNAPSHOT' ? '检测柜 + 采集器' : '检测柜' }}<small v-if="row.sourceType === 'COLLECTOR'">{{ row.channelCode }}</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>
+              <td>{{ row.extensionMeasurements }}</td>
               <td>
                 <el-input v-if="editing && draftRows[row.id]" v-model="draftRows[row.id].voltage" size="small" />
                 <span v-else>{{ row.voltage }}</span>
@@ -577,7 +600,7 @@
             </tr></template
           >
           <tr v-if="!loading && !pagedRows.length">
-            <td :colspan="activeTab === 'realtime' ? 18 : activeTab === 'step' ? 15 : 12" class="empty-cell">
+            <td :colspan="activeTab === 'realtime' ? 19 : activeTab === 'step' ? 15 : 12" class="empty-cell">
               {{ loadError || '暂无检测柜记录' }}
             </td>
           </tr>
diff --git a/tests/test-console-step-chart.contract.test.mjs b/tests/test-console-step-chart.contract.test.mjs
index 2098855..8836504 100644
--- a/tests/test-console-step-chart.contract.test.mjs
+++ b/tests/test-console-step-chart.contract.test.mjs
@@ -3,6 +3,7 @@
 
 const chart = await readFile(new URL('../src/components/ConsoleChartPanel.vue', import.meta.url), 'utf8')
 const consoleView = await readFile(new URL('../src/views/TestConsole.vue', import.meta.url), 'utf8')
+const dataList = await readFile(new URL('../src/components/ConsoleDataListPanel.vue', import.meta.url), 'utf8')
 
 assert.match(chart, /PersistedTestRecord/)
 assert.match(chart, /metricCode/)
@@ -16,5 +17,9 @@
 assert.doesNotMatch(chart, /function seriesValues\(/)
 assert.match(consoleView, /getPersistedTestRecords/)
 assert.match(consoleView, /:records="realtimeRecords"/)
+assert.match(dataList, /<th>温度\(℃\)<\/th>/)
+assert.match(dataList, /<th>正电位\(V\)<\/th>/)
+assert.match(dataList, /<th>负电位\(V\)<\/th>/)
+assert.match(dataList, /temperature: (?:extension\('TEMPERATURE'\)|snapshot \?)/)
 
 console.log('Ticket02/03 工步图表实时数据契约通过')

--
Gitblit v1.7.1