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) => (
+
+ ))}
);
}