yeg
2026-09-05 952f3616c49fe88a087cbcc3cc690f21f25314a8
分离离线实时提醒与异常待办处理含义

移除离线提醒的待处理文案,验证设备恢复在线和再次离线的实时更新,以及异常处理记录对控制台提醒无影响。
2个文件已修改
46 ■■■■■ 已修改文件
src/views/TestConsole.vue 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
tests/test-offline-exceptions-browser.mjs 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/TestConsole.vue
@@ -40,7 +40,7 @@
function exceptionTitle(item: Record<string, unknown>) {
  if (item.exceptionType === 'DEVICE_OFFLINE') {
    const source = item.sourceType === 'COLLECTOR' ? '采集器' : '检测柜通道'
    return `${source}离线${item.exceptionId ? '(待处理)' : ''} · ${item.sourceIdentity || item.sourceId || '-'}`
    return `${source}离线 · ${item.sourceIdentity || item.sourceId || '-'}`
  }
  if (item.exceptionId !== undefined && item.exceptionId !== null) {
    const source = item.sourceType === 'COLLECTOR' ? '采集器' : '检测柜'
@@ -270,7 +270,7 @@
  }
}))
// 实时告警只展示后端当前仍开放的异常,异常事实由后端状态和消息说明。
// 离线提醒由后端当前设备事实生成,与异常管理的处理状态无关。
const currentExceptions = computed(() => (liveState.value?.exceptions || [])
  .filter(item => receptionState.value === '接收中'
    && (!item.status || item.status === 'OPEN')))
tests/test-offline-exceptions-browser.mjs
@@ -9,6 +9,7 @@
const browser = await chromium.launch({ channel: 'chrome', headless: true })
const now = Date.UTC(2026, 8, 5, 11, 0, 0)
let receptionStatus = 'RECEIVING'
let deviceStatus = 'OFFLINE'
const warnings = ['CABINET_CHANNEL', 'COLLECTOR'].map((sourceType, index) => ({
  exceptionId: index + 1, testId: 1, testName: '离线告警验收', exceptionType: 'DEVICE_OFFLINE',
  sourceType, sourceIdentity: index ? '9' : 'dev-sim:22:1:1:1', metricCode: 'DEVICE_ONLINE',
@@ -22,14 +23,29 @@
    binding: { channelId: 1, cabinetId: 1, cabinetName: '演示检测柜', adapterCode: 'dev-sim', devtype: 22, devid: 1, subdevid: 1, chlid: 1 } }],
})
const live = () => ({ testId: 1, testStatus: detail().testStatus, receptionStatus, serverTime: now, testPlan: null,
  sources: ['CABINET_CHANNEL', 'COLLECTOR'].map(sourceType => ({ sourceType, sourceId: sourceType === 'COLLECTOR' ? 9 : 1, batteryNo: 'B-01', deviceStatus: 'OFFLINE', latestSnapshot: {} })),
  exceptions: receptionStatus === 'RECEIVING' ? warnings.filter(w => w.exceptionStatus === 'OPEN').map(w => ({ ...w, status: w.exceptionStatus, value: null, message: '接收期间设备离线,等待处理' })) : [],
  sources: ['CABINET_CHANNEL', 'COLLECTOR'].map(sourceType => ({ sourceType, sourceId: sourceType === 'COLLECTOR' ? 9 : 1, batteryNo: 'B-01', deviceStatus, latestSnapshot: {} })),
  exceptions: receptionStatus === 'RECEIVING' && deviceStatus === 'OFFLINE' ? warnings.map(w => ({
    exceptionType: 'DEVICE_OFFLINE', sourceType: w.sourceType, sourceIdentity: w.sourceIdentity,
    status: 'OPEN', message: '接收中数据来源离线,当前无法取得测量数据',
  })) : [],
})
try {
  const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } })
  const errors = []
  page.on('pageerror', error => errors.push(error.message))
  await page.addInitScript(() => localStorage.setItem('battery_detection_auth_token', 'browser-test'))
  await page.addInitScript(() => {
    const fetch = window.fetch.bind(window)
    window.fetch = (input, init) => {
      if (!String(input).endsWith('/test/1/live')) return fetch(input, init)
      // 保持实时流打开,直接验证设备状态和异常事件无需刷新即可更新页面。
      const body = new ReadableStream({ start(controller) {
        window.emitOfflineTestEvent = (event, data) => controller.enqueue(
          new TextEncoder().encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`))
      } })
      return Promise.resolve(new Response(body, { headers: { 'Content-Type': 'text/event-stream' } }))
    }
  })
  await page.route(`${origin}/**`, async route => {
    const path = new URL(route.request().url()).pathname
    if (path.startsWith('/assets/')) return route.fulfill({ body: await readFile(`dist${path}`), contentType: path.endsWith('.css') ? 'text/css' : 'application/javascript' })
@@ -52,6 +68,7 @@
  await expect(alarm).toContainText('检测柜通道离线')
  await expect(alarm).toContainText('采集器离线')
  await expect(alarm).not.toContainText('超限')
  await expect(alarm).not.toContainText('处理')
  await expect(alarm).not.toContainText('暂无异常')
  await alarm.screenshot({ path: `${output}/console-alarm.png` })
  await page.setViewportSize({ width: 390, height: 844 })
@@ -65,11 +82,21 @@
    if (status === 'RECEIVING') await expect(alarm).toContainText('采集器离线')
    else await expect(alarm).toContainText('暂无异常')
  }
  deviceStatus = 'ONLINE'
  await page.evaluate(state => {
    window.emitOfflineTestEvent('device-status', state.sources)
    window.emitOfflineTestEvent('exception', state.exceptions)
  }, live())
  await expect(alarm).toContainText('暂无异常')
  await page.reload()
  await expect(alarm).toContainText('暂无异常')
  assert.ok(warnings.every(w => w.exceptionStatus === 'OPEN'))
  if (process.argv[2] !== 'console') {
    await page.goto(`${origin}/exception`)
    const table = page.locator('.exception-page table')
    await expect(table.locator('tbody tr')).toHaveCount(2)
    await expect(table).toContainText('设备离线')
    await expect(table).toContainText('待处理')
    await expect(table).toContainText('2026-09-05')
    await expect(table).not.toContainText('null')
    await table.locator('tbody tr').first().getByRole('button', { name: '处理', exact: true }).click()
@@ -79,9 +106,18 @@
    await page.reload()
    await expect(table).toContainText('已处理')
    await page.screenshot({ path: `${output}/exception-management.png`, fullPage: true, animations: 'disabled' })
    await page.goto(`${origin}/test-list/1/console`)
    await expect(alarm).toContainText('暂无异常')
    deviceStatus = 'OFFLINE'
    await page.evaluate(state => window.emitOfflineTestEvent('snapshot', state), live())
    await expect(alarm.locator('.alarm-list > div')).toHaveCount(2)
    await expect(alarm).toContainText('检测柜通道离线')
    await expect(alarm).toContainText('采集器离线')
    await expect(alarm).not.toContainText('处理')
    assert.equal(warnings[0].exceptionStatus, 'CLOSED')
  }
  assert.deepEqual(errors, [])
  console.log('离线告警浏览器验证通过:接收中告警、暂停和结束隐藏、刷新及异常记录处理。')
  console.log('离线告警浏览器验证通过:接收中告警、暂停和结束隐藏、恢复在线移除提醒;完整模式同时检查异常处理与实时提醒独立。')
} finally {
  await browser.close()
}