From d80b338eb822455f230cb0888513147d50965904 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期日, 30 八月 2026 01:26:09 +0800
Subject: [PATCH] 接入双状态实时控制台
---
src/api/tests.ts | 75 +++++++++++++++++++++++++++++++++++++
1 files changed, 74 insertions(+), 1 deletions(-)
diff --git a/src/api/tests.ts b/src/api/tests.ts
index 08d5f8b..8d4ccfc 100644
--- a/src/api/tests.ts
+++ b/src/api/tests.ts
@@ -1,5 +1,5 @@
import type { ReceptionStatus, TestRecord, TestStatus } from '@/stores/tests'
-import { request, type ApiResponse } from './request'
+import { getAuthToken, request, type ApiResponse } from './request'
export type TestStatusCode = 'WAITING' | 'RUNNING' | 'PAUSED' | 'RUNNING_ABNORMAL' | 'COMPLETION_PENDING' | 'COMPLETED' | 'TERMINATED'
export type ReceptionStatusCode = 'NOT_STARTED' | 'RECEIVING' | 'PAUSED' | 'ENDED'
@@ -168,3 +168,76 @@
export async function startTestReception(testId: number): Promise<TestConfigurationDetail> {
return (await request<TestDetailResponse>(`/system/test/${testId}/reception/start`, { method: 'POST' })).data
}
+
+export interface LiveSource {
+ sourceType: 'CABINET_CHANNEL' | 'COLLECTOR' | string
+ sourceId: number
+ batteryNo: string
+ deviceStatus: string
+ latestSnapshot: {
+ source?: string
+ unit?: string
+ observedAt?: string
+ sampleIntervalMs?: number
+ measurements?: Array<Record<string, unknown>>
+ units?: string[]
+ [key: string]: unknown
+ }
+}
+
+export interface TestLiveState {
+ testId: number
+ testStatus: TestStatusCode
+ receptionStatus: ReceptionStatusCode
+ updatedAt: string
+ sources: LiveSource[]
+ exceptions: Array<Record<string, unknown>>
+}
+
+interface TestLiveStateResponse extends ApiResponse { data: TestLiveState }
+
+export async function getTestLiveState(testId: number): Promise<TestLiveState> {
+ return (await request<TestLiveStateResponse>(`/system/test/${testId}/live-state`)).data
+}
+
+export type TestLiveEvent = 'snapshot' | 'device-status' | 'reception-status' | 'test-status' | 'exception' | 'heartbeat'
+
+/** 使用 fetch 流读取鉴权 SSE;调用返回函数关闭连接,断线由页面决定是否重连。 */
+export function openTestLiveStream(testId: number, onEvent: (event: TestLiveEvent, data: unknown) => void,
+ onError: (error: Error) => void): () => void {
+ const controller = new AbortController()
+ const token = getAuthToken()
+ void (async () => {
+ try {
+ const response = await fetch(`${import.meta.env.VITE_API_BASE_URL || '/dev-api'}/system/test/${testId}/live`, {
+ headers: token ? { Authorization: `Bearer ${token}`, Accept: 'text/event-stream' } : { Accept: 'text/event-stream' },
+ signal: controller.signal,
+ })
+ if (!response.ok || !response.body) throw new Error(response.status === 401 ? '实时流权限不足' : `实时流连接失败(${response.status})`)
+ const reader = response.body.getReader()
+ const decoder = new TextDecoder()
+ let buffer = ''
+ let eventName: TestLiveEvent = 'snapshot'
+ let data = ''
+ while (true) {
+ const chunk = await reader.read()
+ if (chunk.done) throw new Error('实时流已断开')
+ buffer += decoder.decode(chunk.value, { stream: true })
+ const lines = buffer.split(/\r?\n/)
+ buffer = lines.pop() || ''
+ for (const line of lines) {
+ if (line.startsWith('event:')) eventName = line.slice(6).trim() as TestLiveEvent
+ else if (line.startsWith('data:')) data += line.slice(5).trim()
+ else if (line === '' && data) {
+ try { onEvent(eventName, JSON.parse(data)) } catch { onError(new Error('实时流数据格式错误')) }
+ eventName = 'snapshot'
+ data = ''
+ }
+ }
+ }
+ } catch (error) {
+ if (!controller.signal.aborted) onError(error instanceof Error ? error : new Error('实时流连接失败'))
+ }
+ })()
+ return () => controller.abort()
+}
--
Gitblit v1.7.1