538 lines
14 KiB
JavaScript
538 lines
14 KiB
JavaScript
// app.js
|
||
const api = require('./utils/api')
|
||
const auth = require('./utils/auth')
|
||
const config = require('./config/index')
|
||
const { preloadAssets } = require('./utils/assets')
|
||
|
||
App({
|
||
globalData: {
|
||
// 用户信息
|
||
userInfo: null,
|
||
userId: null,
|
||
isLoggedIn: false,
|
||
|
||
// 登录状态检查
|
||
loginCheckComplete: false,
|
||
loginCheckCallbacks: [],
|
||
|
||
// 余额信息
|
||
flowerBalance: 0,
|
||
earnings: 0,
|
||
|
||
// 系统信息
|
||
systemInfo: null,
|
||
statusBarHeight: 44,
|
||
navBarHeight: 44, // 导航内容高度(胶囊按钮区域)
|
||
totalNavHeight: 88, // 总导航栏高度(状态栏+导航内容)
|
||
navHeight: 88, // 兼容旧代码,等同于 totalNavHeight
|
||
|
||
// 审核状态
|
||
auditStatus: 0, // 1 表示开启(审核中),0 表示关闭(正式环境)
|
||
|
||
// 配置
|
||
config: config
|
||
},
|
||
|
||
async onLaunch(options) {
|
||
console.log('小程序启动,options:', options)
|
||
|
||
// 获取审核状态(同步等待完成,确保页面能获取到正确的审核状态)
|
||
await this.loadAuditStatus()
|
||
console.log('审核状态加载完成:', this.globalData.auditStatus)
|
||
|
||
const defaultBaseUrl = String(config.API_BASE_URL || '').replace(/\/api$/, '')
|
||
if (defaultBaseUrl) {
|
||
this.globalData.baseUrl = defaultBaseUrl
|
||
if (!wx.getStorageSync('baseUrl')) {
|
||
wx.setStorageSync('baseUrl', defaultBaseUrl)
|
||
}
|
||
}
|
||
|
||
// 处理邀请码(爱心值系统)
|
||
if (options.query && options.query.invite) {
|
||
wx.setStorageSync('inviteCode', options.query.invite)
|
||
console.log('保存邀请码:', options.query.invite)
|
||
}
|
||
|
||
// 处理推荐码(佣金系统)
|
||
this.handleReferralCode(options)
|
||
|
||
// 获取系统信息
|
||
this.initSystemInfo()
|
||
|
||
// 预加载页面素材配置
|
||
await preloadAssets()
|
||
|
||
// 检查登录状态(支持持久化登录状态恢复)
|
||
this.checkLoginStatus()
|
||
},
|
||
|
||
onShow(options) {
|
||
console.log('小程序显示,options:', options)
|
||
|
||
if (options.query && options.query.invite) {
|
||
wx.setStorageSync('inviteCode', options.query.invite)
|
||
console.log('保存邀请码:', options.query.invite)
|
||
}
|
||
|
||
this.handleReferralCode(options)
|
||
},
|
||
|
||
/**
|
||
* 初始化系统信息
|
||
*/
|
||
initSystemInfo() {
|
||
try {
|
||
const systemInfo = wx.getSystemInfoSync()
|
||
const statusBarHeight = systemInfo.statusBarHeight || 44
|
||
|
||
// 获取胶囊按钮位置信息
|
||
const menuButton = wx.getMenuButtonBoundingClientRect()
|
||
|
||
// 正确计算导航栏高度:
|
||
// 导航内容高度 = 胶囊按钮高度 + 上下边距 × 2(上下对称)
|
||
// 胶囊按钮上边距 = menuButton.top - statusBarHeight
|
||
const navBarHeight = menuButton.height + (menuButton.top - statusBarHeight) * 2
|
||
|
||
// 总导航栏高度 = 状态栏高度 + 导航内容高度
|
||
const totalNavHeight = statusBarHeight + navBarHeight
|
||
|
||
this.globalData.systemInfo = systemInfo
|
||
this.globalData.statusBarHeight = statusBarHeight
|
||
this.globalData.navBarHeight = navBarHeight
|
||
this.globalData.totalNavHeight = totalNavHeight
|
||
this.globalData.navHeight = totalNavHeight // 兼容旧代码
|
||
} catch (e) {
|
||
console.error('获取系统信息失败', e)
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 检查登录状态
|
||
* 支持持久化登录状态恢复(7天免登录)
|
||
* Requirements: 2.2, 2.3, 2.4
|
||
*/
|
||
async checkLoginStatus() {
|
||
// 1. 先检查本地登录状态
|
||
if (!auth.isLoggedIn()) {
|
||
// 无本地登录状态,清除可能残留的数据
|
||
const cachedUserInfo = wx.getStorageSync(config.STORAGE_KEYS.USER_INFO)
|
||
if (cachedUserInfo) {
|
||
console.log('无有效Token,清除残留缓存')
|
||
auth.clearLoginInfo()
|
||
}
|
||
this.globalData.isLoggedIn = false
|
||
this.globalData.loginCheckComplete = true
|
||
this.executeLoginCheckCallbacks()
|
||
return
|
||
}
|
||
|
||
// 2. 有本地登录状态,验证服务端Token
|
||
try {
|
||
const result = await auth.verifyLogin()
|
||
|
||
if (result.valid && result.user) {
|
||
// Token有效,恢复登录状态
|
||
this.setUserInfo(result.user)
|
||
this.globalData.isLoggedIn = true
|
||
|
||
// 获取余额
|
||
this.loadUserBalance()
|
||
|
||
console.log('登录状态恢复成功', result.user)
|
||
} else {
|
||
// Token无效或过期
|
||
if (result.expired) {
|
||
console.log('Token已过期,需要重新登录')
|
||
}
|
||
this.handleTokenInvalid()
|
||
}
|
||
} catch (err) {
|
||
console.log('登录状态验证失败', err)
|
||
// 网络错误时保持本地状态,允许离线使用
|
||
const localUser = auth.getLocalUserInfo()
|
||
if (localUser) {
|
||
this.globalData.userInfo = localUser
|
||
this.globalData.userId = localUser.id
|
||
this.globalData.isLoggedIn = true
|
||
console.log('网络异常,使用本地缓存用户信息')
|
||
} else {
|
||
this.handleTokenInvalid()
|
||
}
|
||
}
|
||
|
||
// 标记登录检查完成
|
||
this.globalData.loginCheckComplete = true
|
||
|
||
// 执行等待中的回调
|
||
this.executeLoginCheckCallbacks()
|
||
},
|
||
|
||
/**
|
||
* 处理Token无效的情况
|
||
* Requirements: 2.4
|
||
*/
|
||
handleTokenInvalid() {
|
||
auth.clearLoginInfo()
|
||
this.globalData.userInfo = null
|
||
this.globalData.userId = null
|
||
this.globalData.isLoggedIn = false
|
||
this.globalData.flowerBalance = 0
|
||
this.globalData.earnings = 0
|
||
},
|
||
|
||
/**
|
||
* 等待登录检查完成
|
||
* @returns {Promise} 登录检查完成后resolve
|
||
*/
|
||
waitForLoginCheck() {
|
||
return new Promise((resolve) => {
|
||
if (this.globalData.loginCheckComplete) {
|
||
resolve(this.globalData.isLoggedIn)
|
||
} else {
|
||
this.globalData.loginCheckCallbacks.push(resolve)
|
||
}
|
||
})
|
||
},
|
||
|
||
/**
|
||
* 执行登录检查完成后的回调
|
||
*/
|
||
executeLoginCheckCallbacks() {
|
||
const callbacks = this.globalData.loginCheckCallbacks
|
||
this.globalData.loginCheckCallbacks = []
|
||
callbacks.forEach(callback => {
|
||
callback(this.globalData.isLoggedIn)
|
||
})
|
||
},
|
||
|
||
/**
|
||
* 设置用户信息
|
||
* @param {object} userInfo - 用户信息
|
||
*/
|
||
setUserInfo(userInfo) {
|
||
this.globalData.userInfo = userInfo
|
||
this.globalData.userId = userInfo.id
|
||
auth.saveUserInfo(userInfo, null) // 只更新用户信息,不更新token
|
||
},
|
||
|
||
/**
|
||
* 加载用户余额
|
||
*/
|
||
async loadUserBalance() {
|
||
try {
|
||
const res = await api.user.getBalance()
|
||
if (res.success && res.data) {
|
||
this.globalData.flowerBalance = res.data.flower_balance || 0
|
||
this.globalData.earnings = res.data.earnings || 0
|
||
}
|
||
} catch (err) {
|
||
console.error('获取余额失败', err)
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 微信登录
|
||
*/
|
||
async wxLogin() {
|
||
return new Promise((resolve, reject) => {
|
||
wx.login({
|
||
success: async (loginRes) => {
|
||
if (loginRes.code) {
|
||
try {
|
||
const res = await api.auth.wxLogin(loginRes.code)
|
||
if (res.success && res.data) {
|
||
// 计算过期时间(7天后)
|
||
const expiresAt = new Date()
|
||
expiresAt.setDate(expiresAt.getDate() + 7)
|
||
|
||
// 保存登录信息
|
||
auth.saveUserInfo(res.data.user, res.data.token, expiresAt.toISOString())
|
||
|
||
// 设置全局状态
|
||
this.globalData.userInfo = res.data.user
|
||
this.globalData.userId = res.data.user.id
|
||
this.globalData.isLoggedIn = true
|
||
|
||
// 获取余额
|
||
this.loadUserBalance()
|
||
|
||
resolve(res.data)
|
||
} else {
|
||
reject(res)
|
||
}
|
||
} catch (err) {
|
||
reject(err)
|
||
}
|
||
} else {
|
||
reject({ message: '微信登录失败' })
|
||
}
|
||
},
|
||
fail: (err) => {
|
||
reject(err)
|
||
}
|
||
})
|
||
})
|
||
},
|
||
|
||
/**
|
||
* 手机号登录
|
||
* @param {string} phone - 手机号
|
||
* @param {string} code - 验证码
|
||
*/
|
||
async phoneLogin(phone, code) {
|
||
try {
|
||
const res = await api.auth.phoneLogin(phone, code)
|
||
if (res.success && res.data) {
|
||
// 计算过期时间(7天后)
|
||
const expiresAt = new Date()
|
||
expiresAt.setDate(expiresAt.getDate() + 7)
|
||
|
||
// 保存登录信息
|
||
auth.saveUserInfo(res.data.user, res.data.token, expiresAt.toISOString())
|
||
|
||
// 设置全局状态
|
||
this.globalData.userInfo = res.data.user
|
||
this.globalData.userId = res.data.user.id
|
||
this.globalData.isLoggedIn = true
|
||
|
||
// 获取余额
|
||
this.loadUserBalance()
|
||
|
||
return res.data
|
||
}
|
||
throw res
|
||
} catch (err) {
|
||
throw err
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 微信手机号快速登录
|
||
* @param {string} code - 微信getPhoneNumber返回的code
|
||
* Requirements: 1.3, 1.4, 1.5, 2.1
|
||
*/
|
||
async wxPhoneLogin(code, loginCode) {
|
||
try {
|
||
const result = await auth.wxPhoneLogin(code, loginCode)
|
||
|
||
if (result.success && result.user) {
|
||
// 设置全局状态
|
||
this.globalData.userInfo = result.user
|
||
this.globalData.userId = result.user.id
|
||
this.globalData.isLoggedIn = true
|
||
|
||
// 获取余额
|
||
this.loadUserBalance()
|
||
|
||
console.log('微信手机号快速登录成功', result.user)
|
||
|
||
return { token: auth.getToken(), user: result.user }
|
||
}
|
||
throw { message: result.error || '登录失败' }
|
||
} catch (err) {
|
||
console.error('微信手机号快速登录失败', err)
|
||
throw err
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 退出登录
|
||
* Requirements: 2.5
|
||
*/
|
||
async logout() {
|
||
await auth.logout()
|
||
|
||
// 清除全局状态
|
||
this.globalData.userInfo = null
|
||
this.globalData.userId = null
|
||
this.globalData.isLoggedIn = false
|
||
this.globalData.flowerBalance = 0
|
||
this.globalData.earnings = 0
|
||
|
||
console.log('已退出登录,清除所有本地状态')
|
||
},
|
||
|
||
/**
|
||
* 刷新用户信息
|
||
*/
|
||
async refreshUserInfo() {
|
||
try {
|
||
const res = await api.auth.getCurrentUser()
|
||
if (res.success && res.data) {
|
||
this.setUserInfo(res.data)
|
||
}
|
||
} catch (err) {
|
||
console.error('刷新用户信息失败', err)
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 检查是否需要登录
|
||
* @param {boolean} showTip - 是否显示提示
|
||
*/
|
||
checkNeedLogin(showTip = true) {
|
||
if (!this.globalData.isLoggedIn) {
|
||
if (showTip) {
|
||
wx.showModal({
|
||
title: '提示',
|
||
content: '请先登录后再操作',
|
||
confirmText: '去登录',
|
||
confirmColor: '#b06ab3',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
// 跳转到个人中心进行登录
|
||
wx.switchTab({
|
||
url: '/pages/profile/profile'
|
||
})
|
||
}
|
||
}
|
||
})
|
||
}
|
||
return true
|
||
}
|
||
return false
|
||
},
|
||
|
||
/**
|
||
* 验证当前Token是否有效
|
||
* 用于页面需要确认登录状态时调用
|
||
* Requirements: 2.2, 2.4
|
||
* @returns {Promise<boolean>} Token是否有效
|
||
*/
|
||
async validateToken() {
|
||
if (!auth.isLoggedIn()) {
|
||
this.globalData.isLoggedIn = false
|
||
return false
|
||
}
|
||
|
||
try {
|
||
const result = await auth.verifyLogin()
|
||
|
||
if (result.valid && result.user) {
|
||
// Token有效,更新用户信息
|
||
this.globalData.userInfo = result.user
|
||
this.globalData.userId = result.user.id
|
||
this.globalData.isLoggedIn = true
|
||
return true
|
||
} else {
|
||
// Token无效
|
||
this.handleTokenInvalid()
|
||
return false
|
||
}
|
||
} catch (err) {
|
||
console.log('Token验证失败', err)
|
||
this.handleTokenInvalid()
|
||
return false
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 获取登录状态(等待初始化完成)
|
||
* 用于页面onLoad时获取准确的登录状态
|
||
* @returns {Promise<boolean>} 是否已登录
|
||
*/
|
||
async getLoginStatus() {
|
||
await this.waitForLoginCheck()
|
||
return this.globalData.isLoggedIn
|
||
},
|
||
|
||
/**
|
||
* 加载审核状态
|
||
*/
|
||
async loadAuditStatus() {
|
||
try {
|
||
const res = await api.common.getAuditStatus()
|
||
if (res.code === 0 && res.data) {
|
||
this.globalData.auditStatus = Number(res.data.auditStatus || 0)
|
||
console.log('获取审核状态成功:', this.globalData.auditStatus)
|
||
}
|
||
} catch (err) {
|
||
console.error('获取审核状态失败', err)
|
||
// 失败时默认为 0(正式环境),或根据需要设为 1(保守方案)
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 处理推荐码(佣金系统)
|
||
* 支持场景:
|
||
* 1. 小程序分享:path?referralCode=ABC12345
|
||
* 2. 朋友圈分享:query.referralCode
|
||
* 3. 小程序码:scene=r=ABC12345
|
||
*/
|
||
handleReferralCode(options) {
|
||
let referralCode = null
|
||
|
||
if (options.query && options.query.referralCode) {
|
||
referralCode = options.query.referralCode
|
||
console.log('从query获取推荐码(referralCode):', referralCode)
|
||
} else if (options.query && options.query.ref) {
|
||
referralCode = options.query.ref
|
||
console.log('从query获取推荐码(ref):', referralCode)
|
||
}
|
||
|
||
if (!referralCode && options.scene) {
|
||
referralCode = this.parseSceneReferralCode(options.scene)
|
||
if (referralCode) {
|
||
console.log('从scene获取推荐码:', referralCode)
|
||
}
|
||
}
|
||
|
||
if (referralCode) {
|
||
this.globalData.pendingReferralCode = referralCode
|
||
wx.setStorageSync('pendingReferralCode', referralCode)
|
||
console.log('推荐码已保存到本地:', referralCode)
|
||
|
||
const isLoggedIn = this.globalData.isLoggedIn || !!wx.getStorageSync('auth_token')
|
||
if (isLoggedIn) {
|
||
console.log('用户已登录,发起即时静默绑定')
|
||
const userId = this.globalData.userId || wx.getStorageSync('user_id')
|
||
const token = wx.getStorageSync('auth_token')
|
||
if (userId && token) {
|
||
auth.checkAndBindReferralCode(userId, token)
|
||
}
|
||
}
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 解析scene参数中的推荐码
|
||
* scene格式:r=ABC12345
|
||
*/
|
||
parseSceneReferralCode(scene) {
|
||
if (!scene) return null
|
||
|
||
try {
|
||
const decoded = decodeURIComponent(scene)
|
||
const match = decoded.match(/r=([A-Z0-9]+)/)
|
||
return match ? match[1] : null
|
||
} catch (error) {
|
||
console.error('解析scene失败:', error)
|
||
return null
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 全局转发配置
|
||
* 用户转发小程序时的默认配置
|
||
*/
|
||
onShareAppMessage() {
|
||
return {
|
||
title: '欢迎来到心伴俱乐部',
|
||
desc: '随时可聊 一直陪伴',
|
||
path: '/pages/index/index',
|
||
imageUrl: '/images/icon-heart-new.png'
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 全局分享到朋友圈配置
|
||
*/
|
||
onShareTimeline() {
|
||
return {
|
||
title: '欢迎来到心伴俱乐部 - 随时可聊 一直陪伴',
|
||
imageUrl: '/images/icon-heart-new.png'
|
||
}
|
||
}
|
||
})
|