|
|
@@ -1,59 +1,89 @@
|
|
|
<template>
|
|
|
<view class="page">
|
|
|
- <!-- 顶部导航 -->
|
|
|
<view class="nav-bar">
|
|
|
<view class="nav-btn" @click="goBack">
|
|
|
<text class="nav-icon">←</text>
|
|
|
</view>
|
|
|
- <text class="nav-title">会员中心</text>
|
|
|
+ <text class="nav-title">订阅套餐</text>
|
|
|
<view class="nav-btn" />
|
|
|
</view>
|
|
|
|
|
|
- <!-- 权益说明 -->
|
|
|
- <view class="benefits-section" v-if="!loading">
|
|
|
- <text class="section-title">会员权益</text>
|
|
|
- <view class="benefit-item" v-for="(benefit, idx) in benefits" :key="idx">
|
|
|
- <text class="benefit-icon">✓</text>
|
|
|
- <text class="benefit-text">{{ benefit }}</text>
|
|
|
+ <view class="balance-section" v-if="isLoggedIn">
|
|
|
+ <view class="balance-info">
|
|
|
+ <text class="balance-label">剩余Token</text>
|
|
|
+ <text class="balance-value">{{ tokenBalance.remainingTokens.toLocaleString() }}</text>
|
|
|
+ <text class="balance-unit"> / {{ tokenBalance.isUnlimited ? '无限' : tokenBalance.totalTokens.toLocaleString() }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="balance-bar">
|
|
|
+ <view class="balance-progress" :style="{ width: progressWidth + '%' }"></view>
|
|
|
</view>
|
|
|
</view>
|
|
|
-
|
|
|
- <view v-else class="loading-container">
|
|
|
- <text class="loading-text">加载中...</text>
|
|
|
- </view>
|
|
|
|
|
|
- <!-- 会员卡片 -->
|
|
|
- <view class="cards-section">
|
|
|
- <view
|
|
|
- v-for="(level, idx) in memberLevels"
|
|
|
- :key="level.level"
|
|
|
- class="member-card"
|
|
|
- :class="{ active: selectedLevel === level.level, recommend: level.level === 1 }"
|
|
|
- @click="selectedLevel = level.level"
|
|
|
- >
|
|
|
- <view v-if="level.level === 1" class="recommend-badge">
|
|
|
- <text>推荐</text>
|
|
|
+ <view class="plans-section">
|
|
|
+ <text class="section-title">选择您的套餐</text>
|
|
|
+
|
|
|
+ <view v-if="loading" class="loading-container">
|
|
|
+ <text class="loading-text">加载中...</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view v-else class="plans-list">
|
|
|
+ <view
|
|
|
+ v-for="plan in plans"
|
|
|
+ :key="plan.id"
|
|
|
+ class="plan-card"
|
|
|
+ :class="{ active: selectedPlanId === plan.id, recommended: plan.isRecommended, free: plan.level === 0 }"
|
|
|
+ @click="selectedPlanId = plan.id"
|
|
|
+ >
|
|
|
+ <view v-if="plan.isRecommended" class="recommended-badge">推荐</view>
|
|
|
+ <view v-if="plan.level === 0" class="free-badge">免费</view>
|
|
|
+
|
|
|
+ <view class="plan-header">
|
|
|
+ <text class="plan-name">{{ plan.name }}</text>
|
|
|
+ <view class="plan-pricing">
|
|
|
+ <text class="price-symbol">¥</text>
|
|
|
+ <text class="price-value">{{ plan.priceMonthly }}</text>
|
|
|
+ <text class="price-unit">/月</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <text class="plan-description">{{ plan.description }}</text>
|
|
|
+
|
|
|
+ <view class="plan-features">
|
|
|
+ <view v-for="(feature, idx) in plan.features" :key="idx" class="feature-item">
|
|
|
+ <text class="feature-icon">✓</text>
|
|
|
+ <text class="feature-text">{{ feature }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="select-btn" :class="{ selected: selectedPlanId === plan.id }">
|
|
|
+ <text>{{ selectedPlanId === plan.id ? '✓ 已选择' : '选择此套餐' }}</text>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
- <text class="card-name">{{ level.name }}</text>
|
|
|
- <view class="card-price">
|
|
|
- <text class="price-symbol">¥</text>
|
|
|
- <text class="price-value">{{ level.price }}</text>
|
|
|
- <text class="price-unit">/{{ level.level === 2 ? '年' : '月' }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="payment-section">
|
|
|
+ <text class="section-title">支付方式</text>
|
|
|
+ <view class="payment-methods">
|
|
|
+ <view class="payment-method" :class="{ active: paymentMethod === 'alipay' }" @click="paymentMethod = 'alipay'">
|
|
|
+ <text class="method-icon">💙</text>
|
|
|
+ <text class="method-name">支付宝</text>
|
|
|
</view>
|
|
|
- <view class="card-features">
|
|
|
- <text v-for="(feature, fIdx) in level.features" :key="fIdx" class="feature-item">
|
|
|
- {{ feature }}
|
|
|
- </text>
|
|
|
+ <view class="payment-method" :class="{ active: paymentMethod === 'wechat' }" @click="paymentMethod = 'wechat'">
|
|
|
+ <text class="method-icon">🟢</text>
|
|
|
+ <text class="method-name">微信支付</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
- <!-- 开通按钮 -->
|
|
|
<view class="bottom-section">
|
|
|
- <button class="subscribe-btn" @click="handleSubscribe">
|
|
|
- <text class="btn-text">立即开通 ¥{{ selectedPrice }}</text>
|
|
|
+ <view class="total-info">
|
|
|
+ <text class="total-label">应付金额</text>
|
|
|
+ <text class="total-value">¥{{ selectedPlan?.priceMonthly || 0 }}</text>
|
|
|
+ </view>
|
|
|
+ <button class="subscribe-btn" @click="handleSubscribe" :disabled="!selectedPlan || selectedPlan.level === 0">
|
|
|
+ <text>{{ selectedPlan?.level === 0 ? '免费套餐' : '立即订阅' }}</text>
|
|
|
</button>
|
|
|
- <text class="terms">开通即表示同意《会员服务协议》</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
</template>
|
|
|
@@ -65,323 +95,163 @@ import { get, post } from '../../utils/request';
|
|
|
|
|
|
const userStore = useUserStore();
|
|
|
|
|
|
-// 状态
|
|
|
-const memberLevels = ref<any[]>([]);
|
|
|
-const benefits = ref<string[]>([
|
|
|
- '无限次生成音频',
|
|
|
- '无字数限制',
|
|
|
- '10+ 优质音色',
|
|
|
- '优先处理队列',
|
|
|
- '专属客服支持',
|
|
|
-]);
|
|
|
-const selectedLevel = ref(1);
|
|
|
+const plans = ref<any[]>([]);
|
|
|
+const selectedPlanId = ref<number>(1);
|
|
|
+const paymentMethod = ref<'alipay' | 'wechat'>('alipay');
|
|
|
const loading = ref(false);
|
|
|
-
|
|
|
-// 计算属性
|
|
|
-const selectedPrice = computed(() => {
|
|
|
- const level = memberLevels.value.find(l => l.level === selectedLevel.value);
|
|
|
- return level?.price || 0;
|
|
|
+const tokenBalance = ref({ totalTokens: 0, usedTokens: 0, remainingTokens: 0, isUnlimited: false });
|
|
|
+
|
|
|
+const selectedPlan = computed(() => plans.value.find(p => p.id === selectedPlanId.value));
|
|
|
+const isLoggedIn = computed(() => userStore.isLoggedIn);
|
|
|
+const progressWidth = computed(() => {
|
|
|
+ if (tokenBalance.value.isUnlimited) return 100;
|
|
|
+ if (tokenBalance.value.totalTokens === 0) return 0;
|
|
|
+ return Math.max(0, Math.min(100, ((tokenBalance.value.totalTokens - tokenBalance.value.usedTokens) / tokenBalance.value.totalTokens) * 100));
|
|
|
});
|
|
|
|
|
|
-// 初始化
|
|
|
onMounted(async () => {
|
|
|
- await fetchBenefits();
|
|
|
+ await fetchPlans();
|
|
|
+ if (isLoggedIn.value) await fetchTokenBalance();
|
|
|
});
|
|
|
|
|
|
-// 获取权益信息
|
|
|
-async function fetchBenefits() {
|
|
|
+async function fetchPlans() {
|
|
|
loading.value = true;
|
|
|
try {
|
|
|
- const result = await get<{ levels: any[] }>('/member/benefits');
|
|
|
- // 保留所有等级,包括免费用户
|
|
|
- memberLevels.value = result.levels;
|
|
|
+ const result = await get<{ plans: any[] }>('/subscription/plans');
|
|
|
+ plans.value = result.plans;
|
|
|
+ const recommended = result.plans.find((p: any) => p.isRecommended);
|
|
|
+ if (recommended) selectedPlanId.value = recommended.id;
|
|
|
} catch (error) {
|
|
|
- console.error('获取权益失败:', error);
|
|
|
+ console.error('获取套餐失败:', error);
|
|
|
uni.showToast({ title: '加载失败', icon: 'none' });
|
|
|
} finally {
|
|
|
loading.value = false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// 返回
|
|
|
+async function fetchTokenBalance() {
|
|
|
+ try {
|
|
|
+ const result = await get('/subscription/balance');
|
|
|
+ tokenBalance.value = result;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取Token余额失败:', error);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
function goBack() {
|
|
|
uni.navigateBack();
|
|
|
}
|
|
|
|
|
|
-// 开通会员
|
|
|
async function handleSubscribe() {
|
|
|
- if (!userStore.isLoggedIn) {
|
|
|
+ if (!isLoggedIn.value) {
|
|
|
uni.navigateTo({ url: '/pages/login/index' });
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
- const productType = selectedLevel.value === 2 ? 'yearly' : 'monthly';
|
|
|
-
|
|
|
+ const plan = selectedPlan.value;
|
|
|
+ if (!plan || plan.level === 0) {
|
|
|
+ uni.showToast({ title: '免费套餐无需订阅', icon: 'none' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
try {
|
|
|
- // 创建订单
|
|
|
- const orderResult = await post<{ orderNo: string; amount: number }>('/member/order', {
|
|
|
- productType,
|
|
|
+ const orderResult = await post<{ orderNo: string; amount: number }>('/payment/create', {
|
|
|
+ planId: plan.id,
|
|
|
+ paymentMethod: paymentMethod.value
|
|
|
});
|
|
|
-
|
|
|
- // 判断环境:使用平台能力判断
|
|
|
const platform = uni.getSystemInfoSync().platform;
|
|
|
- const isDev = process.env?.NODE_ENV === 'development';
|
|
|
-
|
|
|
- if (isDev || platform === 'devtools') {
|
|
|
- // 开发环境或开发者工具:模拟支付
|
|
|
+ if (platform === 'devtools' || process.env?.NODE_ENV === 'development') {
|
|
|
uni.showModal({
|
|
|
title: '模拟支付',
|
|
|
- content: `确认支付 ¥${orderResult.amount}?\n(开发环境模拟支付)`,
|
|
|
+ content: `确认支付 ¥${orderResult.amount}?\n(开发环境模拟)`,
|
|
|
success: async (res) => {
|
|
|
- if (res.confirm) {
|
|
|
- await mockPay(orderResult.orderNo);
|
|
|
- }
|
|
|
+ if (res.confirm) await mockPay(orderResult.orderNo);
|
|
|
},
|
|
|
});
|
|
|
} else {
|
|
|
- // 生产环境:调用真实支付
|
|
|
- uni.showToast({
|
|
|
- title: '请使用真实支付',
|
|
|
- icon: 'none',
|
|
|
- duration: 3000
|
|
|
- });
|
|
|
+ uni.showToast({ title: '即将跳转支付...', icon: 'none' });
|
|
|
}
|
|
|
} catch (error: any) {
|
|
|
console.error('创建订单失败:', error);
|
|
|
- uni.showToast({
|
|
|
- title: error.message || '创建订单失败',
|
|
|
- icon: 'none'
|
|
|
- });
|
|
|
+ uni.showToast({ title: error.message || '创建订单失败', icon: 'none' });
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// 模拟支付
|
|
|
async function mockPay(orderNo: string) {
|
|
|
try {
|
|
|
uni.showLoading({ title: '支付中...' });
|
|
|
- await post('/member/pay/mock', { orderNo });
|
|
|
+ await post('/payment/mock', { orderNo });
|
|
|
uni.hideLoading();
|
|
|
-
|
|
|
- // 刷新会员状态
|
|
|
await userStore.fetchMemberStatus();
|
|
|
-
|
|
|
+ await fetchTokenBalance();
|
|
|
uni.showModal({
|
|
|
title: '支付成功',
|
|
|
- content: '恭喜您成为会员!',
|
|
|
+ content: '恭喜您订阅成功!',
|
|
|
showCancel: false,
|
|
|
- success: () => {
|
|
|
- uni.navigateBack();
|
|
|
- },
|
|
|
+ success: () => uni.navigateBack(),
|
|
|
});
|
|
|
} catch (error) {
|
|
|
uni.hideLoading();
|
|
|
- console.error('支付失败:', error);
|
|
|
+ uni.showToast({ title: '支付失败', icon: 'none' });
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
-.page {
|
|
|
- min-height: 100vh;
|
|
|
- background: linear-gradient(180deg, #f9fafb 0%, #ffffff 100%);
|
|
|
- padding-bottom: 200rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.nav-bar {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: space-between;
|
|
|
- padding: 44rpx 32rpx 24rpx;
|
|
|
- background: #ffffff;
|
|
|
-}
|
|
|
-
|
|
|
-.nav-btn {
|
|
|
- width: 64rpx;
|
|
|
- height: 64rpx;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
-}
|
|
|
-
|
|
|
-.nav-icon {
|
|
|
- font-size: 36rpx;
|
|
|
- color: #1f2937;
|
|
|
-}
|
|
|
-
|
|
|
-.nav-title {
|
|
|
- font-size: 34rpx;
|
|
|
- font-weight: 600;
|
|
|
- color: #1f2937;
|
|
|
-}
|
|
|
-
|
|
|
-.loading-container {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- padding: 200rpx 0;
|
|
|
-}
|
|
|
-
|
|
|
-.loading-text {
|
|
|
- font-size: 28rpx;
|
|
|
- color: #9ca3af;
|
|
|
-}
|
|
|
-
|
|
|
-.benefits-section {
|
|
|
- margin: 32rpx;
|
|
|
- padding: 32rpx;
|
|
|
- background: #ffffff;
|
|
|
- border-radius: 24rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.section-title {
|
|
|
- font-size: 32rpx;
|
|
|
- font-weight: 600;
|
|
|
- color: #1f2937;
|
|
|
- margin-bottom: 24rpx;
|
|
|
- display: block;
|
|
|
-}
|
|
|
-
|
|
|
-.benefit-item {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- margin-bottom: 16rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.benefit-icon {
|
|
|
- width: 40rpx;
|
|
|
- height: 40rpx;
|
|
|
- background: #10b981;
|
|
|
- border-radius: 50%;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- color: #ffffff;
|
|
|
- font-size: 24rpx;
|
|
|
- margin-right: 16rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.benefit-text {
|
|
|
- font-size: 28rpx;
|
|
|
- color: #4b5563;
|
|
|
-}
|
|
|
-
|
|
|
-.cards-section {
|
|
|
- display: flex;
|
|
|
- gap: 24rpx;
|
|
|
- margin: 0 32rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.member-card {
|
|
|
- flex: 1;
|
|
|
- background: #ffffff;
|
|
|
- border-radius: 24rpx;
|
|
|
- padding: 32rpx 24rpx;
|
|
|
- border: 3rpx solid #e5e7eb;
|
|
|
- position: relative;
|
|
|
- transition: all 0.3s;
|
|
|
-}
|
|
|
-
|
|
|
-.member-card.active {
|
|
|
- border-color: #4f46e5;
|
|
|
- box-shadow: 0 8rpx 32rpx rgba(79, 70, 229, 0.2);
|
|
|
-}
|
|
|
-
|
|
|
-.member-card.recommend {
|
|
|
- background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
|
|
|
- border-color: #fbbf24;
|
|
|
-}
|
|
|
-
|
|
|
-.recommend-badge {
|
|
|
- position: absolute;
|
|
|
- top: -12rpx;
|
|
|
- right: 24rpx;
|
|
|
- background: linear-gradient(135deg, #f97316 0%, #fb923c 100%);
|
|
|
- padding: 4rpx 16rpx;
|
|
|
- border-radius: 16rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.recommend-badge text {
|
|
|
- font-size: 20rpx;
|
|
|
- color: #ffffff;
|
|
|
-}
|
|
|
-
|
|
|
-.card-name {
|
|
|
- display: block;
|
|
|
- font-size: 28rpx;
|
|
|
- font-weight: 600;
|
|
|
- color: #1f2937;
|
|
|
- margin-bottom: 16rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.card-price {
|
|
|
- display: flex;
|
|
|
- align-items: baseline;
|
|
|
- margin-bottom: 24rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.price-symbol {
|
|
|
- font-size: 28rpx;
|
|
|
- color: #f97316;
|
|
|
-}
|
|
|
-
|
|
|
-.price-value {
|
|
|
- font-size: 56rpx;
|
|
|
- font-weight: 700;
|
|
|
- color: #f97316;
|
|
|
-}
|
|
|
-
|
|
|
-.price-unit {
|
|
|
- font-size: 24rpx;
|
|
|
- color: #6b7280;
|
|
|
- margin-left: 4rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.card-features {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- gap: 8rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.feature-item {
|
|
|
- font-size: 22rpx;
|
|
|
- color: #6b7280;
|
|
|
-}
|
|
|
-
|
|
|
-.bottom-section {
|
|
|
- position: fixed;
|
|
|
- bottom: 0;
|
|
|
- left: 0;
|
|
|
- right: 0;
|
|
|
- padding: 24rpx 32rpx;
|
|
|
- padding-bottom: constant(safe-area-inset-bottom);
|
|
|
- padding-bottom: env(safe-area-inset-bottom);
|
|
|
- background: #ffffff;
|
|
|
- box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.04);
|
|
|
-}
|
|
|
-
|
|
|
-.subscribe-btn {
|
|
|
- width: 100%;
|
|
|
- height: 96rpx;
|
|
|
- background: linear-gradient(135deg, #4f46e5 0%, #818cf8 100%);
|
|
|
- border-radius: 24rpx;
|
|
|
- border: none;
|
|
|
-}
|
|
|
-
|
|
|
-.subscribe-btn::after {
|
|
|
- border: none;
|
|
|
-}
|
|
|
-
|
|
|
-.btn-text {
|
|
|
- font-size: 32rpx;
|
|
|
- font-weight: 600;
|
|
|
- color: #ffffff;
|
|
|
-}
|
|
|
-
|
|
|
-.terms {
|
|
|
- display: block;
|
|
|
- text-align: center;
|
|
|
- font-size: 22rpx;
|
|
|
- color: #9ca3af;
|
|
|
- margin-top: 16rpx;
|
|
|
-}
|
|
|
-</style>
|
|
|
+.page { min-height: 100vh; background: linear-gradient(180deg, #f0f9ff 0%, #ffffff 100%); padding-bottom: 280rpx; }
|
|
|
+.nav-bar { display: flex; align-items: center; justify-content: space-between; padding: 44rpx 32rpx 24rpx; background: #fff; }
|
|
|
+.nav-btn { width: 64rpx; height: 64rpx; display: flex; align-items: center; justify-content: center; }
|
|
|
+.nav-icon { font-size: 36rpx; color: #1f2937; }
|
|
|
+.nav-title { font-size: 34rpx; font-weight: 600; color: #1f2937; }
|
|
|
+
|
|
|
+.balance-section { margin: 24rpx 32rpx; padding: 24rpx; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 20rpx; color: #fff; }
|
|
|
+.balance-info { display: flex; align-items: baseline; margin-bottom: 12rpx; }
|
|
|
+.balance-label { font-size: 28rpx; opacity: 0.9; margin-right: 8rpx; }
|
|
|
+.balance-value { font-size: 48rpx; font-weight: 700; }
|
|
|
+.balance-unit { font-size: 24rpx; opacity: 0.8; margin-left: 8rpx; }
|
|
|
+.balance-bar { height: 8rpx; background: rgba(255,255,255,0.3); border-radius: 4rpx; overflow: hidden; }
|
|
|
+.balance-progress { height: 100%; background: #fff; border-radius: 4rpx; transition: width 0.3s; }
|
|
|
+
|
|
|
+.plans-section { padding: 0 32rpx; }
|
|
|
+.section-title { font-size: 32rpx; font-weight: 600; color: #1f2937; margin-bottom: 20rpx; display: block; }
|
|
|
+.loading-container { display: flex; align-items: center; justify-content: center; padding: 100rpx 0; }
|
|
|
+.loading-text { font-size: 28rpx; color: #9ca3af; }
|
|
|
+
|
|
|
+.plans-list { display: flex; flex-direction: column; gap: 20rpx; }
|
|
|
+.plan-card { background: #fff; border-radius: 20rpx; padding: 24rpx; border: 3rpx solid #e5e7eb; position: relative; transition: all 0.3s; }
|
|
|
+.plan-card.active { border-color: #667eea; box-shadow: 0 8rpx 32rpx rgba(102, 126, 234, 0.2); }
|
|
|
+.plan-card.recommended { background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); border-color: #fbbf24; }
|
|
|
+.plan-card.free { background: #f9fafb; }
|
|
|
+
|
|
|
+.recommended-badge { position: absolute; top: -14rpx; right: 20rpx; background: linear-gradient(135deg, #f97316 0%, #fb923c 100%); padding: 6rpx 20rpx; border-radius: 16rpx; font-size: 22rpx; color: #fff; font-weight: 600; }
|
|
|
+.free-badge { position: absolute; top: -14rpx; right: 20rpx; background: linear-gradient(135deg, #10b981 0%, #34d399 100%); padding: 6rpx 20rpx; border-radius: 16rpx; font-size: 22rpx; color: #fff; font-weight: 600; }
|
|
|
+
|
|
|
+.plan-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8rpx; }
|
|
|
+.plan-name { font-size: 30rpx; font-weight: 600; color: #1f2937; }
|
|
|
+.plan-pricing { display: flex; align-items: baseline; }
|
|
|
+.price-symbol { font-size: 24rpx; color: #f97316; }
|
|
|
+.price-value { font-size: 44rpx; font-weight: 700; color: #f97316; }
|
|
|
+.price-unit { font-size: 24rpx; color: #6b7280; margin-left: 4rpx; }
|
|
|
+
|
|
|
+.plan-description { font-size: 24rpx; color: #6b7280; margin-bottom: 16rpx; }
|
|
|
+.plan-features { display: flex; flex-direction: column; gap: 8rpx; margin-bottom: 16rpx; }
|
|
|
+.feature-item { display: flex; align-items: center; }
|
|
|
+.feature-icon { font-size: 22rpx; color: #10b981; margin-right: 8rpx; }
|
|
|
+.feature-text { font-size: 22rpx; color: #4b5563; }
|
|
|
+
|
|
|
+.select-btn { width: 100%; padding: 16rpx; text-align: center; background: #f3f4f6; border-radius: 12rpx; font-size: 26rpx; color: #6b7280; transition: all 0.3s; }
|
|
|
+.select-btn.selected { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #fff; }
|
|
|
+
|
|
|
+.payment-section { padding: 32rpx; margin-top: 16rpx; }
|
|
|
+.payment-methods { display: flex; gap: 20rpx; }
|
|
|
+.payment-method { flex: 1; display: flex; align-items: center; justify-content: center; padding: 24rpx; background: #fff; border: 3rpx solid #e5e7eb; border-radius: 16rpx; transition: all 0.3s; }
|
|
|
+.payment-method.active { border-color: #667eea; background: #f0f2ff; }
|
|
|
+.method-icon { font-size: 36rpx; margin-right: 12rpx; }
|
|
|
+.method-name { font-size: 28rpx; color: #1f2937; }
|
|
|
+
|
|
|
+.bottom-section { position: fixed; bottom: 0; left: 0; right: 0; padding: 24rpx 32rpx; padding-bottom: constant(safe-area-inset-bottom); padding-bottom: env(safe-area-inset-bottom); background: #fff; box-shadow: 0 -4rpx 16rpx rgba(0,0,0,0.04); }
|
|
|
+.total-info { display: flex; align-items: baseline; margin-bottom: 16rpx; }
|
|
|
+.total-label { font-size: 28rpx; color: #6b7280; margin-right: 8rpx; }
|
|
|
+.total-value { font-size: 48rpx; font-weight: 700; color: #f97316; }
|
|
|
+
|
|
|
+.subscribe-btn { width: 100%; height: 96rpx; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 24rpx; border: none; display: flex; align-items: center; justify-content: center; font-size: 32rpx; font-weight: 600; color: #fff; }
|
|
|
+.subscribe-btn[disabled] { background: #d1d5db; }
|
|
|
+</style>
|