From ce91e2cbbefa1623a7400b04e0572b1b71ca551e Mon Sep 17 00:00:00 2001 From: Blizzard Date: Tue, 28 Apr 2026 10:32:19 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=A0=B7=E5=BC=8F=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.json | 8 +- app.wxss | 92 +++-- components/global-player/index.js | 28 +- components/global-player/index.wxml | 18 +- components/global-player/index.wxss | 43 +-- pages/channel-detail/index.js | 33 +- pages/channel-detail/index.json | 3 +- pages/channel-detail/index.wxml | 76 +++-- pages/channel-detail/index.wxss | 54 ++- pages/discover/index.js | 14 +- pages/discover/index.wxml | 15 +- pages/discover/index.wxss | 14 +- pages/history/index.js | 183 ++++++++-- pages/history/index.wxml | 114 +++++-- pages/history/index.wxss | 187 +++++++--- pages/index/index.js | 67 +++- pages/index/index.wxml | 66 ++-- pages/index/index.wxss | 131 +++++-- pages/player/index.js | 166 ++++++++- pages/player/index.wxml | 147 ++++---- pages/player/index.wxss | 509 +++++++++++++++------------- pages/profile/index.js | 12 + pages/profile/index.wxss | 43 +-- pages/splash/index.wxml | 4 +- pages/splash/index.wxss | 24 +- pages/subscribe/index.wxml | 12 +- pages/subscribe/index.wxss | 10 +- pages/vip/index.js | 15 +- pages/vip/index.wxml | 36 +- pages/vip/index.wxss | 19 +- project.config.json | 2 +- utils/api.js | 17 +- utils/audioManager.js | 10 +- 33 files changed, 1553 insertions(+), 619 deletions(-) diff --git a/app.json b/app.json index a4220fc..43e9755 100644 --- a/app.json +++ b/app.json @@ -12,14 +12,14 @@ ], "window": { "navigationBarBackgroundColor": "#FFFFFF", - "navigationBarTitleText": "早安电台", + "navigationBarTitleText": "全声汇", "navigationBarTextStyle": "black", - "backgroundColor": "#F6F6F6", - "backgroundTextStyle": "dark" + "backgroundColor": "#FAFAF8", + "backgroundTextStyle": "light" }, "tabBar": { "color": "#999999", - "selectedColor": "#FF9D42", + "selectedColor": "#FF5722", "backgroundColor": "#FFFFFF", "borderStyle": "white", "list": [ diff --git a/app.wxss b/app.wxss index 5ab776a..b2e523f 100644 --- a/app.wxss +++ b/app.wxss @@ -1,23 +1,23 @@ /* ============================================ - 早安电台 — 全局样式 - 品牌主色: #FF9D42 背景: #F6F6F6 + 全声汇 — 全局样式 + 主色: #FF9E6D(暖橙) 行动色: #FF5722(橙红) ============================================ */ /* TDesign 主题变量覆盖 */ page { - /* 品牌色 */ - --td-brand-color: #FF9D42; - --td-brand-color-light: rgba(255, 157, 66, 0.1); - --td-brand-color-focus: rgba(255, 157, 66, 0.2); - --td-brand-color-active: #E88A35; - --td-brand-color-disabled: rgba(255, 157, 66, 0.4); + /* 品牌色 → 暖橙 */ + --td-brand-color: #FF9E6D; + --td-brand-color-light: rgba(255, 158, 109, 0.1); + --td-brand-color-focus: rgba(255, 158, 109, 0.2); + --td-brand-color-active: #FF5722; + --td-brand-color-disabled: rgba(255, 158, 109, 0.4); - /* 成功色(森林绿) */ - --td-success-color: #2D5A27; - --td-success-color-light: rgba(45, 90, 39, 0.1); + /* 成功色 */ + --td-success-color: #10B981; + --td-success-color-light: rgba(16, 185, 129, 0.1); /* 警告色 */ - --td-warning-color: #E34D59; + --td-warning-color: #EF4444; /* 圆角 */ --td-radius-default: 16rpx; @@ -25,26 +25,42 @@ page { --td-radius-extra-large: 40rpx; --td-radius-round: 999rpx; - /* 自定义品牌变量 */ - --color-primary: #FF9D42; - --color-primary-light: rgba(255, 157, 66, 0.08); - --color-primary-bg: rgba(255, 157, 66, 0.12); - --color-success: #2D5A27; - --color-warning: #E34D59; - --color-bg-page: #F6F6F6; + /* ── 核心品牌变量 ── */ + --color-primary: #FF9E6D; + --color-primary-dark: #FF5722; + --color-primary-light: rgba(255, 158, 109, 0.08); + --color-primary-bg: rgba(255, 158, 109, 0.12); + --color-accent: #34D399; + --color-success: #10B981; + --color-warning: #EF4444; + + /* ── 页面/容器色 ── */ + --color-bg-page: #FAFAF8; --color-bg-card: #FFFFFF; --color-bg-container: #FEFEFE; + + /* ── 文字色阶 ── */ --color-text-primary: #333333; --color-text-secondary: #666666; --color-text-placeholder: #999999; --color-text-disabled: #CCCCCC; + + /* ── 边框/分割 ── */ --color-border: #EEEEEE; --color-border-light: #F5F5F5; + /* ── 领域色 ── */ + --domain-tech: #3B82F6; + --domain-news: #F97316; + --domain-realtime: #EF4444; + --domain-psych: #10B981; + --domain-money: #F59E0B; + --domain-edu: #8B5CF6; + /* 页面背景 */ background-color: var(--color-bg-page); - font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', - 'PingFang SC', 'Microsoft YaHei', sans-serif; + font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', + 'Helvetica Neue', sans-serif; font-size: 28rpx; color: var(--color-text-primary); line-height: 1.6; @@ -176,6 +192,23 @@ page { padding-top: env(safe-area-inset-top); } +/* 播放栏底部安全占位(全局播放栏高度 128rpx + 底部安全区 + 20rpx 间距) */ +.player-bottom-spacer { + height: calc(148rpx + env(safe-area-inset-bottom)); + flex-shrink: 0; +} + +/* 首字母彩色圆形(频道默认封面) */ +.initial-avatar { + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + font-weight: 800; + color: #FFF; + text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.15); +} + /* ========== 全局隐藏滚动条 ========== */ page { -webkit-overflow-scrolling: touch; @@ -237,3 +270,20 @@ scroll-view::-webkit-scrollbar { .anim-pulse { animation: pulse 1.5s ease-in-out infinite; } + +/* ========== 暗色模式变量覆盖 ========== */ +@media (prefers-color-scheme: dark) { + page { + --color-primary: #FFB088; + --color-primary-dark: #FF5722; + --color-primary-light: rgba(255, 176, 136, 0.12); + --color-bg-page: #121212; + --color-bg-card: #1E1E1E; + --color-text-primary: #E0E0E0; + --color-text-secondary: #999; + --color-text-placeholder: #666; + --color-text-disabled: #555; + --color-border: rgba(255, 255, 255, 0.08); + --color-success: #2ECC71; + } +} diff --git a/components/global-player/index.js b/components/global-player/index.js index 5d0c4b4..0c2d6ed 100644 --- a/components/global-player/index.js +++ b/components/global-player/index.js @@ -9,8 +9,10 @@ Component({ activeContent: null, isPlaying: false, progressPercent: 0, + currentTime: 0, + duration: 0, show: false, - channelIcon: '📻' // 直接存储 emoji 字符(来自频道 cover 字段) + channelIcon: '📻' }, lifetimes: { @@ -31,8 +33,11 @@ Component({ // 监听时间更新 this._onTimeUpdate = (data) => { + if (this._isSeeking) return if (data.duration > 0) { this.setData({ + currentTime: Math.floor(data.currentTime), + duration: Math.floor(data.duration), progressPercent: Math.min((data.currentTime / data.duration) * 100, 100) }) } @@ -62,6 +67,8 @@ Component({ activeContent: gd.activeContent, isPlaying: gd.isPlaying, show: !!gd.activeContent, + currentTime: Math.floor(gd.currentTime || 0), + duration: Math.floor(gd.duration || 0), progressPercent: gd.duration > 0 ? Math.min((gd.currentTime / gd.duration) * 100, 100) : 0 @@ -91,8 +98,25 @@ Component({ */ goToPlayer() { wx.navigateTo({ - url: '/pages/player/index' + url: '/pages/player/index', + routeType: 'none' }) + }, + + /** + * 进度条拖动中 + */ + onProgressChanging(e) { + this._isSeeking = true + this.setData({ currentTime: e.detail.value }) + }, + + /** + * 进度条拖动结束 + */ + onProgressChange(e) { + this._isSeeking = false + app.seekTo(e.detail.value) } } }) diff --git a/components/global-player/index.wxml b/components/global-player/index.wxml index d57b26a..89a726b 100644 --- a/components/global-player/index.wxml +++ b/components/global-player/index.wxml @@ -37,8 +37,18 @@ - - - - + + diff --git a/components/global-player/index.wxss b/components/global-player/index.wxss index 6af8130..ff96b24 100644 --- a/components/global-player/index.wxss +++ b/components/global-player/index.wxss @@ -6,16 +6,15 @@ left: 0; right: 0; height: 128rpx; - /* 暖奶油白 + 轻磨砂,与播放器主题呼应但不突兀 */ - background: rgba(255, 251, 243, 0.97); + background: rgba(255, 255, 255, 0.97); backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px); display: flex; align-items: center; padding: 0 28rpx; z-index: 999; - border-top: 1rpx solid rgba(255, 157, 66, 0.12); - box-shadow: 0 -8rpx 32rpx rgba(200, 120, 40, 0.08); + border-top: 1rpx solid rgba(0, 0, 0, 0.06); + box-shadow: 0 -4rpx 24rpx rgba(0, 0, 0, 0.05); transform: translateY(100%); transition: transform 0.32s cubic-bezier(0.34, 1.2, 0.64, 1); } @@ -38,12 +37,12 @@ width: 80rpx; height: 80rpx; border-radius: 50%; - background: linear-gradient(135deg, #FF9D42, #FFB366); + background: linear-gradient(135deg, #FF9E6D, #FF9E6D); display: flex; align-items: center; justify-content: center; position: relative; - box-shadow: 0 4rpx 16rpx rgba(255, 157, 66, 0.35); + box-shadow: 0 4rpx 16rpx rgba(255, 158, 109, 0.35); } .disc-spin { animation: disc-rotate 8s linear infinite; @@ -75,7 +74,7 @@ position: absolute; inset: -6rpx; border-radius: 50%; - border: 3rpx solid rgba(255, 157, 66, 0.35); + border: 3rpx solid rgba(255, 158, 109, 0.35); animation: pulse-ring 1.8s ease-out infinite; } @keyframes pulse-ring { @@ -93,7 +92,7 @@ display: block; font-size: 26rpx; font-weight: 700; - color: #2C1A08; + color: var(--color-text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -106,7 +105,7 @@ } .subtitle { font-size: 20rpx; - color: rgba(180, 120, 60, 0.7); + color: var(--color-text-secondary); font-weight: 500; } @@ -121,7 +120,7 @@ .mw-bar { width: 4rpx; height: 12rpx; - background: #FF9D42; + background: var(--color-primary); border-radius: 2rpx; animation: mw-dance 0.7s ease-in-out infinite alternate; } @@ -135,12 +134,12 @@ width: 72rpx; height: 72rpx; border-radius: 50%; - background: #FF9D42; + background: var(--color-primary); display: flex; align-items: center; justify-content: center; flex-shrink: 0; - box-shadow: 0 6rpx 20rpx rgba(255, 157, 66, 0.4); + box-shadow: 0 6rpx 20rpx rgba(255, 158, 109, 0.35); transition: transform 0.15s; } .ctrl-btn:active { @@ -168,18 +167,12 @@ border-radius: 3rpx; } -/* ── 底部进度线 ── */ -.progress-track { +/* ── 底部进度 slider ── */ +.bar-slider { position: absolute; - bottom: 0; - left: 0; - right: 0; - height: 3rpx; - background: rgba(255, 157, 66, 0.1); -} -.progress-fill { - height: 100%; - background: linear-gradient(to right, #FF9D42, #FFB366); - transition: width 0.5s linear; - border-radius: 0 2rpx 2rpx 0; + bottom: -4rpx; + left: -16rpx; + right: -16rpx; + margin: 0; + padding: 0; } diff --git a/pages/channel-detail/index.js b/pages/channel-detail/index.js index bac8a68..6d0256d 100644 --- a/pages/channel-detail/index.js +++ b/pages/channel-detail/index.js @@ -33,14 +33,17 @@ Page({ onShow() { this._loadPrograms() this._onPlayerChange = () => this._updatePlayState() - this._onSubChange = () => this._loadPrograms() + this._onSubChange = () => this._loadChannelDetail() + this._onVipChange = () => this._loadChannelDetail() app.on('playerStateChange', this._onPlayerChange) app.on('subscriptionChange', this._onSubChange) + app.on('vipChange', this._onVipChange) }, onHide() { if (this._onPlayerChange) app.off('playerStateChange', this._onPlayerChange) if (this._onSubChange) app.off('subscriptionChange', this._onSubChange) + if (this._onVipChange) app.off('vipChange', this._onVipChange) }, /** @@ -78,6 +81,8 @@ Page({ isVip: app.globalData.isVip, canPlay }) + // 详情加载后刷新节目列表(更新 _isLocked 状态) + self._loadPrograms() } }).catch(function (err) { console.error('[ChannelDetail] 加载频道详情失败:', err) @@ -105,7 +110,7 @@ Page({ return Object.assign({}, item, { _displayIndex: String(total - idx).padStart(2, '0'), _dateDot: item.createdAt ? item.createdAt.substring(0, 10).replace(/-/g, '.') : '', - durationText: util.formatTime(item.duration || 0), + durationText: item.duration > 0 ? util.formatTime(item.duration) : '', _isThisPlaying: gd.activeContent && gd.activeContent.id === item.id, _isLocked: !canPlay // 所有集全部锁定,没有试听 }) @@ -195,6 +200,11 @@ Page({ }, goFirstProgram() { + // 权限保护:不可播放时引导到订阅/VIP + if (!this.data.canPlay) { + this.onSubscribe() + return + } var list = this.data.domainContents if (list && list.length > 0) { var first = list[0] @@ -222,5 +232,24 @@ Page({ goBack() { wx.navigateBack() + }, + + // ===================== 分享钩子 ===================== + onShareAppMessage() { + const domain = this.data.domain || {} + return { + title: domain.name ? '听全声汇频道:' + domain.name : '全声汇 - 精选频道', + path: '/pages/channel-detail/index?id=' + this._domainId, + imageUrl: '' + } + }, + + onShareTimeline() { + const domain = this.data.domain || {} + return { + title: domain.name ? '听全声汇频道:' + domain.name : '全声汇 - 精选频道', + query: 'id=' + this._domainId, + imageUrl: '' + } } }) diff --git a/pages/channel-detail/index.json b/pages/channel-detail/index.json index 735ae50..ddbf84c 100644 --- a/pages/channel-detail/index.json +++ b/pages/channel-detail/index.json @@ -1,7 +1,8 @@ { "usingComponents": { "global-player": "/components/global-player/index", - "t-message": "tdesign-miniprogram/message/message" + "t-message": "tdesign-miniprogram/message/message", + "t-icon": "tdesign-miniprogram/icon/icon" }, "navigationBarTitleText": "频道详情" } \ No newline at end of file diff --git a/pages/channel-detail/index.wxml b/pages/channel-detail/index.wxml index 2df7e84..2e7a458 100644 --- a/pages/channel-detail/index.wxml +++ b/pages/channel-detail/index.wxml @@ -2,20 +2,34 @@ - + + {{domain.cover || domain.icon || '📻'}} {{domain.name}} - {{domain.tag || domain.description || ''}} + {{domain.tag || ''}} + + {{domain.description}} + + + {{domain.subscriberCount || 0}} 人已订阅 + · + {{domainContents.length || 0}} 期节目 + - + + + + 👑 VIP 会员畅享 🎁 永久免费频道 @@ -31,33 +45,53 @@ 🎁 永久免费 - + + + + 👑 VIP专享 - + + + + ⏰ 订阅已到期 - + + + + 已于 {{expiredAt}} 到期 - + + + + @@ -123,8 +157,10 @@ {{item.title}} {{item._dateDot}} - · - {{item.durationText}} + + · + {{item.durationText}} + @@ -141,7 +177,7 @@ - + diff --git a/pages/channel-detail/index.wxss b/pages/channel-detail/index.wxss index 89e05f8..f7a4697 100644 --- a/pages/channel-detail/index.wxss +++ b/pages/channel-detail/index.wxss @@ -56,8 +56,58 @@ font-size: 26rpx; font-weight: 500; color: rgba(255, 255, 255, 0.8); - margin-bottom: 40rpx; + margin-bottom: 12rpx; } +.hero-desc { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + font-size: 24rpx; + color: rgba(255, 255, 255, 0.65); + line-height: 1.6; + text-align: center; + padding: 0 48rpx; + margin-bottom: 12rpx; +} +.hero-stats { + display: flex; + align-items: center; + gap: 10rpx; + margin-bottom: 32rpx; +} +.hero-stat { + font-size: 22rpx; + color: rgba(255, 255, 255, 0.55); + font-weight: 500; +} +.hero-stat-dot { + font-size: 22rpx; + color: rgba(255, 255, 255, 0.35); +} + +/* 按钮区 */ +.hero-action-row { + display: flex; + align-items: center; + gap: 20rpx; + z-index: 10; +} +.hero-share-inline-btn { + width: 72rpx; + height: 72rpx; + border-radius: 50%; + background: rgba(255, 255, 255, 0.2); + backdrop-filter: blur(10px); + border: 2rpx solid rgba(255, 255, 255, 0.3); + display: flex; + align-items: center; + justify-content: center; + padding: 0; + margin: 0; +} +.hero-share-inline-btn::after { border: none; } +.hero-share-inline-btn:active { transform: scale(0.92); } .hero-sub-btn { padding: 16rpx 56rpx; @@ -121,7 +171,7 @@ /* 按钮变体 */ .hero-sub-btn.free-btn { background: #2ECC71; color: #FFF; box-shadow: 0 8rpx 24rpx rgba(46,204,113,0.3); } .hero-sub-btn.vip-btn { background: linear-gradient(135deg, #FBBF24, #D97706); color: #1F2937; box-shadow: 0 8rpx 24rpx rgba(251,191,36,0.35); } -.hero-sub-btn.renew-btn { background: linear-gradient(135deg, #FF9D42, #FF7832); color: #FFF; box-shadow: 0 8rpx 24rpx rgba(255,120,50,0.35); } +.hero-sub-btn.renew-btn { background: linear-gradient(135deg, #FF9E6D, #FF7832); color: #FFF; box-shadow: 0 8rpx 24rpx rgba(255,120,50,0.35); } /* 波浪 */ .hero-wave { diff --git a/pages/discover/index.js b/pages/discover/index.js index 5fd3608..670c04c 100644 --- a/pages/discover/index.js +++ b/pages/discover/index.js @@ -87,12 +87,24 @@ Page({ lowestPrice = { label: prices[0].label, value: (prices[0].value / 100).toFixed(2) } } } + // 按钮类型:用于 WXML 条件渲染 + // VIP 用户优先判断 —— 可播放全部频道 + var btnType = 'subscribe' + if (gd.isVip) { + btnType = isVipOnly ? 'vip-listen' : 'listen' + } else if (isVipOnly) { + btnType = 'vip-only' + } else if (isFree || isSubscribed) { + btnType = 'listen' + } + return Object.assign({}, ch, { _isSubscribed: isSubscribed, _isFree: isFree, _isVipOnly: isVipOnly, _lowestPrice: lowestPrice, - _canPlay: canPlay + _canPlay: canPlay, + _btnType: btnType }) }) diff --git a/pages/discover/index.wxml b/pages/discover/index.wxml index b069575..6ad1b42 100644 --- a/pages/discover/index.wxml +++ b/pages/discover/index.wxml @@ -58,13 +58,18 @@ - - + + + 👑 VIP畅听 + + + + ▶ 收听 - - + + 👑 VIP专享 @@ -90,7 +95,7 @@ - + diff --git a/pages/discover/index.wxss b/pages/discover/index.wxss index 10eefc4..0ac3877 100644 --- a/pages/discover/index.wxss +++ b/pages/discover/index.wxss @@ -5,7 +5,7 @@ display: flex; flex-direction: column; height: 100vh; - background: #F6F6F6; + background: var(--color-bg-page); overflow: hidden; } @@ -15,7 +15,7 @@ padding: 16rpx 32rpx; white-space: nowrap; background: #FFFFFF; - border-bottom: 1rpx solid #F5F5F5; + border-bottom: 1rpx solid var(--color-border); } .filter-tag { display: inline-block; @@ -29,7 +29,7 @@ transition: all 0.2s; } .filter-tag.active { - background: #1A1A1A; + background: var(--color-primary-dark); color: #FFF; } @@ -124,10 +124,16 @@ } /* 付费订阅 */ .sub-btn.paid { - background: linear-gradient(135deg, #FF9D42, #FF7832); + background: linear-gradient(135deg, #FF9E6D, #FF7832); color: #FFF; box-shadow: 0 4rpx 12rpx rgba(255, 157, 66, 0.3); } +/* VIP畅听(VIP用户看到VIP专享频道) */ +.sub-btn.vip-listen { + background: linear-gradient(135deg, #FBBF24, #D97706); + color: #FFF; + box-shadow: 0 4rpx 12rpx rgba(251, 191, 36, 0.3); +} /* ========== 骨架屏 ========== */ @keyframes skele-shimmer { diff --git a/pages/history/index.js b/pages/history/index.js index f5ed5d4..8de4b07 100644 --- a/pages/history/index.js +++ b/pages/history/index.js @@ -7,12 +7,16 @@ const util = require('../../utils/util') Page({ data: { - tab: 'history', // 'history' | 'favorite' + tab: 'history', historyList: [], isPlaying: false, - loading: true + loading: true, + channelFilters: [], + selectedChannel: '全部' }, + _allHistoryList: [], + onShow() { const self = this const gd = app.globalData @@ -40,15 +44,18 @@ Page({ setTab(e) { const tab = e.currentTarget.dataset.val if (tab === this.data.tab) return - this.setData({ tab, historyList: [], loading: true }) + this.setData({ tab, historyList: [], loading: true, channelFilters: [], selectedChannel: '全部' }) + this._allHistoryList = [] this._refresh() }, _refresh() { if (this.data.tab === 'history') { this._loadHistory() - } else { + } else if (this.data.tab === 'favorite') { this._loadFavorites() + } else { + this._loadLikes() } }, @@ -59,31 +66,32 @@ Page({ api.getHistoryList({ current: 1, pageSize: 30 }).then(function (res) { if (res.code === 200 && res.data) { var list = (res.data.list || []).map(function (item) { - // program 嵌套在 item.program 下 var program = item.program || {} - // channel 可能为 null,channel cover 在 program.cover - var channel = program.channel || {} + var channel = program.channel || null + // 历史用 item.duration(记录收听时长),优先级高于 program.duration + var dur = item.duration || program.duration || 0 + var bgColors = ['#FFE8CC', '#FFF0E0', '#FFE4D6', '#FFF3DC', '#FFE9F0'] + var cid = program.channelId || '' + var colorIdx = cid ? (cid.charCodeAt(cid.length - 1) % bgColors.length) : 0 return { - // 播放所需字段 id: program.id, title: program.title || '未知节目', channelId: program.channelId || '', content: program.content || '', audioId: program.audioId || '', - // 用历史记录的 duration(program.duration 可能是 0) - duration: item.duration || program.duration || 0, - // 显示字段 - _domainName: channel.name || '', - _icon: program.cover || channel.cover || '📻', - _bgColor: '#FFE8CC', + duration: dur, + _domainName: channel ? (channel.name || '') : '', + _icon: program.cover || (channel && channel.cover) || '📻', + _bgColor: bgColors[colorIdx], _friendlyDate: util.getFriendlyDate(item.createdAtStr ? item.createdAtStr.substring(0, 10) : ''), - durationText: util.formatTime(item.duration || 0), - // 播放进度 + durationText: dur > 0 ? util.formatTime(dur) : '', _progress: item.progress || 0, _isThisPlaying: gd.activeContent && gd.activeContent.id === program.id } }) self.setData({ historyList: list, isPlaying: gd.isPlaying, loading: false }) + self._allHistoryList = list + self._extractChannelFilters(list) } else { self.setData({ historyList: [], loading: false }) } @@ -100,24 +108,35 @@ Page({ 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 || {} + // program 嵌套在 item.program 下,channel 可能为 null + var program = item.program || {} + var channel = program.channel || null + var dur = program.duration || 0 + // 根据 channelId 末位生成固定暖色,增加视觉区分 + var bgColors = ['#FFE8CC', '#FFF0E0', '#FFE4D6', '#FFF3DC', '#FFE9F0'] + var cid = program.channelId || '' + var colorIdx = cid ? (cid.charCodeAt(cid.length - 1) % bgColors.length) : 0 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', + duration: dur, + // channel 为 null 时不显示频道名 + _domainName: channel ? (channel.name || '') : '', + _icon: program.cover || (channel && channel.cover) || '📻', + _bgColor: bgColors[colorIdx], + // 收藏时间用 item.createdAtStr _friendlyDate: util.getFriendlyDate(item.createdAtStr ? item.createdAtStr.substring(0, 10) : ''), - durationText: util.formatTime(program.duration || 0), + // duration 为 0 则不显示时长 + durationText: dur > 0 ? util.formatTime(dur) : '', _isThisPlaying: gd.activeContent && gd.activeContent.id === program.id } }) self.setData({ historyList: list, isPlaying: gd.isPlaying, loading: false }) + self._allHistoryList = list + self._extractChannelFilters(list) } else { self.setData({ historyList: [], loading: false }) } @@ -127,6 +146,53 @@ Page({ }) }, + _loadLikes() { + const self = this + const gd = app.globalData + + api.getLikeList({ current: 1, pageSize: 30 }).then(function (res) { + if (res.code === 200 && res.data) { + var list = (res.data.list || []).map(function (item) { + // RadioLike → program → channel (可能为 null) + var program = item.program || {} + var channel = program.channel || null + // duration 为 0 时不显示时长 + var dur = program.duration || 0 + // 从 channelId 生成一个固定暖色背景,增加视觉区分 + var bgColors = ['#FFE8CC', '#FFF0E0', '#FFE4D6', '#FFF3DC', '#FFE9F0'] + var cid = program.channelId || '' + var colorIdx = cid ? (cid.charCodeAt(cid.length - 1) % bgColors.length) : 0 + return { + id: program.id, + title: program.title || '未知节目', + channelId: program.channelId || '', + content: program.content || '', + audioId: program.audioId || '', + duration: dur, + // channel 为 null 时频道名不显示 + _domainName: channel ? (channel.name || '') : '', + _icon: program.cover || (channel && channel.cover) || '📻', + _bgColor: bgColors[colorIdx], + // 显示"点赞时间",用 item.createdAtStr(点赞时间) + _friendlyDate: util.getFriendlyDate(item.createdAtStr ? item.createdAtStr.substring(0, 10) : ''), + // duration 为 0 则不显示时长,避免展示 "00:00" + durationText: dur > 0 ? util.formatTime(dur) : '', + _isLiked: true, + _isThisPlaying: gd.activeContent && gd.activeContent.id === program.id + } + }) + self.setData({ historyList: list, isPlaying: gd.isPlaying, loading: false }) + self._allHistoryList = list + self._extractChannelFilters(list) + } else { + self.setData({ historyList: [], loading: false }) + } + }).catch(function (err) { + console.error('[History] 加载点赞失败:', err) + self.setData({ loading: false }) + }) + }, + _updatePlayState() { var gd = app.globalData var list = this.data.historyList.map(function (item) { @@ -137,6 +203,32 @@ Page({ this.setData({ historyList: list, isPlaying: gd.isPlaying }) }, + _extractChannelFilters(list) { + var names = {} + list.forEach(function (item) { + if (item._domainName) names[item._domainName] = true + }) + var filters = ['全部'].concat(Object.keys(names).sort()) + this.setData({ channelFilters: filters }) + }, + + onChannelFilter(e) { + var name = e.currentTarget.dataset.name + this.setData({ selectedChannel: name }) + this._applyChannelFilter(name) + }, + + _applyChannelFilter(name) { + if (name === '全部') { + this.setData({ historyList: this._allHistoryList }) + } else { + var filtered = this._allHistoryList.filter(function (item) { + return item._domainName === name + }) + this.setData({ historyList: filtered }) + } + }, + onPlay(e) { const id = e.currentTarget.dataset.id const gd = app.globalData @@ -175,8 +267,30 @@ Page({ const id = e.currentTarget.dataset.id const tab = this.data.tab const self = this - const label = tab === 'history' ? '历史' : '收藏' + // 赞过 tab 点击爱心可取消点赞 + if (tab === 'like') { + wx.showModal({ + title: '取消点赞', + content: '确定取消对这个节目的点赞吗?', + success(res) { + if (!res.confirm) return + api.toggleLike(id).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' }) + }) + } + }) + return + } + + const label = tab === 'history' ? '历史' : '收藏' wx.showModal({ title: '删除' + label, content: '确定删除这条' + label + '吗?', @@ -188,7 +302,6 @@ Page({ 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 { @@ -204,15 +317,27 @@ Page({ onClear() { const self = this const tab = this.data.tab - const label = tab === 'history' ? '收听历史' : '全部收藏' + var label = '' + var fn = null + + if (tab === 'history') { + label = '收听历史' + fn = api.deleteAllHistory() + } else if (tab === 'favorite') { + label = '全部收藏' + fn = api.removeAllFavorites() + } else if (tab === 'like') { + label = '全部点赞' + fn = api.removeAllLikes() + } + + if (!fn) return + wx.showModal({ title: '清空' + label, content: '确定要清空所有' + label + '吗?', success(res) { if (!res.confirm) return - const fn = tab === 'history' - ? api.deleteAllHistory() - : api.removeAllFavorites() fn.then(function (r) { if (r.code === 200) { self.setData({ historyList: [] }) diff --git a/pages/history/index.wxml b/pages/history/index.wxml index f356869..4576948 100644 --- a/pages/history/index.wxml +++ b/pages/history/index.wxml @@ -1,4 +1,5 @@ + @@ -12,7 +13,12 @@ 收藏 + + 赞过 + + + @@ -27,48 +33,81 @@ - - + + + + {{item}} + + - - - - - - - - + + + + + + + + + + + + + - - 🎧 - 还没有收听记录 - 去发现频道,开始你的第一段收听 + + + 🎧 + + 拓展专属听觉边界 + 海量精彩节目频道,好声音都在这里 + + + 去发现页探索 + - 去发现频道 + 开始探索 - 回到首页 + 回到主页 - 🔖 - 还没有收藏内容 - 听到喜欢的节目,点击 ♡ 收藏它 + + + 🔖 + + 打造个人专属收藏 + 好声音不怕遗忘,收集你的听觉库 + + 🔖 + 播放页点击书签图标,即可加入 + - 订阅感兴趣的频道 + 去发现好声音 - + 👑 开通会员 @@ -80,6 +119,25 @@ + + + + + ♥️ + + 留下与声音共鸣的印记 + 不吝啬你的赞扬,这是最大的鼓励 + + + 播放页点击心形按钮,点亮爱心 + + + + 去寻找心动 + + + + - + - + + + + @@ -133,8 +196,9 @@ - - + + + diff --git a/pages/history/index.wxss b/pages/history/index.wxss index 44c30f5..c897e4f 100644 --- a/pages/history/index.wxss +++ b/pages/history/index.wxss @@ -1,8 +1,42 @@ /* 收听历史 / 收藏 */ +::-webkit-scrollbar { display: none !important; width: 0 !important; height: 0 !important; } .history-page { - min-height: 100vh; - background: #F7F3EE; + display: flex; + flex-direction: column; + height: 100vh; + background: var(--color-bg-page); + overflow: hidden; +} + +/* 列表滚动区域 */ +.list-scroll { + flex: 1; + overflow: hidden; +} + +/* 频道筛选栏 */ +.channel-filter-bar { + flex-shrink: 0; + white-space: nowrap; + padding: 12rpx 32rpx; + background: #FFF; + border-bottom: 1rpx solid var(--color-border); +} +.ch-filter-tag { + display: inline-block; + padding: 8rpx 24rpx; + margin-right: 12rpx; + border-radius: 999rpx; + font-size: 24rpx; + font-weight: 500; + color: #999; + background: #F5F5F5; + transition: all 0.2s; +} +.ch-filter-tag.active { + color: #FFF; + background: var(--color-primary); } /* ── Tab 头部 ── */ @@ -12,7 +46,7 @@ justify-content: space-between; padding: 0 32rpx; background: #FFFFFF; - border-bottom: 1rpx solid #F0EAE2; + border-bottom: 1rpx solid var(--color-border); } .filter-tabs { display: flex; @@ -34,18 +68,36 @@ transition: color 0.2s; } .tab-item.active .tab-text { - color: #2C1A08; + color: var(--color-primary-dark); } .tab-underline { height: 4rpx; width: 0; - background: #FF9D42; + background: var(--color-primary); border-radius: 2rpx; transition: width 0.25s; } .tab-item.active .tab-underline { width: 100%; } +/* 赞过 tab 用粉红色下划线 */ +.tab-item.active .tab-underline-like { + background: #FF4D6D; +} + +/* 赞过 tab 取消点赞心形按钮 */ +.like-del-btn { + width: 52rpx; + height: 52rpx; + display: flex; + align-items: center; + justify-content: center; +} +.like-heart-icon { + font-size: 36rpx; + color: #FF4D6D; + line-height: 1; +} /* 清空按钮 */ .clear-btn { @@ -79,7 +131,7 @@ width: 88rpx; height: 88rpx; border-radius: 20rpx; - background: linear-gradient(90deg, #F0EAE2 0%, #E8DFD5 50%, #F0EAE2 100%); + background: linear-gradient(90deg, #EEEEEE 0%, #E8E8E8 50%, #EEEEEE 100%); background-size: 200% 100%; animation: shimmer 1.4s infinite; flex-shrink: 0; @@ -92,7 +144,7 @@ } .sk-line { height: 18rpx; - background: linear-gradient(90deg, #F0EAE2 0%, #E8DFD5 50%, #F0EAE2 100%); + background: linear-gradient(90deg, #EEEEEE 0%, #E8E8E8 50%, #EEEEEE 100%); background-size: 200% 100%; animation: shimmer 1.4s infinite; border-radius: 9rpx; @@ -105,32 +157,86 @@ 100% { background-position: -200% 0; } } -/* ── 空状态 ── */ +/* ── 空状态优化 ── */ .empty-state { display: flex; flex-direction: column; align-items: center; - padding: 80rpx 48rpx 48rpx; + padding: 100rpx 48rpx 64rpx; + animation: fade-up 0.4s ease-out; +} +@keyframes fade-up { + 0% { opacity: 0; transform: translateY(20rpx); } + 100% { opacity: 1; transform: translateY(0); } +} + +.empty-icon-wrap { + position: relative; + width: 200rpx; + height: 200rpx; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 40rpx; +} +.empty-halo { + position: absolute; + width: 100%; + height: 100%; + border-radius: 50%; + background: radial-gradient(circle, rgba(255,157,66,0.15) 0%, transparent 60%); + animation: pulse-halo 2.5s infinite alternate ease-in-out; +} +.favored-halo { background: radial-gradient(circle, rgba(103,194,58,0.15) 0%, transparent 60%); } +.liked-halo { background: radial-gradient(circle, rgba(255,77,109,0.15) 0%, transparent 60%); } + +@keyframes pulse-halo { + 0% { transform: scale(0.9); opacity: 0.6; } + 100% { transform: scale(1.3); opacity: 1; } } .empty-icon { - font-size: 96rpx; - margin-bottom: 32rpx; - filter: drop-shadow(0 8rpx 16rpx rgba(255,157,66,0.2)); + font-size: 100rpx; + position: relative; + z-index: 2; + filter: drop-shadow(0 12rpx 20rpx rgba(0,0,0,0.08)); } + .empty-title { - font-size: 36rpx; - font-weight: 700; - color: #2C1A08; + font-size: 38rpx; + font-weight: 800; + color: var(--color-text-primary); margin-bottom: 16rpx; font-family: 'PingFang SC', sans-serif; + letter-spacing: 1rpx; } .empty-desc { font-size: 26rpx; - color: #B0A090; + color: var(--color-text-secondary); text-align: center; - line-height: 1.6; - margin-bottom: 48rpx; + margin-bottom: 32rpx; } + +.empty-guide-badge { + display: flex; + align-items: center; + gap: 12rpx; + padding: 12rpx 32rpx; + background: rgba(255,157,66,0.1); + border-radius: 40rpx; + margin-bottom: 64rpx; +} +.guide-dot { width: 10rpx; height: 10rpx; border-radius: 50%; background: #FF9E6D; } +.guide-icon { font-size: 24rpx; margin-top: -2rpx; color: rgba(0,0,0,0.4); } +.guide-text { + font-size: 24rpx; + color: var(--color-primary); + font-weight: 500; +} +.warning-badge { background: rgba(5,150,105,0.08); } +.warning-badge .guide-text { color: #059669; } +.danger-badge { background: rgba(255,77,109,0.08); } +.danger-badge .guide-text { color: #E11D48; } + .empty-actions { display: flex; flex-direction: column; @@ -138,8 +244,13 @@ width: 100%; max-width: 480rpx; } +.empty-upsell { + margin-top: 48rpx; + box-sizing: border-box; +} + .btn-primary { - background: linear-gradient(135deg, #FF9D42, #E07020); + background: linear-gradient(135deg, var(--color-primary), #FF5722); border-radius: 48rpx; padding: 28rpx 0; display: flex; @@ -154,7 +265,7 @@ letter-spacing: 1rpx; } .btn-ghost { - border: 2rpx solid #E8DFD5; + border: 2rpx solid var(--color-border); border-radius: 48rpx; padding: 26rpx 0; display: flex; @@ -165,7 +276,7 @@ .btn-ghost-text { font-size: 28rpx; font-weight: 600; - color: #8C7B6A; + color: var(--color-text-secondary); } /* VIP upsell 卡片 */ @@ -192,14 +303,14 @@ .upsell-title { font-size: 28rpx; font-weight: 700; - color: #C07000; + color: #B45309; } .upsell-desc { font-size: 22rpx; - color: #C09040; + color: #D97706; } .upsell-btn { - background: #FF9D42; + background: #FF9E6D; border-radius: 28rpx; padding: 12rpx 28rpx; } @@ -217,10 +328,10 @@ margin-bottom: 16rpx; background: #FFFFFF; border-radius: 20rpx; - box-shadow: 0 2rpx 12rpx rgba(44,26,8,0.04); + box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04); } .history-item:active { - box-shadow: 0 4rpx 20rpx rgba(44,26,8,0.08); + box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08); transform: scale(0.99); } @@ -244,7 +355,7 @@ display: block; font-size: 20rpx; font-weight: 700; - color: #FF9D42; + color: #FF9E6D; letter-spacing: 1rpx; margin-bottom: 6rpx; text-transform: uppercase; @@ -253,13 +364,13 @@ display: block; font-size: 28rpx; font-weight: 700; - color: #2C1A08; + color: var(--color-text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: 'PingFang SC', sans-serif; } -.h-title.text-primary { color: #FF9D42; } +.h-title.text-primary { color: #FF9E6D; } .h-meta-row { display: flex; align-items: center; @@ -269,21 +380,21 @@ .h-meta { display: inline; font-size: 22rpx; - color: #C8BAAA; + color: var(--color-text-placeholder); font-weight: 500; } /* 播放进度条 */ .h-progress-wrap { width: 100%; height: 4rpx; - background: #F0EAE2; + background: var(--color-border); border-radius: 2rpx; margin-top: 12rpx; overflow: hidden; } .h-progress-bar { height: 100%; - background: linear-gradient(90deg, #FF9D42, #E07020); + background: linear-gradient(90deg, var(--color-primary), #FF5722); border-radius: 2rpx; max-width: 100%; } @@ -305,7 +416,7 @@ .bar { width: 6rpx; border-radius: 4rpx; - background: #FF9D42; + background: #FF9E6D; animation: bounce 0.6s ease-in-out infinite; } .bar-1 { height: 24rpx; animation-delay: 0s; } @@ -320,7 +431,7 @@ width: 56rpx; height: 56rpx; border-radius: 50%; - background: #FFF4E8; + background: #FFF7ED; display: flex; align-items: center; justify-content: center; @@ -328,7 +439,7 @@ } .play-mini-icon { font-size: 22rpx; - color: #FF9D42; + color: #FF9E6D; padding-left: 4rpx; } @@ -393,10 +504,10 @@ .icon-trash-sm .trash-handle, .icon-trash-sm .trash-lid-bar, .icon-trash-sm .trash-stripe { - border-color: #D0C8C0; - background: #D0C8C0; + border-color: #CCCCCC; + background: #CCCCCC; } .icon-trash-sm .trash-body { - border-color: #D0C8C0; + border-color: #CCCCCC; background: transparent; } diff --git a/pages/index/index.js b/pages/index/index.js index 9d06597..9cea7be 100644 --- a/pages/index/index.js +++ b/pages/index/index.js @@ -148,10 +148,13 @@ Page({ const list = res.data.list || [] const freeChannels = list.map(function (ch) { + var cover = ch.cover || '📻' return { id: ch.id, name: ch.name || '未命名', - cover: ch.cover || '📻', + cover: cover, + _isDefaultCover: cover === '📻', + _initial: (ch.name || '频').substring(0, 1), bgColor: self._genColor(ch.id), isFree: ch.isFree, isVipOnly: ch.isVipOnly @@ -172,8 +175,8 @@ Page({ _mapChannel(channel, gd) { const programs = channel.programs || [] const latest = programs.length > 0 ? programs[0] : null - // cover 直接是 emoji 字符串 const cover = channel.cover || '📻' + const isDefaultCover = cover === '📻' const todayContent = latest ? Object.assign({}, latest, { durationText: util.formatTime(latest.duration || 0) }) : null @@ -188,6 +191,8 @@ Page({ isFree: channel.isFree, isVipOnly: channel.isVipOnly, cover: cover, + _isDefaultCover: isDefaultCover, + _initial: (channel.name || '频').substring(0, 1), bgColor: this._genColor(channel.id), _todayContent: todayContent, _isThisPlaying: isThisPlaying @@ -292,4 +297,62 @@ Page({ // ===================== 工具方法 ===================== // _computeGreeting 已提至模块级,可在 data 初始化时直接调用 + + // ===================== 左滑操作 ===================== + + onSwipeStart(e) { + this._touchStartX = e.touches[0].clientX + this._touchStartY = e.touches[0].clientY + this._swiping = false + }, + + onSwipeMove(e) { + var dx = e.touches[0].clientX - this._touchStartX + var dy = e.touches[0].clientY - this._touchStartY + // 水平滑动距离大于垂直才算滑动 + if (Math.abs(dx) < Math.abs(dy)) return + this._swiping = true + var idx = e.currentTarget.dataset.idx + var x = Math.max(-180, Math.min(0, dx)) + var key = 'subscribedData[' + idx + ']._swipeX' + this.setData({ [key]: x }) + }, + + onSwipeEnd(e) { + if (!this._swiping) return + var idx = e.currentTarget.dataset.idx + var cur = this.data.subscribedData[idx]._swipeX || 0 + var key = 'subscribedData[' + idx + ']._swipeX' + // 超过 90rpx 则展开操作区,否则回弹 + this.setData({ [key]: cur < -90 ? -180 : 0 }) + }, + + onUnsubscribe(e) { + var id = e.currentTarget.dataset.id + var name = e.currentTarget.dataset.name || '' + wx.showModal({ + title: '取消订阅', + content: '确定取消订阅「' + name + '」频道吗?', + success: function (res) { + if (res.confirm) { + app.unsubscribeFromDomain(id) + } + } + }) + }, + + // ===================== 分享钩子 ===================== + onShareAppMessage() { + return { + title: '全声汇 - 听见世界的声音', + path: '/pages/index/index' + } + }, + + onShareTimeline() { + return { + title: '全声汇 - 听见世界的声音', + query: '' + } + } }) diff --git a/pages/index/index.wxml b/pages/index/index.wxml index 26dc53a..921306c 100644 --- a/pages/index/index.wxml +++ b/pages/index/index.wxml @@ -5,7 +5,7 @@ - + @@ -14,12 +14,12 @@ - + {{greetingSub}} - + - + {{locationName}} · @@ -27,7 +27,7 @@ · {{weather.icon}} - {{weather.desc}} {{weather.temp}}°C + {{weather.temp}}°C @@ -96,15 +96,29 @@ + + + 取消订阅 + + + - - {{item.cover || '📻'}} + + {{item._initial}} + {{item.cover}} {{item.name}} @@ -124,36 +138,41 @@ bindtap="onPlayContent" data-content-id="{{item._todayContent.id}}" > - + + + - - - - - + + + + + + + + - - + {{item._todayContent.title}} - {{item._todayContent.durationText}} - - + + + 暂无节目,敬请期待 - + + @@ -197,8 +216,9 @@ bindtap="goChannel" data-id="{{item.id}}" > - - {{item.cover || '📻'}} + + {{item._initial}} + {{item.cover}} {{item.name}} @@ -216,7 +236,7 @@ - + diff --git a/pages/index/index.wxss b/pages/index/index.wxss index 819dcc3..819a75d 100644 --- a/pages/index/index.wxss +++ b/pages/index/index.wxss @@ -23,7 +23,7 @@ align-items: center; justify-content: center; height: 80rpx; - background: #FFFAF5; + background: #FFFFFF; flex-shrink: 0; position: relative; } @@ -31,16 +31,16 @@ font-size: 34rpx; font-weight: 600; font-family: 'PingFang SC', 'Helvetica Neue', sans-serif; - color: #2C1A08; + color: #333333; letter-spacing: 4rpx; } /* ========== 顶部问候栏(吸顶) ========== */ .meta-bar { flex-shrink: 0; - background: linear-gradient(180deg, #FFFAF5 0%, #FFFFFF 100%); + background: linear-gradient(180deg, #FFFFFF 0%, var(--color-bg-page) 100%); padding: 28rpx 36rpx 22rpx; - border-bottom: 1rpx solid #F0ECE8; + border-bottom: 1rpx solid var(--color-border); z-index: 10; } @@ -71,7 +71,7 @@ display: block; font-size: 30rpx; font-weight: 600; - color: #5C3D1E; + color: var(--color-text-primary); letter-spacing: 1rpx; margin-bottom: 12rpx; font-family: 'PingFang SC', -apple-system, sans-serif; @@ -88,22 +88,22 @@ display: flex; align-items: center; gap: 6rpx; - background: rgba(255, 157, 66, 0.08); + background: var(--color-primary-light); padding: 6rpx 16rpx; border-radius: 999rpx; } .loc-name-text { font-weight: 600; - color: #8B6914; + color: var(--color-primary); } .info-text { font-size: 22rpx; - color: #999; + color: var(--color-text-placeholder); font-weight: 500; } .info-dot { font-size: 22rpx; - color: #D5D0CA; + color: var(--color-text-disabled); margin: 0 2rpx; } .weather-icon-sm { font-size: 22rpx; } @@ -123,8 +123,8 @@ display: flex; align-items: center; gap: 20rpx; - background: linear-gradient(135deg, #FFF8EE, #FFF3DC); - border: 1rpx solid #FFDFA0; + background: linear-gradient(135deg, #FFF9F0, #FFF3E0); + border: 1rpx solid rgba(245, 158, 11, 0.3); border-radius: 24rpx; padding: 24rpx 28rpx; margin-bottom: 32rpx; @@ -139,20 +139,51 @@ .vip-home-title { font-size: 28rpx; font-weight: 700; - color: #B07000; + color: #B45309; font-family: 'PingFang SC', sans-serif; } .vip-home-desc { font-size: 22rpx; - color: #C09040; + color: #D97706; } .vip-home-tag { font-size: 22rpx; font-weight: 600; - color: #FF9D42; + color: var(--color-primary); } +/* ========== 左滑取消订阅 ========== */ +.swipe-container { + position: relative; + overflow: hidden; + border-radius: var(--radius-lg); + margin-bottom: 24rpx; +} +.swipe-action { + position: absolute; + right: 0; + top: 0; + bottom: 0; + width: 180rpx; + background: #FF4D4F; + display: flex; + align-items: center; + justify-content: center; + border-radius: 0 var(--radius-lg) var(--radius-lg) 0; +} +.swipe-action-text { + font-size: 24rpx; + font-weight: 700; + color: #FFF; +} +.swipe-card { + position: relative; + z-index: 2; + transition: transform 0.25s ease; + margin-bottom: 0; +} + /* ========== Section Header ========== */ .section-header { display: flex; @@ -177,12 +208,12 @@ flex-shrink: 0; } .dot-free { - background: #00C853; + background: var(--color-success); } .section-title { font-size: 32rpx; font-weight: 800; - color: #1A1A1A; + color: var(--color-text-primary); letter-spacing: 1rpx; } .section-subtitle { @@ -328,6 +359,7 @@ overflow: hidden; } .icon-emoji { font-size: 36rpx; } +.icon-initial { font-size: 36rpx; color: #FFF; font-weight: 800; } .channel-info { flex: 1; min-width: 0; } .channel-name { @@ -351,22 +383,49 @@ .play-row { display: flex; align-items: center; - padding: 20rpx 18rpx 20rpx 16rpx; + padding: 22rpx 18rpx 22rpx 24rpx; border-radius: 24rpx; - background: #F7F7F7; + background: #FFFFFF; + border: 1rpx solid #F0F0F0; + box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04); transition: all 0.3s ease; gap: 16rpx; + position: relative; + overflow: hidden; } -.play-row:active { background: #EEEEEE; } +.play-row:active { background: #FAFAFA; box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06); } .play-row.playing { - background: linear-gradient(135deg, rgba(255, 157, 66, 0.08) 0%, rgba(255, 120, 50, 0.15) 100%); - border: 1rpx solid rgba(255, 157, 66, 0.15); + background: linear-gradient(135deg, rgba(255, 157, 66, 0.06) 0%, rgba(255, 120, 50, 0.12) 100%); + border: 1rpx solid rgba(255, 157, 66, 0.2); + box-shadow: 0 4rpx 20rpx rgba(255, 157, 66, 0.1); +} + +/* 播放行左侧装饰色条 */ +.play-row-accent { + position: absolute; + left: 0; + top: 12rpx; + bottom: 12rpx; + width: 6rpx; + border-radius: 0 6rpx 6rpx 0; + opacity: 0.6; +} +.play-row.playing .play-row-accent { + opacity: 1; } /* 左侧:音波 or 图标 */ .play-left { - width: 48rpx; - height: 48rpx; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} +/* 扬声器图标圆形底板 */ +.play-left-circle { + width: 60rpx; + height: 60rpx; + border-radius: 50%; display: flex; align-items: center; justify-content: center; @@ -397,7 +456,7 @@ } /* 中间信息 */ -.play-info { flex: 1; padding-right: 12rpx; overflow: hidden; } +.play-info { flex: 1; padding: 0 12rpx 0 4rpx; overflow: hidden; } .play-title { display: block; font-size: 27rpx; @@ -438,26 +497,33 @@ } /* 按钮主体 */ .play-btn { - width: 68rpx; - height: 68rpx; + width: 72rpx; + height: 72rpx; border-radius: 50%; display: flex; align-items: center; justify-content: center; - background: #ECECEC; + background: #EEEEEE; flex-shrink: 0; transition: all 0.25s ease; - box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.06); + box-shadow: 0 4rpx 14rpx rgba(0, 0, 0, 0.1); } .play-btn:active { transform: scale(0.88); } .play-btn.active { - background: linear-gradient(135deg, #FF9D42 0%, #FF7832 100%); - box-shadow: 0 8rpx 24rpx rgba(255, 120, 50, 0.4); + background: linear-gradient(135deg, #FF9E6D 0%, #FF7832 100%); + box-shadow: 0 8rpx 28rpx rgba(255, 120, 50, 0.45); } -/* t-icon 已替代旧 .play-icon / .pause-bars */ /* 暂无节目 */ -.no-content { padding: 16rpx 12rpx; } +.no-content { + display: flex; + align-items: center; + gap: 12rpx; + padding: 20rpx 18rpx; + background: #FAFAFA; + border-radius: 20rpx; + border: 1rpx dashed #ECECEC; +} .no-content-text { font-size: 23rpx; color: #CCCCCC; } /* ========== 免费频道横向区 ========== */ @@ -511,6 +577,7 @@ } .free-cover { width: 100%; height: 100%; } .free-emoji { font-size: 48rpx; } +.free-initial { font-size: 40rpx; color: #FFF; font-weight: 800; } .free-name { font-size: 22rpx; font-weight: 700; diff --git a/pages/player/index.js b/pages/player/index.js index 48c249f..3ffd634 100644 --- a/pages/player/index.js +++ b/pages/player/index.js @@ -14,6 +14,7 @@ Page({ isPlaying: false, isVip: false, isLiked: false, + isFavorited: false, currentTime: 0, duration: 0, currentTimeText: '00:00', @@ -27,7 +28,21 @@ Page({ commentList: [], commentText: '', commentLoading: false, - submitting: false + submitting: false, + // 分享面板 + // 分享面板 + showSharePanel: false, + // 倍速 ActionSheet + showSpeedSheet: false, + speedItems: [ + { label: '0.75x' }, + { label: '1.0x' }, + { label: '1.25x' }, + { label: '1.5x' }, + { label: '2.0x' } + ], + // 频道节目列表(用于上/下期切换) + _channelPrograms: [] }, _isSeeking: false, @@ -102,10 +117,13 @@ Page({ durationText: util.formatTime(gd.duration || content.duration), displayDate: dateStr, playbackRate: gd.playbackRate, - statusBarHeight: gd.statusBarHeight || 0 + statusBarHeight: gd.statusBarHeight || 0, + isLiked: !!content.hasLiked, + isFavorited: content.HasFavorite === 1 || content.hasFavorite === 1 }) this._updateDomain() + this._loadChannelPrograms() }, /** @@ -136,7 +154,7 @@ Page({ }, /** - * 查询当前节目点赞状态 + * 查询当前节目点赞和收藏状态(刷新最新值) */ _loadLikeStatus() { const content = this.data.activeContent @@ -144,11 +162,74 @@ Page({ var self = this api.getProgramDetail(content.id).then(function (res) { if (res.code === 200 && res.data) { - self.setData({ isLiked: !!res.data.isLiked }) + self.setData({ + isLiked: res.data.hasLiked === 1, + isFavorited: res.data.HasFavorite === 1 || res.data.hasFavorite === 1 + }) } }).catch(function () { }) }, + /** + * 加载当前频道的节目列表(用于上/下期切换) + */ + _loadChannelPrograms() { + const content = this.data.activeContent + if (!content) return + var channelId = content.channelId || (content.channel && content.channel.id) + if (!channelId) return + var self = this + api.getProgramList({ channelId: channelId, current: 1, pageSize: 100 }) + .then(function (res) { + if (res.code === 200 && res.data) { + var list = res.data.list || res.data || [] + self.setData({ _channelPrograms: list }) + } + }).catch(function () { }) + }, + + /** + * 上一期 + */ + onPrev() { + var programs = this.data._channelPrograms + var content = this.data.activeContent + if (!content || programs.length === 0) { + wx.showToast({ title: '没有更多了', icon: 'none' }) + return + } + var idx = -1 + for (var i = 0; i < programs.length; i++) { + if (programs[i].id === content.id) { idx = i; break } + } + if (idx <= 0) { + wx.showToast({ title: '已是第一期', icon: 'none' }) + return + } + app.playContent(programs[idx - 1]) + }, + + /** + * 下一期 + */ + onNext() { + var programs = this.data._channelPrograms + var content = this.data.activeContent + if (!content || programs.length === 0) { + wx.showToast({ title: '没有更多了', icon: 'none' }) + return + } + var idx = -1 + for (var i = 0; i < programs.length; i++) { + if (programs[i].id === content.id) { idx = i; break } + } + if (idx < 0 || idx >= programs.length - 1) { + wx.showToast({ title: '已是最新一期', icon: 'none' }) + return + } + app.playContent(programs[idx + 1]) + }, + /** * 播放/暂停 */ @@ -270,7 +351,6 @@ Page({ 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 { @@ -282,6 +362,29 @@ Page({ }) }, + onFavorite() { + const content = this.data.activeContent + if (!content) return + const self = this + const wasFavorited = this.data.isFavorited + // 乐观更新 + this.setData({ isFavorited: !wasFavorited }) + const fn = wasFavorited + ? api.removeFavorite(content.id) + : api.addFavorite(content.id) + fn.then(function (res) { + if (res.code !== 200) { + self.setData({ isFavorited: wasFavorited }) + wx.showToast({ title: res.msg || '操作失败', icon: 'none' }) + } else { + wx.showToast({ title: wasFavorited ? '已取消收藏' : '已收藏 🔖', icon: 'none' }) + } + }).catch(function () { + self.setData({ isFavorited: wasFavorited }) + wx.showToast({ title: '网络异常', icon: 'none' }) + }) + }, + // ===== 评论弹层 ===== onOpenComments() { @@ -358,11 +461,62 @@ Page({ }) }, + onShareTap() { + this.setData({ showSharePanel: true }) + }, + + onCloseShare() { + this.setData({ showSharePanel: false }) + }, + + /** + * 朋友圈分享按钮点击 → 引导用户使用右上角胶囊菜单 + * 微信小程序限制:无法编程式触发朋友圈分享,只能从胶囊菜单触发 + */ + onShareMomentTip() { + this.setData({ showSharePanel: false }) + wx.showModal({ + title: '分享到朋友圈', + content: '请点击右上角「···」菜单,选择「分享」即可发布到朋友圈', + showCancel: false, + confirmText: '我知道了' + }) + }, + onShare() { - wx.showToast({ title: '分享功能开发中', icon: 'none' }) + this.onShareTap() }, goBack() { wx.navigateBack() + }, + + // ===== 小程序分享生命周期钩子 ===== + + /** + * 转发给朋友(胶囊菜单内「转发」,或 button open-type=share 触发) + */ + onShareAppMessage() { + const content = this.data.activeContent || {} + const domain = this.data.domain || {} + this.setData({ showSharePanel: false }) + return { + title: (domain.name ? '【' + domain.name + '】' : '') + (content.title || '全声汇'), + path: '/pages/index/index', + imageUrl: '' + } + }, + + /** + * 分享到朋友圈(定义此函数后,胶囊菜单中「分享」自动出现) + */ + onShareTimeline() { + const content = this.data.activeContent || {} + const domain = this.data.domain || {} + return { + title: (domain.name ? '【' + domain.name + '】' : '') + (content.title || '全声汇'), + query: '', + imageUrl: '' + } } }) diff --git a/pages/player/index.wxml b/pages/player/index.wxml index 7a7c18b..d45d2b0 100644 --- a/pages/player/index.wxml +++ b/pages/player/index.wxml @@ -1,16 +1,16 @@ - + - + + style="background: radial-gradient(ellipse at 50% -10%, {{domain.bgColor || '#FF9E6D'}}26 0%, transparent 60%);"> - + @@ -19,9 +19,7 @@ 正在播放 {{activeContent.title || '加载中...'}} - - 💬 - + @@ -42,27 +40,37 @@ {{domain.name}} - + - + - + {{displayDate}} - @@ -73,10 +81,10 @@ min="0" max="{{duration}}" value="{{currentTime}}" - activeColor="#FF9D42" + activeColor="#FF9E6D" backgroundColor="rgba(255,255,255,0.12)" block-size="14" - block-color="#FF9D42" + block-color="#FF9E6D" bindchange="onSliderChange" bindchanging="onSliderChanging" /> @@ -86,8 +94,13 @@ - + + + + + + 15 @@ -105,64 +118,58 @@ 15 + + + + + - - - - - - 评论 ({{commentList.length}}) - - - + + + + {{playbackRate === 1 ? '倍速' : playbackRate + 'x'}} + - - - - - 发送 + + + + + + + + diff --git a/pages/player/index.wxss b/pages/player/index.wxss index d73a764..a6e0774 100644 --- a/pages/player/index.wxss +++ b/pages/player/index.wxss @@ -1,29 +1,36 @@ -/* 播放器 — 暖棕沉浸主题 */ +/* 播放器 — 深空沉浸主题 · 重设计 */ ::-webkit-scrollbar { display: none !important; } .player-page { height: 100vh; - background: #1A1208; + background: #FAFAF8; display: flex; flex-direction: column; position: relative; overflow: hidden; + animation: player-slide-up 0.4s cubic-bezier(0.16, 1, 0.3, 1) both; } -/* 环境光 */ +@keyframes player-slide-up { + 0% { transform: translateY(40%); opacity: 0; } + 100% { transform: translateY(0); opacity: 1; } +} + +/* 环境光 — 日间柔和光晕 */ .bg-glow { position: absolute; top: 0; left: 0; right: 0; height: 60vh; pointer-events: none; z-index: 0; + opacity: 1; } /* ── 顶部栏 ── */ .top-bar { display: flex; align-items: center; - padding: 16rpx 32rpx 12rpx; + padding: 12rpx 24rpx 8rpx; position: relative; z-index: 10; } @@ -34,21 +41,15 @@ align-items: center; justify-content: center; border-radius: 50%; - background: rgba(255,255,255,0.06); flex-shrink: 0; } .top-back { - font-size: 48rpx; - color: rgba(255,240,210,0.9); + font-size: 52rpx; + color: #333333; font-weight: 300; line-height: 1; margin-top: -4rpx; } -.top-share { - font-size: 32rpx; - color: rgba(255,220,160,0.6); - font-weight: 600; -} .top-title-wrap { flex: 1; text-align: center; @@ -58,15 +59,16 @@ .top-label { display: block; font-size: 18rpx; - color: rgba(255,200,120,0.45); - letter-spacing: 2rpx; - margin-bottom: 6rpx; + color: #999999; + letter-spacing: 4rpx; + text-transform: uppercase; + margin-bottom: 4rpx; } .top-title { display: block; font-size: 26rpx; - font-weight: 700; - color: rgba(255,240,210,0.95); + font-weight: 600; + color: #333333; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -81,6 +83,7 @@ position: relative; z-index: 10; padding: 0 40rpx; + min-height: 0; } .banner-cover, @@ -91,140 +94,196 @@ flex-direction: column; align-items: center; justify-content: center; - transition: opacity 0.35s ease, transform 0.35s ease; + transition: opacity 0.4s ease, transform 0.4s ease; opacity: 1; transform: scale(1); } .banner-hidden { opacity: 0; - transform: scale(0.96); + transform: scale(0.92); pointer-events: none; } /* ── 涟漪声波 ── */ .ripple-wrap { position: absolute; - width: 300rpx; - height: 300rpx; + width: 360rpx; + height: 360rpx; display: flex; align-items: center; justify-content: center; } .ripple-ring { position: absolute; - width: 220rpx; - height: 220rpx; + width: 240rpx; + height: 240rpx; border-radius: 50%; - border: 2rpx solid rgba(255, 157, 66, 0.5); - animation: ripple-out 2.1s ease-out infinite; + border: 2rpx solid rgba(255, 158, 109, 0.25); + animation: ripple-out 2.4s ease-out infinite; } @keyframes ripple-out { - 0% { transform: scale(0.6); opacity: 0.8; } - 100% { transform: scale(2.4); opacity: 0; } + 0% { transform: scale(0.5); opacity: 0.7; } + 100% { transform: scale(2.6); opacity: 0; } } -/* ── 大 Emoji(无卡片背景) ── */ +/* ── 大 Emoji ── */ .cover-emoji { - font-size: 180rpx; + font-size: 200rpx; position: relative; z-index: 2; - transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1); - transform: scale(0.9); - filter: drop-shadow(0 20rpx 40rpx rgba(0,0,0,0.5)); + transition: transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); + transform: scale(0.85); + filter: drop-shadow(0 16rpx 32rpx rgba(255, 158, 109, 0.3)); } .cover-emoji.emoji-active { - transform: scale(1.05); + transform: scale(1.08); } .cover-channel { - font-size: 22rpx; - font-weight: 600; - color: rgba(255,200,120,0.45); - letter-spacing: 2rpx; - margin-top: 24rpx; + font-size: 24rpx; + font-weight: 700; + color: #999999; + letter-spacing: 6rpx; + text-transform: uppercase; + margin-top: 32rpx; position: relative; z-index: 2; } -/* 提示文字 */ +/* 提示文字 — 胶囊按钮风格 */ .banner-hint { - display: block; - margin-top: 20rpx; - font-size: 20rpx; - color: rgba(255,200,120,0.3); - letter-spacing: 1rpx; + display: inline-block; + margin-top: 28rpx; + font-size: 22rpx; + color: #999999; + letter-spacing: 2rpx; position: relative; z-index: 2; + background: rgba(0, 0, 0, 0.03); + border: 1rpx solid rgba(0, 0, 0, 0.05); + border-radius: 999rpx; + padding: 10rpx 28rpx; } -/* ── 文案视图 ── */ +/* ── 文案视图(阅读模式) ── */ .banner-transcript { - padding: 24rpx 48rpx 16rpx; + padding: 16rpx 40rpx 16rpx; flex-direction: column; } + .transcript-scroll { flex: 1; width: 100%; - max-height: 480rpx; - background: rgba(255,200,120,0.05); - border-radius: 32rpx; - border: 1rpx solid rgba(255,200,120,0.12); - padding: 32rpx; + max-height: 55vh; + background: rgba(255, 255, 255, 0.85); + border-radius: 24rpx; + border: 1rpx solid rgba(0, 0, 0, 0.04); + padding: 36rpx 32rpx; box-sizing: border-box; + backdrop-filter: blur(24px); + box-shadow: 0 16rpx 48rpx rgba(255, 158, 109, 0.12); } .transcript-content { - font-size: 28rpx; - line-height: 1.95; - color: rgba(255,240,210,0.8); + font-size: 30rpx; + line-height: 2.1; + color: #666666; white-space: pre-wrap; + letter-spacing: 1rpx; } .transcript-empty { display: flex; flex-direction: column; align-items: center; - padding: 40rpx 0; + padding: 56rpx 0; } -.transcript-empty-icon { font-size: 60rpx; } +.transcript-empty-icon { font-size: 72rpx; } .transcript-empty-text { - font-size: 24rpx; - color: rgba(255,200,120,0.4); - margin-top: 12rpx; + font-size: 28rpx; + color: #999999; + margin-top: 20rpx; + font-weight: 600; +} +.transcript-empty-sub { + font-size: 22rpx; + color: #CCCCCC; + margin-top: 8rpx; +} +.transcript-close-hint { + display: flex; + justify-content: center; + margin-top: 20rpx; +} +.transcript-close-text { + font-size: 20rpx; + color: #999999; + letter-spacing: 2rpx; } -/* 日期 + like 同行 */ +/* ── 日期 + 操作行 ── */ .date-like-row { display: flex; align-items: center; justify-content: space-between; - padding: 0 48rpx; - margin-bottom: 4rpx; + padding: 0 52rpx; + margin-bottom: 8rpx; position: relative; z-index: 10; } .date-text { font-size: 22rpx; - color: rgba(255,200,120,0.4); - letter-spacing: 1rpx; + color: #999999; + letter-spacing: 2rpx; + font-weight: 500; } -.like-btn-inline { +.action-btns { + display: flex; + align-items: center; + gap: 8rpx; +} +.action-btn { display: flex; align-items: center; justify-content: center; - padding: 4rpx 0; + width: 72rpx; + height: 72rpx; + border-radius: 50%; + background: rgba(0, 0, 0, 0.04); + transition: all 0.2s; +} +.action-btn:active { + background: rgba(0, 0, 0, 0.08); + transform: scale(1.1); +} +.action-icon { + font-size: 34rpx; + color: #666666; + transition: color 0.2s; +} +.action-icon.favorited { + color: #FF9E6D; } .like-icon { - font-size: 40rpx; - color: rgba(255,200,120,0.55); + font-size: 36rpx; + color: #666666; + transition: all 0.25s; +} +.like-icon.liked { + color: #EF4444; + filter: drop-shadow(0 0 8rpx rgba(239, 68, 68, 0.6)); + animation: like-pop 0.35s ease; +} +@keyframes like-pop { + 0% { transform: scale(1); } + 40% { transform: scale(1.3); } + 100% { transform: scale(1); } } -/* ── 进度条 + like 浮动 ── */ +/* ── 进度条 ── */ .progress-section { padding: 0 48rpx; - margin-bottom: 16rpx; + margin-bottom: 12rpx; position: relative; z-index: 10; } -/* like 按钮已内联至 time-row,旧绝对定位样式已移除 */ - .progress-slider { margin: 0; } .time-row { display: flex; @@ -234,20 +293,10 @@ } .time-text { font-size: 22rpx; - color: rgba(255,200,120,0.4); - font-family: 'Menlo', 'SF Mono', monospace; - font-weight: 600; -} -/* like 按鈕内联在时间行中间 */ -.like-btn-inline { - display: flex; - align-items: center; - justify-content: center; - padding: 4rpx 16rpx; -} -.like-icon { - font-size: 40rpx; - color: rgba(255,200,120,0.55); + color: #999999; + font-family: 'SF Mono', 'Menlo', monospace; + font-weight: 500; + letter-spacing: 1rpx; } /* ── 控制区 ── */ @@ -255,215 +304,211 @@ display: flex; align-items: center; justify-content: center; - gap: 64rpx; - padding: 16rpx 48rpx 64rpx; + gap: 24rpx; + padding: 8rpx 48rpx 16rpx; position: relative; z-index: 10; } + +/* 上/下期按钮 */ +.prev-next-btn { + width: 80rpx; + height: 80rpx; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + transition: background 0.2s; +} +.prev-next-btn:active { + background: rgba(0, 0, 0, 0.04); +} +.pn-icon { + font-size: 36rpx; + color: #666666; +} +.prev-next-btn:active .pn-icon { + color: #333333; +} + +/* 快退/快进 */ .skip-btn { - width: 96rpx; - height: 96rpx; + width: 88rpx; + height: 88rpx; display: flex; flex-direction: column; align-items: center; justify-content: center; position: relative; + border-radius: 50%; + transition: background 0.2s; +} +.skip-btn:active { + background: rgba(0, 0, 0, 0.04); } .skip-arrow { - font-size: 76rpx; - color: rgba(255,220,160,0.8); + font-size: 64rpx; + color: #666666; + font-weight: 300; line-height: 1; } .skip-sec { - position: absolute; - font-size: 19rpx; + font-size: 18rpx; font-weight: 800; - color: rgba(255,220,160,0.8); + color: #999999; + position: absolute; top: 50%; left: 50%; - transform: translate(-50%, -46%); - letter-spacing: -1rpx; + transform: translate(-50%, -50%); + margin-top: 2rpx; } + +/* 播放/暂停主按钮 */ .play-btn { - width: 136rpx; - height: 136rpx; + width: 120rpx; + height: 120rpx; border-radius: 50%; - background: #FF9D42; + background: linear-gradient(135deg, #FF8C42 0%, #FF6B2C 100%); display: flex; align-items: center; justify-content: center; - box-shadow: - 0 16rpx 48rpx rgba(255, 157, 66, 0.5), - 0 0 0 12rpx rgba(255, 157, 66, 0.15); + box-shadow: 0 16rpx 32rpx rgba(255, 107, 44, 0.25); transition: transform 0.15s, box-shadow 0.15s; } .play-btn:active { transform: scale(0.92); - box-shadow: - 0 8rpx 24rpx rgba(255, 157, 66, 0.4), - 0 0 0 8rpx rgba(255, 157, 66, 0.1); -} -.play-tri { - font-size: 52rpx; - color: #FFF8EE; - margin-left: 8rpx; - line-height: 1; + box-shadow: 0 4rpx 16rpx rgba(255, 107, 44, 0.5); } .pause-group { display: flex; - gap: 12rpx; + gap: 10rpx; align-items: center; } .pause-bar { - width: 10rpx; - height: 44rpx; - background: #FFF8EE; - border-radius: 5rpx; + width: 8rpx; + height: 36rpx; + background: #FFF; + border-radius: 4rpx; +} +.play-tri { + font-size: 40rpx; + color: #FFF; + padding-left: 6rpx; } -/* ── like 状态 ── */ -.like-icon.liked { - color: #FF4D6D; +/* ── 倍速按钮行 ── */ +.speed-row { + display: flex; + justify-content: center; + padding: 0 48rpx 40rpx; + position: relative; + z-index: 10; } -.top-comment-icon { - font-size: 38rpx; - line-height: 1; +.speed-btn { + background: rgba(0, 0, 0, 0.04); + border: 1rpx solid rgba(0, 0, 0, 0.06); + border-radius: 999rpx; + padding: 12rpx 36rpx; + transition: all 0.2s; +} +.speed-btn:active { + background: rgba(0, 0, 0, 0.08); +} +.speed-label { + font-size: 24rpx; + font-weight: 700; + color: #666666; + letter-spacing: 1rpx; } -/* ── 评论弹层 ── */ -.comment-mask { +/* ── 分享面板 ── */ +.share-mask { position: fixed; inset: 0; - background: rgba(0,0,0,0.5); - z-index: 200; + background: rgba(0, 0, 0, 0.5); + z-index: 100; + animation: fadeIn 0.25s ease; } -.comment-sheet { +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} +.share-sheet { position: fixed; - bottom: -100%; + bottom: 0; left: 0; right: 0; - height: 70vh; - background: #231808; + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(20px); 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); + padding: 24rpx 48rpx calc(48rpx + env(safe-area-inset-bottom)); + transform: translateY(100%); + transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1); + z-index: 200; + box-shadow: 0 -8rpx 32rpx rgba(0, 0, 0, 0.05); } -.comment-sheet.sheet-up { - bottom: 0; +.share-sheet.sheet-up { + transform: translateY(0); } -.sheet-handle { - width: 72rpx; - height: 6rpx; - background: rgba(255,200,120,0.2); - border-radius: 3rpx; - margin: 20rpx auto 0; +.share-sheet-handle { + width: 64rpx; + height: 8rpx; + background: #CCCCCC; + border-radius: 4rpx; + margin: 0 auto 28rpx; } -.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 { +.share-sheet-title { font-size: 30rpx; font-weight: 700; - color: rgba(255,240,210,0.9); + color: #333333; + text-align: center; + margin-bottom: 40rpx; } -.sheet-close-icon { - font-size: 32rpx; - color: rgba(255,200,120,0.4); -} -.comment-input-row { +.share-options { display: flex; - align-items: center; - gap: 16rpx; - padding: 20rpx 32rpx; - border-bottom: 1rpx solid rgba(255,200,120,0.08); + justify-content: center; + gap: 64rpx; + margin-bottom: 40rpx; } -.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; +.share-option-btn { + background: transparent !important; + border: none !important; + padding: 0 !important; + margin: 0 !important; + line-height: normal !important; } -.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 { +.share-option-btn::after { border: none !important; } +.share-option { 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); +.share-opt-icon { + width: 100rpx; + height: 100rpx; + border-radius: 28rpx; 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 { +.share-opt-emoji { font-size: 44rpx; } +.share-opt-friend { background: rgba(7, 193, 96, 0.15); } +.share-opt-moment { background: rgba(255, 157, 66, 0.15); } +.share-opt-label { font-size: 22rpx; - color: rgba(255,200,120,0.3); + color: #666666; } -.comment-del { - padding: 8rpx; +.share-cancel { + text-align: center; + font-size: 28rpx; + color: #999999; + padding: 20rpx 0; + border-top: 1rpx solid #EEEEEE; +} + +/* ── 分享图标(顶部栏) ── */ +.top-share-icon { + font-size: 36rpx; + color: #333333; } -.comment-del-icon { font-size: 32rpx; } diff --git a/pages/profile/index.js b/pages/profile/index.js index e8550a4..b4b65ac 100644 --- a/pages/profile/index.js +++ b/pages/profile/index.js @@ -11,6 +11,7 @@ Page({ userInfo: null, menuItems: [ { id: 'vip', label: '会员中心', icon: '👑', desc: '未开通', highlight: true }, + { id: 'notification', label: '通知设置', icon: '🔔', desc: '', highlight: false }, { id: 'help', label: '帮助与反馈', icon: '❓', desc: '', highlight: false }, { id: 'about', label: '关于我们', icon: '📄', desc: 'v1.0.0', highlight: false } ] @@ -49,6 +50,17 @@ Page({ const id = e.currentTarget.dataset.id if (id === 'vip') { wx.navigateTo({ url: '/pages/vip/index' }) + } else if (id === 'notification') { + wx.openSetting({ + success(res) { + if (res.authSetting['scope.subscribeMessage'] !== undefined) { + wx.showToast({ title: '设置已更新', icon: 'none' }) + } + }, + fail() { + wx.showToast({ title: '请在系统设置中允许通知', icon: 'none' }) + } + }) } else if (id === 'help') { wx.showToast({ title: '帮助中心开发中', icon: 'none' }) } else if (id === 'about') { diff --git a/pages/profile/index.wxss b/pages/profile/index.wxss index 31bc2ae..c14df16 100644 --- a/pages/profile/index.wxss +++ b/pages/profile/index.wxss @@ -1,15 +1,8 @@ -/* 个人中心 - 全新极简温暖主题 */ -:root { - --primary-color: #F38600; - --bg-color: #F7F8FA; - --card-bg: #FFFFFF; - --text-main: #2C2C2C; - --text-sub: #8E8E93; -} +/* 个人中心 — 使用全局变量体系 */ .profile-page { min-height: 100vh; - background-color: #F9F9F9; + background-color: var(--color-bg-page); position: relative; overflow-x: hidden; } @@ -29,11 +22,11 @@ left: 0; width: 100%; height: 100%; - background: linear-gradient(135deg, #FFF0E0 0%, #FFDFB8 100%); + background: linear-gradient(135deg, rgba(255, 122, 47, 0.08) 0%, rgba(255, 122, 47, 0.18) 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); + box-shadow: 0 4rpx 24rpx rgba(255, 122, 47, 0.08); } .user-info-box { @@ -47,8 +40,8 @@ height: 140rpx; border-radius: 50%; border: 6rpx solid rgba(255, 255, 255, 0.6); - background: #FFF; - box-shadow: 0 12rpx 32rpx rgba(243, 134, 0, 0.15); + background: var(--color-bg-card); + box-shadow: 0 12rpx 32rpx rgba(255, 122, 47, 0.15); display: flex; align-items: center; justify-content: center; @@ -82,14 +75,14 @@ .user-name { font-size: 40rpx; font-weight: 800; - color: #333333; + color: var(--color-text-primary); letter-spacing: 2rpx; } .vip-badge { display: flex; align-items: center; - background: linear-gradient(90deg, #333333 0%, #1A1A1A 100%); + background: linear-gradient(90deg, var(--color-primary-dark) 0%, #1A1A1A 100%); padding: 4rpx 16rpx; border-radius: 999rpx; margin-left: 16rpx; @@ -108,7 +101,7 @@ .user-id { font-size: 24rpx; - color: #8C6F50; + color: var(--color-text-secondary); font-weight: 500; } @@ -125,10 +118,10 @@ display: flex; align-items: center; justify-content: space-between; - background: linear-gradient(135deg, #FFEFD5 0%, #FFE4B5 100%); + background: linear-gradient(135deg, rgba(255, 122, 47, 0.1) 0%, rgba(255, 122, 47, 0.18) 100%); padding: 32rpx 40rpx; border-radius: 32rpx; - box-shadow: 0 8rpx 24rpx rgba(217, 119, 6, 0.08); + box-shadow: 0 8rpx 24rpx rgba(255, 122, 47, 0.08); margin-bottom: 32rpx; border: 1rpx solid rgba(255, 255, 255, 0.5); } @@ -151,18 +144,18 @@ .promo-title { font-size: 30rpx; font-weight: 800; - color: #8B4513; + color: var(--color-text-primary); margin-bottom: 6rpx; } .promo-desc { font-size: 22rpx; - color: #AA7A55; + color: var(--color-text-secondary); font-weight: 500; } .promo-btn { - background: #333333; + background: var(--color-primary-dark); color: #FDF1C2; font-size: 24rpx; font-weight: 700; @@ -173,7 +166,7 @@ /* 列表菜单 */ .menu-list-card { - background: #FFFFFF; + background: var(--color-bg-card); border-radius: 32rpx; box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.02); padding: 16rpx 0; @@ -226,7 +219,7 @@ .menu-title { font-size: 30rpx; font-weight: 600; - color: #333333; + color: var(--color-text-primary); } .menu-right { @@ -236,7 +229,7 @@ .menu-status { font-size: 24rpx; - color: #999999; + color: var(--color-text-placeholder); margin-right: 12rpx; } @@ -247,7 +240,7 @@ .menu-arrow { font-size: 32rpx; - color: #CCCCCC; + color: var(--color-text-disabled); } .bottom-spacer { diff --git a/pages/splash/index.wxml b/pages/splash/index.wxml index 341e601..fdcd44a 100644 --- a/pages/splash/index.wxml +++ b/pages/splash/index.wxml @@ -1,5 +1,5 @@ - + @@ -39,7 +39,7 @@ - 每天三分钟,精准获取所需 + 每天五分钟,精准获取所需 diff --git a/pages/splash/index.wxss b/pages/splash/index.wxss index bc1b846..99153f5 100644 --- a/pages/splash/index.wxss +++ b/pages/splash/index.wxss @@ -1,9 +1,9 @@ -/* 启动页 — 暖棕沉浸主题,与播放器保持一致 */ +/* 启动页 — 深空蓝沉浸主题 */ .splash-page { width: 100%; height: 100vh; - background: #1A1208; + background: #FAFAF8; display: flex; flex-direction: column; align-items: center; @@ -20,7 +20,7 @@ transform: translateX(-50%); width: 120vw; height: 60vh; - background: radial-gradient(ellipse at center, rgba(255,157,66,0.22) 0%, transparent 70%); + background: radial-gradient(ellipse at center, rgba(255,158,109,0.2) 0%, transparent 60%); pointer-events: none; } @@ -49,7 +49,7 @@ } .logo-icon { font-size: 120rpx; - filter: drop-shadow(0 16rpx 40rpx rgba(255,120,0,0.4)); + filter: drop-shadow(0 16rpx 40rpx rgba(255,158,109,0.35)); position: relative; z-index: 2; } @@ -59,7 +59,7 @@ position: absolute; inset: -20rpx; border-radius: 50%; - border: 3rpx solid rgba(255, 157, 66, 0.45); + border: 3rpx solid rgba(255, 158, 109, 0.35); animation: pulse-expand 2s ease-out infinite; } .pulse-ring-2 { @@ -74,14 +74,14 @@ .app-title { font-size: 64rpx; font-weight: 800; - color: rgba(255, 240, 210, 0.95); + color: #333333; letter-spacing: -2rpx; margin-bottom: 8rpx; } .app-sub { font-size: 22rpx; font-weight: 400; - color: rgba(255, 200, 120, 0.45); + color: #999999; letter-spacing: 6rpx; margin-bottom: 28rpx; text-transform: uppercase; @@ -89,7 +89,7 @@ .app-slogan { font-size: 26rpx; font-weight: 400; - color: rgba(255, 200, 120, 0.55); + color: #666666; letter-spacing: 3rpx; } @@ -117,7 +117,7 @@ width: 12rpx; height: 12rpx; border-radius: 50%; - background: rgba(255, 157, 66, 0.7); + background: rgba(255, 158, 109, 0.8); animation: dot-bounce 1.2s ease-in-out infinite alternate; } @keyframes dot-bounce { @@ -128,12 +128,12 @@ /* 成功状态 */ .status-check { font-size: 32rpx; - color: #FF9D42; + color: #FF9E6D; font-weight: 700; } .status-ok { font-size: 26rpx; - color: rgba(255, 200, 120, 0.7); + color: #999999; font-weight: 500; } @@ -147,6 +147,6 @@ } .footer-text { font-size: 20rpx; - color: rgba(255, 200, 120, 0.2); + color: #CCCCCC; letter-spacing: 2rpx; } diff --git a/pages/subscribe/index.wxml b/pages/subscribe/index.wxml index cf7c6f0..a34df69 100644 --- a/pages/subscribe/index.wxml +++ b/pages/subscribe/index.wxml @@ -23,7 +23,7 @@ 包月 - + ¥{{monthlyPrice}} @@ -41,7 +41,7 @@ 包季 省{{_quarterlySaving}}元 - + ¥{{quarterlyPrice}} @@ -60,7 +60,7 @@ 包年 省{{_annualSaving}}元 - + ¥{{annualPrice}} @@ -71,15 +71,15 @@ - + 订阅后可无限收听该频道所有节目 - + 后台播放,边听边做其他事 - + 新节目第一时间推送通知 diff --git a/pages/subscribe/index.wxss b/pages/subscribe/index.wxss index c0a5ee1..a5cf477 100644 --- a/pages/subscribe/index.wxss +++ b/pages/subscribe/index.wxss @@ -9,7 +9,7 @@ /* 顶部频道卡片 */ .channel-card { - background: linear-gradient(135deg, #FF9D42, #FF7832); + background: linear-gradient(135deg, #FF9E6D, #FF7832); padding: 60rpx 40rpx 48rpx; display: flex; flex-direction: column; @@ -54,21 +54,21 @@ box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.04); } .plan-card.selected { - border-color: #FF9D42; + border-color: #FF9E6D; background: #FFFAF5; } .plan-card.popular { border-color: #FFD580; } .plan-card.popular.selected { - border-color: #FF9D42; + border-color: #FF9E6D; } /* 最受欢迎标签 */ .plan-hot-tag { position: absolute; top: -20rpx; left: 36rpx; - background: linear-gradient(135deg, #FF9D42, #FF7832); + background: linear-gradient(135deg, #FF9E6D, #FF7832); color: #FFF; font-size: 20rpx; font-weight: 700; @@ -136,7 +136,7 @@ .pay-label { font-size: 22rpx; color: #999; display: block; } .pay-price { font-size: 44rpx; font-weight: 800; color: #FF7832; line-height: 1; } .pay-btn { - background: linear-gradient(135deg, #FF9D42, #FF7832); + background: linear-gradient(135deg, #FF9E6D, #FF7832); color: #FFF; font-size: 30rpx; font-weight: 700; diff --git a/pages/vip/index.js b/pages/vip/index.js index cfba32a..d4bf5d4 100644 --- a/pages/vip/index.js +++ b/pages/vip/index.js @@ -77,20 +77,19 @@ Page({ 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 originalPriceYuan = (cfg.price / 100).toFixed(2) var hasDiscount = cfg.discountedPrice > 0 && cfg.discountedPrice < cfg.price + var payPrice = hasDiscount ? (cfg.discountedPrice / 100).toFixed(2) : originalPriceYuan self.setData({ - currentPrice: price, - vipPrice: price, - vipOriginalPrice: hasDiscount ? originalPrice : '', + currentPrice: payPrice, + vipPrice: payPrice, + vipOriginalPrice: hasDiscount ? originalPriceYuan : '', vipRemark: cfg.remark || '' }) } }).catch(function (err) { console.error('[VIP] 获取配置失败:', err) - // 容错:使用默认价格 - self.setData({ currentPrice: '19.90', vipPrice: '19.90', vipOriginalPrice: '29.90' }) + self.setData({ currentPrice: '--', vipPrice: '--', vipOriginalPrice: '' }) }) }, @@ -103,7 +102,7 @@ Page({ /** 选择套餐 */ selectPlan(e) { const plan = e.currentTarget.dataset.plan - let price = '19.9' + let price = this.data.vipPrice || '--' if (this.data.mode === 'channel') { const map = { monthly: this.data.monthlyPrice, diff --git a/pages/vip/index.wxml b/pages/vip/index.wxml index 5499634..7441abb 100644 --- a/pages/vip/index.wxml +++ b/pages/vip/index.wxml @@ -82,17 +82,31 @@ - + 🔓 - 全频道特权 + 全频道解锁 所有频道自由听 🎧 - 纯净免广告 - 收听无任何打扰 + 免广告收听 + 纯净无打扰体验 + + + + + + 优先推送 + 新内容第一时间送达 + + + + 💬 + + 互动评论 + 与创作者直接交流 @@ -110,7 +124,7 @@ 限时特惠 永久会员 - {{vipRemark || '一次购买,永久畅听全部频道'}} + 一次购买,永久畅听全部频道 ¥{{vipPrice || currentPrice}} @@ -118,6 +132,11 @@ + + + @@ -211,6 +230,11 @@ + + + @@ -231,7 +255,7 @@ block size="large" bind:tap="onPay" - style="--td-button-primary-bg-color: #FF9D42; --td-button-primary-active-bg-color: #E88A35;" + style="--td-button-primary-bg-color: #FF9E6D; --td-button-primary-active-bg-color: #E88A35;" > {{mode === 'channel' ? '立即订阅并支付' : '立即开通并支付'}} diff --git a/pages/vip/index.wxss b/pages/vip/index.wxss index 9bfd608..e170b5d 100644 --- a/pages/vip/index.wxss +++ b/pages/vip/index.wxss @@ -5,7 +5,7 @@ display: flex; flex-direction: column; height: 100vh; - background: #FCFCFC; + background: var(--color-bg-page); overflow: hidden; } @@ -312,7 +312,7 @@ border-radius: 0 20rpx 0 20rpx; } .plan-badge-hot { - background: linear-gradient(135deg, #FF9D42, #FF7832); + background: linear-gradient(135deg, #FF9E6D, #FF7832); color: #FFF; } .plan-badge-save { @@ -398,3 +398,18 @@ border-radius: 4rpx; margin-left: 8rpx; } + +/* 订阅免责提示 */ +.subscribe-disclaimer { + margin: 32rpx 32rpx 24rpx; + padding: 24rpx 28rpx; + background: #F9F9F9; + border-radius: 16rpx; + border: 1rpx solid #F0F0F0; +} +.disclaimer-text { + font-size: 22rpx; + color: #B0B0B0; + line-height: 1.7; + letter-spacing: 0.5rpx; +} diff --git a/project.config.json b/project.config.json index 3a84028..9a3626c 100644 --- a/project.config.json +++ b/project.config.json @@ -47,7 +47,7 @@ "disableSWC": true }, "compileType": "miniprogram", - "libVersion": "3.3.4", + "libVersion": "3.14.3", "appid": "wx52dfc635739a9c19", "projectname": "morning-radio-mp", "condition": {}, diff --git a/utils/api.js b/utils/api.js index 76cf790..72dd201 100644 --- a/utils/api.js +++ b/utils/api.js @@ -194,7 +194,7 @@ function getVipConfig() { /** 发起 VIP 开通预支付 */ function initiateVipPayment() { - return post('/vip/vip',{}) + return post('/vip/vip', {}) } function getUserInfo() { @@ -231,6 +231,19 @@ function unlockChannel(channelId, type) { return post('/radio/subscription/unlock', { channelId, type }) } +/** 获取点赞列表 */ +function getLikeList(params) { + return post('/like/list', { + current: (params && params.current) || 1, + pageSize: (params && params.pageSize) || 30 + }) +} + +/** 清空全部点赞 */ +function removeAllLikes() { + return get('/like/removeAll') +} + module.exports = { miniLogin, getLocation, @@ -256,6 +269,8 @@ module.exports = { removeAllFavorites, getFavoriteList, toggleLike, + getLikeList, + removeAllLikes, addComment, deleteComment, getCommentList, diff --git a/utils/audioManager.js b/utils/audioManager.js index 188afa5..dd53ae4 100644 --- a/utils/audioManager.js +++ b/utils/audioManager.js @@ -11,6 +11,7 @@ let bgAudioManager = null let appInstance = null let _switching = false // 切换音频时的锁,防止 onStop 事件干扰 let _ended = false // 标记音频是否已自然播放完毕 +let _stopped = false // 标记音频是否被系统停止(浮窗关闭等) /** * 初始化音频管理器 @@ -36,6 +37,7 @@ function init(app) { // 如果正在切换音频,不处理 onStop(新的 src 设置会触发旧的 onStop) if (_switching) return + _stopped = true // 标记:音频被系统停止,下次播放需重新设置 src reportHistory() updatePlayState(false) appInstance.globalData.currentTime = 0 @@ -142,7 +144,8 @@ function playContent(content) { // 标记正在切换,防止旧音频的 onStop 干扰 _switching = true - _ended = false // 重置结束标记 + _ended = false + _stopped = false // 重置停止标记 appInstance.globalData.activeContent = content appInstance.globalData.currentTime = 0 @@ -170,12 +173,13 @@ function togglePlay() { if (appInstance.globalData.isPlaying) { bgAudioManager.pause() } else { - // 音频已自然播放完毕,或 src 被系统回收 → 从头重新播放 + // 音频已结束 / 被系统停止(浮窗关闭) / src 被回收 → 重新播放 var srcEmpty = false try { srcEmpty = !bgAudioManager.src } catch (e) { srcEmpty = true } - if (_ended || srcEmpty) { + if (_ended || _stopped || srcEmpty) { _ended = false + _stopped = false playContent(appInstance.globalData.activeContent) } else { bgAudioManager.play()