chore: 新增 .gitignore 排除开发文档,同步代码与构建产物

Made-with: Cursor
This commit is contained in:
卡若
2026-03-16 09:21:39 +08:00
parent fa9903d235
commit 85ce2422d1
40 changed files with 2315 additions and 947 deletions

View File

@@ -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()) {

View File

@@ -28,11 +28,7 @@ Page({
expandedPart: null,
// 附录
appendixList: [
{ id: 'appendix-1', title: '附录1Soul派对房精选对话' },
{ 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
})

View File

@@ -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() {

View File

@@ -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' })
}
}
},

View File

@@ -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创业派对 - 我的',

View File

@@ -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">

View File

@@ -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)); }

View File

@@ -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(/&nbsp;/g, ' ')
.replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&').replace(/&quot;/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(/&nbsp;/g, ' ')
.replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&').replace(/&quot;/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' })
})
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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'
})
}
},

View File

@@ -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')

View File

@@ -206,4 +206,4 @@ class ChapterAccessManager {
// 导出单例
const accessManager = new ChapterAccessManager()
export default accessManager
module.exports = accessManager

View File

@@ -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
}

View File

@@ -246,4 +246,4 @@ class ReadingTracker {
// 导出单例
const readingTracker = new ReadingTracker()
export default readingTracker
module.exports = readingTracker

View File

@@ -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 || []
}