Files
2026-03-05 17:04:40 +08:00

305 lines
9.8 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 早安电台 — 全局应用入口
* 负责:全局状态管理、音频管理初始化、登录态检查、事件总线
*/
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
self.globalData.isVip = user.isVip === 1
self.globalData.vipExpireAt = user.vipExpireAt || null
wx.setStorageSync('token', token)
self.emit('loginStateChange', { isLoggedIn: true })
if (user.isVip === 1) {
self.emit('vipChange', { isVip: 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 '🌤️'
}