/** * Soul创业派对 - 小程序入口 * 开发: 卡若 */ App({ globalData: { // API基础地址 - 连接真实后端 baseUrl: 'https://soul.quwanzhi.com', // 小程序配置 - 真实AppID appId: 'wxb8bbb2b10dec74aa', // 微信支付配置 mchId: '1318592501', // 商户号 // 用户信息 userInfo: null, openId: null, // 微信openId,支付必需 isLoggedIn: false, // 书籍数据 bookData: null, totalSections: 62, // 购买记录 purchasedSections: [], hasFullBook: false, // 推荐绑定 pendingReferralCode: null, // 待绑定的推荐码 // 主题配置 theme: { brandColor: '#00CED1', brandSecondary: '#20B2AA', goldColor: '#FFD700', bgColor: '#000000', cardBg: '#1c1c1e' }, // 系统信息 systemInfo: null, statusBarHeight: 44, navBarHeight: 88, // TabBar相关 currentTab: 0 }, onLaunch(options) { // 获取系统信息 this.getSystemInfo() // 检查登录状态 this.checkLoginStatus() // 加载书籍数据 this.loadBookData() // 检查更新 this.checkUpdate() // 处理分享参数(推荐码绑定) this.handleReferralCode(options) }, // 小程序显示时也检查分享参数 onShow(options) { this.handleReferralCode(options) }, // 处理推荐码绑定 handleReferralCode(options) { const query = options?.query || {} const refCode = query.ref || query.referralCode if (refCode) { console.log('[App] 检测到推荐码:', refCode) // 检查是否已经绑定过 const boundRef = wx.getStorageSync('boundReferralCode') if (boundRef && boundRef !== refCode) { console.log('[App] 已绑定过其他推荐码,跳过') return } // 保存待绑定的推荐码 this.globalData.pendingReferralCode = refCode wx.setStorageSync('pendingReferralCode', refCode) // 如果已登录,立即绑定 if (this.globalData.isLoggedIn && this.globalData.userInfo) { this.bindReferralCode(refCode) } } }, // 绑定推荐码到用户 async bindReferralCode(refCode) { try { const userId = this.globalData.userInfo?.id if (!userId || !refCode) return // 检查是否已绑定 const boundRef = wx.getStorageSync('boundReferralCode') if (boundRef) { console.log('[App] 已绑定推荐码,跳过') return } console.log('[App] 绑定推荐码:', refCode, '到用户:', userId) // 调用API绑定推荐关系 const res = await this.request('/api/referral/bind', { method: 'POST', data: { userId, referralCode: refCode } }) if (res.success) { console.log('[App] 推荐码绑定成功') wx.setStorageSync('boundReferralCode', refCode) this.globalData.pendingReferralCode = null wx.removeStorageSync('pendingReferralCode') } } catch (e) { console.error('[App] 绑定推荐码失败:', e) } }, // 获取系统信息 getSystemInfo() { try { const systemInfo = wx.getSystemInfoSync() this.globalData.systemInfo = systemInfo this.globalData.statusBarHeight = systemInfo.statusBarHeight || 44 // 计算导航栏高度 const menuButton = wx.getMenuButtonBoundingClientRect() if (menuButton) { this.globalData.navBarHeight = (menuButton.top - systemInfo.statusBarHeight) * 2 + menuButton.height + systemInfo.statusBarHeight } } catch (e) { console.error('获取系统信息失败:', e) } }, // 检查登录状态 checkLoginStatus() { try { const userInfo = wx.getStorageSync('userInfo') const token = wx.getStorageSync('token') if (userInfo && token) { this.globalData.userInfo = userInfo this.globalData.isLoggedIn = true this.globalData.purchasedSections = userInfo.purchasedSections || [] this.globalData.hasFullBook = userInfo.hasFullBook || false } } catch (e) { console.error('检查登录状态失败:', e) } }, // 加载书籍数据 async loadBookData() { try { // 先从缓存加载 const cachedData = wx.getStorageSync('bookData') if (cachedData) { this.globalData.bookData = cachedData } // 从服务器获取最新数据 const res = await this.request('/api/book/all-chapters') if (res && res.data) { this.globalData.bookData = res.data wx.setStorageSync('bookData', res.data) } } catch (e) { console.error('加载书籍数据失败:', e) } }, // 检查更新 checkUpdate() { if (wx.canIUse('getUpdateManager')) { const updateManager = wx.getUpdateManager() updateManager.onCheckForUpdate((res) => { if (res.hasUpdate) { console.log('发现新版本') } }) updateManager.onUpdateReady(() => { wx.showModal({ title: '更新提示', content: '新版本已准备好,是否重启应用?', success: (res) => { if (res.confirm) { updateManager.applyUpdate() } } }) }) updateManager.onUpdateFailed(() => { wx.showToast({ title: '更新失败,请稍后重试', icon: 'none' }) }) } }, // 统一请求方法 request(url, options = {}) { return new Promise((resolve, reject) => { const token = wx.getStorageSync('token') wx.request({ url: this.globalData.baseUrl + url, method: options.method || 'GET', data: options.data || {}, header: { 'Content-Type': 'application/json', 'Authorization': token ? `Bearer ${token}` : '', ...options.header }, success: (res) => { if (res.statusCode === 200) { resolve(res.data) } else if (res.statusCode === 401) { // 未授权,清除登录状态 this.logout() reject(new Error('未授权')) } else { reject(new Error(res.data?.message || '请求失败')) } }, fail: (err) => { reject(err) } }) }) }, // 登录方法 - 获取openId用于支付 async login() { try { // 获取微信登录code const loginRes = await new Promise((resolve, reject) => { wx.login({ success: resolve, fail: reject }) }) console.log('[App] 获取登录code成功') try { // 发送code到服务器获取openId const res = await this.request('/api/miniprogram/login', { method: 'POST', data: { code: loginRes.code } }) if (res.success && res.data) { // 保存openId if (res.data.openId) { this.globalData.openId = res.data.openId wx.setStorageSync('openId', res.data.openId) console.log('[App] 获取openId成功') } // 保存用户信息 if (res.data.user) { this.globalData.userInfo = res.data.user this.globalData.isLoggedIn = true this.globalData.purchasedSections = res.data.user.purchasedSections || [] this.globalData.hasFullBook = res.data.user.hasFullBook || false wx.setStorageSync('userInfo', res.data.user) wx.setStorageSync('token', res.data.token || '') } return res.data } } catch (apiError) { console.log('[App] API登录失败,使用模拟登录:', apiError.message) } // API不可用时使用模拟登录 return this.mockLogin() } catch (e) { console.error('[App] 登录失败:', e) // 最后尝试模拟登录 return this.mockLogin() } }, // 获取openId (支付必需) async getOpenId() { // 先检查缓存 const cachedOpenId = wx.getStorageSync('openId') if (cachedOpenId) { this.globalData.openId = cachedOpenId return cachedOpenId } // 没有缓存则登录获取 try { const loginRes = await new Promise((resolve, reject) => { wx.login({ success: resolve, fail: reject }) }) const res = await this.request('/api/miniprogram/login', { method: 'POST', data: { code: loginRes.code } }) if (res.success && res.data?.openId) { this.globalData.openId = res.data.openId wx.setStorageSync('openId', res.data.openId) return res.data.openId } } catch (e) { console.error('[App] 获取openId失败:', e) } return null }, // 模拟登录(后端不可用时使用) mockLogin() { const mockUser = { id: 'user_' + Date.now(), nickname: '访客用户', phone: '', avatar: '', referralCode: 'SOUL' + Date.now().toString(36).toUpperCase().slice(-6), purchasedSections: [], hasFullBook: false, earnings: 0, pendingEarnings: 0, referralCount: 0, createdAt: new Date().toISOString() } const mockToken = 'mock_token_' + Date.now() // 保存用户信息 this.globalData.userInfo = mockUser this.globalData.isLoggedIn = true this.globalData.purchasedSections = mockUser.purchasedSections this.globalData.hasFullBook = mockUser.hasFullBook wx.setStorageSync('userInfo', mockUser) wx.setStorageSync('token', mockToken) console.log('模拟登录成功:', mockUser) return { user: mockUser, token: mockToken } }, // 手机号登录 async loginWithPhone(phoneCode) { try { // 尝试API登录 const res = await this.request('/api/wechat/phone-login', { method: 'POST', data: { code: phoneCode } }) if (res.success && res.data) { this.globalData.userInfo = res.data.user this.globalData.isLoggedIn = true this.globalData.purchasedSections = res.data.user.purchasedSections || [] this.globalData.hasFullBook = res.data.user.hasFullBook || false wx.setStorageSync('userInfo', res.data.user) wx.setStorageSync('token', res.data.token) return res.data } } catch (e) { console.log('手机号API登录失败,使用模拟登录:', e) } // 回退到模拟登录 return this.mockLogin() }, // 退出登录 logout() { this.globalData.userInfo = null this.globalData.isLoggedIn = false this.globalData.purchasedSections = [] this.globalData.hasFullBook = false wx.removeStorageSync('userInfo') wx.removeStorageSync('token') }, // 检查是否已购买章节 hasPurchased(sectionId) { if (this.globalData.hasFullBook) return true return this.globalData.purchasedSections.includes(sectionId) }, // 获取章节总数 getTotalSections() { return this.globalData.totalSections }, // 切换TabBar switchTab(index) { this.globalData.currentTab = index }, // 显示Toast showToast(title, icon = 'none') { wx.showToast({ title, icon, duration: 2000 }) }, // 显示Loading showLoading(title = '加载中...') { wx.showLoading({ title, mask: true }) }, // 隐藏Loading hideLoading() { wx.hideLoading() } })