diff --git a/apps/miniprogram/config/index.ts b/apps/miniprogram/config/index.ts index bd35460..d41d2a1 100644 --- a/apps/miniprogram/config/index.ts +++ b/apps/miniprogram/config/index.ts @@ -33,9 +33,7 @@ export default defineConfig(async (merge) => { mini: { compile: { exclude: [], - include: [ - require.resolve('zod').replace(/[\\/]index\.cjs$/, ''), - ], + include: [], }, postcss: { pxtransform: { enable: true, config: {} }, diff --git a/apps/miniprogram/src/pages/pkg-health/daily-monitoring/index.tsx b/apps/miniprogram/src/pages/pkg-health/daily-monitoring/index.tsx index b92bd7a..b31441a 100644 --- a/apps/miniprogram/src/pages/pkg-health/daily-monitoring/index.tsx +++ b/apps/miniprogram/src/pages/pkg-health/daily-monitoring/index.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { View, Text, Input, Picker } from '@tarojs/components'; import Taro, { useDidShow } from '@tarojs/taro'; -import { z } from 'zod'; +import { validateNum } from '@/utils/validate'; import { createDailyMonitoring } from '@/services/health'; import { useAuthStore } from '@/stores/auth'; import { useHealthStore } from '@/stores/health'; @@ -11,10 +11,10 @@ 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(); +const BP_RANGE = { min: 30, minMsg: '血压值不能低于30', max: 300, maxMsg: '血压值不能高于300', optional: true }; +const WEIGHT_RANGE = { min: 1, minMsg: '体重不能低于1kg', max: 500, maxMsg: '体重不能高于500kg', optional: true }; +const SUGAR_RANGE = { min: 0.1, minMsg: '血糖值不能低于0.1', max: 50, maxMsg: '血糖值不能高于50', optional: true }; +const VOLUME_RANGE = { min: 0, minMsg: '数值不能为负', max: 10000, maxMsg: '数值超出合理范围', optional: true }; function formatDate(date: Date): string { const y = date.getFullYear(); @@ -172,24 +172,22 @@ export default function DailyMonitoring() { 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, '尿量'], + const validations: Array<[number | undefined, string, typeof BP_RANGE]> = [ + [fields.morningSystolic, '晨起收缩压', BP_RANGE], + [fields.morningDiastolic, '晨起舒张压', BP_RANGE], + [fields.eveningSystolic, '晚间收缩压', BP_RANGE], + [fields.eveningDiastolic, '晚间舒张压', BP_RANGE], + [fields.weight, '体重', WEIGHT_RANGE], + [fields.bloodSugar, '血糖', SUGAR_RANGE], + [fields.fluidIntake, '饮水量', VOLUME_RANGE], + [fields.urineOutput, '尿量', VOLUME_RANGE], ]; - 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; - } + for (const [value, label, range] of validations) { + const err = validateNum(value, label, range); + if (err) { + Taro.showToast({ title: err, icon: 'none' }); + return; } } diff --git a/apps/miniprogram/src/pages/pkg-health/input/index.tsx b/apps/miniprogram/src/pages/pkg-health/input/index.tsx index 63f5ca5..e508149 100644 --- a/apps/miniprogram/src/pages/pkg-health/input/index.tsx +++ b/apps/miniprogram/src/pages/pkg-health/input/index.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { View, Text, Input, Picker } from '@tarojs/components'; import Taro, { useDidShow } from '@tarojs/taro'; -import { z } from 'zod'; +import { num, validateStr } from '@/utils/validate'; import { inputVitalSign, getHealthThresholds, findThreshold, DEFAULT_THRESHOLDS, type HealthThreshold } from '../../../services/health'; import { useAuthStore } from '../../../stores/auth'; import { useHealthStore } from '@/stores/health'; @@ -23,15 +23,9 @@ const INDICATORS = [ const BP_INDICATORS = ['blood_pressure', 'blood_pressure_evening']; -const vitalSignSchema = z.object({ - indicator_type: z.enum(['blood_pressure', 'blood_pressure_evening', '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 valueCheck = num({ posMsg: '请输入有效数值' }); +const systolicCheck = num({ min: 60, minMsg: '收缩压过低', max: 250, maxMsg: '收缩压过高,请及时就医', optional: true }); +const diastolicCheck = num({ min: 40, minMsg: '舒张压过低', max: 150, maxMsg: '舒张压过高,请及时就医', optional: true }); /** 根据动态阈值生成警告配置 */ function getWarnForIndicator( @@ -120,11 +114,23 @@ export default function HealthInput() { ? { indicator_type: currentIndicator as 'blood_pressure' | 'blood_pressure_evening', 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.issues[0].message, icon: 'none' }); + const valueResult = valueCheck.safeParse(input.value); + if (!valueResult.ok) { + Taro.showToast({ title: valueResult.message, icon: 'none' }); return; } + if (input.extra?.systolic !== undefined) { + const r = systolicCheck.safeParse(input.extra.systolic); + if (!r.ok) { Taro.showToast({ title: r.message, icon: 'none' }); return; } + } + if (input.extra?.diastolic !== undefined) { + const r = diastolicCheck.safeParse(input.extra.diastolic); + if (!r.ok) { Taro.showToast({ title: r.message, icon: 'none' }); return; } + } + if (note) { + const err = validateStr(note, 200, '备注'); + if (err) { Taro.showToast({ title: err, icon: 'none' }); return; } + } const threshold = getWarnForIndicator(thresholds, currentIndicator); if (threshold) { diff --git a/apps/miniprogram/src/utils/validate.ts b/apps/miniprogram/src/utils/validate.ts new file mode 100644 index 0000000..a9434cb --- /dev/null +++ b/apps/miniprogram/src/utils/validate.ts @@ -0,0 +1,49 @@ +interface NumRule { + min?: number; + max?: number; + minMsg?: string; + maxMsg?: string; + posMsg?: string; + optional?: boolean; +} + +interface ValidateResult { + ok: boolean; + message: string; +} + +export function num(rule: NumRule) { + return { + safeParse(value: number | undefined): ValidateResult { + if (value === undefined || value === null) { + return rule.optional ? { ok: true, message: '' } : { ok: false, message: posMsg || '请输入有效数值' }; + } + if (isNaN(value)) return { ok: false, message: '请输入有效数值' }; + if (rule.min !== undefined && value < rule.min) return { ok: false, message: rule.minMsg || `数值不能低于${rule.min}` }; + if (rule.max !== undefined && value > rule.max) return { ok: false, message: rule.maxMsg || `数值不能高于${rule.max}` }; + return { ok: true, message: '' }; + }, + }; +} + +interface FieldRule { + min?: number; + max?: number; + minMsg?: string; + maxMsg?: string; + optional?: boolean; +} + +export function validateNum(value: number | undefined, label: string, rule: FieldRule): string | null { + if (value === undefined || value === null) return rule.optional ? null : `${label}: 请输入有效数值`; + if (isNaN(value)) return `${label}: 请输入有效数值`; + if (rule.min !== undefined && value < rule.min) return `${label}: ${rule.minMsg ?? `数值不能低于${rule.min}`}`; + if (rule.max !== undefined && value > rule.max) return `${label}: ${rule.maxMsg ?? `数值不能高于${rule.max}`}`; + return null; +} + +export function validateStr(value: string | undefined, maxLen: number, label: string): string | null { + if (!value) return null; + if (value.length > maxLen) return `${label}不能超过${maxLen}字`; + return null; +}