491 lines
14 KiB
JavaScript
491 lines
14 KiB
JavaScript
/**
|
||
* Soul创业派对 - 小程序入口
|
||
* 开发: 卡若
|
||
*/
|
||
|
||
App({
|
||
globalData: {
|
||
// API基础地址 - 连接真实后端
|
||
// baseUrl: 'https://soul.quwanzhi.com',
|
||
baseUrl: 'http://localhost:3006',
|
||
|
||
// 小程序配置 - 真实AppID
|
||
appId: 'wxb8bbb2b10dec74aa',
|
||
|
||
// 订阅消息:用户点击「申请提现」→「立即提现」时会先弹出订阅授权窗
|
||
withdrawSubscribeTmplId: 'u3MbZGPRkrZIk-I7QdpwzFxnO_CeQPaCWF2FkiIablE',
|
||
|
||
// 微信支付配置
|
||
mchId: '1318592501', // 商户号
|
||
|
||
// 用户信息
|
||
userInfo: null,
|
||
openId: null, // 微信openId,支付必需
|
||
isLoggedIn: false,
|
||
|
||
// 书籍数据
|
||
bookData: null,
|
||
totalSections: 62,
|
||
|
||
// 购买记录
|
||
purchasedSections: [],
|
||
hasFullBook: false,
|
||
|
||
// 已读章节(仅统计有权限打开过的章节,用于首页「已读/待读」)
|
||
readSectionIds: [],
|
||
|
||
// 推荐绑定
|
||
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.globalData.readSectionIds = wx.getStorageSync('readSectionIds') || []
|
||
// 获取系统信息
|
||
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)
|
||
|
||
// 立即记录访问(不需要登录,用于统计"通过链接进的人数")
|
||
this.recordReferralVisit(refCode)
|
||
|
||
// 保存待绑定的推荐码(不再在前端做"只能绑定一次"的限制,让后端根据30天规则判断续期/抢夺)
|
||
this.globalData.pendingReferralCode = refCode
|
||
wx.setStorageSync('pendingReferralCode', refCode)
|
||
// 同步写入 referral_code,供章节/找伙伴支付时传给后端,订单会记录 referrer_id 与 referral_code
|
||
wx.setStorageSync('referral_code', refCode)
|
||
|
||
// 如果已登录,立即尝试绑定,由 /api/referral/bind 按 30 天规则决定 new / renew / takeover
|
||
if (this.globalData.isLoggedIn && this.globalData.userInfo) {
|
||
this.bindReferralCode(refCode)
|
||
}
|
||
}
|
||
},
|
||
|
||
// 记录推荐访问(不需要登录,用于统计)
|
||
async recordReferralVisit(refCode) {
|
||
try {
|
||
// 获取openId(如果有)
|
||
const openId = this.globalData.openId || wx.getStorageSync('openId') || ''
|
||
const userId = this.globalData.userInfo?.id || ''
|
||
|
||
await this.request('/api/referral/visit', {
|
||
method: 'POST',
|
||
data: {
|
||
referralCode: refCode,
|
||
visitorOpenId: openId,
|
||
visitorId: userId,
|
||
source: 'miniprogram',
|
||
page: getCurrentPages()[getCurrentPages().length - 1]?.route || ''
|
||
}
|
||
})
|
||
console.log('[App] 记录推荐访问成功')
|
||
} catch (e) {
|
||
console.log('[App] 记录推荐访问失败:', e.message)
|
||
// 忽略错误,不影响用户体验
|
||
}
|
||
},
|
||
|
||
// 绑定推荐码到用户
|
||
async bindReferralCode(refCode) {
|
||
try {
|
||
const userId = this.globalData.userInfo?.id
|
||
if (!userId || !refCode) 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] 推荐码绑定成功')
|
||
// 仅记录当前已绑定的推荐码,用于展示/调试;是否允许更换由后端根据30天规则判断
|
||
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 || '')
|
||
|
||
// 登录成功后,检查待绑定的推荐码并执行绑定
|
||
const pendingRef = wx.getStorageSync('pendingReferralCode') || this.globalData.pendingReferralCode
|
||
if (pendingRef) {
|
||
console.log('[App] 登录后自动绑定推荐码:', pendingRef)
|
||
this.bindReferralCode(pendingRef)
|
||
}
|
||
}
|
||
|
||
return res.data
|
||
}
|
||
} catch (apiError) {
|
||
console.log('[App] API登录失败:', apiError.message)
|
||
// 不使用模拟登录,提示用户网络问题
|
||
wx.showToast({ title: '网络异常,请重试', icon: 'none' })
|
||
return null
|
||
}
|
||
|
||
return null
|
||
} catch (e) {
|
||
console.error('[App] 登录失败:', e)
|
||
wx.showToast({ title: '登录失败,请重试', icon: 'none' })
|
||
return null
|
||
}
|
||
},
|
||
|
||
// 获取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)
|
||
// 接口同时返回 user 时视为登录,补全登录态并从登录开始绑定推荐码
|
||
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 || '')
|
||
const pendingRef = wx.getStorageSync('pendingReferralCode') || this.globalData.pendingReferralCode
|
||
if (pendingRef) {
|
||
console.log('[App] getOpenId 登录后自动绑定推荐码:', pendingRef)
|
||
this.bindReferralCode(pendingRef)
|
||
}
|
||
}
|
||
return res.data.openId
|
||
}
|
||
} catch (e) {
|
||
console.error('[App] 获取openId失败:', e)
|
||
}
|
||
|
||
return null
|
||
},
|
||
|
||
// 模拟登录已废弃 - 不再使用
|
||
// 现在必须使用真实的微信登录获取openId作为唯一标识
|
||
mockLogin() {
|
||
console.warn('[App] mockLogin已废弃,请使用真实登录')
|
||
return null
|
||
},
|
||
|
||
// 手机号登录
|
||
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)
|
||
|
||
// 登录成功后绑定推荐码
|
||
const pendingRef = wx.getStorageSync('pendingReferralCode') || this.globalData.pendingReferralCode
|
||
if (pendingRef) {
|
||
console.log('[App] 手机号登录后自动绑定推荐码:', pendingRef)
|
||
this.bindReferralCode(pendingRef)
|
||
}
|
||
|
||
return res.data
|
||
}
|
||
} catch (e) {
|
||
console.log('[App] 手机号登录失败:', e)
|
||
wx.showToast({ title: '登录失败,请重试', icon: 'none' })
|
||
}
|
||
|
||
return null
|
||
},
|
||
|
||
// 退出登录
|
||
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)
|
||
},
|
||
|
||
// 标记章节为已读(仅在有权限打开时由阅读页调用,用于首页已读/待读统计)
|
||
markSectionAsRead(sectionId) {
|
||
if (!sectionId) return
|
||
const list = this.globalData.readSectionIds || []
|
||
if (list.includes(sectionId)) return
|
||
list.push(sectionId)
|
||
this.globalData.readSectionIds = list
|
||
wx.setStorageSync('readSectionIds', list)
|
||
},
|
||
|
||
// 已读章节数(用于首页展示)
|
||
getReadCount() {
|
||
return (this.globalData.readSectionIds || []).length
|
||
},
|
||
|
||
// 获取章节总数
|
||
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()
|
||
}
|
||
})
|