yeg
2026-09-06 6b22d827a392306f7c976c40739985923700b3f1
src/components/ConsoleDataListPanel.vue
@@ -7,6 +7,7 @@
import { getPersistedTestCycles, getPersistedTestRecords, getPersistedTestSteps, type PersistedTestCycle, type PersistedTestRecord, type PersistedTestStep } from '@/api/tests'
import { consoleImportColumns, submitConsoleDataAction, type ConsoleDataType } from '@/api/consoleData'
import { formatBusinessTime as formatTime } from '@/utils/businessTime'
import { formatTestDisplayValue, toTestApiValue } from '@/utils/testDataUnits'
type DataTab = 'realtime' | 'step' | 'cycle'
interface ConsoleCell {
@@ -87,9 +88,9 @@
  activeTab.value === 'realtime'
      ? [
          { label: '电压(V)', value: 'voltage' },
          { label: '电流(A)', value: 'current' },
          { label: '容量(Ah)', value: 'capacity' },
          { label: '能量(Wh)', value: 'energy' },
          { label: '电流(mA)', value: 'current' },
          { label: '容量(mAh)', value: 'capacity' },
          { label: '能量(mWh)', value: 'energy' },
          { label: '温度(℃)', value: 'temperature' },
          { label: '正电位(V)', value: 'positivePotential' },
          { label: '负电位(V)', value: 'negativePotential' },
@@ -97,19 +98,19 @@
    : activeTab.value === 'step'
      ? [
          { label: '工步时间', value: 'stepDuration' },
          { label: '容量(Ah)', value: 'capacity' },
          { label: '能量(Wh)', value: 'energy' },
          { label: '容量(mAh)', value: 'capacity' },
          { label: '能量(mWh)', value: 'energy' },
          { label: '起始电压(V)', value: 'startVoltage' },
          { label: '终止电压(V)', value: 'endVoltage' },
          { label: '充电峰值电压(V)', value: 'chargePeakVoltage' },
          { label: '放电谷值电压(V)', value: 'dischargeValleyVoltage' },
        ]
      : [
          { label: '充电容量(Ah)', value: 'chargeCapacity' },
          { label: '放电容量(Ah)', value: 'dischargeCapacity' },
          { label: '充电容量(mAh)', value: 'chargeCapacity' },
          { label: '放电容量(mAh)', value: 'dischargeCapacity' },
          { label: '效率(%)', value: 'efficiency' },
          { label: '充电能量(Wh)', value: 'chargeEnergy' },
          { label: '放电能量(Wh)', value: 'dischargeEnergy' },
          { label: '充电能量(mWh)', value: 'chargeEnergy' },
          { label: '放电能量(mWh)', value: 'dischargeEnergy' },
        ],
)
function emptyRow(base: Partial<DataRow>): DataRow {
@@ -130,17 +131,17 @@
      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({ 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 })
  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 ? '-' : formatTestDisplayValue('current', record.current), capacity: collector ? '-' : formatTestDisplayValue('capacity', record.capacity), energy: collector ? '-' : formatTestDisplayValue('energy', record.energy), 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)
  const identity = record.stableIdentity || `step:${[record.adapterId, record.devtype, record.deviceId, record.subdevid, record.channelId, record.cycleNo, record.stepNo].join(':')}`
  return emptyRow({ id: identity, recordNo: record.recordNo, batteryNo: record.batteryNo, cycleNo: Number(record.cycleNo), stepNo: Number(record.stepNo), stepName: record.stepName || '-', actualStepLabel: record.actualStepLabel || `${record.stepNo} · ${record.stepName || '-'}`, sequenceId: Number(record.sequenceId || 0), recordedAt: startedAt, absoluteTime: formatTime(record.absoluteTime || record.recordedAt), serverReceivedAt: formatTime(record.serverReceivedAt), stepDuration: record.stepDuration == null ? '-' : String(record.stepDuration), capacity: record.capacity == null ? '-' : Number(record.capacity).toFixed(4), energy: record.energy == null ? '-' : Number(record.energy).toFixed(4), startVoltage: record.startVoltage == null ? '-' : Number(record.startVoltage).toFixed(4), endVoltage: record.endVoltage == null ? '-' : Number(record.endVoltage).toFixed(4), chargePeakVoltage: record.chargePeakVoltage == null ? '-' : Number(record.chargePeakVoltage).toFixed(4), dischargeValleyVoltage: record.dischargeValleyVoltage == null ? '-' : Number(record.dischargeValleyVoltage).toFixed(4) })
  return emptyRow({ id: identity, recordNo: record.recordNo, batteryNo: record.batteryNo, cycleNo: Number(record.cycleNo), stepNo: Number(record.stepNo), stepName: record.stepName || '-', actualStepLabel: record.actualStepLabel || `${record.stepNo} · ${record.stepName || '-'}`, sequenceId: Number(record.sequenceId || 0), recordedAt: startedAt, absoluteTime: formatTime(record.absoluteTime || record.recordedAt), serverReceivedAt: formatTime(record.serverReceivedAt), stepDuration: record.stepDuration == null ? '-' : String(record.stepDuration), capacity: formatTestDisplayValue('capacity', record.capacity), energy: formatTestDisplayValue('energy', record.energy), startVoltage: record.startVoltage == null ? '-' : Number(record.startVoltage).toFixed(4), endVoltage: record.endVoltage == null ? '-' : Number(record.endVoltage).toFixed(4), chargePeakVoltage: record.chargePeakVoltage == null ? '-' : Number(record.chargePeakVoltage).toFixed(4), dischargeValleyVoltage: record.dischargeValleyVoltage == null ? '-' : Number(record.dischargeValleyVoltage).toFixed(4) })
}
function mapCycle(record: PersistedTestCycle): DataRow {
  const startedAt = formatTime(record.recordedAt)
  const identity = record.stableIdentity || `cycle:${[record.adapterId, record.devtype, record.deviceId, record.subdevid, record.channelId, record.cycleNo].join(':')}`
  return emptyRow({ id: identity, recordNo: record.recordNo, batteryNo: record.batteryNo, cycleNo: Number(record.cycleNo), sequenceId: Number(record.sequenceId || 0), recordedAt: startedAt, absoluteTime: formatTime(record.absoluteTime || record.recordedAt), serverReceivedAt: formatTime(record.serverReceivedAt), chargeCapacity: record.chargeCapacity == null ? '-' : Number(record.chargeCapacity).toFixed(4), dischargeCapacity: record.dischargeCapacity == null ? '-' : Number(record.dischargeCapacity).toFixed(4), efficiency: record.efficiency == null ? '-' : Number(record.efficiency).toFixed(2), chargeEnergy: record.chargeEnergy == null ? '-' : Number(record.chargeEnergy).toFixed(4), dischargeEnergy: record.dischargeEnergy == null ? '-' : Number(record.dischargeEnergy).toFixed(4) })
  return emptyRow({ id: identity, recordNo: record.recordNo, batteryNo: record.batteryNo, cycleNo: Number(record.cycleNo), sequenceId: Number(record.sequenceId || 0), recordedAt: startedAt, absoluteTime: formatTime(record.absoluteTime || record.recordedAt), serverReceivedAt: formatTime(record.serverReceivedAt), chargeCapacity: formatTestDisplayValue('chargeCapacity', record.chargeCapacity), dischargeCapacity: formatTestDisplayValue('dischargeCapacity', record.dischargeCapacity), efficiency: record.efficiency == null ? '-' : Number(record.efficiency).toFixed(2), chargeEnergy: formatTestDisplayValue('chargeEnergy', record.chargeEnergy), dischargeEnergy: formatTestDisplayValue('dischargeEnergy', record.dischargeEnergy) })
}
async function loadRecords() {
  loading.value = true
@@ -281,8 +282,10 @@
        const values = Object.fromEntries(fields
          .filter(item => row[item.value as keyof DataRow] !== before[item.value as keyof DataRow])
          .map(item => [item.value, row[item.value as keyof DataRow]]))
        const after = { ...before, ...values }
        return { identity: id, before, after, override: { stableIdentity: id, fieldValues: JSON.stringify(values), deleted: false } }
        // 编辑框使用毫单位;提交字段和摘要都回到 API 单位,正式修订前值仍由后端读取。
        const apiValues = Object.fromEntries(Object.entries(values).map(([field, value]) => [field, toTestApiValue(field, String(value))]))
        const apiBefore = { ...before, ...Object.fromEntries(fields.map(item => [item.value, toTestApiValue(item.value, String(before[item.value as keyof DataRow]))])) }
        return { identity: id, before: apiBefore, after: { ...apiBefore, ...apiValues }, override: { stableIdentity: id, fieldValues: JSON.stringify(apiValues), deleted: false } }
      })
    })
    await submitConsoleDataAction(props.testId, { dataType: dataType.value, operationType: 'EDIT', scopeSummary: `按格修改${changed.length}条${activeTab.value}数据`, identitySummary: JSON.stringify(editItems.map(item => item.identity)), changesSummary: JSON.stringify(editItems.map(item => ({ identity: item.identity, before: item.before, after: item.after }))), reason: editForm.remark.trim(), overrides: editItems.map(item => item.override) })
@@ -341,7 +344,7 @@
    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)', '温度(℃)', '正电位(V)', '负电位(V)']
  const headers = activeTab.value === 'cycle' ? ['记录编号', '电池', '循环号', '充电容量(mAh)', '放电容量(mAh)', '充放电效率(%)', '充电能量(mWh)', '放电能量(mWh)'] : activeTab.value === 'step' ? ['记录编号', '电池', '循环号', '工步序号', '工步', '工步时间', '容量(mAh)', '能量(mWh)', '起始电压(V)', '终止电压(V)', '充电峰值(V)', '放电谷值(V)'] : ['记录编号', '电池', '循环号', '实际工步号', '实际工步名称', '记录序号', '设备时间', '服务器接收时间', '电压(V)', '电流(mA)', '容量(mAh)', '能量(mWh)', '温度(℃)', '正电位(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])
  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)
@@ -436,9 +439,9 @@
            <th>正电位(V)</th>
            <th>负电位(V)</th>
            <th>电压(V)</th>
            <th>电流(A)</th>
            <th>容量(Ah)</th>
            <th>能量(Wh)</th>
            <th>电流(mA)</th>
            <th>容量(mAh)</th>
            <th>能量(mWh)</th>
            <th class="action-column">操作</th>
          </tr>
          <tr v-else-if="activeTab === 'step'">
@@ -450,8 +453,8 @@
            <th>设备时间</th>
            <th>服务器接收时间</th>
            <th>工步时间</th>
            <th>容量(Ah)</th>
            <th>能量(Wh)</th>
            <th>容量(mAh)</th>
            <th>能量(mWh)</th>
            <th>起始电压(V)</th>
            <th>终止电压(V)</th>
            <th>充电峰值(V)</th>
@@ -465,11 +468,11 @@
            <th>循环号</th>
            <th>设备时间</th>
            <th>服务器接收时间</th>
            <th>充电容量(Ah)</th>
            <th>放电容量(Ah)</th>
            <th>充电容量(mAh)</th>
            <th>放电容量(mAh)</th>
            <th>充放电效率(%)</th>
            <th>充电能量(Wh)</th>
            <th>放电能量(Wh)</th>
            <th>充电能量(mWh)</th>
            <th>放电能量(mWh)</th>
            <th class="action-column">操作</th>
          </tr>
        </thead>
@@ -811,8 +814,13 @@
}
.data-toolbar > div {
  display: flex;
  flex-wrap: wrap;
  max-width: 100%;
  gap: 8px;
}
.data-toolbar button {
  white-space: nowrap;
}
.load-error {
  color: #b34f4f;
}