| | |
| | | } |
| | | else if (event === 'test-status' && data && typeof data === 'object' && 'testStatus' in data && liveState.value) |
| | | applyLiveState({ ...liveState.value, testStatus: (data as { testStatus: TestLiveState['testStatus'] }).testStatus }) |
| | | else if (event === 'exception' && Array.isArray(data) && data.length) liveError.value = '检测来源存在异常' |
| | | else if (event === 'exception' && Array.isArray(data) && liveState.value) { |
| | | liveState.value = { ...liveState.value, exceptions: data as TestLiveState['exceptions'] } |
| | | liveError.value = data.length ? '检测来源存在异常' : '' |
| | | } |
| | | }, error => { |
| | | liveConnection.value = error.message.includes('权限') ? '权限不足' : '已断开' |
| | | liveError.value = error.message |
| | |
| | | deviceStatus: ({ ONLINE: '在线', OFFLINE: '离线', UNKNOWN: '未知', WORKING: '运行', PAUSED: '暂停', STOPPED: '结束', FINISHED: '结束' } as Record<string, string>)[source?.deviceStatus || 'UNKNOWN'] || '异常', |
| | | } |
| | | })) |
| | | |
| | | const sourceExceptions = computed(() => (liveState.value?.exceptions || []).filter(item => item.sourceType === 'COLLECTOR')) |
| | | |
| | | const steps = [ |
| | | { no: 1, name: '恒流充电', time: '-', voltage: '0.9000 V', state: '已完成' }, |
| | |
| | | <div class="console-layout"> |
| | | <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="profile.abnormal" class="alarm-list"><div><el-icon><WarningFilled /></el-icon><span><strong>壳体温度超过预警值</strong><small>SP26-216 · 33.1℃ · 2026-07-10 15:18:06</small></span></div><div><el-icon><WarningFilled /></el-icon><span><strong>通道电压波动异常</strong><small>通道 03 · 波动 0.12V · 2026-07-10 15:20:12</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><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 在线">{{ item.collectorState }}</span></td><td>{{ item.temperature }}</td><td>{{ item.positive }}</td><td>{{ item.negative }}</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="sourceExceptions.length || profile.abnormal" class="alarm-list"><div v-for="(item, index) in sourceExceptions" :key="`${item.sourceId}-${item.channelCode}-${index}`"><el-icon><WarningFilled /></el-icon><span><strong>采集器{{ item.collectorName || item.collectorCode || `#${item.sourceId}` }}{{ item.status === 'OPEN' ? '离线' : '断线已恢复' }} · {{ item.channelCode || '来源' }}</strong><small>{{ item.measurementName ? `${item.measurementName}受影响 · ` : '' }}{{ item.status === 'OPEN' ? '缺失区间开放' : '已恢复' }} · {{ item.missingFrom || item.openedAt || '-' }}<template v-if="item.missingTo"> 至 {{ item.missingTo }}</template></small></span></div><div v-if="profile.abnormal && !sourceExceptions.length"><el-icon><WarningFilled /></el-icon><span><strong>存在测试异常</strong><small>请查看异常记录并确认处理</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><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> |
| New file |
| | |
| | | import { expect, test } from '@playwright/test' |
| | | |
| | | test('采集器断线异常开放并在恢复后关闭', async ({ page }) => { |
| | | let recovered = false |
| | | const live = () => ({ |
| | | testId: 42, testStatus: recovered ? 'RUNNING' : 'RUNNING_ABNORMAL', receptionStatus: 'RECEIVING', |
| | | updatedAt: '2026-08-30T06:00:00Z', sources: [{ |
| | | sourceType: 'COLLECTOR', sourceId: 8, batteryNo: 'BAT-01', deviceStatus: recovered ? 'ONLINE' : 'OFFLINE', |
| | | latestSnapshot: { source: '采集器', measurements: [], units: [] }, |
| | | }], exceptions: recovered ? [{ |
| | | sourceType: 'COLLECTOR', sourceId: 8, channelCode: 'S-01', exceptionType: 'COLLECTOR_DISCONNECTED', |
| | | status: 'CLOSED', missingFrom: '2026-08-30T05:59:00Z', missingTo: '2026-08-30T06:00:00Z', |
| | | }] : [{ |
| | | sourceType: 'COLLECTOR', sourceId: 8, channelCode: 'S-01', exceptionType: 'COLLECTOR_DISCONNECTED', |
| | | status: 'OPEN', missingFrom: '2026-08-30T05:59:00Z', |
| | | }], |
| | | }) |
| | | await page.addInitScript(() => localStorage.setItem('battery_detection_auth_token', 'ticket11-test-token')) |
| | | await page.route('**/system/test/42**', async route => { |
| | | if (route.request().url().endsWith('/live-state')) { |
| | | await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ code: 200, data: live() }) }) |
| | | return |
| | | } |
| | | if (route.request().url().endsWith('/live')) { |
| | | const state = live() |
| | | await route.fulfill({ status: 200, contentType: 'text/event-stream', body: `event: snapshot\ndata: ${JSON.stringify(state)}\n\nevent: exception\ndata: ${JSON.stringify(state.exceptions)}\n\n` }) |
| | | return |
| | | } |
| | | await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ code: 200, data: { ...live(), batteries: [] } }) }) |
| | | }) |
| | | await page.goto('/test-list/42/console') |
| | | await expect(page.getByText('测试状态:检测中(异常)', { exact: true })).toBeVisible() |
| | | await expect(page.getByText('采集器#8离线 · S-01', { exact: true })).toBeVisible() |
| | | await expect(page.getByText('缺失区间开放 · 2026-08-30T05:59:00Z', { exact: true })).toBeVisible() |
| | | |
| | | recovered = true |
| | | await page.evaluate(() => window.dispatchEvent(new Event('ticket11-reconnect'))) |
| | | await page.reload() |
| | | await expect(page.getByText('采集器#8断线已恢复 · S-01', { exact: true })).toBeVisible() |
| | | await expect(page.getByText('已恢复 · 2026-08-30T05:59:00Z 至 2026-08-30T06:00:00Z', { exact: true })).toBeVisible() |
| | | }) |