feat: 优化UI
This commit is contained in:
@@ -191,8 +191,13 @@ App({
|
|||||||
self.globalData.isLoggedIn = true
|
self.globalData.isLoggedIn = true
|
||||||
self.globalData.token = token
|
self.globalData.token = token
|
||||||
self.globalData.userInfo = user
|
self.globalData.userInfo = user
|
||||||
|
self.globalData.isVip = user.isVip === 1
|
||||||
|
self.globalData.vipExpireAt = user.vipExpireAt || null
|
||||||
wx.setStorageSync('token', token)
|
wx.setStorageSync('token', token)
|
||||||
self.emit('loginStateChange', { isLoggedIn: true })
|
self.emit('loginStateChange', { isLoggedIn: true })
|
||||||
|
if (user.isVip === 1) {
|
||||||
|
self.emit('vipChange', { isVip: true })
|
||||||
|
}
|
||||||
resolve(res.data)
|
resolve(res.data)
|
||||||
} else {
|
} else {
|
||||||
reject(new Error(res.msg || '登录失败'))
|
reject(new Error(res.msg || '登录失败'))
|
||||||
|
|||||||
@@ -13,10 +13,12 @@ Page({
|
|||||||
data: {
|
data: {
|
||||||
domain: {},
|
domain: {},
|
||||||
isSubscribed: false,
|
isSubscribed: false,
|
||||||
isExpired: false, // 订阅是否已过期
|
isExpired: false,
|
||||||
expiredAt: '', // 到期时间(格式化)
|
expiredAt: '',
|
||||||
isFree: false, // 快捷字段,避免模板 domain.isFree
|
isFree: false,
|
||||||
isVipOnly: false,
|
isVipOnly: false,
|
||||||
|
isVip: false,
|
||||||
|
canPlay: false,
|
||||||
domainContents: [],
|
domainContents: [],
|
||||||
isPlaying: false,
|
isPlaying: false,
|
||||||
loading: true
|
loading: true
|
||||||
@@ -52,7 +54,6 @@ Page({
|
|||||||
const ch = res.data
|
const ch = res.data
|
||||||
const isFree = ch.isFree === 1
|
const isFree = ch.isFree === 1
|
||||||
|
|
||||||
// 免费频道:不关心订阅状态和到期时间
|
|
||||||
var expiredAt = ''
|
var expiredAt = ''
|
||||||
var isExpired = false
|
var isExpired = false
|
||||||
var isSubscribed = false
|
var isSubscribed = false
|
||||||
@@ -64,13 +65,18 @@ Page({
|
|||||||
isSubscribed = ch.hasSubscribed === 1 && !isExpired
|
isSubscribed = ch.hasSubscribed === 1 && !isExpired
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 可播放:VIP 或免费频道或已订阅
|
||||||
|
const canPlay = app.globalData.isVip || isFree || isSubscribed
|
||||||
|
|
||||||
self.setData({
|
self.setData({
|
||||||
domain: ch,
|
domain: ch,
|
||||||
isSubscribed,
|
isSubscribed,
|
||||||
isExpired,
|
isExpired,
|
||||||
expiredAt,
|
expiredAt,
|
||||||
isFree,
|
isFree,
|
||||||
isVipOnly: ch.isVipOnly === 1
|
isVipOnly: ch.isVipOnly === 1,
|
||||||
|
isVip: app.globalData.isVip,
|
||||||
|
canPlay
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
@@ -92,8 +98,7 @@ Page({
|
|||||||
}
|
}
|
||||||
|
|
||||||
var gd = app.globalData
|
var gd = app.globalData
|
||||||
var isSubscribed = self.data.isSubscribed
|
var canPlay = self.data.canPlay
|
||||||
var isFree = self.data.domain.isFree === 1
|
|
||||||
var total = contents.length
|
var total = contents.length
|
||||||
|
|
||||||
contents = contents.map(function (item, idx) {
|
contents = contents.map(function (item, idx) {
|
||||||
@@ -102,7 +107,7 @@ Page({
|
|||||||
_dateDot: item.createdAt ? item.createdAt.substring(0, 10).replace(/-/g, '.') : '',
|
_dateDot: item.createdAt ? item.createdAt.substring(0, 10).replace(/-/g, '.') : '',
|
||||||
durationText: util.formatTime(item.duration || 0),
|
durationText: util.formatTime(item.duration || 0),
|
||||||
_isThisPlaying: gd.activeContent && gd.activeContent.id === item.id,
|
_isThisPlaying: gd.activeContent && gd.activeContent.id === item.id,
|
||||||
_isLocked: !isSubscribed && !isFree && idx > 0
|
_isLocked: !canPlay // 所有集全部锁定,没有试听
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -133,11 +138,12 @@ Page({
|
|||||||
|
|
||||||
onPlayItem(e) {
|
onPlayItem(e) {
|
||||||
const id = e.currentTarget.dataset.id
|
const id = e.currentTarget.dataset.id
|
||||||
const idx = parseInt(e.currentTarget.dataset.idx)
|
|
||||||
const gd = app.globalData
|
const gd = app.globalData
|
||||||
|
|
||||||
if (!this.data.isSubscribed && !(this.data.domain.isFree === 1) && idx > 0) {
|
// 核心权限判断:VIP || 免费频道 || 已订阅
|
||||||
wx.showToast({ title: '请先订阅该频道以解锁往期内容', icon: 'none' })
|
if (!this.data.canPlay) {
|
||||||
|
// 引导到支付页
|
||||||
|
this.onSubscribe()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,25 +167,51 @@ Page({
|
|||||||
const id = this._domainId
|
const id = this._domainId
|
||||||
const domain = this.data.domain
|
const domain = this.data.domain
|
||||||
|
|
||||||
// 已订阅 → 已在订阅中,无需操作
|
// 已可播放(VIP / 免费 / 已订阅)——正常情况下不会触发此方法
|
||||||
if (this.data.isSubscribed) {
|
if (this.data.canPlay) {
|
||||||
wx.showToast({ title: '您已订阅该频道', icon: 'none' })
|
wx.showToast({ title: '您已可收听该频道', icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 免费频道 → 直接收听
|
// VIP专享频道:不支持单独订阅,必须开通 VIP
|
||||||
|
if (domain.isVipOnly === 1) {
|
||||||
|
wx.navigateTo({ url: '/pages/vip/index' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 免费频道(理论上不会到这里,安全先)
|
||||||
if (domain.isFree === 1) {
|
if (domain.isFree === 1) {
|
||||||
wx.showToast({ title: '免费频道,直接收听!', icon: 'none' })
|
wx.showToast({ title: '免费频道,直接收听!', icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// VIP专享且未开通 → VIP页
|
// 付费频道(未订阅 / 已过期)——跳转订阅/支付页
|
||||||
if (domain.isVipOnly === 1 && !app.globalData.isVip) {
|
var params = 'channelId=' + id
|
||||||
wx.navigateTo({ url: '/pages/vip/index' })
|
+ '&channelName=' + encodeURIComponent(domain.name || '')
|
||||||
return
|
+ '&monthlyPrice=' + (domain.monthlyPrice || 0)
|
||||||
}
|
+ '&quarterlyPrice=' + (domain.quarterlyPrice || 0)
|
||||||
|
+ '&annualPrice=' + (domain.annualPrice || 0)
|
||||||
|
wx.navigateTo({ url: '/pages/vip/index?' + params })
|
||||||
|
},
|
||||||
|
|
||||||
// 付费频道(含已过期续费)→ 跳转订阅/支付页
|
goFirstProgram() {
|
||||||
|
var list = this.data.domainContents
|
||||||
|
if (list && list.length > 0) {
|
||||||
|
var first = list[0]
|
||||||
|
if (app.globalData.activeContent && app.globalData.activeContent.id === first.id) {
|
||||||
|
app.togglePlay()
|
||||||
|
} else {
|
||||||
|
app.playContent(first)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
wx.showToast({ title: '暂无节目', icon: 'none' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 续订:直接跳支付页,不走 canPlay 检查 */
|
||||||
|
onRenew() {
|
||||||
|
const id = this._domainId
|
||||||
|
const domain = this.data.domain
|
||||||
var params = 'channelId=' + id
|
var params = 'channelId=' + id
|
||||||
+ '&channelName=' + encodeURIComponent(domain.name || '')
|
+ '&channelName=' + encodeURIComponent(domain.name || '')
|
||||||
+ '&monthlyPrice=' + (domain.monthlyPrice || 0)
|
+ '&monthlyPrice=' + (domain.monthlyPrice || 0)
|
||||||
|
|||||||
@@ -9,10 +9,27 @@
|
|||||||
<text class="hero-name">{{domain.name}}</text>
|
<text class="hero-name">{{domain.name}}</text>
|
||||||
<text class="hero-tag">{{domain.tag || domain.description || ''}}</text>
|
<text class="hero-tag">{{domain.tag || domain.description || ''}}</text>
|
||||||
|
|
||||||
<!-- ═══ 按钮区:根据频道类型和订阅状态分情况 ═══ -->
|
<!-- ═══ 按钮区 ═══ -->
|
||||||
|
|
||||||
<!-- 1. 免费频道 -->
|
<!-- 0. 可播放(VIP / 免费 / 已订阅)→ 直接收听 -->
|
||||||
<block wx:if="{{isFree}}">
|
<block wx:if="{{canPlay}}">
|
||||||
|
<button class="hero-sub-btn free-btn" bindtap="goFirstProgram">
|
||||||
|
<text>▶ 开始收听</text>
|
||||||
|
</button>
|
||||||
|
<!-- 副标签 -->
|
||||||
|
<text wx:if="{{isVip}}" class="hero-expired">👑 VIP 会员畅享</text>
|
||||||
|
<text wx:elif="{{isFree}}" class="hero-expired">🎁 永久免费频道</text>
|
||||||
|
<view wx:elif="{{isSubscribed}}" class="hero-sub-row">
|
||||||
|
<text class="hero-expired">有效至 {{expiredAt || '长期有效'}}</text>
|
||||||
|
<!-- 续订按钮:付费已订阅频道才显示 -->
|
||||||
|
<view wx:if="{{!isVipOnly}}" class="renew-inline-btn tap-active" bindtap="onRenew">
|
||||||
|
<text class="renew-inline-text">续订</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</block>
|
||||||
|
|
||||||
|
<!-- 1. 免费频道(但不可播放,理论上不会到这里) -->
|
||||||
|
<block wx:elif="{{isFree}}">
|
||||||
<view class="hero-badge free-badge">🎁 永久免费</view>
|
<view class="hero-badge free-badge">🎁 永久免费</view>
|
||||||
<button class="hero-sub-btn free-btn" bindtap="onSubscribe">
|
<button class="hero-sub-btn free-btn" bindtap="onSubscribe">
|
||||||
<text>▶ 开始收听</text>
|
<text>▶ 开始收听</text>
|
||||||
@@ -27,24 +44,16 @@
|
|||||||
</button>
|
</button>
|
||||||
</block>
|
</block>
|
||||||
|
|
||||||
<!-- 3. 已订阅且有效 -->
|
<!-- 3. 订阅已过期 -->
|
||||||
<block wx:elif="{{isSubscribed}}">
|
|
||||||
<button class="hero-sub-btn subscribed" bindtap="onSubscribe">
|
|
||||||
<text>✓ 已订阅</text>
|
|
||||||
</button>
|
|
||||||
<text wx:if="{{expiredAt}}" class="hero-expired">有效至 {{expiredAt}}</text>
|
|
||||||
</block>
|
|
||||||
|
|
||||||
<!-- 4. 订阅已过期(重新订阅) -->
|
|
||||||
<block wx:elif="{{isExpired}}">
|
<block wx:elif="{{isExpired}}">
|
||||||
<view class="hero-badge expired-badge">⏰ 订阅已到期</view>
|
<view class="hero-badge expired-badge">⏰ 订阅已到期</view>
|
||||||
<button class="hero-sub-btn renew-btn" bindtap="onSubscribe">
|
<button class="hero-sub-btn renew-btn" bindtap="onSubscribe">
|
||||||
<text>续费订阅</text>
|
<text>续费订阅</text>
|
||||||
</button>
|
</button>
|
||||||
<text wx:if="{{expiredAt}}" class="hero-expired">已于 {{expiredAt}} 到期</text>
|
<text class="hero-expired">已于 {{expiredAt}} 到期</text>
|
||||||
</block>
|
</block>
|
||||||
|
|
||||||
<!-- 5. 未订阅付费频道 -->
|
<!-- 4. 未订阅付费频道 -->
|
||||||
<block wx:else>
|
<block wx:else>
|
||||||
<button class="hero-sub-btn" bindtap="onSubscribe">
|
<button class="hero-sub-btn" bindtap="onSubscribe">
|
||||||
<text>订阅频道</text>
|
<text>订阅频道</text>
|
||||||
@@ -61,7 +70,7 @@
|
|||||||
|
|
||||||
<!-- 提示条:根据状态动态切换 -->
|
<!-- 提示条:根据状态动态切换 -->
|
||||||
<!-- VIP专享未开通 -->
|
<!-- VIP专享未开通 -->
|
||||||
<view wx:if="{{isVipOnly && !isSubscribed}}" class="trial-notice vip-notice" bindtap="onSubscribe">
|
<view wx:if="{{isVipOnly && !canPlay}}" class="trial-notice vip-notice" bindtap="onSubscribe">
|
||||||
<text class="notice-icon">👑</text>
|
<text class="notice-icon">👑</text>
|
||||||
<view class="notice-info">
|
<view class="notice-info">
|
||||||
<text class="notice-title">VIP专属频道</text>
|
<text class="notice-title">VIP专属频道</text>
|
||||||
@@ -80,14 +89,14 @@
|
|||||||
<text class="notice-action">续费 ›</text>
|
<text class="notice-action">续费 ›</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 付费频道未订阅(试听) -->
|
<!-- 付费频道未订阅(全锁,无试听) -->
|
||||||
<view wx:elif="{{!isSubscribed && !isFree}}" class="trial-notice" bindtap="onSubscribe">
|
<view wx:elif="{{!canPlay && !isFree}}" class="trial-notice locked-notice" bindtap="onSubscribe">
|
||||||
<text class="notice-icon">🔒</text>
|
<text class="notice-icon">🔐</text>
|
||||||
<view class="notice-info">
|
<view class="notice-info">
|
||||||
<text class="notice-title">试听模式</text>
|
<text class="notice-title">频道未解锁</text>
|
||||||
<text class="notice-desc">可试听最新一期,订阅后解锁全部历史内容</text>
|
<text class="notice-desc">订阅频道或开通全频道会员,即可畅听全部内容</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="notice-action">订阅 ›</text>
|
<text class="notice-action">立即解锁 ›</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 内容列表标题 -->
|
<!-- 内容列表标题 -->
|
||||||
@@ -119,18 +128,14 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 播放按钮 -->
|
<!-- 播放 / 锁定按钮 -->
|
||||||
<view wx:if="{{!item._isLocked}}" class="item-play-btn {{item._isThisPlaying ? 'active' : ''}}">
|
<view class="item-play-btn {{item._isThisPlaying ? 'active' : ''}} {{item._isLocked ? 'locked-btn' : ''}}">
|
||||||
<image
|
<text wx:if="{{item._isLocked}}" class="lock-icon">🔒</text>
|
||||||
wx:if="{{item._isThisPlaying && isPlaying}}"
|
<view wx:elif="{{item._isThisPlaying && isPlaying}}" class="mini-pause">
|
||||||
src="/assets/icons/pause.svg"
|
<view class="mp-bar"></view>
|
||||||
class="item-play-icon"
|
<view class="mp-bar"></view>
|
||||||
/>
|
</view>
|
||||||
<image
|
<text wx:else class="play-tri">▶</text>
|
||||||
wx:else
|
|
||||||
src="/assets/icons/play.svg"
|
|
||||||
class="item-play-icon"
|
|
||||||
/>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -86,6 +86,24 @@
|
|||||||
color: rgba(255, 255, 255, 0.7);
|
color: rgba(255, 255, 255, 0.7);
|
||||||
letter-spacing: 0.5rpx;
|
letter-spacing: 0.5rpx;
|
||||||
}
|
}
|
||||||
|
/* 到期日 + 续订按钮 同一行 */
|
||||||
|
.hero-sub-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
margin-top: 12rpx;
|
||||||
|
}
|
||||||
|
.renew-inline-btn {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border: 1rpx solid rgba(255, 255, 255, 0.5);
|
||||||
|
border-radius: 32rpx;
|
||||||
|
padding: 6rpx 20rpx;
|
||||||
|
}
|
||||||
|
.renew-inline-text {
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #FFF;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
/* 徽标(免费 / VIP / 到期)*/
|
/* 徽标(免费 / VIP / 到期)*/
|
||||||
.hero-badge {
|
.hero-badge {
|
||||||
@@ -286,10 +304,39 @@
|
|||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
box-shadow: 0 4rpx 16rpx rgba(255, 157, 66, 0.3);
|
box-shadow: 0 4rpx 16rpx rgba(255, 157, 66, 0.3);
|
||||||
}
|
}
|
||||||
.item-play-icon {
|
/* 锁定状态的圆圈 */
|
||||||
width: 28rpx;
|
.item-play-btn.locked-btn {
|
||||||
height: 28rpx;
|
background: #F0F0F0;
|
||||||
|
border-color: #E0E0E0;
|
||||||
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
.item-play-btn.active .item-play-icon {
|
.lock-icon {
|
||||||
filter: brightness(0) invert(1);
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
/* 纯文字播放三角 */
|
||||||
|
.play-tri {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #888;
|
||||||
|
padding-left: 4rpx;
|
||||||
|
}
|
||||||
|
.item-play-btn.active .play-tri {
|
||||||
|
color: #FFF;
|
||||||
|
}
|
||||||
|
/* 迷你暂停两条竖线 */
|
||||||
|
.mini-pause {
|
||||||
|
display: flex;
|
||||||
|
gap: 6rpx;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.mp-bar {
|
||||||
|
width: 5rpx;
|
||||||
|
height: 26rpx;
|
||||||
|
background: #FFF;
|
||||||
|
border-radius: 3rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 全锁定提示条(橙色调) */
|
||||||
|
.locked-notice {
|
||||||
|
background: rgba(255, 157, 66, 0.06);
|
||||||
|
border-color: rgba(255, 157, 66, 0.25);
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-12
@@ -10,6 +10,7 @@ const api = require('../../utils/api')
|
|||||||
Page({
|
Page({
|
||||||
data: {
|
data: {
|
||||||
isVip: false,
|
isVip: false,
|
||||||
|
vipPriceText: '',
|
||||||
categories: [],
|
categories: [],
|
||||||
activeFilter: '',
|
activeFilter: '',
|
||||||
filteredDomains: [],
|
filteredDomains: [],
|
||||||
@@ -22,6 +23,7 @@ Page({
|
|||||||
|
|
||||||
onShow() {
|
onShow() {
|
||||||
this._loadChannels()
|
this._loadChannels()
|
||||||
|
this._loadVipPrice()
|
||||||
this._onSubChange = () => this._loadChannels()
|
this._onSubChange = () => this._loadChannels()
|
||||||
this._onVipChange = () => this._loadChannels()
|
this._onVipChange = () => this._loadChannels()
|
||||||
app.on('subscriptionChange', this._onSubChange)
|
app.on('subscriptionChange', this._onSubChange)
|
||||||
@@ -69,6 +71,9 @@ Page({
|
|||||||
var filtered = channels.map(function (ch) {
|
var filtered = channels.map(function (ch) {
|
||||||
var isFree = ch.isFree === 1
|
var isFree = ch.isFree === 1
|
||||||
var isVipOnly = ch.isVipOnly === 1
|
var isVipOnly = ch.isVipOnly === 1
|
||||||
|
var isSubscribed = ch.hasSubscribed === 1
|
||||||
|
// VIP 用户可播放所有频道
|
||||||
|
var canPlay = gd.isVip || isFree || isSubscribed
|
||||||
// 最低价(分→元)
|
// 最低价(分→元)
|
||||||
var lowestPrice = null
|
var lowestPrice = null
|
||||||
if (!isFree && !isVipOnly) {
|
if (!isFree && !isVipOnly) {
|
||||||
@@ -83,10 +88,11 @@ Page({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Object.assign({}, ch, {
|
return Object.assign({}, ch, {
|
||||||
_isSubscribed: ch.hasSubscribed === 1,
|
_isSubscribed: isSubscribed,
|
||||||
_isFree: isFree,
|
_isFree: isFree,
|
||||||
_isVipOnly: isVipOnly,
|
_isVipOnly: isVipOnly,
|
||||||
_lowestPrice: lowestPrice
|
_lowestPrice: lowestPrice,
|
||||||
|
_canPlay: canPlay
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -102,6 +108,19 @@ Page({
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_loadVipPrice() {
|
||||||
|
var self = this
|
||||||
|
api.getVipConfig().then(function (res) {
|
||||||
|
if (res.code === 200 && res.data) {
|
||||||
|
var cfg = res.data
|
||||||
|
var p = cfg.discountedPrice > 0 ? cfg.discountedPrice : cfg.price
|
||||||
|
self.setData({ vipPriceText: (p / 100).toFixed(2) + '元' })
|
||||||
|
}
|
||||||
|
}).catch(function () {
|
||||||
|
self.setData({ vipPriceText: '' })
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 切换分类筛选
|
* 切换分类筛选
|
||||||
*/
|
*/
|
||||||
@@ -129,25 +148,19 @@ Page({
|
|||||||
}
|
}
|
||||||
if (!channel) return
|
if (!channel) return
|
||||||
|
|
||||||
// 已订阅 → 直接进详情页
|
// 规则1:canPlay(VIP || isFree || hasSubscribed)→ 进频道详情
|
||||||
if (channel._isSubscribed) {
|
if (channel._canPlay) {
|
||||||
wx.navigateTo({ url: '/pages/channel-detail/index?id=' + id })
|
wx.navigateTo({ url: '/pages/channel-detail/index?id=' + id })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 免费 → 直接进详情收听
|
// 规则2:VIP专享 → 只能开通VIP,不可订阅
|
||||||
if (channel._isFree) {
|
|
||||||
wx.navigateTo({ url: '/pages/channel-detail/index?id=' + id })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// VIP专享 → 引导 VIP 页
|
|
||||||
if (channel._isVipOnly) {
|
if (channel._isVipOnly) {
|
||||||
wx.navigateTo({ url: '/pages/vip/index' })
|
wx.navigateTo({ url: '/pages/vip/index' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 付费订阅 → 跳转 VIP/订阅页(channel 模式)
|
// 付费频道未订阅 → 跳转订阅页
|
||||||
var params = 'channelId=' + id
|
var params = 'channelId=' + id
|
||||||
+ '&channelName=' + encodeURIComponent(channel.name || '')
|
+ '&channelName=' + encodeURIComponent(channel.name || '')
|
||||||
+ '&monthlyPrice=' + (channel.monthlyPrice || 0)
|
+ '&monthlyPrice=' + (channel.monthlyPrice || 0)
|
||||||
|
|||||||
@@ -58,26 +58,18 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 行动按钮 -->
|
<!-- 行动按钮 -->
|
||||||
<!-- 已订阅 -->
|
<!-- 可播放(VIP / 免费 / 已订阅)→ 收听 -->
|
||||||
<view wx:if="{{item._isSubscribed}}" class="sub-btn subscribed" bindtap="onAction" data-id="{{item.id}}">
|
<view wx:if="{{item._canPlay}}" class="sub-btn free" bindtap="onAction" data-id="{{item.id}}">
|
||||||
<t-icon name="check-circle" size="28rpx" color="#999" />
|
<text>▶ 收听</text>
|
||||||
<text>已订阅</text>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 免费 → 收听 -->
|
<!-- VIP专享(且非VIP用户)-->
|
||||||
<view wx:elif="{{item._isFree}}" class="sub-btn free" bindtap="onAction" data-id="{{item.id}}">
|
|
||||||
<t-icon name="play-circle" size="28rpx" color="#FFF" />
|
|
||||||
<text>收听</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- VIP专享 -->
|
|
||||||
<view wx:elif="{{item._isVipOnly}}" class="sub-btn vip" bindtap="onAction" data-id="{{item.id}}">
|
<view wx:elif="{{item._isVipOnly}}" class="sub-btn vip" bindtap="onAction" data-id="{{item.id}}">
|
||||||
<text>👑 VIP专享</text>
|
<text>👑 VIP专享</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 付费订阅 -->
|
<!-- 付费订阅 -->
|
||||||
<view wx:else class="sub-btn paid" bindtap="onAction" data-id="{{item.id}}">
|
<view wx:else class="sub-btn paid" bindtap="onAction" data-id="{{item.id}}">
|
||||||
<t-icon name="shop" size="28rpx" color="#FFF" />
|
|
||||||
<text>订阅</text>
|
<text>订阅</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -94,7 +86,7 @@
|
|||||||
<text class="vip-banner-desc">立享极致畅听体验</text>
|
<text class="vip-banner-desc">立享极致畅听体验</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="vip-banner-price">
|
<view class="vip-banner-price">
|
||||||
<text>19.9元/月</text>
|
<text>{{vipPriceText || '会员价'}}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|||||||
+159
-43
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* 收听历史 — 从后端获取历史列表
|
* 收听历史 — 历史 / 收藏 两 Tab
|
||||||
*/
|
*/
|
||||||
const app = getApp()
|
const app = getApp()
|
||||||
const api = require('../../utils/api')
|
const api = require('../../utils/api')
|
||||||
@@ -7,15 +7,28 @@ const util = require('../../utils/util')
|
|||||||
|
|
||||||
Page({
|
Page({
|
||||||
data: {
|
data: {
|
||||||
filter: 'all',
|
tab: 'history', // 'history' | 'favorite'
|
||||||
cleared: false,
|
|
||||||
historyList: [],
|
historyList: [],
|
||||||
isPlaying: false,
|
isPlaying: false,
|
||||||
loading: true
|
loading: true
|
||||||
},
|
},
|
||||||
|
|
||||||
onShow() {
|
onShow() {
|
||||||
|
const self = this
|
||||||
|
const gd = app.globalData
|
||||||
|
|
||||||
|
// 未登录则先登录
|
||||||
|
if (!gd.isLoggedIn || !gd.token) {
|
||||||
|
app.login().then(function () {
|
||||||
|
self._refresh()
|
||||||
|
}).catch(function () {
|
||||||
|
self.setData({ loading: false })
|
||||||
|
wx.showToast({ title: '请先登录', icon: 'none' })
|
||||||
|
})
|
||||||
|
} else {
|
||||||
this._refresh()
|
this._refresh()
|
||||||
|
}
|
||||||
|
|
||||||
this._onPlayerChange = () => this._updatePlayState()
|
this._onPlayerChange = () => this._updatePlayState()
|
||||||
app.on('playerStateChange', this._onPlayerChange)
|
app.on('playerStateChange', this._onPlayerChange)
|
||||||
},
|
},
|
||||||
@@ -24,35 +37,52 @@ Page({
|
|||||||
if (this._onPlayerChange) app.off('playerStateChange', this._onPlayerChange)
|
if (this._onPlayerChange) app.off('playerStateChange', this._onPlayerChange)
|
||||||
},
|
},
|
||||||
|
|
||||||
_refresh() {
|
setTab(e) {
|
||||||
if (this.data.cleared) return
|
const tab = e.currentTarget.dataset.val
|
||||||
|
if (tab === this.data.tab) return
|
||||||
|
this.setData({ tab, historyList: [], loading: true })
|
||||||
|
this._refresh()
|
||||||
|
},
|
||||||
|
|
||||||
|
_refresh() {
|
||||||
|
if (this.data.tab === 'history') {
|
||||||
|
this._loadHistory()
|
||||||
|
} else {
|
||||||
|
this._loadFavorites()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_loadHistory() {
|
||||||
const self = this
|
const self = this
|
||||||
const gd = app.globalData
|
const gd = app.globalData
|
||||||
|
|
||||||
api.getHistoryList({ current: 1, pageSize: 30 }).then(function (res) {
|
api.getHistoryList({ current: 1, pageSize: 30 }).then(function (res) {
|
||||||
if (res.code === 200 && res.data) {
|
if (res.code === 200 && res.data) {
|
||||||
var list = res.data.list || res.data || []
|
var list = (res.data.list || []).map(function (item) {
|
||||||
|
// program 嵌套在 item.program 下
|
||||||
// 附带格式化信息
|
var program = item.program || {}
|
||||||
list = list.map(function (item) {
|
// channel 可能为 null,channel cover 在 program.cover
|
||||||
// 节目可能包含频道信息,根据后端返回结构适配
|
var channel = program.channel || {}
|
||||||
var channel = item.channel || item.program && item.program.channel || {}
|
return {
|
||||||
var program = item.program || item
|
// 播放所需字段
|
||||||
|
id: program.id,
|
||||||
return Object.assign({}, program, {
|
title: program.title || '未知节目',
|
||||||
_domainName: channel.name || program.channelName || '',
|
channelId: program.channelId || '',
|
||||||
_icon: channel.icon || '🎵',
|
content: program.content || '',
|
||||||
_bgColor: channel.bgColor || '#F0F0F0',
|
audioId: program.audioId || '',
|
||||||
_coverUrl: (channel.cover && channel.cover.url) || channel.coverUrl || '',
|
// 用历史记录的 duration(program.duration 可能是 0)
|
||||||
_friendlyDate: util.getFriendlyDate(
|
duration: item.duration || program.duration || 0,
|
||||||
program.createdAt ? program.createdAt.substring(0, 10) : ''
|
// 显示字段
|
||||||
),
|
_domainName: channel.name || '',
|
||||||
durationText: util.formatTime(program.duration || 0),
|
_icon: program.cover || channel.cover || '📻',
|
||||||
|
_bgColor: '#FFE8CC',
|
||||||
|
_friendlyDate: util.getFriendlyDate(item.createdAtStr ? item.createdAtStr.substring(0, 10) : ''),
|
||||||
|
durationText: util.formatTime(item.duration || 0),
|
||||||
|
// 播放进度
|
||||||
|
_progress: item.progress || 0,
|
||||||
_isThisPlaying: gd.activeContent && gd.activeContent.id === program.id
|
_isThisPlaying: gd.activeContent && gd.activeContent.id === program.id
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
self.setData({ historyList: list, isPlaying: gd.isPlaying, loading: false })
|
self.setData({ historyList: list, isPlaying: gd.isPlaying, loading: false })
|
||||||
} else {
|
} else {
|
||||||
self.setData({ historyList: [], loading: false })
|
self.setData({ historyList: [], loading: false })
|
||||||
@@ -63,9 +93,40 @@ Page({
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
_loadFavorites() {
|
||||||
* 仅更新播放状态
|
const self = this
|
||||||
*/
|
const gd = app.globalData
|
||||||
|
|
||||||
|
api.getFavoriteList({ current: 1, pageSize: 30 }).then(function (res) {
|
||||||
|
if (res.code === 200 && res.data) {
|
||||||
|
var list = (res.data.list || []).map(function (item) {
|
||||||
|
var program = item.program || item
|
||||||
|
var channel = program.channel || {}
|
||||||
|
return {
|
||||||
|
id: program.id,
|
||||||
|
title: program.title || '未知节目',
|
||||||
|
channelId: program.channelId || '',
|
||||||
|
content: program.content || '',
|
||||||
|
audioId: program.audioId || '',
|
||||||
|
duration: program.duration || 0,
|
||||||
|
_domainName: channel.name || '',
|
||||||
|
_icon: program.cover || channel.cover || '📻',
|
||||||
|
_bgColor: '#FFE8CC',
|
||||||
|
_friendlyDate: util.getFriendlyDate(item.createdAtStr ? item.createdAtStr.substring(0, 10) : ''),
|
||||||
|
durationText: util.formatTime(program.duration || 0),
|
||||||
|
_isThisPlaying: gd.activeContent && gd.activeContent.id === program.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
self.setData({ historyList: list, isPlaying: gd.isPlaying, loading: false })
|
||||||
|
} else {
|
||||||
|
self.setData({ historyList: [], loading: false })
|
||||||
|
}
|
||||||
|
}).catch(function (err) {
|
||||||
|
console.error('[History] 加载收藏失败:', err)
|
||||||
|
self.setData({ loading: false })
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
_updatePlayState() {
|
_updatePlayState() {
|
||||||
var gd = app.globalData
|
var gd = app.globalData
|
||||||
var list = this.data.historyList.map(function (item) {
|
var list = this.data.historyList.map(function (item) {
|
||||||
@@ -76,41 +137,96 @@ Page({
|
|||||||
this.setData({ historyList: list, isPlaying: gd.isPlaying })
|
this.setData({ historyList: list, isPlaying: gd.isPlaying })
|
||||||
},
|
},
|
||||||
|
|
||||||
setFilter(e) {
|
|
||||||
this.setData({ filter: e.currentTarget.dataset.val })
|
|
||||||
},
|
|
||||||
|
|
||||||
onPlay(e) {
|
onPlay(e) {
|
||||||
const id = e.currentTarget.dataset.id
|
const id = e.currentTarget.dataset.id
|
||||||
const gd = app.globalData
|
const gd = app.globalData
|
||||||
|
|
||||||
// 从已加载数据中查找
|
if (gd.activeContent && gd.activeContent.id === id) {
|
||||||
var content = null
|
app.togglePlay()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从列表中找到基础信息
|
||||||
|
var base = null
|
||||||
for (var i = 0; i < this.data.historyList.length; i++) {
|
for (var i = 0; i < this.data.historyList.length; i++) {
|
||||||
if (this.data.historyList[i].id === id) {
|
if (this.data.historyList[i].id === id) {
|
||||||
content = this.data.historyList[i]
|
base = this.data.historyList[i]
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!content) return
|
if (!base) return
|
||||||
|
|
||||||
if (gd.activeContent && gd.activeContent.id === id) {
|
// 拉取完整节目详情(含 audio.url)再播放
|
||||||
app.togglePlay()
|
wx.showLoading({ title: '加载中' })
|
||||||
|
api.getProgramDetail(id).then(function (res) {
|
||||||
|
wx.hideLoading()
|
||||||
|
if (res.code === 200 && res.data) {
|
||||||
|
app.playContent(Object.assign({}, base, res.data))
|
||||||
} else {
|
} else {
|
||||||
app.playContent(content)
|
wx.showToast({ title: '加载失败', icon: 'none' })
|
||||||
}
|
}
|
||||||
|
}).catch(function () {
|
||||||
|
wx.hideLoading()
|
||||||
|
wx.showToast({ title: '网络异常', icon: 'none' })
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onDeleteItem(e) {
|
||||||
|
const id = e.currentTarget.dataset.id
|
||||||
|
const tab = this.data.tab
|
||||||
|
const self = this
|
||||||
|
const label = tab === 'history' ? '历史' : '收藏'
|
||||||
|
|
||||||
|
wx.showModal({
|
||||||
|
title: '删除' + label,
|
||||||
|
content: '确定删除这条' + label + '吗?',
|
||||||
|
success(res) {
|
||||||
|
if (!res.confirm) return
|
||||||
|
const fn = tab === 'history'
|
||||||
|
? api.deleteHistory(id)
|
||||||
|
: api.removeFavorite(id)
|
||||||
|
|
||||||
|
fn.then(function (r) {
|
||||||
|
if (r.code === 200) {
|
||||||
|
// 本地即时移除
|
||||||
|
var list = self.data.historyList.filter(function (item) { return item.id !== id })
|
||||||
|
self.setData({ historyList: list })
|
||||||
|
} else {
|
||||||
|
wx.showToast({ title: r.msg || '删除失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
}).catch(function () {
|
||||||
|
wx.showToast({ title: '网络异常', icon: 'none' })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
onClear() {
|
onClear() {
|
||||||
const self = this
|
const self = this
|
||||||
|
const tab = this.data.tab
|
||||||
|
const label = tab === 'history' ? '收听历史' : '全部收藏'
|
||||||
wx.showModal({
|
wx.showModal({
|
||||||
title: '提示',
|
title: '清空' + label,
|
||||||
content: '确定要清空所有收听历史吗?',
|
content: '确定要清空所有' + label + '吗?',
|
||||||
success(res) {
|
success(res) {
|
||||||
if (res.confirm) {
|
if (!res.confirm) return
|
||||||
self.setData({ cleared: true, historyList: [] })
|
const fn = tab === 'history'
|
||||||
}
|
? api.deleteAllHistory()
|
||||||
|
: api.removeAllFavorites()
|
||||||
|
fn.then(function (r) {
|
||||||
|
if (r.code === 200) {
|
||||||
|
self.setData({ historyList: [] })
|
||||||
|
} else {
|
||||||
|
wx.showToast({ title: r.msg || '操作失败', icon: 'none' })
|
||||||
}
|
}
|
||||||
|
}).catch(function () {
|
||||||
|
wx.showToast({ title: '网络异常', icon: 'none' })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
goDiscover() { wx.switchTab({ url: '/pages/discover/index' }) },
|
||||||
|
goHome() { wx.switchTab({ url: '/pages/index/index' }) },
|
||||||
|
goVip() { wx.navigateTo({ url: '/pages/vip/index' }) }
|
||||||
})
|
})
|
||||||
|
|||||||
+99
-30
@@ -1,66 +1,135 @@
|
|||||||
<!-- 收听历史 —— 按日期倒序的已听列表 -->
|
<!-- 收听历史 / 收藏 -->
|
||||||
<view class="history-page">
|
<view class="history-page">
|
||||||
|
|
||||||
<!-- 筛选Tab + 清空按钮 -->
|
<!-- Tab 切换 + 清空 -->
|
||||||
<view class="filter-header">
|
<view class="filter-header">
|
||||||
<view class="filter-tabs">
|
<view class="filter-tabs">
|
||||||
<text
|
<view class="tab-item {{tab === 'history' ? 'active' : ''}}" bindtap="setTab" data-val="history">
|
||||||
class="tab {{filter === 'all' ? 'active' : ''}}"
|
<text class="tab-text">历史</text>
|
||||||
bindtap="setFilter"
|
<view class="tab-underline"></view>
|
||||||
data-val="all"
|
</view>
|
||||||
>全部片段</text>
|
<view class="tab-item {{tab === 'favorite' ? 'active' : ''}}" bindtap="setTab" data-val="favorite">
|
||||||
<text
|
<text class="tab-text">收藏</text>
|
||||||
class="tab {{filter === 'subscribed' ? 'active' : ''}}"
|
<view class="tab-underline"></view>
|
||||||
bindtap="setFilter"
|
</view>
|
||||||
data-val="subscribed"
|
</view>
|
||||||
>仅看已订阅</text>
|
<view class="clear-btn tap-active" bindtap="onClear" wx:if="{{historyList.length > 0}}">
|
||||||
|
<view class="icon-trash">
|
||||||
|
<view class="trash-handle"></view>
|
||||||
|
<view class="trash-lid-bar"></view>
|
||||||
|
<view class="trash-body">
|
||||||
|
<view class="trash-stripe"></view>
|
||||||
|
<view class="trash-stripe"></view>
|
||||||
|
<view class="trash-stripe"></view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="clear-btn tap-active" bindtap="onClear">
|
|
||||||
<text class="clear-icon">🗑</text>
|
|
||||||
<text class="clear-text">清空</text>
|
<text class="clear-text">清空</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 历史列表 -->
|
<!-- 列表区 -->
|
||||||
<view class="list-area">
|
<view class="list-area">
|
||||||
|
|
||||||
<!-- 空状态 -->
|
<!-- 加载骨架 -->
|
||||||
<view wx:if="{{cleared || historyList.length === 0}}" class="empty-state">
|
<view wx:if="{{loading}}" class="skeleton-wrap">
|
||||||
<view class="empty-icon-wrap">
|
<view wx:for="{{[1,2,3]}}" wx:key="*this" class="skeleton-item">
|
||||||
<text class="empty-emoji">📭</text>
|
<view class="sk-icon"></view>
|
||||||
|
<view class="sk-lines">
|
||||||
|
<view class="sk-line sk-line-short"></view>
|
||||||
|
<view class="sk-line sk-line-long"></view>
|
||||||
|
<view class="sk-line sk-line-mid"></view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<text class="empty-text">暂无收听历史</text>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 历史条目 -->
|
<!-- 历史空状态 -->
|
||||||
|
<view wx:elif="{{historyList.length === 0 && tab === 'history'}}" class="empty-state">
|
||||||
|
<text class="empty-icon">🎧</text>
|
||||||
|
<text class="empty-title">还没有收听记录</text>
|
||||||
|
<text class="empty-desc">去发现频道,开始你的第一段收听</text>
|
||||||
|
<view class="empty-actions">
|
||||||
|
<view class="btn-primary tap-active" bindtap="goDiscover">
|
||||||
|
<text class="btn-text">去发现频道</text>
|
||||||
|
</view>
|
||||||
|
<view class="btn-ghost tap-active" bindtap="goHome">
|
||||||
|
<text class="btn-ghost-text">回到首页</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 收藏空状态 -->
|
||||||
|
<view wx:elif="{{historyList.length === 0 && tab === 'favorite'}}" class="empty-state">
|
||||||
|
<text class="empty-icon">🔖</text>
|
||||||
|
<text class="empty-title">还没有收藏内容</text>
|
||||||
|
<text class="empty-desc">听到喜欢的节目,点击 ♡ 收藏它</text>
|
||||||
|
<view class="empty-actions">
|
||||||
|
<view class="btn-primary tap-active" bindtap="goDiscover">
|
||||||
|
<text class="btn-text">订阅感兴趣的频道</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- 推荐订阅提示 -->
|
||||||
|
<view class="upsell-card">
|
||||||
|
<text class="upsell-icon">👑</text>
|
||||||
|
<view class="upsell-body">
|
||||||
|
<text class="upsell-title">开通会员</text>
|
||||||
|
<text class="upsell-desc">解锁全部付费频道,无限收藏</text>
|
||||||
|
</view>
|
||||||
|
<view class="upsell-btn tap-active" bindtap="goVip">
|
||||||
|
<text class="upsell-btn-text">了解</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 条目列表 -->
|
||||||
<view
|
<view
|
||||||
wx:for="{{historyList}}"
|
wx:for="{{historyList}}"
|
||||||
wx:key="id"
|
wx:key="id"
|
||||||
class="history-item card"
|
class="history-item"
|
||||||
bindtap="onPlay"
|
bindtap="onPlay"
|
||||||
data-id="{{item.id}}"
|
data-id="{{item.id}}"
|
||||||
>
|
>
|
||||||
<!-- 频道图标 -->
|
<!-- 频道/节目图标 -->
|
||||||
<view class="h-icon" style="background: {{item._bgColor}};">
|
<view class="h-icon" style="background: {{item._bgColor}};">
|
||||||
<image wx:if="{{item._coverUrl}}" src="{{item._coverUrl}}" class="h-cover-img" mode="aspectFill" />
|
<text class="h-emoji">{{item._icon}}</text>
|
||||||
<text wx:else class="h-emoji">{{item._icon}}</text>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 信息 -->
|
<!-- 文字信息 -->
|
||||||
<view class="h-info">
|
<view class="h-info">
|
||||||
<text class="h-channel">{{item._domainName}}</text>
|
<text class="h-channel" wx:if="{{item._domainName}}">{{item._domainName}}</text>
|
||||||
<text class="h-title {{item._isThisPlaying ? 'text-primary' : ''}}">{{item.title}}</text>
|
<text class="h-title {{item._isThisPlaying ? 'text-primary' : ''}}">{{item.title}}</text>
|
||||||
<text class="h-meta">{{item._friendlyDate}} · {{item.durationText}}</text>
|
<view class="h-meta-row">
|
||||||
|
<text class="h-meta">{{item._friendlyDate}}</text>
|
||||||
|
<text class="h-meta" wx:if="{{item.durationText}}"> · {{item.durationText}}</text>
|
||||||
|
</view>
|
||||||
|
<!-- 播放进度条 -->
|
||||||
|
<view class="h-progress-wrap" wx:if="{{item._progress > 0 && item.duration > 0}}">
|
||||||
|
<view class="h-progress-bar" style="width: {{item._progress / item.duration * 100}}%;"></view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 播放指示 -->
|
<!-- 播放指示 / 播放按钮 -->
|
||||||
|
<view class="h-right">
|
||||||
<view wx:if="{{item._isThisPlaying && isPlaying}}" class="playing-indicator">
|
<view wx:if="{{item._isThisPlaying && isPlaying}}" class="playing-indicator">
|
||||||
<view class="bar bar-1"></view>
|
<view class="bar bar-1"></view>
|
||||||
<view class="bar bar-2"></view>
|
<view class="bar bar-2"></view>
|
||||||
<view class="bar bar-3"></view>
|
<view class="bar bar-3"></view>
|
||||||
</view>
|
</view>
|
||||||
<view wx:else class="play-mini">
|
<view wx:else class="play-mini">
|
||||||
<image src="/assets/icons/play.svg" class="play-mini-icon" />
|
<text class="play-mini-icon">▶</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 删除按钮 -->
|
||||||
|
<view class="h-del tap-active" catchtap="onDeleteItem" data-id="{{item.id}}">
|
||||||
|
<view class="icon-trash icon-trash-sm">
|
||||||
|
<view class="trash-handle"></view>
|
||||||
|
<view class="trash-lid-bar"></view>
|
||||||
|
<view class="trash-body">
|
||||||
|
<view class="trash-stripe"></view>
|
||||||
|
<view class="trash-stripe"></view>
|
||||||
|
<view class="trash-stripe"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|||||||
+296
-71
@@ -1,106 +1,239 @@
|
|||||||
/* 收听历史样式 */
|
/* 收听历史 / 收藏 */
|
||||||
|
|
||||||
.history-page {
|
.history-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: var(--color-bg-page);
|
background: #F7F3EE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 筛选头部 */
|
/* ── Tab 头部 ── */
|
||||||
.filter-header {
|
.filter-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 16rpx 32rpx;
|
padding: 0 32rpx;
|
||||||
background: #FFFFFF;
|
background: #FFFFFF;
|
||||||
border-bottom: 1rpx solid #F5F5F5;
|
border-bottom: 1rpx solid #F0EAE2;
|
||||||
}
|
}
|
||||||
|
.filter-tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 8rpx;
|
||||||
|
}
|
||||||
|
.tab-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 24rpx 24rpx 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.tab-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #AAA;
|
||||||
|
padding-bottom: 18rpx;
|
||||||
|
font-family: 'PingFang SC', sans-serif;
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
.tab-item.active .tab-text {
|
||||||
|
color: #2C1A08;
|
||||||
|
}
|
||||||
|
.tab-underline {
|
||||||
|
height: 4rpx;
|
||||||
|
width: 0;
|
||||||
|
background: #FF9D42;
|
||||||
|
border-radius: 2rpx;
|
||||||
|
transition: width 0.25s;
|
||||||
|
}
|
||||||
|
.tab-item.active .tab-underline {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 清空按钮 */
|
||||||
.clear-btn {
|
.clear-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
gap: 8rpx;
|
||||||
.clear-icon {
|
padding: 12rpx 0;
|
||||||
font-size: 22rpx;
|
|
||||||
margin-right: 6rpx;
|
|
||||||
}
|
}
|
||||||
.clear-text {
|
.clear-text {
|
||||||
font-size: 22rpx;
|
font-size: 24rpx;
|
||||||
color: #999;
|
color: #BBBBBB;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 筛选Tab */
|
/* ── 列表区 ── */
|
||||||
.filter-tabs {
|
|
||||||
display: flex;
|
|
||||||
gap: 32rpx;
|
|
||||||
}
|
|
||||||
.tab {
|
|
||||||
font-size: 26rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #999;
|
|
||||||
padding-bottom: 12rpx;
|
|
||||||
border-bottom: 4rpx solid transparent;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
.tab.active {
|
|
||||||
color: #333;
|
|
||||||
border-bottom-color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 列表 */
|
|
||||||
.list-area {
|
.list-area {
|
||||||
padding: 20rpx 32rpx;
|
padding: 24rpx 28rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 空状态 */
|
/* ── 骨架屏 ── */
|
||||||
|
.skeleton-wrap { display: flex; flex-direction: column; gap: 20rpx; }
|
||||||
|
.skeleton-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24rpx;
|
||||||
|
background: #FFFFFF;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
}
|
||||||
|
.sk-icon {
|
||||||
|
width: 88rpx;
|
||||||
|
height: 88rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
background: linear-gradient(90deg, #F0EAE2 0%, #E8DFD5 50%, #F0EAE2 100%);
|
||||||
|
background-size: 200% 100%;
|
||||||
|
animation: shimmer 1.4s infinite;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.sk-lines {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
.sk-line {
|
||||||
|
height: 18rpx;
|
||||||
|
background: linear-gradient(90deg, #F0EAE2 0%, #E8DFD5 50%, #F0EAE2 100%);
|
||||||
|
background-size: 200% 100%;
|
||||||
|
animation: shimmer 1.4s infinite;
|
||||||
|
border-radius: 9rpx;
|
||||||
|
}
|
||||||
|
.sk-line-short { width: 40%; }
|
||||||
|
.sk-line-long { width: 80%; }
|
||||||
|
.sk-line-mid { width: 55%; }
|
||||||
|
@keyframes shimmer {
|
||||||
|
0% { background-position: 200% 0; }
|
||||||
|
100% { background-position: -200% 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── 空状态 ── */
|
||||||
.empty-state {
|
.empty-state {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 160rpx 0;
|
padding: 80rpx 48rpx 48rpx;
|
||||||
opacity: 0.5;
|
|
||||||
}
|
}
|
||||||
.empty-icon-wrap {
|
.empty-icon {
|
||||||
width: 128rpx;
|
font-size: 96rpx;
|
||||||
height: 128rpx;
|
margin-bottom: 32rpx;
|
||||||
border-radius: 50%;
|
filter: drop-shadow(0 8rpx 16rpx rgba(255,157,66,0.2));
|
||||||
background: #F5F5F5;
|
}
|
||||||
|
.empty-title {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #2C1A08;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
font-family: 'PingFang SC', sans-serif;
|
||||||
|
}
|
||||||
|
.empty-desc {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #B0A090;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin-bottom: 48rpx;
|
||||||
|
}
|
||||||
|
.empty-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20rpx;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 480rpx;
|
||||||
|
}
|
||||||
|
.btn-primary {
|
||||||
|
background: linear-gradient(135deg, #FF9D42, #E07020);
|
||||||
|
border-radius: 48rpx;
|
||||||
|
padding: 28rpx 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-bottom: 24rpx;
|
box-shadow: 0 8rpx 24rpx rgba(255,157,66,0.35);
|
||||||
}
|
}
|
||||||
.empty-emoji {
|
.btn-text {
|
||||||
font-size: 48rpx;
|
font-size: 30rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #FFFFFF;
|
||||||
|
letter-spacing: 1rpx;
|
||||||
}
|
}
|
||||||
.empty-text {
|
.btn-ghost {
|
||||||
|
border: 2rpx solid #E8DFD5;
|
||||||
|
border-radius: 48rpx;
|
||||||
|
padding: 26rpx 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: #FFFFFF;
|
||||||
|
}
|
||||||
|
.btn-ghost-text {
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
color: #999;
|
font-weight: 600;
|
||||||
font-weight: 500;
|
color: #8C7B6A;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 历史条目 */
|
/* VIP upsell 卡片 */
|
||||||
|
.upsell-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20rpx;
|
||||||
|
background: linear-gradient(135deg, #FFF8EE, #FFF3E0);
|
||||||
|
border: 1rpx solid #FFDFA0;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
padding: 24rpx 28rpx;
|
||||||
|
margin-top: 40rpx;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 480rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.upsell-icon { font-size: 48rpx; }
|
||||||
|
.upsell-body {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4rpx;
|
||||||
|
}
|
||||||
|
.upsell-title {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #C07000;
|
||||||
|
}
|
||||||
|
.upsell-desc {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #C09040;
|
||||||
|
}
|
||||||
|
.upsell-btn {
|
||||||
|
background: #FF9D42;
|
||||||
|
border-radius: 28rpx;
|
||||||
|
padding: 12rpx 28rpx;
|
||||||
|
}
|
||||||
|
.upsell-btn-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── 历史条目 ── */
|
||||||
.history-item {
|
.history-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 24rpx;
|
padding: 24rpx;
|
||||||
margin-bottom: 20rpx;
|
margin-bottom: 16rpx;
|
||||||
|
background: #FFFFFF;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
box-shadow: 0 2rpx 12rpx rgba(44,26,8,0.04);
|
||||||
}
|
}
|
||||||
.history-item:active {
|
.history-item:active {
|
||||||
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.06);
|
box-shadow: 0 4rpx 20rpx rgba(44,26,8,0.08);
|
||||||
|
transform: scale(0.99);
|
||||||
}
|
}
|
||||||
|
|
||||||
.h-icon {
|
.h-icon {
|
||||||
width: 88rpx;
|
width: 88rpx;
|
||||||
height: 88rpx;
|
height: 88rpx;
|
||||||
border-radius: 24rpx;
|
border-radius: 20rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.h-emoji {
|
.h-emoji { font-size: 42rpx; }
|
||||||
font-size: 40rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h-info {
|
.h-info {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -111,50 +244,73 @@
|
|||||||
display: block;
|
display: block;
|
||||||
font-size: 20rpx;
|
font-size: 20rpx;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #999;
|
color: #FF9D42;
|
||||||
letter-spacing: 2rpx;
|
letter-spacing: 1rpx;
|
||||||
margin-bottom: 6rpx;
|
margin-bottom: 6rpx;
|
||||||
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
.h-title {
|
.h-title {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 26rpx;
|
font-size: 28rpx;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #333;
|
color: #2C1A08;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
font-family: 'PingFang SC', sans-serif;
|
||||||
}
|
}
|
||||||
.h-title.text-primary {
|
.h-title.text-primary { color: #FF9D42; }
|
||||||
color: var(--color-primary);
|
.h-meta-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: 8rpx;
|
||||||
}
|
}
|
||||||
.h-meta {
|
.h-meta {
|
||||||
display: block;
|
display: inline;
|
||||||
font-size: 20rpx;
|
font-size: 22rpx;
|
||||||
color: #BBB;
|
color: #C8BAAA;
|
||||||
margin-top: 6rpx;
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
/* 播放进度条 */
|
||||||
|
.h-progress-wrap {
|
||||||
|
width: 100%;
|
||||||
|
height: 4rpx;
|
||||||
|
background: #F0EAE2;
|
||||||
|
border-radius: 2rpx;
|
||||||
|
margin-top: 12rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.h-progress-bar {
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, #FF9D42, #E07020);
|
||||||
|
border-radius: 2rpx;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-right {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* 播放指示器 */
|
/* 播放指示器 */
|
||||||
.playing-indicator {
|
.playing-indicator {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
gap: 4rpx;
|
gap: 4rpx;
|
||||||
width: 48rpx;
|
width: 40rpx;
|
||||||
height: 48rpx;
|
height: 40rpx;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-shrink: 0;
|
padding-bottom: 4rpx;
|
||||||
}
|
}
|
||||||
.bar {
|
.bar {
|
||||||
width: 6rpx;
|
width: 6rpx;
|
||||||
border-radius: 4rpx;
|
border-radius: 4rpx;
|
||||||
background: var(--color-primary);
|
background: #FF9D42;
|
||||||
animation: bounce 0.6s ease-in-out infinite;
|
animation: bounce 0.6s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
.bar-1 { height: 24rpx; animation-delay: 0s; }
|
.bar-1 { height: 24rpx; animation-delay: 0s; }
|
||||||
.bar-2 { height: 40rpx; animation-delay: 0.1s; }
|
.bar-2 { height: 40rpx; animation-delay: 0.1s; }
|
||||||
.bar-3 { height: 16rpx; animation-delay: 0.2s; }
|
.bar-3 { height: 16rpx; animation-delay: 0.2s; }
|
||||||
|
|
||||||
@keyframes bounce {
|
@keyframes bounce {
|
||||||
0%, 100% { transform: scaleY(0.4); }
|
0%, 100% { transform: scaleY(0.4); }
|
||||||
50% { transform: scaleY(1); }
|
50% { transform: scaleY(1); }
|
||||||
@@ -164,14 +320,83 @@
|
|||||||
width: 56rpx;
|
width: 56rpx;
|
||||||
height: 56rpx;
|
height: 56rpx;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
border: 2rpx solid #EEE;
|
background: #FFF4E8;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.play-mini-icon {
|
.play-mini-icon {
|
||||||
width: 20rpx;
|
font-size: 22rpx;
|
||||||
height: 20rpx;
|
color: #FF9D42;
|
||||||
opacity: 0.4;
|
padding-left: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 单条删除按钮 */
|
||||||
|
.h-del {
|
||||||
|
width: 52rpx;
|
||||||
|
height: 52rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-left: 4rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── 纯 view 垃圾桶图标 ── */
|
||||||
|
.icon-trash {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 3rpx;
|
||||||
|
width: 28rpx;
|
||||||
|
padding-top: 6rpx;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.trash-handle {
|
||||||
|
width: 12rpx;
|
||||||
|
height: 6rpx;
|
||||||
|
border: 3rpx solid #BBBBBB;
|
||||||
|
border-bottom: none;
|
||||||
|
border-radius: 4rpx 4rpx 0 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-bottom: 2rpx;
|
||||||
|
}
|
||||||
|
.trash-lid-bar {
|
||||||
|
width: 28rpx;
|
||||||
|
height: 4rpx;
|
||||||
|
background: #BBBBBB;
|
||||||
|
border-radius: 2rpx;
|
||||||
|
}
|
||||||
|
.trash-body {
|
||||||
|
width: 22rpx;
|
||||||
|
height: 24rpx;
|
||||||
|
border: 3rpx solid #BBBBBB;
|
||||||
|
border-top: none;
|
||||||
|
border-radius: 0 0 5rpx 5rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: stretch;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 4rpx;
|
||||||
|
padding: 4rpx 3rpx 3rpx;
|
||||||
|
}
|
||||||
|
.trash-stripe {
|
||||||
|
flex: 1;
|
||||||
|
background: #BBBBBB;
|
||||||
|
border-radius: 2rpx;
|
||||||
|
max-width: 2rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 行内小号 */
|
||||||
|
.icon-trash-sm .trash-handle,
|
||||||
|
.icon-trash-sm .trash-lid-bar,
|
||||||
|
.icon-trash-sm .trash-stripe {
|
||||||
|
border-color: #D0C8C0;
|
||||||
|
background: #D0C8C0;
|
||||||
|
}
|
||||||
|
.icon-trash-sm .trash-body {
|
||||||
|
border-color: #D0C8C0;
|
||||||
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|||||||
+44
-70
@@ -12,9 +12,36 @@ const app = getApp()
|
|||||||
const api = require('../../utils/api')
|
const api = require('../../utils/api')
|
||||||
const util = require('../../utils/util')
|
const util = require('../../utils/util')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模块级函数:根据时间段随机返回文案
|
||||||
|
* 必须在 Page() 外定义,才能在 data 初始化时同步调用,避免闪烁
|
||||||
|
*/
|
||||||
|
function _computeGreeting() {
|
||||||
|
const hour = new Date().getHours()
|
||||||
|
var pool
|
||||||
|
if (hour >= 5 && hour < 9) {
|
||||||
|
pool = ['新的一天,从声音开始', '早起的人,先听一段', '清晨的第一居声,属于你', '晚起不如早起,早起不如听起']
|
||||||
|
} else if (hour >= 9 && hour < 12) {
|
||||||
|
pool = ['上午充能量,声音加持', '专注工作,也别忘了呼吸', '高效早上,入耳知识', '好状态,从一段内容开始']
|
||||||
|
} else if (hour >= 12 && hour < 14) {
|
||||||
|
pool = ['午后小憩,听点轻松的', '借耳机隔绝喖鼺,中午也有自己的时间', '饭后十分钟,充电一下', '慢下来,下午还长']
|
||||||
|
} else if (hour >= 14 && hour < 17) {
|
||||||
|
pool = ['下午了,来点声音撤个困', '下午三点,刚好开始听一段', '下午的阳光和一段好内容', '止止广告,呜口内容']
|
||||||
|
} else if (hour >= 17 && hour < 19) {
|
||||||
|
pool = ['日落时分,放慢脚步', '下班了,耳机里换一个频道', '偶尔不刷短视频,试试听点实的', '归途中最适合听一段']
|
||||||
|
} else if (hour >= 19 && hour < 22) {
|
||||||
|
pool = ['夜晚温柔,适合倾听', '夜晚的声音,不用起时间', '放下手机,喂口内容', '夹着夜色,听一段值得的']
|
||||||
|
} else {
|
||||||
|
pool = ['夜深了,听点帮助入眠的', '出发前最后一段,晚安', '夜深的小时光,留给自己', '好梦将至,晚安']
|
||||||
|
}
|
||||||
|
return pool[Math.floor(Math.random() * pool.length)]
|
||||||
|
}
|
||||||
|
|
||||||
Page({
|
Page({
|
||||||
data: {
|
data: {
|
||||||
greetingSub: '',
|
// 同步计算,第一帧就正确,避免闪烁
|
||||||
|
greetingSub: _computeGreeting(),
|
||||||
|
statusBarHeight: app.globalData.statusBarHeight || 0,
|
||||||
locationName: '',
|
locationName: '',
|
||||||
weather: null,
|
weather: null,
|
||||||
dateDisplay: '',
|
dateDisplay: '',
|
||||||
@@ -23,7 +50,6 @@ Page({
|
|||||||
freeChannels: [],
|
freeChannels: [],
|
||||||
isPlaying: false,
|
isPlaying: false,
|
||||||
isVip: false,
|
isVip: false,
|
||||||
statusBarHeight: 0,
|
|
||||||
loadingSub: true,
|
loadingSub: true,
|
||||||
loadingFree: true
|
loadingFree: true
|
||||||
},
|
},
|
||||||
@@ -33,13 +59,13 @@ Page({
|
|||||||
onShow() {
|
onShow() {
|
||||||
const gd = app.globalData
|
const gd = app.globalData
|
||||||
this.setData({
|
this.setData({
|
||||||
greetingSub: this._getGreeting(),
|
greetingSub: _computeGreeting(), // 每次进页随机刷新文案
|
||||||
dateDisplay: util.getDateDisplay(),
|
dateDisplay: util.getDateDisplay(),
|
||||||
weekDay: util.getWeekDay(),
|
weekDay: util.getWeekDay(),
|
||||||
locationName: gd.locationName || '',
|
locationName: gd.locationName || '',
|
||||||
weather: gd.weather || null,
|
weather: gd.weather || null,
|
||||||
isVip: gd.isVip || false,
|
isVip: gd.isVip || false
|
||||||
statusBarHeight: gd.statusBarHeight || 0
|
// statusBarHeight 小程序运行期间不会变化,无需重设
|
||||||
})
|
})
|
||||||
this._loadAll()
|
this._loadAll()
|
||||||
this._bindEvents()
|
this._bindEvents()
|
||||||
@@ -96,18 +122,24 @@ Page({
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 拉取免费频道列表
|
* 拉取频道列表(Section 2)
|
||||||
* 首次无数据时显示骨架屏,后续静默刷新
|
* VIP 用户:全部频道;普通用户:仅免费频道
|
||||||
*/
|
*/
|
||||||
_loadFreeChannels() {
|
_loadFreeChannels() {
|
||||||
const self = this
|
const self = this
|
||||||
|
const gd = app.globalData
|
||||||
const isFirstLoad = self.data.freeChannels.length === 0
|
const isFirstLoad = self.data.freeChannels.length === 0
|
||||||
|
|
||||||
if (isFirstLoad) {
|
if (isFirstLoad) {
|
||||||
self.setData({ loadingFree: true })
|
self.setData({ loadingFree: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
api.getFreeChannelList({ current: 1, pageSize: 20 })
|
// VIP 用户加载全量频道,普通用户仅免费频道
|
||||||
|
var apiCall = gd.isVip
|
||||||
|
? api.getChannelList({ current: 1, pageSize: 50 })
|
||||||
|
: api.getFreeChannelList({ current: 1, pageSize: 20 })
|
||||||
|
|
||||||
|
apiCall
|
||||||
.then(function (res) {
|
.then(function (res) {
|
||||||
if (res.code !== 200 || !res.data) {
|
if (res.code !== 200 || !res.data) {
|
||||||
self.setData({ freeChannels: [], loadingFree: false })
|
self.setData({ freeChannels: [], loadingFree: false })
|
||||||
@@ -115,23 +147,21 @@ Page({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const list = res.data.list || []
|
const list = res.data.list || []
|
||||||
|
|
||||||
const freeChannels = list.map(function (ch) {
|
const freeChannels = list.map(function (ch) {
|
||||||
return {
|
return {
|
||||||
id: ch.id,
|
id: ch.id,
|
||||||
name: ch.name || '未命名',
|
name: ch.name || '未命名',
|
||||||
// cover 直接是 emoji 字符串
|
|
||||||
cover: ch.cover || '📻',
|
cover: ch.cover || '📻',
|
||||||
bgColor: self._genColor(ch.id),
|
bgColor: self._genColor(ch.id),
|
||||||
programCount: (ch.Programs || ch.programs || []).length,
|
isFree: ch.isFree,
|
||||||
isFree: ch.isFree
|
isVipOnly: ch.isVipOnly
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
self.setData({ freeChannels: freeChannels, loadingFree: false })
|
self.setData({ freeChannels: freeChannels, loadingFree: false })
|
||||||
})
|
})
|
||||||
.catch(function (err) {
|
.catch(function (err) {
|
||||||
console.error('[首页] 免费频道失败', err)
|
console.error('[首页] 频道加载失败', err)
|
||||||
self.setData({ loadingFree: false })
|
self.setData({ loadingFree: false })
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -261,61 +291,5 @@ Page({
|
|||||||
},
|
},
|
||||||
|
|
||||||
// ===================== 工具方法 =====================
|
// ===================== 工具方法 =====================
|
||||||
|
// _computeGreeting 已提至模块级,可在 data 初始化时直接调用
|
||||||
/** 根据时间段返回问候语 */
|
|
||||||
_getGreeting() {
|
|
||||||
const hour = new Date().getHours()
|
|
||||||
var pool
|
|
||||||
if (hour >= 5 && hour < 9) {
|
|
||||||
pool = [
|
|
||||||
'新的一天,从声音开始',
|
|
||||||
'早起的人,先听一段',
|
|
||||||
'清晨的第一居声,属于你',
|
|
||||||
'晚起不如早起,早起不如听起'
|
|
||||||
]
|
|
||||||
} else if (hour >= 9 && hour < 12) {
|
|
||||||
pool = [
|
|
||||||
'上午充能量,声音加持',
|
|
||||||
'专注工作,也别忘了呼吸',
|
|
||||||
'高效早上,入耳知识',
|
|
||||||
'好状态,从一段内容开始'
|
|
||||||
]
|
|
||||||
} else if (hour >= 12 && hour < 14) {
|
|
||||||
pool = [
|
|
||||||
'午后小憩,听点轻松的',
|
|
||||||
'借耳机隔绝喧嚣,中午也有自己的时间',
|
|
||||||
'饭后十分钟,充电一下',
|
|
||||||
'慢下来,下午还长'
|
|
||||||
]
|
|
||||||
} else if (hour >= 14 && hour < 17) {
|
|
||||||
pool = [
|
|
||||||
'下午了,来点声音撤个困',
|
|
||||||
'下午三点,刚好开始听一段',
|
|
||||||
'下午的阳光和一段好内容',
|
|
||||||
'止止广告,喝口内容'
|
|
||||||
]
|
|
||||||
} else if (hour >= 17 && hour < 19) {
|
|
||||||
pool = [
|
|
||||||
'日落时分,放慢脚步',
|
|
||||||
'下班了,耳机里换一个频道',
|
|
||||||
'偶尔不刷短视频,试试听点实的',
|
|
||||||
'归途中最适合听一段'
|
|
||||||
]
|
|
||||||
} else if (hour >= 19 && hour < 22) {
|
|
||||||
pool = [
|
|
||||||
'夜晚温柔,适合倾听',
|
|
||||||
'夜晚的声音,不用赶时间',
|
|
||||||
'放下手机,喂口内容',
|
|
||||||
'夹着夜色,听一段值得的'
|
|
||||||
]
|
|
||||||
} else {
|
|
||||||
pool = [
|
|
||||||
'夜深了,听点帮助入眠的',
|
|
||||||
'出发前最后一段,晚安',
|
|
||||||
'夜奥的小时光,留给自己',
|
|
||||||
'好梦将至,晚安'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
return pool[Math.floor(Math.random() * pool.length)]
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|||||||
+16
-3
@@ -41,6 +41,16 @@
|
|||||||
>
|
>
|
||||||
<view class="content-area">
|
<view class="content-area">
|
||||||
|
|
||||||
|
<!-- VIP 状态卡片(仅 VIP 用户显示) -->
|
||||||
|
<view wx:if="{{isVip}}" class="vip-home-card tap-active" bindtap="goVip">
|
||||||
|
<text class="vip-home-icon">👑</text>
|
||||||
|
<view class="vip-home-body">
|
||||||
|
<text class="vip-home-title">全部频道会员</text>
|
||||||
|
<text class="vip-home-desc">所有频道面向你全量开放 · 畅听无限制</text>
|
||||||
|
</view>
|
||||||
|
<text class="vip-home-tag">享看权益 ›</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
<!-- ─── Section 1: 我的订阅 ─── -->
|
<!-- ─── Section 1: 我的订阅 ─── -->
|
||||||
<view class="section-header">
|
<view class="section-header">
|
||||||
<view class="section-title-wrap">
|
<view class="section-title-wrap">
|
||||||
@@ -152,8 +162,8 @@
|
|||||||
<view class="section-header section-header-free">
|
<view class="section-header section-header-free">
|
||||||
<view class="section-title-wrap">
|
<view class="section-title-wrap">
|
||||||
<text class="section-dot dot-free"></text>
|
<text class="section-dot dot-free"></text>
|
||||||
<text class="section-title">免费频道</text>
|
<text class="section-title">{{isVip ? '全部频道' : '免费频道'}}</text>
|
||||||
<text class="section-subtitle">无需订阅,随时收听</text>
|
<text class="section-subtitle">{{isVip ? '会员全开放,点即收听' : '无需订阅,随时收听'}}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="section-action tap-active" bindtap="goDiscover">
|
<view class="section-action tap-active" bindtap="goDiscover">
|
||||||
<text class="section-action-text">全部</text>
|
<text class="section-action-text">全部</text>
|
||||||
@@ -191,7 +201,10 @@
|
|||||||
<text class="free-emoji">{{item.cover || '📻'}}</text>
|
<text class="free-emoji">{{item.cover || '📻'}}</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="free-name">{{item.name}}</text>
|
<text class="free-name">{{item.name}}</text>
|
||||||
<t-tag size="small" variant="light-outline" theme="success">免费</t-tag>
|
<!-- VIP 用户显示频道类型,普通用户只显免费帘记 -->
|
||||||
|
<t-tag wx:if="{{item.isVipOnly}}" size="small" variant="light" theme="warning">👑</t-tag>
|
||||||
|
<t-tag wx:elif="{{item.isFree !== 1}}" size="small" variant="light" theme="default">付费</t-tag>
|
||||||
|
<t-tag wx:else size="small" variant="light-outline" theme="success">免费</t-tag>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
|
|||||||
@@ -118,6 +118,39 @@
|
|||||||
.content-area {
|
.content-area {
|
||||||
padding: 24rpx 32rpx 0;
|
padding: 24rpx 32rpx 0;
|
||||||
}
|
}
|
||||||
|
/* ========== VIP 首页状态卡 ========== */
|
||||||
|
.vip-home-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20rpx;
|
||||||
|
background: linear-gradient(135deg, #FFF8EE, #FFF3DC);
|
||||||
|
border: 1rpx solid #FFDFA0;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
padding: 24rpx 28rpx;
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
}
|
||||||
|
.vip-home-icon { font-size: 44rpx; }
|
||||||
|
.vip-home-body {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4rpx;
|
||||||
|
}
|
||||||
|
.vip-home-title {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #B07000;
|
||||||
|
font-family: 'PingFang SC', sans-serif;
|
||||||
|
}
|
||||||
|
.vip-home-desc {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #C09040;
|
||||||
|
}
|
||||||
|
.vip-home-tag {
|
||||||
|
font-size: 22rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #FF9D42;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ========== Section Header ========== */
|
/* ========== Section Header ========== */
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
/**
|
|
||||||
* 首次引导页 — 选择2个免费频道
|
|
||||||
*/
|
|
||||||
const app = getApp()
|
|
||||||
const mock = require('../../utils/mock')
|
|
||||||
|
|
||||||
Page({
|
|
||||||
data: {
|
|
||||||
domains: mock.DOMAINS,
|
|
||||||
selectedIds: [],
|
|
||||||
isValid: false,
|
|
||||||
statusBarHeight: 0
|
|
||||||
},
|
|
||||||
|
|
||||||
onLoad() {
|
|
||||||
this.setData({
|
|
||||||
statusBarHeight: app.globalData.statusBarHeight
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 切换频道选中状态
|
|
||||||
*/
|
|
||||||
onToggle(e) {
|
|
||||||
const id = e.currentTarget.dataset.id
|
|
||||||
let selected = this.data.selectedIds.slice()
|
|
||||||
|
|
||||||
const idx = selected.indexOf(id)
|
|
||||||
if (idx > -1) {
|
|
||||||
// 取消选中
|
|
||||||
selected.splice(idx, 1)
|
|
||||||
} else {
|
|
||||||
// 选中(最多2个)
|
|
||||||
if (selected.length >= 2) {
|
|
||||||
wx.showToast({ title: '最多选择2个免费频道', icon: 'none' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
selected.push(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setData({
|
|
||||||
selectedIds: selected,
|
|
||||||
isValid: selected.length > 0 && selected.length <= 2
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 确认选择
|
|
||||||
*/
|
|
||||||
onConfirm() {
|
|
||||||
if (!this.data.isValid) return
|
|
||||||
|
|
||||||
const self = this
|
|
||||||
this.data.selectedIds.forEach(function (id) {
|
|
||||||
app.subscribeToDomain(id)
|
|
||||||
})
|
|
||||||
|
|
||||||
wx.showToast({ title: '订阅成功!', icon: 'success' })
|
|
||||||
|
|
||||||
setTimeout(function () {
|
|
||||||
wx.switchTab({ url: '/pages/index/index' })
|
|
||||||
}, 800)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"usingComponents": {
|
|
||||||
"t-message": "tdesign-miniprogram/message/message"
|
|
||||||
},
|
|
||||||
"navigationStyle": "custom"
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
<!-- 首次引导页 —— 选择2个免费频道 -->
|
|
||||||
<view class="onboarding-page">
|
|
||||||
<!-- 顶部区域 -->
|
|
||||||
<view class="header" style="padding-top: {{statusBarHeight + 10}}px;">
|
|
||||||
<text class="header-title">选择感兴趣的频道</text>
|
|
||||||
<view class="header-sub">
|
|
||||||
<text class="sub-text">可免费选择 2 个</text>
|
|
||||||
<text class="sub-warn">选择后不可更换</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 频道网格 -->
|
|
||||||
<scroll-view scroll-y enhanced show-scrollbar="{{false}}" class="grid-scroll">
|
|
||||||
<view class="grid">
|
|
||||||
<view
|
|
||||||
wx:for="{{domains}}"
|
|
||||||
wx:key="id"
|
|
||||||
class="grid-item {{selectedIds.indexOf(item.id) > -1 ? 'selected' : ''}}"
|
|
||||||
bindtap="onToggle"
|
|
||||||
data-id="{{item.id}}"
|
|
||||||
>
|
|
||||||
<!-- 图标 -->
|
|
||||||
<view class="item-icon" style="background: {{item.bgColor}};">
|
|
||||||
<text class="icon-emoji">{{item.icon}}</text>
|
|
||||||
</view>
|
|
||||||
<!-- 名称 -->
|
|
||||||
<text class="item-name">{{item.name}}</text>
|
|
||||||
<text class="item-tag">{{item.tag}}</text>
|
|
||||||
|
|
||||||
<!-- 选中对勾 -->
|
|
||||||
<view class="check-mark" wx:if="{{selectedIds.indexOf(item.id) > -1}}">
|
|
||||||
<text class="check-icon">✓</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</scroll-view>
|
|
||||||
|
|
||||||
<!-- 底部确认按钮 -->
|
|
||||||
<view class="footer">
|
|
||||||
<button
|
|
||||||
class="confirm-btn {{isValid ? 'active' : 'disabled'}}"
|
|
||||||
bindtap="onConfirm"
|
|
||||||
disabled="{{!isValid}}"
|
|
||||||
>
|
|
||||||
确认选择 {{selectedIds.length > 0 ? '(' + selectedIds.length + '/2)' : ''}}
|
|
||||||
</button>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<t-message id="t-message" />
|
|
||||||
</view>
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
/* 首次引导页样式 */
|
|
||||||
|
|
||||||
.onboarding-page {
|
|
||||||
height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
background: #FFFFFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 顶部 */
|
|
||||||
.header {
|
|
||||||
padding: 24rpx 40rpx 24rpx;
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
z-index: 10;
|
|
||||||
background: rgba(255, 255, 255, 0.95);
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
border-bottom: 1rpx solid #F5F5F5;
|
|
||||||
}
|
|
||||||
.header-title {
|
|
||||||
font-size: 44rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #333;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.header-sub {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-top: 16rpx;
|
|
||||||
}
|
|
||||||
.sub-text {
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
.sub-warn {
|
|
||||||
font-size: 22rpx;
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 网格滚动 */
|
|
||||||
.grid-scroll {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
scrollbar-width: none;
|
|
||||||
-ms-overflow-style: none;
|
|
||||||
}
|
|
||||||
.grid-scroll::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
width: 0 !important;
|
|
||||||
height: 0 !important;
|
|
||||||
}
|
|
||||||
.grid {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
padding: 24rpx 20rpx 200rpx;
|
|
||||||
gap: 16rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 网格项 */
|
|
||||||
.grid-item {
|
|
||||||
width: calc(33.33% - 12rpx);
|
|
||||||
box-sizing: border-box;
|
|
||||||
background: #F8F8F8;
|
|
||||||
border-radius: 32rpx;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 28rpx 16rpx;
|
|
||||||
position: relative;
|
|
||||||
border: 4rpx solid transparent;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
.grid-item.selected {
|
|
||||||
border-color: var(--color-primary);
|
|
||||||
background: var(--color-primary-light);
|
|
||||||
box-shadow: 0 8rpx 24rpx rgba(255, 157, 66, 0.15);
|
|
||||||
transform: scale(1.02);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 图标 */
|
|
||||||
.item-icon {
|
|
||||||
width: 80rpx;
|
|
||||||
height: 80rpx;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
margin-bottom: 16rpx;
|
|
||||||
}
|
|
||||||
.icon-emoji {
|
|
||||||
font-size: 40rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 文字 */
|
|
||||||
.item-name {
|
|
||||||
font-size: 24rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #333;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 1.3;
|
|
||||||
margin-bottom: 6rpx;
|
|
||||||
}
|
|
||||||
.item-tag {
|
|
||||||
font-size: 18rpx;
|
|
||||||
color: #999;
|
|
||||||
text-align: center;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 选中对勾 */
|
|
||||||
.check-mark {
|
|
||||||
position: absolute;
|
|
||||||
top: 12rpx;
|
|
||||||
right: 12rpx;
|
|
||||||
width: 40rpx;
|
|
||||||
height: 40rpx;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: var(--color-primary);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
.check-icon {
|
|
||||||
font-size: 22rpx;
|
|
||||||
color: #FFF;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 底部 */
|
|
||||||
.footer {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
padding: 24rpx 40rpx;
|
|
||||||
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
|
|
||||||
background: linear-gradient(to top, #FFFFFF, #FFFFFF, rgba(255,255,255,0));
|
|
||||||
}
|
|
||||||
.confirm-btn {
|
|
||||||
width: 100%;
|
|
||||||
height: 100rpx;
|
|
||||||
border-radius: 999rpx;
|
|
||||||
font-size: 32rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
border: none;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
.confirm-btn.active {
|
|
||||||
background: var(--color-primary);
|
|
||||||
color: #FFF;
|
|
||||||
box-shadow: 0 12rpx 32rpx rgba(255, 157, 66, 0.3);
|
|
||||||
}
|
|
||||||
.confirm-btn.active:active {
|
|
||||||
transform: scale(0.97);
|
|
||||||
}
|
|
||||||
.confirm-btn.disabled {
|
|
||||||
background: #E5E5E5;
|
|
||||||
color: #BBB;
|
|
||||||
}
|
|
||||||
.confirm-btn::after {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
+115
-15
@@ -13,6 +13,7 @@ Page({
|
|||||||
activeContent: null,
|
activeContent: null,
|
||||||
isPlaying: false,
|
isPlaying: false,
|
||||||
isVip: false,
|
isVip: false,
|
||||||
|
isLiked: false,
|
||||||
currentTime: 0,
|
currentTime: 0,
|
||||||
duration: 0,
|
duration: 0,
|
||||||
currentTimeText: '00:00',
|
currentTimeText: '00:00',
|
||||||
@@ -20,16 +21,13 @@ Page({
|
|||||||
displayDate: '',
|
displayDate: '',
|
||||||
playbackRate: 1.0,
|
playbackRate: 1.0,
|
||||||
statusBarHeight: 0,
|
statusBarHeight: 0,
|
||||||
showTranscript: false, // 封面 ⇔ 文案切换
|
showTranscript: false,
|
||||||
showSpeedSheet: false,
|
// 评论弹层
|
||||||
speedItems: [
|
showComments: false,
|
||||||
{ label: '0.5x' },
|
commentList: [],
|
||||||
{ label: '0.75x' },
|
commentText: '',
|
||||||
{ label: '1.0x' },
|
commentLoading: false,
|
||||||
{ label: '1.25x' },
|
submitting: false
|
||||||
{ label: '1.5x' },
|
|
||||||
{ label: '2.0x' }
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_isSeeking: false,
|
_isSeeking: false,
|
||||||
@@ -65,6 +63,9 @@ Page({
|
|||||||
|
|
||||||
app.on('playerStateChange', this._onPlayerChange)
|
app.on('playerStateChange', this._onPlayerChange)
|
||||||
app.on('timeUpdate', this._onTimeUpdate)
|
app.on('timeUpdate', this._onTimeUpdate)
|
||||||
|
|
||||||
|
// 查询当前节目点赞状态
|
||||||
|
this._loadLikeStatus()
|
||||||
},
|
},
|
||||||
|
|
||||||
onHide() {
|
onHide() {
|
||||||
@@ -108,7 +109,7 @@ Page({
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取频道信息 — 从后端 API 获取
|
* 获取频道信息
|
||||||
*/
|
*/
|
||||||
_updateDomain() {
|
_updateDomain() {
|
||||||
const content = this.data.activeContent
|
const content = this.data.activeContent
|
||||||
@@ -116,14 +117,12 @@ Page({
|
|||||||
|
|
||||||
var channelId = content.channelId || (content.channel && content.channel.id)
|
var channelId = content.channelId || (content.channel && content.channel.id)
|
||||||
if (!channelId) {
|
if (!channelId) {
|
||||||
// 如果节目数据中直接包含 channel 信息
|
|
||||||
if (content.channel) {
|
if (content.channel) {
|
||||||
this.setData({ domain: content.channel })
|
this.setData({ domain: content.channel })
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果已经加载过且 channelId 没变,跳过
|
|
||||||
if (this.data.domain && this.data.domain.id === channelId) return
|
if (this.data.domain && this.data.domain.id === channelId) return
|
||||||
|
|
||||||
var self = this
|
var self = this
|
||||||
@@ -136,6 +135,20 @@ Page({
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询当前节目点赞状态
|
||||||
|
*/
|
||||||
|
_loadLikeStatus() {
|
||||||
|
const content = this.data.activeContent
|
||||||
|
if (!content) return
|
||||||
|
var self = this
|
||||||
|
api.getProgramDetail(content.id).then(function (res) {
|
||||||
|
if (res.code === 200 && res.data) {
|
||||||
|
self.setData({ isLiked: !!res.data.isLiked })
|
||||||
|
}
|
||||||
|
}).catch(function () { })
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 播放/暂停
|
* 播放/暂停
|
||||||
*/
|
*/
|
||||||
@@ -251,13 +264,100 @@ Page({
|
|||||||
onLike() {
|
onLike() {
|
||||||
const content = this.data.activeContent
|
const content = this.data.activeContent
|
||||||
if (!content) return
|
if (!content) return
|
||||||
api.toggleLike({ contentId: content.id }).then(function (res) {
|
const self = this
|
||||||
wx.showToast({ title: res.code === 200 ? '已收藏 ♥' : '操作失败', icon: 'none' })
|
const wasLiked = this.data.isLiked
|
||||||
|
// 乐观更新
|
||||||
|
this.setData({ isLiked: !wasLiked })
|
||||||
|
api.toggleLike(content.id).then(function (res) {
|
||||||
|
if (res.code !== 200) {
|
||||||
|
// 回滚
|
||||||
|
self.setData({ isLiked: wasLiked })
|
||||||
|
wx.showToast({ title: res.msg || '操作失败', icon: 'none' })
|
||||||
|
} else {
|
||||||
|
wx.showToast({ title: wasLiked ? '已取消喜欢' : '已喜欢 ♥', icon: 'none' })
|
||||||
|
}
|
||||||
}).catch(function () {
|
}).catch(function () {
|
||||||
|
self.setData({ isLiked: wasLiked })
|
||||||
wx.showToast({ title: '网络异常', icon: 'none' })
|
wx.showToast({ title: '网络异常', icon: 'none' })
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ===== 评论弹层 =====
|
||||||
|
|
||||||
|
onOpenComments() {
|
||||||
|
this.setData({ showComments: true })
|
||||||
|
this._loadComments()
|
||||||
|
},
|
||||||
|
|
||||||
|
onCloseComments() {
|
||||||
|
this.setData({ showComments: false, commentText: '' })
|
||||||
|
},
|
||||||
|
|
||||||
|
_loadComments() {
|
||||||
|
const content = this.data.activeContent
|
||||||
|
if (!content) return
|
||||||
|
const self = this
|
||||||
|
this.setData({ commentLoading: true })
|
||||||
|
api.getCommentList(content.id, { current: 1, pageSize: 30 }).then(function (res) {
|
||||||
|
if (res.code === 200 && res.data) {
|
||||||
|
var list = (res.data.list || res.data || []).map(function (c) {
|
||||||
|
return Object.assign({}, c, {
|
||||||
|
_isOwn: c.userId === (getApp().globalData.userInfo && getApp().globalData.userInfo.id)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
self.setData({ commentList: list, commentLoading: false })
|
||||||
|
} else {
|
||||||
|
self.setData({ commentLoading: false })
|
||||||
|
}
|
||||||
|
}).catch(function () {
|
||||||
|
self.setData({ commentLoading: false })
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onCommentInput(e) {
|
||||||
|
this.setData({ commentText: e.detail.value })
|
||||||
|
},
|
||||||
|
|
||||||
|
onSubmitComment() {
|
||||||
|
const text = (this.data.commentText || '').trim()
|
||||||
|
if (!text) return
|
||||||
|
const content = this.data.activeContent
|
||||||
|
if (!content) return
|
||||||
|
const self = this
|
||||||
|
this.setData({ submitting: true })
|
||||||
|
api.addComment(content.id, text).then(function (res) {
|
||||||
|
self.setData({ submitting: false, commentText: '' })
|
||||||
|
if (res.code === 200) {
|
||||||
|
wx.showToast({ title: '发布成功', icon: 'none' })
|
||||||
|
self._loadComments()
|
||||||
|
} else {
|
||||||
|
wx.showToast({ title: res.msg || '发布失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
}).catch(function () {
|
||||||
|
self.setData({ submitting: false })
|
||||||
|
wx.showToast({ title: '网络异常', icon: 'none' })
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onDeleteComment(e) {
|
||||||
|
const id = e.currentTarget.dataset.id
|
||||||
|
const self = this
|
||||||
|
wx.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '确认删除该评论吗?',
|
||||||
|
success(res) {
|
||||||
|
if (!res.confirm) return
|
||||||
|
api.deleteComment(id).then(function (r) {
|
||||||
|
if (r.code === 200) {
|
||||||
|
self._loadComments()
|
||||||
|
} else {
|
||||||
|
wx.showToast({ title: '删除失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
onShare() {
|
onShare() {
|
||||||
wx.showToast({ title: '分享功能开发中', icon: 'none' })
|
wx.showToast({ title: '分享功能开发中', icon: 'none' })
|
||||||
},
|
},
|
||||||
|
|||||||
+64
-13
@@ -2,7 +2,7 @@
|
|||||||
<page-meta page-style="overflow:hidden; background:#1A1208;" />
|
<page-meta page-style="overflow:hidden; background:#1A1208;" />
|
||||||
<view class="player-page">
|
<view class="player-page">
|
||||||
|
|
||||||
<!-- 状态栏占位(custom 导航模式必须手动留出状态栏高度) -->
|
<!-- 状态栏占位 -->
|
||||||
<view style="height: {{statusBarHeight}}px; flex-shrink: 0;"></view>
|
<view style="height: {{statusBarHeight}}px; flex-shrink: 0;"></view>
|
||||||
|
|
||||||
<!-- 环境光背景 -->
|
<!-- 环境光背景 -->
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
style="background: radial-gradient(ellipse at 50% -10%, {{domain.bgColor || '#FF9D42'}}44 0%, transparent 65%);">
|
style="background: radial-gradient(ellipse at 50% -10%, {{domain.bgColor || '#FF9D42'}}44 0%, transparent 65%);">
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 顶部:返回 + 标题 + 分享 -->
|
<!-- 顶部:返回 + 标题 + 评论 -->
|
||||||
<view class="top-bar">
|
<view class="top-bar">
|
||||||
<view class="top-btn tap-active" bindtap="goBack">
|
<view class="top-btn tap-active" bindtap="goBack">
|
||||||
<text class="top-back">‹</text>
|
<text class="top-back">‹</text>
|
||||||
@@ -19,8 +19,8 @@
|
|||||||
<text class="top-label">正在播放</text>
|
<text class="top-label">正在播放</text>
|
||||||
<text class="top-title">{{activeContent.title || '加载中...'}}</text>
|
<text class="top-title">{{activeContent.title || '加载中...'}}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="top-btn tap-active" bindtap="onShare">
|
<view class="top-btn tap-active" bindtap="onOpenComments">
|
||||||
<text class="top-share">↑</text>
|
<text class="top-comment-icon">💬</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
<view class="ripple-ring" style="animation-delay: 1.4s;"></view>
|
<view class="ripple-ring" style="animation-delay: 1.4s;"></view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 大 emoji,无卡片背景 -->
|
<!-- 大 emoji -->
|
||||||
<text class="cover-emoji {{isPlaying ? 'emoji-active' : ''}}">📻</text>
|
<text class="cover-emoji {{isPlaying ? 'emoji-active' : ''}}">📻</text>
|
||||||
|
|
||||||
<!-- 频道名 -->
|
<!-- 频道名 -->
|
||||||
@@ -58,11 +58,11 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 日期 + like 同行,进度条上方 -->
|
<!-- 日期 + like 同行 -->
|
||||||
<view class="date-like-row">
|
<view class="date-like-row">
|
||||||
<text class="date-text">{{displayDate}}</text>
|
<text class="date-text">{{displayDate}}</text>
|
||||||
<view class="like-btn-inline tap-active" bindtap="onLike">
|
<view class="like-btn-inline tap-active" bindtap="onLike">
|
||||||
<text class="like-icon">♡</text>
|
<text class="like-icon {{isLiked ? 'liked' : ''}}">{{isLiked ? '♥' : '♡'}}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -107,11 +107,62 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 倍速选择面板 -->
|
<!-- 评论弹层 -->
|
||||||
<t-action-sheet
|
<view class="comment-mask" wx:if="{{showComments}}" bindtap="onCloseComments"></view>
|
||||||
visible="{{showSpeedSheet}}"
|
<view class="comment-sheet {{showComments ? 'sheet-up' : ''}}">
|
||||||
items="{{speedItems}}"
|
<view class="sheet-handle"></view>
|
||||||
bind:selected="onSpeedSelect"
|
<view class="sheet-header">
|
||||||
bind:cancel="onSpeedCancel"
|
<text class="sheet-title">评论 ({{commentList.length}})</text>
|
||||||
|
<view class="sheet-close tap-active" bindtap="onCloseComments">
|
||||||
|
<text class="sheet-close-icon">✕</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 输入区 -->
|
||||||
|
<view class="comment-input-row">
|
||||||
|
<input
|
||||||
|
class="comment-input"
|
||||||
|
placeholder="说点什么..."
|
||||||
|
placeholder-style="color:rgba(255,200,120,0.3);"
|
||||||
|
value="{{commentText}}"
|
||||||
|
bindinput="onCommentInput"
|
||||||
|
confirm-type="send"
|
||||||
|
bindconfirm="onSubmitComment"
|
||||||
|
maxlength="200"
|
||||||
/>
|
/>
|
||||||
|
<view class="send-btn tap-active {{submitting ? 'sending' : ''}}" bindtap="onSubmitComment">
|
||||||
|
<text class="send-text">发送</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 评论列表 -->
|
||||||
|
<scroll-view scroll-y class="comment-list-scroll" enhanced show-scrollbar="{{false}}">
|
||||||
|
<view wx:if="{{commentLoading}}" class="comment-loading">
|
||||||
|
<text class="comment-loading-text">加载中...</text>
|
||||||
|
</view>
|
||||||
|
<view wx:elif="{{commentList.length === 0}}" class="comment-empty">
|
||||||
|
<text class="comment-empty-icon">💬</text>
|
||||||
|
<text class="comment-empty-text">还没有评论,来说第一句话</text>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
wx:for="{{commentList}}"
|
||||||
|
wx:key="id"
|
||||||
|
class="comment-item"
|
||||||
|
>
|
||||||
|
<view class="comment-avatar">
|
||||||
|
<text class="comment-avatar-text">{{item.userName ? item.userName[0] : '?'}}</text>
|
||||||
|
</view>
|
||||||
|
<view class="comment-body">
|
||||||
|
<text class="comment-author">{{item.userName || '匿名'}}</text>
|
||||||
|
<text class="comment-text">{{item.content}}</text>
|
||||||
|
<text class="comment-time">{{item.createdAtStr || item.createdAt}}</text>
|
||||||
|
</view>
|
||||||
|
<view wx:if="{{item._isOwn}}" class="comment-del tap-active" bindtap="onDeleteComment" data-id="{{item.id}}">
|
||||||
|
<text class="comment-del-icon">🗑</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view style="height: 40rpx;"></view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -320,3 +320,150 @@
|
|||||||
background: #FFF8EE;
|
background: #FFF8EE;
|
||||||
border-radius: 5rpx;
|
border-radius: 5rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── like 状态 ── */
|
||||||
|
.like-icon.liked {
|
||||||
|
color: #FF4D6D;
|
||||||
|
}
|
||||||
|
.top-comment-icon {
|
||||||
|
font-size: 38rpx;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── 评论弹层 ── */
|
||||||
|
.comment-mask {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0,0,0,0.5);
|
||||||
|
z-index: 200;
|
||||||
|
}
|
||||||
|
.comment-sheet {
|
||||||
|
position: fixed;
|
||||||
|
bottom: -100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 70vh;
|
||||||
|
background: #231808;
|
||||||
|
border-radius: 40rpx 40rpx 0 0;
|
||||||
|
z-index: 201;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0 0 env(safe-area-inset-bottom);
|
||||||
|
transition: bottom 0.3s cubic-bezier(0.32,0.72,0,1);
|
||||||
|
}
|
||||||
|
.comment-sheet.sheet-up {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
.sheet-handle {
|
||||||
|
width: 72rpx;
|
||||||
|
height: 6rpx;
|
||||||
|
background: rgba(255,200,120,0.2);
|
||||||
|
border-radius: 3rpx;
|
||||||
|
margin: 20rpx auto 0;
|
||||||
|
}
|
||||||
|
.sheet-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 24rpx 40rpx;
|
||||||
|
border-bottom: 1rpx solid rgba(255,200,120,0.08);
|
||||||
|
}
|
||||||
|
.sheet-title {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: rgba(255,240,210,0.9);
|
||||||
|
}
|
||||||
|
.sheet-close-icon {
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: rgba(255,200,120,0.4);
|
||||||
|
}
|
||||||
|
.comment-input-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
padding: 20rpx 32rpx;
|
||||||
|
border-bottom: 1rpx solid rgba(255,200,120,0.08);
|
||||||
|
}
|
||||||
|
.comment-input {
|
||||||
|
flex: 1;
|
||||||
|
background: rgba(255,200,120,0.07);
|
||||||
|
border-radius: 40rpx;
|
||||||
|
padding: 16rpx 28rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: rgba(255,240,210,0.9);
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
.send-btn {
|
||||||
|
background: #FF9D42;
|
||||||
|
border-radius: 32rpx;
|
||||||
|
padding: 14rpx 28rpx;
|
||||||
|
}
|
||||||
|
.send-btn.sending { opacity: 0.5; }
|
||||||
|
.send-text {
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #FFF;
|
||||||
|
}
|
||||||
|
.comment-list-scroll {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8rpx 0;
|
||||||
|
}
|
||||||
|
.comment-loading, .comment-empty {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 80rpx 0;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
.comment-loading-text, .comment-empty-text {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: rgba(255,200,120,0.3);
|
||||||
|
}
|
||||||
|
.comment-empty-icon { font-size: 60rpx; }
|
||||||
|
.comment-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 20rpx;
|
||||||
|
padding: 24rpx 32rpx;
|
||||||
|
border-bottom: 1rpx solid rgba(255,200,120,0.05);
|
||||||
|
}
|
||||||
|
.comment-avatar {
|
||||||
|
width: 64rpx;
|
||||||
|
height: 64rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255,157,66,0.25);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.comment-avatar-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #FF9D42;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.comment-body {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6rpx;
|
||||||
|
}
|
||||||
|
.comment-author {
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: rgba(255,200,120,0.6);
|
||||||
|
}
|
||||||
|
.comment-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: rgba(255,240,210,0.85);
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.comment-time {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: rgba(255,200,120,0.3);
|
||||||
|
}
|
||||||
|
.comment-del {
|
||||||
|
padding: 8rpx;
|
||||||
|
}
|
||||||
|
.comment-del-icon { font-size: 32rpx; }
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ Page({
|
|||||||
data: {
|
data: {
|
||||||
isVip: false,
|
isVip: false,
|
||||||
userInfo: null,
|
userInfo: null,
|
||||||
subscribedData: [],
|
|
||||||
menuItems: [
|
menuItems: [
|
||||||
{ id: 'vip', label: '会员中心', icon: '👑', desc: '未开通', highlight: true },
|
{ id: 'vip', label: '会员中心', icon: '👑', desc: '未开通', highlight: true },
|
||||||
{ id: 'help', label: '帮助与反馈', icon: '❓', desc: '', highlight: false },
|
{ id: 'help', label: '帮助与反馈', icon: '❓', desc: '', highlight: false },
|
||||||
@@ -39,24 +38,6 @@ Page({
|
|||||||
userInfo: gd.userInfo
|
userInfo: gd.userInfo
|
||||||
})
|
})
|
||||||
|
|
||||||
// 从后端获取已订阅频道
|
|
||||||
api.getSubscriptionList({ current: 1, pageSize: 50 }).then(function (res) {
|
|
||||||
if (res.code === 200 && res.data) {
|
|
||||||
var subList = res.data.list || res.data || []
|
|
||||||
|
|
||||||
// 适配封面 URL
|
|
||||||
subList = subList.map(function (ch) {
|
|
||||||
return Object.assign({}, ch, {
|
|
||||||
_coverUrl: (ch.cover && ch.cover.url) || ch.coverUrl || ''
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
self.setData({ subscribedData: subList })
|
|
||||||
}
|
|
||||||
}).catch(function (err) {
|
|
||||||
console.error('[Profile] 加载订阅失败:', err)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 更新菜单VIP状态
|
// 更新菜单VIP状态
|
||||||
var menuItems = this.data.menuItems.slice()
|
var menuItems = this.data.menuItems.slice()
|
||||||
menuItems[0].desc = gd.isVip ? '已开通' : '未开通'
|
menuItems[0].desc = gd.isVip ? '已开通' : '未开通'
|
||||||
@@ -64,24 +45,6 @@ Page({
|
|||||||
this.setData({ menuItems: menuItems })
|
this.setData({ menuItems: menuItems })
|
||||||
},
|
},
|
||||||
|
|
||||||
onUnsubscribe(e) {
|
|
||||||
const id = e.currentTarget.dataset.id
|
|
||||||
const name = e.currentTarget.dataset.name
|
|
||||||
const self = this
|
|
||||||
|
|
||||||
wx.showModal({
|
|
||||||
title: '提示',
|
|
||||||
content: '确定要取消订阅【' + name + '】吗?',
|
|
||||||
success(res) {
|
|
||||||
if (res.confirm) {
|
|
||||||
app.unsubscribeFromDomain(id).then(function () {
|
|
||||||
self._refresh()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
onMenuTap(e) {
|
onMenuTap(e) {
|
||||||
const id = e.currentTarget.dataset.id
|
const id = e.currentTarget.dataset.id
|
||||||
if (id === 'vip') {
|
if (id === 'vip') {
|
||||||
|
|||||||
+44
-56
@@ -1,85 +1,73 @@
|
|||||||
<!-- 个人中心 —— 用户信息 + 订阅管理 + 菜单 -->
|
<!-- 个人中心 - 全新极简温暖主题 -->
|
||||||
<view class="profile-page">
|
<view class="profile-page">
|
||||||
|
|
||||||
<!-- 橙色头部背景 -->
|
<!-- 沉浸式头部区域 -->
|
||||||
<view class="header-bg">
|
<view class="header-section">
|
||||||
<view class="user-info">
|
<!-- 装饰背景 -->
|
||||||
<!-- 头像 -->
|
<view class="header-bg-shape"></view>
|
||||||
<view class="avatar-wrap">
|
|
||||||
|
<!-- 用户信息块 -->
|
||||||
|
<view class="user-info-box">
|
||||||
|
<view class="avatar-container">
|
||||||
<image wx:if="{{userInfo && userInfo.avatarId}}" src="{{userInfo.avatar.url || ''}}" class="avatar-img" mode="aspectFill" />
|
<image wx:if="{{userInfo && userInfo.avatarId}}" src="{{userInfo.avatar.url || ''}}" class="avatar-img" mode="aspectFill" />
|
||||||
<text wx:else class="avatar-emoji">😊</text>
|
<text wx:else class="avatar-emoji">😊</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="user-name">
|
|
||||||
{{userInfo.nickName || userInfo.name || '微信用户'}}
|
|
||||||
<text wx:if="{{isVip}}" class="vip-crown">👑</text>
|
|
||||||
</text>
|
|
||||||
<text class="user-desc">
|
|
||||||
{{isVip ? '全频道会员' : '免费用户 · 开通会员畅听全频道'}}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 订阅管理卡片(上移覆盖) -->
|
<view class="user-details">
|
||||||
<view class="sub-card-wrap">
|
<view class="name-line">
|
||||||
<view class="card sub-card">
|
<text class="user-name">{{userInfo.nickName || userInfo.name || '你好,收听者'}}</text>
|
||||||
|
<view wx:if="{{isVip}}" class="vip-badge">
|
||||||
<!-- 空状态 -->
|
<text class="vip-badge-icon">👑</text>
|
||||||
<view wx:if="{{subscribedData.length === 0}}" class="sub-empty">
|
<text class="vip-badge-text">VIP会员</text>
|
||||||
<text class="sub-empty-text">暂未订阅任何频道</text>
|
|
||||||
<button class="btn-ghost" bindtap="goDiscover">去广场添加</button>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 订阅列表 -->
|
|
||||||
<view wx:else>
|
|
||||||
<view
|
|
||||||
wx:for="{{subscribedData}}"
|
|
||||||
wx:key="id"
|
|
||||||
class="sub-item"
|
|
||||||
>
|
|
||||||
<view class="sub-item-left">
|
|
||||||
<view class="sub-icon" style="background: {{item.bgColor || '#F0F0F0'}};">
|
|
||||||
<image wx:if="{{item._coverUrl}}" src="{{item._coverUrl}}" class="sub-cover-img" mode="aspectFill" />
|
|
||||||
<text wx:else class="sub-emoji">{{item.icon || '📻'}}</text>
|
|
||||||
</view>
|
|
||||||
<view class="sub-info">
|
|
||||||
<text class="sub-name">{{item.name}}</text>
|
|
||||||
<text class="sub-tag {{item.isFree === 1 ? 'free' : ''}}">
|
|
||||||
{{item.isFree === 1 ? '免费' : '已订阅'}}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="sub-del tap-active" bindtap="onUnsubscribe" data-id="{{item.id}}" data-name="{{item.name}}">
|
|
||||||
<text class="del-icon">🗑</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<text class="user-id">今日也要元气满满哦</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 菜单列表 -->
|
<!-- 内容区 -->
|
||||||
<view class="menu-card-wrap">
|
<view class="content-section">
|
||||||
<view class="card menu-card">
|
|
||||||
|
<!-- VIP 专属引导卡片 (若未开通) -->
|
||||||
|
<view wx:if="{{!isVip}}" class="vip-promo-card tap-active" bindtap="onMenuTap" data-id="vip">
|
||||||
|
<view class="promo-left">
|
||||||
|
<text class="promo-icon">✨</text>
|
||||||
|
<view class="promo-text">
|
||||||
|
<text class="promo-title">开通全频道会员</text>
|
||||||
|
<text class="promo-desc">畅听无阻 · 专属标识 · 尊贵体验</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="promo-right">
|
||||||
|
<text class="promo-btn">立即开通</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 菜单列表卡片 -->
|
||||||
|
<view class="menu-list-card">
|
||||||
<view
|
<view
|
||||||
wx:for="{{menuItems}}"
|
wx:for="{{menuItems}}"
|
||||||
wx:key="id"
|
wx:key="id"
|
||||||
class="menu-item tap-active"
|
class="menu-row tap-active"
|
||||||
bindtap="onMenuTap"
|
bindtap="onMenuTap"
|
||||||
data-id="{{item.id}}"
|
data-id="{{item.id}}"
|
||||||
>
|
>
|
||||||
<view class="menu-item-left">
|
<view class="menu-left">
|
||||||
<view class="menu-icon-wrap {{item.highlight ? 'highlight' : ''}}">
|
<view class="menu-icon-box {{item.id === 'vip' ? 'is-vip-icon' : ''}}">
|
||||||
<text class="menu-emoji">{{item.icon}}</text>
|
<text class="menu-emoji">{{item.icon}}</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="menu-label">{{item.label}}</text>
|
<text class="menu-title">{{item.label}}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="menu-item-right">
|
<view class="menu-right">
|
||||||
<text class="menu-desc {{item.highlight ? 'highlight' : ''}}">{{item.desc}}</text>
|
<text class="menu-status {{item.highlight ? 'highlight-text' : ''}}">{{item.desc}}</text>
|
||||||
<text class="menu-arrow">›</text>
|
<text class="menu-arrow">›</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view style="height: 200rpx;"></view>
|
<!-- 底部留白以防遮挡播放器 -->
|
||||||
|
<view class="bottom-spacer"></view>
|
||||||
<global-player />
|
<global-player />
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
+198
-159
@@ -1,223 +1,262 @@
|
|||||||
/* 个人中心样式 */
|
/* 个人中心 - 全新极简温暖主题 */
|
||||||
|
:root {
|
||||||
|
--primary-color: #F38600;
|
||||||
|
--bg-color: #F7F8FA;
|
||||||
|
--card-bg: #FFFFFF;
|
||||||
|
--text-main: #2C2C2C;
|
||||||
|
--text-sub: #8E8E93;
|
||||||
|
}
|
||||||
|
|
||||||
.profile-page {
|
.profile-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: var(--color-bg-page);
|
background-color: #F9F9F9;
|
||||||
|
position: relative;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 橙色头部 */
|
/* 沉浸式头部 */
|
||||||
.header-bg {
|
.header-section {
|
||||||
background: #F38600;
|
position: relative;
|
||||||
border-radius: 0 0 48rpx 48rpx;
|
width: 100%;
|
||||||
padding-bottom: 200rpx;
|
padding-top: 140rpx;
|
||||||
|
padding-bottom: 80rpx;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
.user-info {
|
|
||||||
|
.header-bg-shape {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(135deg, #FFF0E0 0%, #FFDFB8 100%);
|
||||||
|
border-bottom-left-radius: 64rpx;
|
||||||
|
border-bottom-right-radius: 64rpx;
|
||||||
|
z-index: -1;
|
||||||
|
box-shadow: 0 4rpx 24rpx rgba(243, 134, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 32rpx 40rpx;
|
padding: 0 48rpx;
|
||||||
}
|
}
|
||||||
.avatar-wrap {
|
|
||||||
width: 128rpx;
|
.avatar-container {
|
||||||
height: 128rpx;
|
width: 140rpx;
|
||||||
|
height: 140rpx;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
border: 6rpx solid rgba(255, 255, 255, 0.3);
|
border: 6rpx solid rgba(255, 255, 255, 0.6);
|
||||||
background: linear-gradient(135deg, #FFB366, #FFE0B2);
|
background: #FFF;
|
||||||
|
box-shadow: 0 12rpx 32rpx rgba(243, 134, 0, 0.15);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-bottom: 20rpx;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.avatar-emoji {
|
|
||||||
font-size: 64rpx;
|
|
||||||
}
|
|
||||||
.avatar-img {
|
.avatar-img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 50%;
|
object-fit: cover;
|
||||||
}
|
|
||||||
.user-name {
|
|
||||||
font-size: 32rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #FFF;
|
|
||||||
letter-spacing: 2rpx;
|
|
||||||
}
|
|
||||||
.vip-crown {
|
|
||||||
font-size: 28rpx;
|
|
||||||
margin-left: 8rpx;
|
|
||||||
}
|
|
||||||
.user-desc {
|
|
||||||
font-size: 22rpx;
|
|
||||||
color: rgba(255, 255, 255, 0.9);
|
|
||||||
margin-top: 8rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 订阅卡片 */
|
.avatar-emoji {
|
||||||
.sub-card-wrap {
|
font-size: 72rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-details {
|
||||||
|
margin-left: 36rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name-line {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-name {
|
||||||
|
font-size: 40rpx;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #333333;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vip-badge {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: linear-gradient(90deg, #333333 0%, #1A1A1A 100%);
|
||||||
|
padding: 4rpx 16rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
margin-left: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vip-badge-icon {
|
||||||
|
font-size: 20rpx;
|
||||||
|
margin-right: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vip-badge-text {
|
||||||
|
font-size: 20rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #FDF1C2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-id {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #8C6F50;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 内容区 */
|
||||||
|
.content-section {
|
||||||
padding: 0 32rpx;
|
padding: 0 32rpx;
|
||||||
margin-top: -140rpx;
|
margin-top: -30rpx;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
.sub-card {
|
|
||||||
padding: 32rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 空状态 */
|
/* VIP宣发引导卡片 */
|
||||||
.sub-empty {
|
.vip-promo-card {
|
||||||
text-align: center;
|
|
||||||
padding: 40rpx 0;
|
|
||||||
}
|
|
||||||
.sub-empty-text {
|
|
||||||
display: block;
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #999;
|
|
||||||
margin-bottom: 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 订阅列表项 */
|
|
||||||
.sub-item {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 20rpx;
|
background: linear-gradient(135deg, #FFEFD5 0%, #FFE4B5 100%);
|
||||||
|
padding: 32rpx 40rpx;
|
||||||
border-radius: 32rpx;
|
border-radius: 32rpx;
|
||||||
background: #FEFEFE;
|
box-shadow: 0 8rpx 24rpx rgba(217, 119, 6, 0.08);
|
||||||
border: 1rpx solid rgba(0,0,0,0.04);
|
margin-bottom: 32rpx;
|
||||||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.02);
|
border: 1rpx solid rgba(255, 255, 255, 0.5);
|
||||||
margin-bottom: 16rpx;
|
|
||||||
}
|
|
||||||
.sub-item-left {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.sub-icon {
|
|
||||||
width: 80rpx;
|
|
||||||
height: 80rpx;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
.sub-emoji {
|
|
||||||
font-size: 36rpx;
|
|
||||||
}
|
|
||||||
.sub-cover-img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
.sub-info {
|
|
||||||
margin-left: 24rpx;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.sub-name {
|
|
||||||
display: block;
|
|
||||||
font-size: 28rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
.sub-tag {
|
|
||||||
display: inline-block;
|
|
||||||
margin-top: 8rpx;
|
|
||||||
padding: 4rpx 12rpx;
|
|
||||||
border-radius: 6rpx;
|
|
||||||
font-size: 20rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
.sub-tag.free {
|
|
||||||
background: rgba(45, 90, 39, 0.1);
|
|
||||||
color: #2D5A27;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sub-del {
|
.promo-left {
|
||||||
width: 56rpx;
|
|
||||||
height: 56rpx;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: #F5F5F5;
|
|
||||||
border: 1rpx solid #EEE;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
.del-icon {
|
|
||||||
font-size: 24rpx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sub-notice {
|
.promo-icon {
|
||||||
display: block;
|
font-size: 48rpx;
|
||||||
font-size: 20rpx;
|
margin-right: 24rpx;
|
||||||
color: rgba(255, 157, 66, 0.7);
|
}
|
||||||
text-align: center;
|
|
||||||
margin-top: 12rpx;
|
.promo-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.promo-title {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #8B4513;
|
||||||
|
margin-bottom: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.promo-desc {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #AA7A55;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 菜单卡片 */
|
.promo-btn {
|
||||||
.menu-card-wrap {
|
background: #333333;
|
||||||
padding: 24rpx 32rpx 0;
|
color: #FDF1C2;
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 12rpx 28rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
.menu-card {
|
|
||||||
padding: 8rpx 0;
|
/* 列表菜单 */
|
||||||
|
.menu-list-card {
|
||||||
|
background: #FFFFFF;
|
||||||
|
border-radius: 32rpx;
|
||||||
|
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.02);
|
||||||
|
padding: 16rpx 0;
|
||||||
}
|
}
|
||||||
.menu-item {
|
|
||||||
|
.menu-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 28rpx 32rpx;
|
padding: 32rpx 40rpx;
|
||||||
border-bottom: 1rpx solid rgba(0,0,0,0.03);
|
position: relative;
|
||||||
}
|
}
|
||||||
.menu-item:last-child {
|
|
||||||
border-bottom: none;
|
.menu-row::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 110rpx;
|
||||||
|
right: 40rpx;
|
||||||
|
border-bottom: 1rpx solid #F5F5F5;
|
||||||
}
|
}
|
||||||
.menu-item-left {
|
.menu-row:last-child::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.menu-icon-wrap {
|
|
||||||
width: 52rpx;
|
.menu-icon-box {
|
||||||
height: 52rpx;
|
width: 60rpx;
|
||||||
border-radius: 50%;
|
height: 60rpx;
|
||||||
background: #F5F5F5;
|
background: #F8F9FA;
|
||||||
|
border-radius: 20rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-right: 20rpx;
|
margin-right: 28rpx;
|
||||||
}
|
}
|
||||||
.menu-icon-wrap.highlight {
|
|
||||||
background: rgba(251, 191, 36, 0.1);
|
.menu-icon-box.is-vip-icon {
|
||||||
|
background: rgba(251, 191, 36, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-emoji {
|
.menu-emoji {
|
||||||
font-size: 24rpx;
|
font-size: 32rpx;
|
||||||
}
|
}
|
||||||
.menu-label {
|
|
||||||
font-size: 28rpx;
|
.menu-title {
|
||||||
font-weight: 700;
|
font-size: 30rpx;
|
||||||
color: #333;
|
font-weight: 600;
|
||||||
letter-spacing: 2rpx;
|
color: #333333;
|
||||||
}
|
}
|
||||||
.menu-item-right {
|
|
||||||
|
.menu-right {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12rpx;
|
|
||||||
}
|
}
|
||||||
.menu-desc {
|
|
||||||
font-size: 22rpx;
|
.menu-status {
|
||||||
color: #999;
|
font-size: 24rpx;
|
||||||
font-weight: 500;
|
color: #999999;
|
||||||
|
margin-right: 12rpx;
|
||||||
}
|
}
|
||||||
.menu-desc.highlight {
|
|
||||||
|
.menu-status.highlight-text {
|
||||||
color: #D97706;
|
color: #D97706;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-arrow {
|
.menu-arrow {
|
||||||
font-size: 28rpx;
|
font-size: 32rpx;
|
||||||
color: #CCC;
|
color: #CCCCCC;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-spacer {
|
||||||
|
height: 240rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 点击态 */
|
||||||
|
.tap-active:active {
|
||||||
|
opacity: 0.7;
|
||||||
|
transform: scale(0.98);
|
||||||
|
transition: all 0.2s;
|
||||||
}
|
}
|
||||||
|
|||||||
+109
-14
@@ -37,7 +37,6 @@ Page({
|
|||||||
const quarterly = (parseFloat(options.quarterlyPrice) || 0) / 100
|
const quarterly = (parseFloat(options.quarterlyPrice) || 0) / 100
|
||||||
const annual = (parseFloat(options.annualPrice) || 0) / 100
|
const annual = (parseFloat(options.annualPrice) || 0) / 100
|
||||||
|
|
||||||
// 默认选中包年,否则最合算的
|
|
||||||
let defaultPlan = 'monthly'
|
let defaultPlan = 'monthly'
|
||||||
let defaultPrice = monthly
|
let defaultPrice = monthly
|
||||||
if (annual > 0) { defaultPlan = 'annual'; defaultPrice = annual }
|
if (annual > 0) { defaultPlan = 'annual'; defaultPrice = annual }
|
||||||
@@ -59,13 +58,40 @@ Page({
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// ─── VIP 会员模式 ───
|
// ─── VIP 会员模式 ───
|
||||||
|
const gd = app.globalData
|
||||||
this.setData({
|
this.setData({
|
||||||
mode: 'vip',
|
mode: 'vip',
|
||||||
isVip: app.globalData.isVip,
|
isVip: gd.isVip,
|
||||||
selectedPlan: 'vip-all',
|
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() {
|
onShow() {
|
||||||
@@ -95,20 +121,42 @@ Page({
|
|||||||
const { mode, selectedPlan, currentPrice, channelId } = this.data
|
const { mode, selectedPlan, currentPrice, channelId } = this.data
|
||||||
|
|
||||||
if (mode === 'vip') {
|
if (mode === 'vip') {
|
||||||
// ── VIP 全频道(模拟,后续接入时替换) ──
|
// ── VIP 永久会员:调后端预支付接口 ──
|
||||||
wx.showModal({
|
wx.showLoading({ title: '获取支付信息...' })
|
||||||
title: '确认支付',
|
|
||||||
content: `即将支付 ¥${currentPrice} 开通全频道会员`,
|
api.initiateVipPayment()
|
||||||
success(res) {
|
.then(function (res) {
|
||||||
if (res.confirm) {
|
if (res.code !== 200 || !res.data || !res.data.payments) {
|
||||||
app.upgradeVip()
|
wx.hideLoading()
|
||||||
wx.showToast({ title: '开通成功!', icon: 'success' })
|
wx.showToast({ title: res.msg || '获取支付信息失败', icon: 'none' })
|
||||||
setTimeout(function () {
|
return
|
||||||
self.setData({ isVip: true })
|
|
||||||
}, 500)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
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() {
|
goBack() {
|
||||||
wx.navigateBack()
|
wx.navigateBack()
|
||||||
}
|
}
|
||||||
|
|||||||
+55
-25
@@ -2,12 +2,56 @@
|
|||||||
<page-meta page-style="overflow: hidden;" />
|
<page-meta page-style="overflow: hidden;" />
|
||||||
<view class="vip-page">
|
<view class="vip-page">
|
||||||
|
|
||||||
<!-- ── 已是VIP,直接返回 ── -->
|
<!-- ── 已是 VIP,展示会员权益页 ── -->
|
||||||
<view wx:if="{{isVip && mode === 'vip'}}" class="vip-done">
|
<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>
|
<view class="vip-glow"></view>
|
||||||
<button class="done-back-btn" bindtap="goBack">返回</button>
|
|
||||||
|
<!-- 安全区域占位 -->
|
||||||
|
<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>
|
</view>
|
||||||
|
|
||||||
<!-- ── 主Scroll区域 ── -->
|
<!-- ── 主Scroll区域 ── -->
|
||||||
@@ -23,7 +67,7 @@
|
|||||||
<view class="vip-hero">
|
<view class="vip-hero">
|
||||||
<text class="vip-hero-title">{{mode === 'channel' ? channelName : '开通全频道会员'}}</text>
|
<text class="vip-hero-title">{{mode === 'channel' ? channelName : '开通全频道会员'}}</text>
|
||||||
<text class="vip-hero-desc">
|
<text class="vip-hero-desc">
|
||||||
{{mode === 'channel' ? '选择适合你的订阅方案,随时随地收听' : '解锁全部频道,告别无聊早晨'}}
|
{{mode === 'channel' ? '选择适合你的订阅方案,随时随地收听' : '一次开通,永久解锁全部频道'}}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -51,20 +95,6 @@
|
|||||||
<text class="benefit-desc">收听无任何打扰</text>
|
<text class="benefit-desc">收听无任何打扰</text>
|
||||||
</view>
|
</view>
|
||||||
</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>
|
||||||
</view>
|
</view>
|
||||||
@@ -77,14 +107,14 @@
|
|||||||
bindtap="selectPlan"
|
bindtap="selectPlan"
|
||||||
data-plan="vip-all"
|
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">
|
<view class="plan-info">
|
||||||
<text class="plan-name">全频道连续包月</text>
|
<text class="plan-name">永久会员</text>
|
||||||
<text class="plan-desc">自动续费,随时可取消</text>
|
<text class="plan-desc">{{vipRemark || '一次购买,永久畅听全部频道'}}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="plan-price">
|
<view class="plan-price">
|
||||||
<text class="price-amount"><text class="price-symbol">¥</text>19.9</text>
|
<text class="price-amount"><text class="price-symbol">¥</text>{{vipPrice || currentPrice}}</text>
|
||||||
<text class="price-original">¥29.9</text>
|
<text class="price-original" wx:if="{{vipOriginalPrice}}">¥{{vipOriginalPrice}}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
+134
-21
@@ -9,39 +9,152 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 已是VIP */
|
/* 已是VIP 页面整体 */
|
||||||
.vip-done {
|
.vip-done {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
padding: 40rpx;
|
background: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%);
|
||||||
background: #FFFFFF;
|
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 {
|
.vip-done-title {
|
||||||
font-size: 36rpx;
|
font-size: 44rpx;
|
||||||
font-weight: 700;
|
font-weight: 800;
|
||||||
color: #333;
|
color: #FFF;
|
||||||
margin-bottom: 16rpx;
|
letter-spacing: 4rpx;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
text-shadow: 0 4rpx 16rpx rgba(0,0,0,0.5);
|
||||||
}
|
}
|
||||||
.vip-done-desc {
|
|
||||||
font-size: 28rpx;
|
.vip-expire-badge {
|
||||||
color: #999;
|
background: rgba(255, 255, 255, 0.1);
|
||||||
margin-bottom: 48rpx;
|
padding: 12rpx 32rpx;
|
||||||
}
|
|
||||||
.done-back-btn {
|
|
||||||
padding: 16rpx 48rpx;
|
|
||||||
background: #F5F5F5;
|
|
||||||
border-radius: 999rpx;
|
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;
|
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;
|
border: none;
|
||||||
color: #666;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
.done-back-btn::after { border: none; }
|
.done-back-btn::after { border: none; }
|
||||||
|
|
||||||
|
|||||||
+58
-6
@@ -110,7 +110,7 @@ function unsubscribe(channelId) {
|
|||||||
|
|
||||||
/** 添加收听历史 */
|
/** 添加收听历史 */
|
||||||
function addHistory(params) {
|
function addHistory(params) {
|
||||||
return post('/radio/history/add', {
|
return post('/history/add', {
|
||||||
programId: params.programId,
|
programId: params.programId,
|
||||||
progress: params.progress || 0,
|
progress: params.progress || 0,
|
||||||
duration: params.duration || 0
|
duration: params.duration || 0
|
||||||
@@ -119,27 +119,42 @@ function addHistory(params) {
|
|||||||
|
|
||||||
/** 获取收听历史列表 */
|
/** 获取收听历史列表 */
|
||||||
function getHistoryList(params) {
|
function getHistoryList(params) {
|
||||||
return post('/radio/history/list', {
|
return post('/history/list', {
|
||||||
current: (params && params.current) || 1,
|
current: (params && params.current) || 1,
|
||||||
pageSize: (params && params.pageSize) || 20
|
pageSize: (params && params.pageSize) || 20
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 删除单条收听历史 */
|
||||||
|
function deleteHistory(programId) {
|
||||||
|
return post('/history/delete', { programId })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 清空全部收听历史 */
|
||||||
|
function deleteAllHistory() {
|
||||||
|
return get('/history/deleteAll')
|
||||||
|
}
|
||||||
|
|
||||||
// ======================== 收藏 ========================
|
// ======================== 收藏 ========================
|
||||||
|
|
||||||
/** 添加收藏 */
|
/** 添加收藏 */
|
||||||
function addFavorite(programId) {
|
function addFavorite(programId) {
|
||||||
return post('/radio/favorite/add', { programId })
|
return post('/favorite/add', { programId })
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 取消收藏 */
|
/** 取消收藏 */
|
||||||
function removeFavorite(programId) {
|
function removeFavorite(programId) {
|
||||||
return post('/radio/favorite/remove', { programId })
|
return post('/favorite/remove', { programId })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 清空全部收藏 */
|
||||||
|
function removeAllFavorites() {
|
||||||
|
return get('/favorite/removeAll')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取收藏列表 */
|
/** 获取收藏列表 */
|
||||||
function getFavoriteList(params) {
|
function getFavoriteList(params) {
|
||||||
return post('/radio/favorite/list', {
|
return post('/favorite/list', {
|
||||||
current: (params && params.current) || 1,
|
current: (params && params.current) || 1,
|
||||||
pageSize: (params && params.pageSize) || 20
|
pageSize: (params && params.pageSize) || 20
|
||||||
})
|
})
|
||||||
@@ -149,10 +164,39 @@ function getFavoriteList(params) {
|
|||||||
|
|
||||||
/** 切换点赞 */
|
/** 切换点赞 */
|
||||||
function toggleLike(programId) {
|
function toggleLike(programId) {
|
||||||
return post('/radio/like/toggle', { programId })
|
return post('/like/toggle', { programId })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 添加评论 */
|
||||||
|
function addComment(programId, content) {
|
||||||
|
return post('/comment/add', { programId, content })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除评论 */
|
||||||
|
function deleteComment(id) {
|
||||||
|
return post('/comment/delete', { id })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取评论列表 */
|
||||||
|
function getCommentList(programId, params) {
|
||||||
|
return post('/comment/list', {
|
||||||
|
programId,
|
||||||
|
current: (params && params.current) || 1,
|
||||||
|
pageSize: (params && params.pageSize) || 20
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取当前登录用户 */
|
/** 获取当前登录用户 */
|
||||||
|
/** 获取 VIP 配置(价格等) */
|
||||||
|
function getVipConfig() {
|
||||||
|
return post('/vip/config/detail', {})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 发起 VIP 开通预支付 */
|
||||||
|
function initiateVipPayment() {
|
||||||
|
return post('/vip/vip',{})
|
||||||
|
}
|
||||||
|
|
||||||
function getUserInfo() {
|
function getUserInfo() {
|
||||||
return get('/user/info')
|
return get('/user/info')
|
||||||
}
|
}
|
||||||
@@ -205,10 +249,18 @@ module.exports = {
|
|||||||
unsubscribe,
|
unsubscribe,
|
||||||
addHistory,
|
addHistory,
|
||||||
getHistoryList,
|
getHistoryList,
|
||||||
|
deleteHistory,
|
||||||
|
deleteAllHistory,
|
||||||
addFavorite,
|
addFavorite,
|
||||||
removeFavorite,
|
removeFavorite,
|
||||||
|
removeAllFavorites,
|
||||||
getFavoriteList,
|
getFavoriteList,
|
||||||
toggleLike,
|
toggleLike,
|
||||||
|
addComment,
|
||||||
|
deleteComment,
|
||||||
|
getCommentList,
|
||||||
|
getVipConfig,
|
||||||
|
initiateVipPayment,
|
||||||
getUserInfo,
|
getUserInfo,
|
||||||
subscribeChannel,
|
subscribeChannel,
|
||||||
unlockChannel,
|
unlockChannel,
|
||||||
|
|||||||
+8
-11
@@ -10,6 +10,7 @@ const api = require('./api')
|
|||||||
let bgAudioManager = null
|
let bgAudioManager = null
|
||||||
let appInstance = null
|
let appInstance = null
|
||||||
let _switching = false // 切换音频时的锁,防止 onStop 事件干扰
|
let _switching = false // 切换音频时的锁,防止 onStop 事件干扰
|
||||||
|
let _ended = false // 标记音频是否已自然播放完毕
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化音频管理器
|
* 初始化音频管理器
|
||||||
@@ -42,6 +43,7 @@ function init(app) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
bgAudioManager.onEnded(() => {
|
bgAudioManager.onEnded(() => {
|
||||||
|
_ended = true
|
||||||
reportHistory()
|
reportHistory()
|
||||||
updatePlayState(false)
|
updatePlayState(false)
|
||||||
appInstance.globalData.currentTime = appInstance.globalData.duration
|
appInstance.globalData.currentTime = appInstance.globalData.duration
|
||||||
@@ -140,6 +142,7 @@ function playContent(content) {
|
|||||||
|
|
||||||
// 标记正在切换,防止旧音频的 onStop 干扰
|
// 标记正在切换,防止旧音频的 onStop 干扰
|
||||||
_switching = true
|
_switching = true
|
||||||
|
_ended = false // 重置结束标记
|
||||||
|
|
||||||
appInstance.globalData.activeContent = content
|
appInstance.globalData.activeContent = content
|
||||||
appInstance.globalData.currentTime = 0
|
appInstance.globalData.currentTime = 0
|
||||||
@@ -167,18 +170,12 @@ function togglePlay() {
|
|||||||
if (appInstance.globalData.isPlaying) {
|
if (appInstance.globalData.isPlaying) {
|
||||||
bgAudioManager.pause()
|
bgAudioManager.pause()
|
||||||
} else {
|
} else {
|
||||||
// 检查 bgAudioManager 是否还有有效的 src
|
// 音频已自然播放完毕,或 src 被系统回收 → 从头重新播放
|
||||||
// 音频结束/被系统回收后,src 会变为空,此时 play() 无效
|
var srcEmpty = false
|
||||||
// 需要重新设置 src 来恢复播放
|
try { srcEmpty = !bgAudioManager.src } catch (e) { srcEmpty = true }
|
||||||
var currentSrc = ''
|
|
||||||
try {
|
|
||||||
currentSrc = bgAudioManager.src
|
|
||||||
} catch (e) {
|
|
||||||
currentSrc = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!currentSrc) {
|
if (_ended || srcEmpty) {
|
||||||
// src 已被清空(音频结束、系统回收等),重新播放
|
_ended = false
|
||||||
playContent(appInstance.globalData.activeContent)
|
playContent(appInstance.globalData.activeContent)
|
||||||
} else {
|
} else {
|
||||||
bgAudioManager.play()
|
bgAudioManager.play()
|
||||||
|
|||||||
+2
-2
@@ -4,8 +4,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// 接口基础地址(预留占位符,对接后端时替换)
|
// 接口基础地址(预留占位符,对接后端时替换)
|
||||||
const API_BASE_URL = 'https://radio.sundynix.cn/api'
|
//const API_BASE_URL = 'https://radio.sundynix.cn/api'
|
||||||
//const API_BASE_URL = 'http://192.168.0.184:8888'
|
const API_BASE_URL = 'http://192.168.0.184:8888'
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user