feat(health): 表单验证升级为 zod schema + 异常值警告 + 录入后清除缓存
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

This commit is contained in:
iven
2026-04-24 12:39:36 +08:00
parent a9861a0cde
commit 4f2efdb643
3 changed files with 51 additions and 24 deletions

View File

@@ -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) {