| | |
| | | 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 |
| | |
| | | stepName: string |
| | | stepType?: string |
| | | sequenceId: number |
| | | cabinetSequenceId?: number | null |
| | | recordedAt: string |
| | | absoluteTime: string |
| | | deviceTime?: string |
| | |
| | | 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 |
| | |
| | | } |
| | | |
| | | 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') |
| | |
| | | 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) |
| | | } |
| | | |
| | |
| | | current: string |
| | | capacity: string |
| | | energy: string |
| | | temperature: string |
| | | positivePotential: string |
| | | negativePotential: string |
| | | extensionMeasurements: string |
| | | chargeCapacity: string |
| | | dischargeCapacity: string |
| | | efficiency: string |
| | |
| | | { 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' |
| | | ? [ |
| | |
| | | : '-' |
| | | } |
| | | 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) |
| | |
| | | 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`) |
| | |
| | | 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') |
| | |
| | | <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> |
| | |
| | | <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> |
| | |
| | | </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> |
| | |
| | | |
| | | 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/) |
| | |
| | | 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 工步图表实时数据契约通过') |