feat(medications): 优化药品管理功能和登录流程
- 更新默认药品图片为专用图标 - 移除未使用的 loading 状态选择器 - 优化 Apple 登录按钮样式,支持毛玻璃效果和加载状态 - 添加登录成功后返回功能(shouldBack 参数) - 药品详情页添加信息卡片点击交互 - 添加药品添加页面的登录状态检查 - 增强时间选择器错误处理和数据验证 - 修复药品图片显示逻辑,支持网络图片 - 优化药品卡片样式和布局 - 添加图片加载错误处理
This commit is contained in:
@@ -3,6 +3,7 @@ import { HeaderBar } from '@/components/ui/HeaderBar';
|
||||
import { IconSymbol } from '@/components/ui/IconSymbol';
|
||||
import { Colors } from '@/constants/Colors';
|
||||
import { useAppDispatch } from '@/hooks/redux';
|
||||
import { useAuthGuard } from '@/hooks/useAuthGuard';
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
import { useCosUpload } from '@/hooks/useCosUpload';
|
||||
import { createMedicationAction, fetchMedicationRecords, fetchMedications } from '@/store/medicationsSlice';
|
||||
@@ -86,10 +87,40 @@ const DEFAULT_TIME_PRESETS = ['08:00', '12:00', '18:00', '22:00'];
|
||||
const formatTime = (date: Date) => dayjs(date).format('HH:mm');
|
||||
const getDefaultTimeByIndex = (index: number) => DEFAULT_TIME_PRESETS[index % DEFAULT_TIME_PRESETS.length];
|
||||
const createDateFromTime = (time: string) => {
|
||||
const [hour, minute] = time.split(':').map((val) => parseInt(val, 10));
|
||||
const next = new Date();
|
||||
next.setHours(hour || 0, minute || 0, 0, 0);
|
||||
return next;
|
||||
try {
|
||||
if (!time || typeof time !== 'string') {
|
||||
console.warn('[MEDICATION] Invalid time string provided:', time);
|
||||
return new Date();
|
||||
}
|
||||
|
||||
const parts = time.split(':');
|
||||
if (parts.length !== 2) {
|
||||
console.warn('[MEDICATION] Invalid time format:', time);
|
||||
return new Date();
|
||||
}
|
||||
|
||||
const hour = parseInt(parts[0], 10);
|
||||
const minute = parseInt(parts[1], 10);
|
||||
|
||||
if (isNaN(hour) || isNaN(minute) || hour < 0 || hour > 23 || minute < 0 || minute > 59) {
|
||||
console.warn('[MEDICATION] Invalid time values:', { hour, minute });
|
||||
return new Date();
|
||||
}
|
||||
|
||||
const next = new Date();
|
||||
next.setHours(hour, minute, 0, 0);
|
||||
|
||||
// 验证日期是否有效
|
||||
if (isNaN(next.getTime())) {
|
||||
console.error('[MEDICATION] Failed to create valid date');
|
||||
return new Date();
|
||||
}
|
||||
|
||||
return next;
|
||||
} catch (error) {
|
||||
console.error('[MEDICATION] Error in createDateFromTime:', error);
|
||||
return new Date();
|
||||
}
|
||||
};
|
||||
|
||||
export default function AddMedicationScreen() {
|
||||
@@ -103,6 +134,8 @@ export default function AddMedicationScreen() {
|
||||
const [medicationName, setMedicationName] = useState('');
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const { upload, uploading } = useCosUpload({ prefix: 'images/medications' });
|
||||
// 获取登录验证相关的功能
|
||||
const { ensureLoggedIn } = useAuthGuard();
|
||||
const softBorderColor = useMemo(() => withAlpha(colors.border, 0.45), [colors.border]);
|
||||
const fadedBorderFill = useMemo(() => withAlpha(colors.border, 0.2), [colors.border]);
|
||||
const glassPrimaryTint = useMemo(() => withAlpha(colors.primary, theme === 'dark' ? 0.55 : 0.45), [colors.primary, theme]);
|
||||
@@ -231,6 +264,16 @@ export default function AddMedicationScreen() {
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
// 先检查用户是否已登录,如果未登录则跳转到登录页面
|
||||
const isLoggedIn = await ensureLoggedIn({
|
||||
shouldBack: true
|
||||
});
|
||||
if (!isLoggedIn) {
|
||||
// 未登录,ensureLoggedIn 已处理跳转,直接返回
|
||||
setIsSubmitting(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建药物数据,符合 CreateMedicationDto 接口
|
||||
const medicationData = {
|
||||
name: medicationName.trim(),
|
||||
@@ -289,6 +332,7 @@ export default function AddMedicationScreen() {
|
||||
startDate,
|
||||
note,
|
||||
dispatch,
|
||||
ensureLoggedIn,
|
||||
]);
|
||||
|
||||
const handlePrev = useCallback(() => {
|
||||
@@ -378,29 +422,51 @@ export default function AddMedicationScreen() {
|
||||
|
||||
const openTimePicker = useCallback(
|
||||
(index?: number) => {
|
||||
if (typeof index === 'number') {
|
||||
setEditingTimeIndex(index);
|
||||
setTimePickerDate(createDateFromTime(medicationTimes[index]));
|
||||
} else {
|
||||
setEditingTimeIndex(null);
|
||||
setTimePickerDate(createDateFromTime(getDefaultTimeByIndex(medicationTimes.length)));
|
||||
try {
|
||||
if (typeof index === 'number') {
|
||||
if (index >= 0 && index < medicationTimes.length) {
|
||||
setEditingTimeIndex(index);
|
||||
setTimePickerDate(createDateFromTime(medicationTimes[index]));
|
||||
} else {
|
||||
console.error('[MEDICATION] Invalid time index:', index);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
setEditingTimeIndex(null);
|
||||
setTimePickerDate(createDateFromTime(getDefaultTimeByIndex(medicationTimes.length)));
|
||||
}
|
||||
setTimePickerVisible(true);
|
||||
} catch (error) {
|
||||
console.error('[MEDICATION] Error in openTimePicker:', error);
|
||||
}
|
||||
setTimePickerVisible(true);
|
||||
},
|
||||
[medicationTimes]
|
||||
);
|
||||
|
||||
const confirmTime = useCallback(
|
||||
(date: Date) => {
|
||||
const nextValue = formatTime(date);
|
||||
setMedicationTimes((prev) => {
|
||||
if (editingTimeIndex == null) {
|
||||
return [...prev, nextValue];
|
||||
try {
|
||||
if (!date || isNaN(date.getTime())) {
|
||||
console.error('[MEDICATION] Invalid date provided to confirmTime');
|
||||
setTimePickerVisible(false);
|
||||
setEditingTimeIndex(null);
|
||||
return;
|
||||
}
|
||||
return prev.map((time, idx) => (idx === editingTimeIndex ? nextValue : time));
|
||||
});
|
||||
setTimePickerVisible(false);
|
||||
setEditingTimeIndex(null);
|
||||
|
||||
const nextValue = formatTime(date);
|
||||
setMedicationTimes((prev) => {
|
||||
if (editingTimeIndex == null) {
|
||||
return [...prev, nextValue];
|
||||
}
|
||||
return prev.map((time, idx) => (idx === editingTimeIndex ? nextValue : time));
|
||||
});
|
||||
setTimePickerVisible(false);
|
||||
setEditingTimeIndex(null);
|
||||
} catch (error) {
|
||||
console.error('[MEDICATION] Error in confirmTime:', error);
|
||||
setTimePickerVisible(false);
|
||||
setEditingTimeIndex(null);
|
||||
}
|
||||
},
|
||||
[editingTimeIndex]
|
||||
);
|
||||
@@ -915,15 +981,26 @@ export default function AddMedicationScreen() {
|
||||
visible={timePickerVisible}
|
||||
transparent
|
||||
animationType="fade"
|
||||
onRequestClose={() => setTimePickerVisible(false)}
|
||||
onRequestClose={() => {
|
||||
setTimePickerVisible(false);
|
||||
setEditingTimeIndex(null);
|
||||
}}
|
||||
>
|
||||
<Pressable style={styles.pickerBackdrop} onPress={() => setTimePickerVisible(false)} />
|
||||
<View style={[styles.pickerSheet, { backgroundColor: colors.surface }]}
|
||||
>
|
||||
<Pressable
|
||||
style={styles.pickerBackdrop}
|
||||
onPress={() => {
|
||||
setTimePickerVisible(false);
|
||||
setEditingTimeIndex(null);
|
||||
}}
|
||||
/>
|
||||
<View style={[styles.pickerSheet, { backgroundColor: colors.surface }]}>
|
||||
<ThemedText style={[styles.modalTitle, { color: colors.text }]}>
|
||||
{editingTimeIndex !== null ? '修改提醒时间' : '添加提醒时间'}
|
||||
</ThemedText>
|
||||
<DateTimePicker
|
||||
value={timePickerDate}
|
||||
mode="time"
|
||||
display={Platform.OS === 'ios' ? 'spinner' : 'clock'}
|
||||
display={Platform.OS === 'ios' ? 'spinner' : 'default'}
|
||||
onChange={(event, date) => {
|
||||
if (Platform.OS === 'ios') {
|
||||
if (date) setTimePickerDate(date);
|
||||
|
||||
Reference in New Issue
Block a user