| | |
| | | import ConsoleProjectChartPanel from '@/components/ConsoleProjectChartPanel.vue' |
| | | import ConsoleDataListPanel from '@/components/ConsoleDataListPanel.vue' |
| | | import { useTests } from '@/stores/tests' |
| | | import { endTestReception, getTestDetail, getTestLiveState, openTestLiveStream, pauseTestReception, resumeTestReception, startTestReception, type TestConfigurationDetail, type TestLiveState, type TestIntegrityIssue } from '@/api/tests' |
| | | import { endTestReception, getTestDetail, getTestLiveState, openTestLiveStream, pauseTestReception, resumeTestReception, startTestReception, type TestConfigurationDetail, type TestLiveState } from '@/api/tests' |
| | | |
| | | type ConsoleState = '未开始接收' | '检测中' | '暂停' | '已结束' |
| | | type ReceptionState = '未开始接收' | '接收中' | '暂停接收' | '已结束接收' |
| | |
| | | } |
| | | }) |
| | | const initialStatus = currentTest.value?.status |
| | | const state = ref<ConsoleState>(initialStatus === '检测完成' || route.query.state === 'ended' ? '已结束' : initialStatus === '暂停' ? '暂停' : initialStatus === '待测试' ? '未开始接收' : '检测中') |
| | | const state = ref<ConsoleState>(initialStatus === '检测完成' || initialStatus === '完成确认中' || initialStatus === '已终止' || route.query.state === 'ended' ? '已结束' : initialStatus === '暂停' ? '暂停' : initialStatus === '待测试' ? '未开始接收' : '检测中') |
| | | const elapsedSeconds = ref(42 * 60 + 25) |
| | | const stepDialogVisible = ref(false) |
| | | const activeTab = ref<ConsoleTab>(route.query.tab === 'data-list' ? 'data-list' : 'realtime') |
| | |
| | | liveConnection.value = next.sources.some(source => Boolean(source.latestSnapshot?.observedAt) |
| | | || Boolean(source.latestSnapshot?.measurements?.length)) ? '已连接' : '空数据' |
| | | liveError.value = '' |
| | | testStatusLabel.value = ({ WAITING: '待测试', RUNNING: '检测中', PAUSED: '暂停', RUNNING_ABNORMAL: '检测中(异常)', COMPLETION_PENDING: '完成确认中', COMPLETED: '检测完成', TERMINATED: '已终止' } as Record<string, string>)[next.testStatus] || '未知' |
| | | testStatusLabel.value = ({ WAITING: '待测试', RUNNING: '检测中', PAUSED: '暂停', RUNNING_ABNORMAL: '检测中(异常)', COMPLETION_PENDING: '检测完成', COMPLETED: '检测完成', TERMINATED: '检测完成' } as Record<string, string>)[next.testStatus] || '未知' |
| | | const mapped = next.receptionStatus |
| | | if (mapped === 'NOT_STARTED') receptionState.value = '未开始接收' |
| | | else if (mapped === 'PAUSED') receptionState.value = '暂停接收' |
| | |
| | | liveState.value = { ...liveState.value, exceptions: current } |
| | | liveError.value = current.length ? '检测来源存在异常' : '' |
| | | } |
| | | else if (event === 'integrity' && Array.isArray(data) && liveState.value) |
| | | liveState.value = { ...liveState.value, integrityIssues: data as TestIntegrityIssue[] } |
| | | }, error => { |
| | | liveConnection.value = error.message.includes('权限') ? '权限不足' : '已断开' |
| | | liveError.value = error.message |
| | |
| | | backendDetail.value = await endTestReception(testId.value) |
| | | state.value = '已结束' |
| | | receptionState.value = '已结束接收' |
| | | testStatusLabel.value = ({ WAITING: '待测试', RUNNING: '检测中', PAUSED: '暂停', RUNNING_ABNORMAL: '检测中(异常)', COMPLETION_PENDING: '完成确认中', COMPLETED: '检测完成', TERMINATED: '已终止' } as Record<string, string>)[backendDetail.value.testStatus || 'COMPLETION_PENDING'] || '完成确认中' |
| | | testStatusLabel.value = ({ WAITING: '待测试', RUNNING: '检测中', PAUSED: '暂停', RUNNING_ABNORMAL: '检测中(异常)', COMPLETION_PENDING: '检测完成', COMPLETED: '检测完成', TERMINATED: '检测完成' } as Record<string, string>)[backendDetail.value.testStatus || 'COMPLETED'] || '检测完成' |
| | | ElMessage.success('测试已停止,系统不再请求数据') |
| | | } catch (error) { |
| | | if (error === 'cancel' || error === 'close') return |
| | |
| | | <main> |
| | | <section class="panel step-panel"><header class="panel-heading"><div><i></i><strong>项目参数</strong><span>STEP PARAMETERS</span></div><button class="primary-button compact-button" type="button" @click="stepDialogVisible=true"><el-icon><Setting /></el-icon>工步设置</button></header><div class="project-meta"><span>测试项目 <strong>{{ profile.purpose }}</strong></span><span>当前循环 <strong>循环 36 / 计划 60</strong></span></div><div class="step-table-wrap"><table><thead><tr><th>工步号</th><th>工步名称</th><th>时间(h:min:s.ms)</th><th>电压(V)</th><th>状态</th></tr></thead><tbody><tr v-for="step in steps" :key="step.no" :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></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.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.status === 'OPEN' ? '缺失区间开放' : (item.message || '当前异常') }} · {{ item.missingFrom || item.openedAt || '-' }}<template v-if="item.missingTo"> 至 {{ item.missingTo }}</template></small></span></div></div><div v-else class="alarm-empty"><el-icon><CircleCheckFilled /></el-icon>暂无异常</div></section> |
| | | <section class="panel integrity-panel"><header class="panel-heading"><div><i></i><strong>完整性检查</strong><span>INTERVAL INTEGRITY</span></div><span :class="(liveState?.integrityIssues || []).some(item => item.issueStatus === 'OPEN') ? 'integrity-open' : 'integrity-ok'">{{ (liveState?.integrityIssues || []).some(item => item.issueStatus === 'OPEN') ? '完成确认中' : '已通过' }}</span></header><div v-if="(liveState?.integrityIssues || []).length" class="integrity-list"><div v-for="item in (liveState?.integrityIssues || [])" :key="item.issueId" :class="item.issueStatus === 'OPEN' ? 'open' : 'resolved'"><strong>{{ item.sourceType === 'COLLECTOR_CHANNEL' ? '采集器' : '检测柜' }}<template v-if="item.intervalId"> · 区间 #{{ item.intervalId }}</template></strong><span>{{ item.issueType === 'SEQUENCE_GAP' ? `序号缺口 ${item.missingFromSequence} - ${item.missingToSequence}` : item.issueType }}</span><small>{{ item.issueStatus === 'OPEN' ? '待补采或处理' : '已恢复' }} · 重试 {{ item.retryCount }} 次</small></div></div><div v-else class="integrity-empty">所有接收区间序号连续,时间间隔仅作辅助指标</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><thead><tr><th>电池号</th><th>测试柜</th><th>柜状态</th><th>检测通道</th><th>采集器</th><th>采集器状态</th><th>温度</th><th>正电位</th><th>负电位</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>{{ item.temperature }}</td><td>{{ item.positive }}</td><td>{{ item.negative }}</td></tr></tbody></table></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><thead><tr><th>电池号</th><th>测试柜</th><th>柜状态</th><th>检测通道</th><th>采集器</th><th>采集器状态</th><th>温度</th><th>正电位</th><th>负电位</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>{{ item.temperature }}</td><td>{{ item.positive }}</td><td>{{ item.negative }}</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> |
| | |
| | | .console-tabs{min-height:52px;display:flex;align-items:center;gap:10px;margin-bottom:14px;padding:0 4px;border-bottom:1px solid #d5e0e9}.console-tabs button{height:30px;min-width:88px;padding:0 16px;border:1px solid transparent;border-radius:16px;color:#52606d;background:#f5f7fa;font-size:12px;cursor:pointer;transition:.16s ease}.console-tabs button:hover{color:var(--blue);background:#edf5fd}.console-tabs button.active{color:#1677dd;background:#dff0ff;border-color:#c4e3ff;font-weight:600}.tab-content{min-width:0}.data-list-panel{min-width:0;border:1px solid #d8e1e9;background:#fff}.data-list-panel .panel-heading>small{color:#8795a3;font-size:10px}.data-table-wrap{width:100%;overflow-x:auto;padding:16px 18px 20px}.data-table-wrap table{min-width:1260px}.data-table-wrap td strong{color:#1268dc}.data-voltage{color:#7f35c5;font-weight:600}.data-temperature{color:#bf6100;font-weight:600}.console-layout{grid-template-columns:minmax(0,2.35fr) minmax(300px,1fr);gap:24px;min-width:0}.console-layout>main,.console-layout>.parameter-panel,.console-layout main>.panel{min-width:0;max-width:100%}.step-table-wrap,.channel-table-wrap{width:100%;max-width:100%;min-width:0;overflow-x:auto;overflow-y:hidden}.step-table-wrap table{min-width:720px}.channel-table-wrap table{width:1120px;min-width:1120px;max-width:none}.parameter-panel{position:relative;z-index:1;overflow:hidden}@media(max-width:1280px){.console-layout{grid-template-columns:minmax(0,1fr) 300px;gap:20px}}@media(max-width:1050px){.console-layout{grid-template-columns:minmax(0,1fr);gap:16px}.parameter-panel{grid-row:2}}@media(max-width:600px){.console-tabs{overflow-x:auto}.console-tabs button{flex:0 0 auto}.data-table-wrap{padding:12px}} |
| | | .device-state.离线{color:#b34f4f;background:#fff1f1;border-color:#e9bcbc} |
| | | .device-state.未知{color:#7b8794;background:#f2f4f6;border-color:#d8dde2} |
| | | .integrity-panel .panel-heading>span{font-size:11px}.integrity-open{color:#b34f4f}.integrity-ok{color:#23865e}.integrity-list{display:grid;gap:8px;padding:14px 16px}.integrity-list>div{display:grid;grid-template-columns:1.2fr 1fr auto;align-items:center;gap:10px;padding:9px 12px;border:1px solid #e5c3a4;background:#fff8f1;font-size:11px}.integrity-list>div.resolved{border-color:#b8dfcd;background:#f2fbf6}.integrity-list span{color:#52606d}.integrity-list small{color:#8795a3}.integrity-empty{padding:16px;color:#71808d;font-size:11px} |
| | | </style> |