| | |
| | | function formatDateTime(value?: string | null) { |
| | | return value ? value.replace('T', ' ').replace(/Z$/, '').replace(/\.\d{3,6}$/, '') : '-' |
| | | } |
| | | |
| | | // 控制台的柜状态和采集器状态只表达连接事实,设备工作状态由设备事实另行保留。 |
| | | function connectionStatusLabel(status?: string) { |
| | | return status === 'ONLINE' ? '在线' : '离线' |
| | | } |
| | | |
| | | function exceptionTitle(item: Record<string, unknown>) { |
| | | if (item.exceptionId !== undefined && item.exceptionId !== null) { |
| | | const source = item.sourceType === 'COLLECTOR' ? '采集器' : '检测柜' |
| | | const field = String(item.metricCode || item.measurementName || '字段') |
| | | return `${source} · ${field}超限` |
| | | } |
| | | if (item.sourceType === 'COLLECTOR') { |
| | | const name = item.collectorName || item.collectorCode || `#${item.sourceId || '-'}` |
| | | return `采集器${name}${item.status === 'OPEN' ? '离线' : '异常已处理'} · ${item.channelCode || '来源'}` |
| | | } |
| | | return String(item.message || '数据来源当前不可观测') |
| | | } |
| | | |
| | | function exceptionDetail(item: Record<string, unknown>) { |
| | | if (item.exceptionId !== undefined && item.exceptionId !== null) |
| | | return `异常值:${item.value ?? '-'} ${item.unit || ''} · 测试预警异常` |
| | | return String(item.message || (item.status === 'OPEN' ? '当前异常' : '异常已处理')) |
| | | } |
| | | const profile = computed(() => { |
| | | const detail = backendDetail.value |
| | | const batteries = detail?.batteries || [] |
| | |
| | | observedAt: source?.latestSnapshot?.observedAt, |
| | | collectorObservedAt: collectorSource?.latestSnapshot?.observedAt, |
| | | collectorUnit: collectorSource?.latestSnapshot?.units?.join('/') || '-', |
| | | collectorStatus: ({ ONLINE: '在线', OFFLINE: '离线', UNKNOWN: '未知', WORKING: '运行', PAUSED: '暂停', STOPPED: '结束', FINISHED: '结束' } as Record<string, string>)[collectorSource?.deviceStatus || 'UNKNOWN'] || '异常', |
| | | deviceStatus: ({ ONLINE: '在线', OFFLINE: '离线', UNKNOWN: '未知', WORKING: '运行', PAUSED: '暂停', STOPPED: '结束', FINISHED: '结束' } as Record<string, string>)[source?.deviceStatus || 'UNKNOWN'] || '异常', |
| | | collectorStatus: connectionStatusLabel(collectorSource?.deviceStatus), |
| | | deviceStatus: connectionStatusLabel(source?.deviceStatus), |
| | | } |
| | | })) |
| | | |
| | |
| | | const cabinet = sources.find(item => item.sourceType === 'CABINET_CHANNEL') |
| | | const collector = sources.find(item => item.sourceType === 'COLLECTOR') |
| | | const battery = backendDetail.value?.batteries?.find(item => item.batteryNo === cell.no) |
| | | const label = (status?: string) => ({ ONLINE: '在线', OFFLINE: '离线', UNKNOWN: '未知', WORKING: '运行', PAUSED: '暂停', STOPPED: '结束', FINISHED: '结束' } as Record<string, string>)[status || 'UNKNOWN'] || '异常' |
| | | const measurements: Record<string, string> = {} |
| | | if (receptionState.value === '接收中') { |
| | | const sourceMeasurements = collector?.latestSnapshot?.measurements || [] |
| | |
| | | if (measurement?.value !== undefined && measurement?.value !== null) measurements[measurementCode] = String(measurement.value) |
| | | } |
| | | } |
| | | return { no: cell.no, cabinet: cell.cabinet, cabinetState: label(cabinet?.deviceStatus), channel: cell.channel, |
| | | collector: battery?.collectorName || '-', collectorState: label(collector?.deviceStatus), measurements } |
| | | return { no: cell.no, cabinet: cell.cabinet, cabinetState: connectionStatusLabel(cabinet?.deviceStatus), channel: cell.channel, |
| | | collector: battery?.collectorName || '-', collectorState: connectionStatusLabel(collector?.deviceStatus), measurements } |
| | | })) |
| | | |
| | | async function togglePause() { |
| | |
| | | <div class="console-layout"> |
| | | <main> |
| | | <section class="panel step-panel"><header class="panel-heading"><div><i></i><strong>项目参数</strong><span>STEP PARAMETERS</span></div><div class="step-actions"><template v-if="stepEditing"><button class="primary-button compact-button" type="button" @click="addStepMapping"><el-icon><Plus /></el-icon>合并选中</button><button class="secondary-button compact-button" type="button" @click="cancelStepSettings">取消</button></template><button v-else class="primary-button compact-button" type="button" @click="openStepSettings"><el-icon><Setting /></el-icon>工步设置</button></div></header><div class="project-meta"><span>测试项目 <strong>{{ profile.purpose }}</strong></span><span>当前循环 <strong>{{ currentCycleNo || '-' }} / 计划 {{ plannedCycleCount || '-' }}</strong></span><span>当前工步 <strong>{{ currentStepNo || '-' }}</strong></span><span>执行状态 <strong>{{ testStatusLabel }}</strong></span></div><div class="step-table-wrap"><table><thead><tr><th v-if="stepEditing">选择</th><th>工步号</th><th>工步名称</th><th>时间(h:min:s.ms)</th><th>电压(V)</th><th>执行状态</th></tr></thead><tbody><template v-if="stepEditing"><tr v-for="step in actualPlanRows" :key="`edit-${step.cyclePosition}`" :class="{ active: step.executionStatus === '进行中' }"><td><el-checkbox :model-value="selectedPlanSteps.includes(step.cyclePosition)" @change="(checked: string | number | boolean) => togglePlanStep(step.cyclePosition, checked)" /></td><td>{{ step.stepNo }}</td><td><strong>{{ step.stepName }}</strong></td><td>{{ formatDuration(step.durationSeconds) }}</td><td>{{ step.voltageV == null ? '-' : `${Number(step.voltageV).toFixed(4)} V` }}</td><td><span class="step-state" :class="step.executionStatus">{{ step.executionStatus }}</span></td></tr></template><template v-else><tr v-for="(step, index) in planRows" :key="`${index}-${step.no}-${step.name}`" :class="{ active: step.state === '进行中' }"><td>{{ step.no }}</td><td><strong>{{ step.name }}</strong></td><td>{{ step.time }}</td><td>{{ step.voltage }}</td><td><span class="step-state" :class="step.state">{{ step.state }}</span></td></tr></template><tr v-if="!planRows.length && !actualPlanRows.length"><td :colspan="stepEditing ? 6 : 5" class="empty-cell">暂无后端检测柜工步方案</td></tr></tbody></table></div><div v-if="stepEditing && stepDraft.length" class="mapping-list"><div v-for="(item, index) in stepDraft" :key="index" class="mapping-row"><strong>逻辑工步 {{ item.logicalNo }}</strong><span>第 {{ item.startStepNo }} 至 {{ item.endStepNo }} 个工步</span><button class="secondary-button compact-button" type="button" aria-label="解除合并" @click="removeStepMapping(index)"><el-icon><Delete /></el-icon>解除合并</button></div></div></section> |
| | | <section class="panel alarm-panel"><header class="panel-heading"><div><i></i><strong>异常警告</strong><span>ALARM</span></div></header><div v-if="currentExceptions.length" class="alarm-list"><div v-for="(item, index) in currentExceptions" :key="`${item.sourceId || item.sourceType}-${item.channelCode || index}-${index}`"><el-icon><WarningFilled /></el-icon><span><strong>{{ item.sourceType === 'COLLECTOR' ? `采集器${item.collectorName || item.collectorCode || `#${item.sourceId}`}${item.status === 'OPEN' ? '离线' : '异常已处理'} · ${item.channelCode || '来源'}` : (item.message || '数据来源当前不可观测') }}</strong><small>{{ item.measurementName ? `${item.measurementName}受影响 · ` : '' }}{{ item.message || (item.status === 'OPEN' ? '当前异常' : '异常已处理') }}</small></span></div></div><div v-else class="alarm-empty"><el-icon><CircleCheckFilled /></el-icon>暂无异常</div></section> |
| | | <section class="panel channel-panel"><header class="panel-heading"><div><i></i><strong>通道</strong><span>CHANNEL STATUS</span></div></header><div class="channel-table-wrap"><table :style="{ minWidth: `${1120 + collectorColumns.length * 120}px` }"><thead><tr><th>电池号</th><th>测试柜</th><th>柜状态</th><th>检测通道</th><th>采集器</th><th>采集器状态</th><th v-for="column in collectorColumns" :key="column.key">{{ column.label }}</th></tr></thead><tbody><tr v-for="item in channels" :key="item.no"><td><strong>{{ item.no }}</strong></td><td>{{ item.cabinet }}</td><td><span class="device-state" :class="item.cabinetState">{{ item.cabinetState }}</span></td><td>{{ item.channel }}</td><td>{{ item.collector }}</td><td><span class="device-state" :class="{ 在线: item.collectorState === '在线', 异常: item.collectorState !== '在线' }">{{ item.collectorState }}</span></td><td v-for="column in collectorColumns" :key="column.key">{{ item.measurements[column.measurementCode] || '-' }}</td></tr><tr v-if="!channels.length"><td :colspan="6 + collectorColumns.length" class="empty-cell">暂无通道配置</td></tr></tbody></table></div></section> |
| | | <section class="panel alarm-panel"><header class="panel-heading"><div><i></i><strong>异常警告</strong><span>ALARM</span></div></header><div v-if="currentExceptions.length" class="alarm-list"><div v-for="(item, index) in currentExceptions" :key="`${item.exceptionId || item.sourceId || item.sourceType}-${item.channelCode || index}-${index}`"><el-icon><WarningFilled /></el-icon><span><strong>{{ exceptionTitle(item) }}</strong><small>{{ item.measurementName ? `${item.measurementName}受影响 · ` : '' }}{{ exceptionDetail(item) }}</small></span></div></div><div v-else class="alarm-empty"><el-icon><CircleCheckFilled /></el-icon>暂无异常</div></section> |
| | | <section class="panel channel-panel"><header class="panel-heading"><div><i></i><strong>通道</strong><span>CHANNEL STATUS</span></div></header><div class="channel-table-wrap"><table :style="{ minWidth: `${1120 + collectorColumns.length * 120}px` }"><thead><tr><th>电池号</th><th>测试柜</th><th>柜状态</th><th>检测通道</th><th>采集器</th><th>采集器状态</th><th v-for="column in collectorColumns" :key="column.key">{{ column.label }}</th></tr></thead><tbody><tr v-for="item in channels" :key="item.no"><td><strong>{{ item.no }}</strong></td><td>{{ item.cabinet }}</td><td><span class="device-state" :class="item.cabinetState">{{ item.cabinetState }}</span></td><td>{{ item.channel }}</td><td>{{ item.collector }}</td><td><span class="device-state" :class="item.collectorState">{{ item.collectorState }}</span></td><td v-for="column in collectorColumns" :key="column.key">{{ item.measurements[column.measurementCode] || '-' }}</td></tr><tr v-if="!channels.length"><td :colspan="6 + collectorColumns.length" class="empty-cell">暂无通道配置</td></tr></tbody></table></div></section> |
| | | </main> |
| | | <aside class="parameter-panel"><header class="panel-heading"><div><i></i><strong>测试参数</strong><span>TEST SETTINGS</span></div></header><dl><div v-for="item in parameters" :key="item[0]"><dt>{{ item[0] }}</dt><dd>{{ item[1] }}</dd></div></dl></aside> |
| | | </div> |