From d9975813cbf40bafcabc8dc9598142a5ec53b311 Mon Sep 17 00:00:00 2001 From: richarjiang Date: Tue, 11 Nov 2025 14:40:26 +0800 Subject: [PATCH] =?UTF-8?q?feat(medications):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=8D=AF=E5=93=81=E5=9B=BE=E7=89=87=E9=A2=84=E8=A7=88=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=B9=B6=E4=BC=98=E5=8C=96InfoCard=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在药品详情页面集成 react-native-image-viewing 实现图片全屏预览 - 添加图片预览提示图标,提升用户交互体验 - 优化 InfoCard 组件渲染逻辑,简化代码结构 - 调整药品图片样式,增加圆角效果并优化尺寸比例 - 为可点击的 InfoCard 图标和箭头添加玻璃态效果支持 --- app/medications/[medicationId].tsx | 102 ++++++++++++++++++++- components/ui/InfoCard.tsx | 138 +++++++++++++++++------------ 2 files changed, 179 insertions(+), 61 deletions(-) diff --git a/app/medications/[medicationId].tsx b/app/medications/[medicationId].tsx index 423af81..b78d7b1 100644 --- a/app/medications/[medicationId].tsx +++ b/app/medications/[medicationId].tsx @@ -38,6 +38,7 @@ import { TouchableOpacity, View, } from 'react-native'; +import ImageViewing from 'react-native-image-viewing'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; const DEFAULT_IMAGE = require('@/assets/images/medicine/image-medicine.png'); @@ -79,6 +80,7 @@ export default function MedicationDetailScreen() { const [keyboardHeight, setKeyboardHeight] = useState(0); const [deleteSheetVisible, setDeleteSheetVisible] = useState(false); const [deleteLoading, setDeleteLoading] = useState(false); + const [showImagePreview, setShowImagePreview] = useState(false); // 剂量选择相关状态 const [dosagePickerVisible, setDosagePickerVisible] = useState(false); @@ -518,6 +520,12 @@ export default function MedicationDetailScreen() { } }, [deleteLoading, dispatch, medication, router]); + const handleImagePreview = useCallback(() => { + if (medication?.photoUrl) { + setShowImagePreview(true); + } + }, [medication?.photoUrl]); + const handleStartDatePress = useCallback(() => { Alert.alert('开始日期', `开始服药日期:${startDateLabel}`); }, [startDateLabel]); @@ -743,13 +751,23 @@ export default function MedicationDetailScreen() { > - + - + {medication.photoUrl && ( + + + + )} + {medication.name} @@ -1305,6 +1323,35 @@ export default function MedicationDetailScreen() { loading={deleteLoading} /> ) : null} + + {/* 图片预览 */} + {medication?.photoUrl && ( + setShowImagePreview(false)} + swipeToCloseEnabled={true} + doubleTapToZoomEnabled={true} + HeaderComponent={() => ( + + + {medication.name} + + + )} + FooterComponent={() => ( + + setShowImagePreview(false)} + > + 关闭 + + + )} + /> + )} ); } @@ -1381,10 +1428,12 @@ const styles = StyleSheet.create({ backgroundColor: '#F2F2F2', alignItems: 'center', justifyContent: 'center', + position: 'relative', }, heroImage: { - width: '80%', - height: '80%', + width: '60%', + height: '60%', + borderRadius: '20%' }, heroTitle: { fontSize: 20, @@ -1709,4 +1758,49 @@ const styles = StyleSheet.create({ fontSize: 13, fontWeight: '600', }, + imagePreviewHint: { + position: 'absolute', + top: 4, + right: 4, + backgroundColor: 'rgba(0, 0, 0, 0.5)', + borderRadius: 10, + padding: 4, + }, + // ImageViewing 组件样式 + imageViewerHeader: { + position: 'absolute', + top: 60, + left: 20, + right: 20, + backgroundColor: 'rgba(0, 0, 0, 0.7)', + borderRadius: 12, + paddingHorizontal: 16, + paddingVertical: 12, + zIndex: 1, + }, + imageViewerHeaderText: { + color: '#FFF', + fontSize: 14, + fontWeight: '500', + textAlign: 'center', + }, + imageViewerFooter: { + position: 'absolute', + bottom: 60, + left: 20, + right: 20, + alignItems: 'center', + zIndex: 1, + }, + imageViewerFooterButton: { + backgroundColor: 'rgba(0, 0, 0, 0.7)', + paddingHorizontal: 24, + paddingVertical: 12, + borderRadius: 20, + }, + imageViewerFooterButtonText: { + color: '#FFF', + fontSize: 16, + fontWeight: '500', + }, }); diff --git a/components/ui/InfoCard.tsx b/components/ui/InfoCard.tsx index d3f4f1a..206a455 100644 --- a/components/ui/InfoCard.tsx +++ b/components/ui/InfoCard.tsx @@ -24,10 +24,72 @@ export const InfoCard: React.FC = ({ onPress, clickable = false, glassEffectStyle = 'clear', - + tintColor, }) => { const isGlassAvailable = isLiquidGlassAvailable(); + // 渲染图标按钮 - 只在可点击时应用 GlassView + const renderIcon = () => { + if (clickable && isGlassAvailable) { + return ( + + + + ); + } + + return ( + + + + ); + }; + + // 渲染箭头 - 只在可点击时显示并应用 GlassView + const renderArrow = () => { + if (!clickable) return null; + + if (isGlassAvailable) { + return ( + + + + ); + } + + return ( + + + + ); + }; + + // 卡片内容 + const cardContent = ( + + {renderArrow()} + {renderIcon()} + {label} + {value} + + ); + // 如果可点击且有onPress回调,使用TouchableOpacity包装 if (clickable && onPress) { return ( @@ -36,35 +98,7 @@ export const InfoCard: React.FC = ({ onPress={onPress} activeOpacity={0.7} > - {isGlassAvailable ? ( - - - - - - - - {label} - {value} - - ) : ( - - - - - - - - {label} - {value} - - )} + {cardContent} ); } @@ -72,32 +106,7 @@ export const InfoCard: React.FC = ({ // 不可点击的版本 return ( - {isGlassAvailable ? ( - - - - - {label} - {value} - - ) : ( - - - - - {label} - {value} - - )} + {cardContent} ); }; @@ -118,13 +127,23 @@ const styles = StyleSheet.create({ shadowOffset: { width: 0, height: 4 }, elevation: 2, position: 'relative', - overflow: 'hidden', // 保证玻璃边界圆角效果 }, infoCardArrow: { position: 'absolute', top: 12, right: 12, zIndex: 1, + width: 24, + height: 24, + borderRadius: 12, + alignItems: 'center', + justifyContent: 'center', + overflow: 'hidden', // 保证玻璃边界圆角效果 + }, + arrowFallback: { + backgroundColor: 'rgba(255, 255, 255, 0.9)', + borderWidth: 1, + borderColor: 'rgba(0, 0, 0, 0.1)', }, infoCardIcon: { width: 28, @@ -133,6 +152,11 @@ const styles = StyleSheet.create({ backgroundColor: '#EEF1FF', alignItems: 'center', justifyContent: 'center', + overflow: 'hidden', // 保证玻璃边界圆角效果 + }, + clickableIconFallback: { + borderWidth: 1, + borderColor: 'rgba(76, 110, 245, 0.3)', }, infoCardLabel: { fontSize: 13,