From 35f06951a049352f4db6f029706762763d29236c Mon Sep 17 00:00:00 2001 From: richarjiang Date: Wed, 12 Nov 2025 10:27:20 +0800 Subject: [PATCH] =?UTF-8?q?feat(medications):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=8D=AF=E5=93=81=E7=BB=93=E6=9D=9F=E6=97=A5=E6=9C=9F=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增药品结束日期选择器,支持设置服药周期 - 优化日期显示格式,从"开始日期"改为"服药周期" - 添加日期验证逻辑,确保开始日期不早于今天且结束日期不早于开始日期 - 改进添加药品页面的日期选择UI,采用并排布局 - 调整InfoCard组件样式,移除图标背景色并减小字体大小 --- app/medications/[medicationId].tsx | 39 +++++- app/medications/add-medication.tsx | 199 +++++++++++++++++++++++++---- components/ui/InfoCard.tsx | 8 +- 3 files changed, 206 insertions(+), 40 deletions(-) diff --git a/app/medications/[medicationId].tsx b/app/medications/[medicationId].tsx index c64594a..c51caa5 100644 --- a/app/medications/[medicationId].tsx +++ b/app/medications/[medicationId].tsx @@ -378,6 +378,23 @@ export default function MedicationDetailScreen() { const startDateLabel = medication ? dayjs(medication.startDate).format('YYYY年M月D日') : '--'; + + // 计算服药周期显示 + const medicationPeriodLabel = useMemo(() => { + if (!medication) return '--'; + + const startDate = dayjs(medication.startDate).format('YYYY年M月D日'); + + if (medication.endDate) { + // 有结束日期,显示开始日期到结束日期 + const endDate = dayjs(medication.endDate).format('YYYY年M月D日'); + return `${startDate} - ${endDate}`; + } else { + // 没有结束日期,显示长期 + return `${startDate} - 长期`; + } + }, [medication]); + const reminderTimes = medication?.medicationTimes?.length ? medication.medicationTimes.join('、') : '尚未设置'; @@ -467,8 +484,20 @@ export default function MedicationDetailScreen() { }, [medication?.photoUrl]); const handleStartDatePress = useCallback(() => { - Alert.alert('开始日期', `开始服药日期:${startDateLabel}`); - }, [startDateLabel]); + if (!medication) return; + + const startDate = dayjs(medication.startDate).format('YYYY年M月D日'); + let message = `开始服药日期:${startDate}`; + + if (medication.endDate) { + const endDate = dayjs(medication.endDate).format('YYYY年M月D日'); + message += `\n结束服药日期:${endDate}`; + } else { + message += `\n服药计划:长期服药`; + } + + Alert.alert('服药周期', message); + }, [medication]); const handleTimePress = useCallback(() => { Alert.alert('服药时间', `设置的时间:${reminderTimes}`); @@ -676,15 +705,15 @@ export default function MedicationDetailScreen() {
withAlpha(colors.border, 0.45), [colors.border]); + const softBorderColor = useMemo(() => withAlpha(colors.border, 0.25), [colors.border]); const fadedBorderFill = useMemo(() => withAlpha('#ffffff', 1), [colors.border]); const glassPrimaryTint = useMemo(() => withAlpha(colors.primary, theme === 'dark' ? 0.55 : 0.45), [colors.primary, theme]); const glassDisabledTint = useMemo(() => withAlpha(colors.border, theme === 'dark' ? 0.45 : 0.6), [colors.border, theme]); @@ -146,8 +146,12 @@ export default function AddMedicationScreen() { const [timesPickerVisible, setTimesPickerVisible] = useState(false); const [timesPickerValue, setTimesPickerValue] = useState(1); const [startDate, setStartDate] = useState(new Date()); + const [endDate, setEndDate] = useState(null); const [datePickerVisible, setDatePickerVisible] = useState(false); const [datePickerValue, setDatePickerValue] = useState(new Date()); + const [endDatePickerVisible, setEndDatePickerVisible] = useState(false); + const [endDatePickerValue, setEndDatePickerValue] = useState(new Date()); + const [datePickerMode, setDatePickerMode] = useState<'start' | 'end'>('start'); const [medicationTimes, setMedicationTimes] = useState([DEFAULT_TIME_PRESETS[0]]); const [timePickerVisible, setTimePickerVisible] = useState(false); const [timePickerDate, setTimePickerDate] = useState(createDateFromTime(DEFAULT_TIME_PRESETS[0])); @@ -276,6 +280,7 @@ export default function AddMedicationScreen() { timesPerDay: timesPerDay, medicationTimes: medicationTimes, startDate: dayjs(startDate).startOf('day').toISOString(), // ISO 8601 格式 + endDate: endDate ? dayjs(endDate).endOf('day').toISOString() : undefined, // 如果有结束日期,设置为当天结束时间 repeatPattern: 'daily' as RepeatPattern, note: note.trim() || undefined, }; @@ -332,6 +337,7 @@ export default function AddMedicationScreen() { dosageUnit, medicationTimes, startDate, + endDate, note, dispatch, ensureLoggedIn, @@ -469,15 +475,47 @@ export default function AddMedicationScreen() { setPhotoUrl(null); }, []); - const openDatePicker = useCallback(() => { + const openStartDatePicker = useCallback(() => { setDatePickerValue(startDate); setDatePickerVisible(true); }, [startDate]); + const openEndDatePicker = useCallback(() => { + setEndDatePickerValue(endDate || new Date()); + setEndDatePickerVisible(true); + }, [endDate]); + const confirmStartDate = useCallback((date: Date) => { + // 验证开始日期不能早于今天 + const today = new Date(); + today.setHours(0, 0, 0, 0); + const selectedDate = new Date(date); + selectedDate.setHours(0, 0, 0, 0); + + if (selectedDate < today) { + Alert.alert('日期无效', '开始日期不能早于今天'); + return; + } + setStartDate(date); setDatePickerVisible(false); - }, []); + + // 如果结束日期早于新的开始日期,清空结束日期 + if (endDate && endDate < date) { + setEndDate(null); + } + }, [endDate]); + + const confirmEndDate = useCallback((date: Date) => { + // 验证结束日期不能早于开始日期 + if (date < startDate) { + Alert.alert('日期无效', '结束日期不能早于开始日期'); + return; + } + + setEndDate(date); + setEndDatePickerVisible(false); + }, [startDate]); const openTimePicker = useCallback( (index?: number) => { @@ -718,7 +756,6 @@ export default function AddMedicationScreen() { case 2: return ( - 每日次数 + + + + 用药周期 + + + + + + 开始 + + {dayjs(startDate).format('MM/DD')} + + + + + + + + + 结束 + + {endDate ? dayjs(endDate).format('MM/DD') : '长期'} + + + + + + ); case 3: @@ -895,30 +983,6 @@ export default function AddMedicationScreen() { {renderStepContent()} - {showDateField && ( - - - - - 开始日期 - - {dayjs(startDate).format('YYYY 年 MM 月 DD 日')} - - - - - - )} {currentStep > 0 && ( @@ -1010,6 +1074,7 @@ export default function AddMedicationScreen() { setDatePickerVisible(false)} /> + 选择开始日期 + setEndDatePickerVisible(false)} + > + setEndDatePickerVisible(false)} /> + + 选择结束日期 + { + if (Platform.OS === 'ios') { + if (date) setEndDatePickerValue(date); + } else { + if (event.type === 'set' && date) { + confirmEndDate(date); + } else { + setEndDatePickerVisible(false); + } + } + }} + /> + {Platform.OS === 'ios' && ( + + setEndDatePickerVisible(false)} + style={[styles.modalBtn, { borderColor: softBorderColor }]} + > + 取消 + + confirmEndDate(endDatePickerValue)} + style={[styles.modalBtn, styles.modalBtnPrimary, { backgroundColor: colors.primary }]} + > + 确定 + + + )} + + + = ({ return ( @@ -104,21 +103,16 @@ const styles = StyleSheet.create({ width: 28, height: 28, borderRadius: 14, - backgroundColor: '#EEF1FF', alignItems: 'center', justifyContent: 'center', }, - clickableIconFallback: { - borderWidth: 1, - borderColor: 'rgba(76, 110, 245, 0.3)', - }, infoCardLabel: { fontSize: 13, color: '#6B7280', marginTop: 8, }, infoCardValue: { - fontSize: 16, + fontSize: 14, fontWeight: '600', color: '#1F2933', },