feat(health): 表单验证升级为 zod schema + 异常值警告 + 录入后清除缓存
This commit is contained in:
@@ -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": {
|
||||
|
||||
8
apps/miniprogram/pnpm-lock.yaml
generated
8
apps/miniprogram/pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
@@ -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<string, { max?: number; min?: number; warning: string }> = {
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user