feat(medications): 添加药品详情页面和删除功能

新增药品详情页面,支持查看药品信息、编辑备注、切换提醒状态和删除药品
- 创建动态路由页面 /medications/[medicationId].tsx 展示药品详细信息
- 添加语音输入备注功能,支持 iOS 语音识别
- 实现药品删除确认对话框和删除操作
- 优化药品卡片点击跳转详情页面的交互
- 添加删除操作的加载状态和错误处理
- 改进药品管理页面的开关状态显示和加载指示器
This commit is contained in:
richarjiang
2025-11-10 14:46:13 +08:00
parent 25b8e45af8
commit 0594831c9f
6 changed files with 1375 additions and 46 deletions

View File

@@ -59,7 +59,6 @@ export default function ManageMedicationsScreen() {
const [activeFilter, setActiveFilter] = useState<FilterType>('all');
const [pendingMedicationId, setPendingMedicationId] = useState<string | null>(null);
const updateLoading = loading.update;
const listLoading = loading.medications && medications.length === 0;
useFocusEffect(
@@ -116,7 +115,7 @@ export default function ManageMedicationsScreen() {
);
// 创建独立的药品卡片组件,使用 React.memo 优化渲染
const MedicationCard = React.memo(({ medication }: { medication: Medication }) => {
const MedicationCard = React.memo(({ medication, onPress }: { medication: Medication; onPress: () => void }) => {
const dosageLabel = `${medication.dosageValue} ${medication.dosageUnit || ''} ${FORM_LABELS[medication.form] ?? ''}`.trim();
const frequencyLabel = `${medication.repeatPattern === 'daily' ? '每日' : medication.repeatPattern === 'weekly' ? '每周' : '自定义'} | ${dosageLabel}`;
const startDateLabel = dayjs(medication.startDate).isValid()
@@ -127,7 +126,11 @@ export default function ManageMedicationsScreen() {
: `${medication.timesPerDay} 次/日`;
return (
<View style={[styles.card, { backgroundColor: colors.surface }]}>
<TouchableOpacity
style={[styles.card, { backgroundColor: colors.surface }]}
activeOpacity={0.9}
onPress={onPress}
>
<View style={styles.cardInfo}>
<Image
source={medication.photoUrl ? { uri: medication.photoUrl } : DEFAULT_IMAGE}
@@ -144,15 +147,24 @@ export default function ManageMedicationsScreen() {
</ThemedText>
</View>
</View>
<Switch
value={medication.isActive}
onValueChange={(value) => handleToggleMedication(medication, value)}
disabled={updateLoading || pendingMedicationId === medication.id}
trackColor={{ false: '#D9D9D9', true: colors.primary }}
thumbColor={medication.isActive ? '#fff' : '#fff'}
ios_backgroundColor="#D9D9D9"
/>
</View>
<View style={styles.switchContainer}>
<Switch
value={medication.isActive}
onValueChange={(value) => handleToggleMedication(medication, value)}
disabled={pendingMedicationId === medication.id}
trackColor={{ false: '#D9D9D9', true: colors.primary }}
thumbColor={medication.isActive ? '#fff' : '#fff'}
ios_backgroundColor="#D9D9D9"
/>
{pendingMedicationId === medication.id && (
<ActivityIndicator
size="small"
color={colors.primary}
style={styles.switchLoading}
/>
)}
</View>
</TouchableOpacity>
);
}, (prevProps, nextProps) => {
// 自定义比较函数,只有当药品的 isActive 状态或 ID 改变时才重新渲染
@@ -166,11 +178,24 @@ export default function ManageMedicationsScreen() {
MedicationCard.displayName = 'MedicationCard';
const handleOpenMedicationDetails = useCallback((medicationId: string) => {
router.push({
pathname: '/medications/[medicationId]',
params: { medicationId },
});
}, []);
const renderMedicationCard = useCallback(
(medication: Medication) => {
return <MedicationCard key={medication.id} medication={medication} />;
return (
<MedicationCard
key={medication.id}
medication={medication}
onPress={() => handleOpenMedicationDetails(medication.id)}
/>
);
},
[handleToggleMedication, pendingMedicationId, updateLoading, colors]
[handleToggleMedication, pendingMedicationId, colors, handleOpenMedicationDetails]
);
return (
@@ -398,4 +423,13 @@ const styles = StyleSheet.create({
fontSize: 14,
textAlign: 'center',
},
switchContainer: {
alignItems: 'center',
justifyContent: 'center',
position: 'relative',
},
switchLoading: {
position: 'absolute',
marginLeft: 30, // 确保加载指示器显示在开关旁边
},
});