feat(web): IoT + FHIR V1 Plan 5 — Web 前端实施
- API 层: deviceReadings 日聚合查询 + OAuth 合作方 CRUD 接口 - 常量: 设备连接状态/连接类型/实时监控指标常量 - Hook: useVitalSSE — 复用全局 SSE 连接的 vital_update 事件 - 页面: RealtimeMonitor 实时体征监控台 (SSE + 告警排序) - 页面: OAuthClientList FHIR 合作方管理 (CRUD + Secret 重置) - 增强: DeviceManage 设备状态/固件/连接类型列 + 状态筛选 - 路由: 新增 3 个懒加载路由 - 测试: RealtimeMonitor + OAuthClientList 单元测试
This commit is contained in:
@@ -22,6 +22,17 @@ export interface HourlyReading {
|
||||
sample_count: number;
|
||||
}
|
||||
|
||||
export interface DailyReading {
|
||||
id: string;
|
||||
device_type: string;
|
||||
date_bucket: string;
|
||||
min_val?: number;
|
||||
max_val?: number;
|
||||
avg_val: number;
|
||||
sample_count: number;
|
||||
percentile_95?: number;
|
||||
}
|
||||
|
||||
export interface BatchReadingRequest {
|
||||
device_id: string;
|
||||
device_model?: string;
|
||||
@@ -53,4 +64,9 @@ export const deviceReadingApi = {
|
||||
const { patient_id, ...query } = params;
|
||||
return client.get(`/health/patients/${patient_id}/device-readings/hourly`, { params: query }).then((r) => r.data.data as PaginatedResponse<HourlyReading>);
|
||||
},
|
||||
|
||||
queryDaily: (params: { patient_id: string; device_type?: string; from_date?: string; to_date?: string; page?: number; page_size?: number }) => {
|
||||
const { patient_id, ...query } = params;
|
||||
return client.get(`/health/patients/${patient_id}/device-readings/daily`, { params: query }).then((r) => r.data.data as PaginatedResponse<DailyReading>);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -8,6 +8,11 @@ export interface DeviceItem {
|
||||
device_id: string;
|
||||
device_model: string;
|
||||
device_type: string;
|
||||
status?: string;
|
||||
firmware_version?: string;
|
||||
manufacturer?: string;
|
||||
connection_type?: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
bound_at: string;
|
||||
last_sync_at: string;
|
||||
version: number;
|
||||
|
||||
73
apps/web/src/api/health/oauthClients.ts
Normal file
73
apps/web/src/api/health/oauthClients.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import client from '../client';
|
||||
|
||||
// --- Types ---
|
||||
export interface OAuthClient {
|
||||
id: string;
|
||||
client_id: string;
|
||||
client_name: string;
|
||||
scopes: string[];
|
||||
rate_limit_per_minute: number;
|
||||
is_active: boolean;
|
||||
token_lifetime_seconds: number;
|
||||
created_at: string;
|
||||
version: number;
|
||||
}
|
||||
|
||||
export interface OAuthClientDetail extends OAuthClient {
|
||||
tenant_id: string;
|
||||
client_secret: string;
|
||||
allowed_patient_ids?: string[];
|
||||
}
|
||||
|
||||
export interface CreateOAuthClientReq {
|
||||
client_name: string;
|
||||
scopes: string[];
|
||||
allowed_patient_ids?: string[];
|
||||
rate_limit_per_minute?: number;
|
||||
token_lifetime_seconds?: number;
|
||||
}
|
||||
|
||||
export interface UpdateOAuthClientReq {
|
||||
client_name?: string;
|
||||
scopes?: string[];
|
||||
allowed_patient_ids?: string[] | null;
|
||||
rate_limit_per_minute?: number;
|
||||
is_active?: boolean;
|
||||
token_lifetime_seconds?: number;
|
||||
version: number;
|
||||
}
|
||||
|
||||
export interface RegenerateSecretResp {
|
||||
client_id: string;
|
||||
client_secret: string;
|
||||
}
|
||||
|
||||
// --- FHIR Scope ---
|
||||
export const FHIR_SCOPE_OPTIONS = [
|
||||
{ value: 'Patient.read', label: 'Patient.read — 读取患者' },
|
||||
{ value: 'Observation.read', label: 'Observation.read — 读取体征' },
|
||||
{ value: 'Device.read', label: 'Device.read — 读取设备' },
|
||||
{ value: 'DiagnosticReport.read', label: 'DiagnosticReport.read — 读取诊断报告' },
|
||||
{ value: 'Encounter.read', label: 'Encounter.read — 读取就诊记录' },
|
||||
{ value: 'Practitioner.read', label: 'Practitioner.read — 读取医护' },
|
||||
{ value: 'Appointment.read', label: 'Appointment.read — 读取预约' },
|
||||
{ value: 'Task.read', label: 'Task.read — 读取随访任务' },
|
||||
];
|
||||
|
||||
// --- API ---
|
||||
export const oauthClientApi = {
|
||||
list: () =>
|
||||
client.get('/health/oauth/clients').then((r) => r.data.data as OAuthClient[]),
|
||||
|
||||
create: (data: CreateOAuthClientReq) =>
|
||||
client.post('/health/oauth/clients', data).then((r) => r.data.data as OAuthClientDetail),
|
||||
|
||||
update: (id: string, data: UpdateOAuthClientReq) =>
|
||||
client.put(`/health/oauth/clients/${id}`, data).then((r) => r.data.data as OAuthClient),
|
||||
|
||||
delete: (id: string) =>
|
||||
client.delete(`/health/oauth/clients/${id}`).then((r) => r.data),
|
||||
|
||||
regenerateSecret: (id: string) =>
|
||||
client.post(`/health/oauth/clients/${id}/regenerate-secret`).then((r) => r.data.data as RegenerateSecretResp),
|
||||
};
|
||||
Reference in New Issue
Block a user