feat(web): IoT + FHIR V1 Plan 5 — Web 前端实施
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

- API 层: deviceReadings 日聚合查询 + OAuth 合作方 CRUD 接口
- 常量: 设备连接状态/连接类型/实时监控指标常量
- Hook: useVitalSSE — 复用全局 SSE 连接的 vital_update 事件
- 页面: RealtimeMonitor 实时体征监控台 (SSE + 告警排序)
- 页面: OAuthClientList FHIR 合作方管理 (CRUD + Secret 重置)
- 增强: DeviceManage 设备状态/固件/连接类型列 + 状态筛选
- 路由: 新增 3 个懒加载路由
- 测试: RealtimeMonitor + OAuthClientList 单元测试
This commit is contained in:
iven
2026-05-04 02:40:57 +08:00
parent 24562dd54b
commit 70aacf47a0
11 changed files with 668 additions and 3 deletions

View File

@@ -0,0 +1,67 @@
import { useState, useCallback } from 'react';
import { useAlertSSE, type VitalUpdateSSEEvent } from './useAlertSSE';
export type VitalUpdateEvent = VitalUpdateSSEEvent;
interface PatientVital {
patient_id: string;
device_type: string;
latest_value?: number;
updated_at: string;
}
interface UseVitalSSEOptions {
enabled?: boolean;
patientIds?: string[];
onUpdate?: (data: VitalUpdateEvent) => void;
}
interface UseVitalSSEReturn {
connected: boolean;
patientVitals: Map<string, PatientVital>;
lastUpdate: VitalUpdateEvent | null;
}
/**
* 实时体征 hook — 复用全局 SSE 连接的 vital_update 事件。
*
* 内部调用 useAlertSSE共享 /messages/stream 连接),
* 聚合患者最新体征数据到 Map。
*/
export function useVitalSSE(options: UseVitalSSEOptions = {}): UseVitalSSEReturn {
const { enabled = true, patientIds, onUpdate } = options;
const [patientVitals, setPatientVitals] = useState<Map<string, PatientVital>>(new Map());
const [lastUpdate, setLastUpdate] = useState<VitalUpdateEvent | null>(null);
const handleVitalUpdate = useCallback(
(data: VitalUpdateEvent) => {
if (patientIds && patientIds.length > 0 && !patientIds.includes(data.patient_id)) {
return;
}
setPatientVitals((prev) => {
const next = new Map(prev);
if (data.device_model) {
const key = `${data.patient_id}_${data.device_model}`;
next.set(key, {
patient_id: data.patient_id,
device_type: data.device_model,
latest_value: data.count > 0 ? undefined : undefined,
updated_at: data.occurred_at ?? new Date().toISOString(),
});
}
return next;
});
setLastUpdate(data);
onUpdate?.(data);
},
[patientIds, onUpdate],
);
const { connected } = useAlertSSE({
enabled,
onVitalUpdate: handleVitalUpdate,
});
return { connected, patientVitals, lastUpdate };
}