src/api/tests.ts
@@ -135,7 +135,7 @@
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: '检测完成',
@@ -144,7 +144,7 @@
  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: '-',
@@ -220,6 +220,10 @@
    unit?: string
    observedAt?: number
    sampleIntervalMs?: number
    /** 通道工作状态;读库来源下为 UNOBSERVABLE,界面按“无法观测”展示。 */
    workStatus?: string
    /** 来源里最近一次采样行的设备时间(毫秒);没有采样行时为空。 */
    lastDataAt?: number | null
    measurements?: Array<Record<string, unknown>>
    units?: string[]
    [key: string]: unknown
@@ -261,72 +265,9 @@
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> {
@@ -352,6 +293,7 @@
  stepType?: string
  sequenceId: number
  cabinetSequenceId?: number | null
  relativeTimeSeconds?: number | string | null
  recordedAt?: number | null
  absoluteTime?: number | null
  deviceTime?: number | null
@@ -381,8 +323,8 @@
  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 {
@@ -391,8 +333,11 @@
  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 : []
@@ -412,9 +357,10 @@
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);
@@ -428,7 +374,7 @@
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()