/** * 首页 — 订阅频道 + 免费频道 * * 数据来源: * - 订阅列表: POST /radio/subscription/list * - 免费频道: POST /radio/channel/freeList * * 订阅返回结构: { data: { list: [{ id, channel: { id, name, cover, Programs[] } }] } } * 免费频道结构: { data: { list: [{ id, name, cover, Programs[] }] } } (待确认类似) */ const app = getApp() const api = require('../../utils/api') 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({ data: { // 同步计算,第一帧就正确,避免闪烁 greetingSub: _computeGreeting(), statusBarHeight: app.globalData.statusBarHeight || 0, locationName: '', weather: null, dateDisplay: '', weekDay: '', subscribedData: [], freeChannels: [], isPlaying: false, isVip: false, loadingSub: true, loadingFree: true }, onShow() { const gd = app.globalData this.setData({ greetingSub: _computeGreeting(), // 每次进页随机刷新文案 dateDisplay: util.getDateDisplay(), weekDay: util.getWeekDay(), locationName: gd.locationName || '', weather: gd.weather || null, isVip: gd.isVip || false // statusBarHeight 小程序运行期间不会变化,无需重设 }) this._loadAll() this._bindEvents() }, onHide() { this._unbindEvents() }, // ===================== 数据加载 ===================== /** 并行加载订阅列表 + 免费频道 */ _loadAll() { this._refreshSubscriptions() this._loadFreeChannels() }, /** * 拉取已订阅频道列表 * 首次无数据时显示骨架屏,后续刷新静默替换(无闪烁) */ _refreshSubscriptions() { const self = this const gd = app.globalData const isFirstLoad = self.data.subscribedData.length === 0 // 只有首次加载才显示骨架屏 if (isFirstLoad) { self.setData({ loadingSub: true }) } api.getSubscriptionList({ current: 1, pageSize: 50 }) .then(function (res) { if (res.code !== 200 || !res.data) { self.setData({ subscribedData: [], loadingSub: false }) return } const subList = res.data.list || [] const subscribedData = subList.map(function (subItem) { return self._mapChannel(subItem.channel || {}, gd) }) self.setData({ subscribedData: subscribedData, isPlaying: gd.isPlaying || false, loadingSub: false }) }) .catch(function (err) { console.error('[首页] 订阅列表失败', err) self.setData({ loadingSub: false }) }) }, /** * 拉取频道列表(Section 2) * VIP 用户:全部频道;普通用户:仅免费频道 */ _loadFreeChannels() { const self = this const gd = app.globalData const isFirstLoad = self.data.freeChannels.length === 0 if (isFirstLoad) { self.setData({ loadingFree: true }) } // VIP 用户加载全量频道,普通用户仅免费频道 var apiCall = gd.isVip ? api.getChannelList({ current: 1, pageSize: 50 }) : api.getFreeChannelList({ current: 1, pageSize: 20 }) apiCall .then(function (res) { if (res.code !== 200 || !res.data) { self.setData({ freeChannels: [], loadingFree: false }) return } const list = res.data.list || [] const freeChannels = list.map(function (ch) { return { id: ch.id, name: ch.name || '未命名', cover: ch.cover || '📻', bgColor: self._genColor(ch.id), isFree: ch.isFree, isVipOnly: ch.isVipOnly } }) self.setData({ freeChannels: freeChannels, loadingFree: false }) }) .catch(function (err) { console.error('[首页] 频道加载失败', err) self.setData({ loadingFree: false }) }) }, /** * 将频道对象映射为 UI 数据结构(订阅区通用) */ _mapChannel(channel, gd) { const programs = channel.programs || [] const latest = programs.length > 0 ? programs[0] : null // cover 直接是 emoji 字符串 const cover = channel.cover || '📻' const todayContent = latest ? Object.assign({}, latest, { durationText: util.formatTime(latest.duration || 0) }) : null const isThisPlaying = !!(todayContent && gd.activeContent && gd.activeContent.id === todayContent.id) return { id: channel.id, name: channel.name || '未命名频道', description: channel.description || '', isFree: channel.isFree, isVipOnly: channel.isVipOnly, cover: cover, bgColor: this._genColor(channel.id), _todayContent: todayContent, _isThisPlaying: isThisPlaying } }, /** * 根据 id 生成稳定的暖色系颜色 */ _genColor(id) { const palette = [ '#FF9D42', '#FFB366', '#FF8C69', '#FFA07A', '#E8956D', '#D4845A', '#F4A460', '#CD853F' ] if (!id) return palette[0] var hash = 0 for (var i = 0; i < id.length; i++) { hash = (hash + id.charCodeAt(i)) % palette.length } return palette[hash] }, // ===================== 事件监听 ===================== _bindEvents() { const self = this // 播放状态变化 this._onPlayerChange = function () { const gd = app.globalData const data = self.data.subscribedData.map(function (channel) { const latest = channel._todayContent return Object.assign({}, channel, { _isThisPlaying: !!(latest && gd.activeContent && gd.activeContent.id === latest.id) }) }) self.setData({ subscribedData: data, isPlaying: gd.isPlaying || false }) } // 订阅变化 this._onSubChange = function () { self._loadAll() } // 冷启动位置/天气数据就绪(异步,可能比页面加载晚) this._onLocationWeather = function (data) { self.setData({ locationName: data.locationName || '', weather: data.weather || null }) } app.on('playerStateChange', this._onPlayerChange) app.on('subscriptionChange', this._onSubChange) app.on('locationWeatherReady', this._onLocationWeather) }, _unbindEvents() { if (this._onPlayerChange) app.off('playerStateChange', this._onPlayerChange) if (this._onSubChange) app.off('subscriptionChange', this._onSubChange) if (this._onLocationWeather) app.off('locationWeatherReady', this._onLocationWeather) }, // ===================== 用户操作 ===================== /** 点击播放/暂停 */ onPlayContent(e) { const contentId = e.currentTarget.dataset.contentId const gd = app.globalData var content = null for (var i = 0; i < this.data.subscribedData.length; i++) { var c = this.data.subscribedData[i]._todayContent if (c && c.id === contentId) { content = c; break } } if (!content) return if (gd.activeContent && gd.activeContent.id === contentId) { app.togglePlay() } else { app.playContent(content) } }, /** 跳转发现广场 */ goDiscover() { wx.switchTab({ url: '/pages/discover/index' }) }, /** 跳转 VIP 页 */ goVip() { wx.navigateTo({ url: '/pages/vip/index' }) }, /** 跳转频道详情(订阅或免费) */ goChannel(e) { const id = e.currentTarget.dataset.id wx.navigateTo({ url: '/pages/channel-detail/index?id=' + id }) }, // ===================== 工具方法 ===================== // _computeGreeting 已提至模块级,可在 data 初始化时直接调用 })