From 4f2efdb64320d9feeea4a8929be6d459655fc37a Mon Sep 17 00:00:00 2001 From: iven Date: Fri, 24 Apr 2026 12:39:36 +0800 Subject: [PATCH] =?UTF-8?q?feat(health):=20=E8=A1=A8=E5=8D=95=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E5=8D=87=E7=BA=A7=E4=B8=BA=20zod=20schema=20+=20?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E5=80=BC=E8=AD=A6=E5=91=8A=20+=20=E5=BD=95?= =?UTF-8?q?=E5=85=A5=E5=90=8E=E6=B8=85=E9=99=A4=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/miniprogram/package.json | 1 + apps/miniprogram/pnpm-lock.yaml | 8 +++ .../src/pages/health/input/index.tsx | 66 ++++++++++++------- 3 files changed, 51 insertions(+), 24 deletions(-) diff --git a/apps/miniprogram/package.json b/apps/miniprogram/package.json index 43f59d8..7603d69 100644 --- a/apps/miniprogram/package.json +++ b/apps/miniprogram/package.json @@ -29,6 +29,7 @@ "echarts-taro3-react": "^1.0.13", "react": "^18.3.0", "react-dom": "^18.3.0", + "zod": "^4.3.6", "zustand": "^5.0.0" }, "devDependencies": { diff --git a/apps/miniprogram/pnpm-lock.yaml b/apps/miniprogram/pnpm-lock.yaml index 90b3e69..b59013c 100644 --- a/apps/miniprogram/pnpm-lock.yaml +++ b/apps/miniprogram/pnpm-lock.yaml @@ -56,6 +56,9 @@ importers: react-dom: specifier: ^18.3.0 version: 18.3.1(react@18.3.1) + zod: + specifier: ^4.3.6 + version: 4.3.6 zustand: specifier: ^5.0.0 version: 5.0.12(@types/react@18.3.28)(react@18.3.1) @@ -5267,6 +5270,9 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + zrender@6.0.0: resolution: {integrity: sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==} @@ -10807,6 +10813,8 @@ snapshots: yocto-queue@0.1.0: {} + zod@4.3.6: {} + zrender@6.0.0: dependencies: tslib: 2.3.0 diff --git a/apps/miniprogram/src/pages/health/input/index.tsx b/apps/miniprogram/src/pages/health/input/index.tsx index 85549af..67c8938 100644 --- a/apps/miniprogram/src/pages/health/input/index.tsx +++ b/apps/miniprogram/src/pages/health/input/index.tsx @@ -1,8 +1,10 @@ import { useState } from 'react'; import { View, Text, Input, Picker } from '@tarojs/components'; import Taro from '@tarojs/taro'; +import { z } from 'zod'; import { inputVitalSign } from '../../../services/health'; import { useAuthStore } from '../../../stores/auth'; +import { useHealthStore } from '@/stores/health'; import './index.scss'; const INDICATORS = [ @@ -14,6 +16,22 @@ const INDICATORS = [ { value: 'temperature', label: '体温 (℃)' }, ]; +const vitalSignSchema = z.object({ + indicator_type: z.enum(['blood_pressure', 'heart_rate', 'blood_sugar_fasting', 'blood_sugar_postprandial', 'weight', 'temperature']), + value: z.number().positive({ message: '请输入有效数值' }), + extra: z.object({ + systolic: z.number().min(60, '收缩压过低').max(250, '收缩压过高,请及时就医').optional(), + diastolic: z.number().min(40, '舒张压过低').max(150, '舒张压过高,请及时就医').optional(), + }).optional(), + note: z.string().max(200, '备注不能超过200字').optional(), +}); + +const WARN_THRESHOLDS: Record = { + blood_pressure: { max: 180, warning: '收缩压偏高,建议及时就医' }, + heart_rate: { max: 120, min: 50, warning: '心率异常,请注意休息' }, + blood_sugar_fasting: { max: 11.0, warning: '血糖偏高,建议就医检查' }, +}; + export default function HealthInput() { const [indicatorIdx, setIndicatorIdx] = useState(0); const [value, setValue] = useState(''); @@ -22,6 +40,7 @@ export default function HealthInput() { const [note, setNote] = useState(''); const [submitting, setSubmitting] = useState(false); const { currentPatient } = useAuthStore(); + const { clearCache } = useHealthStore(); const handleSubmit = async () => { if (!currentPatient) { @@ -31,32 +50,31 @@ export default function HealthInput() { const currentIndicator = INDICATORS[indicatorIdx].value; + const input = currentIndicator === 'blood_pressure' + ? { indicator_type: 'blood_pressure' as const, value: parseFloat(systolic), extra: { systolic: parseFloat(systolic), diastolic: parseFloat(diastolic) } } + : { indicator_type: currentIndicator as any, value: parseFloat(value) }; + + const result = vitalSignSchema.safeParse(input); + if (!result.success) { + Taro.showToast({ title: result.error.errors[0].message, icon: 'none' }); + return; + } + + const threshold = WARN_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 { - if (currentIndicator === 'blood_pressure') { - if (!systolic || !diastolic) { - Taro.showToast({ title: '请输入收缩压和舒张压', icon: 'none' }); - setSubmitting(false); - return; - } - await inputVitalSign(currentPatient.id, { - indicator_type: 'blood_pressure', - value: parseFloat(systolic), - extra: { systolic: parseFloat(systolic), diastolic: parseFloat(diastolic) }, - note: note || undefined, - }); - } else { - if (!value) { - Taro.showToast({ title: '请输入数值', icon: 'none' }); - setSubmitting(false); - return; - } - await inputVitalSign(currentPatient.id, { - indicator_type: currentIndicator, - value: parseFloat(value), - note: note || undefined, - }); - } + await inputVitalSign(currentPatient.id, { + ...input, + note: note || undefined, + }); + clearCache(); Taro.showToast({ title: '录入成功', icon: 'success' }); setTimeout(() => Taro.navigateBack(), 1000); } catch (e: unknown) {