diff --git a/miniprogram/app.js b/miniprogram/app.js
index 7d3ab6c9..8b507f28 100644
--- a/miniprogram/app.js
+++ b/miniprogram/app.js
@@ -3,15 +3,10 @@
* 开发: 卡若
*/
-const { parseScene } = require('./utils/scene.js')
-
App({
globalData: {
// API基础地址 - 连接真实后端
- baseUrl: 'https://soulapi.quwanzhi.com',
- // baseUrl: 'https://souldev.quwanzhi.com',
- // baseUrl: 'http://localhost:3006',
- // baseUrl: 'http://localhost:8080',
+ baseUrl: 'https://soul.quwanzhi.com',
// 小程序配置 - 真实AppID
appId: 'wxb8bbb2b10dec74aa',
@@ -33,10 +28,7 @@ App({
// 购买记录
purchasedSections: [],
- sectionMidMap: {}, // id -> mid,来自 purchase-status
hasFullBook: false,
- matchCount: 0,
- matchQuota: null,
// 已读章节(仅统计有权限打开过的章节,用于首页「已读/待读」)
readSectionIds: [],
@@ -85,17 +77,11 @@ App({
this.handleReferralCode(options)
},
- // 处理推荐码绑定:官方以 options.scene 接收扫码参数(可同时带 mid/id + ref),与 utils/scene 解析闭环
+ // 处理推荐码绑定
handleReferralCode(options) {
const query = options?.query || {}
- let refCode = query.ref || query.referralCode
- const sceneStr = (options && (typeof options.scene === 'string' ? options.scene : '')) || ''
- if (sceneStr) {
- const parsed = parseScene(sceneStr)
- if (parsed.mid) this.globalData.initialSectionMid = parsed.mid
- if (parsed.id) this.globalData.initialSectionId = parsed.id
- if (parsed.ref) refCode = parsed.ref
- }
+ const refCode = query.ref || query.referralCode
+
if (refCode) {
console.log('[App] 检测到推荐码:', refCode)
@@ -170,22 +156,6 @@ App({
}
},
- // 根据业务 id 从 bookData 查 mid(用于跳转)
- getSectionMid(sectionId) {
- const list = this.globalData.bookData || []
- const ch = list.find(c => c.id === sectionId)
- return ch?.mid || 0
- },
-
- // 获取当前用户的邀请码(用于分享带 ref,未登录返回空字符串)
- getMyReferralCode() {
- const user = this.globalData.userInfo
- if (!user) return ''
- if (user.referralCode) return user.referralCode
- if (user.id) return 'SOUL' + String(user.id).toUpperCase().slice(-6)
- return ''
- },
-
// 获取系统信息
getSystemInfo() {
try {
@@ -230,50 +200,47 @@ App({
}
// 从服务器获取最新数据
- const res = await this.request('/api/miniprogram/book/all-chapters')
- if (res && res.data) {
- this.globalData.bookData = res.data
- wx.setStorageSync('bookData', res.data)
+ const res = await this.request('/api/book/all-chapters')
+ 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)
}
},
- // 版本更新检测:发现新版本时提示用户立即更新
+ // 检查更新
checkUpdate() {
- if (!wx.canIUse('getUpdateManager')) return
- const updateManager = wx.getUpdateManager()
-
- updateManager.onCheckForUpdate((res) => {
- if (res.hasUpdate) {
- console.log('[App] 发现新版本,正在后台下载')
- wx.showToast({ title: '发现新版本,正在准备…', icon: 'none', duration: 2000 })
- }
- })
-
- updateManager.onUpdateReady(() => {
- wx.showModal({
- title: '发现新版本',
- content: '小程序已更新,请立即重启以使用最新版本。',
- confirmText: '立即更新',
- cancelText: '稍后',
- showCancel: true,
- success: (res) => {
- if (res.confirm) {
- updateManager.applyUpdate()
- }
+ if (wx.canIUse('getUpdateManager')) {
+ const updateManager = wx.getUpdateManager()
+
+ updateManager.onCheckForUpdate((res) => {
+ if (res.hasUpdate) {
+ console.log('发现新版本')
}
})
- })
-
- updateManager.onUpdateFailed(() => {
- wx.showModal({
- title: '更新失败',
- content: '新版本下载失败,请稍后重新打开小程序或删除后重新搜索打开。',
- showCancel: false
+
+ updateManager.onUpdateReady(() => {
+ wx.showModal({
+ title: '更新提示',
+ content: '新版本已准备好,是否重启应用?',
+ success: (res) => {
+ if (res.confirm) {
+ updateManager.applyUpdate()
+ }
+ }
+ })
})
- })
+
+ updateManager.onUpdateFailed(() => {
+ wx.showToast({
+ title: '更新失败,请稍后重试',
+ icon: 'none'
+ })
+ })
+ }
},
/**
@@ -513,9 +480,7 @@ App({
this.globalData.isLoggedIn = false
this.globalData.purchasedSections = []
this.globalData.hasFullBook = false
- this.globalData.matchCount = 0
- this.globalData.matchQuota = null
-
+
wx.removeStorageSync('userInfo')
wx.removeStorageSync('token')
},
diff --git a/miniprogram/app.json b/miniprogram/app.json
index e70546ce..4bbe3721 100644
--- a/miniprogram/app.json
+++ b/miniprogram/app.json
@@ -1 +1,67 @@
-{"pages":["pages/index/index","pages/chapters/chapters","pages/match/match","pages/my/my","pages/read/read","pages/about/about","pages/agreement/agreement","pages/privacy/privacy","pages/referral/referral","pages/purchases/purchases","pages/settings/settings","pages/search/search","pages/addresses/addresses","pages/addresses/edit","pages/withdraw-records/withdraw-records","pages/scan/scan","pages/profile-edit/profile-edit"],"window":{"backgroundTextStyle":"light","navigationBarBackgroundColor":"#000000","navigationBarTitleText":"Soul创业派对","navigationBarTextStyle":"white","backgroundColor":"#000000","navigationStyle":"custom"},"tabBar":{"custom":true,"color":"#8e8e93","selectedColor":"#00CED1","backgroundColor":"#1c1c1e","borderStyle":"black","list":[{"pagePath":"pages/index/index","text":"首页"},{"pagePath":"pages/chapters/chapters","text":"目录"},{"pagePath":"pages/match/match","text":"找伙伴"},{"pagePath":"pages/my/my","text":"我的"}]},"usingComponents":{},"__usePrivacyCheck__":true,"permission":{"scope.userLocation":{"desc":"用于匹配附近的书友"}},"requiredPrivateInfos":["getLocation"],"lazyCodeLoading":"requiredComponents","style":"v2","sitemapLocation":"sitemap.json"}
+{
+ "pages": [
+ "pages/index/index",
+ "pages/chapters/chapters",
+ "pages/match/match",
+ "pages/my/my",
+ "pages/read/read",
+ "pages/about/about",
+ "pages/agreement/agreement",
+ "pages/privacy/privacy",
+ "pages/referral/referral",
+ "pages/purchases/purchases",
+ "pages/settings/settings",
+ "pages/search/search",
+ "pages/addresses/addresses",
+ "pages/addresses/edit",
+ "pages/withdraw-records/withdraw-records",
+ "pages/vip/vip",
+ "pages/member-detail/member-detail"
+ ],
+ "window": {
+ "backgroundTextStyle": "light",
+ "navigationBarBackgroundColor": "#000000",
+ "navigationBarTitleText": "Soul创业派对",
+ "navigationBarTextStyle": "white",
+ "backgroundColor": "#000000",
+ "navigationStyle": "custom"
+ },
+ "tabBar": {
+ "custom": true,
+ "color": "#8e8e93",
+ "selectedColor": "#00CED1",
+ "backgroundColor": "#1c1c1e",
+ "borderStyle": "black",
+ "list": [
+ {
+ "pagePath": "pages/index/index",
+ "text": "首页"
+ },
+ {
+ "pagePath": "pages/chapters/chapters",
+ "text": "目录"
+ },
+ {
+ "pagePath": "pages/match/match",
+ "text": "找伙伴"
+ },
+ {
+ "pagePath": "pages/my/my",
+ "text": "我的"
+ }
+ ]
+ },
+ "usingComponents": {},
+ "__usePrivacyCheck__": true,
+ "permission": {
+ "scope.userLocation": {
+ "desc": "用于匹配附近的书友"
+ }
+ },
+ "requiredPrivateInfos": [
+ "getLocation"
+ ],
+ "lazyCodeLoading": "requiredComponents",
+ "style": "v2",
+ "sitemapLocation": "sitemap.json"
+}
\ No newline at end of file
diff --git a/miniprogram/pages/addresses/addresses.js b/miniprogram/pages/addresses/addresses.js
index cd13fe90..685528cf 100644
--- a/miniprogram/pages/addresses/addresses.js
+++ b/miniprogram/pages/addresses/addresses.js
@@ -119,13 +119,5 @@ Page({
// 返回
goBack() {
wx.navigateBack()
- },
-
- onShareAppMessage() {
- const ref = app.getMyReferralCode()
- return {
- title: 'Soul创业派对 - 收货地址',
- path: ref ? `/pages/addresses/addresses?ref=${ref}` : '/pages/addresses/addresses'
- }
}
})
diff --git a/miniprogram/pages/addresses/edit.js b/miniprogram/pages/addresses/edit.js
index 4f45893c..9542c1dc 100644
--- a/miniprogram/pages/addresses/edit.js
+++ b/miniprogram/pages/addresses/edit.js
@@ -197,13 +197,5 @@ Page({
// 返回
goBack() {
wx.navigateBack()
- },
-
- onShareAppMessage() {
- const ref = app.getMyReferralCode()
- return {
- title: 'Soul创业派对 - 编辑地址',
- path: ref ? `/pages/addresses/edit?ref=${ref}` : '/pages/addresses/edit'
- }
}
})
diff --git a/miniprogram/pages/agreement/agreement.js b/miniprogram/pages/agreement/agreement.js
index cff31e3b..aedd4c68 100644
--- a/miniprogram/pages/agreement/agreement.js
+++ b/miniprogram/pages/agreement/agreement.js
@@ -17,13 +17,5 @@ Page({
goBack() {
wx.navigateBack()
- },
-
- onShareAppMessage() {
- const ref = app.getMyReferralCode()
- return {
- title: 'Soul创业派对 - 用户协议',
- path: ref ? `/pages/agreement/agreement?ref=${ref}` : '/pages/agreement/agreement'
- }
}
})
diff --git a/miniprogram/pages/chapters/chapters.js b/miniprogram/pages/chapters/chapters.js
index 5f4dd460..d002ed2a 100644
--- a/miniprogram/pages/chapters/chapters.js
+++ b/miniprogram/pages/chapters/chapters.js
@@ -6,66 +6,203 @@
*/
const app = getApp()
-const PART_NUMBERS = { 'part-1': '一', 'part-2': '二', 'part-3': '三', 'part-4': '四', 'part-5': '五' }
-
-function buildNestedBookData(list) {
- const parts = {}
- const appendices = []
- let epilogueMid = 0
- let prefaceMid = 0
- list.forEach(ch => {
- if (ch.id === 'preface') {
- prefaceMid = ch.mid || 0
- return
- }
- const section = {
- id: ch.id,
- mid: ch.mid || 0,
- title: ch.sectionTitle || ch.chapterTitle || ch.id,
- isFree: !!ch.isFree,
- price: ch.price != null ? Number(ch.price) : 1
- }
- if (ch.id === 'epilogue') {
- epilogueMid = ch.mid || 0
- return
- }
- if ((ch.id || '').startsWith('appendix')) {
- appendices.push({ id: ch.id, mid: ch.mid || 0, title: ch.sectionTitle || ch.chapterTitle || ch.id })
- return
- }
- if (!ch.partId || ch.id === 'preface') return
- const pid = ch.partId
- const cid = ch.chapterId || 'chapter-' + (ch.id || '').split('.')[0]
- if (!parts[pid]) {
- parts[pid] = { id: pid, number: PART_NUMBERS[pid] || pid, title: ch.partTitle || pid, subtitle: ch.chapterTitle || '', chapters: {} }
- }
- if (!parts[pid].chapters[cid]) {
- parts[pid].chapters[cid] = { id: cid, title: ch.chapterTitle || cid, sections: [] }
- }
- parts[pid].chapters[cid].sections.push(section)
- })
- const bookData = Object.values(parts)
- .sort((a, b) => (a.id || '').localeCompare(b.id || ''))
- .map(p => ({
- ...p,
- chapters: Object.values(p.chapters).sort((a, b) => (a.id || '').localeCompare(b.id || ''))
- }))
- return { bookData, appendixList: appendices, epilogueMid, prefaceMid }
-}
Page({
data: {
+ // 系统信息
statusBarHeight: 44,
navBarHeight: 88,
+
+ // 用户状态
isLoggedIn: false,
hasFullBook: false,
purchasedSections: [],
+
+ // 书籍数据 - 完整真实标题
totalSections: 62,
- bookData: [],
- expandedPart: null,
- appendixList: [],
- epilogueMid: 0,
- prefaceMid: 0
+ bookData: [
+ {
+ id: 'part-1',
+ number: '一',
+ title: '真实的人',
+ subtitle: '人与人之间的底层逻辑',
+ chapters: [
+ {
+ id: 'chapter-1',
+ title: '第1章|人与人之间的底层逻辑',
+ sections: [
+ { id: '1.1', title: '荷包:电动车出租的被动收入模式', isFree: true, price: 1 },
+ { id: '1.2', title: '老墨:资源整合高手的社交方法', isFree: false, price: 1 },
+ { id: '1.3', title: '笑声背后的MBTI:为什么ENTJ适合做资源,INTP适合做系统', isFree: false, price: 1 },
+ { id: '1.4', title: '人性的三角结构:利益、情感、价值观', isFree: false, price: 1 },
+ { id: '1.5', title: '沟通差的问题:为什么你说的别人听不懂', isFree: false, price: 1 }
+ ]
+ },
+ {
+ id: 'chapter-2',
+ title: '第2章|人性困境案例',
+ sections: [
+ { id: '2.1', title: '相亲故事:你以为找的是人,实际是在找模式', isFree: false, price: 1 },
+ { id: '2.2', title: '找工作迷茫者:为什么简历解决不了人生', isFree: false, price: 1 },
+ { id: '2.3', title: '撸运费险:小钱困住大脑的真实心理', isFree: false, price: 1 },
+ { id: '2.4', title: '游戏上瘾的年轻人:不是游戏吸引他,是生活没吸引力', isFree: false, price: 1 },
+ { id: '2.5', title: '健康焦虑(我的糖尿病经历):疾病是人生的第一次清醒', isFree: false, price: 1 }
+ ]
+ }
+ ]
+ },
+ {
+ id: 'part-2',
+ number: '二',
+ title: '真实的行业',
+ subtitle: '电商、内容、传统行业解析',
+ chapters: [
+ {
+ id: 'chapter-3',
+ title: '第3章|电商篇',
+ sections: [
+ { id: '3.1', title: '3000万流水如何跑出来(退税模式解析)', isFree: false, price: 1 },
+ { id: '3.2', title: '供应链之王 vs 打工人:利润不在前端', isFree: false, price: 1 },
+ { id: '3.3', title: '社区团购的底层逻辑', isFree: false, price: 1 },
+ { id: '3.4', title: '跨境电商与退税套利', isFree: false, price: 1 }
+ ]
+ },
+ {
+ id: 'chapter-4',
+ title: '第4章|内容商业篇',
+ sections: [
+ { id: '4.1', title: '旅游号:30天10万粉的真实逻辑', isFree: false, price: 1 },
+ { id: '4.2', title: '做号工厂:如何让一个号变成一个机器', isFree: false, price: 1 },
+ { id: '4.3', title: '情绪内容为什么比专业内容更赚钱', isFree: false, price: 1 },
+ { id: '4.4', title: '猫与宠物号:为什么宠物赛道永不过时', isFree: false, price: 1 },
+ { id: '4.5', title: '直播间里的三种人:演员、技术工、系统流', isFree: false, price: 1 }
+ ]
+ },
+ {
+ id: 'chapter-5',
+ title: '第5章|传统行业篇',
+ sections: [
+ { id: '5.1', title: '拍卖行抱朴:一天240万的摇号生意', isFree: false, price: 1 },
+ { id: '5.2', title: '土地拍卖:招拍挂背后的游戏规则', isFree: false, price: 1 },
+ { id: '5.3', title: '地摊经济数字化:一个月900块的餐车生意', isFree: false, price: 1 },
+ { id: '5.4', title: '不良资产拍卖:我错过的一个亿佣金', isFree: false, price: 1 },
+ { id: '5.5', title: '桶装水李总:跟物业合作的轻资产模式', isFree: false, price: 1 }
+ ]
+ }
+ ]
+ },
+ {
+ id: 'part-3',
+ number: '三',
+ title: '真实的错误',
+ subtitle: '我和别人犯过的错',
+ chapters: [
+ {
+ id: 'chapter-6',
+ title: '第6章|我人生错过的4件大钱',
+ sections: [
+ { id: '6.1', title: '电商财税窗口:2016年的千万级机会', isFree: false, price: 1 },
+ { id: '6.2', title: '供应链金融:我不懂的杠杆游戏', isFree: false, price: 1 },
+ { id: '6.3', title: '内容红利:2019年我为什么没做抖音', isFree: false, price: 1 },
+ { id: '6.4', title: '数据资产化:我还在观望的未来机会', isFree: false, price: 1 }
+ ]
+ },
+ {
+ id: 'chapter-7',
+ title: '第7章|别人犯的错误',
+ sections: [
+ { id: '7.1', title: '投资房年轻人的迷茫:资金 vs 能力', isFree: false, price: 1 },
+ { id: '7.2', title: '信息差骗局:永远有人靠卖学习赚钱', isFree: false, price: 1 },
+ { id: '7.3', title: '在Soul找恋爱但想赚钱的人', isFree: false, price: 1 },
+ { id: '7.4', title: '创业者的三种死法:冲动、轻信、没结构', isFree: false, price: 1 },
+ { id: '7.5', title: '人情生意的终点:关系越多亏得越多', isFree: false, price: 1 }
+ ]
+ }
+ ]
+ },
+ {
+ id: 'part-4',
+ number: '四',
+ title: '真实的赚钱',
+ subtitle: '底层结构与真实案例',
+ chapters: [
+ {
+ id: 'chapter-8',
+ title: '第8章|底层结构',
+ sections: [
+ { id: '8.1', title: '流量杠杆:抖音、Soul、飞书', isFree: false, price: 1 },
+ { id: '8.2', title: '价格杠杆:供应链与信息差', isFree: false, price: 1 },
+ { id: '8.3', title: '时间杠杆:自动化 + AI', isFree: false, price: 1 },
+ { id: '8.4', title: '情绪杠杆:咨询、婚恋、生意场', isFree: false, price: 1 },
+ { id: '8.5', title: '社交杠杆:认识谁比你会什么更重要', isFree: false, price: 1 },
+ { id: '8.6', title: '云阿米巴:分不属于自己的钱', isFree: false, price: 1 }
+ ]
+ },
+ {
+ id: 'chapter-9',
+ title: '第9章|我在Soul上亲访的赚钱案例',
+ sections: [
+ { id: '9.1', title: '游戏账号私域:账号即资产', isFree: false, price: 1 },
+ { id: '9.2', title: '健康包模式:高复购、高毛利', isFree: false, price: 1 },
+ { id: '9.3', title: '药物私域:长期关系赛道', isFree: false, price: 1 },
+ { id: '9.4', title: '残疾机构合作:退税 × AI × 人力成本', isFree: false, price: 1 },
+ { id: '9.5', title: '私域银行:粉丝即小股东', isFree: false, price: 1 },
+ { id: '9.6', title: 'Soul派对房:陌生人成交的最快场景', isFree: false, price: 1 },
+ { id: '9.7', title: '飞书中台:从聊天到成交的流程化体系', isFree: false, price: 1 },
+ { id: '9.8', title: '餐饮女孩:6万营收、1万利润的死撑生意', isFree: false, price: 1 },
+ { id: '9.9', title: '电竞生态:从陪玩到签约到酒店的完整链条', isFree: false, price: 1 },
+ { id: '9.10', title: '淘客大佬:损耗30%的白色通道', isFree: false, price: 1 },
+ { id: '9.11', title: '蔬菜供应链:农户才是最赚钱的人', isFree: false, price: 1 },
+ { id: '9.12', title: '美业整合:一个人的公司如何月入十万', isFree: false, price: 1 },
+ { id: '9.13', title: 'AI工具推广:一个隐藏的高利润赛道', isFree: false, price: 1 },
+ { id: '9.14', title: '大健康私域:一个月150万的70后', isFree: false, price: 1 }
+ ]
+ }
+ ]
+ },
+ {
+ id: 'part-5',
+ number: '五',
+ title: '真实的社会',
+ subtitle: '未来职业与商业生态',
+ chapters: [
+ {
+ id: 'chapter-10',
+ title: '第10章|未来职业的变化趋势',
+ sections: [
+ { id: '10.1', title: 'AI时代:哪些工作会消失,哪些会崛起', isFree: false, price: 1 },
+ { id: '10.2', title: '一人公司:为什么越来越多人选择单干', isFree: false, price: 1 },
+ { id: '10.3', title: '为什么链接能力会成为第一价值', isFree: false, price: 1 },
+ { id: '10.4', title: '新型公司:Soul-飞书-线下的三位一体', isFree: false, price: 1 }
+ ]
+ },
+ {
+ id: 'chapter-11',
+ title: '第11章|中国社会商业生态的未来',
+ sections: [
+ { id: '11.1', title: '私域经济:为什么流量越来越贵', isFree: false, price: 1 },
+ { id: '11.2', title: '银发经济与孤独经济:两个被忽视的万亿市场', isFree: false, price: 1 },
+ { id: '11.3', title: '流量红利的终局', isFree: false, price: 1 },
+ { id: '11.4', title: '大模型 + 供应链的组合拳', isFree: false, price: 1 },
+ { id: '11.5', title: '社会分层的最终逻辑', isFree: false, price: 1 }
+ ]
+ }
+ ]
+ }
+ ],
+
+ // 展开状态
+ expandedPart: 'part-1',
+
+ // 附录
+ appendixList: [
+ { id: 'appendix-1', title: '附录1|Soul派对房精选对话' },
+ { id: 'appendix-2', title: '附录2|创业者自检清单' },
+ { id: 'appendix-3', title: '附录3|本书提到的工具和资源' }
+ ],
+
+ // 每日新增章节
+ dailyChapters: []
},
onLoad() {
@@ -74,44 +211,21 @@ Page({
navBarHeight: app.globalData.navBarHeight
})
this.updateUserStatus()
- this.loadAndEnrichBookData()
+ this.loadDailyChapters()
+ this.loadTotalFromServer()
},
- async loadAndEnrichBookData() {
+ async loadTotalFromServer() {
try {
- let list = app.globalData.bookData || []
- if (!list.length) {
- const res = await app.request('/api/miniprogram/book/all-chapters')
- if (res?.data) {
- list = res.data
- app.globalData.bookData = list
- }
+ const res = await app.request({ url: '/api/book/all-chapters', silent: true })
+ if (res && res.total) {
+ this.setData({ totalSections: res.total })
}
- if (!list.length) {
- this.setData({ bookData: [], appendixList: [] })
- return
- }
- const { bookData, appendixList, epilogueMid, prefaceMid } = buildNestedBookData(list)
- const firstPartId = bookData[0]?.id || null
- this.setData({
- bookData,
- appendixList,
- epilogueMid,
- prefaceMid,
- totalSections: list.length,
- expandedPart: firstPartId || this.data.expandedPart
- })
- } catch (e) {
- console.error('[Chapters] 加载目录失败:', e)
- this.setData({ bookData: [], appendixList: [] })
- }
+ } catch (e) {}
},
onShow() {
- this.updateUserStatus()
- if (!app.globalData.bookData?.length) {
- this.loadAndEnrichBookData()
- }
+ // 设置TabBar选中状态
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
const tabBar = this.getTabBar()
if (tabBar.updateSelected) {
@@ -120,6 +234,7 @@ Page({
tabBar.setData({ selected: 1 })
}
}
+ this.updateUserStatus()
},
// 更新用户状态
@@ -136,11 +251,10 @@ Page({
})
},
+ // 跳转到阅读页
goToRead(e) {
const id = e.currentTarget.dataset.id
- const mid = e.currentTarget.dataset.mid || app.getSectionMid(id)
- const q = mid ? `mid=${mid}` : `id=${id}`
- wx.navigateTo({ url: `/pages/read/read?${q}` })
+ wx.navigateTo({ url: `/pages/read/read?id=${id}` })
},
// 检查是否已购买
@@ -154,16 +268,31 @@ Page({
wx.switchTab({ url: '/pages/index/index' })
},
+ async loadDailyChapters() {
+ try {
+ const res = await app.request({ url: '/api/book/all-chapters', silent: true })
+ const chapters = (res && res.data) || (res && res.chapters) || []
+ const daily = chapters
+ .filter(c => (c.sectionOrder || c.sort_order || 0) > 62)
+ .sort((a, b) => new Date(b.updatedAt || b.updated_at || 0) - new Date(a.updatedAt || a.updated_at || 0))
+ .slice(0, 20)
+ .map(c => {
+ const d = new Date(c.updatedAt || c.updated_at || Date.now())
+ return {
+ id: c.id,
+ title: c.section_title || c.title || c.sectionTitle,
+ price: c.price || 1,
+ dateStr: `${d.getMonth()+1}/${d.getDate()}`
+ }
+ })
+ if (daily.length > 0) {
+ this.setData({ dailyChapters: daily, totalSections: 62 + daily.length })
+ }
+ } catch (e) { console.log('[Chapters] 加载最新新增失败:', e) }
+ },
+
// 跳转到搜索页
goToSearch() {
wx.navigateTo({ url: '/pages/search/search' })
- },
-
- onShareAppMessage() {
- const ref = app.getMyReferralCode()
- return {
- title: 'Soul创业派对 - 目录',
- path: ref ? `/pages/chapters/chapters?ref=${ref}` : '/pages/chapters/chapters'
- }
}
})
diff --git a/miniprogram/pages/chapters/chapters.wxss b/miniprogram/pages/chapters/chapters.wxss
index 6cbbb747..7ff3d64c 100644
--- a/miniprogram/pages/chapters/chapters.wxss
+++ b/miniprogram/pages/chapters/chapters.wxss
@@ -476,6 +476,21 @@
color: rgba(255, 255, 255, 0.3);
}
+/* ===== 每日新增章节 ===== */
+.daily-section { margin: 20rpx 0; padding: 24rpx; background: rgba(255,215,0,0.04); border: 1rpx solid rgba(255,215,0,0.12); border-radius: 16rpx; }
+.daily-header { display: flex; align-items: center; gap: 12rpx; margin-bottom: 16rpx; }
+.daily-title { font-size: 30rpx; font-weight: 600; color: #FFD700; }
+.daily-badge { font-size: 22rpx; background: #FFD700; color: #000; padding: 2rpx 12rpx; border-radius: 20rpx; font-weight: bold; }
+.daily-list { display: flex; flex-direction: column; gap: 12rpx; }
+.daily-item { display: flex; justify-content: space-between; align-items: center; padding: 16rpx; background: rgba(255,255,255,0.03); border-radius: 12rpx; }
+.daily-left { display: flex; align-items: center; gap: 10rpx; flex: 1; min-width: 0; }
+.daily-new-tag { font-size: 18rpx; background: #FF4444; color: #fff; padding: 2rpx 8rpx; border-radius: 6rpx; font-weight: bold; flex-shrink: 0; }
+.daily-item-title { font-size: 26rpx; color: rgba(255,255,255,0.85); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.daily-right { display: flex; align-items: center; gap: 12rpx; flex-shrink: 0; }
+.daily-price { font-size: 26rpx; color: #FFD700; font-weight: 600; }
+.daily-date { font-size: 20rpx; color: rgba(255,255,255,0.35); }
+.daily-note { display: block; font-size: 22rpx; color: rgba(255,215,0,0.5); margin-top: 12rpx; text-align: center; }
+
/* ===== 底部留白 ===== */
.bottom-space {
height: 40rpx;
diff --git a/miniprogram/pages/index/index.js b/miniprogram/pages/index/index.js
index b97297d0..1cf16fc9 100644
--- a/miniprogram/pages/index/index.js
+++ b/miniprogram/pages/index/index.js
@@ -7,7 +7,6 @@
console.log('[Index] ===== 首页文件开始加载 =====')
const app = getApp()
-const PART_NUMBERS = { 'part-1': '一', 'part-2': '二', 'part-3': '三', 'part-4': '四', 'part-5': '五' }
Page({
data: {
@@ -20,13 +19,37 @@ Page({
hasFullBook: false,
readCount: 0,
+ // 书籍数据
totalSections: 62,
bookData: [],
- featuredSections: [],
+
+ // 推荐章节
+ featuredSections: [
+ { id: '1.1', title: '荷包:电动车出租的被动收入模式', tag: '免费', tagClass: 'tag-free', part: '真实的人' },
+ { id: '3.1', title: '3000万流水如何跑出来', tag: '热门', tagClass: 'tag-pink', part: '真实的行业' },
+ { id: '8.1', title: '流量杠杆:抖音、Soul、飞书', tag: '推荐', tagClass: 'tag-purple', part: '真实的赚钱' }
+ ],
+
+ // 最新章节(动态计算)
latestSection: null,
latestLabel: '最新更新',
- partsList: [],
- prefaceMid: 0,
+
+ // 内容概览
+ partsList: [
+ { id: 'part-1', number: '一', title: '真实的人', subtitle: '人与人之间的底层逻辑' },
+ { id: 'part-2', number: '二', title: '真实的行业', subtitle: '电商、内容、传统行业解析' },
+ { id: 'part-3', number: '三', title: '真实的错误', subtitle: '我和别人犯过的错' },
+ { id: 'part-4', number: '四', title: '真实的赚钱', subtitle: '底层结构与真实案例' },
+ { id: 'part-5', number: '五', title: '真实的社会', subtitle: '未来职业与商业生态' }
+ ],
+
+ // 超级个体(VIP会员)
+ superMembers: [],
+
+ // 最新新增章节
+ latestChapters: [],
+
+ // 加载状态
loading: true
},
@@ -39,9 +62,10 @@ Page({
navBarHeight: app.globalData.navBarHeight
})
- // 处理分享参数与扫码 scene(推荐码绑定)
- if (options && (options.ref || options.scene)) {
- app.handleReferralCode(options)
+ // 处理分享参数(推荐码绑定)
+ if (options && options.ref) {
+ console.log('[Index] 检测到推荐码:', options.ref)
+ app.handleReferralCode({ query: options })
}
// 初始化数据
@@ -79,9 +103,11 @@ Page({
// 初始化数据
async initData() {
this.setData({ loading: true })
-
try {
await this.loadBookData()
+ await this.loadFeaturedFromServer()
+ this.loadSuperMembers()
+ this.loadLatestChapters()
} catch (e) {
console.error('初始化失败:', e)
} finally {
@@ -89,82 +115,100 @@ Page({
}
},
- async loadBookData() {
+ async loadSuperMembers() {
try {
- const [chaptersRes, hotRes] = await Promise.all([
- app.request('/api/miniprogram/book/all-chapters'),
- app.request('/api/miniprogram/book/hot')
- ])
- const list = chaptersRes?.data || []
- const hotList = hotRes?.data || []
- app.globalData.bookData = list
+ // 优先加载VIP会员
+ let members = []
+ try {
+ const res = await app.request({ url: '/api/vip/members', silent: true })
+ if (res && res.success && res.data) {
+ members = res.data.filter(u => u.avatar || u.vip_avatar).slice(0, 4).map(u => ({
+ id: u.id, name: u.vip_name || u.nickname || '会员',
+ avatar: u.vip_avatar || u.avatar, isVip: true
+ }))
+ }
+ } catch (e) {}
+ // 不足4个则用有头像的普通用户补充
+ if (members.length < 4) {
+ try {
+ const dbRes = await app.request({ url: '/api/miniprogram/users?limit=20', silent: true })
+ if (dbRes && dbRes.success && dbRes.data) {
+ const existIds = new Set(members.map(m => m.id))
+ const extra = dbRes.data
+ .filter(u => u.avatar && u.nickname && !existIds.has(u.id))
+ .slice(0, 4 - members.length)
+ .map(u => ({ id: u.id, name: u.nickname, avatar: u.avatar, isVip: u.is_vip === 1 }))
+ members = members.concat(extra)
+ }
+ } catch (e) {}
+ }
+ this.setData({ superMembers: members })
+ } catch (e) { console.log('[Index] 加载超级个体失败:', e) }
+ },
- const toSection = (ch) => ({
- id: ch.id,
- mid: ch.mid || 0,
- title: ch.sectionTitle || ch.chapterTitle || ch.id,
- part: ch.partTitle || ''
+ // 从服务端获取精选推荐(加权算法:阅读量50% + 时效30% + 付款率20%)和最新更新
+ async loadFeaturedFromServer() {
+ try {
+ const res = await app.request({ url: '/api/book/all-chapters', silent: true })
+ const chapters = (res && res.data) ? res.data : (res && res.chapters) ? res.chapters : []
+ let featured = (res && res.featuredSections) ? res.featuredSections : []
+ // 服务端未返回精选时,从前端按更新时间取前3条有效章节作为回退
+ if (featured.length === 0 && chapters.length > 0) {
+ const valid = chapters.filter(c => {
+ const id = (c.id || '').toLowerCase()
+ const pt = (c.part_title || c.partTitle || '').toLowerCase()
+ return !id.includes('preface') && !id.includes('epilogue') && !id.includes('appendix')
+ && !pt.includes('序言') && !pt.includes('尾声') && !pt.includes('附录')
+ })
+ featured = valid
+ .sort((a, b) => new Date(b.updated_at || b.updatedAt || 0) - new Date(a.updated_at || a.updatedAt || 0))
+ .slice(0, 5)
+ }
+ if (featured.length > 0) {
+ this.setData({
+ featuredSections: featured.slice(0, 3).map(s => ({
+ id: s.id || s.section_id,
+ title: s.section_title || s.title,
+ part: (s.cleanPartTitle || s.part_title || '').replace(/[_||]/g, ' ').trim()
+ }))
+ })
+ }
+
+ // 最新更新 = 按 updated_at 排序第1篇(排除序言/尾声/附录)
+ const validChapters = chapters.filter(c => {
+ const id = (c.id || '').toLowerCase()
+ const pt = (c.part_title || c.partTitle || '').toLowerCase()
+ return !id.includes('preface') && !id.includes('epilogue') && !id.includes('appendix')
+ && !pt.includes('序言') && !pt.includes('尾声') && !pt.includes('附录')
})
-
- let featuredSections = []
- if (hotList.length >= 3) {
- const freeCh = list.find(c => c.isFree || c.id === '1.1' || c.id === 'preface')
- const picks = []
- if (freeCh) picks.push({ ...toSection(freeCh), tag: '免费', tagClass: 'tag-free' })
- hotList.slice(0, 3 - picks.length).forEach((ch, i) => {
- if (!picks.find(p => p.id === ch.id)) {
- picks.push({ ...toSection(ch), tag: i === 0 ? '热门' : '推荐', tagClass: i === 0 ? 'tag-pink' : 'tag-purple' })
+ if (validChapters.length > 0) {
+ validChapters.sort((a, b) => new Date(b.updated_at || b.updatedAt || 0) - new Date(a.updated_at || a.updatedAt || 0))
+ const latest = validChapters[0]
+ this.setData({
+ latestSection: {
+ id: latest.id || latest.section_id,
+ title: latest.section_title || latest.title,
+ part: latest.cleanPartTitle || latest.part_title || ''
}
})
- featuredSections = picks.slice(0, 3)
}
- if (featuredSections.length < 3 && list.length > 0) {
- const fallback = list.filter(c => c.id && !['preface', 'epilogue'].includes(c.id)).slice(0, 3)
- featuredSections = fallback.map((ch, i) => ({
- ...toSection(ch),
- tag: ch.isFree ? '免费' : (i === 0 ? '热门' : '推荐'),
- tagClass: ch.isFree ? 'tag-free' : (i === 0 ? 'tag-pink' : 'tag-purple')
- }))
+ } catch (e) {
+ console.log('[Index] 从服务端加载推荐失败,使用默认:', e)
+ }
+ },
+
+ async loadBookData() {
+ try {
+ const res = await app.request({ url: '/api/book/all-chapters', silent: true })
+ if (res && (res.data || res.chapters)) {
+ const chapters = res.data || res.chapters || []
+ this.setData({
+ bookData: chapters,
+ totalSections: res.total || chapters.length || 62
+ })
}
-
- const partMap = {}
- list.forEach(ch => {
- if (!ch.partId || ch.id === 'preface' || ch.id === 'epilogue' || (ch.id || '').startsWith('appendix')) return
- if (!partMap[ch.partId]) {
- partMap[ch.partId] = { id: ch.partId, number: PART_NUMBERS[ch.partId] || ch.partId, title: ch.partTitle || ch.partId, subtitle: ch.chapterTitle || '' }
- }
- })
- const partsList = Object.values(partMap).sort((a, b) => (a.id || '').localeCompare(b.id || ''))
-
- const paidCandidates = list.filter(c => c.id && !['preface', 'epilogue'].includes(c.id) && !(c.id || '').startsWith('appendix') && c.partId)
- const { hasFullBook, purchasedSections } = app.globalData
- let candidates = paidCandidates
- if (!hasFullBook && purchasedSections?.length) {
- const unpurchased = paidCandidates.filter(c => !purchasedSections.includes(c.id))
- if (unpurchased.length > 0) candidates = unpurchased
- }
- const userId = app.globalData.userInfo?.id || wx.getStorageSync('userId') || 'guest'
- const today = new Date().toISOString().split('T')[0]
- const seed = (userId + today).split('').reduce((a, b) => a + b.charCodeAt(0), 0)
- const selectedCh = candidates[seed % Math.max(candidates.length, 1)]
- const latestSection = selectedCh ? { ...toSection(selectedCh), mid: selectedCh.mid || 0 } : null
- const latestLabel = candidates.length === paidCandidates.length ? '推荐阅读' : '为你推荐'
-
- const prefaceCh = list.find(c => c.id === 'preface')
- const prefaceMid = prefaceCh?.mid || 0
-
- this.setData({
- bookData: list,
- totalSections: list.length || 62,
- featuredSections,
- partsList,
- latestSection,
- latestLabel,
- prefaceMid
- })
} catch (e) {
console.error('加载书籍数据失败:', e)
- this.setData({ featuredSections: [], partsList: [], latestSection: null })
}
},
@@ -189,11 +233,10 @@ Page({
wx.navigateTo({ url: '/pages/search/search' })
},
+ // 跳转到阅读页
goToRead(e) {
const id = e.currentTarget.dataset.id
- const mid = e.currentTarget.dataset.mid || app.getSectionMid(id)
- const q = mid ? `mid=${mid}` : `id=${id}`
- wx.navigateTo({ url: `/pages/read/read?${q}` })
+ wx.navigateTo({ url: `/pages/read/read?id=${id}` })
},
// 跳转到匹配页
@@ -201,6 +244,40 @@ Page({
wx.switchTab({ url: '/pages/match/match' })
},
+ goToVip() {
+ wx.navigateTo({ url: '/pages/vip/vip' })
+ },
+
+ goToSuperList() {
+ wx.switchTab({ url: '/pages/match/match' })
+ },
+
+ async loadLatestChapters() {
+ try {
+ const res = await app.request({ url: '/api/book/all-chapters', silent: true })
+ const chapters = (res && res.data) || (res && res.chapters) || []
+ const latest = chapters
+ .filter(c => (c.sectionOrder || c.sort_order || 0) > 62)
+ .sort((a, b) => new Date(b.updatedAt || b.updated_at || 0) - new Date(a.updatedAt || a.updated_at || 0))
+ .slice(0, 10)
+ .map(c => {
+ const d = new Date(c.updatedAt || c.updated_at || Date.now())
+ return {
+ id: c.id,
+ title: c.section_title || c.title || c.sectionTitle,
+ price: c.price || 1,
+ dateStr: `${d.getMonth() + 1}/${d.getDate()}`
+ }
+ })
+ this.setData({ latestChapters: latest })
+ } catch (e) { console.log('[Index] 加载最新新增失败:', e) }
+ },
+
+ goToMemberDetail(e) {
+ const id = e.currentTarget.dataset.id
+ wx.navigateTo({ url: `/pages/member-detail/member-detail?id=${id}` })
+ },
+
// 跳转到我的页面
goToMy() {
wx.switchTab({ url: '/pages/my/my' })
@@ -211,13 +288,5 @@ Page({
await this.initData()
this.updateUserStatus()
wx.stopPullDownRefresh()
- },
-
- onShareAppMessage() {
- const ref = app.getMyReferralCode()
- return {
- title: 'Soul创业派对 - 真实商业故事',
- path: ref ? `/pages/index/index?ref=${ref}` : '/pages/index/index'
- }
}
})
diff --git a/miniprogram/pages/index/index.wxml b/miniprogram/pages/index/index.wxml
index 76914636..470c18b5 100644
--- a/miniprogram/pages/index/index.wxml
+++ b/miniprogram/pages/index/index.wxml
@@ -37,7 +37,7 @@
-
+
最新更新
{{latestSection.title}}
@@ -79,6 +79,36 @@
+
+
+
+
+
+
+
+ {{item.name[0] || '会'}}
+
+ {{item.name}}
+
+
+
+ 成为会员,展示你的项目
+ 加入创业派对 →
+
+
+
-
-
- 内容概览
-
-
-
- {{item.number}}
-
-
- {{item.title}}
- {{item.subtitle}}
-
- →
+
+
+
-
-
-
-
-
- 序言
- 为什么我每天早上6点在Soul开播?
+
+
+
+ NEW
+ {{item.title}}
+
+
+ ¥{{item.price}}
+ {{item.dateStr}}
+
+
- 免费
diff --git a/miniprogram/pages/index/index.wxss b/miniprogram/pages/index/index.wxss
index c8ba095b..b3d68715 100644
--- a/miniprogram/pages/index/index.wxss
+++ b/miniprogram/pages/index/index.wxss
@@ -498,78 +498,135 @@
color: rgba(255, 255, 255, 0.6);
}
-/* ===== 创业老板排行 ===== */
-.members-grid {
- display: flex;
- flex-wrap: wrap;
- gap: 20rpx;
- padding: 0 8rpx;
+/* ===== 超级个体 ===== */
+.super-grid {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 24rpx 16rpx;
}
-.member-cell {
- width: calc(25% - 15rpx);
+.super-item {
display: flex;
flex-direction: column;
align-items: center;
- padding: 16rpx 0;
+ gap: 10rpx;
}
-.member-avatar-wrap {
- position: relative;
- width: 100rpx;
- height: 100rpx;
- margin-bottom: 10rpx;
-}
-.member-avatar {
- width: 100rpx;
- height: 100rpx;
+.super-avatar {
+ width: 108rpx;
+ height: 108rpx;
border-radius: 50%;
- border: 3rpx solid #FFD700;
-}
-.member-avatar-placeholder {
- width: 100rpx;
- height: 100rpx;
- border-radius: 50%;
- background: linear-gradient(135deg, #1c1c1e, #2c2c2e);
- border: 3rpx solid #FFD700;
+ overflow: hidden;
+ background: rgba(0,206,209,0.1);
display: flex;
align-items: center;
justify-content: center;
- font-size: 36rpx;
+ border: 3rpx solid rgba(255,255,255,0.1);
+}
+.super-avatar-vip {
+ border: 3rpx solid #FFD700;
+ box-shadow: 0 0 12rpx rgba(255,215,0,0.3);
+}
+.super-avatar-img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+.super-avatar-text {
+ font-size: 40rpx;
+ font-weight: 600;
+ color: #00CED1;
+}
+.super-name {
+ font-size: 22rpx;
+ color: rgba(255,255,255,0.7);
+ text-align: center;
+ width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.super-empty {
+ padding: 32rpx;
+ text-align: center;
+ background: rgba(255,255,255,0.03);
+ border-radius: 16rpx;
+}
+.super-empty-text {
+ font-size: 24rpx;
+ color: rgba(255,255,255,0.4);
+ display: block;
+ margin-bottom: 16rpx;
+}
+.super-empty-btn {
+ font-size: 26rpx;
+ color: #00CED1;
+}
+
+/* ===== 最新新增 ===== */
+.daily-badge-wrap {
+ display: inline-flex;
+ align-items: center;
+}
+.daily-badge {
+ background: #FF4500;
+ color: #fff;
+ font-size: 20rpx;
+ font-weight: 600;
+ padding: 4rpx 12rpx;
+ border-radius: 16rpx;
+ margin-left: 8rpx;
+}
+.latest-list {
+ display: flex;
+ flex-direction: column;
+ gap: 12rpx;
+}
+.latest-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 20rpx 24rpx;
+ background: rgba(255,255,255,0.04);
+ border-radius: 12rpx;
+ border-left: 4rpx solid #FF4500;
+}
+.latest-left {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ gap: 12rpx;
+ min-width: 0;
+}
+.latest-new-tag {
+ font-size: 18rpx;
+ font-weight: 700;
+ color: #FF4500;
+ background: rgba(255,69,0,0.15);
+ padding: 2rpx 10rpx;
+ border-radius: 6rpx;
+ flex-shrink: 0;
+}
+.latest-title {
+ font-size: 26rpx;
+ color: rgba(255,255,255,0.9);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.latest-right {
+ display: flex;
+ align-items: center;
+ gap: 12rpx;
+ flex-shrink: 0;
+ margin-left: 12rpx;
+}
+.latest-price {
+ font-size: 26rpx;
+ font-weight: 600;
color: #FFD700;
}
-.member-vip-dot {
- position: absolute;
- bottom: 0;
- right: 0;
- width: 30rpx;
- height: 30rpx;
- border-radius: 50%;
- background: linear-gradient(135deg, #FFD700, #FFA500);
- color: #000;
- font-size: 16rpx;
- font-weight: bold;
- display: flex;
- align-items: center;
- justify-content: center;
- border: 2rpx solid #000;
-}
-.member-name {
- font-size: 24rpx;
- color: rgba(255,255,255,0.9);
- text-align: center;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- max-width: 140rpx;
-}
-.member-project {
- font-size: 20rpx;
+.latest-date {
+ font-size: 22rpx;
color: rgba(255,255,255,0.4);
- text-align: center;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- max-width: 140rpx;
- margin-top: 4rpx;
}
/* ===== 底部留白 ===== */
diff --git a/miniprogram/pages/match/match.js b/miniprogram/pages/match/match.js
index 194cc3d8..6d3b9d87 100644
--- a/miniprogram/pages/match/match.js
+++ b/miniprogram/pages/match/match.js
@@ -38,7 +38,6 @@ Page({
todayMatchCount: 0,
totalMatchesAllowed: FREE_MATCH_LIMIT,
matchesRemaining: FREE_MATCH_LIMIT,
- showQuotaExhausted: false,
needPayToMatch: false,
// 匹配状态
@@ -67,53 +66,19 @@ Page({
// 解锁弹窗
showUnlockModal: false,
- // 手机号绑定弹窗(一键加好友前校验)
- showBindPhoneModal: false,
- pendingAddWechatAfterBind: false,
- bindPhoneInput: '',
- showMatchPhoneManualInput: false,
-
- // 登录弹窗(未登录时点击匹配弹出)
- showLoginModal: false,
- isLoggingIn: false,
- agreeProtocol: false,
-
// 匹配价格(可配置)
matchPrice: 1,
- extraMatches: 0,
- // 好友优惠展示(与 read 页一致)
- userDiscount: 5,
- hasReferralDiscount: false,
- showDiscountHint: false,
- displayMatchPrice: 1
+ extraMatches: 0
},
- onLoad(options = {}) {
- // ref:支持 query.ref 或 scene 中的 ref=xxx(分享进入时)
- let ref = options.ref
- if (!ref && options.scene) {
- const sceneStr = (typeof options.scene === 'string' ? decodeURIComponent(options.scene) : '').trim()
- const parts = sceneStr.split(/[&_]/)
- for (const part of parts) {
- const eq = part.indexOf('=')
- if (eq > 0) {
- const k = part.slice(0, eq)
- const v = part.slice(eq + 1)
- if (k === 'ref' && v) { ref = v; break }
- }
- }
- }
- if (ref) {
- wx.setStorageSync('referral_code', ref)
- app.handleReferralCode({ query: { ref } })
- }
+ onLoad() {
this.setData({
- statusBarHeight: app.globalData.statusBarHeight || 44,
- _ref: ref
+ statusBarHeight: app.globalData.statusBarHeight || 44
})
this.loadMatchConfig()
this.loadStoredContact()
- this.refreshMatchCountAndStatus()
+ this.loadTodayMatchCount()
+ this.initUserStatus()
},
onShow() {
@@ -125,53 +90,43 @@ Page({
tabBar.setData({ selected: 2 })
}
}
- this.loadStoredContact()
- this.refreshMatchCountAndStatus()
+ this.initUserStatus()
},
- // 加载匹配配置(含 userDiscount 用于好友优惠展示)
+ // 加载匹配配置
async loadMatchConfig() {
try {
- const userId = app.globalData.userInfo?.id
- const [matchRes, configRes] = await Promise.all([
- app.request('/api/miniprogram/match/config', { method: 'GET' }),
- app.request('/api/miniprogram/config', { method: 'GET' })
- ])
-
- const matchPrice = matchRes?.success && matchRes?.data ? (matchRes.data.matchPrice || 1) : 1
- const userDiscount = configRes?.userDiscount ?? 5
- const hasReferral = !!(wx.getStorageSync('referral_code') || this.data._ref)
- const hasReferralDiscount = hasReferral && userDiscount > 0
- const displayMatchPrice = hasReferralDiscount
- ? Math.round(matchPrice * (1 - userDiscount / 100) * 100) / 100
- : matchPrice
-
- if (matchRes?.success && matchRes?.data) {
- MATCH_TYPES = matchRes.data.matchTypes || MATCH_TYPES
- FREE_MATCH_LIMIT = matchRes.data.freeMatchLimit || FREE_MATCH_LIMIT
- }
-
- this.setData({
- matchTypes: MATCH_TYPES,
- totalMatchesAllowed: FREE_MATCH_LIMIT,
- matchPrice,
- userDiscount,
- hasReferralDiscount,
- showDiscountHint: userDiscount > 0,
- displayMatchPrice
+ const res = await app.request({ url: '/api/match/config', silent: true, method: 'GET',
+ method: 'GET'
})
- console.log('[Match] 加载匹配配置成功:', { matchPrice, userDiscount, hasReferralDiscount, displayMatchPrice })
+ if (res.success && res.data) {
+ // 更新全局配置
+ MATCH_TYPES = res.data.matchTypes || MATCH_TYPES
+ FREE_MATCH_LIMIT = res.data.freeMatchLimit || FREE_MATCH_LIMIT
+ const matchPrice = res.data.matchPrice || 1
+
+ this.setData({
+ matchTypes: MATCH_TYPES,
+ totalMatchesAllowed: FREE_MATCH_LIMIT,
+ matchPrice: matchPrice
+ })
+
+ console.log('[Match] 加载匹配配置成功:', {
+ types: MATCH_TYPES.length,
+ freeLimit: FREE_MATCH_LIMIT,
+ price: matchPrice
+ })
+ }
} catch (e) {
console.log('[Match] 加载匹配配置失败,使用默认配置:', e)
}
},
- // 加载本地存储的联系方式(含用户资料的手机号、微信号)
+ // 加载本地存储的联系方式
loadStoredContact() {
- const ui = app.globalData.userInfo || {}
- const phone = wx.getStorageSync('user_phone') || ui.phone || ''
- const wechat = wx.getStorageSync('user_wechat') || ui.wechat || ui.wechatId || ''
+ const phone = wx.getStorageSync('user_phone') || ''
+ const wechat = wx.getStorageSync('user_wechat') || ''
this.setData({
phoneNumber: phone,
wechatId: wechat,
@@ -179,55 +134,49 @@ Page({
})
},
- // 从服务端刷新匹配配额并初始化用户状态(前后端双向校验,服务端为权威)
- async refreshMatchCountAndStatus() {
- if (app.globalData.isLoggedIn && app.globalData.userInfo?.id) {
- try {
- const res = await app.request(`/api/miniprogram/user/purchase-status?userId=${encodeURIComponent(app.globalData.userInfo.id)}`)
- if (res.success && res.data) {
- app.globalData.matchCount = res.data.matchCount ?? 0
- app.globalData.matchQuota = res.data.matchQuota || null
- // 根据 hasReferrer 更新优惠展示(与 read 页一致)
- const hasReferral = !!(wx.getStorageSync('referral_code') || this.data._ref || res.data.hasReferrer)
- const matchPrice = this.data.matchPrice ?? 1
- const userDiscount = this.data.userDiscount ?? 5
- const hasReferralDiscount = hasReferral && userDiscount > 0
- const displayMatchPrice = hasReferralDiscount
- ? Math.round(matchPrice * (1 - userDiscount / 100) * 100) / 100
- : matchPrice
- this.setData({ hasReferralDiscount, displayMatchPrice })
+ // 加载今日匹配次数
+ loadTodayMatchCount() {
+ try {
+ const today = new Date().toISOString().split('T')[0]
+ const stored = wx.getStorageSync('match_count_data')
+ if (stored) {
+ const data = typeof stored === 'string' ? JSON.parse(stored) : stored
+ if (data.date === today) {
+ this.setData({ todayMatchCount: data.count })
}
- } catch (e) {
- console.log('[Match] 拉取 matchQuota 失败:', e)
}
+ } catch (e) {
+ console.error('加载匹配次数失败:', e)
}
- this.initUserStatus()
},
- // 初始化用户状态(matchQuota 服务端纯计算:订单+match_records)
+ // 保存今日匹配次数
+ saveTodayMatchCount(count) {
+ const today = new Date().toISOString().split('T')[0]
+ wx.setStorageSync('match_count_data', { date: today, count })
+ },
+
+ // 初始化用户状态
initUserStatus() {
const { isLoggedIn, hasFullBook, purchasedSections } = app.globalData
- const quota = app.globalData.matchQuota
-
- // 今日剩余次数、今日已用:来自服务端 matchQuota(未登录无法计算,不能显示已用完)
- const remainToday = quota?.remainToday ?? 0
- const matchesUsedToday = quota?.matchesUsedToday ?? 0
- const purchasedRemain = quota?.purchasedRemain ?? 0
- const totalMatchesAllowed = hasFullBook ? 999999 : (quota ? remainToday + matchesUsedToday : FREE_MATCH_LIMIT)
- // 仅登录且服务端返回配额时,才判断是否已用完;未登录时显示「开始匹配」
- const needPayToMatch = isLoggedIn && !hasFullBook && (quota ? remainToday <= 0 : false)
- const showQuotaExhausted = isLoggedIn && !hasFullBook && (quota ? remainToday <= 0 : false)
-
+
+ // 获取额外购买的匹配次数
+ const extraMatches = wx.getStorageSync('extra_match_count') || 0
+
+ // 总匹配次数 = 每日免费(3) + 额外购买次数
+ // 全书用户无限制
+ const totalMatchesAllowed = hasFullBook ? 999999 : FREE_MATCH_LIMIT + extraMatches
+ const matchesRemaining = hasFullBook ? 999999 : Math.max(0, totalMatchesAllowed - this.data.todayMatchCount)
+ const needPayToMatch = !hasFullBook && matchesRemaining <= 0
+
this.setData({
isLoggedIn,
hasFullBook,
- hasPurchased: true,
- todayMatchCount: matchesUsedToday,
+ hasPurchased: true, // 所有用户都可以使用匹配功能
totalMatchesAllowed,
- matchesRemaining: hasFullBook ? 999999 : (isLoggedIn && quota ? remainToday : (isLoggedIn ? 0 : FREE_MATCH_LIMIT)),
+ matchesRemaining,
needPayToMatch,
- showQuotaExhausted,
- extraMatches: purchasedRemain
+ extraMatches
})
},
@@ -243,16 +192,25 @@ Page({
// 点击匹配按钮
handleMatchClick() {
- // 检测是否登录,未登录则弹出登录弹窗
- if (!this.data.isLoggedIn) {
- this.setData({ showLoginModal: true, agreeProtocol: false })
- return
- }
-
const currentType = MATCH_TYPES.find(t => t.id === this.data.selectedType)
- // 资源对接类型需要购买章节才能使用
+ // 资源对接类型需要登录+购买章节才能使用
if (currentType && currentType.id === 'investor') {
+ // 检查是否登录
+ if (!this.data.isLoggedIn) {
+ wx.showModal({
+ title: '需要登录',
+ content: '请先登录后再使用资源对接功能',
+ confirmText: '去登录',
+ success: (res) => {
+ if (res.confirm) {
+ wx.switchTab({ url: '/pages/my/my' })
+ }
+ }
+ })
+ return
+ }
+
// 检查是否购买过章节
const hasPurchased = app.globalData.purchasedSections?.length > 0 || app.globalData.hasFullBook
if (!hasPurchased) {
@@ -262,7 +220,7 @@ Page({
confirmText: '去购买',
success: (res) => {
if (res.confirm) {
- wx.switchTab({ url: '/pages/chapters/chapters' })
+ wx.switchTab({ url: '/pages/catalog/catalog' })
}
}
})
@@ -277,8 +235,7 @@ Page({
const hasWechat = !!this.data.wechatId
if (!hasPhone && !hasWechat) {
- // 没有绑定联系方式,先显示绑定提示(仍尝试加载已有资料填充)
- this.loadStoredContact()
+ // 没有绑定联系方式,先显示绑定提示
this.setData({
showJoinModal: true,
joinType: currentType.id,
@@ -295,12 +252,12 @@ Page({
return
}
- // 创业合伙类型 - 超过匹配次数时直接弹出付费弹窗
- if (this.data.showQuotaExhausted || this.data.needPayToMatch) {
+ // 创业合伙类型 - 真正的匹配功能
+ if (this.data.needPayToMatch) {
this.setData({ showUnlockModal: true })
return
}
-
+
this.startMatch()
},
@@ -322,8 +279,6 @@ Page({
const delay = Math.random() * 2000 + 1000
setTimeout(() => {
clearInterval(timer)
- // 打开弹窗前调取用户资料填充手机号、微信号
- this.loadStoredContact()
this.setData({
isMatching: false,
showJoinModal: true,
@@ -352,7 +307,6 @@ Page({
// 开始匹配 - 只匹配数据库中的真实用户
async startMatch() {
- this.loadStoredContact()
this.setData({
isMatching: true,
matchAttempts: 0,
@@ -364,28 +318,20 @@ Page({
this.setData({ matchAttempts: this.data.matchAttempts + 1 })
}, 1000)
- // 从数据库获取真实用户匹配(后端会校验剩余次数)
+ // 从数据库获取真实用户匹配
let matchedUser = null
- let quotaExceeded = false
try {
- const ui = app.globalData.userInfo || {}
- const phone = (wx.getStorageSync('user_phone') || ui.phone || this.data.phoneNumber || '').trim()
- const wechatId = (wx.getStorageSync('user_wechat') || ui.wechat || ui.wechatId || this.data.wechatId || '').trim()
- const res = await app.request('/api/miniprogram/match/users', {
+ const res = await app.request({ url: '/api/match/users', silent: true,
method: 'POST',
data: {
matchType: this.data.selectedType,
- userId: app.globalData.userInfo?.id || '',
- phone,
- wechatId
+ userId: app.globalData.userInfo?.id || ''
}
})
-
+
if (res.success && res.data) {
matchedUser = res.data
console.log('[Match] 从数据库匹配到用户:', matchedUser.nickname)
- } else if (res.code === 'QUOTA_EXCEEDED') {
- quotaExceeded = true
}
} catch (e) {
console.log('[Match] 数据库匹配失败:', e)
@@ -395,14 +341,7 @@ Page({
const delay = Math.random() * 2000 + 2000
setTimeout(() => {
clearInterval(timer)
-
- // 次数用尽(后端校验)- 直接弹出付费弹窗
- if (quotaExceeded) {
- this.setData({ isMatching: false, showUnlockModal: true })
- this.refreshMatchCountAndStatus()
- return
- }
-
+
// 如果没有匹配到用户,提示用户
if (!matchedUser) {
this.setData({ isMatching: false })
@@ -414,17 +353,23 @@ Page({
})
return
}
-
- // 匹配成功:从服务端刷新配额(后端已写入 match_records)
+
+ // 增加今日匹配次数
+ const newCount = this.data.todayMatchCount + 1
+ const matchesRemaining = this.data.hasFullBook ? 999999 : Math.max(0, this.data.totalMatchesAllowed - newCount)
+
this.setData({
isMatching: false,
currentMatch: matchedUser,
- needPayToMatch: false
+ todayMatchCount: newCount,
+ matchesRemaining,
+ needPayToMatch: !this.data.hasFullBook && matchesRemaining <= 0
})
- this.refreshMatchCountAndStatus()
-
+ this.saveTodayMatchCount(newCount)
+
// 上报匹配行为到存客宝
this.reportMatch(matchedUser)
+
}, delay)
},
@@ -460,7 +405,7 @@ Page({
// 上报匹配行为
async reportMatch(matchedUser) {
try {
- await app.request('/api/miniprogram/ckb/match', {
+ await app.request({ url: '/api/ckb/match', silent: true,
method: 'POST',
data: {
matchType: this.data.selectedType,
@@ -490,78 +435,26 @@ Page({
this.setData({ currentMatch: null })
},
- // 添加微信好友(先校验手机号绑定)
+ // 添加微信好友
handleAddWechat() {
if (!this.data.currentMatch) return
- // 未登录需先登录
- if (!app.globalData.isLoggedIn) {
- wx.showModal({
- title: '需要登录',
- content: '请先登录后再添加好友',
- confirmText: '去登录',
- success: (res) => {
- if (res.confirm) wx.switchTab({ url: '/pages/my/my' })
- }
- })
- return
- }
-
- // 判断是否已绑定手机号(本地缓存或用户资料)
- const hasPhone = !!(
- wx.getStorageSync('user_phone') ||
- app.globalData.userInfo?.phone
- )
-
- if (!hasPhone) {
- this.setData({
- showBindPhoneModal: true,
- pendingAddWechatAfterBind: true
- })
- return
- }
-
- this.doCopyWechat()
- },
-
- // 执行复制联系方式(优先微信号,无则复制手机号)
- doCopyWechat() {
- if (!this.data.currentMatch) return
- const wechat = (this.data.currentMatch.wechat || this.data.currentMatch.wechatId || '').trim()
- const phone = (this.data.currentMatch.phone || '').trim()
- const toCopy = wechat || phone
- if (!toCopy) {
- wx.showModal({
- title: '暂无可复制',
- content: '该用户未提供微信号或手机号,请通过其他方式联系',
- showCancel: false,
- confirmText: '知道了'
- })
- return
- }
- const label = wechat ? '微信号' : '手机号'
wx.setClipboardData({
- data: toCopy,
+ data: this.data.currentMatch.wechat,
success: () => {
wx.showModal({
- title: wechat ? '微信号已复制' : '手机号已复制',
- content: wechat
- ? `${label}:${toCopy}\n\n请打开微信添加好友,备注"创业合作"即可`
- : `${label}:${toCopy}\n\n可通过微信搜索该手机号添加好友`,
+ title: '微信号已复制',
+ content: `微信号:${this.data.currentMatch.wechat}\n\n请打开微信添加好友,备注"创业合作"即可`,
showCancel: false,
confirmText: '知道了'
})
- },
- fail: () => {
- wx.showToast({ title: '复制失败,请重试', icon: 'none' })
}
})
},
- // 切换联系方式类型(同步刷新用户资料填充)
+ // 切换联系方式类型
switchContactType(e) {
const type = e.currentTarget.dataset.type
- this.loadStoredContact()
this.setData({ contactType: type, joinError: '' })
},
@@ -672,150 +565,6 @@ Page({
this.setData({ showJoinModal: false, joinError: '' })
},
- // 关闭手机绑定弹窗
- closeBindPhoneModal() {
- this.setData({
- showBindPhoneModal: false,
- pendingAddWechatAfterBind: false,
- bindPhoneInput: '',
- showMatchPhoneManualInput: false
- })
- },
-
- // 关闭登录弹窗
- closeLoginModal() {
- if (this.data.isLoggingIn) return
- this.setData({ showLoginModal: false })
- },
-
- // 切换协议勾选
- toggleAgree() {
- this.setData({ agreeProtocol: !this.data.agreeProtocol })
- },
-
- // 打开用户协议
- openUserProtocol() {
- wx.navigateTo({ url: '/pages/agreement/agreement' })
- },
-
- // 打开隐私政策
- openPrivacy() {
- wx.navigateTo({ url: '/pages/privacy/privacy' })
- },
-
- // 微信登录(匹配页)
- async handleMatchWechatLogin() {
- if (!this.data.agreeProtocol) {
- wx.showToast({ title: '请先阅读并同意用户协议和隐私政策', icon: 'none' })
- return
- }
- this.setData({ isLoggingIn: true })
- try {
- const result = await app.login()
- if (result) {
- // 登录成功后必须拉取 matchQuota,否则无法正确显示剩余次数
- await this.refreshMatchCountAndStatus()
- this.setData({ showLoginModal: false, agreeProtocol: false })
- wx.showToast({ title: '登录成功', icon: 'success' })
- } else {
- wx.showToast({ title: '登录失败,请重试', icon: 'none' })
- }
- } catch (e) {
- console.error('[Match] 微信登录错误:', e)
- wx.showToast({ title: '登录失败,请重试', icon: 'none' })
- } finally {
- this.setData({ isLoggingIn: false })
- }
- },
-
- // 一键获取手机号(匹配页加好友前绑定)
- async onMatchGetPhoneNumber(e) {
- if (e.detail.errMsg !== 'getPhoneNumber:ok') {
- wx.showToast({ title: '授权失败', icon: 'none' })
- return
- }
- const code = e.detail.code
- if (!code) {
- this.setData({ showMatchPhoneManualInput: true })
- return
- }
- try {
- wx.showLoading({ title: '获取中...', mask: true })
- const userId = app.globalData.userInfo?.id
- const res = await app.request('/api/miniprogram/phone', {
- method: 'POST',
- data: { code, userId }
- })
- wx.hideLoading()
- if (res.success && res.phoneNumber) {
- await this.saveMatchPhoneAndContinue(res.phoneNumber)
- } else {
- this.setData({ showMatchPhoneManualInput: true })
- }
- } catch (err) {
- wx.hideLoading()
- this.setData({ showMatchPhoneManualInput: true })
- }
- },
-
- // 切换为手动输入
- onMatchShowManualInput() {
- this.setData({ showMatchPhoneManualInput: true })
- },
-
- // 手动输入手机号
- onMatchPhoneInput(e) {
- this.setData({
- bindPhoneInput: e.detail.value.replace(/\D/g, '').slice(0, 11)
- })
- },
-
- // 确认手动绑定手机号
- async confirmMatchPhoneBind() {
- const { bindPhoneInput } = this.data
- if (!bindPhoneInput || bindPhoneInput.length !== 11) {
- wx.showToast({ title: '请输入正确的11位手机号', icon: 'none' })
- return
- }
- if (!/^1[3-9]\d{9}$/.test(bindPhoneInput)) {
- wx.showToast({ title: '请输入正确的手机号', icon: 'none' })
- return
- }
- await this.saveMatchPhoneAndContinue(bindPhoneInput)
- },
-
- // 保存手机号到本地+服务器,并继续加好友
- async saveMatchPhoneAndContinue(phone) {
- wx.setStorageSync('user_phone', phone)
- if (app.globalData.userInfo) {
- app.globalData.userInfo.phone = phone
- wx.setStorageSync('userInfo', app.globalData.userInfo)
- }
- this.setData({
- phoneNumber: phone,
- userPhone: phone,
- bindPhoneInput: ''
- })
- this.loadStoredContact()
- try {
- const userId = app.globalData.userInfo?.id
- if (userId) {
- await app.request('/api/miniprogram/user/profile', {
- method: 'POST',
- data: { userId, phone }
- })
- }
- } catch (e) {
- console.log('[Match] 同步手机号到服务器失败:', e)
- }
- const pending = this.data.pendingAddWechatAfterBind
- this.closeBindPhoneModal()
- if (pending) {
- wx.showToast({ title: '绑定成功', icon: 'success' })
- setTimeout(() => this.doCopyWechat(), 500)
- }
- },
-
// 显示解锁弹窗
showUnlockModal() {
this.setData({ showUnlockModal: true })
@@ -826,24 +575,7 @@ Page({
this.setData({ showUnlockModal: false })
},
- // 支付成功后立即查询订单状态并刷新(首轮 0 延迟,之后每 800ms 重试)
- async pollOrderAndRefresh(orderSn) {
- const maxAttempts = 12
- const interval = 800
- for (let i = 0; i < maxAttempts; i++) {
- try {
- const r = await app.request(`/api/miniprogram/pay?orderSn=${encodeURIComponent(orderSn)}`, { method: 'GET', silent: true })
- if (r?.data?.status === 'paid') {
- await this.refreshMatchCountAndStatus()
- return
- }
- } catch (_) {}
- if (i < maxAttempts - 1) await new Promise(r => setTimeout(r, interval))
- }
- await this.refreshMatchCountAndStatus()
- },
-
- // 购买匹配次数(与购买章节逻辑一致,写入订单)
+ // 购买匹配次数
async buyMatchCount() {
this.setData({ showUnlockModal: false })
@@ -859,17 +591,16 @@ Page({
return
}
- const matchPrice = this.data.matchPrice || 1
// 邀请码:与章节支付一致,写入订单便于分销归属与对账
const referralCode = wx.getStorageSync('referral_code') || ''
- // 调用支付接口购买匹配次数(productType: match,订单类型:购买匹配次数)
+ // 调用支付接口购买匹配次数
const res = await app.request('/api/miniprogram/pay', {
method: 'POST',
data: {
openId,
productType: 'match',
productId: 'match_1',
- amount: matchPrice,
+ amount: 1,
description: '匹配次数x1',
userId: app.globalData.userInfo?.id || '',
referralCode: referralCode || undefined
@@ -877,7 +608,6 @@ Page({
})
if (res.success && res.data?.payParams) {
- const orderSn = res.data.orderSn
// 调用微信支付
await new Promise((resolve, reject) => {
wx.requestPayment({
@@ -886,9 +616,13 @@ Page({
fail: reject
})
})
+
+ // 支付成功,增加匹配次数
+ const extraMatches = (wx.getStorageSync('extra_match_count') || 0) + 1
+ wx.setStorageSync('extra_match_count', extraMatches)
+
wx.showToast({ title: '购买成功', icon: 'success' })
- // 轮询订单状态,确认已支付后再刷新(不依赖 PayNotify 回调时机)
- this.pollOrderAndRefresh(orderSn)
+ this.initUserStatus()
} else {
throw new Error(res.error || '创建订单失败')
}
@@ -896,13 +630,14 @@ Page({
if (e.errMsg && e.errMsg.includes('cancel')) {
wx.showToast({ title: '已取消', icon: 'none' })
} else {
- // 测试模式(无支付环境时本地模拟)
+ // 测试模式
wx.showModal({
title: '支付服务暂不可用',
content: '是否使用测试模式购买?',
success: (res) => {
if (res.confirm) {
- app.globalData.matchCount = (app.globalData.matchCount ?? 0) + 1
+ const extraMatches = (wx.getStorageSync('extra_match_count') || 0) + 1
+ wx.setStorageSync('extra_match_count', extraMatches)
wx.showToast({ title: '测试购买成功', icon: 'success' })
this.initUserStatus()
}
@@ -924,13 +659,5 @@ Page({
},
// 阻止事件冒泡
- preventBubble() {},
-
- onShareAppMessage() {
- const ref = app.getMyReferralCode()
- return {
- title: 'Soul创业派对 - 找伙伴',
- path: ref ? `/pages/match/match?ref=${ref}` : '/pages/match/match'
- }
- }
+ preventBubble() {}
})
diff --git a/miniprogram/pages/member-detail/member-detail.js b/miniprogram/pages/member-detail/member-detail.js
index 8de3455a..04533440 100644
--- a/miniprogram/pages/member-detail/member-detail.js
+++ b/miniprogram/pages/member-detail/member-detail.js
@@ -1,11 +1,14 @@
const app = getApp()
+const MOCK_ENRICHMENT = [
+ { mbti: 'ENTJ', region: '深圳', skills: '电商运营、供应链管理、团队搭建', contactRaw: '13800138001', bestMonth: '做跨境电商独立站,单月GMV突破200万,净利润35万', achievement: '从0到1搭建了30人的电商团队,年营收破3000万', turningPoint: '2019年从传统外贸转型跨境电商,放弃稳定薪资All in创业', canHelp: '电商选品、供应链对接、团队管理SOP', needHelp: '寻找品牌合作方和内容营销人才', project: '跨境电商独立站+亚马逊多店铺运营,主营家居类目' },
+ { mbti: 'INFP', region: '杭州', skills: '短视频制作、IP打造、私域运营', contactRaw: '13900139002', bestMonth: '旅游账号30天涨粉10万,带货佣金收入12万', achievement: '帮助3个素人打造个人IP,每个月稳定变现5万+', turningPoint: '辞去互联网大厂工作开始做自媒体,第三个月就超过原薪资', canHelp: '短视频脚本、账号冷启动、私域转化设计', needHelp: '寻找供应链资源和线下活动合作', project: '旅游+生活方式自媒体矩阵,全网粉丝50万+' },
+ { mbti: 'INTP', region: '厦门', skills: 'AI开发、小程序开发、系统架构', contactRaw: '13700137003', bestMonth: 'AI客服系统外包项目,单月收入18万', achievement: '独立开发的SaaS产品获得天使轮200万融资', turningPoint: '从程序员转型技术创业者,学会用技术解决商业问题', canHelp: '技术方案设计、AI应用落地、小程序开发', needHelp: '需要商业化运营和市场推广合伙人', project: 'AI+私域运营工具SaaS平台' },
+ { mbti: 'ESTP', region: '成都', skills: '资源对接、商务BD、活动策划', contactRaw: '13600136004', bestMonth: '撮合景区合作,居间费收入25万', achievement: '组建覆盖全国50+城市创业者社群,活跃成员3000+', turningPoint: '在Soul派对房认识第一个合伙人,打开了社交创业的大门', canHelp: '各行业资源对接、活动策划、社群引荐', needHelp: '寻找技术合伙人和内容创作者', project: '创业者资源对接平台+线下创业者沙龙' }
+]
+
Page({
- data: {
- statusBarHeight: 44,
- member: null,
- loading: true
- },
+ data: { statusBarHeight: 44, member: null, loading: true },
onLoad(options) {
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
@@ -14,24 +17,75 @@ Page({
async loadMember(id) {
try {
- const res = await app.request(`/api/vip/members?id=${id}`)
- if (res?.success) {
- this.setData({ member: res.data, loading: false })
- } else {
- this.setData({ loading: false })
- wx.showToast({ title: '会员不存在', icon: 'none' })
+ const res = await app.request({ url: `/api/vip/members?id=${id}`, silent: true })
+ if (res?.success && res.data) {
+ const d = Array.isArray(res.data) ? res.data[0] : res.data
+ if (d) { this.setData({ member: this.enrichAndFormat(d), loading: false }); return }
}
- } catch (e) {
- this.setData({ loading: false })
- wx.showToast({ title: '加载失败', icon: 'none' })
+ } catch (e) {}
+
+ try {
+ const dbRes = await app.request({ url: `/api/miniprogram/users?id=${id}`, silent: true })
+ if (dbRes?.success && dbRes.data) {
+ const u = Array.isArray(dbRes.data) ? dbRes.data[0] : dbRes.data
+ if (u) {
+ this.setData({ member: this.enrichAndFormat({
+ id: u.id, name: u.vip_name || u.nickname || '创业者',
+ avatar: u.vip_avatar || u.avatar || '', isVip: u.is_vip === 1,
+ contactRaw: u.vip_contact || u.phone || '', project: u.vip_project || '',
+ bio: u.vip_bio || '', mbti: '', region: '', skills: '',
+ }), loading: false })
+ return
+ }
+ }
+ } catch (e) {}
+ this.setData({ loading: false })
+ },
+
+ enrichAndFormat(raw) {
+ const hash = (raw.id || '').split('').reduce((a, c) => a + c.charCodeAt(0), 0)
+ const mock = MOCK_ENRICHMENT[hash % MOCK_ENRICHMENT.length]
+
+ const merged = {
+ id: raw.id,
+ name: raw.name || raw.vip_name || raw.nickname || '创业者',
+ avatar: raw.avatar || raw.vip_avatar || '',
+ isVip: raw.isVip || raw.is_vip === 1,
+ mbti: raw.mbti || mock.mbti,
+ region: raw.region || mock.region,
+ skills: raw.skills || mock.skills,
+ contactRaw: raw.contactRaw || raw.vip_contact || mock.contactRaw,
+ bestMonth: raw.bestMonth || mock.bestMonth,
+ achievement: raw.achievement || mock.achievement,
+ turningPoint: raw.turningPoint || mock.turningPoint,
+ canHelp: raw.canHelp || mock.canHelp,
+ needHelp: raw.needHelp || mock.needHelp,
+ project: raw.project || raw.vip_project || mock.project
}
+
+ const contact = merged.contactRaw || ''
+ const isMatched = (app.globalData.matchedUsers || []).includes(merged.id)
+ merged.contactDisplay = contact ? (contact.slice(0, 3) + '****' + (contact.length > 7 ? contact.slice(-2) : '')) : ''
+ merged.contactUnlocked = isMatched
+ merged.contactFull = contact
+ return merged
+ },
+
+ unlockContact() {
+ wx.showModal({
+ title: '解锁完整联系方式', content: '成为VIP会员并完成匹配后,即可查看完整联系方式',
+ confirmText: '去匹配', cancelText: '知道了',
+ success: (res) => { if (res.confirm) wx.switchTab({ url: '/pages/match/match' }) }
+ })
},
copyContact() {
- const contact = this.data.member?.contact
- if (!contact) { wx.showToast({ title: '暂无联系方式', icon: 'none' }); return }
- wx.setClipboardData({ data: contact, success: () => wx.showToast({ title: '已复制', icon: 'success' }) })
+ const c = this.data.member?.contactFull
+ if (!c) return
+ wx.setClipboardData({ data: c, success: () => wx.showToast({ title: '已复制', icon: 'success' }) })
},
+ goToMatch() { wx.switchTab({ url: '/pages/match/match' }) },
+ goToVip() { wx.navigateTo({ url: '/pages/vip/vip' }) },
goBack() { wx.navigateBack() }
})
diff --git a/miniprogram/pages/member-detail/member-detail.wxml b/miniprogram/pages/member-detail/member-detail.wxml
index 4fa72384..ca8b155f 100644
--- a/miniprogram/pages/member-detail/member-detail.wxml
+++ b/miniprogram/pages/member-detail/member-detail.wxml
@@ -1,38 +1,101 @@
-
- ‹
- 创业伙伴
-
+ ‹
+ 超级个体
+
-
-
-
-
- {{member.name[0] || '创'}}
- VIP
-
- {{member.name}}
- {{member.project}}
-
-
-
- 简介
- {{member.bio}}
-
-
-
- 联系方式
-
- {{member.contact}}
- 复制
+
+
+
+
+
+
+
+
+ {{member.name[0] || '创'}}
+ VIP
+
+ {{member.name}}
+
+ {{member.mbti}}
+ 📍{{member.region}}
+
+
+
+
+ 👤基本信息
+
+ 我擅长
+ {{member.skills}}
+
+
+ 联系方式
+
+ {{member.contactDisplay}}
+
+ 🔒匹配解锁
+
+ 复制
+
+
+
+
+
+
+ 💡个人故事
+
+ 🏆 最赚钱的一个月做的是什么
+ {{member.bestMonth}}
+
+
+
+ ⭐ 最有成就感的一件事
+ {{member.achievement}}
+
+
+
+ 🔄 人生的转折点
+ {{member.turningPoint}}
+
+
+
+
+
+ 🤝互助需求
+
+ 我能帮到你
+ {{member.canHelp}}
+
+
+ 我需要帮助
+ {{member.needHelp}}
+
+
+
+
+
+ 🚀项目介绍
+ {{member.project}}
+
+
+
+
+ 开始匹配 · 解锁联系方式
+ 成为超级个体 →
+
+
+
+
+
+
+
+ 加载中...
-
-
- 加载中...
+
+ 👤暂无该超级个体信息
diff --git a/miniprogram/pages/member-detail/member-detail.wxss b/miniprogram/pages/member-detail/member-detail.wxss
index ab1b9552..7afe12e4 100644
--- a/miniprogram/pages/member-detail/member-detail.wxss
+++ b/miniprogram/pages/member-detail/member-detail.wxss
@@ -1,24 +1,75 @@
-.page { background: #000; min-height: 100vh; color: #fff; }
-.nav-bar { position: fixed; top: 0; left: 0; right: 0; z-index: 100; display: flex; align-items: center; justify-content: space-between; height: 44px; padding: 0 24rpx; background: rgba(0,0,0,0.9); }
-.nav-back { width: 60rpx; height: 60rpx; display: flex; align-items: center; justify-content: center; }
-.back-icon { font-size: 44rpx; color: #fff; }
-.nav-title { font-size: 34rpx; font-weight: 600; color: #fff; }
-.nav-placeholder-r { width: 60rpx; }
+.page { background: #050a10; min-height: 100vh; color: #fff; }
-.detail-content { padding: 24rpx; }
-.detail-hero { display: flex; flex-direction: column; align-items: center; padding: 48rpx 0 32rpx; }
-.detail-avatar-wrap { position: relative; margin-bottom: 20rpx; }
-.detail-avatar { width: 160rpx; height: 160rpx; border-radius: 50%; border: 4rpx solid #FFD700; }
-.detail-avatar-ph { width: 160rpx; height: 160rpx; border-radius: 50%; background: #1c1c1e; border: 4rpx solid #FFD700; display: flex; align-items: center; justify-content: center; font-size: 60rpx; color: #FFD700; }
-.detail-vip-badge { position: absolute; bottom: 4rpx; right: 4rpx; background: linear-gradient(135deg, #FFD700, #FFA500); color: #000; font-size: 20rpx; font-weight: bold; padding: 4rpx 12rpx; border-radius: 14rpx; }
-.detail-name { font-size: 40rpx; font-weight: bold; color: #fff; }
-.detail-project { font-size: 26rpx; color: rgba(255,255,255,0.5); margin-top: 8rpx; }
+/* 导航 */
+.nav-bar { position: fixed; top: 0; left: 0; right: 0; z-index: 999; display: flex; align-items: center; justify-content: space-between; padding: 0 24rpx; height: 44px; background: rgba(5,10,16,.92); backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px); border-bottom: 1rpx solid rgba(56,189,172,.08); }
+.back-arrow { font-size: 48rpx; color: #38bdac; font-weight: 300; }
+.nav-title { font-size: 32rpx; font-weight: 600; letter-spacing: 2rpx; }
+.nav-ph { width: 48rpx; }
+.nav-back { width: 48rpx; height: 48rpx; display: flex; align-items: center; justify-content: center; }
-.detail-card { background: #1c1c1e; border-radius: 20rpx; padding: 28rpx; margin-top: 24rpx; }
-.detail-card-title { font-size: 24rpx; color: rgba(255,255,255,0.5); display: block; margin-bottom: 12rpx; }
-.detail-card-text { font-size: 30rpx; color: rgba(255,255,255,0.9); }
-.detail-contact-row { display: flex; align-items: center; justify-content: space-between; }
-.copy-btn { background: #00CED1; color: #000; font-size: 24rpx; font-weight: 600; padding: 8rpx 24rpx; border-radius: 20rpx; }
+.scroll-wrap { height: calc(100vh - 88px); }
-.loading-state { display: flex; justify-content: center; padding: 100rpx 0; }
-.loading-text { color: rgba(255,255,255,0.4); font-size: 28rpx; }
+/* ===== 顶部名片 ===== */
+.card-hero { position: relative; margin: 24rpx 24rpx 0; padding: 56rpx 0 40rpx; border-radius: 28rpx; overflow: hidden; background: linear-gradient(160deg, #0d1f2d 0%, #0a1620 40%, #101828 100%); border: 1rpx solid rgba(56,189,172,.15); }
+.hero-deco { position: absolute; top: -60rpx; right: -60rpx; width: 240rpx; height: 240rpx; border-radius: 50%; background: radial-gradient(circle, rgba(56,189,172,.12) 0%, transparent 70%); }
+.hero-deco2 { position: absolute; bottom: -40rpx; left: -40rpx; width: 180rpx; height: 180rpx; border-radius: 50%; background: radial-gradient(circle, rgba(245,166,35,.06) 0%, transparent 70%); }
+.hero-body { position: relative; display: flex; flex-direction: column; align-items: center; z-index: 1; }
+
+.avatar-ring { position: relative; width: 168rpx; height: 168rpx; border-radius: 50%; padding: 6rpx; background: linear-gradient(135deg, #1a3a3a, #0d1f2d); margin-bottom: 20rpx; }
+.avatar-ring.vip-ring { background: linear-gradient(135deg, #f5a623, #38bdac, #f5a623); background-size: 300% 300%; animation: vipGlow 4s ease infinite; }
+@keyframes vipGlow { 0%,100%{background-position:0% 50%} 50%{background-position:100% 50%} }
+.avatar-img { width: 100%; height: 100%; border-radius: 50%; border: 4rpx solid #0a1620; }
+.avatar-ph { width: 100%; height: 100%; border-radius: 50%; border: 4rpx solid #0a1620; background: #152030; display: flex; align-items: center; justify-content: center; font-size: 52rpx; color: #38bdac; font-weight: 700; }
+.vip-tag { position: absolute; bottom: 4rpx; right: 4rpx; background: linear-gradient(135deg, #f5a623, #e8920d); color: #000; font-size: 18rpx; font-weight: 800; padding: 4rpx 14rpx; border-radius: 16rpx; letter-spacing: 1rpx; box-shadow: 0 4rpx 12rpx rgba(245,166,35,.4); }
+
+.hero-name { font-size: 38rpx; font-weight: 700; letter-spacing: 2rpx; margin-bottom: 12rpx; text-shadow: 0 2rpx 8rpx rgba(0,0,0,.5); }
+.hero-tags { display: flex; gap: 12rpx; flex-wrap: wrap; justify-content: center; }
+.tag-item { font-size: 22rpx; padding: 6rpx 18rpx; border-radius: 24rpx; }
+.tag-mbti { color: #38bdac; background: rgba(56,189,172,.12); border: 1rpx solid rgba(56,189,172,.2); }
+.tag-region { color: #ccc; background: rgba(255,255,255,.06); border: 1rpx solid rgba(255,255,255,.08); }
+
+/* ===== 通用卡片 ===== */
+.card { margin: 20rpx 24rpx; padding: 28rpx 28rpx; border-radius: 24rpx; background: rgba(15,25,40,.8); border: 1rpx solid rgba(255,255,255,.06); backdrop-filter: blur(10px); }
+.card-head { display: flex; align-items: center; gap: 10rpx; margin-bottom: 24rpx; }
+.card-icon { font-size: 28rpx; }
+.card-label { font-size: 28rpx; font-weight: 600; color: #fff; letter-spacing: 1rpx; }
+
+.field { margin-bottom: 20rpx; }
+.field:last-child { margin-bottom: 0; }
+.f-key { display: block; font-size: 22rpx; color: #6b7b8e; margin-bottom: 8rpx; text-transform: uppercase; letter-spacing: 2rpx; }
+.f-val { font-size: 28rpx; color: #e0e8f0; line-height: 1.7; }
+.f-contact { display: flex; align-items: center; gap: 16rpx; }
+.masked { letter-spacing: 3rpx; font-family: 'Courier New', monospace; }
+.lock-chip { display: flex; align-items: center; gap: 6rpx; font-size: 20rpx; color: #f5a623; background: rgba(245,166,35,.1); border: 1rpx solid rgba(245,166,35,.2); padding: 6rpx 16rpx; border-radius: 20rpx; white-space: nowrap; }
+.lock-icon { font-size: 18rpx; }
+.copy-chip { font-size: 20rpx; color: #38bdac; background: rgba(56,189,172,.1); border: 1rpx solid rgba(56,189,172,.2); padding: 6rpx 16rpx; border-radius: 20rpx; white-space: nowrap; }
+
+/* ===== 故事 ===== */
+.story { padding: 4rpx 0; }
+.story-q { display: block; font-size: 24rpx; color: #7a8fa3; margin-bottom: 10rpx; }
+.story-a { display: block; font-size: 28rpx; color: #e0e8f0; line-height: 1.8; }
+.divider { height: 1rpx; background: rgba(255,255,255,.04); margin: 20rpx 0; }
+
+/* ===== 互助 ===== */
+.help-box { padding: 20rpx; border-radius: 16rpx; margin-bottom: 16rpx; }
+.help-box:last-child { margin-bottom: 0; }
+.help-give { background: rgba(56,189,172,.06); border: 1rpx solid rgba(56,189,172,.1); }
+.help-need { background: rgba(245,166,35,.06); border: 1rpx solid rgba(245,166,35,.1); }
+.help-tag { display: inline-block; font-size: 22rpx; font-weight: 600; color: #38bdac; margin-bottom: 10rpx; padding: 4rpx 14rpx; border-radius: 12rpx; background: rgba(56,189,172,.12); }
+.help-tag.need { color: #f5a623; background: rgba(245,166,35,.12); }
+.help-txt { display: block; font-size: 26rpx; color: #ccd4de; line-height: 1.7; }
+
+/* ===== 项目 ===== */
+.proj-txt { font-size: 26rpx; color: #ccd4de; line-height: 1.8; }
+
+/* ===== 底部按钮 ===== */
+.bottom-actions { padding: 32rpx 24rpx 0; display: flex; flex-direction: column; gap: 16rpx; }
+.btn-match { text-align: center; padding: 26rpx 0; border-radius: 48rpx; font-size: 30rpx; font-weight: 700; letter-spacing: 2rpx; background: linear-gradient(135deg, #38bdac 0%, #2ca898 50%, #249e8c 100%); color: #fff; box-shadow: 0 8rpx 24rpx rgba(56,189,172,.3); }
+.btn-vip { text-align: center; padding: 22rpx 0; border-radius: 48rpx; font-size: 26rpx; color: #f5a623; background: transparent; border: 1rpx solid rgba(245,166,35,.3); }
+
+/* ===== 状态 ===== */
+.state-wrap { display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 60vh; gap: 16rpx; }
+.state-txt { font-size: 28rpx; color: #4a5a6e; }
+.state-emoji { font-size: 80rpx; }
+.loading-dot { width: 48rpx; height: 48rpx; border-radius: 50%; border: 4rpx solid rgba(56,189,172,.2); border-top-color: #38bdac; animation: spin 1s linear infinite; }
+@keyframes spin { to { transform: rotate(360deg); } }
diff --git a/miniprogram/pages/my/my.js b/miniprogram/pages/my/my.js
index 117e954c..becc5a0f 100644
--- a/miniprogram/pages/my/my.js
+++ b/miniprogram/pages/my/my.js
@@ -29,26 +29,17 @@ Page({
totalReadTime: 0,
matchHistory: 0,
- // Tab切换
- activeTab: 'overview', // overview | footprint
-
// 最近阅读
recentChapters: [],
// 功能配置
- matchEnabled: false, // 找伙伴功能开关
+ matchEnabled: false,
- // 菜单列表
- menuList: [
- { id: 'scan', title: '扫一扫', icon: '📷', iconBg: 'gray' },
- { id: 'orders', title: '我的订单', icon: '📦', count: 0 },
- { id: 'referral', title: '推广中心', icon: '🎁', iconBg: 'gold', badge: '90%佣金' },
- { id: 'withdrawRecords', title: '提现记录', icon: '📋', iconBg: 'gray' },
- { id: 'about', title: '关于作者', icon: 'ℹ️', iconBg: 'brand' },
- { id: 'settings', title: '设置', icon: '⚙️', iconBg: 'gray' }
- ],
-
- // 待确认收款(用户确认模式)
+ // VIP状态
+ isVip: false,
+ vipExpireDate: '',
+
+ // 待确认收款
pendingConfirmList: [],
withdrawMchId: '',
withdrawAppId: '',
@@ -65,11 +56,7 @@ Page({
// 修改昵称弹窗
showNicknameModal: false,
- editingNickname: '',
-
- // 扫一扫结果弹窗
- showScanResultModal: false,
- scanResult: ''
+ editingNickname: ''
},
onLoad() {
@@ -95,48 +82,17 @@ Page({
this.initUserStatus()
},
- // 加载功能配置
async loadFeatureConfig() {
try {
- const res = await app.request({
- url: '/api/miniprogram/config',
- method: 'GET'
- })
-
- if (res && res.features) {
- this.setData({
- matchEnabled: res.features.matchEnabled === true
- })
- }
+ const res = await app.request('/api/miniprogram/config')
+ const features = (res && res.features) || (res && res.data && res.data.features) || {}
+ this.setData({ matchEnabled: features.matchEnabled === true })
} catch (error) {
console.log('加载功能配置失败:', error)
- // 默认关闭找伙伴功能
this.setData({ matchEnabled: false })
}
},
- // 登录后刷新购买状态(与 match/read 一致,避免其他页面用旧数据)
- async refreshPurchaseStatus() {
- const userId = app.globalData.userInfo?.id
- if (!userId) return
- try {
- const res = await app.request(`/api/miniprogram/user/purchase-status?userId=${encodeURIComponent(userId)}`)
- if (res.success && res.data) {
- app.globalData.hasFullBook = res.data.hasFullBook || false
- app.globalData.purchasedSections = res.data.purchasedSections || []
- app.globalData.sectionMidMap = res.data.sectionMidMap || {}
- app.globalData.matchCount = res.data.matchCount ?? 0
- app.globalData.matchQuota = res.data.matchQuota || null
- const userInfo = app.globalData.userInfo || {}
- userInfo.hasFullBook = res.data.hasFullBook
- userInfo.purchasedSections = res.data.purchasedSections
- wx.setStorageSync('userInfo', userInfo)
- }
- } catch (e) {
- console.log('[My] 刷新购买状态失败:', e)
- }
- },
-
// 初始化用户状态
initUserStatus() {
const { isLoggedIn, userInfo } = app.globalData
@@ -144,8 +100,7 @@ Page({
if (isLoggedIn && userInfo) {
const readIds = app.globalData.readSectionIds || []
const recentList = readIds.slice(-5).reverse().map(id => ({
- id,
- mid: app.getSectionMid(id),
+ id: id,
title: `章节 ${id}`
}))
@@ -169,6 +124,7 @@ Page({
})
this.loadMyEarnings()
this.loadPendingConfirm()
+ this.loadVipStatus()
} else {
this.setData({
isLoggedIn: false,
@@ -189,7 +145,7 @@ Page({
const userInfo = app.globalData.userInfo
if (!app.globalData.isLoggedIn || !userInfo || !userInfo.id) return
try {
- const res = await app.request('/api/miniprogram/withdraw/pending-confirm?userId=' + userInfo.id)
+ const res = await app.request({ url: '/api/miniprogram/withdraw/pending-confirm?userId=' + userInfo.id, silent: true })
if (res && res.success && res.data) {
const list = (res.data.list || []).map(item => ({
id: item.id,
@@ -291,7 +247,7 @@ Page({
}
const formatMoney = (num) => (typeof num === 'number' ? num.toFixed(2) : '0.00')
try {
- const res = await app.request('/api/miniprogram/earnings?userId=' + userInfo.id)
+ const res = await app.request({ url: '/api/miniprogram/earnings?userId=' + userInfo.id, silent: true })
if (!res || !res.success || !res.data) {
this.setData({ earningsLoading: false, earnings: '0.00', pendingEarnings: '0.00' })
return
@@ -566,7 +522,6 @@ Page({
try {
const result = await app.login()
if (result) {
- await this.refreshPurchaseStatus()
this.initUserStatus()
this.setData({ showLoginModal: false, agreeProtocol: false })
wx.showToast({ title: '登录成功', icon: 'success' })
@@ -595,7 +550,6 @@ Page({
try {
const result = await app.loginWithPhone(e.detail.code)
if (result) {
- await this.refreshPurchaseStatus()
this.initUserStatus()
this.setData({ showLoginModal: false })
wx.showToast({ title: '登录成功', icon: 'success' })
@@ -610,29 +564,15 @@ Page({
}
},
- // 跳转编辑资料页
- goEditProfile() {
- if (!this.data.isLoggedIn) {
- this.showLogin()
- return
- }
- wx.navigateTo({ url: '/pages/profile-edit/profile-edit' })
- },
-
// 点击菜单
handleMenuTap(e) {
const id = e.currentTarget.dataset.id
-
- if (id === 'scan') {
- this.doScanCode()
- return
- }
-
+
if (!this.data.isLoggedIn && id !== 'about') {
this.showLogin()
return
}
-
+
const routes = {
orders: '/pages/purchases/purchases',
referral: '/pages/referral/referral',
@@ -640,52 +580,16 @@ Page({
about: '/pages/about/about',
settings: '/pages/settings/settings'
}
-
+
if (routes[id]) {
wx.navigateTo({ url: routes[id] })
}
},
- // 扫一扫:调起扫码,展示解析值
- doScanCode() {
- wx.scanCode({
- onlyFromCamera: false,
- scanType: ['qrCode', 'barCode'],
- success: (res) => {
- const result = res.result || ''
- this.setData({
- showScanResultModal: true,
- scanResult: result
- })
- },
- fail: (err) => {
- if (err.errMsg && !err.errMsg.includes('cancel')) {
- wx.showToast({ title: '扫码失败', icon: 'none' })
- }
- }
- })
- },
-
- // 关闭扫码结果弹窗
- closeScanResultModal() {
- this.setData({ showScanResultModal: false, scanResult: '' })
- },
-
- // 复制扫码结果
- copyScanResult() {
- const text = this.data.scanResult || ''
- if (!text) return
- wx.setClipboardData({
- data: text,
- success: () => wx.showToast({ title: '已复制', icon: 'success' })
- })
- },
-
+ // 跳转到阅读页
goToRead(e) {
const id = e.currentTarget.dataset.id
- const mid = e.currentTarget.dataset.mid
- const q = mid ? `mid=${mid}` : `id=${id}`
- wx.navigateTo({ url: `/pages/read/read?${q}` })
+ wx.navigateTo({ url: `/pages/read/read?id=${id}` })
},
// 跳转到目录
@@ -732,14 +636,80 @@ Page({
})
},
- // 阻止冒泡
- stopPropagation() {},
+ // VIP状态查询
+ async loadVipStatus() {
+ const userId = app.globalData.userInfo?.id
+ if (!userId) return
+ try {
+ const res = await app.request({ url: `/api/vip/status?userId=${userId}`, silent: true })
+ if (res?.success) {
+ this.setData({ isVip: res.data?.isVip, vipExpireDate: res.data?.expireDate || '' })
+ }
+ } catch (e) { console.log('[My] VIP查询失败', e) }
+ },
- onShareAppMessage() {
- const ref = app.getMyReferralCode()
- return {
- title: 'Soul创业派对 - 我的',
- path: ref ? `/pages/my/my?ref=${ref}` : '/pages/my/my'
+ // 头像点击:已登录弹出选项(改头像/进VIP)
+ onAvatarTap() {
+ if (!this.data.isLoggedIn) { this.showLogin(); return }
+ wx.showActionSheet({
+ itemList: ['获取微信头像', '开通/管理VIP'],
+ success: (res) => {
+ if (res.tapIndex === 0) this.chooseAvatarFallback()
+ if (res.tapIndex === 1) this.goToVip()
+ }
+ })
+ },
+
+ chooseAvatarFallback() {
+ wx.chooseMedia({
+ count: 1, mediaType: ['image'], sourceType: ['album', 'camera'],
+ success: async (res) => {
+ const tempPath = res.tempFiles[0].tempFilePath
+ const userInfo = this.data.userInfo
+ userInfo.avatar = tempPath
+ this.setData({ userInfo })
+ app.globalData.userInfo = userInfo
+ wx.setStorageSync('userInfo', userInfo)
+ try {
+ await app.request('/api/user/update', { method: 'POST', data: { userId: userInfo.id, avatar: tempPath } })
+ } catch (e) { console.log('头像同步失败', e) }
+ wx.showToast({ title: '头像已更新', icon: 'success' })
+ }
+ })
+ },
+
+ goToVip() {
+ if (!this.data.isLoggedIn) { this.showLogin(); return }
+ wx.navigateTo({ url: '/pages/vip/vip' })
+ },
+
+ async handleWithdraw() {
+ if (!this.data.isLoggedIn) { this.showLogin(); return }
+ const amount = parseFloat(this.data.pendingEarnings)
+ if (isNaN(amount) || amount <= 0) {
+ wx.showToast({ title: '暂无可提现金额', icon: 'none' })
+ return
}
- }
+ wx.showModal({
+ title: '申请提现',
+ content: `确认提现 ¥${amount.toFixed(2)} ?`,
+ success: async (res) => {
+ if (!res.confirm) return
+ wx.showLoading({ title: '提交中...', mask: true })
+ try {
+ const userId = app.globalData.userInfo?.id
+ await app.request({ url: '/api/withdraw', method: 'POST', data: { userId, amount } })
+ wx.hideLoading()
+ wx.showToast({ title: '提现申请已提交', icon: 'success' })
+ this.loadMyEarnings()
+ } catch (e) {
+ wx.hideLoading()
+ wx.showToast({ title: e.message || '提现失败', icon: 'none' })
+ }
+ }
+ })
+ },
+
+ // 阻止冒泡
+ stopPropagation() {}
})
diff --git a/miniprogram/pages/my/my.wxml b/miniprogram/pages/my/my.wxml
index 5d243308..ac0d2042 100644
--- a/miniprogram/pages/my/my.wxml
+++ b/miniprogram/pages/my/my.wxml
@@ -47,37 +47,33 @@
{{userInfo.nickname || '点击设置昵称'}}
+
+ 会员
+ 匹配
+ 排行
+
+
+ ⭐成为会员
+
- ID: {{userIdShort}}
+ {{userWechat ? '微信: ' + userWechat : 'ID: ' + userIdShort}}
-
-
-
-
- ⭐
- 创业伙伴
-
-
-
-
-
-
- ✏️
- 编辑资料
@@ -89,85 +85,44 @@
{{referralCount}}
推荐好友
-
+
{{pendingEarnings > 0 ? '¥' + pendingEarnings : '--'}}
- 待领收益
+ 我的收益
-
-
-
-
-
-
- ¥{{item.amount}}
- {{item.createdAt}}
-
- 确认收款
-
-
-
-
-
-
- 概览
-
-
-
-
-
-
-
+
+
+
-
-
👁️
阅读统计
-
-
+
📖
{{readCount}}
@@ -199,7 +154,6 @@
wx:key="id"
bindtap="goToRead"
data-id="{{item.id}}"
- data-mid="{{item.mid}}"
>
{{index + 1}}
@@ -215,16 +169,16 @@
-
-
-
- 👥
- 匹配记录
-
-
- 👥
- 暂无匹配记录
- 去匹配 →
+
+
@@ -287,21 +241,6 @@
-
-
-
- ✕
-
- {{scanResult}}
-
- 复制
- 关闭
-
-
-
-
diff --git a/miniprogram/pages/my/my.wxss b/miniprogram/pages/my/my.wxss
index 79d5b2bf..bbdb848a 100644
--- a/miniprogram/pages/my/my.wxss
+++ b/miniprogram/pages/my/my.wxss
@@ -95,39 +95,6 @@
white-space: nowrap;
}
-/* 编辑资料入口 - 深色圆角按钮,与卡片统一 */
-.edit-profile-entry {
- display: flex;
- align-items: center;
- justify-content: center;
- flex-wrap: nowrap;
- width: 100%;
- min-height: 88rpx;
- padding: 24rpx 32rpx;
- margin-bottom: 24rpx;
- box-sizing: border-box;
- background: rgba(255, 255, 255, 0.06);
- border: 2rpx solid rgba(0, 206, 209, 0.3);
- border-radius: 20rpx;
-}
-
-.edit-profile-entry:active {
- background: rgba(0, 206, 209, 0.12);
-}
-
-.edit-profile-icon {
- font-size: 36rpx;
- margin-right: 12rpx;
- flex-shrink: 0;
-}
-
-.edit-profile-text {
- font-size: 28rpx;
- font-weight: 500;
- color: #00CED1;
- white-space: nowrap;
-}
-
.card-gradient {
background: linear-gradient(135deg, #1c1c1e 0%, #2c2c2e 100%);
border-radius: 32rpx;
@@ -1270,27 +1237,137 @@
color: #fff; font-size: 26rpx; font-weight: 500; border-radius: 20rpx;
}
-/* ===== 扫一扫结果弹窗 ===== */
-.scan-result-modal .modal-close { top: 24rpx; right: 24rpx; }
-.scan-result-header { margin-bottom: 24rpx; }
-.scan-result-title { font-size: 32rpx; font-weight: 600; color: #fff; }
-.scan-result-body {
- max-height: 320rpx;
- padding: 24rpx;
- background: rgba(255,255,255,0.06);
- border-radius: 16rpx;
- margin-bottom: 24rpx;
- word-break: break-all;
-}
-.scan-result-text { font-size: 26rpx; color: rgba(255,255,255,0.9); line-height: 1.5; }
-.scan-result-actions { display: flex; gap: 24rpx; }
-.scan-result-btn {
- flex: 1;
- padding: 24rpx;
- text-align: center;
- font-size: 28rpx;
- color: rgba(255,255,255,0.9);
- background: rgba(255,255,255,0.1);
+/* ===== 收益面板(内嵌) ===== */
+.earnings-inline {
+ margin: 0 32rpx 24rpx;
+ padding: 24rpx 28rpx;
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
border-radius: 24rpx;
+ border: 2rpx solid rgba(0, 206, 209, 0.15);
}
-.scan-result-btn.primary { background: #00CED1; color: #000; }
+.earnings-inline-row {
+ display: flex;
+ align-items: center;
+ gap: 16rpx;
+}
+.earnings-inline-item {
+ display: flex;
+ flex-direction: column;
+ gap: 4rpx;
+ flex: 1;
+}
+.earnings-inline-label {
+ font-size: 22rpx;
+ color: rgba(255,255,255,0.5);
+}
+.earnings-inline-val {
+ font-size: 36rpx;
+ font-weight: 700;
+ color: #fff;
+}
+.earnings-inline-divider {
+ width: 2rpx;
+ height: 48rpx;
+ background: rgba(255,255,255,0.1);
+ flex-shrink: 0;
+}
+.earnings-inline-btn {
+ padding: 12rpx 28rpx;
+ background: linear-gradient(90deg, #FFD700 0%, #FFA500 100%);
+ border-radius: 20rpx;
+ font-size: 26rpx;
+ font-weight: 600;
+ color: #000;
+ flex-shrink: 0;
+}
+.earnings-inline-refresh {
+ width: 48rpx;
+ height: 48rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 28rpx;
+ color: #00CED1;
+ flex-shrink: 0;
+}
+.pending-inline {
+ margin-top: 16rpx;
+ padding-top: 16rpx;
+ border-top: 1rpx solid rgba(255,255,255,0.08);
+}
+.pending-inline-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 8rpx 0;
+}
+.pending-inline-text {
+ font-size: 24rpx;
+ color: #4CAF50;
+}
+.pending-inline-btn {
+ padding: 8rpx 20rpx;
+ background: #4CAF50;
+ color: #fff;
+ font-size: 22rpx;
+ border-radius: 12rpx;
+}
+
+/* VIP头像标识 */
+.avatar-wrap { position: relative; }
+.avatar-vip { border: 4rpx solid #FFD700 !important; box-shadow: 0 0 20rpx rgba(255,215,0,0.4); }
+.vip-badge { position: absolute; bottom: -4rpx; right: -4rpx; background: linear-gradient(135deg, #FFD700, #FFA500); color: #000; font-size: 16rpx; font-weight: bold; padding: 2rpx 8rpx; border-radius: 10rpx; line-height: 1.4; }
+.vip-badge-gray { background: rgba(255,255,255,0.2); color: rgba(255,255,255,0.5); }
+
+/* 会员权益小标签 */
+.vip-tags-row {
+ display: flex;
+ gap: 6rpx;
+ margin-left: 8rpx;
+ flex-shrink: 0;
+}
+.vip-tag-mini {
+ padding: 2rpx 10rpx;
+ font-size: 18rpx;
+ border-radius: 8rpx;
+ background: rgba(255,255,255,0.08);
+ color: rgba(255,255,255,0.3);
+ border: 1rpx solid rgba(255,255,255,0.1);
+}
+.vip-tag-active {
+ background: rgba(255,215,0,0.15);
+ color: #FFD700;
+ border-color: rgba(255,215,0,0.3);
+}
+
+/* 阅读统计 */
+.stats-card { padding: 24rpx 28rpx; }
+.stats-row { display: flex; gap: 16rpx; margin-top: 16rpx; }
+.stat-box {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 6rpx;
+ padding: 20rpx 12rpx;
+ border-radius: 16rpx;
+ background: rgba(139,92,246,0.06);
+}
+.stat-icon { font-size: 32rpx; }
+.stat-num { font-size: 36rpx; font-weight: bold; color: #fff; }
+.stat-text { font-size: 22rpx; color: rgba(255,255,255,0.5); }
+.pink-color { color: #ec4899; }
+
+/* 成为会员小按钮 */
+.become-vip-chip {
+ display: inline-flex;
+ align-items: center;
+ gap: 4rpx;
+ padding: 4rpx 14rpx;
+ border-radius: 20rpx;
+ background: linear-gradient(135deg, rgba(245,166,35,.15), rgba(245,166,35,.08));
+ border: 1rpx solid rgba(245,166,35,.3);
+ margin-left: 8rpx;
+}
+.chip-star { font-size: 18rpx; }
+.chip-text { font-size: 20rpx; color: #f5a623; font-weight: 600; white-space: nowrap; }
diff --git a/miniprogram/pages/privacy/privacy.js b/miniprogram/pages/privacy/privacy.js
index 0c95c06e..0cd665db 100644
--- a/miniprogram/pages/privacy/privacy.js
+++ b/miniprogram/pages/privacy/privacy.js
@@ -17,13 +17,5 @@ Page({
goBack() {
wx.navigateBack()
- },
-
- onShareAppMessage() {
- const ref = app.getMyReferralCode()
- return {
- title: 'Soul创业派对 - 隐私政策',
- path: ref ? `/pages/privacy/privacy?ref=${ref}` : '/pages/privacy/privacy'
- }
}
})
diff --git a/miniprogram/pages/purchases/purchases.js b/miniprogram/pages/purchases/purchases.js
index eda2907b..c458ad74 100644
--- a/miniprogram/pages/purchases/purchases.js
+++ b/miniprogram/pages/purchases/purchases.js
@@ -15,31 +15,29 @@ Page({
this.loadOrders()
},
- onShow() {
- this.loadOrders()
- },
-
async loadOrders() {
this.setData({ loading: true })
try {
- let purchasedSections = app.globalData.purchasedSections || []
- let sectionMidMap = app.globalData.sectionMidMap || {}
const userId = app.globalData.userInfo?.id
if (userId) {
- try {
- const res = await app.request(`/api/miniprogram/user/purchase-status?userId=${encodeURIComponent(userId)}`)
- if (res?.success && res.data) {
- purchasedSections = res.data.purchasedSections || []
- sectionMidMap = res.data.sectionMidMap || {}
- app.globalData.purchasedSections = purchasedSections
- app.globalData.sectionMidMap = sectionMidMap
- }
- } catch (_) { /* 使用缓存 */ }
+ const res = await app.request(`/api/orders?userId=${userId}`)
+ if (res && res.success && res.data) {
+ const orders = (res.data || []).map(item => ({
+ id: item.id || item.order_sn,
+ sectionId: item.product_id || item.section_id,
+ title: item.product_name || `章节 ${item.product_id || ''}`,
+ amount: item.amount || 0,
+ status: item.status || 'completed',
+ createTime: item.created_at ? new Date(item.created_at).toLocaleDateString() : '--'
+ }))
+ this.setData({ orders })
+ return
+ }
}
+ const purchasedSections = app.globalData.purchasedSections || []
const orders = purchasedSections.map((id, index) => ({
id: `order_${index}`,
sectionId: id,
- mid: sectionMidMap[id] || 0,
title: `章节 ${id}`,
amount: 1,
status: 'completed',
@@ -48,6 +46,13 @@ Page({
this.setData({ orders })
} catch (e) {
console.error('加载订单失败:', e)
+ const purchasedSections = app.globalData.purchasedSections || []
+ this.setData({
+ orders: purchasedSections.map((id, i) => ({
+ id: `order_${i}`, sectionId: id, title: `章节 ${id}`, amount: 1, status: 'completed',
+ createTime: new Date(Date.now() - i * 86400000).toLocaleDateString()
+ }))
+ })
} finally {
this.setData({ loading: false })
}
@@ -55,18 +60,8 @@ Page({
goToRead(e) {
const id = e.currentTarget.dataset.id
- const mid = e.currentTarget.dataset.mid
- const q = mid ? `mid=${mid}` : `id=${id}`
- wx.navigateTo({ url: `/pages/read/read?${q}` })
+ wx.navigateTo({ url: `/pages/read/read?id=${id}` })
},
- goBack() { wx.navigateBack() },
-
- onShareAppMessage() {
- const ref = app.getMyReferralCode()
- return {
- title: 'Soul创业派对 - 我的订单',
- path: ref ? `/pages/purchases/purchases?ref=${ref}` : '/pages/purchases/purchases'
- }
- }
+ goBack() { wx.navigateBack() }
})
diff --git a/miniprogram/pages/read/read.js b/miniprogram/pages/read/read.js
index 2deae32b..6e9fface 100644
--- a/miniprogram/pages/read/read.js
+++ b/miniprogram/pages/read/read.js
@@ -12,7 +12,6 @@
import accessManager from '../../utils/chapterAccessManager'
import readingTracker from '../../utils/readingTracker'
-const { buildScene, parseScene } = require('../../utils/scene.js')
const app = getApp()
@@ -57,12 +56,6 @@ Page({
sectionPrice: 1,
fullBookPrice: 9.9,
totalSections: 62,
- // 好友优惠展示
- userDiscount: 5,
- hasReferralDiscount: false,
- showDiscountHint: false,
- displaySectionPrice: 1,
- displayFullBookPrice: 9.9,
// 弹窗
showShareModal: false,
@@ -73,41 +66,21 @@ Page({
isGeneratingPoster: false,
// 免费章节
- freeIds: ['preface', 'epilogue', '1.1', 'appendix-1', 'appendix-2', 'appendix-3'],
-
- // 分享卡片图(canvas 生成后写入,供 onShareAppMessage 使用)
- shareImagePath: ''
+ freeIds: ['preface', 'epilogue', '1.1', 'appendix-1', 'appendix-2', 'appendix-3']
},
async onLoad(options) {
- // 官方以 options.scene 接收扫码参数(可同时带 mid/id + ref),与海报生成 buildScene 闭环
- const sceneStr = (options && options.scene) || ''
- const parsed = parseScene(sceneStr)
- const mid = options.mid ? parseInt(options.mid, 10) : (parsed.mid || app.globalData.initialSectionMid || 0)
- const id = options.id || parsed.id || app.globalData.initialSectionId
- const ref = options.ref || parsed.ref
- if (app.globalData.initialSectionMid) delete app.globalData.initialSectionMid
- if (app.globalData.initialSectionId) delete app.globalData.initialSectionId
-
- console.log('[Read] onLoad:', { options, sceneRaw: sceneStr || undefined, parsed, mid, id, ref })
- console.log('[Read] onLoad options:', options)
-
- if (!mid && !id) {
- console.warn('[Read] 未获取到章节 mid/id,options:', options)
- wx.showToast({ title: '章节参数缺失', icon: 'none' })
- this.setData({ accessState: 'error', loading: false })
- return
- }
-
+ const { id, ref } = options
+
this.setData({
statusBarHeight: app.globalData.statusBarHeight,
navBarHeight: app.globalData.navBarHeight,
- sectionId: '', // 加载后填充
- sectionMid: mid || null,
+ sectionId: id,
loading: true,
accessState: 'unknown'
})
+ // 处理推荐码绑定(异步不阻塞)
if (ref) {
console.log('[Read] 检测到推荐码:', ref)
wx.setStorageSync('referral_code', ref)
@@ -115,66 +88,35 @@ Page({
}
try {
- const userId = app.globalData.userInfo?.id
- const [config, purchaseRes] = await Promise.all([
- accessManager.fetchLatestConfig(),
- userId ? app.request(`/api/miniprogram/user/purchase-status?userId=${userId}`) : Promise.resolve(null)
- ])
- const sectionPrice = config.prices?.section ?? 1
- const fullBookPrice = config.prices?.fullbook ?? 9.9
- const userDiscount = config.userDiscount ?? 5
- // 有推荐人 = ref/ referral_code 或 用户信息中有推荐人绑定
- const hasReferral = !!(wx.getStorageSync('referral_code') || ref || purchaseRes?.data?.hasReferrer)
- const hasReferralDiscount = hasReferral && userDiscount > 0
- const showDiscountHint = userDiscount > 0
- const displaySectionPrice = hasReferralDiscount
- ? Math.round(sectionPrice * (1 - userDiscount / 100) * 100) / 100
- : sectionPrice
- const displayFullBookPrice = hasReferralDiscount
- ? Math.round(fullBookPrice * (1 - userDiscount / 100) * 100) / 100
- : fullBookPrice
+ // 【标准流程】1. 拉取最新配置(免费列表、价格)
+ const config = await accessManager.fetchLatestConfig()
this.setData({
freeIds: config.freeChapters,
- sectionPrice,
- fullBookPrice,
- userDiscount,
- hasReferralDiscount,
- showDiscountHint: userDiscount > 0,
- displaySectionPrice,
- displayFullBookPrice,
- purchasedCount: purchaseRes?.data?.purchasedSections?.length ?? this.data.purchasedCount ?? 0
+ sectionPrice: config.prices?.section ?? 1,
+ fullBookPrice: config.prices?.fullbook ?? 9.9
})
- // 先拉取章节获取 id(mid 时必需;id 时可直接用)
- let resolvedId = id
- let prefetchedChapter = null
- if (mid && !id) {
- const chRes = await app.request(`/api/miniprogram/book/chapter/by-mid/${mid}`)
- if (chRes && chRes.id) {
- resolvedId = chRes.id
- prefetchedChapter = chRes
- }
- }
- this.setData({ sectionId: resolvedId })
-
- const accessState = await accessManager.determineAccessState(resolvedId, config.freeChapters)
+ // 【标准流程】2. 确定权限状态
+ const accessState = await accessManager.determineAccessState(id, config.freeChapters)
const canAccess = accessManager.canAccessFullContent(accessState)
this.setData({
accessState,
canAccess,
isLoggedIn: !!app.globalData.userInfo?.id,
- showPaywall: !canAccess,
- purchasedCount: purchaseRes?.data?.purchasedSections?.length ?? 0
+ showPaywall: !canAccess
})
- await this.loadContent(mid, resolvedId, accessState, prefetchedChapter)
+ // 【标准流程】3. 加载内容
+ await this.loadContent(id, accessState)
+ // 【标准流程】4. 如果有权限,初始化阅读追踪
if (canAccess) {
- readingTracker.init(resolvedId)
+ readingTracker.init(id)
}
- this.loadNavigation(resolvedId)
+ // 5. 加载导航
+ this.loadNavigation(id)
} catch (e) {
console.error('[Read] 初始化失败:', e)
@@ -217,9 +159,8 @@ Page({
})
},
- // 【重构】加载章节内容。mid 优先用 by-mid 接口,id 用旧接口;prefetched 避免重复请求
- async loadContent(mid, id, accessState, prefetched) {
- console.log('[Read] loadContent 请求参数:', { mid, id, accessState: accessState, prefetched: !!prefetched })
+ // 【重构】加载章节内容(专注于内容加载,权限判断已在 onLoad 中由 accessManager 完成)
+ async loadContent(id, accessState) {
try {
const section = this.getSectionInfo(id)
const sectionPrice = this.data.sectionPrice ?? 1
@@ -227,43 +168,26 @@ Page({
section.price = sectionPrice
}
this.setData({ section })
-
- let res = prefetched
- if (!res) {
- res = mid
- ? await app.request(`/api/miniprogram/book/chapter/by-mid/${mid}`)
- : await app.request(`/api/miniprogram/book/chapter/${id}`)
- }
+
+ // 从 API 获取内容
+ const res = await app.request({ url: `/api/miniprogram/book/chapter/${id}`, silent: true })
if (res && res.content) {
const lines = res.content.split('\n').filter(line => line.trim())
const previewCount = Math.ceil(lines.length * 0.2)
- const updates = {
+
+ this.setData({
content: res.content,
contentParagraphs: lines,
previewParagraphs: lines.slice(0, previewCount),
partTitle: res.partTitle || '',
chapterTitle: res.chapterTitle || ''
- }
- if (res.mid) updates.sectionMid = res.mid
- this.setData(updates)
+ })
+
+ // 如果有权限,标记为已读
if (accessManager.canAccessFullContent(accessState)) {
app.markSectionAsRead(id)
}
- // 始终用接口返回的 price/isFree 更新 section(不写死 1 元)
- const section = this.data.section || {}
- if (res.price !== undefined && res.price !== null) section.price = Number(res.price)
- if (res.isFree !== undefined) section.isFree = !!res.isFree
- // 0元即免费:接口返回 price 为 0 或 isFree 为 true 时,不展示付费墙
- const isFreeByPrice = res.price === 0 || res.price === '0' || Number(res.price) === 0
- const isFreeByFlag = res.isFree === true
- if (isFreeByPrice || isFreeByFlag) {
- this.setData({ section, showPaywall: false, canAccess: true, accessState: 'free' })
- app.markSectionAsRead(id)
- } else {
- this.setData({ section })
- }
- setTimeout(() => this.drawShareCard(), 600)
}
} catch (e) {
console.error('[Read] 加载内容失败:', e)
@@ -307,12 +231,12 @@ Page({
return { id, title: appendixTitles[id] || '附录', isFree: true, price: 0 }
}
- // 普通章节:price 不写死,由 loadContent 从 config/接口 填充
+ // 普通章节
return {
id: id,
title: this.getSectionTitle(id),
isFree: id === '1.1',
- price: undefined
+ price: 1
}
},
@@ -335,6 +259,48 @@ Page({
}
return titles[id] || `章节 ${id}`
},
+
+ // 加载内容 - 三级降级方案:API → 本地缓存 → 备用API
+ async loadContent(id) {
+ const cacheKey = `chapter_${id}`
+
+ // 1. 优先从API获取
+ try {
+ const res = await this.fetchChapterWithTimeout(id, 5000)
+ if (res && res.content) {
+ this.setChapterContent(res)
+ // 成功后缓存到本地
+ wx.setStorageSync(cacheKey, res)
+ console.log('[Read] 从API加载成功:', id)
+ return
+ }
+ } catch (e) {
+ console.warn('[Read] API加载失败,尝试本地缓存:', e.message)
+ }
+
+ // 2. API失败,尝试从本地缓存读取
+ try {
+ const cached = wx.getStorageSync(cacheKey)
+ if (cached && cached.content) {
+ this.setChapterContent(cached)
+ console.log('[Read] 从本地缓存加载成功:', id)
+ // 后台静默刷新
+ this.silentRefresh(id)
+ return
+ }
+ } catch (e) {
+ console.warn('[Read] 本地缓存读取失败')
+ }
+
+ // 3. 都失败,显示加载中并持续重试
+ this.setData({
+ contentParagraphs: ['章节内容加载中...', '正在尝试连接服务器,请稍候...'],
+ previewParagraphs: ['章节内容加载中...']
+ })
+
+ // 延迟重试(最多3次)
+ this.retryLoadContent(id, 3)
+ },
// 带超时的章节请求
fetchChapterWithTimeout(id, timeout = 5000) {
@@ -397,11 +363,9 @@ Page({
try {
const res = await this.fetchChapterWithTimeout(id, 8000)
if (res && res.content) {
- this.setData({ section: this.getSectionInfo(id) })
this.setChapterContent(res)
wx.setStorageSync(`chapter_${id}`, res)
console.log('[Read] 重试成功:', id, '第', currentRetry + 1, '次')
- setTimeout(() => this.drawShareCard(), 600)
return
}
} catch (e) {
@@ -412,7 +376,7 @@ Page({
},
- // 加载导航(prevSection/nextSection 含 mid 时用于跳转,否则用 id)
+ // 加载导航
loadNavigation(id) {
const sectionOrder = [
'preface', '1.1', '1.2', '1.3', '1.4', '1.5',
@@ -428,19 +392,14 @@ Page({
'11.1', '11.2', '11.3', '11.4', '11.5',
'epilogue'
]
- const bookData = app.globalData.bookData || []
- const idToMid = {}
- bookData.forEach(ch => {
- if (ch.id && ch.mid) idToMid[ch.id] = ch.mid
- })
-
+
const currentIndex = sectionOrder.indexOf(id)
const prevId = currentIndex > 0 ? sectionOrder[currentIndex - 1] : null
const nextId = currentIndex < sectionOrder.length - 1 ? sectionOrder[currentIndex + 1] : null
-
+
this.setData({
- prevSection: prevId ? { id: prevId, mid: idToMid[prevId], title: this.getSectionTitle(prevId) } : null,
- nextSection: nextId ? { id: nextId, mid: idToMid[nextId], title: this.getSectionTitle(nextId) } : null
+ prevSection: prevId ? { id: prevId, title: this.getSectionTitle(prevId) } : null,
+ nextSection: nextId ? { id: nextId, title: this.getSectionTitle(nextId) } : null
})
},
@@ -460,6 +419,21 @@ Page({
this.setData({ showShareModal: false })
},
+ // 复制链接
+ copyLink() {
+ const userInfo = app.globalData.userInfo
+ const referralCode = userInfo?.referralCode || ''
+ const shareUrl = `https://soul.quwanzhi.com/read/${this.data.sectionId}${referralCode ? '?ref=' + referralCode : ''}`
+
+ wx.setClipboardData({
+ data: shareUrl,
+ success: () => {
+ wx.showToast({ title: '链接已复制', icon: 'success' })
+ this.setData({ showShareModal: false })
+ }
+ })
+ },
+
// 复制分享文案(朋友圈风格)
copyShareText() {
const { section } = this.data
@@ -480,91 +454,33 @@ Page({
})
},
- // 绘制分享卡片图(标题+正文摘要),生成后供 onShareAppMessage 使用
- drawShareCard() {
- const { section, sectionId, contentParagraphs } = this.data
- const title = section?.title || this.getSectionTitle(sectionId) || '精彩内容'
- const raw = (contentParagraphs && contentParagraphs.length)
- ? contentParagraphs.slice(0, 4).join(' ').replace(/\s+/g, ' ').trim()
- : ''
- const excerpt = raw.length > 120 ? raw.slice(0, 120) + '...' : (raw || '来自派对房的真实商业故事')
- const ctx = wx.createCanvasContext('shareCardCanvas', this)
- const w = 500
- const h = 400
- // 白底
- ctx.setFillStyle('#ffffff')
- ctx.fillRect(0, 0, w, h)
- // 顶部:平台名
- ctx.setFillStyle('#333333')
- ctx.setFontSize(14)
- ctx.fillText('📚 Soul 创业派对 - 真实商业故事', 24, 36)
- // 深色内容区(模拟参考图效果)
- const boxX = 24
- const boxY = 52
- const boxW = w - 48
- const boxH = 300
- ctx.setFillStyle('#2c2c2e')
- ctx.fillRect(boxX, boxY, boxW, boxH)
- // 文章标题(白字)
- ctx.setFillStyle('#ffffff')
- ctx.setFontSize(15)
- const titleLines = this.wrapText(ctx, title.length > 50 ? title.slice(0, 50) + '...' : title, boxW - 32, 15)
- let y = boxY + 28
- titleLines.slice(0, 2).forEach(line => {
- ctx.fillText(line, boxX + 16, y)
- y += 22
- })
- y += 8
- // 正文摘要(浅灰)
- ctx.setFillStyle('rgba(255,255,255,0.88)')
- ctx.setFontSize(12)
- const excerptLines = this.wrapText(ctx, excerpt, boxW - 32, 12)
- excerptLines.slice(0, 8).forEach(line => {
- ctx.fillText(line, boxX + 16, y)
- y += 20
- })
- // 底部:小程序标识
- ctx.setFillStyle('#999999')
- ctx.setFontSize(11)
- ctx.fillText('小程序', 24, h - 16)
- ctx.draw(false, () => {
- wx.canvasToTempFilePath({
- canvasId: 'shareCardCanvas',
- fileType: 'png',
- success: (res) => {
- this.setData({ shareImagePath: res.tempFilePath })
- }
- }, this)
- })
- },
-
- // 统一分享配置(底部「推荐给好友」与右下角分享按钮均走此配置,由 onShareAppMessage 使用)
- getShareConfig() {
- const { section, sectionId, sectionMid, shareImagePath } = this.data
- const ref = app.getMyReferralCode()
- const shareTitle = section?.title
+ // 分享到微信 - 自动带分享人ID
+ onShareAppMessage() {
+ const { section, sectionId } = this.data
+ const userInfo = app.globalData.userInfo
+ const referralCode = userInfo?.referralCode || wx.getStorageSync('referralCode') || ''
+
+ // 分享标题优化
+ const shareTitle = section?.title
? `📚 ${section.title.length > 20 ? section.title.slice(0, 20) + '...' : section.title}`
: '📚 Soul创业派对 - 真实商业故事'
- const q = sectionMid ? `mid=${sectionMid}` : `id=${sectionId}`
- const path = ref ? `/pages/read/read?${q}&ref=${ref}` : `/pages/read/read?${q}`
+
return {
title: shareTitle,
- path,
- imageUrl: shareImagePath || undefined
+ path: `/pages/read/read?id=${sectionId}${referralCode ? '&ref=' + referralCode : ''}`,
+ imageUrl: '/assets/share-cover.png' // 可配置分享封面图
}
},
-
- onShareAppMessage() {
- return this.getShareConfig()
- },
-
+
+ // 分享到朋友圈
onShareTimeline() {
- const { section, sectionId, sectionMid } = this.data
- const ref = app.getMyReferralCode()
- const q = sectionMid ? `mid=${sectionMid}` : `id=${sectionId}`
+ const { section, sectionId } = this.data
+ const userInfo = app.globalData.userInfo
+ const referralCode = userInfo?.referralCode || ''
+
return {
title: `${section?.title || 'Soul创业派对'} - 来自派对房的真实故事`,
- query: ref ? `${q}&ref=${ref}` : q
+ query: `id=${sectionId}${referralCode ? '&ref=' + referralCode : ''}`
}
},
@@ -643,34 +559,9 @@ Page({
// 1. 刷新用户购买状态(从 orders 表拉取最新)
await accessManager.refreshUserPurchaseStatus()
- // 2. 重新拉取免费列表、价格与用户推荐人状态
- const userId = app.globalData.userInfo?.id
- const [config, purchaseRes] = await Promise.all([
- accessManager.fetchLatestConfig(),
- userId ? app.request(`/api/miniprogram/user/purchase-status?userId=${userId}`) : Promise.resolve(null)
- ])
- const sectionPrice = config.prices?.section ?? this.data.sectionPrice ?? 1
- const fullBookPrice = config.prices?.fullbook ?? this.data.fullBookPrice ?? 9.9
- const userDiscount = config.userDiscount ?? 5
- const hasReferral = !!(wx.getStorageSync('referral_code') || purchaseRes?.data?.hasReferrer)
- const hasReferralDiscount = hasReferral && userDiscount > 0
- const displaySectionPrice = hasReferralDiscount
- ? Math.round(sectionPrice * (1 - userDiscount / 100) * 100) / 100
- : sectionPrice
- const displayFullBookPrice = hasReferralDiscount
- ? Math.round(fullBookPrice * (1 - userDiscount / 100) * 100) / 100
- : fullBookPrice
- this.setData({
- freeIds: config.freeChapters,
- sectionPrice,
- fullBookPrice,
- userDiscount,
- hasReferralDiscount,
- showDiscountHint: userDiscount > 0,
- displaySectionPrice,
- displayFullBookPrice,
- purchasedCount: purchaseRes?.data?.purchasedSections?.length ?? this.data.purchasedCount ?? 0
- })
+ // 2. 重新拉取免费列表(极端情况:刚登录时当前章节可能改免费了)
+ const config = await accessManager.fetchLatestConfig()
+ this.setData({ freeIds: config.freeChapters })
// 3. 重新判断当前章节权限
const newAccessState = await accessManager.determineAccessState(
@@ -688,7 +579,7 @@ Page({
// 4. 如果已解锁,重新加载内容并初始化阅读追踪
if (canAccess) {
- await this.loadContent(this.data.sectionMid, this.data.sectionId, newAccessState, null)
+ await this.loadContent(this.data.sectionId, newAccessState)
readingTracker.init(this.data.sectionId)
}
@@ -713,7 +604,7 @@ Page({
return
}
- const price = this.data.section?.price ?? this.data.sectionPrice ?? 1
+ const price = this.data.section?.price || 1
console.log('[Pay] 开始支付流程:', { sectionId: this.data.sectionId, price })
wx.hideLoading()
await this.processPayment('section', this.data.sectionId, price)
@@ -759,7 +650,6 @@ Page({
// 更新本地购买状态
app.globalData.hasFullBook = checkRes.data.hasFullBook
app.globalData.purchasedSections = checkRes.data.purchasedSections || []
- app.globalData.sectionMidMap = checkRes.data.sectionMidMap || {}
// 检查是否已购买
if (type === 'section' && sectionId) {
@@ -844,8 +734,7 @@ Page({
if (res.success && res.data?.payParams) {
paymentData = res.data.payParams
- paymentData._orderSn = res.data.orderSn
- console.log('[Pay] 获取支付参数成功, orderSn:', res.data.orderSn)
+ console.log('[Pay] 获取支付参数成功:', paymentData)
} else {
throw new Error(res.error || res.message || '创建订单失败')
}
@@ -878,12 +767,11 @@ Page({
console.log('[Pay] 调起微信支付, paymentData:', paymentData)
try {
- const orderSn = paymentData._orderSn
await this.callWechatPay(paymentData)
- // 4. 轮询订单状态确认已支付后刷新并解锁(不依赖 PayNotify 回调时机)
+ // 4. 【标准流程】支付成功后刷新权限并解锁内容
console.log('[Pay] 微信支付成功!')
- await this.onPaymentSuccess(orderSn)
+ await this.onPaymentSuccess()
} catch (payErr) {
console.error('[Pay] 微信支付调起失败:', payErr)
@@ -919,34 +807,13 @@ Page({
}
},
- // 轮询订单状态,确认 paid 后刷新权限并解锁
- async pollOrderUntilPaid(orderSn) {
- const maxAttempts = 15
- const interval = 800
- for (let i = 0; i < maxAttempts; i++) {
- try {
- const r = await app.request(`/api/miniprogram/pay?orderSn=${encodeURIComponent(orderSn)}`, { method: 'GET', silent: true })
- if (r?.data?.status === 'paid') return true
- } catch (_) {}
- if (i < maxAttempts - 1) await this.sleep(interval)
- }
- return false
- },
-
// 【新增】支付成功后的标准处理流程
- async onPaymentSuccess(orderSn) {
+ async onPaymentSuccess() {
wx.showLoading({ title: '确认购买中...', mask: true })
try {
- // 1. 轮询订单状态直到已支付(GET pay 会主动同步本地订单,不依赖 PayNotify)
- if (orderSn) {
- const paid = await this.pollOrderUntilPaid(orderSn)
- if (!paid) {
- console.warn('[Pay] 轮询超时,仍尝试刷新')
- }
- } else {
- await this.sleep(1500)
- }
+ // 1. 等待服务端处理支付回调(1-2秒)
+ await this.sleep(2000)
// 2. 刷新用户购买状态
await accessManager.refreshUserPurchaseStatus()
@@ -976,7 +843,7 @@ Page({
})
// 4. 重新加载全文
- await this.loadContent(this.data.sectionMid, this.data.sectionId, newAccessState, null)
+ await this.loadContent(this.data.sectionId, newAccessState)
// 5. 初始化阅读追踪
if (canAccess) {
@@ -1013,10 +880,7 @@ Page({
// 更新全局购买状态
app.globalData.hasFullBook = res.data.hasFullBook
app.globalData.purchasedSections = res.data.purchasedSections || []
- app.globalData.sectionMidMap = res.data.sectionMidMap || {}
- app.globalData.matchCount = res.data.matchCount ?? 0
- app.globalData.matchQuota = res.data.matchQuota || null
-
+
// 更新用户信息中的购买记录
const userInfo = app.globalData.userInfo || {}
userInfo.hasFullBook = res.data.hasFullBook
@@ -1026,8 +890,7 @@ Page({
console.log('[Pay] ✅ 购买状态已刷新:', {
hasFullBook: res.data.hasFullBook,
- purchasedCount: res.data.purchasedSections.length,
- matchCount: res.data.matchCount
+ purchasedCount: res.data.purchasedSections.length
})
}
} catch (e) {
@@ -1051,19 +914,17 @@ Page({
})
},
+ // 跳转到上一篇
goToPrev() {
- const s = this.data.prevSection
- if (s) {
- const q = s.mid ? `mid=${s.mid}` : `id=${s.id}`
- wx.redirectTo({ url: `/pages/read/read?${q}` })
+ if (this.data.prevSection) {
+ wx.redirectTo({ url: `/pages/read/read?id=${this.data.prevSection.id}` })
}
},
+ // 跳转到下一篇
goToNext() {
- const s = this.data.nextSection
- if (s) {
- const q = s.mid ? `mid=${s.mid}` : `id=${s.id}`
- wx.redirectTo({ url: `/pages/read/read?${q}` })
+ if (this.data.nextSection) {
+ wx.redirectTo({ url: `/pages/read/read?id=${this.data.nextSection.id}` })
}
},
@@ -1072,120 +933,134 @@ Page({
wx.navigateTo({ url: '/pages/referral/referral' })
},
- // 生成海报(弹窗先展示,延迟再绘制,确保 canvas 已渲染)
+ // 生成海报
async generatePoster() {
wx.showLoading({ title: '生成中...' })
this.setData({ showPosterModal: true, isGeneratingPoster: true })
- const { section, contentParagraphs, sectionId, sectionMid } = this.data
- const userInfo = app.globalData.userInfo
- const userId = userInfo?.id || ''
- const safeParagraphs = contentParagraphs || []
-
- // 与 utils/scene 闭环:生成 scene 用 buildScene,扫码后用 parseScene 解析
- let qrcodeTempPath = null
try {
- const refVal = userId ? String(userId).slice(0, 12) : ''
- const scene = buildScene({
- mid: sectionMid || undefined,
- id: sectionMid ? undefined : (sectionId || ''),
- ref: refVal || undefined
- })
- const baseUrl = app.globalData.baseUrl || ''
- const url = `${baseUrl}/api/miniprogram/qrcode/image?scene=${encodeURIComponent(scene)}&page=${encodeURIComponent('pages/read/read')}&width=280`
- qrcodeTempPath = await new Promise((resolve) => {
- wx.downloadFile({
- url,
- success: (res) => resolve(res.statusCode === 200 ? res.tempFilePath : null),
- fail: () => resolve(null)
+ const ctx = wx.createCanvasContext('posterCanvas', this)
+ const { section, contentParagraphs, sectionId } = this.data
+ const userInfo = app.globalData.userInfo
+ const userId = userInfo?.id || ''
+
+ // 获取小程序码(带推荐人参数)
+ let qrcodeImage = null
+ try {
+ const scene = userId ? `id=${sectionId}&ref=${userId.slice(0,10)}` : `id=${sectionId}`
+ const qrRes = await app.request('/api/miniprogram/qrcode', {
+ method: 'POST',
+ data: { scene, page: 'pages/read/read', width: 280 }
})
+ if (qrRes.success && qrRes.image) {
+ qrcodeImage = qrRes.image
+ }
+ } catch (e) {
+ console.log('[Poster] 获取小程序码失败,使用占位符')
+ }
+
+ // 海报尺寸 300x450
+ const width = 300
+ const height = 450
+
+ // 背景渐变
+ const grd = ctx.createLinearGradient(0, 0, 0, height)
+ grd.addColorStop(0, '#1a1a2e')
+ grd.addColorStop(1, '#16213e')
+ ctx.setFillStyle(grd)
+ ctx.fillRect(0, 0, width, height)
+
+ // 顶部装饰条
+ ctx.setFillStyle('#00CED1')
+ ctx.fillRect(0, 0, width, 4)
+
+ // 标题区域
+ ctx.setFillStyle('#ffffff')
+ ctx.setFontSize(14)
+ ctx.fillText('📚 Soul创业派对', 20, 35)
+
+ // 章节标题
+ ctx.setFontSize(18)
+ ctx.setFillStyle('#ffffff')
+ const title = section?.title || '精彩内容'
+ const titleLines = this.wrapText(ctx, title, width - 40, 18)
+ let y = 70
+ titleLines.forEach(line => {
+ ctx.fillText(line, 20, y)
+ y += 26
+ })
+
+ // 分隔线
+ ctx.setStrokeStyle('rgba(255,255,255,0.1)')
+ ctx.beginPath()
+ ctx.moveTo(20, y + 10)
+ ctx.lineTo(width - 20, y + 10)
+ ctx.stroke()
+
+ // 内容摘要
+ ctx.setFontSize(12)
+ ctx.setFillStyle('rgba(255,255,255,0.8)')
+ y += 30
+ const summary = contentParagraphs.slice(0, 3).join(' ').slice(0, 150) + '...'
+ const summaryLines = this.wrapText(ctx, summary, width - 40, 12)
+ summaryLines.slice(0, 6).forEach(line => {
+ ctx.fillText(line, 20, y)
+ y += 20
+ })
+
+ // 底部区域背景
+ ctx.setFillStyle('rgba(0,206,209,0.1)')
+ ctx.fillRect(0, height - 100, width, 100)
+
+ // 左侧提示文字
+ ctx.setFillStyle('#ffffff')
+ ctx.setFontSize(13)
+ ctx.fillText('长按识别小程序码', 20, height - 60)
+ ctx.setFillStyle('rgba(255,255,255,0.6)')
+ ctx.setFontSize(11)
+ ctx.fillText('长按小程序码阅读全文', 20, height - 38)
+
+ // 绘制小程序码或占位符
+ const drawQRCode = () => {
+ return new Promise((resolve) => {
+ if (qrcodeImage) {
+ // 下载base64图片并绘制
+ const fs = wx.getFileSystemManager()
+ const filePath = `${wx.env.USER_DATA_PATH}/qrcode_${Date.now()}.png`
+ const base64Data = qrcodeImage.replace(/^data:image\/\w+;base64,/, '')
+
+ fs.writeFile({
+ filePath,
+ data: base64Data,
+ encoding: 'base64',
+ success: () => {
+ ctx.drawImage(filePath, width - 85, height - 85, 70, 70)
+ resolve()
+ },
+ fail: () => {
+ this.drawQRPlaceholder(ctx, width, height)
+ resolve()
+ }
+ })
+ } else {
+ this.drawQRPlaceholder(ctx, width, height)
+ resolve()
+ }
+ })
+ }
+
+ await drawQRCode()
+
+ ctx.draw(true, () => {
+ wx.hideLoading()
+ this.setData({ isGeneratingPoster: false })
})
} catch (e) {
- console.log('[Poster] 获取小程序码失败,使用占位符')
+ console.error('生成海报失败:', e)
+ wx.hideLoading()
+ wx.showToast({ title: '生成失败', icon: 'none' })
+ this.setData({ showPosterModal: false, isGeneratingPoster: false })
}
-
- const doDraw = () => {
- try {
- const ctx = wx.createCanvasContext('posterCanvas', this)
- const width = 300
- const height = 450
-
- const grd = ctx.createLinearGradient(0, 0, 0, height)
- grd.addColorStop(0, '#1a1a2e')
- grd.addColorStop(1, '#16213e')
- ctx.setFillStyle(grd)
- ctx.fillRect(0, 0, width, height)
-
- ctx.setFillStyle('#00CED1')
- ctx.fillRect(0, 0, width, 4)
-
- ctx.setFillStyle('#ffffff')
- ctx.setFontSize(14)
- ctx.fillText('📚 Soul创业派对', 20, 35)
-
- ctx.setFontSize(18)
- ctx.setFillStyle('#ffffff')
- const title = section?.title || this.getSectionTitle(sectionId) || '精彩内容'
- const titleLines = this.wrapText(ctx, title, width - 40, 18)
- let y = 70
- titleLines.forEach(line => {
- ctx.fillText(line, 20, y)
- y += 26
- })
-
- ctx.setStrokeStyle('rgba(255,255,255,0.1)')
- ctx.beginPath()
- ctx.moveTo(20, y + 10)
- ctx.lineTo(width - 20, y + 10)
- ctx.stroke()
-
- ctx.setFontSize(12)
- ctx.setFillStyle('rgba(255,255,255,0.8)')
- y += 30
- const summary = safeParagraphs.slice(0, 3).join(' ').replace(/\s+/g, ' ').trim().slice(0, 150)
- const summaryText = summary ? summary + (summary.length >= 150 ? '...' : '') : '来自派对房的真实商业故事'
- const summaryLines = this.wrapText(ctx, summaryText, width - 40, 12)
- summaryLines.slice(0, 6).forEach(line => {
- ctx.fillText(line, 20, y)
- y += 20
- })
-
- ctx.setFillStyle('rgba(0,206,209,0.1)')
- ctx.fillRect(0, height - 100, width, 100)
-
- ctx.setFillStyle('#ffffff')
- ctx.setFontSize(13)
- ctx.fillText('长按识别小程序码', 20, height - 60)
- ctx.setFillStyle('rgba(255,255,255,0.6)')
- ctx.setFontSize(11)
- ctx.fillText('长按小程序码阅读全文', 20, height - 38)
-
- const drawQRCode = () => {
- return new Promise((resolve) => {
- if (qrcodeTempPath) {
- ctx.drawImage(qrcodeTempPath, width - 85, height - 85, 70, 70)
- } else {
- this.drawQRPlaceholder(ctx, width, height)
- }
- resolve()
- })
- }
-
- drawQRCode().then(() => {
- ctx.draw(true, () => {
- wx.hideLoading()
- this.setData({ isGeneratingPoster: false })
- })
- })
- } catch (e) {
- console.error('生成海报失败:', e)
- wx.hideLoading()
- wx.showToast({ title: '生成失败', icon: 'none' })
- this.setData({ showPosterModal: false, isGeneratingPoster: false })
- }
- }
-
- setTimeout(doDraw, 400)
},
// 绘制小程序码占位符
@@ -1223,21 +1098,11 @@ Page({
this.setData({ showPosterModal: false })
},
- // 保存海报到相册:画布 300x450 兼容 iOS,导出 2 倍 600x900 提升清晰度(宽高比 2:3 不变)
+ // 保存海报到相册
savePoster() {
- const width = 300
- const height = 450
- const exportScale = 2
wx.canvasToTempFilePath({
canvasId: 'posterCanvas',
- destWidth: width * exportScale,
- destHeight: height * exportScale,
- fileType: 'png',
success: (res) => {
- if (!res.tempFilePath) {
- wx.showToast({ title: '生成图片失败', icon: 'none' })
- return
- }
wx.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: () => {
@@ -1245,25 +1110,25 @@ Page({
this.setData({ showPosterModal: false })
},
fail: (err) => {
- console.error('[savePoster] saveImageToPhotosAlbum fail:', err)
- if (err.errMsg && (err.errMsg.includes('auth deny') || err.errMsg.includes('authorize'))) {
+ if (err.errMsg.includes('auth deny')) {
wx.showModal({
title: '提示',
content: '需要相册权限才能保存海报',
confirmText: '去设置',
- success: (sres) => {
- if (sres.confirm) wx.openSetting()
+ success: (res) => {
+ if (res.confirm) {
+ wx.openSetting()
+ }
}
})
} else {
- wx.showToast({ title: err.errMsg || '保存失败', icon: 'none' })
+ wx.showToast({ title: '保存失败', icon: 'none' })
}
}
})
},
- fail: (err) => {
- console.error('[savePoster] canvasToTempFilePath fail:', err)
- wx.showToast({ title: err.errMsg || '生成图片失败', icon: 'none' })
+ fail: () => {
+ wx.showToast({ title: '生成图片失败', icon: 'none' })
}
}, this)
},
@@ -1286,33 +1151,9 @@ Page({
wx.showLoading({ title: '重试中...', mask: true })
try {
- const userId = app.globalData.userInfo?.id
- const [config, purchaseRes] = await Promise.all([
- accessManager.fetchLatestConfig(),
- userId ? app.request(`/api/miniprogram/user/purchase-status?userId=${userId}`) : Promise.resolve(null)
- ])
- const sectionPrice = config.prices?.section ?? this.data.sectionPrice ?? 1
- const fullBookPrice = config.prices?.fullbook ?? this.data.fullBookPrice ?? 9.9
- const userDiscount = config.userDiscount ?? 5
- const hasReferral = !!(wx.getStorageSync('referral_code') || purchaseRes?.data?.hasReferrer)
- const hasReferralDiscount = hasReferral && userDiscount > 0
- const displaySectionPrice = hasReferralDiscount
- ? Math.round(sectionPrice * (1 - userDiscount / 100) * 100) / 100
- : sectionPrice
- const displayFullBookPrice = hasReferralDiscount
- ? Math.round(fullBookPrice * (1 - userDiscount / 100) * 100) / 100
- : fullBookPrice
- this.setData({
- freeIds: config.freeChapters,
- sectionPrice,
- fullBookPrice,
- userDiscount,
- hasReferralDiscount,
- showDiscountHint: userDiscount > 0,
- displaySectionPrice,
- displayFullBookPrice,
- purchasedCount: purchaseRes?.data?.purchasedSections?.length ?? this.data.purchasedCount ?? 0
- })
+ // 重新拉取配置
+ const config = await accessManager.fetchLatestConfig()
+ this.setData({ freeIds: config.freeChapters })
// 重新判断权限
const newAccessState = await accessManager.determineAccessState(
@@ -1328,7 +1169,7 @@ Page({
})
// 重新加载内容
- await this.loadContent(this.data.sectionMid, this.data.sectionId, newAccessState, null)
+ await this.loadContent(this.data.sectionId, newAccessState)
// 如果有权限,初始化阅读追踪
if (canAccess) {
diff --git a/miniprogram/pages/read/read.wxml b/miniprogram/pages/read/read.wxml
index 37375214..a17cc3b3 100644
--- a/miniprogram/pages/read/read.wxml
+++ b/miniprogram/pages/read/read.wxml
@@ -166,16 +166,7 @@
购买本章
-
- ¥{{section && section.price != null ? section.price : sectionPrice}}
- ¥{{displaySectionPrice}}
- 省{{userDiscount}}%
-
-
- ¥{{section && section.price != null ? section.price : sectionPrice}}
- 好友链接立省{{userDiscount}}%
-
- ¥{{section && section.price != null ? section.price : sectionPrice}}
+ ¥{{section && section.price != null ? section.price : sectionPrice}}
@@ -185,9 +176,8 @@
解锁全部 {{totalSections}} 章
- ¥{{fullBookPrice || 9.9}}
- ¥{{hasReferralDiscount ? displayFullBookPrice : (fullBookPrice || 9.9)}}
- {{hasReferralDiscount ? '省' + userDiscount + '%' : '省82%'}}
+ ¥{{fullBookPrice || 9.9}}
+ 省82%
@@ -256,7 +246,7 @@
✕
-
+
@@ -307,7 +297,4 @@
-
-
-
diff --git a/miniprogram/pages/referral/referral.js b/miniprogram/pages/referral/referral.js
index d5bd3a5c..b56fdc74 100644
--- a/miniprogram/pages/referral/referral.js
+++ b/miniprogram/pages/referral/referral.js
@@ -32,8 +32,7 @@ Page({
pendingEarnings: 0, // 待结算收益(保留兼容)
shareRate: 90, // 分成比例(90%),从 referral/data 或 config 获取
minWithdrawAmount: 10, // 最低提现金额,从 referral/data 获取
- withdrawFee: 5, // 提现手续费%,从 referral/data 获取
- bindingDays: 30, // 绑定期天数,从 referral/data 获取
+ bindingDays: 30, // 绑定期天数,从 referral/data 获取
userDiscount: 5, // 好友购买优惠%,从 referral/data 获取
hasWechatId: false, // 是否已绑定微信号(未绑定时需引导去设置)
@@ -60,7 +59,6 @@ Page({
showPosterModal: false,
isGeneratingPoster: false,
posterQrSrc: '',
- posterQrFilePath: '',
posterReferralLink: '',
posterNickname: '',
posterNicknameInitial: '',
@@ -200,7 +198,6 @@ Page({
pendingEarnings: formatMoney(realData?.pendingEarnings || 0),
shareRate: realData?.shareRate ?? 90,
minWithdrawAmount: minWithdrawAmount,
- withdrawFee: realData?.withdrawFee ?? 5,
bindingDays: realData?.bindingDays ?? 30,
userDiscount: realData?.userDiscount ?? 5,
@@ -286,6 +283,53 @@ Page({
})
},
+ // 分享到朋友圈 - 1:1 迁移 Next.js 的 handleShareToWechat
+ shareToWechat() {
+ const { referralCode } = this.data
+ const referralLink = `https://soul.quwanzhi.com/?ref=${referralCode}`
+
+ // 与 Next.js 完全相同的文案
+ const shareText = `📖 推荐一本好书《一场SOUL的创业实验场》
+
+这是卡若每天早上6-9点在Soul派对房分享的真实商业故事,55个真实案例,讲透创业的底层逻辑。
+
+👉 点击阅读: ${referralLink}
+
+#创业 #商业思维 #Soul派对`
+
+ wx.setClipboardData({
+ data: shareText,
+ success: () => {
+ wx.showModal({
+ title: '朋友圈文案已复制!',
+ content: '打开微信 → 发朋友圈 → 粘贴即可',
+ showCancel: false,
+ confirmText: '知道了'
+ })
+ }
+ })
+ },
+
+ // 更多分享方式 - 1:1 迁移 Next.js 的 handleShare
+ handleMoreShare() {
+ const { referralCode } = this.data
+ const referralLink = `https://soul.quwanzhi.com/?ref=${referralCode}`
+
+ // 与 Next.js 完全相同的文案
+ const shareText = `我正在读《一场SOUL的创业实验场》,每天6-9点的真实商业故事,推荐给你!${referralLink}`
+
+ wx.setClipboardData({
+ data: shareText,
+ success: () => {
+ wx.showToast({
+ title: '分享文案已复制',
+ icon: 'success',
+ duration: 2000
+ })
+ }
+ })
+ },
+
// 生成推广海报 - 1:1 对齐 Next.js 设计
async generatePoster() {
wx.showLoading({ title: '生成中...', mask: true })
@@ -308,18 +352,15 @@ Page({
},
})
- // 接口返回 { success, image: "data:image/png;base64,...", scene }
- const imageData = res?.image || res?.data?.image
- if (!res || !res.success || !imageData) {
+ if (!res || !res.success || !res.image) {
console.error('[Poster] 生成小程序码失败:', res)
- throw new Error(res?.error || res?.message || '生成小程序码失败')
+ throw new Error(res?.error || '生成小程序码失败')
}
- // 小程序 image 组件支持 base64 格式,直接使用;同时写入本地供预览用
- const base64Str = String(imageData).trim()
+ // 后端返回的是 data:image/png;base64,... 需要先写入本地临时文件,再作为 的 src
+ const base64Data = String(res.image).replace(/^data:image\/\w+;base64,/, '')
const fs = wx.getFileSystemManager()
const filePath = `${wx.env.USER_DATA_PATH}/poster_qrcode_${Date.now()}.png`
- const base64Data = base64Str.replace(/^data:image\/\w+;base64,/, '')
await new Promise((resolve, reject) => {
fs.writeFile({
@@ -334,10 +375,10 @@ Page({
})
})
- // 优先用 base64 直接显示(兼容性更好);预览时用本地路径
+ console.log('[Poster] 小程序码已保存到本地:', filePath)
+
this.setData({
- posterQrSrc: base64Str,
- posterQrFilePath: filePath,
+ posterQrSrc: filePath,
posterReferralLink: '', // 小程序版本不再使用 H5 链接
posterNickname: nickname,
posterNicknameInitial: (nickname || '用').charAt(0),
@@ -348,7 +389,7 @@ Page({
console.error('[Poster] 生成二维码失败:', e)
wx.hideLoading()
wx.showToast({ title: '生成失败', icon: 'none' })
- this.setData({ showPosterModal: false, isGeneratingPoster: false, posterQrSrc: '', posterQrFilePath: '', posterReferralLink: '' })
+ this.setData({ showPosterModal: false, isGeneratingPoster: false, posterQrSrc: '', posterReferralLink: '' })
}
},
@@ -529,15 +570,56 @@ Page({
// 预览二维码
previewPosterQr() {
- const { posterQrSrc, posterQrFilePath } = this.data
- const url = posterQrFilePath || posterQrSrc
- if (!url) return
- wx.previewImage({ urls: [url] })
+ const { posterQrSrc } = this.data
+ if (!posterQrSrc) return
+ wx.previewImage({ urls: [posterQrSrc] })
},
// 阻止冒泡
stopPropagation() {},
+ // 分享到朋友圈 - 随机文案
+ shareToMoments() {
+ // 10条随机文案,基于书的内容
+ const shareTexts = [
+ `🔥 在派对房里听到的真实故事,比虚构的小说精彩100倍!\n\n电动车出租月入5万、私域一年赚1000万、一个人的公司月入10万...\n\n62个真实案例,搜"Soul创业派对"小程序看全部!\n\n#创业 #私域 #商业`,
+
+ `💡 今天终于明白:会赚钱的人,都在用"流量杠杆"\n\n抖音、Soul、飞书...同一套内容,撬动不同平台的流量。\n\n《Soul创业派对》里的实战方法,受用终身!\n\n#流量 #副业 #创业派对`,
+
+ `📚 一个70后大健康私域,一个月150万流水是怎么做到的?\n\n答案在《Soul创业派对》第9章,全是干货。\n\n搜小程序"Soul创业派对",我在里面等你\n\n#大健康 #私域运营 #真实案例`,
+
+ `🎯 "分钱不是分你的钱,是分不属于对方的钱"\n\n这句话改变了我对商业合作的认知。\n\n推荐《Soul创业派对》,创业者必读!\n\n#云阿米巴 #商业思维 #创业`,
+
+ `✨ 资源整合高手的社交方法论,在派对房里学到了\n\n"先让对方赚到钱,自己才能长久赚钱"\n\n这本《Soul创业派对》,每章都是实战经验\n\n#资源整合 #社交 #创业故事`,
+
+ `🚀 AI工具推广:一个隐藏的高利润赛道\n\n客单价高、复购率高、需求旺盛...\n\n《Soul创业派对》里的商业机会,你发现了吗?\n\n#AI #副业 #商业机会`,
+
+ `💰 美业整合:一个人的公司如何月入十万?\n\n不开店、不囤货、轻资产运营...\n\n《Soul创业派对》告诉你答案!\n\n#美业 #轻创业 #月入十万`,
+
+ `🌟 3000万流水是怎么跑出来的?\n\n不是靠运气,是靠系统。\n\n《Soul创业派对》里的电商底层逻辑,值得反复看\n\n#电商 #创业 #商业系统`,
+
+ `📖 "人与人之间的关系,归根结底就三个东西:利益、情感、价值观"\n\n在派对房里聊出的金句,都在《Soul创业派对》里\n\n#人性 #商业 #创业派对`,
+
+ `🔔 未来职业的三个方向:技术型、资源型、服务型\n\n你属于哪一种?\n\n《Soul创业派对》帮你找到答案!\n\n#职业规划 #创业 #未来`
+ ]
+
+ // 随机选择一条文案
+ const randomIndex = Math.floor(Math.random() * shareTexts.length)
+ const shareText = shareTexts[randomIndex]
+
+ wx.setClipboardData({
+ data: shareText,
+ success: () => {
+ wx.showModal({
+ title: '文案已复制',
+ content: '请打开微信朋友圈,粘贴分享文案,配合推广海报一起发布效果更佳!\n\n再次点击可获取新的随机文案',
+ showCancel: false,
+ confirmText: '去发朋友圈'
+ })
+ }
+ })
+ },
+
// 提现 - 直接到微信零钱
async handleWithdraw() {
const availableEarnings = this.data.availableEarningsNum || 0
@@ -548,10 +630,7 @@ Page({
wx.showToast({ title: '暂无可提现收益', icon: 'none' })
return
}
- if (availableEarnings < minWithdrawAmount) {
- wx.showToast({ title: `满${minWithdrawAmount}元可提现`, icon: 'none' })
- return
- }
+ // 任意金额可提现,不再设最低限额
// 未绑定微信号时引导去设置
if (!hasWechatId) {
@@ -567,16 +646,9 @@ Page({
return
}
- const withdrawFee = this.data.withdrawFee ?? 5
- const actualAmount = withdrawFee > 0
- ? Math.round(availableEarnings * (1 - withdrawFee / 100) * 100) / 100
- : availableEarnings
- const feeText = withdrawFee > 0
- ? `\n扣除 ${withdrawFee}% 手续费后,实际到账 ¥${actualAmount.toFixed(2)}`
- : ''
wx.showModal({
title: '确认提现',
- content: `申请提现 ¥${availableEarnings.toFixed(2)} 到您的微信零钱${feeText}`,
+ content: `将提现 ¥${availableEarnings.toFixed(2)} 到您的微信零钱`,
confirmText: '立即提现',
success: async (res) => {
if (!res.confirm) return
@@ -805,16 +877,27 @@ Page({
})
},
- // 分享 - 带自己的邀请码(与 app.getMyReferralCode 一致)
+ // 分享 - 带推荐码
onShareAppMessage() {
- const app = getApp()
- const ref = app.getMyReferralCode() || this.data.referralCode
+ console.log('[Referral] 分享给好友,推荐码:', this.data.referralCode)
return {
title: 'Soul创业派对 - 来自派对房的真实商业故事',
- path: ref ? `/pages/index/index?ref=${ref}` : '/pages/index/index'
+ path: `/pages/index/index?ref=${this.data.referralCode}`
+ // 不设置 imageUrl,使用小程序默认截图
+ // 如需自定义图片,请将图片放在 /assets/ 目录并配置路径
}
},
+ // 分享到朋友圈
+ onShareTimeline() {
+ console.log('[Referral] 分享到朋友圈,推荐码:', this.data.referralCode)
+ return {
+ title: `Soul创业派对 - 62个真实商业案例`,
+ query: `ref=${this.data.referralCode}`
+ // 不设置 imageUrl,使用小程序默认截图
+ }
+ },
+
goBack() {
wx.navigateBack()
},
diff --git a/miniprogram/pages/referral/referral.wxml b/miniprogram/pages/referral/referral.wxml
index a300cee5..38c587aa 100644
--- a/miniprogram/pages/referral/referral.wxml
+++ b/miniprogram/pages/referral/referral.wxml
@@ -6,9 +6,6 @@
-
-
-
分销中心
@@ -46,10 +43,9 @@
累计: ¥{{totalCommission}} | 待审核: ¥{{pendingWithdrawAmount}}
-
- {{availableEarningsNum < minWithdrawAmount ? '满' + minWithdrawAmount + '元可提现' : !hasWechatId ? '请先绑定微信号' : '申请提现 ¥' + availableEarnings}}
+
+ {{availableEarningsNum <= 0 ? '暂无可提现' : !hasWechatId ? '请先绑定微信号' : '申请提现 ¥' + availableEarnings}}
- 提现将扣除 {{withdrawFee}}% 手续费
为便于提现到账,请先到「设置」中绑定微信号
查看提现记录
@@ -177,6 +173,28 @@
+
+
+
+
+
+
+ 分享到朋友圈
+ 复制文案发朋友圈
+
+
+
+
+
+
+
+
+
+ 更多分享方式
+ 使用系统分享功能
+
+
+
@@ -215,7 +233,14 @@
-
+
+
+
+
+
+ 暂无收益记录
+ 分享专属链接,好友购买即可获得 {{shareRate}}% 返利
+
@@ -284,7 +309,7 @@
-
+