feat(health+miniprogram): 健康数据录入 + 趋势图
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

后端:
- 新增 GET /health/vital-signs/trend 小程序趋势查询 API
- 通过 JWT user_id 自动关联 patient,支持 range 参数 (7d/30d/90d)
- 新增 MiniTrendQueryParams, MiniTrendResp, DataPoint DTO

前端:
- 实现健康数据首页(今日概览 + 趋势入口 + 录入按钮)
- 实现健康数据录入页(指标选择 + 数值输入 + 提交)
- 实现趋势图页(时间范围切换 + 柱状图 + 数据列表)
- 新增 health service 和 store(趋势缓存 + 今日摘要)
- 修复所有页面相对路径引用问题
This commit is contained in:
iven
2026-04-24 00:36:30 +08:00
parent 0f84c881ef
commit affb3a5578
13 changed files with 714 additions and 15 deletions

View File

@@ -1,12 +1,67 @@
import { View, Text } from '@tarojs/components';
import Taro, { useDidShow } from '@tarojs/taro';
import { useHealthStore } from '../../stores/health';
import './index.scss';
export default function Health() {
const { todaySummary, loading, refreshToday } = useHealthStore();
useDidShow(() => {
refreshToday();
});
const goToInput = () => {
Taro.navigateTo({ url: '/pages/health/input/index' });
};
const goToTrend = (indicator: string) => {
Taro.navigateTo({ url: `/pages/health/trend/index?indicator=${indicator}` });
};
const summary = todaySummary || {};
const items = [
{ label: '血压', value: summary.blood_pressure ? `${summary.blood_pressure.systolic}/${summary.blood_pressure.diastolic}` : '--/--', unit: 'mmHg', indicator: 'blood_pressure_systolic', status: summary.blood_pressure?.status },
{ label: '心率', value: summary.heart_rate ? `${summary.heart_rate.value}` : '--', unit: 'bpm', indicator: 'heart_rate', status: summary.heart_rate?.status },
{ label: '血糖', value: summary.blood_sugar ? `${summary.blood_sugar.value}` : '--', unit: 'mmol/L', indicator: 'blood_sugar_fasting', status: summary.blood_sugar?.status },
{ label: '体重', value: summary.weight ? `${summary.weight.value}` : '--', unit: 'kg', indicator: 'weight', status: summary.weight?.status },
];
return (
<View className='placeholder-page'>
<Text className='placeholder-icon'>📊</Text>
<Text className='placeholder-title'></Text>
<Text className='placeholder-desc'></Text>
<View className='health-page'>
<View className='health-header'>
<Text className='health-header-title'></Text>
<View className='health-header-btn' onClick={goToInput}>
<Text className='health-header-btn-text'>+ </Text>
</View>
</View>
<View className='health-grid'>
{items.map((item) => (
<View className='health-card' key={item.label} onClick={() => goToTrend(item.indicator)}>
<Text className='health-card-label'>{item.label}</Text>
<Text className='health-card-value'>{item.value}</Text>
<View className='health-card-bottom'>
<Text className='health-card-unit'>{item.unit}</Text>
{item.status && <Text className='health-card-status'>{item.status}</Text>}
</View>
</View>
))}
</View>
<View className='health-actions'>
<View className='action-card' onClick={() => goToTrend('blood_pressure_systolic')}>
<Text className='action-icon'>📈</Text>
<Text className='action-label'></Text>
</View>
<View className='action-card' onClick={() => goToTrend('heart_rate')}>
<Text className='action-icon'></Text>
<Text className='action-label'></Text>
</View>
<View className='action-card' onClick={() => goToTrend('blood_sugar_fasting')}>
<Text className='action-icon'>🩸</Text>
<Text className='action-label'></Text>
</View>
</View>
</View>
);
}