diff --git a/miniprogram/RESTORE-ANALYSIS.md b/miniprogram/RESTORE-ANALYSIS.md index bcca5be5..f8128099 100644 --- a/miniprogram/RESTORE-ANALYSIS.md +++ b/miniprogram/RESTORE-ANALYSIS.md @@ -31,24 +31,36 @@ - withdraw-records、addresses、addresses/edit - agreement、about、vip、member-detail -### 3. read.js 分享逻辑 +### 3. read.js 分享逻辑与 mid 支持 - 使用 `app.getMyReferralCode()` 替代 `userInfo?.referralCode || wx.getStorageSync('referralCode')` -- 保持 `onShareAppMessage`、`onShareTimeline` 行为不变 +- **mid 支持**:onLoad 支持 `options.scene`、`options.mid`;mid 有值无 id 时从 bookData 或 `/api/miniprogram/book/chapter/by-mid/:mid` 解析 id +- 分享 path 优先用 `mid=`(扫码/海报闭环),无则用 `id=`;API 返回 `res.mid` 时写入 `sectionMid` ### 4. API 路径修正 - `app.loadBookData`:`/api/book/all-chapters` → `/api/miniprogram/book/all-chapters` - `index.loadBookData`、`loadFeaturedFromServer`、`loadLatestChapters`:同上 - `chapters.loadDailyChapters`:同上 +- **VIP 接口**:`/api/vip/*` → `/api/miniprogram/vip/*`(vip.js、my.js、index.js、member-detail.js) -## 三、未改动项(保留甲方逻辑) +## 三、已处理项 -- **vip 相关接口**:`/api/vip/members`、`/api/vip/status`、`/api/vip/profile` 仍为原路径,未改为 `/api/miniprogram/*`(若 soul-api 无对应 miniprogram 接口,需后端补充) +- **vip 相关接口**:已改为 `/api/miniprogram/vip/*`(members、status、profile),符合项目边界。**后端需在 soul-api 的 miniprogram 组下挂对应路由**,可复用现有 handler。 - **页面结构**:保留 vip、member-detail,未引入 scan、profile-edit -## 四、后续建议 +## 四、后端待办 -1. **soul-api 路由**:确认 `/api/miniprogram/book/all-chapters` 已注册;若 vip 需在小程序使用,建议在 miniprogram 组下增加等价接口。 -2. **referral.js**:检查是否已使用 `app.getMyReferralCode()`,若仍用旧方式可统一替换。 -3. **read.js 的 mid 支持**:miniprogram2 的 read 支持 `mid` 参数(便于扫码直达),若需要可在 miniprogram 的 read 中补充 `sectionMid` 与 `getShareConfig` 的 mid 逻辑。 +soul-api 需在 miniprogram 组下增加以下 VIP 路由(可复用现有 handler): + +| 路径 | 方法 | 用途 | +|------|------|------| +| `/api/miniprogram/vip/status` | GET | 查询用户 VIP 状态 | +| `/api/miniprogram/vip/profile` | GET/POST | 获取/更新 VIP 资料 | +| `/api/miniprogram/vip/members` | GET | 获取 VIP 会员列表(首页超级个体、会员详情) | + +## 五、后续建议 + +1. **soul-api 路由**:确认 `/api/miniprogram/book/all-chapters` 已注册;VIP 接口见「四、后端待办」。 +2. **referral.js**:已统一使用 `app.getMyReferralCode()` 作为回退。 +3. **read.js mid 支持**:已完成。若 soul-api 未提供 `/api/miniprogram/book/chapter/by-mid/:mid`,扫码带 mid 时需依赖 bookData 已加载完成;建议后端补充 by-mid 接口。 diff --git a/miniprogram/app.js b/miniprogram/app.js index c88ab908..1a92cf58 100644 --- a/miniprogram/app.js +++ b/miniprogram/app.js @@ -8,7 +8,10 @@ const { parseScene } = require('./utils/scene.js') App({ globalData: { // API基础地址 - 连接真实后端 - baseUrl: 'https://soul.quwanzhi.com', + // baseUrl: 'https://soulapi.quwanzhi.com', + // baseUrl: 'https://souldev.quwanzhi.com', + baseUrl: 'http://localhost:8080', + // 小程序配置 - 真实AppID appId: 'wxb8bbb2b10dec74aa', diff --git a/miniprogram/pages/index/index.js b/miniprogram/pages/index/index.js index 75154d66..9f9a27c1 100644 --- a/miniprogram/pages/index/index.js +++ b/miniprogram/pages/index/index.js @@ -120,7 +120,7 @@ Page({ // 优先加载VIP会员 let members = [] try { - const res = await app.request({ url: '/api/vip/members', silent: true }) + const res = await app.request({ url: '/api/miniprogram/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 || '会员', diff --git a/miniprogram/pages/member-detail/member-detail.js b/miniprogram/pages/member-detail/member-detail.js index a70cc4fc..8eeb6396 100644 --- a/miniprogram/pages/member-detail/member-detail.js +++ b/miniprogram/pages/member-detail/member-detail.js @@ -17,7 +17,7 @@ Page({ async loadMember(id) { try { - const res = await app.request({ url: `/api/vip/members?id=${id}`, silent: true }) + const res = await app.request({ url: `/api/miniprogram/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 } diff --git a/miniprogram/pages/my/my.js b/miniprogram/pages/my/my.js index dd659d65..e7c92b46 100644 --- a/miniprogram/pages/my/my.js +++ b/miniprogram/pages/my/my.js @@ -641,7 +641,7 @@ Page({ const userId = app.globalData.userInfo?.id if (!userId) return try { - const res = await app.request({ url: `/api/vip/status?userId=${userId}`, silent: true }) + const res = await app.request({ url: `/api/miniprogram/vip/status?userId=${userId}`, silent: true }) if (res?.success) { this.setData({ isVip: res.data?.isVip, vipExpireDate: res.data?.expireDate || '' }) } diff --git a/miniprogram/pages/read/read.js b/miniprogram/pages/read/read.js index d9e7cee9..9b0aa041 100644 --- a/miniprogram/pages/read/read.js +++ b/miniprogram/pages/read/read.js @@ -12,6 +12,7 @@ import accessManager from '../../utils/chapterAccessManager' import readingTracker from '../../utils/readingTracker' +const { parseScene } = require('../../utils/scene.js') const app = getApp() @@ -66,37 +67,67 @@ Page({ isGeneratingPoster: false, // 免费章节 - freeIds: ['preface', 'epilogue', '1.1', 'appendix-1', 'appendix-2', 'appendix-3'] + freeIds: ['preface', 'epilogue', '1.1', 'appendix-1', 'appendix-2', 'appendix-3'], + + // 章节 mid(扫码/海报分享用,便于分享 path 带 mid) + sectionMid: null }, async onLoad(options) { - const { id, ref } = options - + // 支持 scene(扫码)、mid、id、ref + const sceneStr = (options && options.scene) || '' + const parsed = parseScene(sceneStr) + const mid = options.mid ? parseInt(options.mid, 10) : (parsed.mid || app.globalData.initialSectionMid || 0) + let 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 + + // mid 有值但无 id 时,从 bookData 或 API 解析 id + if (mid && !id) { + const bookData = app.globalData.bookData || [] + const ch = bookData.find(c => c.mid == mid || (c.mid && Number(c.mid) === Number(mid))) + if (ch?.id) { + id = ch.id + } else { + try { + const chRes = await app.request({ url: `/api/miniprogram/book/chapter/by-mid/${mid}`, silent: true }) + if (chRes && chRes.id) id = chRes.id + } catch (e) { + console.warn('[Read] by-mid 解析失败:', e) + } + } + } + + if (!id) { + wx.showToast({ title: '章节参数缺失', icon: 'none' }) + this.setData({ accessState: 'error', loading: false }) + return + } + this.setData({ statusBarHeight: app.globalData.statusBarHeight, navBarHeight: app.globalData.navBarHeight, sectionId: id, + sectionMid: mid || null, loading: true, accessState: 'unknown' }) - - // 处理推荐码绑定(异步不阻塞) + if (ref) { console.log('[Read] 检测到推荐码:', ref) wx.setStorageSync('referral_code', ref) app.handleReferralCode({ query: { ref } }) } - + try { - // 【标准流程】1. 拉取最新配置(免费列表、价格) const config = await accessManager.fetchLatestConfig() this.setData({ freeIds: config.freeChapters, sectionPrice: config.prices?.section ?? 1, fullBookPrice: config.prices?.fullbook ?? 9.9 }) - - // 【标准流程】2. 确定权限状态 + const accessState = await accessManager.determineAccessState(id, config.freeChapters) const canAccess = accessManager.canAccessFullContent(accessState) @@ -175,14 +206,15 @@ Page({ if (res && res.content) { const lines = res.content.split('\n').filter(line => line.trim()) const previewCount = Math.ceil(lines.length * 0.2) - - this.setData({ + const updates = { 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)) { @@ -454,31 +486,28 @@ Page({ }) }, - // 分享到微信 - 自动带分享人ID(统一使用 app.getMyReferralCode) + // 分享到微信 - 自动带分享人ID;优先用 mid(扫码/海报闭环),无则用 id onShareAppMessage() { - const { section, sectionId } = this.data + const { section, sectionId, sectionMid } = this.data const ref = app.getMyReferralCode() - - // 分享标题优化 - const shareTitle = section?.title + const q = sectionMid ? `mid=${sectionMid}` : `id=${sectionId}` + const shareTitle = section?.title ? `📚 ${section.title.length > 20 ? section.title.slice(0, 20) + '...' : section.title}` : '📚 Soul创业派对 - 真实商业故事' - return { title: shareTitle, - path: `/pages/read/read?id=${sectionId}${ref ? '&ref=' + ref : ''}`, - imageUrl: '/assets/share-cover.png' // 可配置分享封面图 + path: ref ? `/pages/read/read?${q}&ref=${ref}` : `/pages/read/read?${q}`, + imageUrl: '/assets/share-cover.png' } }, - - // 分享到朋友圈 + onShareTimeline() { - const { section, sectionId } = this.data + const { section, sectionId, sectionMid } = this.data const ref = app.getMyReferralCode() - + const q = sectionMid ? `mid=${sectionMid}` : `id=${sectionId}` return { title: `${section?.title || 'Soul创业派对'} - 来自派对房的真实故事`, - query: `id=${sectionId}${ref ? '&ref=' + ref : ''}` + query: ref ? `${q}&ref=${ref}` : q } }, diff --git a/miniprogram/pages/vip/vip.js b/miniprogram/pages/vip/vip.js index 958da1a5..f28cf200 100644 --- a/miniprogram/pages/vip/vip.js +++ b/miniprogram/pages/vip/vip.js @@ -33,7 +33,7 @@ Page({ const userId = app.globalData.userInfo?.id if (!userId) return try { - const res = await app.request({ url: `/api/vip/status?userId=${userId}`, silent: true }) + const res = await app.request({ url: `/api/miniprogram/vip/status?userId=${userId}`, silent: true }) if (res?.success) { const d = res.data let expStr = '' @@ -54,7 +54,7 @@ Page({ async loadProfile(userId) { try { - const res = await app.request(`/api/vip/profile?userId=${userId}`) + const res = await app.request(`/api/miniprogram/vip/profile?userId=${userId}`) if (res?.success) this.setData({ profile: res.data }) } catch (e) { console.log('[VIP] 资料加载失败', e) } }, @@ -120,7 +120,7 @@ Page({ if (!userId) return const p = this.data.profile try { - const res = await app.request('/api/vip/profile', { + const res = await app.request('/api/miniprogram/vip/profile', { method: 'POST', data: { userId, name: p.name, project: p.project, contact: p.contact, bio: p.bio } }) if (res?.success) wx.showToast({ title: '资料已保存', icon: 'success' })