first commit
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* 会员/订阅中心
|
||||
*
|
||||
* 支持两种入口模式:
|
||||
* mode=vip(默认): 开通全频道会员
|
||||
* mode=channel : 订阅指定频道,URL 需带 channelId/channelName/monthlyPrice/quarterlyPrice/annualPrice
|
||||
*/
|
||||
const app = getApp()
|
||||
const api = require('../../utils/api')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
isVip: false,
|
||||
// 模式:'vip' | 'channel'
|
||||
mode: 'vip',
|
||||
// 频道订阅模式下的频道信息
|
||||
channelId: '',
|
||||
channelName: '',
|
||||
monthlyPrice: 0,
|
||||
quarterlyPrice: 0,
|
||||
annualPrice: 0,
|
||||
// 预计算(WXML 不支持方法调用)
|
||||
_quarterlySaving: 0,
|
||||
_quarterlyMonthly: '0',
|
||||
_annualSaving: 0,
|
||||
_annualMonthly: '0',
|
||||
// 当前选中的套餐,vip 模式固定 'vip-all',channel 模式为 'monthly'|'quarterly'|'annual'
|
||||
selectedPlan: 'vip-all',
|
||||
currentPrice: '19.9'
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
const isChannelMode = !!options.channelId
|
||||
if (isChannelMode) {
|
||||
// ─── 频道订阅模式 ───
|
||||
const monthly = (parseFloat(options.monthlyPrice) || 0) / 100
|
||||
const quarterly = (parseFloat(options.quarterlyPrice) || 0) / 100
|
||||
const annual = (parseFloat(options.annualPrice) || 0) / 100
|
||||
|
||||
// 默认选中包年,否则最合算的
|
||||
let defaultPlan = 'monthly'
|
||||
let defaultPrice = monthly
|
||||
if (annual > 0) { defaultPlan = 'annual'; defaultPrice = annual }
|
||||
else if (quarterly > 0) { defaultPlan = 'quarterly'; defaultPrice = quarterly }
|
||||
|
||||
this.setData({
|
||||
mode: 'channel',
|
||||
channelId: options.channelId,
|
||||
channelName: decodeURIComponent(options.channelName || ''),
|
||||
monthlyPrice: monthly,
|
||||
quarterlyPrice: quarterly,
|
||||
annualPrice: annual,
|
||||
selectedPlan: defaultPlan,
|
||||
currentPrice: defaultPrice.toFixed(2),
|
||||
_quarterlySaving: (monthly > 0 && quarterly > 0) ? Math.round(monthly * 3 - quarterly) : 0,
|
||||
_quarterlyMonthly: quarterly > 0 ? (quarterly / 3).toFixed(1) : '0',
|
||||
_annualSaving: (monthly > 0 && annual > 0) ? Math.round(monthly * 12 - annual) : 0,
|
||||
_annualMonthly: annual > 0 ? (annual / 12).toFixed(1) : '0'
|
||||
})
|
||||
} else {
|
||||
// ─── VIP 会员模式 ───
|
||||
this.setData({
|
||||
mode: 'vip',
|
||||
isVip: app.globalData.isVip,
|
||||
selectedPlan: 'vip-all',
|
||||
currentPrice: '19.9'
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
onShow() {
|
||||
if (this.data.mode === 'vip') {
|
||||
this.setData({ isVip: app.globalData.isVip })
|
||||
}
|
||||
},
|
||||
|
||||
/** 选择套餐 */
|
||||
selectPlan(e) {
|
||||
const plan = e.currentTarget.dataset.plan
|
||||
let price = '19.9'
|
||||
if (this.data.mode === 'channel') {
|
||||
const map = {
|
||||
monthly: this.data.monthlyPrice,
|
||||
quarterly: this.data.quarterlyPrice,
|
||||
annual: this.data.annualPrice
|
||||
}
|
||||
price = (map[plan] || 0).toFixed(2)
|
||||
}
|
||||
this.setData({ selectedPlan: plan, currentPrice: price })
|
||||
},
|
||||
|
||||
/** 发起支付 */
|
||||
onPay() {
|
||||
const self = this
|
||||
const { mode, selectedPlan, currentPrice, channelId } = this.data
|
||||
|
||||
if (mode === 'vip') {
|
||||
// ── VIP 全频道(模拟,后续接入时替换) ──
|
||||
wx.showModal({
|
||||
title: '确认支付',
|
||||
content: `即将支付 ¥${currentPrice} 开通全频道会员`,
|
||||
success(res) {
|
||||
if (res.confirm) {
|
||||
app.upgradeVip()
|
||||
wx.showToast({ title: '开通成功!', icon: 'success' })
|
||||
setTimeout(function () {
|
||||
self.setData({ isVip: true })
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// ── 频道订阅:唤起微信支付 ──
|
||||
if (!selectedPlan) {
|
||||
wx.showToast({ title: '请选择订阅方案', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
// 套餐 → type 映射(后端约定:1=包月 2=包季 3=包年)
|
||||
const typeMap = { monthly: '1', quarterly: '2', annual: '3' }
|
||||
const payType = typeMap[selectedPlan]
|
||||
if (!payType) {
|
||||
wx.showToast({ title: '未知套餐类型', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
wx.showLoading({ title: '获取支付信息...' })
|
||||
|
||||
api.unlockChannel(channelId, payType)
|
||||
.then(function (res) {
|
||||
if (res.code !== 200 || !res.data || !res.data.payments) {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: res.msg || '获取支付信息失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
const payments = res.data.payments
|
||||
const outTradeNo = res.data.outTradeNo
|
||||
wx.hideLoading()
|
||||
|
||||
// 唤起微信支付
|
||||
wx.requestPayment({
|
||||
timeStamp: payments.timeStamp,
|
||||
nonceStr: payments.nonceStr,
|
||||
package: payments.package,
|
||||
signType: payments.signType || 'RSA',
|
||||
paySign: payments.paySign,
|
||||
success() {
|
||||
// 支付 UI 完成后,主动轮询查询支付结果
|
||||
// 策略:立即查一次,失败则间隔 2s 重试,最多 3 次
|
||||
// 原因:微信回调是异步的,可能比 success 回调晚几秒到
|
||||
self._pollPayStatus(outTradeNo, 3, 2000)
|
||||
},
|
||||
fail(err) {
|
||||
if (err.errMsg && err.errMsg.indexOf('cancel') > -1) {
|
||||
return // 用户主动取消,静默处理
|
||||
}
|
||||
wx.showToast({ title: '支付失败,请重试', icon: 'none' })
|
||||
console.error('[支付] wx.requestPayment 失败:', err)
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(function (err) {
|
||||
wx.hideLoading()
|
||||
console.error('[支付] unlockChannel 请求失败:', err)
|
||||
wx.showToast({ title: '网络异常,请重试', icon: 'none' })
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 轮询支付状态
|
||||
* @param {string} outTradeNo 商户订单号
|
||||
* @param {number} retries 剩余重试次数
|
||||
* @param {number} interval 每次重试间隔(ms)
|
||||
*/
|
||||
_pollPayStatus(outTradeNo, retries, interval) {
|
||||
const self = this
|
||||
wx.showLoading({ title: '验证中...' })
|
||||
|
||||
api.queryPayStatus(outTradeNo)
|
||||
.then(function (paid) {
|
||||
if (paid) {
|
||||
// ✅ 支付确认成功
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '订阅成功!', icon: 'success' })
|
||||
app.emit('subscriptionChange')
|
||||
setTimeout(function () { wx.navigateBack() }, 1500)
|
||||
} else if (retries > 1) {
|
||||
// 🔄 尚未到账,等待后重试(回调可能还在路上)
|
||||
setTimeout(function () {
|
||||
self._pollPayStatus(outTradeNo, retries - 1, interval)
|
||||
}, interval)
|
||||
} else {
|
||||
// ⏳ 重试耗尽:回调可能仍在处理,给用户友好提示后返回
|
||||
wx.hideLoading()
|
||||
wx.showModal({
|
||||
title: '支付处理中',
|
||||
content: '支付已完成,订阅正在确认中,稍后请刷新查看',
|
||||
showCancel: false,
|
||||
success: function () { wx.navigateBack() }
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(function (err) {
|
||||
wx.hideLoading()
|
||||
console.error('[支付] 查询状态失败:', err)
|
||||
// 查询本身网络失败,乐观处理——让后端 webhook 兜底
|
||||
wx.showModal({
|
||||
title: '支付处理中',
|
||||
content: '支付已提交,订阅确认中,稍后请刷新查看',
|
||||
showCancel: false,
|
||||
success: function () { wx.navigateBack() }
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
goBack() {
|
||||
wx.navigateBack()
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
"t-button": "tdesign-miniprogram/button/button",
|
||||
"t-icon": "tdesign-miniprogram/icon/icon",
|
||||
"t-tag": "tdesign-miniprogram/tag/tag"
|
||||
},
|
||||
"navigationBarTitleText": "开通会员"
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
<!-- 会员/订阅中心 -->
|
||||
<page-meta page-style="overflow: hidden;" />
|
||||
<view class="vip-page">
|
||||
|
||||
<!-- ── 已是VIP,直接返回 ── -->
|
||||
<view wx:if="{{isVip && mode === 'vip'}}" class="vip-done">
|
||||
<text class="vip-done-icon">👑</text>
|
||||
<text class="vip-done-title">您已经是全频道会员</text>
|
||||
<text class="vip-done-desc">畅享全部频道,尊享专属权益</text>
|
||||
<button class="done-back-btn" bindtap="goBack">返回</button>
|
||||
</view>
|
||||
|
||||
<!-- ── 主Scroll区域 ── -->
|
||||
<scroll-view
|
||||
wx:else
|
||||
scroll-y
|
||||
enhanced
|
||||
show-scrollbar="{{false}}"
|
||||
class="vip-scroll"
|
||||
>
|
||||
|
||||
<!-- 头部区域,根据模式切换文案 -->
|
||||
<view class="vip-hero">
|
||||
<text class="vip-hero-title">{{mode === 'channel' ? channelName : '开通全频道会员'}}</text>
|
||||
<text class="vip-hero-desc">
|
||||
{{mode === 'channel' ? '选择适合你的订阅方案,随时随地收听' : '解锁全部频道,告别无聊早晨'}}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- ────── MODE: VIP 全频道 ────── -->
|
||||
<block wx:if="{{mode === 'vip'}}">
|
||||
<!-- 权益卡片 -->
|
||||
<view class="benefits-card-wrap">
|
||||
<view class="card benefits-card">
|
||||
<view class="benefits-title">
|
||||
<text class="benefit-crown">👑</text>
|
||||
<text>会员专属特权</text>
|
||||
</view>
|
||||
<view class="benefits-grid">
|
||||
<view class="benefit-item">
|
||||
<text class="benefit-check">✅</text>
|
||||
<view class="benefit-info">
|
||||
<text class="benefit-name">全频道特权</text>
|
||||
<text class="benefit-desc">所有频道自由听</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="benefit-item">
|
||||
<text class="benefit-check">🎧</text>
|
||||
<view class="benefit-info">
|
||||
<text class="benefit-name">纯净免广告</text>
|
||||
<text class="benefit-desc">收听无任何打扰</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="benefit-item">
|
||||
<text class="benefit-check">⬇️</text>
|
||||
<view class="benefit-info">
|
||||
<text class="benefit-name">音频全量下载</text>
|
||||
<text class="benefit-desc">支持离线随时听</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="benefit-item">
|
||||
<text class="benefit-check">⏰</text>
|
||||
<view class="benefit-info">
|
||||
<text class="benefit-name">晨间定时播</text>
|
||||
<text class="benefit-desc">专属智能闹钟</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 套餐 -->
|
||||
<view class="plan-section">
|
||||
<text class="plan-title">选择套餐</text>
|
||||
<view
|
||||
class="plan-card {{selectedPlan === 'vip-all' ? 'selected' : ''}}"
|
||||
bindtap="selectPlan"
|
||||
data-plan="vip-all"
|
||||
>
|
||||
<view wx:if="{{selectedPlan === 'vip-all'}}" class="plan-badge">限时特惠</view>
|
||||
<view class="plan-info">
|
||||
<text class="plan-name">全频道连续包月</text>
|
||||
<text class="plan-desc">自动续费,随时可取消</text>
|
||||
</view>
|
||||
<view class="plan-price">
|
||||
<text class="price-amount"><text class="price-symbol">¥</text>19.9</text>
|
||||
<text class="price-original">¥29.9</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- ────── MODE: 频道订阅 ────── -->
|
||||
<block wx:else>
|
||||
<!-- 权益说明 -->
|
||||
<view class="benefits-card-wrap">
|
||||
<view class="card benefits-card">
|
||||
<view class="benefits-title">
|
||||
<text class="benefit-crown">🎙️</text>
|
||||
<text>订阅后专属权益</text>
|
||||
</view>
|
||||
<view class="benefit-item" style="width:100%; margin-bottom:16rpx;">
|
||||
<text class="benefit-check">✅</text>
|
||||
<view class="benefit-info">
|
||||
<text class="benefit-name">无限收听该频道全部节目</text>
|
||||
<text class="benefit-desc">订阅期内随时播放,无次数限制</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="benefit-item" style="width:100%; margin-bottom:16rpx;">
|
||||
<text class="benefit-check">🔔</text>
|
||||
<view class="benefit-info">
|
||||
<text class="benefit-name">新节目第一时间通知</text>
|
||||
<text class="benefit-desc">每日晨间准时推送</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="benefit-item" style="width:100%;">
|
||||
<text class="benefit-check">🎧</text>
|
||||
<view class="benefit-info">
|
||||
<text class="benefit-name">后台播放,不中断</text>
|
||||
<text class="benefit-desc">边听边做其他事</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 价格套餐 -->
|
||||
<view class="plan-section">
|
||||
<text class="plan-title">选择方案</text>
|
||||
|
||||
<!-- 包月 -->
|
||||
<view
|
||||
wx:if="{{monthlyPrice > 0}}"
|
||||
class="plan-card {{selectedPlan === 'monthly' ? 'selected' : ''}}"
|
||||
bindtap="selectPlan"
|
||||
data-plan="monthly"
|
||||
>
|
||||
<view class="plan-info">
|
||||
<text class="plan-name">包月</text>
|
||||
<text class="plan-desc">按月订阅,随时取消</text>
|
||||
</view>
|
||||
<view class="plan-price">
|
||||
<text class="price-amount"><text class="price-symbol">¥</text>{{monthlyPrice}}</text>
|
||||
<text class="price-original">/ 月</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 包季 -->
|
||||
<view
|
||||
wx:if="{{quarterlyPrice > 0}}"
|
||||
class="plan-card {{selectedPlan === 'quarterly' ? 'selected' : ''}}"
|
||||
bindtap="selectPlan"
|
||||
data-plan="quarterly"
|
||||
>
|
||||
<view wx:if="{{_quarterlySaving > 0}}" class="plan-badge plan-badge-save">省{{_quarterlySaving}}元</view>
|
||||
<view class="plan-info">
|
||||
<text class="plan-name">包季</text>
|
||||
<text class="plan-desc">约¥{{_quarterlyMonthly}}/月,比包月更划算</text>
|
||||
</view>
|
||||
<view class="plan-price">
|
||||
<text class="price-amount"><text class="price-symbol">¥</text>{{quarterlyPrice}}</text>
|
||||
<text class="price-original">/ 季</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 包年 -->
|
||||
<view
|
||||
wx:if="{{annualPrice > 0}}"
|
||||
class="plan-card {{selectedPlan === 'annual' ? 'selected' : ''}}"
|
||||
bindtap="selectPlan"
|
||||
data-plan="annual"
|
||||
>
|
||||
<view class="plan-badge plan-badge-hot">最受欢迎</view>
|
||||
<view wx:if="{{_annualSaving > 0}}" class="plan-badge plan-badge-save" style="right: 96rpx;">省{{_annualSaving}}元</view>
|
||||
<view class="plan-info">
|
||||
<text class="plan-name">包年</text>
|
||||
<text class="plan-desc">约¥{{_annualMonthly}}/月,最优惠</text>
|
||||
</view>
|
||||
<view class="plan-price">
|
||||
<text class="price-amount"><text class="price-symbol">¥</text>{{annualPrice}}</text>
|
||||
<text class="price-original">/ 年</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部支付栏(scroll-view 外,flex 布局固定底部,真机可靠) -->
|
||||
<view class="pay-bar" wx:if="{{!isVip || mode === 'channel'}}">
|
||||
<view class="pay-summary">
|
||||
<text class="pay-label">总计:</text>
|
||||
<text class="pay-amount">¥{{currentPrice}}</text>
|
||||
</view>
|
||||
<view class="pay-method">
|
||||
<text class="pay-method-text">使用 微信支付 支付</text>
|
||||
<text class="wechat-check">✓</text>
|
||||
</view>
|
||||
<t-button
|
||||
theme="primary"
|
||||
shape="round"
|
||||
block
|
||||
size="large"
|
||||
bind:tap="onPay"
|
||||
style="--td-button-primary-bg-color: #FF9D42; --td-button-primary-active-bg-color: #E88A35;"
|
||||
>
|
||||
{{mode === 'channel' ? '立即订阅并支付' : '立即开通并支付'}}
|
||||
</t-button>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,287 @@
|
||||
/* 会员中心样式 */
|
||||
::-webkit-scrollbar { display: none !important; width: 0 !important; height: 0 !important; }
|
||||
|
||||
.vip-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background: #FCFCFC;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 已是VIP */
|
||||
.vip-done {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
padding: 40rpx;
|
||||
background: #FFFFFF;
|
||||
}
|
||||
.vip-done-icon {
|
||||
font-size: 120rpx;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
.vip-done-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
.vip-done-desc {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin-bottom: 48rpx;
|
||||
}
|
||||
.done-back-btn {
|
||||
padding: 16rpx 48rpx;
|
||||
background: #F5F5F5;
|
||||
border-radius: 999rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
color: #666;
|
||||
}
|
||||
.done-back-btn::after { border: none; }
|
||||
|
||||
/* 可滚动内容区 */
|
||||
.vip-scroll {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 支付栏(flex 子元素,不用 position:fixed,真机更可靠) */
|
||||
.pay-bar {
|
||||
flex-shrink: 0;
|
||||
background: #FFF;
|
||||
border-top: 1rpx solid #F5F5F5;
|
||||
padding: 20rpx 32rpx 48rpx;
|
||||
box-shadow: 0 -8rpx 32rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* 深色头部 */
|
||||
.vip-hero {
|
||||
background: linear-gradient(to bottom, #1F2937, #111827);
|
||||
padding: 20rpx 40rpx 80rpx;
|
||||
border-radius: 0 0 64rpx 64rpx;
|
||||
box-shadow: 0 16rpx 40rpx rgba(0, 0, 0, 0.2);
|
||||
position: relative;
|
||||
}
|
||||
.back-btn {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 24rpx;
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
}
|
||||
.back-arrow {
|
||||
font-size: 48rpx;
|
||||
color: #FFF;
|
||||
font-weight: 300;
|
||||
}
|
||||
.vip-hero-title {
|
||||
display: block;
|
||||
font-size: 52rpx;
|
||||
font-weight: 800;
|
||||
color: #FFF;
|
||||
letter-spacing: -2rpx;
|
||||
margin-bottom: 12rpx;
|
||||
margin-top: 48rpx;
|
||||
}
|
||||
.vip-hero-desc {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #FCD34D;
|
||||
}
|
||||
|
||||
/* 权益卡片 */
|
||||
.benefits-card-wrap {
|
||||
padding: 0 32rpx;
|
||||
margin-top: -40rpx;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
.benefits-card {
|
||||
padding: 36rpx;
|
||||
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.06);
|
||||
border: 1rpx solid rgba(251, 191, 36, 0.15);
|
||||
}
|
||||
.benefits-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 28rpx;
|
||||
}
|
||||
.benefit-crown {
|
||||
font-size: 36rpx;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.benefits-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 28rpx 16rpx;
|
||||
}
|
||||
.benefit-item {
|
||||
width: calc(50% - 8rpx);
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.benefit-check {
|
||||
font-size: 28rpx;
|
||||
margin-right: 12rpx;
|
||||
flex-shrink: 0;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
.benefit-info {
|
||||
flex: 1;
|
||||
}
|
||||
.benefit-name {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
line-height: 1.3;
|
||||
}
|
||||
.benefit-desc {
|
||||
display: block;
|
||||
font-size: 20rpx;
|
||||
color: #999;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
/* 套餐 */
|
||||
.plan-section {
|
||||
padding: 32rpx 32rpx 0;
|
||||
}
|
||||
.plan-title {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
margin-bottom: 24rpx;
|
||||
padding-left: 8rpx;
|
||||
}
|
||||
|
||||
.plan-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 28rpx;
|
||||
border: 4rpx solid #F0F0F0;
|
||||
border-radius: 24rpx;
|
||||
background: #FFF;
|
||||
margin-bottom: 16rpx;
|
||||
position: relative;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.plan-card.selected {
|
||||
border-color: #FBBF24;
|
||||
background: rgba(251, 191, 36, 0.08);
|
||||
}
|
||||
.plan-badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background: #FBBF24;
|
||||
color: #1F2937;
|
||||
font-size: 20rpx;
|
||||
font-weight: 700;
|
||||
padding: 6rpx 20rpx;
|
||||
border-radius: 0 20rpx 0 20rpx;
|
||||
}
|
||||
.plan-badge-hot {
|
||||
background: linear-gradient(135deg, #FF9D42, #FF7832);
|
||||
color: #FFF;
|
||||
}
|
||||
.plan-badge-save {
|
||||
background: #2ECC71;
|
||||
color: #FFF;
|
||||
border-radius: 0 0 0 20rpx;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
.plan-info {
|
||||
flex: 1;
|
||||
}
|
||||
.plan-name {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
.plan-desc {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.plan-price {
|
||||
text-align: right;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.price-amount {
|
||||
font-size: 40rpx;
|
||||
font-weight: 800;
|
||||
color: #D97706;
|
||||
}
|
||||
.price-amount.single {
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
}
|
||||
.price-symbol {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
.price-original {
|
||||
display: block;
|
||||
font-size: 20rpx;
|
||||
color: #CCC;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
|
||||
.pay-summary {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
margin-bottom: 12rpx;
|
||||
padding: 0 8rpx;
|
||||
}
|
||||
.pay-label {
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
color: #666;
|
||||
}
|
||||
.pay-amount {
|
||||
font-size: 36rpx;
|
||||
font-weight: 800;
|
||||
color: #D97706;
|
||||
margin-left: 4rpx;
|
||||
}
|
||||
.pay-method {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 0 8rpx;
|
||||
}
|
||||
.pay-method-text {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
.wechat-check {
|
||||
font-size: 18rpx;
|
||||
background: #07C160;
|
||||
color: #FFF;
|
||||
padding: 2rpx 8rpx;
|
||||
border-radius: 4rpx;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
Reference in New Issue
Block a user