feat(membership): 重构会员购买界面并添加图标库使用规范

- 将 MaterialIcons 替换为 Ionicons 以保持图标库一致性
- 重新设计会员购买界面,采用分段卡片布局和权益对比表格
- 添加 Liquid Glass 兼容的悬浮返回按钮
- 优化套餐卡片样式,使用渐变背景和标签展示
- 添加会员权益对比功能,清晰展示 VIP 与普通用户差异
- 更新任务文档,记录图标库使用规范和按钮组件 Liquid Glass 兼容性实现模式
This commit is contained in:
richarjiang
2025-10-27 08:19:15 +08:00
parent 2e11f694f8
commit 82edb2593c
2 changed files with 600 additions and 384 deletions

View File

@@ -1,5 +1,141 @@
# 常见任务和模式 # 常见任务和模式
## 图标库使用规范 - 禁止使用 MaterialIcons
**最后更新**: 2025-10-24
### 重要规则
**项目中不允许使用 MaterialIcons**,所有图标必须使用 Ionicons 以保持图标库的一致性。
### 问题描述
在项目中发现使用 MaterialIcons 的情况,需要将所有 MaterialIcons 替换为 Ionicons以保持图标库的一致性。
### 解决方案
将所有 MaterialIcons 导入和使用替换为对应的 Ionicons。
### 实现模式
#### 1. 替换导入语句
```typescript
// ❌ 禁止使用
import { MaterialIcons } from '@expo/vector-icons';
// ✅ 正确写法
import { Ionicons } from '@expo/vector-icons';
```
#### 2. 替换图标名称和属性
```typescript
// ❌ 禁止使用
<MaterialIcons name="arrow-back-ios" size={20} color="#333" />
// ✅ 正确写法 - 使用 HeaderBar 中的返回按钮实现
<Ionicons name="chevron-back" size={24} color="#333" />
```
#### 3. 常见图标映射
- `arrow-back-ios``chevron-back` (返回按钮)
- `auto-awesome``star` (星星/自动推荐)
- `tips-and-updates``bulb` (提示/建议)
- `fact-check``checkbox` (检查/确认)
- `check-circle``checkmark-circle` (勾选圆圈)
- `remove``remove` (移除/删除,名称相同)
### 重要注意事项
1. **图标大小调整**Ionicons 和 MaterialIcons 的默认大小可能不同,需要适当调整
2. **图标名称差异**:两个图标库的图标名称不同,需要找到对应的功能图标
3. **样式一致性**:确保替换后的图标在视觉上与原设计保持一致
4. **Liquid Glass 兼容性**:替换后的图标需要继续支持 Liquid Glass 效果
5. **代码审查**:在代码审查中需要特别检查是否使用了 MaterialIcons
### 参考实现
- `components/ui/HeaderBar.tsx` - 返回按钮的标准实现
- `components/model/MembershipModal.tsx` - 完整的 MaterialIcons 替换示例
## 按钮组件 Liquid Glass 兼容性
**最后更新**: 2025-10-24
### 重要原则
**所有按钮组件都需要尝试兼容 Liquid Glass**,这是项目的设计要求。
### 实现模式
#### 1. 导入必要的组件
```typescript
import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect';
```
#### 2. 检查设备支持情况
```typescript
const isGlassAvailable = isLiquidGlassAvailable();
```
#### 3. 实现条件渲染的按钮
```typescript
<TouchableOpacity
onPress={handlePress}
disabled={isLoading}
activeOpacity={0.7}
>
{isLiquidGlassAvailable() ? (
<GlassView
style={styles.button}
glassEffectStyle="clear" // 或 "regular"
tintColor="rgba(255, 255, 255, 0.3)" // 自定义色调
isInteractive={true} // 启用交互反馈
>
<Ionicons name="icon-name" size={20} color="#333" />
</GlassView>
) : (
<View style={[styles.button, styles.fallbackButton]}>
<Ionicons name="icon-name" size={20} color="#333" />
</View>
)}
</TouchableOpacity>
```
#### 4. 定义样式
```typescript
const styles = StyleSheet.create({
button: {
width: 40,
height: 40,
borderRadius: 20, // 圆形按钮
alignItems: 'center',
justifyContent: 'center',
overflow: 'hidden', // 保证玻璃边界圆角效果
// 其他通用样式...
},
fallbackButton: {
backgroundColor: 'rgba(255, 255, 255, 0.9)',
borderWidth: 1,
borderColor: 'rgba(255, 255, 255, 0.3)',
},
});
```
### 重要注意事项
1. **兼容性检查**:必须使用 `isLiquidGlassAvailable()` 检查设备支持情况
2. **overflow: 'hidden'**GlassView 组件需要设置此属性以保证圆角效果
3. **降级样式**:为不支持 Liquid Glass 的设备提供视觉上相似的替代方案
4. **交互反馈**:设置 `isInteractive={true}` 启用原生的触觉反馈
5. **图标居中**:确保使用 `alignItems: 'center'``justifyContent: 'center'` 使图标完全居中
6. **色调自定义**:通过 `tintColor` 属性自定义按钮的颜色主题
### 常用配置
- **glassEffectStyle**: "clear"(透明)或 "regular"(常规)
- **tintColor**: 根据按钮功能选择合适的颜色
- 返回/导航操作:白色系 `rgba(255, 255, 255, 0.3)`
- 删除操作:红色系 `rgba(244, 67, 54, 0.2)`
- 确认操作:绿色系 `rgba(76, 175, 80, 0.2)`
- 信息操作:蓝色系 `rgba(33, 150, 243, 0.2)`
### 参考实现
- `components/model/MembershipModal.tsx` - 悬浮返回按钮
- `components/glass/button.tsx` - 通用 Glass 按钮组件
- `app/(tabs)/_layout.tsx` - 标签栏按钮实现
## HeaderBar 顶部距离处理 ## HeaderBar 顶部距离处理
**最后更新**: 2025-10-16 **最后更新**: 2025-10-16

View File

@@ -11,8 +11,10 @@ import {
captureUserAction captureUserAction
} from '@/utils/sentry.utils'; } from '@/utils/sentry.utils';
import { Toast as GlobalToast } from '@/utils/toast.utils'; import { Toast as GlobalToast } from '@/utils/toast.utils';
import { MaterialIcons } from '@expo/vector-icons'; import { Ionicons } from '@expo/vector-icons';
import { captureException } from '@sentry/react-native'; import { captureException } from '@sentry/react-native';
import { GlassView, isLiquidGlassAvailable } from 'expo-glass-effect';
import { LinearGradient } from 'expo-linear-gradient';
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import { import {
ActivityIndicator, ActivityIndicator,
@@ -42,6 +44,8 @@ interface MembershipPlan {
subtitle: string; subtitle: string;
type: 'weekly' | 'quarterly' | 'lifetime'; type: 'weekly' | 'quarterly' | 'lifetime';
recommended?: boolean; recommended?: boolean;
tag?: string;
originalPrice?: string;
} }
const DEFAULT_PLANS: MembershipPlan[] = [ const DEFAULT_PLANS: MembershipPlan[] = [
@@ -51,21 +55,50 @@ const DEFAULT_PLANS: MembershipPlan[] = [
subtitle: '一次投入,终身健康陪伴', subtitle: '一次投入,终身健康陪伴',
type: 'lifetime', type: 'lifetime',
recommended: true, recommended: true,
tag: '限时特价',
originalPrice: '¥898',
}, },
{ {
id: 'com.anonymous.digitalpilates.membership.quarter', id: 'com.anonymous.digitalpilates.membership.quarter',
fallbackTitle: '季度会员', fallbackTitle: '季度会员',
subtitle: '3个月蜕变计划见证身材变化', subtitle: '3个月蜕变计划见证身材变化',
type: 'quarterly', type: 'quarterly',
originalPrice: '¥598',
}, },
{ {
id: 'com.anonymous.digitalpilates.membership.weekly', id: 'com.anonymous.digitalpilates.membership.weekly',
fallbackTitle: '周会员', fallbackTitle: '周会员',
subtitle: '7天体验开启健康第一步', subtitle: '7天体验开启健康第一步',
type: 'weekly', type: 'weekly',
originalPrice: '¥128',
}, },
]; ];
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 },
];
const PLAN_STYLE_CONFIG: Record<MembershipPlan['type'], { gradient: readonly [string, string]; accent: string }> = {
lifetime: {
gradient: ['#FFF1DD', '#FFE8FA'] as const,
accent: '#7B2CBF',
},
quarterly: {
gradient: ['#E5EAFE', '#F4E8FF'] as const,
accent: '#5B5BE6',
},
weekly: {
gradient: ['#FFF6E5', '#FFE7CC'] as const,
accent: '#FF9500',
},
};
export function MembershipModal({ visible, onClose, onPurchaseSuccess }: MembershipModalProps) { export function MembershipModal({ visible, onClose, onPurchaseSuccess }: MembershipModalProps) {
const [selectedProduct, setSelectedProduct] = useState<PurchasesStoreProduct | null>(null); const [selectedProduct, setSelectedProduct] = useState<PurchasesStoreProduct | null>(null);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -672,38 +705,6 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
} }
}; };
const renderMembershipBenefits = () => (
<View style={styles.benefitsContainer}>
<View style={styles.benefitsTitleContainer}>
<Text style={styles.benefitsTitle}></Text>
<Text style={styles.benefitsSubtitle}></Text>
</View>
<View style={styles.benefitItem}>
<MaterialIcons name="check-circle" size={20} color="#DF42D0" />
<Text style={styles.benefitText}></Text>
</View>
<View style={styles.benefitItem}>
<MaterialIcons name="check-circle" size={20} color="#DF42D0" />
<Text style={styles.benefitText}></Text>
</View>
<View style={styles.benefitItem}>
<MaterialIcons name="check-circle" size={20} color="#DF42D0" />
<Text style={styles.benefitText}></Text>
</View>
{/* 皇冠图标 */}
<View style={styles.crownContainer}>
{/* <Image
source={require('@/assets/images/img_profile_vip_bg.png')}
style={styles.crownIcon}
/> */}
</View>
</View>
);
const renderPlanCard = (product: PurchasesStoreProduct) => { const renderPlanCard = (product: PurchasesStoreProduct) => {
const plan = DEFAULT_PLANS.find(p => p.id === product.identifier); const plan = DEFAULT_PLANS.find(p => p.id === product.identifier);
@@ -715,13 +716,14 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
const isSelected = selectedProduct === product; const isSelected = selectedProduct === product;
const displayTitle = product.title || plan.fallbackTitle; const displayTitle = product.title || plan.fallbackTitle;
const priceLabel = product.priceString || ''; const priceLabel = product.priceString || '';
const styleConfig = PLAN_STYLE_CONFIG[plan.type];
return ( return (
<TouchableOpacity <TouchableOpacity
key={product.identifier} key={product.identifier}
style={[ style={[
styles.planCard, styles.planCardWrapper,
isSelected && styles.selectedPlan, isSelected && styles.planCardWrapperSelected,
loading && styles.disabledPlanCard, loading && styles.disabledPlanCard,
]} ]}
onPress={() => !loading && product && setSelectedProduct(product)} onPress={() => !loading && product && setSelectedProduct(product)}
@@ -732,35 +734,27 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
accessibilityHint={loading ? '购买进行中,无法切换套餐' : `选择${displayTitle}套餐`} accessibilityHint={loading ? '购买进行中,无法切换套餐' : `选择${displayTitle}套餐`}
accessibilityState={{ disabled: loading, selected: isSelected }} accessibilityState={{ disabled: loading, selected: isSelected }}
> >
{plan.recommended && ( <LinearGradient
<View style={styles.recommendedBadge}> colors={styleConfig?.gradient ?? ['#FFFFFF', '#FFFFFF']}
<Text style={styles.recommendedText}></Text> start={{ x: 0, y: 0 }}
</View> end={{ x: 1, y: 1 }}
)} style={styles.planCardGradient}
>
{plan.tag && (
<View style={styles.planTag}>
<Text style={styles.planTagText}>{plan.tag}</Text>
</View>
)}
<View style={styles.planHeader}> <Text style={styles.planCardTitle}>{displayTitle}</Text>
<Text style={[ <Text style={[styles.planCardPrice, styleConfig && { color: styleConfig.accent }]}>
styles.planTitle,
plan.type === 'lifetime' && styles.lifetimePlanTitle,
plan.type === 'quarterly' && styles.quarterlyPlanTitle,
plan.type === 'weekly' && styles.weeklyPlanTitle,
]}>
{displayTitle}
</Text>
</View>
<View style={styles.planPricing}>
<Text style={[
styles.planPrice,
plan.type === 'lifetime' && styles.lifetimePlanPrice,
plan.type === 'quarterly' && styles.quarterlyPlanPrice,
plan.type === 'weekly' && styles.weeklyPlanPrice,
]}>
{priceLabel || '--'} {priceLabel || '--'}
</Text> </Text>
</View> {plan.originalPrice && (
<Text style={styles.planCardOriginalPrice}>{plan.originalPrice}</Text>
<Text style={styles.planSubtitle}>{plan.subtitle}</Text> )}
<Text style={styles.planCardDescription}>{plan.subtitle}</Text>
</LinearGradient>
</TouchableOpacity> </TouchableOpacity>
); );
}; };
@@ -770,7 +764,7 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
<Modal <Modal
visible={visible} visible={visible}
transparent={true} transparent={true}
animationType="slide" animationType="fade"
presentationStyle="overFullScreen" presentationStyle="overFullScreen"
> >
<View style={styles.overlay}> <View style={styles.overlay}>
@@ -781,119 +775,194 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
onPress={onClose} // 阻止点击背景关闭 onPress={onClose} // 阻止点击背景关闭
/> />
{/* 会员内容 */}
<View style={styles.modalContainer}> <View style={styles.modalContainer}>
<ScrollView style={styles.modalContent} showsVerticalScrollIndicator={false}> {/* 悬浮返回按钮 - 移到 ScrollView 外部以确保始终在最上层 */}
{/* 关闭按钮 */} <TouchableOpacity
{/* <TouchableOpacity onPress={onClose}
style={styles.closeButton} activeOpacity={0.7}
onPress={onClose} accessible={true}
> accessibilityLabel="返回"
<Text style={styles.closeButtonText}>×</Text> accessibilityHint="关闭会员购买弹窗"
</TouchableOpacity> */} style={styles.floatingBackButtonContainer}
>
{isLiquidGlassAvailable() ? (
{/* 会员权益介绍 */} <GlassView
{renderMembershipBenefits()} style={styles.floatingBackButton}
glassEffectStyle="clear"
{/* 会员套餐选择 */} tintColor="rgba(255, 255, 255, 0.3)"
{products.length === 0 && ( isInteractive={true}
<View style={styles.configurationNotice}> >
<Text style={styles.configurationText}> <Ionicons name="chevron-back" size={24} color="#333" />
RevenueCat iOS Offering </GlassView>
</Text> ) : (
<View style={[styles.floatingBackButton, styles.fallbackBackButton]}>
<Ionicons name="chevron-back" size={24} color="#333" />
</View> </View>
)} )}
<View style={styles.plansContainer}> </TouchableOpacity>
{products.map(renderPlanCard)}
</View>
{/* 产品选择提示区域 */} <ScrollView
{selectedProduct && ( style={styles.modalContent}
<View style={styles.tipsContainer}> contentContainerStyle={styles.modalContentContainer}
<Text style={styles.tipsText}> showsVerticalScrollIndicator={false}
{getTipsContent(selectedProduct)} >
</Text>
<View style={styles.sectionCard}>
<View style={styles.sectionTitleRow}>
<View style={styles.sectionTitleBadge}>
<Ionicons name="star" size={16} color="#7B2CBF" />
</View>
<Text style={styles.sectionTitle}></Text>
</View> </View>
)} <Text style={styles.sectionSubtitle}></Text>
{/* 协议同意区域 */} {products.length === 0 ? (
<View style={styles.agreementRow}> <View style={styles.configurationNotice}>
<CustomCheckBox <Text style={styles.configurationText}>
checked={agreementAccepted} RevenueCat iOS Offering
onCheckedChange={setAgreementAccepted} </Text>
size={16} </View>
checkedColor="#E91E63" ) : (
uncheckedColor="#999" <>
/> <View style={styles.plansContainer}>
<Text style={styles.agreementPrefix}></Text> {products.map(renderPlanCard)}
<TouchableOpacity onPress={() => { </View>
Linking.openURL(USER_AGREEMENT_URL); {selectedProduct && (
captureMessage('click user agreement'); <View style={styles.tipsContainer}>
}}> <Ionicons name="bulb" size={18} color="#F29F05" />
<Text style={styles.agreementLink}></Text> <Text style={styles.tipsText}>{getTipsContent(selectedProduct)}</Text>
</TouchableOpacity> </View>
<Text style={styles.agreementSeparator}> | </Text> )}
<TouchableOpacity onPress={() => { </>
// Linking.openURL(MEMBERSHIP_AGREEMENT_URL); )}
captureMessage('click membership agreement');
}}>
<Text style={styles.agreementLink}></Text>
</TouchableOpacity>
<Text style={styles.agreementSeparator}> | </Text>
<TouchableOpacity onPress={() => {
// Linking.openURL(AUTO_RENEWAL_AGREEMENT_URL);
captureMessage('click auto renewal agreement');
}}>
<Text style={styles.agreementLink}></Text>
</TouchableOpacity>
</View> </View>
<View style={styles.sectionCard}>
{/* 购买按钮 */} <View style={styles.sectionTitleRow}>
<TouchableOpacity <View style={styles.sectionTitleBadge}>
style={[ <Ionicons name="checkbox" size={16} color="#FF9F0A" />
styles.purchaseButton,
(loading || !selectedProduct) && styles.disabledButton
]}
onPress={handlePurchase}
disabled={loading || !selectedProduct}
accessible={true}
accessibilityLabel={loading ? '正在处理购买' : '购买会员'}
accessibilityHint={
loading
? '购买正在进行中,请稍候'
: selectedProduct
? `点击购买${selectedProduct.title || '已选'}会员套餐`
: '请选择会员套餐后再进行购买'
}
accessibilityState={{ disabled: loading || !selectedProduct }}
>
{loading ? (
<View style={styles.loadingContainer}>
<ActivityIndicator size="small" color="white" style={styles.loadingSpinner} />
<Text style={styles.purchaseButtonText}>...</Text>
</View> </View>
) : ( <Text style={styles.sectionTitle}></Text>
<Text style={styles.purchaseButtonText}></Text> </View>
)} <Text style={styles.sectionSubtitle}></Text>
</TouchableOpacity>
{/* 恢复购买按钮 */} <View style={styles.comparisonTable}>
<TouchableOpacity <View style={[styles.tableRow, styles.tableHeader]}>
style={[styles.restoreButton, (restoring || loading) && styles.disabledRestoreButton]} <Text style={[styles.tableHeaderText, styles.tableTitleCell]}></Text>
onPress={handleRestore} <Text style={[styles.tableHeaderText, styles.tableVipCell]}>VIP</Text>
disabled={restoring || loading} <Text style={[styles.tableHeaderText, styles.tableNormalCell]}></Text>
>
{restoring ? (
<View style={styles.restoreButtonContent}>
<ActivityIndicator size="small" color="#666" style={styles.restoreButtonLoader} />
<Text style={styles.restoreButtonText}>...</Text>
</View> </View>
) : ( {BENEFIT_COMPARISON.map((row, index) => (
<Text style={styles.restoreButtonText}></Text> <View
)} key={row.title}
</TouchableOpacity> style={[
styles.tableRow,
index % 2 === 1 && styles.tableRowAlt,
]}
>
<Text style={[styles.tableCellText, styles.tableTitleCell]}>{row.title}</Text>
<View style={styles.tableVipCell}>
{row.vip ? (
<Ionicons name="checkmark-circle" size={20} color="#FFB200" />
) : (
<Ionicons name="remove" size={20} color="#D1D4DA" />
)}
</View>
<View style={styles.tableNormalCell}>
{row.regular ? (
<Ionicons name="checkmark-circle" size={20} color="#8E8E93" />
) : (
<Ionicons name="remove" size={20} color="#D1D4DA" />
)}
</View>
</View>
))}
</View>
</View>
<View style={styles.bottomSection}>
<View style={styles.noticeBanner}>
<Text style={styles.noticeText}>667+</Text>
</View>
<TouchableOpacity
style={[
styles.purchaseButton,
(loading || !selectedProduct || !agreementAccepted) && styles.disabledButton
]}
onPress={handlePurchase}
disabled={loading || !selectedProduct || !agreementAccepted}
accessible={true}
accessibilityLabel={loading ? '正在处理购买' : '购买会员'}
accessibilityHint={
loading
? '购买正在进行中,请稍候'
: selectedProduct
? `点击购买${selectedProduct.title || '已选'}会员套餐`
: '请选择会员套餐后再进行购买'
}
accessibilityState={{ disabled: loading || !selectedProduct || !agreementAccepted }}
>
{loading ? (
<View style={styles.loadingContainer}>
<ActivityIndicator size="small" color="white" style={styles.loadingSpinner} />
<Text style={styles.purchaseButtonText}>...</Text>
</View>
) : (
<Text style={styles.purchaseButtonText}></Text>
)}
</TouchableOpacity>
<View style={styles.agreementRow}>
<CustomCheckBox
checked={agreementAccepted}
onCheckedChange={setAgreementAccepted}
size={16}
checkedColor="#E91E63"
uncheckedColor="#999"
/>
<Text style={styles.agreementPrefix}></Text>
<TouchableOpacity
onPress={() => {
Linking.openURL(USER_AGREEMENT_URL);
captureMessage('click user agreement');
}}
>
<Text style={styles.agreementLink}></Text>
</TouchableOpacity>
<Text style={styles.agreementSeparator}>|</Text>
<TouchableOpacity
onPress={() => {
captureMessage('click membership agreement');
}}
>
<Text style={styles.agreementLink}></Text>
</TouchableOpacity>
<Text style={styles.agreementSeparator}>|</Text>
<TouchableOpacity
onPress={() => {
captureMessage('click auto renewal agreement');
}}
>
<Text style={styles.agreementLink}></Text>
</TouchableOpacity>
</View>
<TouchableOpacity
style={[styles.restoreButton, (restoring || loading) && styles.disabledRestoreButton]}
onPress={handleRestore}
disabled={restoring || loading}
>
{restoring ? (
<View style={styles.restoreButtonContent}>
<ActivityIndicator size="small" color="#666" style={styles.restoreButtonLoader} />
<Text style={styles.restoreButtonText}>...</Text>
</View>
) : (
<Text style={styles.restoreButtonText}></Text>
)}
</TouchableOpacity>
</View>
</ScrollView> </ScrollView>
</View> </View>
</View> </View>
@@ -908,231 +977,284 @@ const styles = StyleSheet.create({
}, },
backdrop: { backdrop: {
...StyleSheet.absoluteFillObject, ...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0, 0, 0, 0.3)', backgroundColor: 'rgba(0, 0, 0, 0.35)',
}, },
modalContainer: { modalContainer: {
height: height * 0.6, height: height * 0.92,
backgroundColor: 'white', backgroundColor: '#F5F6FA',
borderTopLeftRadius: 20, borderTopLeftRadius: 28,
borderTopRightRadius: 20, borderTopRightRadius: 28,
overflow: 'hidden', overflow: 'hidden',
}, },
modalContent: { modalContent: {
flex: 1, flex: 1,
},
modalContentContainer: {
paddingHorizontal: 20, paddingHorizontal: 20,
paddingTop: 20, paddingBottom: 32,
paddingTop: 16,
}, },
closeButton: { floatingBackButton: {
position: 'absolute', width: 40,
top: 0, height: 40,
right: 0, borderRadius: 20,
width: 30,
height: 30,
borderRadius: 15,
backgroundColor: 'rgba(0, 0, 0, 0.1)',
justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
zIndex: 1, justifyContent: 'center',
overflow: 'hidden',
}, },
closeButtonText: { floatingBackButtonContainer: {
position: 'absolute',
top: 20,
left: 20,
zIndex: 10, // 确保按钮在最上层
},
fallbackBackButton: {
backgroundColor: 'rgba(255, 255, 255, 0.9)',
},
sectionCard: {
backgroundColor: '#FFFFFF',
borderRadius: 20,
paddingHorizontal: 20,
paddingVertical: 18,
marginBottom: 20,
shadowColor: '#1C1C1E',
shadowOpacity: 0.06,
shadowRadius: 12,
shadowOffset: { width: 0, height: 6 },
elevation: 2,
},
sectionTitleRow: {
flexDirection: 'row',
alignItems: 'center',
},
sectionTitleBadge: {
width: 28,
height: 28,
borderRadius: 14,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#F4EFFD',
marginRight: 8,
},
sectionTitle: {
fontSize: 18, fontSize: 18,
color: '#666', fontWeight: '700',
fontWeight: '300', color: '#2B2B2E',
},
sectionSubtitle: {
fontSize: 13,
color: '#6B6B73',
marginTop: 6,
marginBottom: 16,
}, },
configurationNotice: { configurationNotice: {
backgroundColor: '#FFF9E6', borderRadius: 16,
borderRadius: 12,
padding: 16, padding: 16,
marginTop: 30, backgroundColor: '#FFF4E5',
marginBottom: 20,
alignItems: 'center',
borderWidth: 1,
borderColor: '#FFE4B5',
}, },
configurationText: { configurationText: {
fontSize: 14, fontSize: 14,
color: '#B8860B', color: '#B86A04',
textAlign: 'center', textAlign: 'center',
marginTop: 8, lineHeight: 20,
marginBottom: 12,
},
configurationButton: {
backgroundColor: '#FF9500',
borderRadius: 8,
paddingHorizontal: 16,
paddingVertical: 8,
},
configurationButtonText: {
color: 'white',
fontSize: 12,
fontWeight: '600',
},
benefitsContainer: {
position: 'relative',
backgroundColor: '#FFE7F9',
borderRadius: 16,
marginBottom: 30,
padding: 20,
},
benefitsTitleContainer: {
alignItems: 'center',
marginBottom: 20,
},
benefitsTitle: {
fontSize: 18,
fontWeight: 'bold',
color: '#333',
marginBottom: 5,
},
benefitsSubtitle: {
fontSize: 14,
color: '#666',
textAlign: 'center',
},
benefitsTitleBg: {
width: 180,
height: 20,
marginBottom: 10,
},
benefitItem: {
flexDirection: 'row',
alignItems: 'center',
marginTop: 10,
},
benefitText: {
fontSize: 14,
color: '#333',
marginLeft: 8,
fontWeight: '500',
},
crownContainer: {
position: 'absolute',
top: 10,
right: 10,
width: 100,
height: 100,
justifyContent: 'center',
alignItems: 'center',
},
crownIcon: {
width: 80,
height: 80,
resizeMode: 'contain',
}, },
plansContainer: { plansContainer: {
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',
marginBottom: 20,
}, },
planCard: { planCardWrapper: {
flex: 1, flex: 1,
marginHorizontal: 4, marginHorizontal: 4,
borderRadius: 12, borderRadius: 18,
borderWidth: 2, overflow: 'hidden',
borderColor: '#E0E0E0', borderWidth: 1,
paddingVertical: 20, borderColor: 'rgba(123,44,191,0.08)',
paddingHorizontal: 12,
alignItems: 'center',
position: 'relative',
backgroundColor: 'white',
}, },
selectedPlan: { planCardWrapperSelected: {
borderColor: '#DF42D0', borderColor: '#7B2CBF',
backgroundColor: '#FFF5FE', shadowColor: '#7B2CBF',
shadowOpacity: 0.18,
shadowRadius: 16,
shadowOffset: { width: 0, height: 8 },
elevation: 4,
}, },
recommendedBadge: { planCardGradient: {
position: 'absolute', paddingHorizontal: 16,
top: -10, paddingVertical: 18,
backgroundColor: '#7B2CBF', minHeight: 170,
borderRadius: 10, },
planTag: {
alignSelf: 'flex-start',
backgroundColor: '#2F2F36',
borderRadius: 14,
paddingHorizontal: 12, paddingHorizontal: 12,
paddingVertical: 4, paddingVertical: 4,
marginBottom: 12,
}, },
recommendedText: { planTagText: {
color: 'white', color: '#FFFFFF',
fontSize: 10, fontSize: 11,
fontWeight: 'bold',
},
planHeader: {
alignItems: 'center',
marginBottom: 8,
},
planTitle: {
fontSize: 16,
fontWeight: '600', fontWeight: '600',
color: '#333',
}, },
lifetimePlanTitle: { planCardTitle: {
color: '#7B2CBF', fontSize: 18,
fontWeight: '700',
color: '#241F1F',
}, },
quarterlyPlanTitle: { planCardPrice: {
color: '#DF42D0', fontSize: 28,
fontWeight: '700',
marginTop: 12,
}, },
weeklyPlanTitle: { planCardOriginalPrice: {
color: '#FF9500', fontSize: 13,
color: '#8E8EA1',
textDecorationLine: 'line-through',
marginTop: 4,
}, },
planPricing: { planCardDescription: {
alignItems: 'center',
marginBottom: 8,
},
planPrice: {
fontSize: 24,
fontWeight: 'bold',
color: '#333',
},
lifetimePlanPrice: {
color: '#7B2CBF',
},
quarterlyPlanPrice: {
color: '#DF42D0',
},
weeklyPlanPrice: {
color: '#FF9500',
},
planSubtitle: {
fontSize: 12, fontSize: 12,
color: '#666', color: '#6C6C77',
textAlign: 'center', marginTop: 12,
lineHeight: 17,
},
tipsContainer: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#FEF4E6',
borderRadius: 14,
paddingHorizontal: 12,
paddingVertical: 10,
marginTop: 16,
},
tipsText: {
flex: 1,
fontSize: 12,
color: '#9B6200',
marginLeft: 6,
lineHeight: 16, lineHeight: 16,
}, },
purchaseButton: { comparisonTable: {
backgroundColor: '#DF42D0', borderRadius: 16,
borderRadius: 25, overflow: 'hidden',
height: 50, borderWidth: 1,
justifyContent: 'center', borderColor: '#ECECF3',
},
tableRow: {
flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
marginBottom: 15, paddingVertical: 14,
marginTop: 10, paddingHorizontal: 16,
shadowColor: '#DF42D0', backgroundColor: '#FFFFFF',
shadowOffset: {
width: 0,
height: 4,
},
shadowOpacity: 0.3,
shadowRadius: 8,
elevation: 6,
}, },
disabledButton: { tableHeader: {
backgroundColor: '#ccc', backgroundColor: '#F8F8FF',
shadowOpacity: 0,
elevation: 0,
}, },
purchaseButtonText: { tableHeaderText: {
color: 'white', fontSize: 12,
fontSize: 18, fontWeight: '600',
color: '#575764',
textTransform: 'uppercase',
letterSpacing: 0.4,
},
tableCellText: {
fontSize: 13,
color: '#3E3E44',
},
tableTitleCell: {
flex: 1.3,
},
tableVipCell: {
flex: 0.8,
alignItems: 'center',
},
tableNormalCell: {
flex: 0.8,
alignItems: 'center',
},
tableRowAlt: {
backgroundColor: '#FBFBFF',
},
bottomSection: {
backgroundColor: '#FFFFFF',
borderRadius: 20,
paddingHorizontal: 20,
paddingVertical: 20,
marginBottom: 10,
shadowColor: '#1C1C1E',
shadowOpacity: 0.04,
shadowRadius: 12,
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', fontWeight: '600',
}, },
restoreButton: { purchaseButton: {
backgroundColor: 'transparent', backgroundColor: '#151515',
borderRadius: 25, borderRadius: 28,
height: 50, height: 52,
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
marginBottom: 20, marginBottom: 16,
},
disabledButton: {
backgroundColor: '#C6C6C8',
},
purchaseButtonText: {
color: '#FFFFFF',
fontSize: 18,
fontWeight: '700',
},
loadingContainer: {
flexDirection: 'row',
alignItems: 'center',
},
loadingSpinner: {
marginRight: 8,
},
agreementRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
flexWrap: 'wrap',
marginBottom: 16,
},
agreementPrefix: {
fontSize: 11,
color: '#666672',
marginHorizontal: 6,
},
agreementLink: {
fontSize: 11,
color: '#E91E63',
textDecorationLine: 'underline',
fontWeight: '500',
marginHorizontal: 4,
},
agreementSeparator: {
fontSize: 11,
color: '#A0A0B0',
marginHorizontal: 2,
},
restoreButton: {
alignSelf: 'center',
paddingVertical: 6,
}, },
restoreButtonText: { restoreButtonText: {
color: '#666', color: '#6F6F7A',
fontSize: 16, fontSize: 14,
fontWeight: '500', fontWeight: '500',
}, },
disabledRestoreButton: { disabledRestoreButton: {
@@ -1146,49 +1268,7 @@ const styles = StyleSheet.create({
restoreButtonLoader: { restoreButtonLoader: {
marginRight: 8, marginRight: 8,
}, },
loadingContainer: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
loadingSpinner: {
marginRight: 8,
},
disabledPlanCard: { disabledPlanCard: {
opacity: 0.5, opacity: 0.5,
}, },
agreementRow: {
width: '100%',
marginTop: 6,
borderRadius: 12,
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
agreementPrefix: {
fontSize: 10,
color: '#333',
marginRight: 4,
},
agreementLink: {
fontSize: 10,
color: '#E91E63',
textDecorationLine: 'underline',
fontWeight: '500',
},
agreementSeparator: {
fontSize: 10,
color: '#666',
marginHorizontal: 2,
},
tipsContainer: {
borderRadius: 8,
},
tipsText: {
fontSize: 12,
color: '#666',
lineHeight: 18,
textAlign: 'center',
},
}); });