import { useState } from 'react'; import { View, Text, Input } from '@tarojs/components'; import Taro from '@tarojs/taro'; import { safeNavigateTo } from '@/utils/navigate'; import { useAuthStore } from '../../stores/auth'; import { useElderClass } from '../../hooks/useElderClass'; import { findThreshold, inputVitalSign, type HealthThreshold } from '../../services/health'; import Loading from '../../components/Loading'; import ErrorState from '../../components/ErrorState'; import GuestGuard from '../../components/GuestGuard'; import SegmentTabs from '../../components/SegmentTabs'; import PageShell from '@/components/ui/PageShell'; import ContentCard from '@/components/ui/ContentCard'; import { useHealthData, VITAL_TABS, type VitalType } from './useHealthData'; import { submitSuggestionFeedback } from '../../services/ai-analysis'; import './index.scss'; function buildRefRange(t: HealthThreshold[]): Record { const bpSys = findThreshold(t, 'systolic_bp', 'high')?.threshold_value ?? 140; const bpDia = findThreshold(t, 'diastolic_bp', 'high')?.threshold_value ?? 90; const hrHigh = findThreshold(t, 'heart_rate', 'high')?.threshold_value ?? 100; const hrLow = findThreshold(t, 'heart_rate', 'low')?.threshold_value ?? 60; const bsFasting = findThreshold(t, 'blood_sugar_fasting', 'high')?.threshold_value ?? 6.1; const bsPp = findThreshold(t, 'blood_sugar_postprandial', 'high')?.threshold_value ?? 7.8; return { blood_pressure: `收缩压 90-${bpSys} / 舒张压 60-${bpDia} mmHg`, heart_rate: `${hrLow}-${hrHigh} bpm`, blood_sugar: `空腹 3.9-${bsFasting} / 餐后 <${bsPp} mmol/L`, weight: '根据 BMI 18.5-24 计算', }; } export default function Health() { const currentPatient = useAuthStore((s) => s.currentPatient); const modeClass = useElderClass(); const { user, todaySummary, loading, error, activeTab, trendData, trendLoading, aiSuggestions, thresholds, handleTabChange, loadTrend, refreshToday, fetchData, } = useHealthData(); 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); if (!user) { return ; } if (error) { return ( 健康数据 ); } const getWarnStatus = (type: VitalType): string | null => { if (type === 'blood_pressure') { const sys = parseFloat(systolic); const dia = parseFloat(diastolic); const sysMax = findThreshold(thresholds, 'systolic_bp', 'high')?.threshold_value ?? 140; const diaMax = findThreshold(thresholds, 'diastolic_bp', 'high')?.threshold_value ?? 90; if (sys > sysMax || dia > diaMax) return '血压偏高,确认提交?'; } else if (type === 'heart_rate') { const val = parseFloat(heartRateVal); const hrHigh = findThreshold(thresholds, 'heart_rate', 'high')?.threshold_value ?? 100; const hrLow = findThreshold(thresholds, 'heart_rate', 'low')?.threshold_value ?? 60; if (val > hrHigh || val < hrLow) return '心率异常,确认提交?'; } else if (type === 'blood_sugar') { const val = parseFloat(sugarVal); if (sugarPeriod === 'fasting') { const bsMax = findThreshold(thresholds, 'blood_sugar_fasting', 'high')?.threshold_value ?? 6.1; if (val > bsMax) return '血糖偏高,确认提交?'; } else { const bsMax = findThreshold(thresholds, 'blood_sugar_postprandial', 'high')?.threshold_value ?? 7.8; if (val > bsMax) return '血糖偏高,确认提交?'; } } return null; }; const refRanges = buildRefRange(thresholds); 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; } const bsType = sugarPeriod === 'fasting' ? 'blood_sugar_fasting' : 'blood_sugar_postprandial'; await inputVitalSign(patientId, { indicator_type: bsType, 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 = trendData.reduce((max, d) => Math.max(max, d.value), 1); const getThresholdValue = (type: VitalType, th: HealthThreshold[]): number | null => { if (type === 'blood_pressure') return findThreshold(th, 'systolic_bp', 'high')?.threshold_value ?? 140; if (type === 'heart_rate') return findThreshold(th, 'heart_rate', 'high')?.threshold_value ?? 100; if (type === 'blood_sugar') return findThreshold(th, 'blood_sugar_fasting', 'high')?.threshold_value ?? 6.1; return null; }; const dayLabels = ['日', '一', '二', '三', '四', '五', '六']; return ( 健康数据 {aiSuggestions.length > 0 && ( AI 健康建议 {aiSuggestions.length} 条待查看 {aiSuggestions.map((s) => { const riskCls = s.risk_level === 'high' ? 'ai-risk-high' : s.risk_level === 'medium' ? 'ai-risk-medium' : 'ai-risk-low'; 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 ( { if (s.suggestion_type === 'appointment') { safeNavigateTo(`/pages/appointment/create/index`); } else if (s.suggestion_type === 'followup') { safeNavigateTo('/pages/pkg-profile/followups/index'); } }}> {reason.slice(0, 40)} { try { await submitSuggestionFeedback(s.id, 'adopt'); Taro.showToast({ title: '已采纳', icon: 'success' }); fetchData(); } catch { Taro.showToast({ title: '操作失败', icon: 'none' }); } }}> 采纳 { try { await submitSuggestionFeedback(s.id, 'ignore'); Taro.showToast({ title: '已忽略', icon: 'success' }); fetchData(); } catch { Taro.showToast({ title: '操作失败', icon: 'none' }); } }}> 忽略 { try { await submitSuggestionFeedback(s.id, 'consult'); safeNavigateTo('/pages/consultation/index'); } catch { Taro.showToast({ title: '操作失败', icon: 'none' }); } }}> 咨询医生 ); })} )} {activeTab === 'blood_pressure' && ( 收缩压(高压) setSystolic(e.detail.value)} /> 舒张压(低压) setDiastolic(e.detail.value)} /> {refRanges.blood_pressure} )} {activeTab === 'heart_rate' && ( 心率 setHeartRateVal(e.detail.value)} /> {refRanges.heart_rate} )} {activeTab === 'blood_sugar' && ( 血糖值 setSugarVal(e.detail.value)} /> setSugarPeriod('fasting')} > 空腹 setSugarPeriod('postprandial')} > 餐后 2h {refRanges.blood_sugar} )} {activeTab === 'weight' && ( 体重 (kg) setWeightVal(e.detail.value)} /> {refRanges.weight} )} {saving ? '保存中...' : '保存'} 近 7 天趋势 {trendLoading ? ( ) : trendData.length === 0 ? ( 暂无趋势数据 ) : ( {getThresholdValue(activeTab, thresholds) && (() => { const tv = getThresholdValue(activeTab, thresholds)!; const pct = Math.min(95, (tv / maxTrendValue) * 100); return ( {tv} ); })()} {trendData.map((point, i) => { const heightPct = Math.max(8, (point.value / maxTrendValue) * 100); const tv = getThresholdValue(activeTab, thresholds); const isAbnormal = tv ? point.value >= tv : false; const dayOfWeek = new Date(point.date).getDay(); return ( {dayLabels[dayOfWeek]} ); })} )} safeNavigateTo('/pages/article/index')} > 最新健康资讯 › ); }