import { useState } from 'react'; import { View, Text, Input, Picker } from '@tarojs/components'; import Taro, { useDidShow } from '@tarojs/taro'; import { z } from 'zod'; import { createDailyMonitoring } from '@/services/health'; import { useAuthStore } from '@/stores/auth'; import { useHealthStore } from '@/stores/health'; import { usePointsStore } from '@/stores/points'; import { clearRequestCache } from '@/services/request'; import { trackEvent } from '@/services/analytics'; import { useElderClass } from '../../../hooks/useElderClass'; import './index.scss'; const bpSchema = z.number().min(30, '血压值不能低于30').max(300, '血压值不能高于300').optional(); const weightSchema = z.number().min(1, '体重不能低于1kg').max(500, '体重不能高于500kg').optional(); const bloodSugarSchema = z.number().min(0.1, '血糖值不能低于0.1').max(50, '血糖值不能高于50').optional(); const volumeSchema = z.number().min(0, '数值不能为负').max(10000, '数值超出合理范围').optional(); function formatDate(date: Date): string { const y = date.getFullYear(); const m = String(date.getMonth() + 1).padStart(2, '0'); const d = String(date.getDate()).padStart(2, '0'); return `${y}-${m}-${d}`; } // ── Abnormal value detection ── const REFERENCE_RANGES: Record = { systolic: { min: 90, max: 140 }, diastolic: { min: 60, max: 90 }, bloodSugar: { min: 3.9, max: 6.1 }, weight: null, fluidIntake: null, urineOutput: null, }; type AbnormalResult = { abnormal: boolean; direction: 'high' | 'low' | null }; const checkAbnormal = (value: string, field: string): AbnormalResult => { const ref = REFERENCE_RANGES[field]; if (!value || !ref) return { abnormal: false, direction: null }; const num = parseFloat(value); if (isNaN(num)) return { abnormal: false, direction: null }; if (num > ref.max) return { abnormal: true, direction: 'high' }; if (num < ref.min) return { abnormal: true, direction: 'low' }; return { abnormal: false, direction: null }; }; // ── Section state type ── type SectionKey = 'morning' | 'evening' | 'other'; const FIELD_LABELS: Record = { morningSystolic: '晨间收缩压', morningDiastolic: '晨间舒张压', eveningSystolic: '晚间收缩压', eveningDiastolic: '晚间舒张压', bloodSugar: '血糖', }; export default function DailyMonitoring() { const modeClass = useElderClass(); const { currentPatient } = useAuthStore(); const today = formatDate(new Date()); const [dateIdx, setDateIdx] = useState(0); const [dateList] = useState(() => { const list: string[] = []; for (let i = 0; i < 30; i++) { const d = new Date(); d.setDate(d.getDate() - i); list.push(formatDate(d)); } return list; }); const recordDate = dateList[dateIdx]; const [morningSystolic, setMorningSystolic] = useState(''); const [morningDiastolic, setMorningDiastolic] = useState(''); const [eveningSystolic, setEveningSystolic] = useState(''); const [eveningDiastolic, setEveningDiastolic] = useState(''); const [weight, setWeight] = useState(''); const [bloodSugar, setBloodSugar] = useState(''); const [fluidIntake, setFluidIntake] = useState(''); const [urineOutput, setUrineOutput] = useState(''); const [notes, setNotes] = useState(''); const [submitting, setSubmitting] = useState(false); // ── Collapsible sections ── const [collapsed, setCollapsed] = useState>({ morning: false, evening: false, other: true, }); const toggleSection = (key: SectionKey) => { setCollapsed(prev => ({ ...prev, [key]: !prev[key] })); }; useDidShow(() => { Taro.setNavigationBarTitle({ title: '日常监测上报' }); }); const resetForm = () => { setMorningSystolic(''); setMorningDiastolic(''); setEveningSystolic(''); setEveningDiastolic(''); setWeight(''); setBloodSugar(''); setFluidIntake(''); setUrineOutput(''); setNotes(''); }; // ── Abnormal field gathering for submit confirmation ── const gatherAbnormalFields = (): string[] => { const abnormalFields: string[] = []; const checks: Array<[string, string]> = [ ['morningSystolic', morningSystolic], ['morningDiastolic', morningDiastolic], ['eveningSystolic', eveningSystolic], ['eveningDiastolic', eveningDiastolic], ['bloodSugar', bloodSugar], ]; for (const [field, value] of checks) { const result = checkAbnormal(value, field); if (result.abnormal) { abnormalFields.push(FIELD_LABELS[field]); } } return abnormalFields; }; const handleSubmit = async () => { if (!currentPatient) { Taro.showToast({ title: '请先选择就诊人', icon: 'none' }); return; } const hasData = morningSystolic || morningDiastolic || eveningSystolic || eveningDiastolic || weight || bloodSugar || fluidIntake || urineOutput; if (!hasData) { Taro.showToast({ title: '请至少填写一项数据', icon: 'none' }); return; } if ((morningSystolic && !morningDiastolic) || (!morningSystolic && morningDiastolic)) { Taro.showToast({ title: '晨起血压请同时填写收缩压和舒张压', icon: 'none' }); return; } if ((eveningSystolic && !eveningDiastolic) || (!eveningSystolic && eveningDiastolic)) { Taro.showToast({ title: '晚间血压请同时填写收缩压和舒张压', icon: 'none' }); return; } const parseNum = (v: string) => v ? parseFloat(v) : undefined; const fields = { morningSystolic: parseNum(morningSystolic), morningDiastolic: parseNum(morningDiastolic), eveningSystolic: parseNum(eveningSystolic), eveningDiastolic: parseNum(eveningDiastolic), weight: parseNum(weight), bloodSugar: parseNum(bloodSugar), fluidIntake: parseNum(fluidIntake), urineOutput: parseNum(urineOutput), }; const validations: Array<[z.ZodTypeAny, number | undefined, string]> = [ [bpSchema, fields.morningSystolic, '晨起收缩压'], [bpSchema, fields.morningDiastolic, '晨起舒张压'], [bpSchema, fields.eveningSystolic, '晚间收缩压'], [bpSchema, fields.eveningDiastolic, '晚间舒张压'], [weightSchema, fields.weight, '体重'], [bloodSugarSchema, fields.bloodSugar, '血糖'], [volumeSchema, fields.fluidIntake, '饮水量'], [volumeSchema, fields.urineOutput, '尿量'], ]; for (const [schema, value, label] of validations) { if (value !== undefined) { const result = schema.safeParse(value); if (!result.success) { Taro.showToast({ title: `${label}: ${result.error.errors[0].message}`, icon: 'none' }); return; } } } // ── Pre-submit abnormal confirmation ── const abnormalFields = gatherAbnormalFields(); if (abnormalFields.length > 0) { const confirmed = await Taro.showModal({ title: '数值异常提醒', content: `以下指标超出正常范围:${abnormalFields.join('、')}。确认提交?`, confirmText: '确认提交', cancelText: '返回修改', }); if (!confirmed.confirm) return; } setSubmitting(true); try { await createDailyMonitoring({ patient_id: currentPatient.id, record_date: recordDate, morning_bp_systolic: morningSystolic ? parseFloat(morningSystolic) : undefined, morning_bp_diastolic: morningDiastolic ? parseFloat(morningDiastolic) : undefined, evening_bp_systolic: eveningSystolic ? parseFloat(eveningSystolic) : undefined, evening_bp_diastolic: eveningDiastolic ? parseFloat(eveningDiastolic) : undefined, weight: weight ? parseFloat(weight) : undefined, blood_sugar: bloodSugar ? parseFloat(bloodSugar) : undefined, fluid_intake: fluidIntake ? parseFloat(fluidIntake) : undefined, urine_output: urineOutput ? parseFloat(urineOutput) : undefined, notes: notes || undefined, }); trackEvent('daily_monitoring_submit', { date: recordDate }); useHealthStore.getState().clearCache(); clearRequestCache('/health/'); usePointsStore.getState().invalidate(); Taro.showToast({ title: '上报成功', icon: 'success' }); setTimeout(() => { Taro.showToast({ title: '+10 健康积分', icon: 'none', duration: 1500 }); }, 1600); setTimeout(() => { Taro.navigateBack(); }, 3200); } catch (e: unknown) { const msg = e instanceof Error ? e.message : '上报失败'; if (msg.includes('已有记录') || msg.includes('already exists')) { Taro.showModal({ title: '提示', content: '该日期已有监测记录,请选择其他日期', showCancel: false, }); } else { Taro.showToast({ title: msg, icon: 'none' }); } } finally { setSubmitting(false); } }; const isToday = recordDate === today; // ── Abnormal state helpers for rendering ── const morningSysAbnormal = checkAbnormal(morningSystolic, 'systolic'); const morningDiaAbnormal = checkAbnormal(morningDiastolic, 'diastolic'); const eveningSysAbnormal = checkAbnormal(eveningSystolic, 'systolic'); const eveningDiaAbnormal = checkAbnormal(eveningDiastolic, 'diastolic'); const bloodSugarAbnormal = checkAbnormal(bloodSugar, 'bloodSugar'); return ( {/* 页面标题 */} 日常监测 每日健康数据上报 {/* 日期选择 (standalone card) */} 记录日期 {isToday && ( 今日 )} setDateIdx(Number(e.detail.value))} > {recordDate} V {/* ── Group 1: 晨间体征 (default open) ── */} toggleSection('morning')}> 晨间体征 收缩压 setMorningSystolic(e.detail.value)} /> {morningSysAbnormal.abnormal && ( {morningSysAbnormal.direction === 'high' ? '偏高' : '偏低'} )} / 舒张压 setMorningDiastolic(e.detail.value)} /> {morningDiaAbnormal.abnormal && ( {morningDiaAbnormal.direction === 'high' ? '偏高' : '偏低'} )} mmHg {/* ── Group 2: 晚间体征 (default open) ── */} toggleSection('evening')}> 晚间体征 收缩压 setEveningSystolic(e.detail.value)} /> {eveningSysAbnormal.abnormal && ( {eveningSysAbnormal.direction === 'high' ? '偏高' : '偏低'} )} / 舒张压 setEveningDiastolic(e.detail.value)} /> {eveningDiaAbnormal.abnormal && ( {eveningDiaAbnormal.direction === 'high' ? '偏高' : '偏低'} )} mmHg {/* ── Group 3: 其他指标 (default collapsed) ── */} toggleSection('other')}> 其他指标 {/* 体重 */} 体重 setWeight(e.detail.value)} /> kg {/* 血糖 */} 血糖 setBloodSugar(e.detail.value)} /> mmol/L {bloodSugarAbnormal.abnormal && ( {bloodSugarAbnormal.direction === 'high' ? '偏高' : '偏低'} )} {/* 饮水量 */} 饮水量 setFluidIntake(e.detail.value)} /> ml {/* 尿量 */} 尿量 setUrineOutput(e.detail.value)} /> ml {/* 备注 */} 备注 setNotes(e.detail.value)} /> {/* 提交 */} {submitting ? '提交中...' : '提交上报'} {/* 重置 */} 清空表单 ); }