feat(web): 体征数据页面 UI/UX 优化 — 消除空白+信息密度提升
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

VitalSignsChart:
- 空状态改为带图标的虚线提示区域(替代 Empty 组件大片空白)
- 图表高度约束 180px,加载状态居中显示
- 添加最新值摘要显示
- 头部选择器与摘要信息并排布局

VitalSignsTab:
- 添加最新记录摘要条(体征数据一览)
- 表格上方显示记录总数
- 表头列名带 Tooltip 说明
- 录入按钮改为 small size,节省空间
- 表格添加 scroll.x 防止列溢出
This commit is contained in:
iven
2026-04-26 08:08:05 +08:00
parent c76371fbdc
commit 6c60be0047
2 changed files with 201 additions and 28 deletions

View File

@@ -1,8 +1,11 @@
import { useEffect, useState } from 'react';
import { Line } from '@ant-design/charts';
import { Spin, Empty, Select, Space, Alert } from 'antd';
import { Spin, Select, Alert, Typography } from 'antd';
import { LineChartOutlined } from '@ant-design/icons';
import { healthDataApi } from '../../../api/health/healthData';
const { Text } = Typography;
interface Props {
patientId: string;
indicator?: string;
@@ -16,6 +19,14 @@ const INDICATORS = [
{ value: 'blood_sugar', label: '血糖' },
];
const INDICATOR_UNITS: Record<string, string> = {
systolic_bp_morning: 'mmHg',
diastolic_bp_morning: 'mmHg',
heart_rate: 'bpm',
weight: 'kg',
blood_sugar: 'mmol/L',
};
export function VitalSignsChart({ patientId, indicator: initialIndicator }: Props) {
const [indicator, setIndicator] = useState(initialIndicator ?? 'systolic_bp_morning');
const [data, setData] = useState<{ date: string; value: number }[]>([]);
@@ -33,30 +44,93 @@ export function VitalSignsChart({ patientId, indicator: initialIndicator }: Prop
.finally(() => setLoading(false));
}, [patientId, indicator]);
if (loading) return <Spin />;
if (error) return <Alert type="error" message="加载数据失败,请稍后重试" />;
if (data.length === 0) return <Empty description="暂无数据" />;
const unit = INDICATOR_UNITS[indicator] ?? '';
// 头部:选择器 + 最新值摘要
const latestValue = data.length > 0 ? data[data.length - 1].value : null;
const renderHeader = () => (
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 12,
}}>
<Select
value={indicator}
onChange={setIndicator}
options={INDICATORS}
style={{ width: 160 }}
size="small"
/>
{latestValue != null && (
<Text type="secondary" style={{ fontSize: 13 }}>
{latestValue} {unit}
</Text>
)}
</div>
);
if (loading) {
return (
<div style={{ height: 180, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<Spin size="small" />
</div>
);
}
if (error) {
return (
<div>
{renderHeader()}
<Alert type="error" message="加载数据失败" showIcon style={{ borderRadius: 8 }} />
</div>
);
}
if (data.length === 0) {
return (
<div>
{renderHeader()}
<div style={{
height: 120,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
background: 'var(--ant-color-bg-container, #fafafa)',
borderRadius: 8,
border: '1px dashed var(--ant-color-border, #d9d9d9)',
}}>
<LineChartOutlined style={{ fontSize: 24, color: '#bfbfbf', marginBottom: 4 }} />
<Text type="secondary" style={{ fontSize: 13 }}></Text>
</div>
</div>
);
}
const config = {
data,
xField: 'date',
yField: 'value',
smooth: true,
point: { shapeField: 'circle', sizeField: 4 },
height: 220,
height: 180,
point: { shapeField: 'circle', sizeField: 3 },
axis: {
x: { labelAutoRotate: false },
y: { title: unit || undefined },
},
style: {
lineWidth: 2,
},
};
return (
<Space direction="vertical" style={{ width: '100%' }}>
<Select
value={indicator}
onChange={setIndicator}
options={INDICATORS}
style={{ width: 180 }}
/>
<div>
{renderHeader()}
<div style={{ width: '100%' }}>
<Line {...config} />
</div>
</Space>
</div>
);
}