yeg
2026-08-30 145a4676174cbbf3b256bc86c365eb6d2bb49543
补充采集器断线异常控制台展示
1个文件已添加
1个文件已修改
52 ■■■■■ 已修改文件
src/views/TestConsole.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
tests/e2e/collector-disconnect-recovery.spec.ts 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/TestConsole.vue
@@ -95,7 +95,10 @@
    }
    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
@@ -170,6 +173,8 @@
    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: '已完成' },
@@ -275,8 +280,8 @@
    <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>
tests/e2e/collector-disconnect-recovery.spec.ts
New file
@@ -0,0 +1,41 @@
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()
})