diff --git a/apps/web/src/pages/health/components/VitalSignsChart.tsx b/apps/web/src/pages/health/components/VitalSignsChart.tsx index d6ddc21..575f289 100644 --- a/apps/web/src/pages/health/components/VitalSignsChart.tsx +++ b/apps/web/src/pages/health/components/VitalSignsChart.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import { Line } from '@ant-design/charts'; -import { Spin, Alert, Typography } from 'antd'; +import { Spin, Typography } from 'antd'; import { LineChartOutlined } from '@ant-design/icons'; import { healthDataApi } from '../../../api/health/healthData'; @@ -11,28 +11,111 @@ interface Props { refreshKey?: number; } -const DEFAULT_INDICATOR = 'systolic_bp_morning'; -const UNIT = 'mmHg'; -const LABEL = '收缩压(晨)'; +interface MetricConfig { + key: string; + label: string; + unit: string; + color: string; +} + +const METRICS: MetricConfig[] = [ + { key: 'systolic_bp_morning', label: '收缩压(晨)', unit: 'mmHg', color: '#ef4444' }, + { key: 'diastolic_bp_morning', label: '舒张压(晨)', unit: 'mmHg', color: '#f97316' }, + { key: 'heart_rate', label: '心率', unit: 'bpm', color: '#3b82f6' }, + { key: 'weight', label: '体重', unit: 'kg', color: '#10b981' }, + { key: 'blood_sugar', label: '血糖', unit: 'mmol/L', color: '#8b5cf6' }, +]; + +interface MetricData { + points: { date: string; value: number }[]; + latest: number | null; +} + +function extractData(res: unknown): { date: string; value: number }[] { + const raw = (res as { data?: { date: string; value: number }[] })?.data; + const arr = Array.isArray(res) ? res : Array.isArray(raw) ? raw : []; + return arr.filter((d) => d?.value != null); +} + +/** 单个指标迷你卡片 */ +function MetricCard({ metric, metricData }: { metric: MetricConfig; metricData: MetricData }) { + if (metricData.points.length === 0) { + return ( +
+ + 暂无{metric.label}数据 +
+ ); + } + + return ( +
+
+ {metric.label} + {metricData.latest != null && ( + + {metricData.latest} {metric.unit} + + )} +
+ +
+ ); +} export function VitalSignsChart({ patientId, refreshKey }: Props) { - const [data, setData] = useState<{ date: string; value: number }[]>([]); + const [metricsData, setMetricsData] = useState>({}); const [loading, setLoading] = useState(true); - const [error, setError] = useState(false); useEffect(() => { if (!patientId) return; setLoading(true); - setError(false); - healthDataApi - .getIndicatorTimeseries(patientId, DEFAULT_INDICATOR) - .then((res) => { - const raw = (res as { data?: { date: string; value: number }[] })?.data; - const points = Array.isArray(res) ? res : Array.isArray(raw) ? raw : []; - setData(points.filter((d) => d?.value != null)); - }) - .catch(() => setError(true)) - .finally(() => setLoading(false)); + + Promise.allSettled( + METRICS.map((m) => + healthDataApi + .getIndicatorTimeseries(patientId, m.key) + .then(extractData) + .then((points) => ({ + key: m.key, + data: { points, latest: points.length > 0 ? points[points.length - 1].value : null }, + })) + ) + ).then((results) => { + const map: Record = {}; + results.forEach((r, i) => { + if (r.status === 'fulfilled') { + map[r.value.key] = r.value.data; + } else { + map[METRICS[i].key] = { points: [], latest: null }; + } + }); + setMetricsData(map); + setLoading(false); + }); }, [patientId, refreshKey]); if (loading) { @@ -43,11 +126,8 @@ export function VitalSignsChart({ patientId, refreshKey }: Props) { ); } - if (error) { - return ; - } - - if (data.length === 0) { + const hasAnyData = Object.values(metricsData).some((d) => d.points.length > 0); + if (!hasAnyData) { return (
- 暂无{LABEL}趋势数据 + 暂无趋势数据
); } - const latestValue = data[data.length - 1].value; - return ( -
-
- {LABEL}趋势 - {latestValue != null && ( - - 最新:{latestValue} {UNIT} - - )} -
- +
+ {METRICS.map((m) => ( + + ))}
); }