feat: 优化UI

This commit is contained in:
Blizzard
2026-03-05 17:04:40 +08:00
parent 0a61c4ddec
commit 7f51b2a0a8
28 changed files with 1773 additions and 964 deletions
+111 -16
View File
@@ -37,7 +37,6 @@ Page({
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 }
@@ -59,15 +58,42 @@ Page({
})
} else {
// ─── VIP 会员模式 ───
const gd = app.globalData
this.setData({
mode: 'vip',
isVip: app.globalData.isVip,
isVip: gd.isVip,
selectedPlan: 'vip-all',
currentPrice: '19.9'
currentPrice: '--',
vipExpireAt: gd.vipExpireAt ? gd.vipExpireAt.substring(0, 10) : ''
})
// 从后端拉 VIP 配置
this._loadVipConfig()
}
},
_loadVipConfig() {
const self = this
api.getVipConfig().then(function (res) {
if (res.code === 200 && res.data) {
var cfg = res.data
// 后端单位:分 → 元
var price = cfg.discountedPrice > 0 ? (cfg.discountedPrice / 100).toFixed(2) : (cfg.price / 100).toFixed(2)
var originalPrice = cfg.price > 0 ? (cfg.price / 100).toFixed(2) : ''
var hasDiscount = cfg.discountedPrice > 0 && cfg.discountedPrice < cfg.price
self.setData({
currentPrice: price,
vipPrice: price,
vipOriginalPrice: hasDiscount ? originalPrice : '',
vipRemark: cfg.remark || ''
})
}
}).catch(function (err) {
console.error('[VIP] 获取配置失败:', err)
// 容错:使用默认价格
self.setData({ currentPrice: '19.90', vipPrice: '19.90', vipOriginalPrice: '29.90' })
})
},
onShow() {
if (this.data.mode === 'vip') {
this.setData({ isVip: app.globalData.isVip })
@@ -95,20 +121,42 @@ Page({
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)
// ── VIP 永久会员:调后端预支付接口 ──
wx.showLoading({ title: '获取支付信息...' })
api.initiateVipPayment()
.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() {
self._pollVipStatus(outTradeNo, 3, 2000)
},
fail(err) {
if (err.errMsg && err.errMsg.indexOf('cancel') > -1) return
wx.showToast({ title: '支付失败,请重试', icon: 'none' })
console.error('[VIP支付] wx.requestPayment 失败:', err)
}
})
})
.catch(function (err) {
wx.hideLoading()
console.error('[VIP支付] 接口请求失败:', err)
wx.showToast({ title: '网络异常,请重试', icon: 'none' })
})
return
}
@@ -216,6 +264,53 @@ Page({
})
},
/**
* 轮询 VIP 支付状态
* 成功后更新全局 isVip + 触发 vipChange 事件
*/
_pollVipStatus(outTradeNo, retries, interval) {
const self = this
wx.showLoading({ title: '验证中...' })
api.queryPayStatus(outTradeNo)
.then(function (paid) {
if (paid) {
// ✅ VIP 开通确认成功
wx.hideLoading()
// 更新全局状态
app.globalData.isVip = true
app.emit('vipChange', { isVip: true })
self.setData({ isVip: true })
wx.showToast({ title: '🎉 VIP 开通成功!', icon: 'none' })
setTimeout(function () { wx.navigateBack() }, 1500)
} else if (retries > 1) {
// 🔄 尚未到账,等待后重试
setTimeout(function () {
self._pollVipStatus(outTradeNo, retries - 1, interval)
}, interval)
} else {
// ⏳ 重试耗尽,乐观提示
wx.hideLoading()
wx.showModal({
title: '支付处理中',
content: 'VIP 开通已提交,正在确认中,稍后请重新进入查看',
showCancel: false,
success: function () { wx.navigateBack() }
})
}
})
.catch(function (err) {
wx.hideLoading()
console.error('[VIP支付] 查询状态失败:', err)
wx.showModal({
title: '支付处理中',
content: 'VIP 开通已提交,稍后请刷新查看是否生效',
showCancel: false,
success: function () { wx.navigateBack() }
})
})
},
goBack() {
wx.navigateBack()
}
+55 -25
View File
@@ -2,12 +2,56 @@
<page-meta page-style="overflow: hidden;" />
<view class="vip-page">
<!-- ── 已是VIP直接返回 ── -->
<!-- ── 已是 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 class="vip-glow"></view>
<!-- 安全区域占位 -->
<view style="height: {{statusBarHeight}}px;"></view>
<!-- 嵇章区 -->
<view class="vip-done-hero">
<view class="vip-crown-wrap">
<text class="vip-crown">👑</text>
<view class="vip-crown-ring ring-1"></view>
<view class="vip-crown-ring ring-2"></view>
</view>
<text class="vip-done-title">全频道会员</text>
<view wx:if="{{vipExpireAt}}" class="vip-expire-badge">
<text class="vip-expire-text">永久有效 · 不限期限</text>
</view>
<view wx:else class="vip-expire-badge">
<text class="vip-expire-text">永久有效 · 不限期限</text>
</view>
</view>
<!-- 权益卡片网格 -->
<view class="vip-done-benefits">
<view class="vip-benefit-item">
<text class="vb-icon">🔓</text>
<text class="vb-name">全频道解锁</text>
</view>
<view class="vip-benefit-item">
<text class="vb-icon">🎧</text>
<text class="vb-name">免广告收听</text>
</view>
<view class="vip-benefit-item">
<text class="vb-icon">⭐</text>
<text class="vb-name">优先推送</text>
</view>
<view class="vip-benefit-item">
<text class="vb-icon">💬</text>
<text class="vb-name">互动评论</text>
</view>
</view>
<!-- 返回按钮 -->
<view class="vip-done-actions">
<button class="done-back-btn" bindtap="goBack">返回收听</button>
</view>
</view>
<!-- ── 主Scroll区域 ── -->
@@ -23,7 +67,7 @@
<view class="vip-hero">
<text class="vip-hero-title">{{mode === 'channel' ? channelName : '开通全频道会员'}}</text>
<text class="vip-hero-desc">
{{mode === 'channel' ? '选择适合你的订阅方案,随时随地收听' : '解锁全部频道,告别无聊早晨'}}
{{mode === 'channel' ? '选择适合你的订阅方案,随时随地收听' : '一次开通,永久解锁全部频道'}}
</text>
</view>
@@ -51,20 +95,6 @@
<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>
@@ -77,14 +107,14 @@
bindtap="selectPlan"
data-plan="vip-all"
>
<view wx:if="{{selectedPlan === 'vip-all'}}" class="plan-badge">限时特惠</view>
<view wx:if="{{vipOriginalPrice}}" class="plan-badge">限时特惠</view>
<view class="plan-info">
<text class="plan-name">全频道连续包月</text>
<text class="plan-desc">自动续费,随时可取消</text>
<text class="plan-name">永久会员</text>
<text class="plan-desc">{{vipRemark || '一次购买,永久畅听全部频道'}}</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>
<text class="price-amount"><text class="price-symbol">¥</text>{{vipPrice || currentPrice}}</text>
<text class="price-original" wx:if="{{vipOriginalPrice}}">¥{{vipOriginalPrice}}</text>
</view>
</view>
</view>
+134 -21
View File
@@ -9,39 +9,152 @@
overflow: hidden;
}
/* 已是VIP */
/* 已是VIP 页面整体 */
.vip-done {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
padding: 40rpx;
background: #FFFFFF;
background: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%);
position: relative;
overflow: hidden;
}
.vip-done-icon {
font-size: 120rpx;
margin-bottom: 40rpx;
/* 头部云光装饰 */
.vip-glow {
position: absolute;
top: -150rpx;
left: 50%;
transform: translateX(-50%);
width: 1000rpx;
height: 1000rpx;
background: radial-gradient(circle, rgba(251, 191, 36, 0.15) 0%, rgba(0,0,0,0) 60%);
z-index: 1;
}
/* 勋章区 */
.vip-done-hero {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 120rpx;
z-index: 2;
}
.vip-crown-wrap {
width: 180rpx;
height: 180rpx;
background: linear-gradient(135deg, #FFDF8A 0%, #D99B22 100%);
border-radius: 50%;
border: 4rpx solid rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
position: relative;
box-shadow: 0 16rpx 40rpx rgba(217, 155, 34, 0.4);
margin-bottom: 60rpx;
}
.vip-crown {
font-size: 80rpx;
z-index: 10;
}
.vip-crown-ring {
position: absolute;
border-radius: 50%;
border: 2rpx solid rgba(255, 223, 138, 0.4);
}
.ring-1 {
width: 240rpx;
height: 240rpx;
animation: pulse 2s infinite;
}
.ring-2 {
width: 300rpx;
height: 300rpx;
animation: pulse 2s infinite 1s;
}
@keyframes pulse {
0% { transform: scale(0.8); opacity: 1; }
100% { transform: scale(1.2); opacity: 0; }
}
.vip-done-title {
font-size: 36rpx;
font-weight: 700;
color: #333;
margin-bottom: 16rpx;
font-size: 44rpx;
font-weight: 800;
color: #FFF;
letter-spacing: 4rpx;
margin-bottom: 24rpx;
text-shadow: 0 4rpx 16rpx rgba(0,0,0,0.5);
}
.vip-done-desc {
font-size: 28rpx;
color: #999;
margin-bottom: 48rpx;
}
.done-back-btn {
padding: 16rpx 48rpx;
background: #F5F5F5;
.vip-expire-badge {
background: rgba(255, 255, 255, 0.1);
padding: 12rpx 32rpx;
border-radius: 999rpx;
font-size: 28rpx;
border: 1rpx solid rgba(251, 191, 36, 0.3);
}
.vip-expire-text {
font-size: 24rpx;
color: #FCD34D;
font-weight: 500;
}
/* 权益网格 */
.vip-done-benefits {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 24rpx 0;
margin-top: 80rpx;
padding: 0 50rpx;
z-index: 2;
width: 100%;
box-sizing: border-box;
}
.vip-benefit-item {
width: 48%;
background: rgba(255, 255, 255, 0.06);
border: 1rpx solid rgba(255, 255, 255, 0.1);
border-radius: 24rpx;
padding: 32rpx 24rpx;
display: flex;
align-items: center;
box-sizing: border-box;
}
.vb-icon {
font-size: 40rpx;
margin-right: 16rpx;
}
.vb-name {
font-size: 28rpx;
color: #FFF;
font-weight: 600;
}
/* 按钮操作区 */
.vip-done-actions {
margin-top: auto;
margin-bottom: 120rpx;
z-index: 2;
}
.done-back-btn {
background: linear-gradient(90deg, #FCD34D 0%, #F59E0B 100%);
color: #5F370E;
font-size: 32rpx;
font-weight: 700;
padding: 24rpx 120rpx;
border-radius: 999rpx;
box-shadow: 0 8rpx 32rpx rgba(245, 158, 11, 0.4);
border: none;
color: #666;
line-height: 1.5;
}
.done-back-btn::after { border: none; }