import { useState, useEffect } from 'react'; import { View, Text, Input } from '@tarojs/components'; import Taro, { useDidShow } from '@tarojs/taro'; import { useHealthStore } from '../../stores/health'; import { useAuthStore } from '../../stores/auth'; import { inputVitalSign, getTrend } from '../../services/health'; import { listPendingSuggestions, type AiSuggestionItem } from '../../services/ai-analysis'; import Loading from '../../components/Loading'; import './index.scss'; type VitalType = 'blood_pressure' | 'heart_rate' | 'blood_sugar' | 'weight'; const VITAL_TABS: { key: VitalType; label: string }[] = [ { key: 'blood_pressure', label: '血压' }, { key: 'heart_rate', label: '心率' }, { key: 'blood_sugar', label: '血糖' }, { key: 'weight', label: '体重' }, ]; const REF_RANGES: Record = { blood_pressure: { range: '收缩压 90-140 / 舒张压 60-90 mmHg', warn: '血压偏高,确认提交?' }, heart_rate: { range: '60-100 bpm', warn: '心率异常,确认提交?' }, blood_sugar: { range: '空腹 3.9-6.1 / 餐后 <7.8 mmol/L', warn: '血糖偏高,确认提交?' }, weight: { range: '根据 BMI 18.5-24 计算', warn: '' }, }; interface TrendPoint { date: string; value: number; } export default function Health() { const { todaySummary, loading, refreshToday, getTrend: fetchTrend } = useHealthStore(); const { currentPatient } = useAuthStore(); const [activeTab, setActiveTab] = useState('blood_pressure'); const [systolic, setSystolic] = useState(''); const [diastolic, setDiastolic] = useState(''); const [heartRateVal, setHeartRateVal] = useState(''); const [sugarVal, setSugarVal] = useState(''); const [sugarPeriod, setSugarPeriod] = useState<'fasting' | 'postprandial'>('fasting'); const [weightVal, setWeightVal] = useState(''); const [saving, setSaving] = useState(false); const [trendData, setTrendData] = useState([]); const [trendLoading, setTrendLoading] = useState(false); const [aiSuggestions, setAiSuggestions] = useState([]); useDidShow(() => { refreshToday(); loadTrend(activeTab); loadAiSuggestions(); }); const loadAiSuggestions = async () => { try { const items = await listPendingSuggestions(); setAiSuggestions(items.slice(0, 3)); } catch { // 静默 } }; const loadTrend = async (type: VitalType) => { setTrendLoading(true); try { const indicatorMap: Record = { blood_pressure: 'blood_pressure_systolic', heart_rate: 'heart_rate', blood_sugar: 'blood_sugar_fasting', weight: 'weight', }; const points = await fetchTrend(indicatorMap[type], '7d'); setTrendData(points); } catch { setTrendData([]); } finally { setTrendLoading(false); } }; const handleTabChange = (tab: VitalType) => { setActiveTab(tab); loadTrend(tab); }; const getWarnStatus = (type: VitalType): string | null => { if (type === 'blood_pressure') { const sys = parseFloat(systolic); const dia = parseFloat(diastolic); if (sys > 140 || dia > 90) return REF_RANGES.blood_pressure.warn; } else if (type === 'heart_rate') { const val = parseFloat(heartRateVal); if (val > 100 || val < 60) return REF_RANGES.heart_rate.warn; } else if (type === 'blood_sugar') { const val = parseFloat(sugarVal); if (sugarPeriod === 'fasting' && val > 6.1) return REF_RANGES.blood_sugar.warn; if (sugarPeriod === 'postprandial' && val > 7.8) return REF_RANGES.blood_sugar.warn; } return null; }; const handleSave = async () => { const patientId = currentPatient?.id; if (!patientId) { Taro.showToast({ title: '请先登录', icon: 'none' }); return; } const warnMsg = getWarnStatus(activeTab); if (warnMsg) { const { confirm } = await Taro.showModal({ title: '异常提示', content: warnMsg, confirmText: '确认提交', cancelText: '再看看', }); if (!confirm) return; } setSaving(true); try { switch (activeTab) { case 'blood_pressure': { const sys = parseFloat(systolic); const dia = parseFloat(diastolic); if (!sys || !dia) { Taro.showToast({ title: '请填写完整', icon: 'none' }); return; } await inputVitalSign(patientId, { indicator_type: 'blood_pressure', value: sys, extra: { systolic: sys, diastolic: dia }, }); setSystolic(''); setDiastolic(''); break; } case 'heart_rate': { const val = parseFloat(heartRateVal); if (!val) { Taro.showToast({ title: '请填写心率', icon: 'none' }); return; } await inputVitalSign(patientId, { indicator_type: 'heart_rate', value: val }); setHeartRateVal(''); break; } case 'blood_sugar': { const val = parseFloat(sugarVal); if (!val) { Taro.showToast({ title: '请填写血糖值', icon: 'none' }); return; } await inputVitalSign(patientId, { indicator_type: 'blood_sugar', value: val }); setSugarVal(''); break; } case 'weight': { const val = parseFloat(weightVal); if (!val) { Taro.showToast({ title: '请填写体重', icon: 'none' }); return; } await inputVitalSign(patientId, { indicator_type: 'weight', value: val }); setWeightVal(''); break; } } Taro.showToast({ title: '保存成功', icon: 'success' }); refreshToday(true); loadTrend(activeTab); } catch { Taro.showToast({ title: '保存失败', icon: 'none' }); } finally { setSaving(false); } }; const maxTrendValue = Math.max(...trendData.map((d) => d.value), 1); const dayLabels = ['日', '一', '二', '三', '四', '五', '六']; return ( {/* 页头 */} 健康数据 {/* AI 建议卡片 */} {aiSuggestions.length > 0 && ( { const first = aiSuggestions[0]; if (first?.suggestion_type === 'appointment') { Taro.navigateTo({ url: `/pages/pkg-appointment/create/index?patientId=${first.patient_id}` }); } else if (first?.suggestion_type === 'followup') { Taro.navigateTo({ url: '/pages/pkg-profile/followups/index' }); } else { Taro.navigateTo({ url: '/pages/health/index' }); } }}> AI 健康建议 {aiSuggestions.length} 条待查看 {aiSuggestions.map((s) => { const riskColor = s.risk_level === 'high' ? '#ef4444' : s.risk_level === 'medium' ? '#f59e0b' : '#22c55e'; const typeLabel = s.suggestion_type === 'followup' ? '随访' : s.suggestion_type === 'appointment' ? '预约' : '预警'; const params = s.params as Record | null; const reason = (params?.reason as string) || (params?.message as string) || typeLabel; return ( {reason.slice(0, 40)} ); })} )} {/* 类型 Tab */} {VITAL_TABS.map((tab) => { const hasData = tab.key === 'blood_pressure' ? !!todaySummary?.blood_pressure : tab.key === 'heart_rate' ? !!todaySummary?.heart_rate : tab.key === 'blood_sugar' ? !!todaySummary?.blood_sugar : !!todaySummary?.weight; return ( handleTabChange(tab.key)} > {tab.label} {!hasData && } ); })} {/* 录入区 */} {activeTab === 'blood_pressure' && ( 收缩压(高压) setSystolic(e.detail.value)} /> 舒张压(低压) setDiastolic(e.detail.value)} /> {REF_RANGES.blood_pressure.range} )} {activeTab === 'heart_rate' && ( 心率 setHeartRateVal(e.detail.value)} /> {REF_RANGES.heart_rate.range} )} {activeTab === 'blood_sugar' && ( 血糖值 setSugarVal(e.detail.value)} /> setSugarPeriod('fasting')} > 空腹 setSugarPeriod('postprandial')} > 餐后 2h {REF_RANGES.blood_sugar.range} )} {activeTab === 'weight' && ( 体重 (kg) setWeightVal(e.detail.value)} /> {REF_RANGES.weight.range} )} {saving ? '保存中...' : '保存'} {/* 趋势图 */} 近 7 天趋势 {trendLoading ? ( ) : trendData.length === 0 ? ( 暂无趋势数据 ) : ( {trendData.map((point, i) => { const heightPct = Math.max(8, (point.value / maxTrendValue) * 100); const isAbnormal = activeTab === 'blood_pressure' ? point.value > 140 : activeTab === 'heart_rate' ? (point.value > 100 || point.value < 60) : activeTab === 'blood_sugar' ? point.value > 6.1 : false; const dayOfWeek = new Date(point.date).getDay(); return ( {dayLabels[dayOfWeek]} ); })} )} {/* BLE 设备卡片 */} Taro.navigateTo({ url: '/pages/device-sync/index' })} > 蓝牙设备 连接设备自动同步数据 {/* 健康资讯入口 */} Taro.navigateTo({ url: '/pages/article/index' })} > 最新健康资讯 › ); }