// 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} 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} 是否已登录 */ 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' } } })