feat(medications): 优化药品管理功能和登录流程

- 更新默认药品图片为专用图标
- 移除未使用的 loading 状态选择器
- 优化 Apple 登录按钮样式,支持毛玻璃效果和加载状态
- 添加登录成功后返回功能(shouldBack 参数)
- 药品详情页添加信息卡片点击交互
- 添加药品添加页面的登录状态检查
- 增强时间选择器错误处理和数据验证
- 修复药品图片显示逻辑,支持网络图片
- 优化药品卡片样式和布局
- 添加图片加载错误处理
This commit is contained in:
richarjiang
2025-11-11 10:02:37 +08:00
parent 0594831c9f
commit 50525f82a1
8 changed files with 263 additions and 70 deletions

View File

@@ -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);