chore: 新增 .gitignore 排除开发文档,同步代码与构建产物
Made-with: Cursor
This commit is contained in:
@@ -5,22 +5,43 @@
|
||||
|
||||
const { parseScene } = require('./utils/scene.js')
|
||||
|
||||
const DEFAULT_BASE_URL = 'https://soulapi.quwanzhi.com'
|
||||
const DEFAULT_APP_ID = 'wxb8bbb2b10dec74aa'
|
||||
const DEFAULT_MCH_ID = '1318592501'
|
||||
const DEFAULT_WITHDRAW_TMPL_ID = 'u3MbZGPRkrZIk-I7QdpwzFxnO_CeQPaCWF2FkiIablE'
|
||||
|
||||
function getRuntimeBootstrapConfig() {
|
||||
try {
|
||||
const extCfg = wx.getExtConfigSync ? (wx.getExtConfigSync() || {}) : {}
|
||||
return {
|
||||
baseUrl: extCfg.apiBaseUrl || wx.getStorageSync('apiBaseUrl') || DEFAULT_BASE_URL,
|
||||
appId: extCfg.appId || DEFAULT_APP_ID,
|
||||
mchId: extCfg.mchId || DEFAULT_MCH_ID,
|
||||
withdrawSubscribeTmplId: extCfg.withdrawSubscribeTmplId || DEFAULT_WITHDRAW_TMPL_ID
|
||||
}
|
||||
} catch (_) {
|
||||
return {
|
||||
baseUrl: DEFAULT_BASE_URL,
|
||||
appId: DEFAULT_APP_ID,
|
||||
mchId: DEFAULT_MCH_ID,
|
||||
withdrawSubscribeTmplId: DEFAULT_WITHDRAW_TMPL_ID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bootstrapConfig = getRuntimeBootstrapConfig()
|
||||
|
||||
App({
|
||||
globalData: {
|
||||
// API基础地址 - 连接真实后端
|
||||
baseUrl: 'https://soulapi.quwanzhi.com',
|
||||
// baseUrl: 'https://souldev.quwanzhi.com',
|
||||
// baseUrl: 'http://localhost:8080',
|
||||
|
||||
|
||||
// 小程序配置 - 真实AppID
|
||||
appId: 'wxb8bbb2b10dec74aa',
|
||||
// 运行配置:优先外部配置/缓存,其次默认值
|
||||
baseUrl: bootstrapConfig.baseUrl,
|
||||
appId: bootstrapConfig.appId,
|
||||
|
||||
// 订阅消息:用户点击「申请提现」→「立即提现」时会先弹出订阅授权窗
|
||||
withdrawSubscribeTmplId: 'u3MbZGPRkrZIk-I7QdpwzFxnO_CeQPaCWF2FkiIablE',
|
||||
withdrawSubscribeTmplId: bootstrapConfig.withdrawSubscribeTmplId,
|
||||
|
||||
// 微信支付配置
|
||||
mchId: '1318592501', // 商户号
|
||||
mchId: bootstrapConfig.mchId,
|
||||
|
||||
// 用户信息
|
||||
userInfo: null,
|
||||
@@ -29,7 +50,8 @@ App({
|
||||
|
||||
// 书籍数据
|
||||
bookData: null,
|
||||
totalSections: 62,
|
||||
totalSections: 0,
|
||||
supportWechat: '',
|
||||
|
||||
// 购买记录
|
||||
purchasedSections: [],
|
||||
@@ -86,6 +108,7 @@ App({
|
||||
|
||||
// 检查登录状态
|
||||
this.checkLoginStatus()
|
||||
this.loadRuntimeConfig()
|
||||
|
||||
// 加载书籍数据
|
||||
this.loadBookData()
|
||||
@@ -328,6 +351,23 @@ App({
|
||||
}
|
||||
},
|
||||
|
||||
async loadRuntimeConfig() {
|
||||
try {
|
||||
const res = await this.request({ url: '/api/miniprogram/config', silent: true, timeout: 5000 })
|
||||
const mpConfig = res?.mpConfig || {}
|
||||
this.globalData.baseUrl = mpConfig.apiDomain || this.globalData.baseUrl
|
||||
this.globalData.appId = mpConfig.appId || this.globalData.appId
|
||||
this.globalData.mchId = mpConfig.mchId || this.globalData.mchId
|
||||
this.globalData.withdrawSubscribeTmplId = mpConfig.withdrawSubscribeTmplId || this.globalData.withdrawSubscribeTmplId
|
||||
this.globalData.supportWechat = mpConfig.supportWechat || mpConfig.customerWechat || mpConfig.serviceWechat || ''
|
||||
try {
|
||||
wx.setStorageSync('apiBaseUrl', this.globalData.baseUrl)
|
||||
} catch (_) {}
|
||||
} catch (e) {
|
||||
console.warn('[App] 加载运行配置失败,继续使用默认配置:', e)
|
||||
}
|
||||
},
|
||||
|
||||
// 加载书籍数据
|
||||
async loadBookData() {
|
||||
try {
|
||||
@@ -342,6 +382,7 @@ App({
|
||||
if (res && (res.data || res.chapters)) {
|
||||
const chapters = res.data || res.chapters || []
|
||||
this.globalData.bookData = chapters
|
||||
this.globalData.totalSections = res.total || chapters.length || 0
|
||||
wx.setStorageSync('bookData', chapters)
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -595,13 +636,6 @@ App({
|
||||
return null
|
||||
},
|
||||
|
||||
// 模拟登录已废弃 - 不再使用
|
||||
// 现在必须使用真实的微信登录获取openId作为唯一标识
|
||||
mockLogin() {
|
||||
console.warn('[App] mockLogin已废弃,请使用真实登录')
|
||||
return null
|
||||
},
|
||||
|
||||
// 手机号登录:需同时传 wx.login 的 code 与 getPhoneNumber 的 phoneCode
|
||||
async loginWithPhone(phoneCode) {
|
||||
if (!this.ensureFullAppForAuth()) {
|
||||
|
||||
@@ -28,11 +28,7 @@ Page({
|
||||
expandedPart: null,
|
||||
|
||||
// 附录
|
||||
appendixList: [
|
||||
{ id: 'appendix-1', title: '附录1|Soul派对房精选对话' },
|
||||
{ id: 'appendix-2', title: '附录2|创业者自检清单' },
|
||||
{ id: 'appendix-3', title: '附录3|本书提到的工具和资源' }
|
||||
],
|
||||
appendixList: [],
|
||||
|
||||
// 每日新增章节
|
||||
dailyChapters: []
|
||||
@@ -77,6 +73,7 @@ Page({
|
||||
|
||||
const totalSections = res.total ?? rows.length
|
||||
app.globalData.bookData = rows
|
||||
app.globalData.totalSections = totalSections
|
||||
wx.setStorageSync('bookData', rows)
|
||||
|
||||
// bookData:过滤序言/尾声/附录,按 part 聚合,篇章顺序按 sort_order 与后台一致(含「2026每日派对干货」等)
|
||||
@@ -134,6 +131,16 @@ Page({
|
||||
}))
|
||||
|
||||
const baseSort = 62
|
||||
const appendixList = rows
|
||||
.filter(r => {
|
||||
const partTitle = String(r.partTitle || r.part_title || '')
|
||||
return partTitle.includes('附录')
|
||||
})
|
||||
.sort((a, b) => (a.sort_order ?? a.sectionOrder ?? 999999) - (b.sort_order ?? b.sectionOrder ?? 999999))
|
||||
.map(c => ({
|
||||
id: c.id,
|
||||
title: c.section_title || c.sectionTitle || c.title || c.chapterTitle || '附录'
|
||||
}))
|
||||
const daily = rows
|
||||
.filter(r => (r.sectionOrder ?? r.sort_order ?? 0) > baseSort)
|
||||
.sort((a, b) => new Date(b.updatedAt || b.updated_at || 0) - new Date(a.updatedAt || a.updated_at || 0))
|
||||
@@ -152,6 +159,7 @@ Page({
|
||||
this.setData({
|
||||
bookData,
|
||||
totalSections,
|
||||
appendixList,
|
||||
dailyChapters: daily,
|
||||
expandedPart: this.data.expandedPart
|
||||
})
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
* 技术支持: 存客宝
|
||||
*/
|
||||
|
||||
console.log('[Index] ===== 首页文件开始加载 =====')
|
||||
|
||||
const app = getApp()
|
||||
const { trackClick } = require('../../utils/trackClick')
|
||||
const { checkAndExecute } = require('../../utils/ruleEngine')
|
||||
@@ -22,7 +20,7 @@ Page({
|
||||
readCount: 0,
|
||||
|
||||
// 书籍数据
|
||||
totalSections: 62,
|
||||
totalSections: 0,
|
||||
bookData: [],
|
||||
|
||||
// 精选推荐(按热度排行,默认显示3篇,可展开更多)
|
||||
@@ -64,8 +62,6 @@ Page({
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
console.log('[Index] ===== onLoad 触发 =====')
|
||||
|
||||
// 获取系统信息
|
||||
this.setData({
|
||||
statusBarHeight: app.globalData.statusBarHeight,
|
||||
@@ -74,7 +70,6 @@ Page({
|
||||
|
||||
// 处理分享参数(推荐码绑定)
|
||||
if (options && options.ref) {
|
||||
console.log('[Index] 检测到推荐码:', options.ref)
|
||||
app.handleReferralCode({ query: options })
|
||||
}
|
||||
|
||||
@@ -83,16 +78,11 @@ Page({
|
||||
},
|
||||
|
||||
onShow() {
|
||||
console.log('[Index] onShow 触发')
|
||||
|
||||
// 设置TabBar选中状态
|
||||
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
|
||||
const tabBar = this.getTabBar()
|
||||
console.log('[Index] TabBar 组件:', tabBar ? '已找到' : '未找到')
|
||||
|
||||
// 主动触发配置加载
|
||||
if (tabBar && tabBar.loadFeatureConfig) {
|
||||
console.log('[Index] 主动调用 TabBar.loadFeatureConfig()')
|
||||
tabBar.loadFeatureConfig()
|
||||
}
|
||||
|
||||
@@ -102,8 +92,6 @@ Page({
|
||||
} else if (tabBar) {
|
||||
tabBar.setData({ selected: 0 })
|
||||
}
|
||||
} else {
|
||||
console.log('[Index] TabBar 组件未找到或 getTabBar 方法不存在')
|
||||
}
|
||||
|
||||
// 更新用户状态
|
||||
@@ -139,13 +127,8 @@ Page({
|
||||
avatar: u.avatar || '',
|
||||
isVip: true
|
||||
}))
|
||||
if (members.length > 0) {
|
||||
console.log('[Index] 超级个体加载成功:', members.length, '人')
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('[Index] vip/members 请求失败:', e)
|
||||
}
|
||||
} catch (e) {}
|
||||
// 不足 4 个则用有头像的普通用户补充
|
||||
if (members.length < 4) {
|
||||
try {
|
||||
@@ -162,7 +145,6 @@ Page({
|
||||
}
|
||||
this.setData({ superMembers: members, superMembersLoading: false })
|
||||
} catch (e) {
|
||||
console.log('[Index] 加载超级个体失败:', e)
|
||||
this.setData({ superMembersLoading: false })
|
||||
}
|
||||
},
|
||||
@@ -192,7 +174,7 @@ Page({
|
||||
featuredExpanded: false,
|
||||
})
|
||||
}
|
||||
} catch (e) { console.log('[Index] book/hot 失败:', e) }
|
||||
} catch (e) {}
|
||||
|
||||
// 2. 最新更新:用 book/latest-chapters 取第1条(排除「序言」「尾声」「附录」)
|
||||
try {
|
||||
@@ -233,9 +215,7 @@ Page({
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('[Index] 从服务端加载推荐失败:', e)
|
||||
}
|
||||
} catch (e) {}
|
||||
},
|
||||
|
||||
async loadBookData() {
|
||||
@@ -246,7 +226,7 @@ Page({
|
||||
const partIds = new Set(chapters.map(c => c.partId || c.part_id || '').filter(Boolean))
|
||||
this.setData({
|
||||
bookData: chapters,
|
||||
totalSections: res.total || chapters.length || 62,
|
||||
totalSections: res.total || chapters.length || app.globalData.totalSections || 0,
|
||||
partCount: partIds.size || 5
|
||||
})
|
||||
}
|
||||
@@ -258,7 +238,7 @@ Page({
|
||||
// 更新用户状态(已读数 = 用户实际打开过的章节数,仅统计有权限阅读的)
|
||||
updateUserStatus() {
|
||||
const { isLoggedIn, hasFullBook, purchasedSections } = app.globalData
|
||||
const readCount = Math.min(app.getReadCount(), this.data.totalSections || 62)
|
||||
const readCount = Math.min(app.getReadCount(), this.data.totalSections || app.globalData.totalSections || 0)
|
||||
this.setData({
|
||||
isLoggedIn,
|
||||
hasFullBook,
|
||||
@@ -317,12 +297,6 @@ Page({
|
||||
return
|
||||
}
|
||||
const userId = app.globalData.userInfo.id
|
||||
// 2 分钟内只能点一次(与后端限频一致)
|
||||
const leadLastTs = wx.getStorageSync('lead_last_submit_ts') || 0
|
||||
if (Date.now() - leadLastTs < 2 * 60 * 1000) {
|
||||
wx.showToast({ title: '操作太频繁,请2分钟后再试', icon: 'none' })
|
||||
return
|
||||
}
|
||||
let phone = (app.globalData.userInfo.phone || '').trim()
|
||||
let wechatId = (app.globalData.userInfo.wechatId || app.globalData.userInfo.wechat_id || '').trim()
|
||||
let avatar = (app.globalData.userInfo.avatar || app.globalData.userInfo.avatarUrl || '').trim()
|
||||
@@ -363,8 +337,12 @@ Page({
|
||||
})
|
||||
wx.hideLoading()
|
||||
if (res && res.success) {
|
||||
wx.setStorageSync('lead_last_submit_ts', Date.now())
|
||||
wx.showToast({ title: res.message || '提交成功', icon: 'success' })
|
||||
wx.showModal({
|
||||
title: '提交成功',
|
||||
content: '卡若会主动添加你微信,请注意你的微信消息',
|
||||
showCancel: false,
|
||||
confirmText: '好的'
|
||||
})
|
||||
} else {
|
||||
wx.showToast({ title: (res && res.message) || '提交失败', icon: 'none' })
|
||||
}
|
||||
@@ -427,11 +405,6 @@ Page({
|
||||
wx.showToast({ title: '请输入正确的手机号', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const leadLastTs = wx.getStorageSync('lead_last_submit_ts') || 0
|
||||
if (Date.now() - leadLastTs < 2 * 60 * 1000) {
|
||||
wx.showToast({ title: '操作太频繁,请2分钟后再试', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const app = getApp()
|
||||
const userId = app.globalData.userInfo?.id
|
||||
wx.showLoading({ title: '提交中...', mask: true })
|
||||
@@ -448,7 +421,6 @@ Page({
|
||||
wx.hideLoading()
|
||||
this.setData({ showLeadModal: false, leadPhone: '' })
|
||||
if (res && res.success) {
|
||||
wx.setStorageSync('lead_last_submit_ts', Date.now())
|
||||
// 同步手机号到用户资料
|
||||
try {
|
||||
if (userId) {
|
||||
@@ -492,8 +464,11 @@ Page({
|
||||
|
||||
async loadLatestChapters() {
|
||||
try {
|
||||
const res = await app.request({ url: '/api/miniprogram/book/all-chapters', silent: true })
|
||||
const chapters = (res && res.data) || (res && res.chapters) || []
|
||||
let chapters = app.globalData.bookData || []
|
||||
if (!Array.isArray(chapters) || chapters.length === 0) {
|
||||
const res = await app.request({ url: '/api/miniprogram/book/all-chapters', silent: true })
|
||||
chapters = (res && res.data) || (res && res.chapters) || []
|
||||
}
|
||||
const pt = (c) => (c.partTitle || c.part_title || '').toLowerCase()
|
||||
const exclude = c => !pt(c).includes('序言') && !pt(c).includes('尾声') && !pt(c).includes('附录')
|
||||
let candidates = chapters.filter(c => (c.isNew || c.is_new) === true && exclude(c))
|
||||
@@ -538,7 +513,7 @@ Page({
|
||||
latestChapters: latestAll.slice(0, 5),
|
||||
latestChaptersExpanded: false,
|
||||
})
|
||||
} catch (e) { console.log('[Index] 加载最新新增失败:', e) }
|
||||
} catch (e) {}
|
||||
},
|
||||
|
||||
toggleLatestExpand() {
|
||||
|
||||
@@ -309,7 +309,7 @@ Page({
|
||||
confirmText: '去购买',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
wx.switchTab({ url: '/pages/catalog/catalog' })
|
||||
wx.switchTab({ url: '/pages/chapters/chapters' })
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -466,35 +466,6 @@ Page({
|
||||
}, delay)
|
||||
},
|
||||
|
||||
// 生成模拟匹配数据
|
||||
generateMockMatch() {
|
||||
const nicknames = ['创业先锋', '资源整合者', '私域专家', '导师顾问', '连续创业者']
|
||||
const concepts = [
|
||||
'专注私域流量运营5年,帮助100+品牌实现从0到1的增长。',
|
||||
'连续创业者,擅长商业模式设计和资源整合。',
|
||||
'在Soul分享真实创业故事,希望找到志同道合的合作伙伴。'
|
||||
]
|
||||
const wechats = ['soul_partner_1', 'soul_business_2024', 'soul_startup_fan']
|
||||
|
||||
const index = Math.floor(Math.random() * nicknames.length)
|
||||
const currentType = MATCH_TYPES.find(t => t.id === this.data.selectedType)
|
||||
|
||||
return {
|
||||
id: `user_${Date.now()}`,
|
||||
nickname: nicknames[index],
|
||||
avatar: `https://picsum.photos/200/200?random=${Date.now()}`,
|
||||
tags: ['创业者', '私域运营', currentType?.label || '创业合伙'],
|
||||
matchScore: Math.floor(Math.random() * 20) + 80,
|
||||
concept: concepts[index % concepts.length],
|
||||
wechat: wechats[index % wechats.length],
|
||||
commonInterests: [
|
||||
{ icon: '📚', text: '都在读《创业派对》' },
|
||||
{ icon: '💼', text: '对私域运营感兴趣' },
|
||||
{ icon: '🎯', text: '相似的创业方向' }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// 上报匹配行为
|
||||
async reportMatch(matchedUser) {
|
||||
try {
|
||||
@@ -648,18 +619,16 @@ Page({
|
||||
this.setData({ showJoinModal: false, joinSuccess: false })
|
||||
}, 2000)
|
||||
} else {
|
||||
// 即使API返回失败,也模拟成功(因为已保存本地)
|
||||
this.setData({ joinSuccess: true })
|
||||
setTimeout(() => {
|
||||
this.setData({ showJoinModal: false, joinSuccess: false })
|
||||
}, 2000)
|
||||
this.setData({
|
||||
joinSuccess: false,
|
||||
joinError: res.error || '提交失败,请稍后重试'
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
// 网络错误时也模拟成功
|
||||
this.setData({ joinSuccess: true })
|
||||
setTimeout(() => {
|
||||
this.setData({ showJoinModal: false, joinSuccess: false })
|
||||
}, 2000)
|
||||
this.setData({
|
||||
joinSuccess: false,
|
||||
joinError: e.message || '网络异常,请稍后重试'
|
||||
})
|
||||
} finally {
|
||||
this.setData({ isJoining: false })
|
||||
}
|
||||
@@ -737,19 +706,7 @@ Page({
|
||||
if (e.errMsg && e.errMsg.includes('cancel')) {
|
||||
wx.showToast({ title: '已取消', icon: 'none' })
|
||||
} else {
|
||||
// 测试模式
|
||||
wx.showModal({
|
||||
title: '支付服务暂不可用',
|
||||
content: '是否使用测试模式购买?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
const extraMatches = (wx.getStorageSync('extra_match_count') || 0) + 1
|
||||
wx.setStorageSync('extra_match_count', extraMatches)
|
||||
wx.showToast({ title: '测试购买成功', icon: 'success' })
|
||||
this.initUserStatus()
|
||||
}
|
||||
}
|
||||
})
|
||||
wx.showToast({ title: e.message || '支付失败,请稍后重试', icon: 'none' })
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -19,7 +19,7 @@ Page({
|
||||
userInfo: null,
|
||||
|
||||
// 统计数据
|
||||
totalSections: 62,
|
||||
totalSections: 0,
|
||||
readCount: 0,
|
||||
referralCount: 0,
|
||||
earnings: '-',
|
||||
@@ -74,6 +74,9 @@ Page({
|
||||
|
||||
// 我的余额(wallet 页入口展示)
|
||||
walletBalance: 0,
|
||||
|
||||
// 我的代付链接
|
||||
giftList: [],
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
@@ -142,6 +145,7 @@ Page({
|
||||
this.loadPendingConfirm()
|
||||
this.loadVipStatus()
|
||||
this.loadWalletBalance()
|
||||
this.loadGiftList()
|
||||
} else {
|
||||
this.setData({
|
||||
isLoggedIn: false,
|
||||
@@ -797,12 +801,6 @@ Page({
|
||||
wx.navigateTo({ url: '/pages/referral/referral' })
|
||||
},
|
||||
|
||||
// 跳转到找伙伴
|
||||
goToMatch() {
|
||||
trackClick('my', 'nav_click', '匹配')
|
||||
wx.switchTab({ url: '/pages/match/match' })
|
||||
},
|
||||
|
||||
// 退出登录
|
||||
handleLogout() {
|
||||
wx.showModal({
|
||||
@@ -829,6 +827,35 @@ Page({
|
||||
} catch (e) {}
|
||||
},
|
||||
|
||||
async loadGiftList() {
|
||||
if (!app.globalData.isLoggedIn || !app.globalData.userInfo) return
|
||||
const userId = app.globalData.userInfo.id
|
||||
try {
|
||||
const res = await app.request({ url: `/api/miniprogram/balance/gifts?userId=${userId}`, silent: true })
|
||||
if (res?.success && res.data?.gifts) {
|
||||
this.setData({ giftList: res.data.gifts })
|
||||
}
|
||||
} catch (e) {}
|
||||
},
|
||||
|
||||
onGiftShareTap(e) {
|
||||
const giftCode = e.currentTarget.dataset.code
|
||||
const title = e.currentTarget.dataset.title || '精选文章'
|
||||
const sectionId = e.currentTarget.dataset.sectionId
|
||||
this._pendingGiftShare = { giftCode, title, sectionId }
|
||||
wx.showModal({
|
||||
title: '分享代付链接',
|
||||
content: `将「${title}」的免费阅读链接分享给好友`,
|
||||
confirmText: '立即分享',
|
||||
cancelText: '取消',
|
||||
success: (r) => {
|
||||
if (r.confirm) {
|
||||
wx.shareAppMessage()
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// VIP状态查询(注意:hasFullBook=9.9 买断,不等同 VIP)
|
||||
async loadVipStatus() {
|
||||
const userId = app.globalData.userInfo?.id
|
||||
@@ -1020,6 +1047,17 @@ Page({
|
||||
stopPropagation() {},
|
||||
|
||||
onShareAppMessage() {
|
||||
if (this._pendingGiftShare) {
|
||||
const { giftCode, title, sectionId } = this._pendingGiftShare
|
||||
this._pendingGiftShare = null
|
||||
const ref = app.getMyReferralCode()
|
||||
let path = `/pages/read/read?id=${sectionId}&gift=${giftCode}`
|
||||
if (ref) path += `&ref=${ref}`
|
||||
return {
|
||||
title: `🎁 好友已为你解锁:${title}`,
|
||||
path
|
||||
}
|
||||
}
|
||||
const ref = app.getMyReferralCode()
|
||||
return {
|
||||
title: 'Soul创业派对 - 我的',
|
||||
|
||||
@@ -142,6 +142,26 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 我的代付链接 -->
|
||||
<view class="card gift-card" wx:if="{{giftList.length > 0}}">
|
||||
<view class="card-header">
|
||||
<image class="card-icon-img" src="/assets/icons/wallet.svg" mode="aspectFit"/>
|
||||
<text class="card-title">我的代付链接</text>
|
||||
</view>
|
||||
<view class="gift-list">
|
||||
<view class="gift-item" wx:for="{{giftList}}" wx:key="giftCode">
|
||||
<view class="gift-left">
|
||||
<text class="gift-title">{{item.sectionTitle}}</text>
|
||||
<text class="gift-meta">¥{{item.amount}} · {{item.status === 'pending' ? '待领取' : '已领取'}} · {{item.createdAt}}</text>
|
||||
</view>
|
||||
<view class="gift-action" wx:if="{{item.status === 'pending'}}" bindtap="onGiftShareTap" data-code="{{item.giftCode}}" data-title="{{item.sectionTitle}}" data-section-id="{{item.sectionId}}">
|
||||
<text class="gift-share-btn">分享</text>
|
||||
</view>
|
||||
<text class="gift-done" wx:else>已送出</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 我的订单 + 关于作者 + 设置 -->
|
||||
<view class="card menu-card">
|
||||
<view class="menu-item" bindtap="handleMenuTap" data-id="orders">
|
||||
|
||||
@@ -251,5 +251,14 @@
|
||||
.modal-btn-cancel { background: rgba(255,255,255,0.1); color: #fff; }
|
||||
.modal-btn-confirm { background: #4FD1C5; color: #000; font-weight: 600; }
|
||||
|
||||
/* 代付链接卡片 */
|
||||
.gift-list { display: flex; flex-direction: column; gap: 16rpx; }
|
||||
.gift-item { display: flex; align-items: center; justify-content: space-between; padding: 20rpx 24rpx; background: rgba(255,255,255,0.05); border-radius: 16rpx; }
|
||||
.gift-left { flex: 1; min-width: 0; }
|
||||
.gift-title { display: block; font-size: 28rpx; color: #fff; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.gift-meta { display: block; font-size: 22rpx; color: #9CA3AF; margin-top: 6rpx; }
|
||||
.gift-share-btn { display: inline-block; padding: 8rpx 28rpx; background: #4FD1C5; color: #000; font-size: 24rpx; font-weight: 600; border-radius: 20rpx; }
|
||||
.gift-done { font-size: 24rpx; color: #6B7280; }
|
||||
|
||||
/* 底部留白:配合 page padding-bottom,避免内容被 TabBar 遮挡 */
|
||||
.bottom-space { height: calc(80rpx + env(safe-area-inset-bottom, 0px)); }
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
* - contentSegments 解析每行,mention 高亮可点;点击→确认→登录/资料校验→POST /api/miniprogram/ckb/lead
|
||||
*/
|
||||
|
||||
import accessManager from '../../utils/chapterAccessManager'
|
||||
import readingTracker from '../../utils/readingTracker'
|
||||
const accessManager = require('../../utils/chapterAccessManager')
|
||||
const readingTracker = require('../../utils/readingTracker')
|
||||
const { parseScene } = require('../../utils/scene.js')
|
||||
const contentParser = require('../../utils/contentParser.js')
|
||||
|
||||
@@ -63,7 +63,7 @@ Page({
|
||||
// 价格
|
||||
sectionPrice: 1,
|
||||
fullBookPrice: 9.9,
|
||||
totalSections: 62,
|
||||
totalSections: 0,
|
||||
|
||||
// 弹窗
|
||||
showShareModal: false,
|
||||
@@ -321,49 +321,31 @@ Page({
|
||||
|
||||
// 获取章节信息
|
||||
getSectionInfo(id) {
|
||||
// 特殊章节
|
||||
if (id === 'preface') {
|
||||
return { id: 'preface', title: '为什么我每天早上6点在Soul开播?', isFree: true, price: 0 }
|
||||
}
|
||||
if (id === 'epilogue') {
|
||||
return { id: 'epilogue', title: '这本书的真实目的', isFree: true, price: 0 }
|
||||
}
|
||||
if (id.startsWith('appendix')) {
|
||||
const appendixTitles = {
|
||||
'appendix-1': 'Soul派对房精选对话',
|
||||
'appendix-2': '创业者自检清单',
|
||||
'appendix-3': '本书提到的工具和资源'
|
||||
const cachedSection = (app.globalData.bookData || []).find((item) => item.id === id)
|
||||
if (cachedSection) {
|
||||
return {
|
||||
id,
|
||||
title: cachedSection.sectionTitle || cachedSection.section_title || cachedSection.title || cachedSection.chapterTitle || `章节 ${id}`,
|
||||
isFree: cachedSection.isFree === true || cachedSection.is_free === true || cachedSection.price === 0,
|
||||
price: cachedSection.price ?? 1
|
||||
}
|
||||
return { id, title: appendixTitles[id] || '附录', isFree: true, price: 0 }
|
||||
}
|
||||
|
||||
// 普通章节
|
||||
|
||||
return {
|
||||
id: id,
|
||||
id,
|
||||
title: this.getSectionTitle(id),
|
||||
isFree: id === '1.1',
|
||||
isFree: false,
|
||||
price: 1
|
||||
}
|
||||
},
|
||||
|
||||
// 获取章节标题
|
||||
getSectionTitle(id) {
|
||||
const titles = {
|
||||
'1.1': '荷包:电动车出租的被动收入模式',
|
||||
'1.2': '老墨:资源整合高手的社交方法',
|
||||
'1.3': '笑声背后的MBTI',
|
||||
'1.4': '人性的三角结构:利益、情感、价值观',
|
||||
'1.5': '沟通差的问题:为什么你说的别人听不懂',
|
||||
'2.1': '相亲故事:你以为找的是人,实际是在找模式',
|
||||
'2.2': '找工作迷茫者:为什么简历解决不了人生',
|
||||
'2.3': '撸运费险:小钱困住大脑的真实心理',
|
||||
'2.4': '游戏上瘾的年轻人:不是游戏吸引他,是生活没吸引力',
|
||||
'2.5': '健康焦虑(我的糖尿病经历):疾病是人生的第一次清醒',
|
||||
'3.1': '3000万流水如何跑出来(退税模式解析)',
|
||||
'8.1': '流量杠杆:抖音、Soul、飞书',
|
||||
'9.14': '大健康私域:一个月150万的70后'
|
||||
const cachedSection = (app.globalData.bookData || []).find((item) => item.id === id)
|
||||
if (cachedSection) {
|
||||
return cachedSection.sectionTitle || cachedSection.section_title || cachedSection.title || cachedSection.chapterTitle || `章节 ${id}`
|
||||
}
|
||||
return titles[id] || `章节 ${id}`
|
||||
return `章节 ${id}`
|
||||
},
|
||||
|
||||
// 根据 id/mid 构造章节接口路径(优先使用 mid)。必须带 userId 才能让后端正确判断付费用户并返回完整内容
|
||||
@@ -679,12 +661,6 @@ Page({
|
||||
})
|
||||
return
|
||||
}
|
||||
// 2 分钟内只能点一次(与后端限频一致,与首页链接卡若共用)
|
||||
const leadLastTs = wx.getStorageSync('lead_last_submit_ts') || 0
|
||||
if (Date.now() - leadLastTs < 2 * 60 * 1000) {
|
||||
wx.showToast({ title: '操作太频繁,请2分钟后再试', icon: 'none' })
|
||||
return
|
||||
}
|
||||
wx.showLoading({ title: '提交中...', mask: true })
|
||||
try {
|
||||
const res = await app.request({
|
||||
@@ -702,8 +678,13 @@ Page({
|
||||
})
|
||||
wx.hideLoading()
|
||||
if (res && res.success) {
|
||||
wx.setStorageSync('lead_last_submit_ts', Date.now())
|
||||
wx.showToast({ title: res.message || '提交成功,对方会尽快联系您', icon: 'success' })
|
||||
const who = targetNickname || '对方'
|
||||
wx.showModal({
|
||||
title: '提交成功',
|
||||
content: `${who} 会主动添加你微信,请注意你的微信消息`,
|
||||
showCancel: false,
|
||||
confirmText: '好的'
|
||||
})
|
||||
} else {
|
||||
wx.showToast({ title: (res && res.message) || '提交失败', icon: 'none' })
|
||||
}
|
||||
@@ -728,11 +709,6 @@ Page({
|
||||
return
|
||||
}
|
||||
const userId = app.globalData.userInfo.id
|
||||
const leadLastTs = wx.getStorageSync('lead_last_submit_ts') || 0
|
||||
if (Date.now() - leadLastTs < 2 * 60 * 1000) {
|
||||
wx.showToast({ title: '操作太频繁,请2分钟后再试', icon: 'none' })
|
||||
return
|
||||
}
|
||||
let phone = (app.globalData.userInfo.phone || '').trim()
|
||||
let wechatId = (app.globalData.userInfo.wechatId || app.globalData.userInfo.wechat_id || '').trim()
|
||||
let avatar = (app.globalData.userInfo.avatar || app.globalData.userInfo.avatarUrl || '').trim()
|
||||
@@ -774,8 +750,12 @@ Page({
|
||||
})
|
||||
wx.hideLoading()
|
||||
if (res && res.success) {
|
||||
wx.setStorageSync('lead_last_submit_ts', Date.now())
|
||||
wx.showToast({ title: res.message || '提交成功', icon: 'success' })
|
||||
wx.showModal({
|
||||
title: '提交成功',
|
||||
content: '卡若会主动添加你微信,请注意你的微信消息',
|
||||
showCancel: false,
|
||||
confirmText: '好的'
|
||||
})
|
||||
} else {
|
||||
wx.showToast({ title: (res && res.message) || '提交失败', icon: 'none' })
|
||||
}
|
||||
@@ -811,16 +791,15 @@ Page({
|
||||
|
||||
// 复制分享文案(朋友圈风格)
|
||||
copyShareText() {
|
||||
const { section } = this.data
|
||||
|
||||
const shareText = `🔥 刚看完这篇《${section?.title || 'Soul创业派对'}》,太上头了!
|
||||
|
||||
62个真实商业案例,每个都是从0到1的实战经验。私域运营、资源整合、商业变现,干货满满。
|
||||
|
||||
推荐给正在创业或想创业的朋友,搜"Soul创业派对"小程序就能看!
|
||||
|
||||
#创业派对 #私域运营 #商业案例`
|
||||
|
||||
const title = this.data.section?.title || this.data.chapterTitle || '好文推荐'
|
||||
const raw = (this.data.content || '')
|
||||
.replace(/<[^>]+>/g, '\n')
|
||||
.replace(/ /g, ' ')
|
||||
.replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&').replace(/"/g, '"')
|
||||
.replace(/[#@]\S+/g, '')
|
||||
const sentences = raw.split(/[。!?\n]+/).map(s => s.trim()).filter(s => s.length > 4)
|
||||
const picked = sentences.slice(0, 5)
|
||||
const shareText = title + '\n\n' + picked.join('\n\n')
|
||||
wx.setClipboardData({
|
||||
data: shareText,
|
||||
success: () => {
|
||||
@@ -864,13 +843,23 @@ Page({
|
||||
|
||||
shareToMoments() {
|
||||
const title = this.data.section?.title || this.data.chapterTitle || '好文推荐'
|
||||
const raw = (this.data.content || '').replace(/[#@]\S+/g, '').replace(/\s+/g, ' ').trim()
|
||||
const excerpt = raw.length > 200 ? raw.slice(0, 200) + '……' : raw.length > 100 ? raw + '……' : raw
|
||||
const copyText = `${title}\n\n${excerpt}\n\n👉 来自「Soul创业派对」`
|
||||
const raw = (this.data.content || '')
|
||||
.replace(/<[^>]+>/g, '\n')
|
||||
.replace(/ /g, ' ')
|
||||
.replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&').replace(/"/g, '"')
|
||||
.replace(/[#@]\S+/g, '')
|
||||
const sentences = raw.split(/[。!?\n]+/).map(s => s.trim()).filter(s => s.length > 4)
|
||||
const picked = sentences.slice(0, 5)
|
||||
const copyText = title + '\n\n' + picked.join('\n\n')
|
||||
wx.setClipboardData({
|
||||
data: copyText,
|
||||
success: () => {
|
||||
wx.showToast({ title: '文案已复制,去朋友圈粘贴发布', icon: 'none', duration: 2500 })
|
||||
wx.showModal({
|
||||
title: '文案已复制',
|
||||
content: '请点击右上角「···」菜单,选择「分享到朋友圈」即可发布',
|
||||
showCancel: false,
|
||||
confirmText: '知道了'
|
||||
})
|
||||
},
|
||||
fail: () => {
|
||||
wx.showToast({ title: '复制失败,请手动复制', icon: 'none' })
|
||||
@@ -1154,15 +1143,18 @@ Page({
|
||||
console.error('[Pay] API创建订单失败:', apiError)
|
||||
wx.hideLoading()
|
||||
// 支付接口失败时,显示客服联系方式
|
||||
const supportWechat = app.globalData.supportWechat || ''
|
||||
wx.showModal({
|
||||
title: '支付通道维护中',
|
||||
content: '微信支付正在审核中,请添加客服微信(28533368)手动购买,感谢理解!',
|
||||
confirmText: '复制微信号',
|
||||
content: supportWechat
|
||||
? `微信支付正在审核中,请添加客服微信(${supportWechat})手动购买,感谢理解!`
|
||||
: '微信支付正在审核中,请联系管理员手动购买,感谢理解!',
|
||||
confirmText: supportWechat ? '复制微信号' : '我知道了',
|
||||
cancelText: '稍后再说',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
if (res.confirm && supportWechat) {
|
||||
wx.setClipboardData({
|
||||
data: '28533368',
|
||||
data: supportWechat,
|
||||
success: () => {
|
||||
wx.showToast({ title: '微信号已复制', icon: 'success' })
|
||||
}
|
||||
@@ -1202,15 +1194,18 @@ Page({
|
||||
wx.showToast({ title: '已取消支付', icon: 'none' })
|
||||
} else if (payErr.errMsg && payErr.errMsg.includes('requestPayment:fail')) {
|
||||
// 支付失败,可能是参数错误或权限问题
|
||||
const supportWechat = app.globalData.supportWechat || ''
|
||||
wx.showModal({
|
||||
title: '支付失败',
|
||||
content: '微信支付暂不可用,请添加客服微信(28533368)手动购买',
|
||||
confirmText: '复制微信号',
|
||||
content: supportWechat
|
||||
? `微信支付暂不可用,请添加客服微信(${supportWechat})手动购买`
|
||||
: '微信支付暂不可用,请稍后重试或联系管理员',
|
||||
confirmText: supportWechat ? '复制微信号' : '我知道了',
|
||||
cancelText: '取消',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
if (res.confirm && supportWechat) {
|
||||
wx.setClipboardData({
|
||||
data: '28533368',
|
||||
data: supportWechat,
|
||||
success: () => wx.showToast({ title: '微信号已复制', icon: 'success' })
|
||||
})
|
||||
}
|
||||
|
||||
@@ -63,9 +63,8 @@ Page({
|
||||
posterReferralLink: '',
|
||||
posterNickname: '',
|
||||
posterNicknameInitial: '',
|
||||
posterCaseCount: 62,
|
||||
|
||||
},
|
||||
posterCaseCount: 62
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
|
||||
@@ -94,28 +93,17 @@ Page({
|
||||
// 生成邀请码
|
||||
const referralCode = userInfo.referralCode || 'SOUL' + (userInfo.id || Date.now().toString(36)).toUpperCase().slice(-6)
|
||||
|
||||
console.log('[Referral] 开始加载分销数据,userId:', userInfo.id)
|
||||
|
||||
// 从API获取真实数据
|
||||
let realData = null
|
||||
try {
|
||||
// app.request 第一个参数是 URL 字符串(会自动拼接 baseUrl)
|
||||
const res = await app.request('/api/miniprogram/referral/data?userId=' + userInfo.id)
|
||||
console.log('[Referral] API返回:', JSON.stringify(res).substring(0, 200))
|
||||
|
||||
if (res && res.success && res.data) {
|
||||
realData = res.data
|
||||
console.log('[Referral] ✅ 获取推广数据成功')
|
||||
console.log('[Referral] - bindingCount:', realData.bindingCount)
|
||||
console.log('[Referral] - paidCount:', realData.paidCount)
|
||||
console.log('[Referral] - earnings:', realData.earnings)
|
||||
console.log('[Referral] - expiringCount:', realData.stats?.expiringCount)
|
||||
} else {
|
||||
console.log('[Referral] ❌ API返回格式错误:', res?.error || 'unknown')
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('[Referral] ❌ API调用失败:', e.message || e)
|
||||
console.log('[Referral] 错误详情:', e)
|
||||
console.warn('[Referral] 加载分销数据失败:', e && e.message ? e.message : e)
|
||||
}
|
||||
|
||||
// 使用真实数据或默认值
|
||||
@@ -123,15 +111,9 @@ Page({
|
||||
let convertedBindings = realData?.convertedUsers || []
|
||||
let expiredBindings = realData?.expiredUsers || []
|
||||
|
||||
console.log('[Referral] activeBindings:', activeBindings.length)
|
||||
console.log('[Referral] convertedBindings:', convertedBindings.length)
|
||||
console.log('[Referral] expiredBindings:', expiredBindings.length)
|
||||
|
||||
// 计算即将过期的数量(7天内)
|
||||
const expiringCount = realData?.stats?.expiringCount || activeBindings.filter(b => b.daysRemaining <= 7 && b.daysRemaining > 0).length
|
||||
|
||||
console.log('[Referral] expiringCount:', expiringCount)
|
||||
|
||||
// 计算各类统计
|
||||
const bindingCount = realData?.bindingCount || activeBindings.length
|
||||
const paidCount = realData?.paidCount || convertedBindings.length
|
||||
@@ -153,7 +135,6 @@ Page({
|
||||
purchaseCount: user.purchaseCount || 0,
|
||||
conversionDate: user.conversionDate ? this.formatDate(user.conversionDate) : '--'
|
||||
}
|
||||
console.log('[Referral] 格式化用户:', formatted.nickname, formatted.status, formatted.daysRemaining + '天')
|
||||
return formatted
|
||||
}
|
||||
|
||||
@@ -169,15 +150,6 @@ Page({
|
||||
const availableEarningsNum = Math.max(0, totalCommissionNum - withdrawnNum - pendingWithdrawNum)
|
||||
const minWithdrawAmount = realData?.minWithdrawAmount || 10
|
||||
|
||||
console.log('=== [Referral] 收益计算(完整版)===')
|
||||
console.log('累计佣金 (totalCommission):', totalCommissionNum)
|
||||
console.log('已提现金额 (withdrawnEarnings):', withdrawnNum)
|
||||
console.log('待审核金额 (pendingWithdrawAmount):', pendingWithdrawNum)
|
||||
console.log('可提现金额 = 累计 - 已提现 - 待审核 =', totalCommissionNum, '-', withdrawnNum, '-', pendingWithdrawNum, '=', availableEarningsNum)
|
||||
console.log('最低提现金额 (minWithdrawAmount):', minWithdrawAmount)
|
||||
console.log('按钮判断:', availableEarningsNum, '>=', minWithdrawAmount, '=', availableEarningsNum >= minWithdrawAmount)
|
||||
console.log('✅ 按钮应该:', availableEarningsNum >= minWithdrawAmount ? '🟢 启用(绿色)' : '⚫ 禁用(灰色)')
|
||||
|
||||
const hasWechatId = !!(userInfo?.wechat || userInfo?.wechatId || wx.getStorageSync('user_wechat'))
|
||||
this.setData({
|
||||
isLoggedIn: true,
|
||||
@@ -233,21 +205,6 @@ Page({
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
console.log('[Referral] ✅ 数据设置完成')
|
||||
console.log('[Referral] - 绑定中:', this.data.bindingCount)
|
||||
console.log('[Referral] - 即将过期:', this.data.expiringCount)
|
||||
console.log('[Referral] - 收益:', this.data.earnings)
|
||||
|
||||
console.log('=== [Referral] 按钮状态验证 ===')
|
||||
console.log('累计佣金 (totalCommission):', this.data.totalCommission)
|
||||
console.log('待审核金额 (pendingWithdrawAmount):', this.data.pendingWithdrawAmount)
|
||||
console.log('可提现金额 (availableEarnings 显示):', this.data.availableEarnings)
|
||||
console.log('可提现金额 (availableEarningsNum 判断):', this.data.availableEarningsNum, typeof this.data.availableEarningsNum)
|
||||
console.log('最低提现金额 (minWithdrawAmount):', this.data.minWithdrawAmount, typeof this.data.minWithdrawAmount)
|
||||
console.log('按钮启用条件:', this.data.availableEarningsNum, '>=', this.data.minWithdrawAmount, '=', this.data.availableEarningsNum >= this.data.minWithdrawAmount)
|
||||
console.log('✅ 最终结果: 按钮应该', this.data.availableEarningsNum >= this.data.minWithdrawAmount ? '🟢 启用' : '⚫ 禁用')
|
||||
|
||||
// 隐藏加载提示
|
||||
wx.hideLoading()
|
||||
} else {
|
||||
|
||||
@@ -13,8 +13,8 @@ Page({
|
||||
loading: false,
|
||||
searched: false,
|
||||
total: 0,
|
||||
// 热门搜索关键词
|
||||
hotKeywords: ['私域', '电商', '流量', '赚钱', '创业', 'Soul', '抖音', '变现'],
|
||||
// 热门搜索关键词(运行时根据热门章节/目录动态生成)
|
||||
hotKeywords: [],
|
||||
// 热门章节推荐
|
||||
hotChapters: [
|
||||
{ id: '1.1', title: '荷包:电动车出租的被动收入模式', tag: '免费', part: '真实的人' },
|
||||
@@ -47,13 +47,36 @@ Page({
|
||||
part: c.part_title || c.partTitle || c.part || '',
|
||||
tag: ['免费', '热门', '推荐', '最新'][i % 4] || '热门'
|
||||
}))
|
||||
this.setData({ hotChapters })
|
||||
this.setData({
|
||||
hotChapters,
|
||||
hotKeywords: this.buildHotKeywords(hotChapters)
|
||||
})
|
||||
} else {
|
||||
this.setData({ hotKeywords: this.buildHotKeywords(app.globalData.bookData || []) })
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('加载热门章节失败,使用默认数据')
|
||||
this.setData({ hotKeywords: this.buildHotKeywords(app.globalData.bookData || []) })
|
||||
}
|
||||
},
|
||||
|
||||
buildHotKeywords(sourceList) {
|
||||
const words = []
|
||||
const pushWord = (word) => {
|
||||
const w = (word || '').trim()
|
||||
if (!w || w.length < 2 || words.includes(w)) return
|
||||
words.push(w)
|
||||
}
|
||||
|
||||
;(sourceList || []).forEach((item) => {
|
||||
const title = String(item.title || '').replace(/[||::,.,。!!??]/g, ' ')
|
||||
const part = String(item.part || '').replace(/[||::,.,。!!??]/g, ' ')
|
||||
title.split(/\s+/).forEach(pushWord)
|
||||
part.split(/\s+/).forEach(pushWord)
|
||||
})
|
||||
|
||||
return words.slice(0, 8)
|
||||
},
|
||||
|
||||
// 输入关键词
|
||||
onInput(e) {
|
||||
this.setData({ keyword: e.detail.value })
|
||||
@@ -99,7 +122,6 @@ Page({
|
||||
this.setData({ results: [], total: 0 })
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('搜索失败:', e)
|
||||
wx.showToast({ title: '搜索失败', icon: 'none' })
|
||||
this.setData({ results: [], total: 0 })
|
||||
} finally {
|
||||
|
||||
@@ -245,85 +245,66 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
// 获取微信头像(新版授权)
|
||||
async getWechatAvatar() {
|
||||
// 微信原生 chooseAvatar 回调
|
||||
async onChooseAvatar(e) {
|
||||
const tempAvatarUrl = e.detail?.avatarUrl
|
||||
if (!tempAvatarUrl) return
|
||||
wx.showLoading({ title: '上传中...', mask: true })
|
||||
try {
|
||||
const res = await wx.getUserProfile({
|
||||
desc: '用于完善会员资料'
|
||||
})
|
||||
|
||||
if (res.userInfo) {
|
||||
const { nickName, avatarUrl: tempAvatarUrl } = res.userInfo
|
||||
|
||||
wx.showLoading({ title: '上传中...', mask: true })
|
||||
|
||||
// 1. 先上传图片到服务器
|
||||
console.log('[Settings] 开始上传头像:', tempAvatarUrl)
|
||||
|
||||
const uploadRes = await new Promise((resolve, reject) => {
|
||||
wx.uploadFile({
|
||||
url: app.globalData.baseUrl + '/api/miniprogram/upload',
|
||||
filePath: tempAvatarUrl,
|
||||
name: 'file',
|
||||
formData: {
|
||||
folder: 'avatars'
|
||||
},
|
||||
success: (uploadResult) => {
|
||||
try {
|
||||
const data = JSON.parse(uploadResult.data)
|
||||
if (data.success) {
|
||||
resolve(data)
|
||||
} else {
|
||||
reject(new Error(data.error || '上传失败'))
|
||||
}
|
||||
} catch (err) {
|
||||
reject(new Error('解析响应失败'))
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(err)
|
||||
const uploadRes = await new Promise((resolve, reject) => {
|
||||
wx.uploadFile({
|
||||
url: app.globalData.baseUrl + '/api/miniprogram/upload',
|
||||
filePath: tempAvatarUrl,
|
||||
name: 'file',
|
||||
formData: { folder: 'avatars' },
|
||||
success: (uploadResult) => {
|
||||
try {
|
||||
const data = JSON.parse(uploadResult.data)
|
||||
if (data.success) resolve(data)
|
||||
else reject(new Error(data.error || '上传失败'))
|
||||
} catch (err) {
|
||||
reject(new Error('解析响应失败'))
|
||||
}
|
||||
})
|
||||
},
|
||||
fail: reject
|
||||
})
|
||||
|
||||
// 2. 获取上传后的完整URL
|
||||
const avatarUrl = app.globalData.baseUrl + uploadRes.data.url
|
||||
console.log('[Settings] 头像上传成功:', avatarUrl)
|
||||
|
||||
// 3. 更新本地
|
||||
this.setData({
|
||||
userInfo: {
|
||||
...this.data.userInfo,
|
||||
nickname: nickName,
|
||||
avatar: avatarUrl
|
||||
}
|
||||
})
|
||||
|
||||
const rawUrl = uploadRes.data.url || ''
|
||||
const avatarUrl = rawUrl.startsWith('http://') || rawUrl.startsWith('https://')
|
||||
? rawUrl
|
||||
: app.globalData.baseUrl + rawUrl
|
||||
const nickname = this.data.userInfo?.nickname || app.globalData.userInfo?.nickname || ''
|
||||
|
||||
this.setData({
|
||||
userInfo: {
|
||||
...this.data.userInfo,
|
||||
nickname,
|
||||
avatar: avatarUrl
|
||||
}
|
||||
})
|
||||
|
||||
const userId = app.globalData.userInfo?.id
|
||||
if (userId) {
|
||||
await app.request('/api/miniprogram/user/profile', {
|
||||
method: 'POST',
|
||||
data: { userId, nickname, avatar: avatarUrl }
|
||||
})
|
||||
|
||||
// 4. 同步到服务器数据库
|
||||
const userId = app.globalData.userInfo?.id
|
||||
if (userId) {
|
||||
await app.request('/api/miniprogram/user/profile', {
|
||||
method: 'POST',
|
||||
data: { userId, nickname: nickName, avatar: avatarUrl }
|
||||
})
|
||||
}
|
||||
|
||||
// 5. 更新全局
|
||||
if (app.globalData.userInfo) {
|
||||
app.globalData.userInfo.nickname = nickName
|
||||
app.globalData.userInfo.avatar = avatarUrl
|
||||
wx.setStorageSync('userInfo', app.globalData.userInfo)
|
||||
}
|
||||
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '头像更新成功', icon: 'success' })
|
||||
}
|
||||
|
||||
if (app.globalData.userInfo) {
|
||||
app.globalData.userInfo.avatar = avatarUrl
|
||||
wx.setStorageSync('userInfo', app.globalData.userInfo)
|
||||
}
|
||||
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '头像更新成功', icon: 'success' })
|
||||
} catch (e) {
|
||||
wx.hideLoading()
|
||||
console.error('[Settings] 获取头像失败:', e)
|
||||
wx.showToast({
|
||||
title: e.message || '获取头像失败',
|
||||
icon: 'none'
|
||||
console.error('[Settings] 更新头像失败:', e)
|
||||
wx.showToast({
|
||||
title: e.message || '上传失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import accessManager from '../../utils/chapterAccessManager'
|
||||
const accessManager = require('../../utils/chapterAccessManager')
|
||||
const app = getApp()
|
||||
const { trackClick } = require('../../utils/trackClick')
|
||||
const { checkAndExecute } = require('../../utils/ruleEngine')
|
||||
|
||||
@@ -206,4 +206,4 @@ class ChapterAccessManager {
|
||||
|
||||
// 导出单例
|
||||
const accessManager = new ChapterAccessManager()
|
||||
export default accessManager
|
||||
module.exports = accessManager
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
// miniprogram/utils/payment.js
|
||||
// 微信支付工具类
|
||||
|
||||
const app = getApp()
|
||||
|
||||
/**
|
||||
* 发起微信支付
|
||||
* @param {Object} options - 支付选项
|
||||
* @param {String} options.orderId - 订单ID
|
||||
* @param {Number} options.amount - 支付金额(元)
|
||||
* @param {String} options.description - 商品描述
|
||||
* @param {Function} options.success - 成功回调
|
||||
* @param {Function} options.fail - 失败回调
|
||||
*/
|
||||
function wxPay(options) {
|
||||
const { orderId, amount, description, success, fail } = options
|
||||
|
||||
wx.showLoading({
|
||||
title: '正在支付...',
|
||||
mask: true
|
||||
})
|
||||
|
||||
// 1. 调用后端创建支付订单
|
||||
wx.request({
|
||||
url: `${app.globalData.apiBase}/payment/create`,
|
||||
method: 'POST',
|
||||
header: {
|
||||
'Authorization': `Bearer ${wx.getStorageSync('token')}`
|
||||
},
|
||||
data: {
|
||||
orderId,
|
||||
amount,
|
||||
description,
|
||||
paymentMethod: 'wechat'
|
||||
},
|
||||
success: (res) => {
|
||||
wx.hideLoading()
|
||||
|
||||
if (res.statusCode === 200) {
|
||||
const paymentData = res.data
|
||||
|
||||
// 2. 调起微信支付
|
||||
wx.requestPayment({
|
||||
timeStamp: paymentData.timeStamp,
|
||||
nonceStr: paymentData.nonceStr,
|
||||
package: paymentData.package,
|
||||
signType: paymentData.signType || 'RSA',
|
||||
paySign: paymentData.paySign,
|
||||
success: (payRes) => {
|
||||
console.log('支付成功', payRes)
|
||||
|
||||
// 3. 通知后端支付成功
|
||||
notifyPaymentSuccess(orderId, paymentData.prepayId)
|
||||
|
||||
wx.showToast({
|
||||
title: '支付成功',
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
|
||||
success && success(payRes)
|
||||
},
|
||||
fail: (payErr) => {
|
||||
console.error('支付失败', payErr)
|
||||
|
||||
if (payErr.errMsg.indexOf('cancel') !== -1) {
|
||||
wx.showToast({
|
||||
title: '支付已取消',
|
||||
icon: 'none'
|
||||
})
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: '支付失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
fail && fail(payErr)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: res.data.message || '创建订单失败',
|
||||
icon: 'none'
|
||||
})
|
||||
fail && fail(res)
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
wx.hideLoading()
|
||||
console.error('请求失败', err)
|
||||
|
||||
wx.showToast({
|
||||
title: '网络请求失败',
|
||||
icon: 'none'
|
||||
})
|
||||
|
||||
fail && fail(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知后端支付成功
|
||||
* @param {String} orderId
|
||||
* @param {String} prepayId
|
||||
*/
|
||||
function notifyPaymentSuccess(orderId, prepayId) {
|
||||
wx.request({
|
||||
url: `${app.globalData.apiBase}/payment/notify`,
|
||||
method: 'POST',
|
||||
header: {
|
||||
'Authorization': `Bearer ${wx.getStorageSync('token')}`
|
||||
},
|
||||
data: {
|
||||
orderId,
|
||||
prepayId,
|
||||
status: 'success'
|
||||
},
|
||||
success: (res) => {
|
||||
console.log('支付通知成功', res)
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('支付通知失败', err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询订单状态
|
||||
* @param {String} orderId
|
||||
* @param {Function} callback
|
||||
*/
|
||||
function queryOrderStatus(orderId, callback) {
|
||||
wx.request({
|
||||
url: `${app.globalData.apiBase}/payment/query`,
|
||||
method: 'GET',
|
||||
header: {
|
||||
'Authorization': `Bearer ${wx.getStorageSync('token')}`
|
||||
},
|
||||
data: { orderId },
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
callback && callback(true, res.data)
|
||||
} else {
|
||||
callback && callback(false, null)
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
callback && callback(false, null)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 购买完整电子书
|
||||
* @param {Function} success
|
||||
* @param {Function} fail
|
||||
*/
|
||||
function purchaseFullBook(success, fail) {
|
||||
// 计算动态价格:9.9 + (天数 * 1元)
|
||||
const basePrice = 9.9
|
||||
const startDate = new Date('2025-01-01') // 书籍上架日期
|
||||
const today = new Date()
|
||||
const daysPassed = Math.floor((today - startDate) / (1000 * 60 * 60 * 24))
|
||||
const currentPrice = basePrice + daysPassed
|
||||
|
||||
const orderId = `ORDER_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
||||
|
||||
wxPay({
|
||||
orderId,
|
||||
amount: currentPrice,
|
||||
description: 'Soul派对·创业实验 完整版',
|
||||
success: (res) => {
|
||||
// 更新本地购买状态
|
||||
updatePurchaseStatus(true)
|
||||
success && success(res)
|
||||
},
|
||||
fail
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新购买状态
|
||||
* @param {Boolean} isPurchased
|
||||
*/
|
||||
function updatePurchaseStatus(isPurchased) {
|
||||
const userInfo = app.getUserInfo()
|
||||
if (userInfo) {
|
||||
userInfo.isPurchased = isPurchased
|
||||
wx.setStorageSync('userInfo', userInfo)
|
||||
app.globalData.userInfo = userInfo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否已购买
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
function checkPurchaseStatus() {
|
||||
const userInfo = app.getUserInfo()
|
||||
return userInfo ? userInfo.isPurchased : false
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
wxPay,
|
||||
queryOrderStatus,
|
||||
purchaseFullBook,
|
||||
checkPurchaseStatus,
|
||||
updatePurchaseStatus
|
||||
}
|
||||
@@ -246,4 +246,4 @@ class ReadingTracker {
|
||||
|
||||
// 导出单例
|
||||
const readingTracker = new ReadingTracker()
|
||||
export default readingTracker
|
||||
module.exports = readingTracker
|
||||
|
||||
@@ -42,7 +42,10 @@ function isInCooldown(ruleId) {
|
||||
const ts = map[ruleId]
|
||||
if (!ts) return false
|
||||
return Date.now() - ts < COOLDOWN_MS
|
||||
} catch { return false }
|
||||
} catch (e) {
|
||||
console.warn('[RuleEngine] 读取冷却状态失败:', e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function setCooldown(ruleId) {
|
||||
@@ -50,7 +53,9 @@ function setCooldown(ruleId) {
|
||||
const map = wx.getStorageSync(RULE_COOLDOWN_KEY) || {}
|
||||
map[ruleId] = Date.now()
|
||||
wx.setStorageSync(RULE_COOLDOWN_KEY, map)
|
||||
} catch {}
|
||||
} catch (e) {
|
||||
console.warn('[RuleEngine] 写入冷却状态失败:', e)
|
||||
}
|
||||
}
|
||||
|
||||
function getUserInfo() {
|
||||
@@ -66,7 +71,9 @@ async function loadRules() {
|
||||
_cacheTs = Date.now()
|
||||
return _cachedRules
|
||||
}
|
||||
} catch {}
|
||||
} catch (e) {
|
||||
console.warn('[RuleEngine] 加载规则失败,继续使用缓存:', e)
|
||||
}
|
||||
return _cachedRules || []
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user