first commit
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
/**
|
||||
* 频道广场 — 从后端获取分类 + 频道列表
|
||||
*
|
||||
* 性能优化:直接使用 channel list 返回的 hasSubscribed 字段,
|
||||
* 无需额外请求订阅列表,减少一次 HTTP 请求
|
||||
*/
|
||||
const app = getApp()
|
||||
const api = require('../../utils/api')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
isVip: false,
|
||||
categories: [],
|
||||
activeFilter: '',
|
||||
filteredDomains: [],
|
||||
loading: true
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this._loadCategories()
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this._loadChannels()
|
||||
this._onSubChange = () => this._loadChannels()
|
||||
this._onVipChange = () => this._loadChannels()
|
||||
app.on('subscriptionChange', this._onSubChange)
|
||||
app.on('vipChange', this._onVipChange)
|
||||
},
|
||||
|
||||
onHide() {
|
||||
if (this._onSubChange) app.off('subscriptionChange', this._onSubChange)
|
||||
if (this._onVipChange) app.off('vipChange', this._onVipChange)
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载分类列表
|
||||
*/
|
||||
_loadCategories() {
|
||||
const self = this
|
||||
api.getCategoryList().then(function (res) {
|
||||
if (res.code === 200 && res.data) {
|
||||
const list = Array.isArray(res.data) ? res.data : (res.data.list || [])
|
||||
const categories = [{ id: '', name: '全部' }].concat(list)
|
||||
self.setData({ categories: categories })
|
||||
}
|
||||
}).catch(function (err) {
|
||||
console.error('[Discover] 加载分类失败:', err)
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载频道列表
|
||||
* 直接使用后端返回的 hasSubscribed 字段,无需单独请求订阅列表
|
||||
*/
|
||||
_loadChannels() {
|
||||
const self = this
|
||||
const gd = app.globalData
|
||||
const isFirstLoad = self.data.filteredDomains.length === 0
|
||||
if (isFirstLoad) self.setData({ loading: true })
|
||||
|
||||
api.getChannelList({ categoryId: self.data.activeFilter, current: 1, pageSize: 50 })
|
||||
.then(function (channelRes) {
|
||||
var channels = []
|
||||
if (channelRes.code === 200 && channelRes.data) {
|
||||
channels = channelRes.data.list || channelRes.data || []
|
||||
}
|
||||
|
||||
var filtered = channels.map(function (ch) {
|
||||
var isFree = ch.isFree === 1
|
||||
var isVipOnly = ch.isVipOnly === 1
|
||||
// 最低价(分→元)
|
||||
var lowestPrice = null
|
||||
if (!isFree && !isVipOnly) {
|
||||
var prices = [
|
||||
{ label: '包月', value: ch.monthlyPrice },
|
||||
{ label: '包季', value: ch.quarterlyPrice },
|
||||
{ label: '包年', value: ch.annualPrice }
|
||||
].filter(function (p) { return p.value > 0 })
|
||||
if (prices.length > 0) {
|
||||
prices.sort(function (a, b) { return a.value - b.value })
|
||||
lowestPrice = { label: prices[0].label, value: (prices[0].value / 100).toFixed(2) }
|
||||
}
|
||||
}
|
||||
return Object.assign({}, ch, {
|
||||
_isSubscribed: ch.hasSubscribed === 1,
|
||||
_isFree: isFree,
|
||||
_isVipOnly: isVipOnly,
|
||||
_lowestPrice: lowestPrice
|
||||
})
|
||||
})
|
||||
|
||||
self.setData({
|
||||
isVip: gd.isVip,
|
||||
filteredDomains: filtered,
|
||||
loading: false
|
||||
})
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.error('[Discover] 加载频道失败:', err)
|
||||
self.setData({ loading: false })
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 切换分类筛选
|
||||
*/
|
||||
onFilter(e) {
|
||||
this.setData({ activeFilter: e.currentTarget.dataset.cat })
|
||||
this._loadChannels()
|
||||
},
|
||||
|
||||
/**
|
||||
* 按鈕操作
|
||||
* 1. 已订阅 → 跳转频道详情
|
||||
* 2. isFree → 直接跳转频道详情(收听)
|
||||
* 3. isVipOnly → 引导去 VIP 页
|
||||
* 4. 付费订阅 → 跳转支付页
|
||||
*/
|
||||
onAction(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
|
||||
var channel = null
|
||||
for (var i = 0; i < this.data.filteredDomains.length; i++) {
|
||||
if (this.data.filteredDomains[i].id === id) {
|
||||
channel = this.data.filteredDomains[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!channel) return
|
||||
|
||||
// 已订阅 → 直接进详情页
|
||||
if (channel._isSubscribed) {
|
||||
wx.navigateTo({ url: '/pages/channel-detail/index?id=' + id })
|
||||
return
|
||||
}
|
||||
|
||||
// 免费 → 直接进详情收听
|
||||
if (channel._isFree) {
|
||||
wx.navigateTo({ url: '/pages/channel-detail/index?id=' + id })
|
||||
return
|
||||
}
|
||||
|
||||
// VIP专享 → 引导 VIP 页
|
||||
if (channel._isVipOnly) {
|
||||
wx.navigateTo({ url: '/pages/vip/index' })
|
||||
return
|
||||
}
|
||||
|
||||
// 付费订阅 → 跳转 VIP/订阅页(channel 模式)
|
||||
var params = 'channelId=' + id
|
||||
+ '&channelName=' + encodeURIComponent(channel.name || '')
|
||||
+ '&monthlyPrice=' + (channel.monthlyPrice || 0)
|
||||
+ '&quarterlyPrice=' + (channel.quarterlyPrice || 0)
|
||||
+ '&annualPrice=' + (channel.annualPrice || 0)
|
||||
wx.navigateTo({ url: '/pages/vip/index?' + params })
|
||||
},
|
||||
|
||||
/**
|
||||
* 跳转频道详情
|
||||
*/
|
||||
goDetail(e) {
|
||||
wx.navigateTo({ url: '/pages/channel-detail/index?id=' + e.currentTarget.dataset.id })
|
||||
},
|
||||
|
||||
/**
|
||||
* 跳转VIP
|
||||
*/
|
||||
goVip() {
|
||||
wx.navigateTo({ url: '/pages/vip/index' })
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
"global-player": "/components/global-player/index",
|
||||
"t-message": "tdesign-miniprogram/message/message",
|
||||
"t-image": "tdesign-miniprogram/image/image",
|
||||
"t-tag": "tdesign-miniprogram/tag/tag",
|
||||
"t-icon": "tdesign-miniprogram/icon/icon",
|
||||
"t-empty": "tdesign-miniprogram/empty/empty",
|
||||
"t-loading": "tdesign-miniprogram/loading/loading"
|
||||
},
|
||||
"navigationBarTitleText": "频道广场"
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<!-- 频道广场 —— 后端频道列表 + 分类筛选 -->
|
||||
<page-meta page-style="overflow: hidden;" />
|
||||
<view class="discover-page">
|
||||
|
||||
<!-- 分类筛选(横向滚动) -->
|
||||
<scroll-view scroll-x enhanced show-scrollbar="{{false}}" class="filter-bar">
|
||||
<view
|
||||
wx:for="{{categories}}"
|
||||
wx:key="id"
|
||||
class="filter-tag {{activeFilter === item.id ? 'active' : ''}}"
|
||||
bindtap="onFilter"
|
||||
data-cat="{{item.id}}"
|
||||
>
|
||||
<text>{{item.name}}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 频道网格(可滚动) -->
|
||||
<scroll-view scroll-y enhanced show-scrollbar="{{false}}" class="grid-scroll">
|
||||
|
||||
<!-- 加载中(骨架屏) -->
|
||||
<block wx:if="{{loading}}">
|
||||
<view class="grid">
|
||||
<view class="grid-item skeleton-item" wx:for="{{[1,2,3,4,5,6]}}" wx:key="*this">
|
||||
<view class="skele-circle skele-bg"></view>
|
||||
<view class="skele-line skele-bg"></view>
|
||||
<view class="skele-btn skele-bg"></view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<block wx:else>
|
||||
<!-- 空状态 -->
|
||||
<view wx:if="{{filteredDomains.length === 0}}" class="empty-wrap">
|
||||
<t-empty description="暂无频道" />
|
||||
</view>
|
||||
|
||||
<!-- 频道网格 -->
|
||||
<view class="grid" wx:else>
|
||||
<view
|
||||
wx:for="{{filteredDomains}}"
|
||||
wx:key="id"
|
||||
class="grid-item"
|
||||
>
|
||||
<!-- 封面 emoji -->
|
||||
<view class="item-icon" style="background: {{item.bgColor || '#FFE8CC'}};" bindtap="goDetail" data-id="{{item.id}}">
|
||||
<text class="icon-emoji">{{item.cover || '📻'}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 名称 -->
|
||||
<text class="item-name" bindtap="goDetail" data-id="{{item.id}}">{{item.name}}</text>
|
||||
|
||||
<!-- 类型标签 -->
|
||||
<view class="item-tag-row">
|
||||
<t-tag wx:if="{{item._isFree}}" size="small" variant="light" theme="success">免费</t-tag>
|
||||
<t-tag wx:elif="{{item._isVipOnly}}" size="small" variant="light" theme="warning">VIP专享</t-tag>
|
||||
<t-tag wx:elif="{{item._lowestPrice}}" size="small" variant="light" theme="default">¥{{item._lowestPrice.value}}/{{item._lowestPrice.label}}</t-tag>
|
||||
</view>
|
||||
|
||||
<!-- 行动按钮 -->
|
||||
<!-- 已订阅 -->
|
||||
<view wx:if="{{item._isSubscribed}}" class="sub-btn subscribed" bindtap="onAction" data-id="{{item.id}}">
|
||||
<t-icon name="check-circle" size="28rpx" color="#999" />
|
||||
<text>已订阅</text>
|
||||
</view>
|
||||
|
||||
<!-- 免费 → 收听 -->
|
||||
<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}}">
|
||||
<text>👑 VIP专享</text>
|
||||
</view>
|
||||
|
||||
<!-- 付费订阅 -->
|
||||
<view wx:else class="sub-btn paid" bindtap="onAction" data-id="{{item.id}}">
|
||||
<t-icon name="shop" size="28rpx" color="#FFF" />
|
||||
<text>订阅</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 底部VIP横幅 -->
|
||||
<view class="vip-banner tap-active" wx:if="{{!isVip}}" bindtap="goVip">
|
||||
<view class="vip-banner-icon">
|
||||
<text>👑</text>
|
||||
</view>
|
||||
<view class="vip-banner-info">
|
||||
<text class="vip-banner-title">✨ 解锁全部频道 + 免广告</text>
|
||||
<text class="vip-banner-desc">立享极致畅听体验</text>
|
||||
</view>
|
||||
<view class="vip-banner-price">
|
||||
<text>19.9元/月</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view style="height: 200rpx;"></view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 全局播放条 -->
|
||||
<global-player />
|
||||
<t-message id="t-message" />
|
||||
</view>
|
||||
@@ -0,0 +1,209 @@
|
||||
/* 频道广场样式 */
|
||||
::-webkit-scrollbar { display: none !important; width: 0 !important; height: 0 !important; }
|
||||
|
||||
.discover-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background: #F6F6F6;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 分类筛选 */
|
||||
.filter-bar {
|
||||
flex-shrink: 0;
|
||||
padding: 16rpx 32rpx;
|
||||
white-space: nowrap;
|
||||
background: #FFFFFF;
|
||||
border-bottom: 1rpx solid #F5F5F5;
|
||||
}
|
||||
.filter-tag {
|
||||
display: inline-block;
|
||||
padding: 12rpx 28rpx;
|
||||
border-radius: 999rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
margin-right: 12rpx;
|
||||
background: #F5F5F5;
|
||||
color: #666;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.filter-tag.active {
|
||||
background: #1A1A1A;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
/* 网格滚动区 */
|
||||
.grid-scroll {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
.grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 20rpx 20rpx 0;
|
||||
gap: 16rpx;
|
||||
}
|
||||
.grid-item {
|
||||
width: calc(33.33% - 12rpx);
|
||||
box-sizing: border-box;
|
||||
background: #FFFFFF;
|
||||
border-radius: 28rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 24rpx 12rpx 20rpx;
|
||||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 14rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
.icon-emoji { font-size: 44rpx; }
|
||||
|
||||
.item-name {
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
line-height: 1.3;
|
||||
margin-bottom: 10rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* 类型标签行 */
|
||||
.item-tag-row {
|
||||
margin-bottom: 14rpx;
|
||||
min-height: 36rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 行动按钮(通用容器) */
|
||||
.sub-btn {
|
||||
width: 100%;
|
||||
height: 60rpx;
|
||||
border-radius: 999rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8rpx;
|
||||
font-size: 22rpx;
|
||||
font-weight: 700;
|
||||
transition: opacity 0.2s, transform 0.15s;
|
||||
}
|
||||
.sub-btn:active { transform: scale(0.95); opacity: 0.85; }
|
||||
|
||||
/* 已订阅 */
|
||||
.sub-btn.subscribed {
|
||||
background: #F0F0F0;
|
||||
color: #999;
|
||||
}
|
||||
/* 免费收听 */
|
||||
.sub-btn.free {
|
||||
background: linear-gradient(135deg, #2ECC71, #27AE60);
|
||||
color: #FFF;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 200, 83, 0.25);
|
||||
}
|
||||
/* VIP专享 */
|
||||
.sub-btn.vip {
|
||||
background: linear-gradient(135deg, #F59E0B, #D97706);
|
||||
color: #FFF;
|
||||
box-shadow: 0 4rpx 12rpx rgba(245, 158, 11, 0.3);
|
||||
}
|
||||
/* 付费订阅 */
|
||||
.sub-btn.paid {
|
||||
background: linear-gradient(135deg, #FF9D42, #FF7832);
|
||||
color: #FFF;
|
||||
box-shadow: 0 4rpx 12rpx rgba(255, 157, 66, 0.3);
|
||||
}
|
||||
|
||||
/* ========== 骨架屏 ========== */
|
||||
@keyframes skele-shimmer {
|
||||
0% { background-position: -200% 0; }
|
||||
100% { background-position: 200% 0; }
|
||||
}
|
||||
.skele-bg {
|
||||
background: linear-gradient(90deg, #F0F0F0 25%, #E8E8E8 37%, #F0F0F0 63%);
|
||||
background-size: 400% 100%;
|
||||
animation: skele-shimmer 1.4s ease infinite;
|
||||
}
|
||||
.skeleton-item {
|
||||
background: #FFF;
|
||||
}
|
||||
.skele-circle {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
border-radius: 50%;
|
||||
margin-bottom: 14rpx;
|
||||
}
|
||||
.skele-line {
|
||||
width: 80%;
|
||||
height: 28rpx;
|
||||
border-radius: 6rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
.skele-btn {
|
||||
width: 100%;
|
||||
height: 60rpx;
|
||||
border-radius: 999rpx;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-wrap { padding: 80rpx 0; }
|
||||
|
||||
/* VIP横幅 */
|
||||
.vip-banner {
|
||||
margin: 32rpx 20rpx;
|
||||
padding: 28rpx;
|
||||
background: linear-gradient(135deg, #1F2937, #111827);
|
||||
border-radius: 32rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-shadow: 0 12rpx 32rpx rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.vip-banner-icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(251, 191, 36, 0.2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20rpx;
|
||||
flex-shrink: 0;
|
||||
font-size: 36rpx;
|
||||
}
|
||||
.vip-banner-info { flex: 1; overflow: hidden; }
|
||||
.vip-banner-title {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
color: #FDE68A;
|
||||
}
|
||||
.vip-banner-desc {
|
||||
display: block;
|
||||
font-size: 20rpx;
|
||||
color: #9CA3AF;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
.vip-banner-price {
|
||||
background: #FBBF24;
|
||||
color: #1F2937;
|
||||
padding: 12rpx 20rpx;
|
||||
border-radius: 999rpx;
|
||||
font-size: 22rpx;
|
||||
font-weight: 700;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
Reference in New Issue
Block a user