first commit
This commit is contained in:
+216
@@ -0,0 +1,216 @@
|
||||
/**
|
||||
* 早安电台 — 后端 API 接口封装
|
||||
* 统一调用 request.js,对外暴露业务方法
|
||||
*/
|
||||
const { get, post } = require('./request')
|
||||
|
||||
// ======================== 登录相关 ========================
|
||||
|
||||
/** 小程序登录 (wx.login code → token + user) */
|
||||
function miniLogin(code) {
|
||||
return get('/auth/miniLogin', { code })
|
||||
}
|
||||
|
||||
/** 获取位置信息 */
|
||||
function getLocation(longitude, latitude) {
|
||||
return get('/auth/getLocation', { longitude, latitude })
|
||||
}
|
||||
|
||||
/** 获取天气 */
|
||||
function getWeather(adcode) {
|
||||
return get('/auth/getWeather', { adcode })
|
||||
}
|
||||
|
||||
/** 获取手机号 */
|
||||
function getPhone(code, openId) {
|
||||
return get('/auth/getPhone', { code, openId })
|
||||
}
|
||||
|
||||
// ======================== 分类管理 ========================
|
||||
|
||||
/** 获取分类列表(全量) */
|
||||
function getCategoryList() {
|
||||
return get('/radio/category/list')
|
||||
}
|
||||
|
||||
/** 获取分类树 */
|
||||
function getCategoryTree() {
|
||||
return get('/radio/category/tree')
|
||||
}
|
||||
|
||||
// ======================== 频道管理 ========================
|
||||
|
||||
/** 获取频道列表(分页) */
|
||||
function getChannelList(params) {
|
||||
return post('/radio/channel/list', {
|
||||
current: params.current || 1,
|
||||
pageSize: params.pageSize || 50,
|
||||
categoryId: params.categoryId || '',
|
||||
status: 1 // 仅上架
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取免费频道列表 */
|
||||
function getFreeChannelList(params) {
|
||||
return post('/radio/channel/freeList', {
|
||||
current: (params && params.current) || 1,
|
||||
pageSize: (params && params.pageSize) || 20,
|
||||
keyword: (params && params.keyword) || ''
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取频道详情 */
|
||||
function getChannelDetail(id) {
|
||||
return get('/radio/channel/detail', { id })
|
||||
}
|
||||
|
||||
// ======================== 节目管理 ========================
|
||||
|
||||
/** 获取节目列表(分页,需传 channelId) */
|
||||
function getProgramList(params) {
|
||||
return post('/radio/program/list', {
|
||||
channelId: params.channelId,
|
||||
current: params.current || 1,
|
||||
pageSize: params.pageSize || 50,
|
||||
status: 1
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取节目详情 */
|
||||
function getProgramDetail(id) {
|
||||
return get('/radio/program/detail', { id })
|
||||
}
|
||||
|
||||
// ======================== 订阅管理 ========================
|
||||
|
||||
/** 获取我的订阅列表 */
|
||||
function getSubscriptionList(params) {
|
||||
return post('/radio/subscription/list', {
|
||||
current: (params && params.current) || 1,
|
||||
pageSize: (params && params.pageSize) || 50
|
||||
})
|
||||
}
|
||||
|
||||
/** 检查是否可以订阅 */
|
||||
function canSubscribe(channelId) {
|
||||
return post('/radio/subscription/can-subscribe', { channelId })
|
||||
}
|
||||
|
||||
/** 订阅频道 */
|
||||
function subscribe(channelId) {
|
||||
return post('/radio/subscription/subscribe', { channelId })
|
||||
}
|
||||
|
||||
/** 退订频道 */
|
||||
function unsubscribe(channelId) {
|
||||
return post('/radio/subscription/unsubscribe', { channelId })
|
||||
}
|
||||
|
||||
// ======================== 收听历史 ========================
|
||||
|
||||
/** 添加收听历史 */
|
||||
function addHistory(params) {
|
||||
return post('/radio/history/add', {
|
||||
programId: params.programId,
|
||||
progress: params.progress || 0,
|
||||
duration: params.duration || 0
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取收听历史列表 */
|
||||
function getHistoryList(params) {
|
||||
return post('/radio/history/list', {
|
||||
current: (params && params.current) || 1,
|
||||
pageSize: (params && params.pageSize) || 20
|
||||
})
|
||||
}
|
||||
|
||||
// ======================== 收藏 ========================
|
||||
|
||||
/** 添加收藏 */
|
||||
function addFavorite(programId) {
|
||||
return post('/radio/favorite/add', { programId })
|
||||
}
|
||||
|
||||
/** 取消收藏 */
|
||||
function removeFavorite(programId) {
|
||||
return post('/radio/favorite/remove', { programId })
|
||||
}
|
||||
|
||||
/** 获取收藏列表 */
|
||||
function getFavoriteList(params) {
|
||||
return post('/radio/favorite/list', {
|
||||
current: (params && params.current) || 1,
|
||||
pageSize: (params && params.pageSize) || 20
|
||||
})
|
||||
}
|
||||
|
||||
// ======================== 点赞 / 评论 ========================
|
||||
|
||||
/** 切换点赞 */
|
||||
function toggleLike(programId) {
|
||||
return post('/radio/like/toggle', { programId })
|
||||
}
|
||||
|
||||
/** 获取当前登录用户 */
|
||||
function getUserInfo() {
|
||||
return get('/user/info')
|
||||
}
|
||||
|
||||
/** 付费订阅频道(传入方案 monthly/quarterly/annual 和价格) */
|
||||
function subscribeChannel(params) {
|
||||
return post('/radio/subscription/pay', params)
|
||||
}
|
||||
|
||||
/**
|
||||
* 主动查询微信支付状态
|
||||
* @param {string} outTradeNo 商户订单号(上游返回)
|
||||
* @returns {Promise<boolean>} true = 支付成功
|
||||
*/
|
||||
function queryPayStatus(outTradeNo) {
|
||||
return get('/pay/query', { outTradeNo })
|
||||
.then(function (res) {
|
||||
// 后端返回 bool 或 { data: bool }
|
||||
if (typeof res === 'boolean') return res
|
||||
if (typeof res.data === 'boolean') return res.data
|
||||
return res.code === 200 && !!res.data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 频道解锁(唇起微信支付骄支付单)
|
||||
* @param {string} channelId
|
||||
* @param {string} type '1'=包月 '2'=包季 '3'=包年
|
||||
*/
|
||||
function unlockChannel(channelId, type) {
|
||||
return post('/radio/subscription/unlock', { channelId, type })
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
miniLogin,
|
||||
getLocation,
|
||||
getWeather,
|
||||
getPhone,
|
||||
getCategoryList,
|
||||
getCategoryTree,
|
||||
getChannelList,
|
||||
getFreeChannelList,
|
||||
getChannelDetail,
|
||||
getProgramList,
|
||||
getProgramDetail,
|
||||
getSubscriptionList,
|
||||
canSubscribe,
|
||||
subscribe,
|
||||
unsubscribe,
|
||||
addHistory,
|
||||
getHistoryList,
|
||||
addFavorite,
|
||||
removeFavorite,
|
||||
getFavoriteList,
|
||||
toggleLike,
|
||||
getUserInfo,
|
||||
subscribeChannel,
|
||||
unlockChannel,
|
||||
queryPayStatus
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
/**
|
||||
* 早安电台 — 音频管理器
|
||||
* 封装 wx.getBackgroundAudioManager(),管理全局背景播放
|
||||
* 确保小程序切后台后音频不断
|
||||
* 播放时自动上报收听历史
|
||||
*/
|
||||
|
||||
const api = require('./api')
|
||||
|
||||
let bgAudioManager = null
|
||||
let appInstance = null
|
||||
let _switching = false // 切换音频时的锁,防止 onStop 事件干扰
|
||||
|
||||
/**
|
||||
* 初始化音频管理器
|
||||
* @param {Object} app - App 实例
|
||||
*/
|
||||
function init(app) {
|
||||
appInstance = app
|
||||
bgAudioManager = wx.getBackgroundAudioManager()
|
||||
|
||||
// 绑定音频事件
|
||||
bgAudioManager.onPlay(() => {
|
||||
_switching = false // 新音频开始播放,释放锁
|
||||
updatePlayState(true)
|
||||
})
|
||||
|
||||
bgAudioManager.onPause(() => {
|
||||
updatePlayState(false)
|
||||
// 暂停时上报进度
|
||||
reportHistory()
|
||||
})
|
||||
|
||||
bgAudioManager.onStop(() => {
|
||||
// 如果正在切换音频,不处理 onStop(新的 src 设置会触发旧的 onStop)
|
||||
if (_switching) return
|
||||
|
||||
reportHistory()
|
||||
updatePlayState(false)
|
||||
appInstance.globalData.currentTime = 0
|
||||
appInstance.emit('playerStateChange', getState())
|
||||
})
|
||||
|
||||
bgAudioManager.onEnded(() => {
|
||||
reportHistory()
|
||||
updatePlayState(false)
|
||||
appInstance.globalData.currentTime = appInstance.globalData.duration
|
||||
appInstance.emit('playerStateChange', getState())
|
||||
})
|
||||
|
||||
// 进度更新回调
|
||||
bgAudioManager.onTimeUpdate(() => {
|
||||
if (_switching) return
|
||||
const ct = Math.floor(bgAudioManager.currentTime || 0)
|
||||
const dur = Math.floor(bgAudioManager.duration || 0)
|
||||
appInstance.globalData.currentTime = ct
|
||||
if (dur > 0) {
|
||||
appInstance.globalData.duration = dur
|
||||
}
|
||||
appInstance.emit('timeUpdate', { currentTime: ct, duration: dur })
|
||||
})
|
||||
|
||||
bgAudioManager.onError((err) => {
|
||||
console.error('[AudioManager] 播放出错:', err)
|
||||
_switching = false
|
||||
updatePlayState(false)
|
||||
})
|
||||
|
||||
bgAudioManager.onWaiting(() => {
|
||||
console.log('[AudioManager] 缓冲中...')
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新播放状态并通知
|
||||
*/
|
||||
function updatePlayState(isPlaying) {
|
||||
appInstance.globalData.isPlaying = isPlaying
|
||||
appInstance.emit('playerStateChange', getState())
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前播放器状态
|
||||
*/
|
||||
function getState() {
|
||||
return {
|
||||
activeContent: appInstance.globalData.activeContent,
|
||||
isPlaying: appInstance.globalData.isPlaying,
|
||||
currentTime: appInstance.globalData.currentTime,
|
||||
duration: appInstance.globalData.duration,
|
||||
playbackRate: appInstance.globalData.playbackRate
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取音频 URL
|
||||
*/
|
||||
function getAudioUrl(content) {
|
||||
if (!content) return ''
|
||||
if (content.audio && content.audio.url) return content.audio.url
|
||||
if (content.audioUrl) return content.audioUrl
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 上报收听历史到后端
|
||||
*/
|
||||
function reportHistory() {
|
||||
const content = appInstance.globalData.activeContent
|
||||
if (!content || !content.id) return
|
||||
|
||||
api.addHistory({
|
||||
programId: content.id,
|
||||
progress: appInstance.globalData.currentTime || 0,
|
||||
duration: appInstance.globalData.duration || content.duration || 0
|
||||
}).catch(function (err) {
|
||||
console.warn('[AudioManager] 上报历史失败:', err)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放指定内容
|
||||
* @param {Object} content - 音频内容对象 { id, title, duration, audioUrl/audio.url, ... }
|
||||
*/
|
||||
function playContent(content) {
|
||||
if (!bgAudioManager || !content) return
|
||||
|
||||
var audioUrl = getAudioUrl(content)
|
||||
if (!audioUrl) {
|
||||
console.error('[AudioManager] 音频内容缺少 audioUrl:', content)
|
||||
wx.showToast({ title: '音频地址无效', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
// 如果当前有播放内容,先上报历史
|
||||
var oldContent = appInstance.globalData.activeContent
|
||||
if (oldContent && oldContent.id && oldContent.id !== content.id) {
|
||||
reportHistory()
|
||||
}
|
||||
|
||||
// 标记正在切换,防止旧音频的 onStop 干扰
|
||||
_switching = true
|
||||
|
||||
appInstance.globalData.activeContent = content
|
||||
appInstance.globalData.currentTime = 0
|
||||
appInstance.globalData.duration = content.duration || 0
|
||||
|
||||
// 设置背景音频属性(必须设置 title,否则 iOS 上不允许播放)
|
||||
bgAudioManager.title = content.title || '早安电台'
|
||||
bgAudioManager.singer = '早安电台'
|
||||
bgAudioManager.epname = content.title || '早安电台'
|
||||
bgAudioManager.coverImgUrl = (content.cover && content.cover.url) || content.coverUrl || ''
|
||||
|
||||
// 设置音频源(这会触发旧音频的 onStop,然后自动开始播放新音频)
|
||||
bgAudioManager.src = audioUrl
|
||||
|
||||
appInstance.emit('playerStateChange', getState())
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换播放/暂停
|
||||
* 关键:如果音频已结束或 src 被回收,需要重新设置 src 才能恢复播放
|
||||
*/
|
||||
function togglePlay() {
|
||||
if (!bgAudioManager || !appInstance.globalData.activeContent) return
|
||||
|
||||
if (appInstance.globalData.isPlaying) {
|
||||
bgAudioManager.pause()
|
||||
} else {
|
||||
// 检查 bgAudioManager 是否还有有效的 src
|
||||
// 音频结束/被系统回收后,src 会变为空,此时 play() 无效
|
||||
// 需要重新设置 src 来恢复播放
|
||||
var currentSrc = ''
|
||||
try {
|
||||
currentSrc = bgAudioManager.src
|
||||
} catch (e) {
|
||||
currentSrc = ''
|
||||
}
|
||||
|
||||
if (!currentSrc) {
|
||||
// src 已被清空(音频结束、系统回收等),重新播放
|
||||
playContent(appInstance.globalData.activeContent)
|
||||
} else {
|
||||
bgAudioManager.play()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转到指定时间(秒)
|
||||
*/
|
||||
function seekTo(time) {
|
||||
if (!bgAudioManager) return
|
||||
bgAudioManager.seek(time)
|
||||
appInstance.globalData.currentTime = time
|
||||
appInstance.emit('timeUpdate', {
|
||||
currentTime: time,
|
||||
duration: appInstance.globalData.duration
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置播放速率
|
||||
* @param {number} rate - 0.5 / 0.75 / 1.0 / 1.25 / 1.5 / 2.0
|
||||
*/
|
||||
function setPlaybackRate(rate) {
|
||||
if (!bgAudioManager) return
|
||||
bgAudioManager.playbackRate = rate
|
||||
appInstance.globalData.playbackRate = rate
|
||||
appInstance.emit('playerStateChange', getState())
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init,
|
||||
getState,
|
||||
playContent,
|
||||
togglePlay,
|
||||
seekTo,
|
||||
setPlaybackRate
|
||||
}
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
/**
|
||||
* 早安电台 — 模拟数据
|
||||
* 12个垂直频道定义 + 动态生成音频内容
|
||||
*/
|
||||
|
||||
/**
|
||||
* 频道领域定义
|
||||
* icon: 使用 emoji 或 TDesign icon 名称作为图标标识
|
||||
* bgColor: 频道品牌色(用于渐变背景)
|
||||
* bgColorLight: 浅色版本(用于标签、背景点缀)
|
||||
*/
|
||||
const DOMAINS = [
|
||||
{
|
||||
id: 'career',
|
||||
name: '职场成长',
|
||||
description: '职场沟通、管理技巧、晋升干货',
|
||||
tag: '每日3个职场技巧',
|
||||
icon: '💼',
|
||||
bgColor: '#3B82F6',
|
||||
bgColorEnd: '#2563EB',
|
||||
bgColorLight: 'rgba(59,130,246,0.1)'
|
||||
},
|
||||
{
|
||||
id: 'tech',
|
||||
name: '程序员早报',
|
||||
description: '技术资讯、开源动态、编程技巧',
|
||||
tag: '每日3条技术简讯',
|
||||
icon: '💻',
|
||||
bgColor: '#374151',
|
||||
bgColorEnd: '#111827',
|
||||
bgColorLight: 'rgba(55,65,81,0.1)'
|
||||
},
|
||||
{
|
||||
id: 'ecommerce',
|
||||
name: '电商/跨境资讯',
|
||||
description: '平台规则、选品技巧、流量玩法',
|
||||
tag: '电商人每日必听',
|
||||
icon: '🛒',
|
||||
bgColor: '#F97316',
|
||||
bgColorEnd: '#EA580C',
|
||||
bgColorLight: 'rgba(249,115,22,0.1)'
|
||||
},
|
||||
{
|
||||
id: 'finance',
|
||||
name: '财经轻资讯',
|
||||
description: '股市简讯、理财知识、行业风口',
|
||||
tag: '轻松懂财经',
|
||||
icon: '📈',
|
||||
bgColor: '#EF4444',
|
||||
bgColorEnd: '#B91C1C',
|
||||
bgColorLight: 'rgba(239,68,68,0.1)'
|
||||
},
|
||||
{
|
||||
id: 'health',
|
||||
name: '健康养生',
|
||||
description: '晨间养生、饮食建议、作息调理',
|
||||
tag: '每日养生小知识',
|
||||
icon: '🌿',
|
||||
bgColor: '#22C55E',
|
||||
bgColorEnd: '#16A34A',
|
||||
bgColorLight: 'rgba(34,197,94,0.1)'
|
||||
},
|
||||
{
|
||||
id: 'reading',
|
||||
name: '读书文摘',
|
||||
description: '书籍摘要、名言解读、阅读感悟',
|
||||
tag: '每日一篇读书感悟',
|
||||
icon: '📖',
|
||||
bgColor: '#D97706',
|
||||
bgColorEnd: '#92400E',
|
||||
bgColorLight: 'rgba(217,119,6,0.1)'
|
||||
},
|
||||
{
|
||||
id: 'parenting',
|
||||
name: '育儿晨读',
|
||||
description: '育儿技巧、亲子沟通、启蒙知识',
|
||||
tag: '宝妈每日育儿指南',
|
||||
icon: '👶',
|
||||
bgColor: '#EC4899',
|
||||
bgColorEnd: '#DB2777',
|
||||
bgColorLight: 'rgba(236,72,153,0.1)'
|
||||
},
|
||||
{
|
||||
id: 'psychology',
|
||||
name: '心理学小知识',
|
||||
description: '情绪管理、人际关系、心理常识',
|
||||
tag: '读懂自己与他人',
|
||||
icon: '🧠',
|
||||
bgColor: '#8B5CF6',
|
||||
bgColorEnd: '#7C3AED',
|
||||
bgColorLight: 'rgba(139,92,246,0.1)'
|
||||
},
|
||||
{
|
||||
id: 'english',
|
||||
name: '职场英语',
|
||||
description: '常用句型、单词积累、口语技巧',
|
||||
tag: '每日3句职场英语',
|
||||
icon: '🌐',
|
||||
bgColor: '#6366F1',
|
||||
bgColorEnd: '#4F46E5',
|
||||
bgColorLight: 'rgba(99,102,241,0.1)'
|
||||
},
|
||||
{
|
||||
id: 'startup',
|
||||
name: '创业干货',
|
||||
description: '创业案例、融资动态、运营技巧',
|
||||
tag: '创业者每日灵感',
|
||||
icon: '🚀',
|
||||
bgColor: '#F43F5E',
|
||||
bgColorEnd: '#E11D48',
|
||||
bgColorLight: 'rgba(244,63,94,0.1)'
|
||||
},
|
||||
{
|
||||
id: 'design',
|
||||
name: '设计灵感',
|
||||
description: '设计趋势、作品赏析、技巧分享',
|
||||
tag: '每日设计灵感',
|
||||
icon: '🎨',
|
||||
bgColor: '#14B8A6',
|
||||
bgColorEnd: '#0D9488',
|
||||
bgColorLight: 'rgba(20,184,166,0.1)'
|
||||
},
|
||||
{
|
||||
id: 'speaking',
|
||||
name: '职场口才',
|
||||
description: '沟通技巧、演讲方法、表达逻辑',
|
||||
tag: '提升口才每一天',
|
||||
icon: '🎙️',
|
||||
bgColor: '#06B6D4',
|
||||
bgColorEnd: '#0891B2',
|
||||
bgColorLight: 'rgba(6,182,212,0.1)'
|
||||
}
|
||||
]
|
||||
|
||||
/**
|
||||
* 动态生成模拟音频内容
|
||||
* 为每个频道生成最近 5 天的音频记录
|
||||
*/
|
||||
function generateMockContent() {
|
||||
const contents = []
|
||||
const today = new Date()
|
||||
|
||||
DOMAINS.forEach(function (domain) {
|
||||
for (var i = 0; i < 5; i++) {
|
||||
var d = new Date(today)
|
||||
d.setDate(today.getDate() - i)
|
||||
var year = d.getFullYear()
|
||||
var month = String(d.getMonth() + 1).padStart(2, '0')
|
||||
var day = String(d.getDate()).padStart(2, '0')
|
||||
var dateStr = year + '-' + month + '-' + day
|
||||
|
||||
contents.push({
|
||||
id: domain.id + '-' + dateStr,
|
||||
domainId: domain.id,
|
||||
date: dateStr,
|
||||
title: i === 0
|
||||
? '[今日更新] ' + domain.name + '晨间电台'
|
||||
: dateStr + ' ' + domain.name + '往期回顾',
|
||||
duration: 60 + Math.floor(Math.random() * 60), // 60~120 秒
|
||||
content: '早上好,今天是' + (d.getMonth() + 1) + '月' + d.getDate() + '日。' +
|
||||
'欢迎收听' + domain.name + '频道,这里是为您精选的晨间干货内容...\n' +
|
||||
'(这里是模拟生成的1分钟左右音频文案内容,包含3个核心知识点和一个金句作为结尾。祝您有美好的一天!)',
|
||||
audioUrl: '' // 实际项目中由后端提供
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return contents
|
||||
}
|
||||
|
||||
const MOCK_AUDIO_CONTENTS = generateMockContent()
|
||||
|
||||
/**
|
||||
* 频道分类映射
|
||||
*/
|
||||
const DOMAIN_CATEGORIES = {
|
||||
'全部': null, // null 表示显示所有
|
||||
'职场': ['career', 'english', 'speaking', 'startup'],
|
||||
'科技': ['tech', 'design'],
|
||||
'生活': ['health', 'parenting', 'reading', 'psychology', 'ecommerce', 'finance']
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
DOMAINS,
|
||||
MOCK_AUDIO_CONTENTS,
|
||||
DOMAIN_CATEGORIES,
|
||||
generateMockContent
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* 早安电台 — API 请求封装
|
||||
* 支持 Promise、BaseURL 配置、自动附带 Token
|
||||
*/
|
||||
|
||||
// 接口基础地址(预留占位符,对接后端时替换)
|
||||
const API_BASE_URL = 'https://radio.sundynix.cn/api'
|
||||
//const API_BASE_URL = 'http://192.168.0.184:8888'
|
||||
|
||||
|
||||
/**
|
||||
* 获取本地存储的 token
|
||||
*/
|
||||
function getToken() {
|
||||
return wx.getStorageSync('token') || ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用请求方法
|
||||
* @param {Object} options
|
||||
* @param {string} options.url - 接口路径(不含 BaseURL)
|
||||
* @param {string} [options.method='GET'] - 请求方法
|
||||
* @param {Object} [options.data] - 请求参数
|
||||
* @param {Object} [options.header] - 自定义请求头
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function request(options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const token = getToken()
|
||||
|
||||
wx.request({
|
||||
url: `${API_BASE_URL}${options.url}`,
|
||||
method: options.method || 'GET',
|
||||
data: options.data || {},
|
||||
header: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': token ? `Bearer ${token}` : '',
|
||||
...options.header
|
||||
},
|
||||
success(res) {
|
||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||
resolve(res.data)
|
||||
} else if (res.statusCode === 401) {
|
||||
// Token 过期或未登录,跳转到登录
|
||||
wx.removeStorageSync('token')
|
||||
wx.redirectTo({ url: '/pages/splash/index' })
|
||||
reject(new Error('未授权,请重新登录'))
|
||||
} else {
|
||||
reject(new Error(res.data.message || `请求失败: ${res.statusCode}`))
|
||||
}
|
||||
},
|
||||
fail(err) {
|
||||
reject(new Error(err.errMsg || '网络请求失败'))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 快捷方法
|
||||
*/
|
||||
function get(url, data) {
|
||||
return request({ url, method: 'GET', data })
|
||||
}
|
||||
|
||||
function post(url, data) {
|
||||
return request({ url, method: 'POST', data })
|
||||
}
|
||||
|
||||
function put(url, data) {
|
||||
return request({ url, method: 'PUT', data })
|
||||
}
|
||||
|
||||
function del(url, data) {
|
||||
return request({ url, method: 'DELETE', data })
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
API_BASE_URL,
|
||||
request,
|
||||
get,
|
||||
post,
|
||||
put,
|
||||
del
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* 早安电台 — 通用工具函数
|
||||
*/
|
||||
|
||||
/**
|
||||
* 秒数格式化为 mm:ss
|
||||
* @param {number} seconds - 总秒数
|
||||
* @returns {string} - 如 "01:30"
|
||||
*/
|
||||
function formatTime(seconds) {
|
||||
seconds = Math.floor(seconds || 0)
|
||||
var min = Math.floor(seconds / 60)
|
||||
var sec = seconds % 60
|
||||
return padZero(min) + ':' + padZero(sec)
|
||||
}
|
||||
|
||||
/**
|
||||
* 补零
|
||||
*/
|
||||
function padZero(num) {
|
||||
return num < 10 ? '0' + num : '' + num
|
||||
}
|
||||
|
||||
/**
|
||||
* 日期格式化
|
||||
* @param {Date|string} date
|
||||
* @param {string} [format='YYYY-MM-DD']
|
||||
* @returns {string}
|
||||
*/
|
||||
function formatDate(date, format) {
|
||||
if (typeof date === 'string') {
|
||||
date = new Date(date.replace(/-/g, '/'))
|
||||
}
|
||||
format = format || 'YYYY-MM-DD'
|
||||
|
||||
var year = date.getFullYear()
|
||||
var month = padZero(date.getMonth() + 1)
|
||||
var day = padZero(date.getDate())
|
||||
|
||||
return format
|
||||
.replace('YYYY', year)
|
||||
.replace('MM', month)
|
||||
.replace('DD', day)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取星期几
|
||||
* @param {Date} [date]
|
||||
* @returns {string} 周一~周日
|
||||
*/
|
||||
function getWeekDay(date) {
|
||||
date = date || new Date()
|
||||
var days = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
|
||||
return days[date.getDay()]
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取今天的日期字符串 YYYY-MM-DD
|
||||
*/
|
||||
function getTodayStr() {
|
||||
return formatDate(new Date())
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取友好的日期显示
|
||||
* @param {string} dateStr - YYYY-MM-DD
|
||||
* @returns {string} 如 "今天"、"昨天"、"3月1日"
|
||||
*/
|
||||
function getFriendlyDate(dateStr) {
|
||||
var today = getTodayStr()
|
||||
if (dateStr === today) return '今天'
|
||||
|
||||
var yesterday = new Date()
|
||||
yesterday.setDate(yesterday.getDate() - 1)
|
||||
if (dateStr === formatDate(yesterday)) return '昨天'
|
||||
|
||||
var d = new Date(dateStr.replace(/-/g, '/'))
|
||||
return (d.getMonth() + 1) + '月' + d.getDate() + '日'
|
||||
}
|
||||
|
||||
/**
|
||||
* 日期字符串替换 - 为 .
|
||||
*/
|
||||
function dateToDot(dateStr) {
|
||||
return (dateStr || '').replace(/-/g, '.')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取日期显示 X月X日
|
||||
*/
|
||||
function getDateDisplay(date) {
|
||||
date = date || new Date()
|
||||
return (date.getMonth() + 1) + '月' + date.getDate() + '日'
|
||||
}
|
||||
|
||||
/**
|
||||
* 节流函数
|
||||
*/
|
||||
function throttle(fn, delay) {
|
||||
var timer = null
|
||||
return function () {
|
||||
if (timer) return
|
||||
var args = arguments
|
||||
var context = this
|
||||
timer = setTimeout(function () {
|
||||
fn.apply(context, args)
|
||||
timer = null
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
formatTime,
|
||||
padZero,
|
||||
formatDate,
|
||||
getWeekDay,
|
||||
getTodayStr,
|
||||
getFriendlyDate,
|
||||
dateToDot,
|
||||
getDateDisplay,
|
||||
throttle
|
||||
}
|
||||
Reference in New Issue
Block a user