From db8b50f6d787c470b05ca5e1efcb980ed0656de4 Mon Sep 17 00:00:00 2001 From: richarjiang Date: Mon, 27 Oct 2025 14:43:19 +0800 Subject: [PATCH] =?UTF-8?q?feat(membership):=20=E9=87=8D=E6=9E=84=E4=BC=9A?= =?UTF-8?q?=E5=91=98=E6=9D=83=E7=9B=8A=E5=AF=B9=E6=AF=94=E8=A1=A8=E5=B9=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=B4=AD=E4=B9=B0=E7=95=8C=E9=9D=A2=E5=B8=83?= =?UTF-8?q?=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重构权益对比数据结构,支持独占、有限、无限三种权限类型 - 新增权限图标显示逻辑,区分VIP和普通用户权限状态 - 优化会员卡片布局,采用三段式布局提升视觉效果 - 实现悬浮购买按钮,支持Liquid Glass毛玻璃效果 - 增强购买流程验证,添加自动选择产品和详细错误处理 - 调整界面间距和样式,提升整体用户体验 --- components/model/MembershipModal.tsx | 409 +++++++++++++++++++++------ 1 file changed, 320 insertions(+), 89 deletions(-) diff --git a/components/model/MembershipModal.tsx b/components/model/MembershipModal.tsx index 45b4601..886e05d 100644 --- a/components/model/MembershipModal.tsx +++ b/components/model/MembershipModal.tsx @@ -74,13 +74,96 @@ const DEFAULT_PLANS: MembershipPlan[] = [ }, ]; +// 权限类型枚举 +type PermissionType = 'exclusive' | 'limited' | 'unlimited'; -const BENEFIT_COMPARISON = [ - { title: 'AI拍照记录热量', vip: true, regular: false }, - { title: 'AI拍照识别包装', vip: true, regular: false }, - { title: '私人饮食建议', vip: true, regular: false }, - { title: '定制健身训练', vip: true, regular: false }, - { title: '每日健康提醒', vip: true, regular: true }, +// 权限配置接口 +interface PermissionConfig { + type: PermissionType; + text: string; + vipText?: string; // VIP用户的特殊文案,可选 +} + +// 权益对比项接口 +interface BenefitItem { + title: string; + description?: string; // 功能描述,可选 + vip: PermissionConfig; + regular: PermissionConfig; +} + +// 权益对比配置 +const BENEFIT_COMPARISON: BenefitItem[] = [ + { + title: 'AI拍照记录热量', + description: '通过拍照识别食物并自动记录热量', + vip: { + type: 'unlimited', + text: '无限次使用', + vipText: '无限次使用' + }, + regular: { + type: 'limited', + text: '有限次使用', + vipText: '每日3次' + } + }, + { + title: 'AI拍照识别包装', + description: '识别食品包装上的营养成分信息', + vip: { + type: 'unlimited', + text: '无限次使用', + vipText: '无限次使用' + }, + regular: { + type: 'limited', + text: '有限次使用', + vipText: '每日5次' + } + }, + { + title: '每日健康提醒', + description: '根据个人目标提供个性化健康提醒', + vip: { + type: 'unlimited', + text: '完全支持', + vipText: '智能提醒' + }, + regular: { + type: 'unlimited', + text: '基础提醒', + vipText: '基础提醒' + } + }, + { + title: 'AI教练对话', + description: '与AI健康教练进行个性化对话咨询', + vip: { + type: 'unlimited', + text: '无限次对话', + vipText: '深度分析' + }, + regular: { + type: 'limited', + text: '有限次对话', + vipText: '每日10次' + } + }, + { + title: '体态评估', + description: '通过照片分析体态问题并提供改善建议', + vip: { + type: 'exclusive', + text: '完全支持', + vipText: '专业评估' + }, + regular: { + type: 'exclusive', + text: '不可使用', + vipText: '不可使用' + } + } ]; const PLAN_STYLE_CONFIG: Record = { @@ -98,6 +181,29 @@ const PLAN_STYLE_CONFIG: Record { + switch (type) { + case 'exclusive': + return isVip ? ( + + ) : ( + + ); + case 'limited': + return isVip ? ( + + ) : ( + + ); + case 'unlimited': + return ( + + ); + default: + return ; + } +}; export function MembershipModal({ visible, onClose, onPurchaseSuccess }: MembershipModalProps) { const [selectedProduct, setSelectedProduct] = useState(null); @@ -215,7 +321,14 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members // 获取产品后,检查用户的购买记录并自动选中对应套餐 await checkAndSelectActivePlan(productsToUse); - setSelectedProduct(current => current ?? (productsToUse[0] ?? null)); + // 确保始终有一个选中的产品 + setSelectedProduct(current => { + // 如果已经有选中的产品,保持不变 + if (current) return current; + + // 否则选择第一个可用产品 + return productsToUse[0] ?? null; + }); } catch (e: any) { // 安全地处理错误对象,避免循环引用 const errorData = { @@ -431,6 +544,15 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members const handlePurchase = async () => { + // 添加调试日志 + log.info('handlePurchase 被调用', { + loading, + productsLength: products.length, + selectedProductId: selectedProduct?.identifier, + selectedProductTitle: selectedProduct?.title, + agreementAccepted + }); + // 验证是否已同意协议 if (!agreementAccepted) { Alert.alert( @@ -446,6 +568,16 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members return; } + // 如果没有选中的产品但有可用产品,自动选择第一个 + if (!selectedProduct && products.length > 0) { + log.info('自动选择第一个可用产品', { + firstProductId: products[0]?.identifier, + firstProductTitle: products[0]?.title + }); + setSelectedProduct(products[0]); + return; // 返回让用户确认选择后再点击 + } + // 验证是否选择了产品 if (!selectedProduct) { Alert.alert( @@ -740,20 +872,27 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members end={{ x: 1, y: 1 }} style={styles.planCardGradient} > - {plan.tag && ( - - {plan.tag} - - )} + + {plan.tag && ( + + {plan.tag} + + )} + {displayTitle} + - {displayTitle} - - {priceLabel || '--'} - - {plan.originalPrice && ( - {plan.originalPrice} - )} - {plan.subtitle} + + + {priceLabel || '--'} + + {plan.originalPrice && ( + {plan.originalPrice} + )} + + + + {plan.subtitle} + ); @@ -807,7 +946,9 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members showsVerticalScrollIndicator={false} > - + @@ -860,20 +1001,27 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members index % 2 === 1 && styles.tableRowAlt, ]} > - {row.title} - - {row.vip ? ( - - ) : ( - + + {row.title} + {row.description && ( + {row.description} )} + + {/* VIP 权限列 */} + + + {getPermissionIcon(row.vip.type, true)} + {row.vip.vipText || row.vip.text} + + + + {/* 普通用户权限列 */} - {row.regular ? ( - - ) : ( - - )} + + {getPermissionIcon(row.regular.type, false)} + {row.regular.vipText || row.regular.text} + ))} @@ -881,38 +1029,6 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members - - 667+人正在定制私人计划 - - - - {loading ? ( - - - 正在处理购买... - - ) : ( - 立即订阅 - )} - - + + {/* 悬浮购买按钮 */} + + {isLiquidGlassAvailable() ? ( + + + {loading ? ( + + + 正在处理购买... + + ) : ( + 立即订阅 + )} + + + ) : ( + + + {loading ? ( + + + 正在处理购买... + + ) : ( + 立即订阅 + )} + + + )} + @@ -991,7 +1184,7 @@ const styles = StyleSheet.create({ }, modalContentContainer: { paddingHorizontal: 20, - paddingBottom: 32, + paddingBottom: 100, // 增加底部内边距,避免内容被悬浮按钮遮挡 paddingTop: 16, }, floatingBackButton: { @@ -1080,9 +1273,11 @@ const styles = StyleSheet.create({ elevation: 4, }, planCardGradient: { + flex: 1, paddingHorizontal: 16, paddingVertical: 18, minHeight: 170, + justifyContent: 'space-between', }, planTag: { alignSelf: 'flex-start', @@ -1111,14 +1306,26 @@ const styles = StyleSheet.create({ fontSize: 13, color: '#8E8EA1', textDecorationLine: 'line-through', - marginTop: 4, + marginTop: 2, }, planCardDescription: { fontSize: 12, color: '#6C6C77', - marginTop: 12, lineHeight: 17, }, + planCardTopSection: { + flex: 1, + justifyContent: 'flex-start', + }, + planCardMiddleSection: { + flex: 1, + justifyContent: 'center', + alignItems: 'flex-start', + }, + planCardBottomSection: { + flex: 1, + justifyContent: 'flex-end', + }, tipsContainer: { flexDirection: 'row', alignItems: 'center', @@ -1163,15 +1370,18 @@ const styles = StyleSheet.create({ color: '#3E3E44', }, tableTitleCell: { - flex: 1.3, + flex: 1.5, + justifyContent: 'center', }, tableVipCell: { flex: 0.8, alignItems: 'center', + justifyContent: 'center', }, tableNormalCell: { flex: 0.8, alignItems: 'center', + justifyContent: 'center', }, tableRowAlt: { backgroundColor: '#FBFBFF', @@ -1188,26 +1398,27 @@ const styles = StyleSheet.create({ shadowOffset: { width: 0, height: 6 }, elevation: 1, }, - noticeBanner: { - backgroundColor: '#FFE7E0', - borderRadius: 12, - paddingVertical: 10, - paddingHorizontal: 16, - marginBottom: 16, - }, - noticeText: { - color: '#D35400', - fontSize: 13, - textAlign: 'center', - fontWeight: '600', - }, purchaseButton: { - backgroundColor: '#151515', borderRadius: 28, height: 52, justifyContent: 'center', alignItems: 'center', - marginBottom: 16, + overflow: 'hidden', + }, + purchaseButtonContent: { + flex: 1, + width: '100%', + justifyContent: 'center', + alignItems: 'center', + }, + floatingPurchaseContainer: { + position: 'absolute', + bottom: 34, // 底部安全区域 + left: 20, + right: 20, + }, + fallbackPurchaseButton: { + backgroundColor: '#151515', }, disabledButton: { backgroundColor: '#C6C6C8', @@ -1228,23 +1439,23 @@ const styles = StyleSheet.create({ flexDirection: 'row', alignItems: 'center', justifyContent: 'center', - flexWrap: 'wrap', + flexWrap: 'nowrap', marginBottom: 16, }, agreementPrefix: { - fontSize: 11, + fontSize: 10, color: '#666672', - marginHorizontal: 6, + marginRight: 4, }, agreementLink: { - fontSize: 11, + fontSize: 10, color: '#E91E63', textDecorationLine: 'underline', fontWeight: '500', - marginHorizontal: 4, + marginHorizontal: 2, }, agreementSeparator: { - fontSize: 11, + fontSize: 10, color: '#A0A0B0', marginHorizontal: 2, }, @@ -1271,4 +1482,24 @@ const styles = StyleSheet.create({ disabledPlanCard: { opacity: 0.5, }, + // 新增样式:权限相关 + tableDescriptionText: { + fontSize: 11, + color: '#8E8E93', + marginTop: 2, + lineHeight: 14, + }, + permissionContainer: { + alignItems: 'center', + justifyContent: 'center', + flexDirection: 'column', + paddingVertical: 4, + }, + permissionText: { + fontSize: 10, + color: '#6B6B73', + marginTop: 4, + textAlign: 'center', + lineHeight: 12, + }, });