yeg
2026-09-02 1858334fbc347e284a55b29693a0695ec19694bf
展示完整实时快照字段
4个文件已修改
64 ■■■■■ 已修改文件
src/api/tests.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/ConsoleChartPanel.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/ConsoleDataListPanel.vue 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
tests/test-console-step-chart.contract.test.mjs 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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
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)
}
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>
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 工步图表实时数据契约通过')