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 @@
+
+
+ 取消订阅
+
+
+
@@ -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}}
- 点击查看文案
+ 📖 阅读模式
-
+
{{activeContent.content}}
- 📄
- 暂无文案
+ 📝
+ 暂无文案内容
+ 主播未上传此期文稿
- 点击返回封面
+
+ 点击任意区域返回播放
+
-
+
{{displayDate}}
-
- {{isLiked ? '♥' : '♡'}}
+
+
+
+ {{isFavorited ? '🔖' : '📄'}}
+
+
+
+ {{isLiked ? '❤️' : '♡'}}
+
@@ -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
+
+
+
+ ⏭
+
-
-
-
- ✅
+ 🔓
- 全频道特权
+ 全频道解锁
所有频道自由听
🎧
- 纯净免广告
- 收听无任何打扰
+ 免广告收听
+ 纯净无打扰体验
+
+
+
+ ⭐
+
+ 优先推送
+ 新内容第一时间送达
+
+
+
+ 💬
+
+ 互动评论
+ 与创作者直接交流
@@ -110,7 +124,7 @@
限时特惠
永久会员
- {{vipRemark || '一次购买,永久畅听全部频道'}}
+ 一次购买,永久畅听全部频道
¥{{vipPrice || currentPrice}}
@@ -118,6 +132,11 @@
+
+
+
+ {{vipRemark}}
+
@@ -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()