ai-c/app.js
2026-02-02 18:21:32 +08:00

538 lines
14 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.

// 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'
}
}
})