import { useState, useCallback, useEffect } from 'react'; import { Card, Row, Col, Statistic, Tag, List, Select, Badge, Typography, Space, Empty, Result } from 'antd'; import { AlertOutlined } from '@ant-design/icons'; import { useVitalSSE } from '../../hooks/useVitalSSE'; import { usePermission } from '../../hooks/usePermission'; import { alertApi, type Alert } from '../../api/health/alerts'; import { PageContainer } from '../../components/PageContainer'; import { SEVERITY_COLOR, SEVERITY_LABEL, VITAL_CARD_METRICS } from '../../constants/health'; interface PatientAlertSummary { patient_id: string; critical: number; high: number; medium: number; low: number; } /** * 实时体征监控台 — 医生 Web 端。 * * SSE 实时接收体征更新,按告警严重度排序患者列表。 */ export default function RealtimeMonitor() { const { hasPermission } = usePermission('health.alerts.list'); const [alerts, setAlerts] = useState([]); const [loading, setLoading] = useState(true); const [selectedPatientId, setSelectedPatientId] = useState(null); const [alertSummary, setAlertSummary] = useState([]); const { connected, patientVitals } = useVitalSSE({ enabled: true }); const fetchAlerts = useCallback(async () => { try { setLoading(true); const result = await alertApi.list({ page: 1, page_size: 100, status: 'pending' }); setAlerts(result.data); const summaryMap = new Map(); for (const alert of result.data) { const existing = summaryMap.get(alert.patient_id) ?? { patient_id: alert.patient_id, critical: 0, high: 0, medium: 0, low: 0, }; const sev = alert.severity; if (sev === 'critical') existing.critical++; else if (sev === 'high') existing.high++; else if (sev === 'medium') existing.medium++; else existing.low++; summaryMap.set(alert.patient_id, existing); } setAlertSummary(Array.from(summaryMap.values())); } catch { // 静默降级 } finally { setLoading(false); } }, []); useEffect(() => { fetchAlerts(); }, [fetchAlerts]); const sortedPatients = [...alertSummary].sort((a, b) => { const score = (s: PatientAlertSummary) => s.critical * 4 + s.high * 3 + s.medium * 2 + s.low; return score(b) - score(a); }); const totalCritical = alertSummary.reduce((s, a) => s + a.critical, 0); const totalHigh = alertSummary.reduce((s, a) => s + a.high, 0); const totalMedium = alertSummary.reduce((s, a) => s + a.medium, 0); const totalLow = alertSummary.reduce((s, a) => s + a.low, 0); if (!hasPermission) { return ; } return (