| | |
| | | interface TestOptionsResponse extends ApiResponse { data: TestConfigurationOptions } |
| | | export interface TestListResult { total: number; rows: TestRecord[] } |
| | | |
| | | const statusMap: Record<string, TestStatus> = { |
| | | export const testStatusLabels: Record<string, TestStatus> = { |
| | | WAITING: '待测试', RUNNING: '检测中', PAUSED: '暂停', COMPLETED: '检测完成', |
| | | // 历史数据只作兼容展示,不会由新流程写入。 |
| | | RUNNING_ABNORMAL: '检测中', COMPLETION_PENDING: '检测完成', TERMINATED: '检测完成', |
| | |
| | | NOT_STARTED: '未开始接收', RECEIVING: '接收中', PAUSED: '暂停接收', ENDED: '已结束接收', |
| | | } |
| | | function mapTest(item: TestDto): TestRecord { |
| | | const status = item.testStatus ? statusMap[item.testStatus] : '未知' |
| | | const status = item.testStatus ? testStatusLabels[item.testStatus] : '未知' |
| | | const receptionStatus = item.receptionStatus ? receptionStatusMap[item.receptionStatus] : '未知' |
| | | return { |
| | | id: item.testId, name: item.testName || '-', type: item.testTypeName || '-', batteryNo: item.batteryNo || '-', batteryGroup: '-', |
| | |
| | | unit?: string |
| | | observedAt?: number |
| | | sampleIntervalMs?: number |
| | | /** 通道工作状态;读库来源下为 UNOBSERVABLE,界面按“无法观测”展示。 */ |
| | | workStatus?: string |
| | | /** 来源里最近一次采样行的设备时间(毫秒);没有采样行时为空。 */ |
| | | lastDataAt?: number | null |
| | | measurements?: Array<Record<string, unknown>> |
| | | units?: string[] |
| | | [key: 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 interface BtsRecord { |
| | | sequenceId: number |
| | | deviceTime: string |
| | | currentA: number |
| | | voltageV: number |
| | | capacityAh: number |
| | | energyWh: number |
| | | cycleNo: number |
| | | stepNo: number |
| | | stepName: string |
| | | stepType: string |
| | | } |
| | | |
| | | export interface BtsStepRecord { |
| | | cycleNo: number |
| | | stepNo: number |
| | | cyclePosition: number |
| | | stepName: string |
| | | stepType: string |
| | | recordCount: number |
| | | startedAt: string |
| | | endedAt: string |
| | | capacityAh: number |
| | | energyWh: number |
| | | durationSeconds: number |
| | | startVoltageV: number |
| | | endVoltageV: number |
| | | chargePeakVoltageV: number |
| | | dischargeValleyVoltageV: number |
| | | } |
| | | |
| | | export interface BtsCycleRecord { |
| | | recordNo: string |
| | | cycleNo: number |
| | | recordCount: number |
| | | startedAt: string |
| | | endedAt: string |
| | | chargeCapacityAh: number |
| | | dischargeCapacityAh: number |
| | | chargeEnergyWh: number |
| | | dischargeEnergyWh: number |
| | | efficiency: number |
| | | } |
| | | |
| | | async function getSimulatorChannelData<T>(deviceId: number, channelId: number, kind: 'records' | 'steps' | 'cycles'): Promise<T[]> { |
| | | const token = getAuthToken() |
| | | const response = await fetch(`${import.meta.env.VITE_API_BASE_URL || '/dev-api'}/dev/bts-simulator/devices/${deviceId}/channels/${channelId}/${kind}`, { headers: token ? { Authorization: `Bearer ${token}` } : undefined }) |
| | | if (!response.ok) throw new Error(`检测柜${kind}数据加载失败(${response.status})`) |
| | | const result = await response.json() as T[] |
| | | return Array.isArray(result) ? result : [] |
| | | } |
| | | |
| | | export function getBtsRecords(deviceId: number, channelId: number): Promise<BtsRecord[]> { |
| | | return getSimulatorChannelData<BtsRecord>(deviceId, channelId, 'records') |
| | | } |
| | | |
| | | export function getBtsSteps(deviceId: number, channelId: number): Promise<BtsStepRecord[]> { |
| | | return getSimulatorChannelData<BtsStepRecord>(deviceId, channelId, 'steps') |
| | | } |
| | | |
| | | export function getBtsCycles(deviceId: number, channelId: number): Promise<BtsCycleRecord[]> { |
| | | return getSimulatorChannelData<BtsCycleRecord>(deviceId, channelId, 'cycles') |
| | | } |
| | | |
| | | export async function saveTestConsoleDetail(testId: number, detail: TestConsoleDetail): Promise<TestConfigurationDetail> { |
| | |
| | | stepType?: string |
| | | sequenceId: number |
| | | cabinetSequenceId?: number | null |
| | | relativeTimeSeconds?: number | string | null |
| | | recordedAt?: number | null |
| | | absoluteTime?: number | null |
| | | deviceTime?: number | null |
| | |
| | | stepDuration: number | string | null |
| | | startVoltage: number | string | null |
| | | endVoltage: number | string | null |
| | | chargePeakVoltage: number | string | null |
| | | dischargeValleyVoltage: number | string | null |
| | | chargeMedianVoltage: number | string | null |
| | | dischargeMedianVoltage: number | string | null |
| | | } |
| | | |
| | | export interface PersistedTestCycle extends PersistedTestRecord { |
| | |
| | | efficiency: number | string | null |
| | | chargeEnergy: number | string | null |
| | | dischargeEnergy: number | string | null |
| | | /** 来源类别:DEVICE 设备提供、AGGREGATED 本系统按工步层聚合、IMPORTED 授权文件导入。 */ |
| | | sourceKind?: string | null |
| | | } |
| | | |
| | | // 三子页和图表查询持久化当前有效数据,不回放实时监控或触发预警判定。 |
| | | async function getPersistedTestData<T>(testId: number, kind: 'records' | 'steps' | 'cycles'): Promise<T[]> { |
| | | const result = await request<ApiResponse & { data?: T[] }>(`/system/test/${testId}/data/${kind}`) |
| | | return Array.isArray(result.data) ? result.data : [] |
| | |
| | | |
| | | export interface WarningExceptionRecord { |
| | | exceptionId: number; testId: number; testName?: string; sourceType: string; sourceIdentity: string; |
| | | metricCode: string; abnormalValue: number; unit: string; ruleSnapshot: string; severity: string; |
| | | exceptionStatus: 'OPEN' | 'CLOSED'; firstOccurredAtUtc: string; lastOccurredAtUtc: string; |
| | | occurrenceCount: number; handledByName?: string; handledAtUtc?: string; handlingNote?: string; |
| | | exceptionType: 'THRESHOLD' | 'DEVICE_OFFLINE'; |
| | | metricCode: string; abnormalValue: number | null; unit: string; ruleSnapshot: string; severity: string; |
| | | exceptionStatus: 'OPEN' | 'CLOSED'; firstOccurredAtUtc: number; lastOccurredAtUtc: number; |
| | | occurrenceCount: number; handledByName?: string; handledAtUtc?: number | null; handlingNote?: string; |
| | | } |
| | | export async function getWarningExceptions(params: { testId?: number; status?: string } = {}): Promise<WarningExceptionRecord[]> { |
| | | const query = new URLSearchParams(); if (params.testId) query.set('testId', String(params.testId)); if (params.status) query.set('status', params.status); |
| | |
| | | |
| | | export type TestLiveEvent = 'snapshot' | 'device-status' | 'reception-status' | 'test-status' | 'exception' | 'heartbeat' |
| | | |
| | | /** 使用 fetch 流读取鉴权 SSE;调用返回函数关闭连接,断线由页面决定是否重连。 */ |
| | | /** 订阅实时监控观测与提醒(接收中后端约 5 秒观测);使用鉴权 SSE,调用返回函数关闭连接。 */ |
| | | export function openTestLiveStream(testId: number, onEvent: (event: TestLiveEvent, data: unknown) => void, |
| | | onError: (error: Error) => void): () => void { |
| | | const controller = new AbortController() |