feat(medications): 添加药品结束日期选择功能
- 新增药品结束日期选择器,支持设置服药周期 - 优化日期显示格式,从"开始日期"改为"服药周期" - 添加日期验证逻辑,确保开始日期不早于今天且结束日期不早于开始日期 - 改进添加药品页面的日期选择UI,采用并排布局 - 调整InfoCard组件样式,移除图标背景色并减小字体大小
This commit is contained in:
@@ -378,6 +378,23 @@ export default function MedicationDetailScreen() {
|
|||||||
const startDateLabel = medication
|
const startDateLabel = medication
|
||||||
? dayjs(medication.startDate).format('YYYY年M月D日')
|
? 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
|
const reminderTimes = medication?.medicationTimes?.length
|
||||||
? medication.medicationTimes.join('、')
|
? medication.medicationTimes.join('、')
|
||||||
: '尚未设置';
|
: '尚未设置';
|
||||||
@@ -467,8 +484,20 @@ export default function MedicationDetailScreen() {
|
|||||||
}, [medication?.photoUrl]);
|
}, [medication?.photoUrl]);
|
||||||
|
|
||||||
const handleStartDatePress = useCallback(() => {
|
const handleStartDatePress = useCallback(() => {
|
||||||
Alert.alert('开始日期', `开始服药日期:${startDateLabel}`);
|
if (!medication) return;
|
||||||
}, [startDateLabel]);
|
|
||||||
|
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(() => {
|
const handleTimePress = useCallback(() => {
|
||||||
Alert.alert('服药时间', `设置的时间:${reminderTimes}`);
|
Alert.alert('服药时间', `设置的时间:${reminderTimes}`);
|
||||||
@@ -676,15 +705,15 @@ export default function MedicationDetailScreen() {
|
|||||||
<Section title="服药计划" color={colors.text}>
|
<Section title="服药计划" color={colors.text}>
|
||||||
<View style={styles.row}>
|
<View style={styles.row}>
|
||||||
<InfoCard
|
<InfoCard
|
||||||
label="开始日期"
|
label="服药周期"
|
||||||
value={startDateLabel}
|
value={medicationPeriodLabel}
|
||||||
icon="calendar-outline"
|
icon="calendar-outline"
|
||||||
colors={colors}
|
colors={colors}
|
||||||
clickable={false}
|
clickable={false}
|
||||||
onPress={handleStartDatePress}
|
onPress={handleStartDatePress}
|
||||||
/>
|
/>
|
||||||
<InfoCard
|
<InfoCard
|
||||||
label="时间"
|
label="用药时间"
|
||||||
value={reminderTimes}
|
value={reminderTimes}
|
||||||
icon="time-outline"
|
icon="time-outline"
|
||||||
colors={colors}
|
colors={colors}
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ export default function AddMedicationScreen() {
|
|||||||
const { upload, uploading } = useCosUpload({ prefix: 'images/medications' });
|
const { upload, uploading } = useCosUpload({ prefix: 'images/medications' });
|
||||||
// 获取登录验证相关的功能
|
// 获取登录验证相关的功能
|
||||||
const { ensureLoggedIn } = useAuthGuard();
|
const { ensureLoggedIn } = useAuthGuard();
|
||||||
const softBorderColor = useMemo(() => 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 fadedBorderFill = useMemo(() => withAlpha('#ffffff', 1), [colors.border]);
|
||||||
const glassPrimaryTint = useMemo(() => withAlpha(colors.primary, theme === 'dark' ? 0.55 : 0.45), [colors.primary, theme]);
|
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]);
|
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 [timesPickerVisible, setTimesPickerVisible] = useState(false);
|
||||||
const [timesPickerValue, setTimesPickerValue] = useState(1);
|
const [timesPickerValue, setTimesPickerValue] = useState(1);
|
||||||
const [startDate, setStartDate] = useState<Date>(new Date());
|
const [startDate, setStartDate] = useState<Date>(new Date());
|
||||||
|
const [endDate, setEndDate] = useState<Date | null>(null);
|
||||||
const [datePickerVisible, setDatePickerVisible] = useState(false);
|
const [datePickerVisible, setDatePickerVisible] = useState(false);
|
||||||
const [datePickerValue, setDatePickerValue] = useState<Date>(new Date());
|
const [datePickerValue, setDatePickerValue] = useState<Date>(new Date());
|
||||||
|
const [endDatePickerVisible, setEndDatePickerVisible] = useState(false);
|
||||||
|
const [endDatePickerValue, setEndDatePickerValue] = useState<Date>(new Date());
|
||||||
|
const [datePickerMode, setDatePickerMode] = useState<'start' | 'end'>('start');
|
||||||
const [medicationTimes, setMedicationTimes] = useState<string[]>([DEFAULT_TIME_PRESETS[0]]);
|
const [medicationTimes, setMedicationTimes] = useState<string[]>([DEFAULT_TIME_PRESETS[0]]);
|
||||||
const [timePickerVisible, setTimePickerVisible] = useState(false);
|
const [timePickerVisible, setTimePickerVisible] = useState(false);
|
||||||
const [timePickerDate, setTimePickerDate] = useState<Date>(createDateFromTime(DEFAULT_TIME_PRESETS[0]));
|
const [timePickerDate, setTimePickerDate] = useState<Date>(createDateFromTime(DEFAULT_TIME_PRESETS[0]));
|
||||||
@@ -276,6 +280,7 @@ export default function AddMedicationScreen() {
|
|||||||
timesPerDay: timesPerDay,
|
timesPerDay: timesPerDay,
|
||||||
medicationTimes: medicationTimes,
|
medicationTimes: medicationTimes,
|
||||||
startDate: dayjs(startDate).startOf('day').toISOString(), // ISO 8601 格式
|
startDate: dayjs(startDate).startOf('day').toISOString(), // ISO 8601 格式
|
||||||
|
endDate: endDate ? dayjs(endDate).endOf('day').toISOString() : undefined, // 如果有结束日期,设置为当天结束时间
|
||||||
repeatPattern: 'daily' as RepeatPattern,
|
repeatPattern: 'daily' as RepeatPattern,
|
||||||
note: note.trim() || undefined,
|
note: note.trim() || undefined,
|
||||||
};
|
};
|
||||||
@@ -332,6 +337,7 @@ export default function AddMedicationScreen() {
|
|||||||
dosageUnit,
|
dosageUnit,
|
||||||
medicationTimes,
|
medicationTimes,
|
||||||
startDate,
|
startDate,
|
||||||
|
endDate,
|
||||||
note,
|
note,
|
||||||
dispatch,
|
dispatch,
|
||||||
ensureLoggedIn,
|
ensureLoggedIn,
|
||||||
@@ -469,15 +475,47 @@ export default function AddMedicationScreen() {
|
|||||||
setPhotoUrl(null);
|
setPhotoUrl(null);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const openDatePicker = useCallback(() => {
|
const openStartDatePicker = useCallback(() => {
|
||||||
setDatePickerValue(startDate);
|
setDatePickerValue(startDate);
|
||||||
setDatePickerVisible(true);
|
setDatePickerVisible(true);
|
||||||
}, [startDate]);
|
}, [startDate]);
|
||||||
|
|
||||||
|
const openEndDatePicker = useCallback(() => {
|
||||||
|
setEndDatePickerValue(endDate || new Date());
|
||||||
|
setEndDatePickerVisible(true);
|
||||||
|
}, [endDate]);
|
||||||
|
|
||||||
const confirmStartDate = useCallback((date: Date) => {
|
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);
|
setStartDate(date);
|
||||||
setDatePickerVisible(false);
|
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(
|
const openTimePicker = useCallback(
|
||||||
(index?: number) => {
|
(index?: number) => {
|
||||||
@@ -718,7 +756,6 @@ export default function AddMedicationScreen() {
|
|||||||
case 2:
|
case 2:
|
||||||
return (
|
return (
|
||||||
<View style={styles.stepSection}>
|
<View style={styles.stepSection}>
|
||||||
|
|
||||||
<View style={styles.inputGroup}>
|
<View style={styles.inputGroup}>
|
||||||
<ThemedText style={[styles.groupLabel, { color: colors.textSecondary }]}>每日次数</ThemedText>
|
<ThemedText style={[styles.groupLabel, { color: colors.textSecondary }]}>每日次数</ThemedText>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
@@ -736,6 +773,57 @@ export default function AddMedicationScreen() {
|
|||||||
<Ionicons name="chevron-down" size={18} color={colors.textSecondary} />
|
<Ionicons name="chevron-down" size={18} color={colors.textSecondary} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.inputGroup}>
|
||||||
|
<View style={styles.periodHeader}>
|
||||||
|
<ThemedText style={[styles.groupLabel, { color: colors.textSecondary }]}>用药周期</ThemedText>
|
||||||
|
</View>
|
||||||
|
<View style={styles.dateRowContainer}>
|
||||||
|
<TouchableOpacity
|
||||||
|
activeOpacity={0.85}
|
||||||
|
style={[
|
||||||
|
styles.dateRow,
|
||||||
|
styles.dateRowHalf,
|
||||||
|
{
|
||||||
|
borderColor: softBorderColor,
|
||||||
|
backgroundColor: colors.surface,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
onPress={openStartDatePicker}
|
||||||
|
>
|
||||||
|
<View style={styles.dateLeft}>
|
||||||
|
<Ionicons name="calendar" size={16} color={colors.textSecondary} />
|
||||||
|
<ThemedText style={[styles.dateLabel, { color: colors.textMuted }]}>开始</ThemedText>
|
||||||
|
<ThemedText style={[styles.dateValue, { color: colors.text }]}>
|
||||||
|
{dayjs(startDate).format('MM/DD')}
|
||||||
|
</ThemedText>
|
||||||
|
</View>
|
||||||
|
<Ionicons name="chevron-forward" size={16} color={colors.textSecondary} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
activeOpacity={0.85}
|
||||||
|
style={[
|
||||||
|
styles.dateRow,
|
||||||
|
styles.dateRowHalf,
|
||||||
|
{
|
||||||
|
borderColor: softBorderColor,
|
||||||
|
backgroundColor: colors.surface,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
onPress={openEndDatePicker}
|
||||||
|
>
|
||||||
|
<View style={styles.dateLeft}>
|
||||||
|
<Ionicons name="calendar-outline" size={16} color={colors.textSecondary} />
|
||||||
|
<ThemedText style={[styles.dateLabel, { color: colors.textMuted }]}>结束</ThemedText>
|
||||||
|
<ThemedText style={[styles.dateValue, { color: colors.text }]}>
|
||||||
|
{endDate ? dayjs(endDate).format('MM/DD') : '长期'}
|
||||||
|
</ThemedText>
|
||||||
|
</View>
|
||||||
|
<Ionicons name="chevron-forward" size={16} color={colors.textSecondary} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
case 3:
|
case 3:
|
||||||
@@ -895,30 +983,6 @@ export default function AddMedicationScreen() {
|
|||||||
<View style={styles.contentContainer}>{renderStepContent()}</View>
|
<View style={styles.contentContainer}>{renderStepContent()}</View>
|
||||||
|
|
||||||
<View style={styles.footer}>
|
<View style={styles.footer}>
|
||||||
{showDateField && (
|
|
||||||
<TouchableOpacity
|
|
||||||
activeOpacity={0.85}
|
|
||||||
style={[
|
|
||||||
styles.startDateRow,
|
|
||||||
{
|
|
||||||
borderColor: softBorderColor,
|
|
||||||
backgroundColor: colors.surface,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
onPress={openDatePicker}
|
|
||||||
>
|
|
||||||
<View style={styles.startDateLeft}>
|
|
||||||
<Ionicons name="calendar" size={18} color={colors.textSecondary} />
|
|
||||||
<View>
|
|
||||||
<ThemedText style={[styles.startDateLabel, { color: colors.textMuted }]}>开始日期</ThemedText>
|
|
||||||
<ThemedText style={[styles.startDateValue, { color: colors.text }]}>
|
|
||||||
{dayjs(startDate).format('YYYY 年 MM 月 DD 日')}
|
|
||||||
</ThemedText>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
<Ionicons name="chevron-forward" size={18} color={colors.textSecondary} />
|
|
||||||
</TouchableOpacity>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<View style={styles.footerButtons}>
|
<View style={styles.footerButtons}>
|
||||||
{currentStep > 0 && (
|
{currentStep > 0 && (
|
||||||
@@ -1010,6 +1074,7 @@ export default function AddMedicationScreen() {
|
|||||||
<Pressable style={styles.pickerBackdrop} onPress={() => setDatePickerVisible(false)} />
|
<Pressable style={styles.pickerBackdrop} onPress={() => setDatePickerVisible(false)} />
|
||||||
<View style={[styles.pickerSheet, { backgroundColor: colors.surface }]}
|
<View style={[styles.pickerSheet, { backgroundColor: colors.surface }]}
|
||||||
>
|
>
|
||||||
|
<ThemedText style={[styles.modalTitle, { color: colors.text }]}>选择开始日期</ThemedText>
|
||||||
<DateTimePicker
|
<DateTimePicker
|
||||||
value={datePickerValue}
|
value={datePickerValue}
|
||||||
mode="date"
|
mode="date"
|
||||||
@@ -1045,6 +1110,51 @@ export default function AddMedicationScreen() {
|
|||||||
</View>
|
</View>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
visible={endDatePickerVisible}
|
||||||
|
transparent
|
||||||
|
animationType="fade"
|
||||||
|
onRequestClose={() => setEndDatePickerVisible(false)}
|
||||||
|
>
|
||||||
|
<Pressable style={styles.pickerBackdrop} onPress={() => setEndDatePickerVisible(false)} />
|
||||||
|
<View style={[styles.pickerSheet, { backgroundColor: colors.surface }]}
|
||||||
|
>
|
||||||
|
<ThemedText style={[styles.modalTitle, { color: colors.text }]}>选择结束日期</ThemedText>
|
||||||
|
<DateTimePicker
|
||||||
|
value={endDatePickerValue}
|
||||||
|
mode="date"
|
||||||
|
display={Platform.OS === 'ios' ? 'inline' : 'calendar'}
|
||||||
|
onChange={(event, date) => {
|
||||||
|
if (Platform.OS === 'ios') {
|
||||||
|
if (date) setEndDatePickerValue(date);
|
||||||
|
} else {
|
||||||
|
if (event.type === 'set' && date) {
|
||||||
|
confirmEndDate(date);
|
||||||
|
} else {
|
||||||
|
setEndDatePickerVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{Platform.OS === 'ios' && (
|
||||||
|
<View style={styles.modalActions}>
|
||||||
|
<Pressable
|
||||||
|
onPress={() => setEndDatePickerVisible(false)}
|
||||||
|
style={[styles.modalBtn, { borderColor: softBorderColor }]}
|
||||||
|
>
|
||||||
|
<ThemedText style={[styles.modalBtnText, { color: colors.textSecondary }]}>取消</ThemedText>
|
||||||
|
</Pressable>
|
||||||
|
<Pressable
|
||||||
|
onPress={() => confirmEndDate(endDatePickerValue)}
|
||||||
|
style={[styles.modalBtn, styles.modalBtnPrimary, { backgroundColor: colors.primary }]}
|
||||||
|
>
|
||||||
|
<ThemedText style={[styles.modalBtnText, { color: colors.onPrimary }]}>确定</ThemedText>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
visible={timePickerVisible}
|
visible={timePickerVisible}
|
||||||
transparent
|
transparent
|
||||||
@@ -1500,6 +1610,39 @@ const styles = StyleSheet.create({
|
|||||||
footer: {
|
footer: {
|
||||||
gap: 12,
|
gap: 12,
|
||||||
},
|
},
|
||||||
|
periodHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
dateRowContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: 12,
|
||||||
|
},
|
||||||
|
dateRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: 18,
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
paddingVertical: 10,
|
||||||
|
},
|
||||||
|
dateRowHalf: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
dateLeft: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 8,
|
||||||
|
},
|
||||||
|
dateLabel: {
|
||||||
|
fontSize: 11,
|
||||||
|
},
|
||||||
|
dateValue: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
startDateRow: {
|
startDateRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ export const InfoCard: React.FC<InfoCardProps> = ({
|
|||||||
return (
|
return (
|
||||||
<View style={[
|
<View style={[
|
||||||
styles.infoCardIcon,
|
styles.infoCardIcon,
|
||||||
clickable && styles.clickableIconFallback
|
|
||||||
]}>
|
]}>
|
||||||
<Ionicons name={icon} size={16} color="#4C6EF5" />
|
<Ionicons name={icon} size={16} color="#4C6EF5" />
|
||||||
</View>
|
</View>
|
||||||
@@ -104,21 +103,16 @@ const styles = StyleSheet.create({
|
|||||||
width: 28,
|
width: 28,
|
||||||
height: 28,
|
height: 28,
|
||||||
borderRadius: 14,
|
borderRadius: 14,
|
||||||
backgroundColor: '#EEF1FF',
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
clickableIconFallback: {
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: 'rgba(76, 110, 245, 0.3)',
|
|
||||||
},
|
|
||||||
infoCardLabel: {
|
infoCardLabel: {
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
color: '#6B7280',
|
color: '#6B7280',
|
||||||
marginTop: 8,
|
marginTop: 8,
|
||||||
},
|
},
|
||||||
infoCardValue: {
|
infoCardValue: {
|
||||||
fontSize: 16,
|
fontSize: 14,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
color: '#1F2933',
|
color: '#1F2933',
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user