diff --git a/apps/web/src/pages/health/components/VitalSignsChart.tsx b/apps/web/src/pages/health/components/VitalSignsChart.tsx
index 575f289..b92ff6b 100644
--- a/apps/web/src/pages/health/components/VitalSignsChart.tsx
+++ b/apps/web/src/pages/health/components/VitalSignsChart.tsx
@@ -1,8 +1,9 @@
import { useEffect, useState } from 'react';
import { Line } from '@ant-design/charts';
import { Spin, Typography } from 'antd';
-import { LineChartOutlined } from '@ant-design/icons';
+import { LineChartOutlined, CloseOutlined } from '@ant-design/icons';
import { healthDataApi } from '../../../api/health/healthData';
+import { useThemeMode } from '../../../hooks/useThemeMode';
const { Text } = Typography;
@@ -16,14 +17,15 @@ interface MetricConfig {
label: string;
unit: string;
color: string;
+ normalRange?: [number, number];
}
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: 'systolic_bp_morning', label: '收缩压(晨)', unit: 'mmHg', color: '#ef4444', normalRange: [90, 140] },
+ { key: 'diastolic_bp_morning', label: '舒张压(晨)', unit: 'mmHg', color: '#f97316', normalRange: [60, 90] },
+ { key: 'heart_rate', label: '心率', unit: 'bpm', color: '#3b82f6', normalRange: [60, 100] },
{ key: 'weight', label: '体重', unit: 'kg', color: '#10b981' },
- { key: 'blood_sugar', label: '血糖', unit: 'mmol/L', color: '#8b5cf6' },
+ { key: 'blood_sugar', label: '血糖', unit: 'mmol/L', color: '#8b5cf6', normalRange: [3.9, 6.1] },
];
interface MetricData {
@@ -37,51 +39,63 @@ function extractData(res: unknown): { date: string; value: number }[] {
return arr.filter((d) => d?.value != null);
}
-/** 单个指标迷你卡片 */
-function MetricCard({ metric, metricData }: { metric: MetricConfig; metricData: MetricData }) {
- if (metricData.points.length === 0) {
- return (
-
-
- 暂无{metric.label}数据
-
- );
- }
+const emptyData: MetricData = { points: [], latest: null };
+
+/** 概览卡片 — 指标名 + 最新值 + 微型趋势线 */
+function MetricCard({
+ metric,
+ metricData,
+ selected,
+ onClick,
+}: {
+ metric: MetricConfig;
+ metricData: MetricData;
+ selected: boolean;
+ onClick: () => void;
+}) {
+ const hasData = metricData.points.length > 0;
+ const cardStyle: React.CSSProperties = {
+ minWidth: 130,
+ padding: '8px 12px',
+ background: 'var(--ant-color-bg-container, #fafafa)',
+ borderRadius: 8,
+ border: selected
+ ? `2px solid ${metric.color}`
+ : '1px solid var(--ant-color-border-secondary, #f0f0f0)',
+ cursor: hasData ? 'pointer' : 'default',
+ transition: 'border-color 0.2s, box-shadow 0.2s',
+ flex: '0 0 auto',
+ ...(selected && hasData ? { boxShadow: `0 0 0 1px ${metric.color}22` } : {}),
+ };
return (
-
+
{metric.label}
{metricData.latest != null && (
- {metricData.latest} {metric.unit}
+ {metricData.latest}
+ {metric.unit}
)}
-
+ {hasData ? (
+
+ ) : (
+
+ 暂无数据
+
+ )}
);
}
@@ -89,6 +103,8 @@ function MetricCard({ metric, metricData }: { metric: MetricConfig; metricData:
export function VitalSignsChart({ patientId, refreshKey }: Props) {
const [metricsData, setMetricsData] = useState
>({});
const [loading, setLoading] = useState(true);
+ const [selectedKey, setSelectedKey] = useState(null);
+ const isDark = useThemeMode();
useEffect(() => {
if (!patientId) return;
@@ -110,7 +126,7 @@ export function VitalSignsChart({ patientId, refreshKey }: Props) {
if (r.status === 'fulfilled') {
map[r.value.key] = r.value.data;
} else {
- map[METRICS[i].key] = { points: [], latest: null };
+ map[METRICS[i].key] = emptyData;
}
});
setMetricsData(map);
@@ -120,7 +136,7 @@ export function VitalSignsChart({ patientId, refreshKey }: Props) {
if (loading) {
return (
-
+
);
@@ -144,15 +160,69 @@ export function VitalSignsChart({ patientId, refreshKey }: Props) {
);
}
+ const selectedMetric = METRICS.find((m) => m.key === selectedKey);
+ const selectedData = selectedKey ? metricsData[selectedKey] : null;
+ const axisLabelStyle = { fill: isDark ? '#94a3b8' : '#475569' };
+
return (
-
- {METRICS.map((m) => (
-
- ))}
+
+ {/* 概览卡片条 */}
+
+ {METRICS.map((m) => (
+ setSelectedKey((prev) => (prev === m.key ? null : m.key))}
+ />
+ ))}
+
+
+ {/* 详情图区域 */}
+ {selectedMetric && selectedData && selectedData.points.length > 0 && (
+
+
+
+ {selectedMetric.label}
+
+ {selectedData.latest}
+
+ {selectedMetric.unit}
+
+
setSelectedKey(null)}
+ style={{ cursor: 'pointer', padding: '2px 6px', borderRadius: 4, color: 'var(--ant-color-text-secondary)' }}
+ >
+
+
+
+
+
d.date,
+ items: [{ channel: 'y', valueFormatter: (v: number) => `${v} ${selectedMetric.unit}` }],
+ }}
+ />
+
+ )}
);
}