import { useState, useCallback } from 'react'; import { View, Text, Input, Picker } from '@tarojs/components'; import Taro from '@tarojs/taro'; import { safeNavigateTo } from '@/utils/navigate'; import { usePageData } from '@/hooks/usePageData'; import { num, validateStr } from '@/utils/validate'; import { inputVitalSign, getHealthThresholds, findThreshold, DEFAULT_THRESHOLDS, type HealthThreshold } from '../../../services/health'; import { useAuthStore } from '../../../stores/auth'; import { useHealthStore } from '@/stores/health'; import { usePointsStore } from '@/stores/points'; import { clearRequestCache } from '@/services/request'; import { useSafeTimeout } from '@/hooks/useSafeTimeout'; import { trackEvent } from '@/services/analytics'; import { useElderClass } from '../../../hooks/useElderClass'; import Loading from '../../../components/Loading'; import PageShell from '@/components/ui/PageShell'; import ContentCard from '@/components/ui/ContentCard'; import './index.scss'; const INDICATORS = [ { value: 'blood_pressure', label: '晨间血压 (mmHg)' }, { value: 'blood_pressure_evening', label: '晚间血压 (mmHg)' }, { value: 'heart_rate', label: '心率 (bpm)' }, { value: 'blood_sugar_fasting', label: '空腹血糖 (mmol/L)' }, { value: 'blood_sugar_postprandial', label: '餐后血糖 (mmol/L)' }, { value: 'weight', label: '体重 (kg)' }, { value: 'temperature', label: '体温 (℃)' }, ]; const BP_INDICATORS = ['blood_pressure', 'blood_pressure_evening']; const valueCheck = num({ posMsg: '请输入有效数值' }); const systolicCheck = num({ min: 60, minMsg: '收缩压过低', max: 250, maxMsg: '收缩压过高,请及时就医', optional: true }); const diastolicCheck = num({ min: 40, minMsg: '舒张压过低', max: 150, maxMsg: '舒张压过高,请及时就医', optional: true }); /** 根据动态阈值生成警告配置 */ function getWarnForIndicator( thresholds: HealthThreshold[], indicator: string, ): { max?: number; min?: number; warning: string } | null { const isBp = BP_INDICATORS.includes(indicator); const high = findThreshold(thresholds, isBp ? 'systolic_bp' : indicator, 'high'); const low = findThreshold(thresholds, isBp ? 'systolic_bp' : indicator, 'low'); if (!high && !low) return null; const warningMap: Record = { blood_pressure: '收缩压偏高,建议及时就医', blood_pressure_evening: '收缩压偏高,建议及时就医', heart_rate: '心率异常,请注意休息', blood_sugar_fasting: '血糖偏高,建议就医检查', blood_sugar_postprandial: '血糖偏高,建议就医检查', }; return { max: high?.threshold_value, min: low?.threshold_value, warning: warningMap[indicator] ?? '数值异常,请关注', }; } export default function HealthInput() { const modeClass = useElderClass(); const [indicatorIdx, setIndicatorIdx] = useState(0); const [thresholds, setThresholds] = useState(DEFAULT_THRESHOLDS); const [value, setValue] = useState(''); const [systolic, setSystolic] = useState(''); const [diastolic, setDiastolic] = useState(''); const [note, setNote] = useState(''); const [submitting, setSubmitting] = useState(false); const { safeSetTimeout } = useSafeTimeout(); const [loadingThresholds, setLoadingThresholds] = useState(true); const currentPatient = useAuthStore((s) => s.currentPatient); const clearCache = useHealthStore((s) => s.clearCache); /** 从 storage 中读取设备同步回传的数据并自动填充表单 */ const loadThresholdsAndSync = useCallback(async () => { setLoadingThresholds(true); try { const t = await getHealthThresholds(); if (t.length > 0) setThresholds(t); } finally { setLoadingThresholds(false); } try { const raw = Taro.getStorageSync('device_sync_result'); if (!raw) return; Taro.removeStorageSync('device_sync_result'); const syncData: Record = typeof raw === 'string' ? JSON.parse(raw) : raw; // 字段映射:设备同步数据 → 表单字段 if (syncData.systolic != null && syncData.diastolic != null) { // 有血压数据 → 切换到血压指标并填充 setIndicatorIdx(0); setSystolic(String(syncData.systolic)); setDiastolic(String(syncData.diastolic)); } else if (syncData.blood_sugar != null) { setIndicatorIdx(2); // 空腹血糖 setValue(String(syncData.blood_sugar)); } else if (syncData.heart_rate != null) { setIndicatorIdx(1); // 心率 setValue(String(syncData.heart_rate)); } } catch { // 解析失败则忽略,不影响正常使用 } }, []); usePageData(loadThresholdsAndSync, { throttleMs: 10000 }); const handleSubmit = async () => { if (!currentPatient) { Taro.showToast({ title: '请先选择就诊人', icon: 'none' }); return; } const currentIndicator = INDICATORS[indicatorIdx].value; if (BP_INDICATORS.includes(currentIndicator)) { if (!systolic || !diastolic) { Taro.showToast({ title: '请填写收缩压和舒张压', icon: 'none' }); return; } } else { if (!value) { Taro.showToast({ title: '请输入数值', icon: 'none' }); return; } } const input = BP_INDICATORS.includes(currentIndicator) ? { indicator_type: currentIndicator as 'blood_pressure' | 'blood_pressure_evening', value: parseFloat(systolic), extra: { systolic: parseFloat(systolic), diastolic: parseFloat(diastolic) } } : { indicator_type: currentIndicator as any, value: parseFloat(value) }; const valueResult = valueCheck.safeParse(input.value); if (!valueResult.ok) { Taro.showToast({ title: valueResult.message, icon: 'none' }); return; } if (input.extra?.systolic !== undefined) { const r = systolicCheck.safeParse(input.extra.systolic); if (!r.ok) { Taro.showToast({ title: r.message, icon: 'none' }); return; } } if (input.extra?.diastolic !== undefined) { const r = diastolicCheck.safeParse(input.extra.diastolic); if (!r.ok) { Taro.showToast({ title: r.message, icon: 'none' }); return; } } if (note) { const err = validateStr(note, 200, '备注'); if (err) { Taro.showToast({ title: err, icon: 'none' }); return; } } const threshold = getWarnForIndicator(thresholds, currentIndicator); if (threshold) { const val = input.value; if ((threshold.max && val > threshold.max) || (threshold.min && val < threshold.min)) { await Taro.showModal({ title: '健康提示', content: threshold.warning, showCancel: false }); } } setSubmitting(true); try { await inputVitalSign(currentPatient.id, { ...input, note: note || undefined, }); clearCache(); clearRequestCache('/health/'); usePointsStore.getState().invalidate(); Taro.showToast({ title: '录入成功', icon: 'success' }); trackEvent('health_data_input', { type: currentIndicator }); safeSetTimeout(() => Taro.navigateBack(), 1000); } catch (e: unknown) { const msg = e instanceof Error ? e.message : '录入失败'; Taro.showToast({ title: msg, icon: 'none' }); } finally { setSubmitting(false); } }; const indicatorInitial = INDICATORS[indicatorIdx].label.charAt(0); return ( {loadingThresholds && } {!loadingThresholds && ( <> {/* 页面标题 */} 体征录入 记录今日健康数据 {/* 从设备同步入口 */} safeNavigateTo('/pages/pkg-health/device-sync/index?returnTo=input')}> 从设备同步 蓝牙连接设备自动获取数据 {/* 指标类型选择 */} {indicatorInitial} 指标类型 i.label)} value={indicatorIdx} onChange={(e) => setIndicatorIdx(Number(e.detail.value))} > {INDICATORS[indicatorIdx].label} V {/* 数值输入 */} {BP_INDICATORS.includes(INDICATORS[indicatorIdx].value) ? ( 血压数值 收缩压 setSystolic(e.detail.value)} /> / 舒张压 setDiastolic(e.detail.value)} /> mmHg ) : ( 检测数值 setValue(e.detail.value)} /> {INDICATORS[indicatorIdx].label.match(/\((.+)\)/)?.[1] || ''} )} {/* 备注 */} 备注 setNote(e.detail.value)} /> {/* 提交 */} {submitting ? '提交中...' : '提交录入'} )} ); }