first commit
This commit is contained in:
@@ -0,0 +1,299 @@
|
||||
/**
|
||||
* 早安电台 — 全局应用入口
|
||||
* 负责:全局状态管理、音频管理初始化、登录态检查、事件总线
|
||||
*/
|
||||
|
||||
const audioManager = require('./utils/audioManager')
|
||||
const api = require('./utils/api')
|
||||
|
||||
App({
|
||||
globalData: {
|
||||
// ======== 用户状态 ========
|
||||
isLoggedIn: false,
|
||||
isVip: false,
|
||||
userInfo: null,
|
||||
token: '',
|
||||
|
||||
// ======== 播放器状态 ========
|
||||
activeContent: null, // 当前播放的音频内容对象
|
||||
isPlaying: false,
|
||||
currentTime: 0, // 当前播放时间(秒)
|
||||
duration: 0, // 总时长(秒)
|
||||
playbackRate: 1.0, // 播放速率
|
||||
|
||||
// ======== 系统信息 ========
|
||||
statusBarHeight: 0,
|
||||
navBarHeight: 0,
|
||||
screenHeight: 0,
|
||||
windowHeight: 0,
|
||||
|
||||
// ======== 位置与天气 ========
|
||||
locationName: '', // 城市/区域名称
|
||||
weather: null // { desc, temp, icon }
|
||||
},
|
||||
|
||||
// ======== 事件总线 ========
|
||||
_events: {},
|
||||
|
||||
/**
|
||||
* 注册事件监听
|
||||
* @param {string} event - 事件名
|
||||
* @param {Function} callback - 回调函数
|
||||
*/
|
||||
on(event, callback) {
|
||||
if (!this._events[event]) {
|
||||
this._events[event] = []
|
||||
}
|
||||
this._events[event].push(callback)
|
||||
},
|
||||
|
||||
/**
|
||||
* 移除事件监听
|
||||
*/
|
||||
off(event, callback) {
|
||||
if (!this._events[event]) return
|
||||
if (callback) {
|
||||
this._events[event] = this._events[event].filter(cb => cb !== callback)
|
||||
} else {
|
||||
this._events[event] = []
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 触发事件
|
||||
*/
|
||||
emit(event, data) {
|
||||
if (!this._events[event]) return
|
||||
this._events[event].forEach(cb => cb(data))
|
||||
},
|
||||
|
||||
onLaunch() {
|
||||
// 获取系统信息,用于自定义导航栏计算
|
||||
try {
|
||||
const systemInfo = wx.getWindowInfo()
|
||||
const menuButton = wx.getMenuButtonBoundingClientRect()
|
||||
this.globalData.statusBarHeight = systemInfo.statusBarHeight || 0
|
||||
this.globalData.navBarHeight = (menuButton.top - systemInfo.statusBarHeight) * 2 + menuButton.height
|
||||
this.globalData.screenHeight = systemInfo.screenHeight
|
||||
this.globalData.windowHeight = systemInfo.windowHeight
|
||||
} catch (e) {
|
||||
this.globalData.statusBarHeight = 44
|
||||
this.globalData.navBarHeight = 44
|
||||
}
|
||||
|
||||
// 初始化音频管理器
|
||||
audioManager.init(this)
|
||||
|
||||
// 冷启动:获取位置+天气(当天内只请求一次)
|
||||
this._fetchLocationWeather()
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取位置和天气
|
||||
* 策略:当天内只请求一次,结果缓存于 Storage 和 globalData
|
||||
*/
|
||||
_fetchLocationWeather() {
|
||||
const self = this
|
||||
const today = new Date().toLocaleDateString()
|
||||
let cached = null
|
||||
try { cached = wx.getStorageSync('locationWeatherCache') } catch (e) { }
|
||||
|
||||
// 当天有效缓存,直接用(不发请求)
|
||||
if (cached && cached.date === today && cached.locationName) {
|
||||
self.globalData.locationName = cached.locationName
|
||||
self.globalData.weather = cached.weather || null
|
||||
self.emit('locationWeatherReady', {
|
||||
locationName: cached.locationName,
|
||||
weather: cached.weather || null
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 清掉无效缓存
|
||||
try { wx.removeStorageSync('locationWeatherCache') } catch (e) { }
|
||||
|
||||
// 请求微信定位
|
||||
wx.getLocation({
|
||||
type: 'gcj02',
|
||||
isHighAccuracy: false,
|
||||
success(locRes) {
|
||||
const longitude = locRes.longitude
|
||||
const latitude = locRes.latitude
|
||||
|
||||
// 调用后端:经纬度 → 城市名 + adcode
|
||||
api.getLocation(longitude, latitude)
|
||||
.then(function (res) {
|
||||
if (!res || res.code !== 200 || !res.data) return Promise.reject('位置解析失败')
|
||||
|
||||
const locationName = res.data.city || res.data.district || res.data.province || '未知'
|
||||
const adcode = res.data.adcode || ''
|
||||
|
||||
self.globalData.locationName = locationName
|
||||
// 先更新一次(有城市名,天气还没来)
|
||||
self.emit('locationWeatherReady', { locationName, weather: null })
|
||||
|
||||
if (!adcode) return Promise.reject('无 adcode,跳过天气')
|
||||
return api.getWeather(adcode)
|
||||
})
|
||||
.then(function (res) {
|
||||
if (!res || res.code !== 200 || !res.data) return
|
||||
|
||||
const w = res.data
|
||||
const weather = {
|
||||
desc: w.weather || w.desc || '',
|
||||
temp: w.temperature || w.temp || '',
|
||||
icon: _weatherIcon(w.weather || w.desc || '')
|
||||
}
|
||||
|
||||
self.globalData.weather = weather
|
||||
self.emit('locationWeatherReady', {
|
||||
locationName: self.globalData.locationName,
|
||||
weather
|
||||
})
|
||||
|
||||
// 存入 Storage 供当日复用
|
||||
try {
|
||||
wx.setStorageSync('locationWeatherCache', {
|
||||
date: today,
|
||||
locationName: self.globalData.locationName,
|
||||
weather
|
||||
})
|
||||
} catch (e) { }
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.warn('[位置天气] 请求失败:', err)
|
||||
})
|
||||
},
|
||||
fail(err) {
|
||||
console.warn('[位置天气] wx.getLocation 失败:', err.errMsg)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// ======== 用户相关方法 ========
|
||||
|
||||
/**
|
||||
* 小程序静默登录
|
||||
* wx.login() → code → 后端 /auth/miniLogin → token + user
|
||||
*/
|
||||
login() {
|
||||
const self = this
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.login({
|
||||
success(loginRes) {
|
||||
if (!loginRes.code) {
|
||||
reject(new Error('wx.login 获取 code 失败'))
|
||||
return
|
||||
}
|
||||
api.miniLogin(loginRes.code).then(res => {
|
||||
if (res.code === 200 && res.data) {
|
||||
const { token, user } = res.data
|
||||
self.globalData.isLoggedIn = true
|
||||
self.globalData.token = token
|
||||
self.globalData.userInfo = user
|
||||
wx.setStorageSync('token', token)
|
||||
self.emit('loginStateChange', { isLoggedIn: true })
|
||||
resolve(res.data)
|
||||
} else {
|
||||
reject(new Error(res.msg || '登录失败'))
|
||||
}
|
||||
}).catch(reject)
|
||||
},
|
||||
fail(err) {
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 订阅频道(调用后端)
|
||||
*/
|
||||
subscribeToDomain(channelId) {
|
||||
const self = this
|
||||
return api.subscribe(channelId).then(res => {
|
||||
if (res.code === 200) {
|
||||
self.emit('subscriptionChange', {})
|
||||
return true
|
||||
}
|
||||
wx.showToast({ title: res.msg || '订阅失败', icon: 'none' })
|
||||
return false
|
||||
}).catch(err => {
|
||||
wx.showToast({ title: err.message || '订阅失败', icon: 'none' })
|
||||
return false
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 取消订阅频道(调用后端)
|
||||
*/
|
||||
unsubscribeFromDomain(channelId) {
|
||||
const self = this
|
||||
return api.unsubscribe(channelId).then(res => {
|
||||
if (res.code === 200) {
|
||||
self.emit('subscriptionChange', {})
|
||||
return true
|
||||
}
|
||||
wx.showToast({ title: res.msg || '退订失败', icon: 'none' })
|
||||
return false
|
||||
}).catch(err => {
|
||||
wx.showToast({ title: err.message || '退订失败', icon: 'none' })
|
||||
return false
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 升级VIP
|
||||
*/
|
||||
upgradeVip() {
|
||||
this.globalData.isVip = true
|
||||
this.emit('vipChange', { isVip: true })
|
||||
},
|
||||
|
||||
// ======== 播放相关方法(代理到 audioManager) ========
|
||||
|
||||
/**
|
||||
* 播放指定内容
|
||||
*/
|
||||
playContent(content) {
|
||||
audioManager.playContent(content)
|
||||
},
|
||||
|
||||
/**
|
||||
* 切换播放/暂停
|
||||
*/
|
||||
togglePlay() {
|
||||
audioManager.togglePlay()
|
||||
},
|
||||
|
||||
/**
|
||||
* 跳转到指定时间
|
||||
*/
|
||||
seekTo(time) {
|
||||
audioManager.seekTo(time)
|
||||
},
|
||||
|
||||
/**
|
||||
* 设置播放速率
|
||||
*/
|
||||
setPlaybackRate(rate) {
|
||||
audioManager.setPlaybackRate(rate)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 天气描述 → emoji 图标
|
||||
* @param {string} desc - 天气描述,如"晴"、"多云"、"阵雨"
|
||||
*/
|
||||
function _weatherIcon(desc) {
|
||||
if (!desc) return '🌤'
|
||||
if (/晴/.test(desc)) return '☀️'
|
||||
if (/多云|阴/.test(desc)) return '☁️'
|
||||
if (/雷|电/.test(desc)) return '⛈️'
|
||||
if (/暴雨|大雨/.test(desc)) return '🌧️'
|
||||
if (/小雨|阵雨|中雨|雨/.test(desc)) return '🌦️'
|
||||
if (/雪|冰/.test(desc)) return '❄️'
|
||||
if (/雾|霾/.test(desc)) return '🌫️'
|
||||
if (/风|大风/.test(desc)) return '💨'
|
||||
return '🌤️'
|
||||
}
|
||||
Reference in New Issue
Block a user