miniprogram: 用永平版本替换(含超级个体、会员详情、提现等)
- 来源: 一场soul的创业实验-永平/soul/miniprogram - 新增: addresses/agreement/privacy/withdraw-records 等页面 - 新增: components/icon, utils/chapterAccessManager, readingTracker - 删除: 上传脚本、部署说明等冗余文件 - 同步永平最新结构和功能 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -10,6 +10,9 @@ App({
|
||||
|
||||
// 小程序配置 - 真实AppID
|
||||
appId: 'wxb8bbb2b10dec74aa',
|
||||
|
||||
// 订阅消息:用户点击「申请提现」→「立即提现」时会先弹出订阅授权窗
|
||||
withdrawSubscribeTmplId: 'u3MbZGPRkrZIk-I7QdpwzFxnO_CeQPaCWF2FkiIablE',
|
||||
|
||||
// 微信支付配置
|
||||
mchId: '1318592501', // 商户号
|
||||
@@ -27,6 +30,9 @@ App({
|
||||
purchasedSections: [],
|
||||
hasFullBook: false,
|
||||
|
||||
// 已读章节(仅统计有权限打开过的章节,用于首页「已读/待读」)
|
||||
readSectionIds: [],
|
||||
|
||||
// 推荐绑定
|
||||
pendingReferralCode: null, // 待绑定的推荐码
|
||||
|
||||
@@ -49,6 +55,7 @@ App({
|
||||
},
|
||||
|
||||
onLaunch(options) {
|
||||
this.globalData.readSectionIds = wx.getStorageSync('readSectionIds') || []
|
||||
// 获取系统信息
|
||||
this.getSystemInfo()
|
||||
|
||||
@@ -80,21 +87,16 @@ App({
|
||||
|
||||
// 立即记录访问(不需要登录,用于统计"通过链接进的人数")
|
||||
this.recordReferralVisit(refCode)
|
||||
|
||||
// 保存待绑定的推荐码(不再在前端做"只能绑定一次"的限制,让后端根据30天规则判断续期/抢夺)
|
||||
this.globalData.pendingReferralCode = refCode
|
||||
wx.setStorageSync('pendingReferralCode', refCode)
|
||||
// 同步写入 referral_code,供章节/找伙伴支付时传给后端,订单会记录 referrer_id 与 referral_code
|
||||
wx.setStorageSync('referral_code', refCode)
|
||||
|
||||
// 检查是否已经绑定过
|
||||
const boundRef = wx.getStorageSync('boundReferralCode')
|
||||
if (boundRef && boundRef !== refCode) {
|
||||
console.log('[App] 已绑定过其他推荐码,不更换绑定关系')
|
||||
// 但仍然记录访问,不return
|
||||
} else {
|
||||
// 保存待绑定的推荐码
|
||||
this.globalData.pendingReferralCode = refCode
|
||||
wx.setStorageSync('pendingReferralCode', refCode)
|
||||
|
||||
// 如果已登录,立即绑定
|
||||
if (this.globalData.isLoggedIn && this.globalData.userInfo) {
|
||||
this.bindReferralCode(refCode)
|
||||
}
|
||||
// 如果已登录,立即尝试绑定,由 /api/miniprogram/referral/bind 按 30 天规则决定 new / renew / takeover
|
||||
if (this.globalData.isLoggedIn && this.globalData.userInfo) {
|
||||
this.bindReferralCode(refCode)
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -106,7 +108,7 @@ App({
|
||||
const openId = this.globalData.openId || wx.getStorageSync('openId') || ''
|
||||
const userId = this.globalData.userInfo?.id || ''
|
||||
|
||||
await this.request('/api/referral/visit', {
|
||||
await this.request('/api/miniprogram/referral/visit', {
|
||||
method: 'POST',
|
||||
data: {
|
||||
referralCode: refCode,
|
||||
@@ -114,7 +116,8 @@ App({
|
||||
visitorId: userId,
|
||||
source: 'miniprogram',
|
||||
page: getCurrentPages()[getCurrentPages().length - 1]?.route || ''
|
||||
}
|
||||
},
|
||||
silent: true
|
||||
})
|
||||
console.log('[App] 记录推荐访问成功')
|
||||
} catch (e) {
|
||||
@@ -129,26 +132,21 @@ App({
|
||||
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', {
|
||||
const res = await this.request('/api/miniprogram/referral/bind', {
|
||||
method: 'POST',
|
||||
data: {
|
||||
userId,
|
||||
referralCode: refCode
|
||||
}
|
||||
},
|
||||
silent: true
|
||||
})
|
||||
|
||||
if (res.success) {
|
||||
console.log('[App] 推荐码绑定成功')
|
||||
// 仅记录当前已绑定的推荐码,用于展示/调试;是否允许更换由后端根据30天规则判断
|
||||
wx.setStorageSync('boundReferralCode', refCode)
|
||||
this.globalData.pendingReferralCode = null
|
||||
wx.removeStorageSync('pendingReferralCode')
|
||||
@@ -203,9 +201,10 @@ App({
|
||||
|
||||
// 从服务器获取最新数据
|
||||
const res = await this.request('/api/book/all-chapters')
|
||||
if (res && res.data) {
|
||||
this.globalData.bookData = res.data
|
||||
wx.setStorageSync('bookData', res.data)
|
||||
if (res && (res.data || res.chapters)) {
|
||||
const chapters = res.data || res.chapters || []
|
||||
this.globalData.bookData = chapters
|
||||
wx.setStorageSync('bookData', chapters)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载书籍数据失败:', e)
|
||||
@@ -244,11 +243,41 @@ App({
|
||||
}
|
||||
},
|
||||
|
||||
// 统一请求方法
|
||||
request(url, options = {}) {
|
||||
/**
|
||||
* 从 soul-api 返回体中取错误提示文案(兼容 message / error 字段)
|
||||
*/
|
||||
_getApiErrorMsg(data, defaultMsg = '请求失败') {
|
||||
if (!data || typeof data !== 'object') return defaultMsg
|
||||
const msg = data.message || data.error
|
||||
return (msg && String(msg).trim()) ? String(msg).trim() : defaultMsg
|
||||
},
|
||||
|
||||
/**
|
||||
* 统一请求方法。接口失败时会弹窗提示(与 soul-api 返回的 message/error 一致)。
|
||||
* @param {string|object} urlOrOptions - 接口路径,或 { url, method, data, header, silent }
|
||||
* @param {object} options - { method, data, header, silent }
|
||||
* @param {boolean} options.silent - 为 true 时不弹窗,仅 reject(用于静默请求如访问统计)
|
||||
*/
|
||||
request(urlOrOptions, options = {}) {
|
||||
let url
|
||||
if (typeof urlOrOptions === 'string') {
|
||||
url = urlOrOptions
|
||||
} else if (urlOrOptions && typeof urlOrOptions === 'object' && urlOrOptions.url) {
|
||||
url = urlOrOptions.url
|
||||
options = { ...urlOrOptions, url: undefined }
|
||||
} else {
|
||||
url = ''
|
||||
}
|
||||
const silent = !!options.silent
|
||||
const showError = (msg) => {
|
||||
if (!silent && msg) {
|
||||
wx.showToast({ title: msg, icon: 'none', duration: 2500 })
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const token = wx.getStorageSync('token')
|
||||
|
||||
|
||||
wx.request({
|
||||
url: this.globalData.baseUrl + url,
|
||||
method: options.method || 'GET',
|
||||
@@ -259,38 +288,50 @@ App({
|
||||
...options.header
|
||||
},
|
||||
success: (res) => {
|
||||
const data = res.data
|
||||
if (res.statusCode === 200) {
|
||||
resolve(res.data)
|
||||
} else if (res.statusCode === 401) {
|
||||
// 未授权,清除登录状态
|
||||
this.logout()
|
||||
reject(new Error('未授权'))
|
||||
} else {
|
||||
reject(new Error(res.data?.message || '请求失败'))
|
||||
// 业务失败:success === false,soul-api 用 message 或 error 返回原因
|
||||
if (data && data.success === false) {
|
||||
const msg = this._getApiErrorMsg(data, '操作失败')
|
||||
showError(msg)
|
||||
reject(new Error(msg))
|
||||
return
|
||||
}
|
||||
resolve(data)
|
||||
return
|
||||
}
|
||||
if (res.statusCode === 401) {
|
||||
this.logout()
|
||||
showError('未授权,请重新登录')
|
||||
reject(new Error('未授权'))
|
||||
return
|
||||
}
|
||||
// 4xx/5xx:优先用返回体的 message/error
|
||||
const msg = this._getApiErrorMsg(data, res.statusCode >= 500 ? '服务器异常,请稍后重试' : '请求失败')
|
||||
showError(msg)
|
||||
reject(new Error(msg))
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(err)
|
||||
const msg = (err && err.errMsg) ? (err.errMsg.indexOf('timeout') !== -1 ? '请求超时,请重试' : '网络异常,请重试') : '网络异常,请重试'
|
||||
showError(msg)
|
||||
reject(new Error(msg))
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 登录方法 - 获取openId用于支付
|
||||
// 登录方法 - 获取openId用于支付(加固错误处理,避免审核报“登录报错”)
|
||||
async login() {
|
||||
try {
|
||||
// 获取微信登录code
|
||||
const loginRes = await new Promise((resolve, reject) => {
|
||||
wx.login({
|
||||
success: resolve,
|
||||
fail: reject
|
||||
})
|
||||
wx.login({ success: resolve, fail: reject })
|
||||
})
|
||||
|
||||
console.log('[App] 获取登录code成功')
|
||||
|
||||
if (!loginRes || !loginRes.code) {
|
||||
console.warn('[App] wx.login 未返回 code')
|
||||
wx.showToast({ title: '获取登录态失败,请重试', icon: 'none' })
|
||||
return null
|
||||
}
|
||||
try {
|
||||
// 发送code到服务器获取openId
|
||||
const res = await this.request('/api/miniprogram/login', {
|
||||
method: 'POST',
|
||||
data: { code: loginRes.code }
|
||||
@@ -362,6 +403,20 @@ App({
|
||||
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) {
|
||||
@@ -378,13 +433,19 @@ App({
|
||||
return null
|
||||
},
|
||||
|
||||
// 手机号登录
|
||||
// 手机号登录:需同时传 wx.login 的 code 与 getPhoneNumber 的 phoneCode
|
||||
async loginWithPhone(phoneCode) {
|
||||
try {
|
||||
// 尝试API登录
|
||||
const res = await this.request('/api/wechat/phone-login', {
|
||||
const loginRes = await new Promise((resolve, reject) => {
|
||||
wx.login({ success: resolve, fail: reject })
|
||||
})
|
||||
if (!loginRes.code) {
|
||||
wx.showToast({ title: '获取登录态失败', icon: 'none' })
|
||||
return null
|
||||
}
|
||||
const res = await this.request('/api/miniprogram/phone-login', {
|
||||
method: 'POST',
|
||||
data: { code: phoneCode }
|
||||
data: { code: loginRes.code, phoneCode }
|
||||
})
|
||||
|
||||
if (res.success && res.data) {
|
||||
@@ -430,6 +491,21 @@ App({
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user