feat(health): 表单验证升级为 zod schema + 异常值警告 + 录入后清除缓存
This commit is contained in:
@@ -29,6 +29,7 @@
|
|||||||
"echarts-taro3-react": "^1.0.13",
|
"echarts-taro3-react": "^1.0.13",
|
||||||
"react": "^18.3.0",
|
"react": "^18.3.0",
|
||||||
"react-dom": "^18.3.0",
|
"react-dom": "^18.3.0",
|
||||||
|
"zod": "^4.3.6",
|
||||||
"zustand": "^5.0.0"
|
"zustand": "^5.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
8
apps/miniprogram/pnpm-lock.yaml
generated
8
apps/miniprogram/pnpm-lock.yaml
generated
@@ -56,6 +56,9 @@ importers:
|
|||||||
react-dom:
|
react-dom:
|
||||||
specifier: ^18.3.0
|
specifier: ^18.3.0
|
||||||
version: 18.3.1(react@18.3.1)
|
version: 18.3.1(react@18.3.1)
|
||||||
|
zod:
|
||||||
|
specifier: ^4.3.6
|
||||||
|
version: 4.3.6
|
||||||
zustand:
|
zustand:
|
||||||
specifier: ^5.0.0
|
specifier: ^5.0.0
|
||||||
version: 5.0.12(@types/react@18.3.28)(react@18.3.1)
|
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==}
|
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
zod@4.3.6:
|
||||||
|
resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}
|
||||||
|
|
||||||
zrender@6.0.0:
|
zrender@6.0.0:
|
||||||
resolution: {integrity: sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==}
|
resolution: {integrity: sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==}
|
||||||
|
|
||||||
@@ -10807,6 +10813,8 @@ snapshots:
|
|||||||
|
|
||||||
yocto-queue@0.1.0: {}
|
yocto-queue@0.1.0: {}
|
||||||
|
|
||||||
|
zod@4.3.6: {}
|
||||||
|
|
||||||
zrender@6.0.0:
|
zrender@6.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.3.0
|
tslib: 2.3.0
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { View, Text, Input, Picker } from '@tarojs/components';
|
import { View, Text, Input, Picker } from '@tarojs/components';
|
||||||
import Taro from '@tarojs/taro';
|
import Taro from '@tarojs/taro';
|
||||||
|
import { z } from 'zod';
|
||||||
import { inputVitalSign } from '../../../services/health';
|
import { inputVitalSign } from '../../../services/health';
|
||||||
import { useAuthStore } from '../../../stores/auth';
|
import { useAuthStore } from '../../../stores/auth';
|
||||||
|
import { useHealthStore } from '@/stores/health';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
const INDICATORS = [
|
const INDICATORS = [
|
||||||
@@ -14,6 +16,22 @@ const INDICATORS = [
|
|||||||
{ value: 'temperature', label: '体温 (℃)' },
|
{ 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() {
|
export default function HealthInput() {
|
||||||
const [indicatorIdx, setIndicatorIdx] = useState(0);
|
const [indicatorIdx, setIndicatorIdx] = useState(0);
|
||||||
const [value, setValue] = useState('');
|
const [value, setValue] = useState('');
|
||||||
@@ -22,6 +40,7 @@ export default function HealthInput() {
|
|||||||
const [note, setNote] = useState('');
|
const [note, setNote] = useState('');
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
const { currentPatient } = useAuthStore();
|
const { currentPatient } = useAuthStore();
|
||||||
|
const { clearCache } = useHealthStore();
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!currentPatient) {
|
if (!currentPatient) {
|
||||||
@@ -31,32 +50,31 @@ export default function HealthInput() {
|
|||||||
|
|
||||||
const currentIndicator = INDICATORS[indicatorIdx].value;
|
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);
|
setSubmitting(true);
|
||||||
try {
|
try {
|
||||||
if (currentIndicator === 'blood_pressure') {
|
await inputVitalSign(currentPatient.id, {
|
||||||
if (!systolic || !diastolic) {
|
...input,
|
||||||
Taro.showToast({ title: '请输入收缩压和舒张压', icon: 'none' });
|
note: note || undefined,
|
||||||
setSubmitting(false);
|
});
|
||||||
return;
|
clearCache();
|
||||||
}
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Taro.showToast({ title: '录入成功', icon: 'success' });
|
Taro.showToast({ title: '录入成功', icon: 'success' });
|
||||||
setTimeout(() => Taro.navigateBack(), 1000);
|
setTimeout(() => Taro.navigateBack(), 1000);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
|
|||||||
Reference in New Issue
Block a user