From 601044ec606cc301804b34781ce04a10e12d1d49 Mon Sep 17 00:00:00 2001 From: Alex-larget <33240357+Alex-larget@users.noreply.github.com> Date: Tue, 17 Mar 2026 12:15:08 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E6=96=BD=E7=BE=8E=E5=9B=A2=E5=BC=8F?= =?UTF-8?q?=E7=9A=84=E6=94=AF=E4=BB=98=E6=B5=81=E7=A8=8B=EF=BC=8C=E5=B9=B6?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E7=9B=B8=E5=85=B3=E5=8A=9F=E8=83=BD=20-=20?= =?UTF-8?q?=E5=B0=86=E6=94=AF=E4=BB=98=E6=B5=81=E7=A8=8B=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E8=87=B3=E7=A4=BC=E5=93=81=E6=94=AF=E4=BB=98=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=EF=BC=8C=E7=A6=81=E6=AD=A2=E4=BB=8E=E9=98=85=E8=AF=BB=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E8=BF=9B=E8=A1=8C=E6=94=AF=E4=BB=98=EF=BC=8C=E4=BB=A5?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=94=A8=E6=88=B7=E4=BD=93=E9=AA=8C=E3=80=82?= =?UTF-8?q?=20-=20=E6=9B=B4=E6=96=B0=E4=BA=86=E7=A4=BC=E7=89=A9=E6=94=AF?= =?UTF-8?q?=E4=BB=98=E8=AF=A6=E6=83=85=E9=A1=B5=E9=9D=A2=EF=BC=8C=E4=B8=BA?= =?UTF-8?q?=E5=8F=91=E8=B5=B7=E4=BA=BA=E5=92=8C=E6=9C=8B=E5=8F=8B=E5=B1=95?= =?UTF-8?q?=E7=A4=BA=E4=BA=86=E4=B8=8D=E5=90=8C=E7=9A=84=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E7=95=8C=E9=9D=A2=E5=85=83=E7=B4=A0=EF=BC=8C=E5=8C=85=E6=8B=AC?= =?UTF-8?q?=E4=B8=BA=E5=8F=91=E8=B5=B7=E4=BA=BA=E6=8F=90=E4=BE=9B=E7=9A=84?= =?UTF-8?q?=E5=88=86=E4=BA=AB=E6=8C=89=E9=92=AE=E5=92=8C=E4=B8=BA=E6=9C=8B?= =?UTF-8?q?=E5=8F=8B=E6=8F=90=E4=BE=9B=E7=9A=84=E6=94=AF=E4=BB=98=E6=8C=89?= =?UTF-8?q?=E9=92=AE=E3=80=82=20-=20=E5=A2=9E=E5=BC=BA=E4=BA=86=E5=90=8E?= =?UTF-8?q?=E7=AB=AF=E9=80=BB=E8=BE=91=EF=BC=8C=E4=BB=A5=E7=A1=AE=E4=BF=9D?= =?UTF-8?q?=E5=9C=A8=E6=94=AF=E4=BB=98=E5=A4=84=E7=90=86=E8=BF=87=E7=A8=8B?= =?UTF-8?q?=E4=B8=AD=E6=AD=A3=E7=A1=AE=E5=B0=86=E6=94=B6=E7=9B=8A=E5=BD=92?= =?UTF-8?q?=E5=9B=A0=E4=BA=8E=E5=8F=91=E8=B5=B7=E4=BA=BA=E3=80=82=20-=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E6=AF=8F=E6=97=A5=E7=AB=A0=E8=8A=82?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=EF=BC=8C=E5=B9=B6=E6=94=B9=E8=BF=9B=E4=BA=86?= =?UTF-8?q?=E7=AB=A0=E8=8A=82=E9=A1=B5=E9=9D=A2=E7=9A=84=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E7=8A=B6=E6=80=81=EF=BC=8C=E4=BB=A5=E6=8F=90=E5=8D=87=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E4=BA=A4=E4=BA=92=E4=BD=93=E9=AA=8C=E3=80=82=20-=20?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86=E6=96=87=E6=A1=A3=EF=BC=8C=E4=BB=A5?= =?UTF-8?q?=E5=8F=8D=E6=98=A0=E6=96=B0=E7=9A=84=E6=94=AF=E4=BB=98=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E5=92=8C=E7=9B=B8=E5=85=B3=E5=8F=98=E6=9B=B4=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agent/后端工程师/evolution/2026-03-17.md | 28 +++ .cursor/agent/后端工程师/evolution/索引.md | 1 + .cursor/agent/团队/evolution/2026-03-17.md | 21 +++ .cursor/agent/团队/evolution/索引.md | 1 + .../小程序开发工程师/evolution/2026-03-17.md | 40 ++++ .../agent/小程序开发工程师/evolution/索引.md | 1 + .cursor/agent/开发助理/经验清单.md | 4 +- .cursor/agent/开发助理/项目索引/助理橙子.md | 5 +- .cursor/agent/开发助理/项目索引/后端.md | 3 +- .cursor/agent/开发助理/项目索引/团队.md | 3 +- .cursor/agent/开发助理/项目索引/小程序.md | 5 +- miniprogram/pages/chapters/chapters.js | 34 +++- miniprogram/pages/chapters/chapters.wxml | 26 +++ miniprogram/pages/chapters/chapters.wxss | 83 +++++++++ miniprogram/pages/gift-pay/detail.js | 7 +- miniprogram/pages/gift-pay/detail.wxml | 46 +++-- miniprogram/pages/gift-pay/detail.wxss | 172 ++++++++++++++---- miniprogram/pages/gift-pay/list.js | 18 +- miniprogram/pages/gift-pay/list.wxml | 4 +- miniprogram/pages/read/read.js | 65 ++----- miniprogram/pages/read/read.wxml | 17 -- miniprogram/project.private.config.json | 11 +- soul-api/internal/handler/gift_pay.go | 51 +++++- soul-api/internal/handler/miniprogram.go | 31 ++-- 开发文档/10、项目管理/运营与变更.md | 92 ++++++++++ 开发文档/1、需求/需求汇总.md | 1 + 开发文档/README.md | 1 + 开发文档/代付功能-美团式方案与场景清单.md | 31 +++- 开发文档/找朋友代付-流程与配置.md | 148 ++++++--------- 开发文档/迁移完成度与待办清单.md | 110 +++++++++++ 30 files changed, 822 insertions(+), 238 deletions(-) create mode 100644 .cursor/agent/后端工程师/evolution/2026-03-17.md create mode 100644 .cursor/agent/团队/evolution/2026-03-17.md create mode 100644 .cursor/agent/小程序开发工程师/evolution/2026-03-17.md create mode 100644 开发文档/迁移完成度与待办清单.md diff --git a/.cursor/agent/后端工程师/evolution/2026-03-17.md b/.cursor/agent/后端工程师/evolution/2026-03-17.md new file mode 100644 index 00000000..03cbe02e --- /dev/null +++ b/.cursor/agent/后端工程师/evolution/2026-03-17.md @@ -0,0 +1,28 @@ +# 后端 - 2026-03-17 + +## 代付 PayNotify 权益归属修复 + +### 问题 + +代付支付回调中,`buyerUserID` 由 openID 解析得到,即**代付人**。权益激活(全书、VIP、章节、余额充值)和分佣均用 `buyerUserID`,导致权益错误给到代付人,而非发起人。 + +### 修复 + +引入 `beneficiaryUserID`(权益归属人): + +- **代付订单**:`beneficiaryUserID = order.UserID`(发起人) +- **普通订单**:`beneficiaryUserID = buyerUserID`(付款人) + +权益激活、分佣、取消未支付订单等逻辑统一改用 `beneficiaryUserID`。 + +### 经验 + +- 代付场景:`order.user_id` = 发起人,`payer_user_id` = 代付人;权益与分佣必须按 `order.user_id` 处理 +- PayNotify 中 openID 解析得到的是实际付款人,代付时需以 order 的 user_id 为权益归属 + +--- + +## gift-pay detail 返回 initiatorUserId + +- 供小程序区分发起人/好友,展示不同 UI +- 字段:`initiatorUserId`(发起人 user_id) diff --git a/.cursor/agent/后端工程师/evolution/索引.md b/.cursor/agent/后端工程师/evolution/索引.md index ce510439..598379a7 100644 --- a/.cursor/agent/后端工程师/evolution/索引.md +++ b/.cursor/agent/后端工程师/evolution/索引.md @@ -8,3 +8,4 @@ | 2026-03-12 | persons token 字段与 DB 迁移;CKBLead 用 token 兑换 ckb_api_key | [2026-03-12.md](./2026-03-12.md) | | 2026-03-14 | 内容排名算法修正:排名分公式(阅读/新度/付款前 N 名),支持 hot_score 手动覆盖 | [2026-03-14.md](./2026-03-14.md) | | 2026-03-16 | ParseAutoLinkContent data-label;存客宝 create planType/sceneId/status | [2026-03-16.md](./2026-03-16.md) | +| 2026-03-17 | 代付 PayNotify beneficiaryUserID 权益归发起人;gift-pay detail 返回 initiatorUserId | [2026-03-17.md](./2026-03-17.md) | diff --git a/.cursor/agent/团队/evolution/2026-03-17.md b/.cursor/agent/团队/evolution/2026-03-17.md new file mode 100644 index 00000000..4f47f556 --- /dev/null +++ b/.cursor/agent/团队/evolution/2026-03-17.md @@ -0,0 +1,21 @@ +# 团队 - 2026-03-17 + +## 代付美团式流程与权益归属约定 + +### 流程约定 + +1. **入口**:读页「找好友代付」→ 创建请求 → **跳转代付详情页**(不再弹窗) +2. **代付页**:发起人看到「分享给好友」,好友看到「帮他付款」 +3. **后端**:detail 返回 `initiatorUserId`,前端据此区分 + +### 权益与分佣约定 + +- 代付订单:`order.user_id` = 发起人,`payer_user_id` = 代付人 +- 权益(全书、VIP、章节、余额充值)归属发起人 +- 分佣按发起人的推荐关系计算 +- PayNotify 中 openID 解析得到的是代付人,权益与分佣必须用 `order.user_id` + +### 同时影响 + +- 小程序:代付详情页双态 UI、读页跳转 +- 后端:PayNotify beneficiaryUserID、detail initiatorUserId diff --git a/.cursor/agent/团队/evolution/索引.md b/.cursor/agent/团队/evolution/索引.md index f5714eaf..9d0b01f5 100644 --- a/.cursor/agent/团队/evolution/索引.md +++ b/.cursor/agent/团队/evolution/索引.md @@ -9,3 +9,4 @@ | 2026-03-12 | 密钥/token 设计:关联小程序 key、@ 人物 token,不暴露真实密钥、服务端兑换 | [2026-03-12.md](./2026-03-12.md) | | 2026-03-14 | 内容排名算法跨端复用:管理端内容排行与小程序精选推荐共用 computeArticleRankingSections | [2026-03-14.md](./2026-03-14.md) | | 2026-03-16 | TipTap Mention 需 data-label,否则显示 token | [2026-03-16.md](./2026-03-16.md) | +| 2026-03-17 | 代付美团式流程与权益归属约定:读页→代付页→分享;权益/分佣归发起人 | [2026-03-17.md](./2026-03-17.md) | diff --git a/.cursor/agent/小程序开发工程师/evolution/2026-03-17.md b/.cursor/agent/小程序开发工程师/evolution/2026-03-17.md new file mode 100644 index 00000000..ee4aa5e0 --- /dev/null +++ b/.cursor/agent/小程序开发工程师/evolution/2026-03-17.md @@ -0,0 +1,40 @@ +# 小程序 - 2026-03-17 + +## 代付美团式流程改造 + +### 场景 + +用户希望代付流程类似美团:先进入代付页面,再分享给好友,而非在阅读页弹窗分享。 + +### 实现 + +1. **读页「找好友代付」**:创建请求后 `wx.navigateTo` 跳转 `/pages/gift-pay/detail?requestSn=xxx`,移除弹窗 +2. **代付详情页双态**: + - 发起人(`detail.initiatorUserId === 当前用户ID`):标题「找朋友代付」,主按钮「分享给好友」(`open-type="share"`) + - 好友:标题「帮他付款」,主按钮「帮他付款」 +3. **后端**:detail 接口返回 `initiatorUserId` 供前端区分 + +### 经验 + +- 代付分享入口:优先「进入代付页再分享」而非「弹窗分享」,符合用户心智 +- 同一页面根据 `initiatorUserId` 与当前用户比对,展示不同 UI + +--- + +## 代付统一到代付页(禁止阅读页代付) + +### 规则 + +代付必须在代付页完成,禁止在阅读页代付。代付页可显示部分文章信息。 + +### 实现 + +- 读页:`gift=1&ref=requestSn` 打开时,`redirectTo` 到 gift-pay/detail,不展示阅读页 +- 代付页:已展示 description(章节标题等)、金额、发起人昵称 + +--- + +## 目录页 loading、首页最新新增 + +- 目录页:`book/parts` 加载时 `partsLoading: true`,展示旋转圈 +「加载目录中...」 +- 首页最新新增:默认 5 条、折叠状态,`>5` 时显示「展开更多」 diff --git a/.cursor/agent/小程序开发工程师/evolution/索引.md b/.cursor/agent/小程序开发工程师/evolution/索引.md index 90b5d8fe..7d9bc89c 100644 --- a/.cursor/agent/小程序开发工程师/evolution/索引.md +++ b/.cursor/agent/小程序开发工程师/evolution/索引.md @@ -12,3 +12,4 @@ | 2026-03-12 | 链接标签 mpKey、@ 人物 token 兑换:contentParser、onLinkTagTap、onMentionTap | [2026-03-12.md](./2026-03-12.md) | | 2026-03-14 | 我的页设置入口隐藏;资料修改引导场景梳理(登录后、@某人、找伙伴、链接卡若) | [2026-03-14.md](./2026-03-14.md) | | 2026-03-16 | 编辑资料页分享名片:转发/朋友圈特殊处理,Canvas 绘制封面,标题「昵称+为您分享名片」 | [2026-03-16.md](./2026-03-16.md) | +| 2026-03-17 | 代付美团式:读页→代付页→分享;详情页双态(发起人/好友);目录 loading、最新新增 5 条折叠 | [2026-03-17.md](./2026-03-17.md) | diff --git a/.cursor/agent/开发助理/经验清单.md b/.cursor/agent/开发助理/经验清单.md index a9fcf77f..d41079ff 100644 --- a/.cursor/agent/开发助理/经验清单.md +++ b/.cursor/agent/开发助理/经验清单.md @@ -48,6 +48,8 @@ | 2026-03-16 | 软件测试 | 配置约定 | testing SKILL | pytest 架构、配置从 soul-api/.env* 读取、SOUL_TEST_ENV 必显;运行前报告头部显示测试环境,避免误测正式库 | | 2026-03-16 | 小程序 | 最佳实践 | miniprogram-dev SKILL §10 | 编辑资料页分享名片:转发/朋友圈特殊处理,Canvas 绘制 5:4 封面,标题「昵称+为您分享名片」,路径 member-detail | | 2026-03-16 | 开发助理 | 交互习惯分析 | - | 乘风读取 agent-transcripts 抽样分析:角色触发词、表达方式、工作流程、沟通风格、技术偏好、Agent 响应建议 | +| 2026-03-17 | 小程序、后端、团队 | 业务规则/bug 修复 | - | 代付美团式:读页→代付页→分享;PayNotify beneficiaryUserID 权益归发起人;detail 返回 initiatorUserId;目录 loading、最新新增 5 条折叠 | +| 2026-03-17 | 小程序 | 业务规则 | - | 代付统一到代付页:gift=1&ref 打开 read 时 redirectTo 代付页,禁止在阅读页代付 | --- @@ -58,4 +60,4 @@ --- -**最后更新**:2026-03-16(编辑资料页分享名片) +**最后更新**:2026-03-17(代付美团式与 PayNotify 完善) diff --git a/.cursor/agent/开发助理/项目索引/助理橙子.md b/.cursor/agent/开发助理/项目索引/助理橙子.md index a903edec..657a7496 100644 --- a/.cursor/agent/开发助理/项目索引/助理橙子.md +++ b/.cursor/agent/开发助理/项目索引/助理橙子.md @@ -19,9 +19,12 @@ | 2026-03-11 | 会议收尾:开发团队对齐业务逻辑与以界面定需求;纪要生成、各角色经验入库、项目索引与会议索引更新 | 已完成 | | 2026-03-16 | 乘风发起例行开发进度同步 | 已完成 | | 2026-03-16 | 乘风读取 agent-transcripts 分析交互习惯,总结经验并吸收 | 已完成 | +| 2026-03-17 | 吸收需求:代付美团式流程、PayNotify 权益归属、目录 loading、最新新增 5 条折叠 → 开发文档与 agent | 已完成 | +| 2026-03-17 | 乘风吸收经验与交互:迁移完成度与待办清单、运营与变更第十二部分 | 已完成 | +| 2026-03-17 | 吸收新需求:代付统一到代付页(gift=1&ref redirectTo)→ 需求汇总、找朋友代付流程、运营与变更 | 已完成 | > **格式说明**:每次开发后在此追加一行,日期格式 YYYY-MM-DD,状态用:已完成 / 进行中 / 待续 / 搁置 --- -**最后更新**:2026-03-16(交互习惯分析) +**最后更新**:2026-03-17(吸收代付与体验优化需求) diff --git a/.cursor/agent/开发助理/项目索引/后端.md b/.cursor/agent/开发助理/项目索引/后端.md index c25faa95..eb6e4df1 100644 --- a/.cursor/agent/开发助理/项目索引/后端.md +++ b/.cursor/agent/开发助理/项目索引/后端.md @@ -31,9 +31,10 @@ soul-api(Go + Gin + GORM + MySQL)提供三组路由:`/api/miniprogram/*` | 2026-03-16 | 乘风发起例行开发进度同步 | 已完成 | | 2026-03-16 | ParseAutoLinkContent 添加 data-label;存客宝 create planType=1 sceneId=9 status=1 | 已完成 | | 2026-03-16 | 会议:new-soul 新需求与当前项目差异分析;content_upload.py 与 chapters 一致性待核对 | 待续 | +| 2026-03-17 | 代付 PayNotify 权益归属修复:beneficiaryUserID(代付=发起人);gift-pay detail 返回 initiatorUserId | 已完成 | > **格式说明**:每次开发后在此追加一行,日期格式 YYYY-MM-DD,状态用:已完成 / 进行中 / 待续 / 搁置 --- -**最后更新**:2026-03-16 +**最后更新**:2026-03-17 diff --git a/.cursor/agent/开发助理/项目索引/团队.md b/.cursor/agent/开发助理/项目索引/团队.md index d13a4159..068c8974 100644 --- a/.cursor/agent/开发助理/项目索引/团队.md +++ b/.cursor/agent/开发助理/项目索引/团队.md @@ -27,9 +27,10 @@ Soul 创业派对全项目架构与约定:路由隔离(miniprogram/admin/db | 2026-03-14 | 内容排名算法跨端复用:管理端内容排行与小程序精选推荐共用 computeArticleRankingSections,排名分公式、权重配置统一 | 已完成 | | 2026-03-16 | 乘风发起例行开发进度同步 | 已完成 | | 2026-03-16 | TipTap Mention 需 data-label 规则;链接人与事与存客宝对接优化会议收尾 | 已完成 | +| 2026-03-17 | 代付美团式流程与权益归属约定:读页→代付页→分享;权益/分佣归发起人;PayNotify beneficiaryUserID | 已完成 | > **格式说明**:每次架构级讨论后在此追加一行,日期格式 YYYY-MM-DD --- -**最后更新**:2026-03-16 +**最后更新**:2026-03-17 diff --git a/.cursor/agent/开发助理/项目索引/小程序.md b/.cursor/agent/开发助理/项目索引/小程序.md index 348c77b5..b48686de 100644 --- a/.cursor/agent/开发助理/项目索引/小程序.md +++ b/.cursor/agent/开发助理/项目索引/小程序.md @@ -34,9 +34,12 @@ | 2026-03-16 | 乘风发起例行开发进度同步 | 已完成 | | 2026-03-16 | 编辑资料页分享名片:转发/朋友圈改为分享名片,Canvas 封面(头像+昵称+四栏信息),路径 member-detail | 已完成 | | 2026-03-16 | 会议:new-soul 新需求与当前项目差异分析;派对AI 小程序站管理与当前一致 | 已完成 | +| 2026-03-17 | 代付美团式:读页→创建请求→跳转代付详情页;详情页双态(发起人分享/好友帮他付款);目录 loading、首页最新新增 5 条折叠 | 已完成 | +| 2026-03-17 | 代付统一到代付页:gift=1&ref 打开 read 时 redirectTo 代付页,禁止在阅读页代付 | 已完成 | +| 2026-03-17 | 代付页营销:章节标题+20%内容预览;我的代付列表点击进详情;页面协调 | 已完成 | > **格式说明**:每次开发后在此追加一行,日期格式 YYYY-MM-DD,状态用:已完成 / 进行中 / 待续 / 搁置 --- -**最后更新**:2026-03-16 +**最后更新**:2026-03-17 diff --git a/miniprogram/pages/chapters/chapters.js b/miniprogram/pages/chapters/chapters.js index 7e24bc5d..16a10a8f 100644 --- a/miniprogram/pages/chapters/chapters.js +++ b/miniprogram/pages/chapters/chapters.js @@ -6,6 +6,7 @@ */ const app = getApp() +const { trackClick } = require('../../utils/trackClick') Page({ data: { @@ -55,6 +56,7 @@ Page({ this.updateUserStatus() this.loadVipStatus() this.loadParts() + this.loadDailyChapters() }, // 懒加载:仅拉取篇章列表 + totalSections + fixedSections @@ -174,7 +176,34 @@ Page({ }, onPullDownRefresh() { - this.loadParts().then(() => wx.stopPullDownRefresh()).catch(() => wx.stopPullDownRefresh()) + Promise.all([this.loadParts(), this.loadDailyChapters()]) + .then(() => wx.stopPullDownRefresh()) + .catch(() => wx.stopPullDownRefresh()) + }, + + // 每日新增:用 latest-chapters 接口,展示最近更新章节 + async loadDailyChapters() { + try { + const res = await app.request({ url: '/api/miniprogram/book/latest-chapters', silent: true }) + const list = (res && res.data) ? res.data : [] + const pt = (c) => (c.partTitle || c.part_title || '').toLowerCase() + const exclude = c => !pt(c).includes('序言') && !pt(c).includes('尾声') && !pt(c).includes('附录') + const daily = list + .filter(exclude) + .slice(0, 10) + .map(c => { + const d = new Date(c.updatedAt || c.updated_at || Date.now()) + const title = c.section_title || c.sectionTitle || c.title || c.chapterTitle || '' + return { + id: c.id, + mid: c.mid ?? c.MID ?? 0, + title, + price: c.price ?? 1, + dateStr: `${d.getMonth() + 1}/${d.getDate()}` + } + }) + this.setData({ dailyChapters: daily }) + } catch (e) { console.log('[Chapters] 加载每日新增失败:', e) } }, onShow() { @@ -219,6 +248,7 @@ Page({ // 切换展开状态,展开时懒加载该篇章章节 async togglePart(e) { + trackClick('chapters', 'tab_click', e.currentTarget.dataset.id || '篇章') const partId = e.currentTarget.dataset.id const isExpanding = this.data.expandedPart !== partId this.setData({ @@ -231,6 +261,7 @@ Page({ goToRead(e) { const id = e.currentTarget.dataset.id const mid = e.currentTarget.dataset.mid || app.getSectionMid(id) + trackClick('chapters', 'card_click', id || '章节') const q = mid ? `mid=${mid}` : `id=${id}` wx.navigateTo({ url: `/pages/read/read?${q}` }) }, @@ -249,6 +280,7 @@ Page({ // 跳转到搜索页 goToSearch() { + trackClick('chapters', 'nav_click', '搜索') wx.navigateTo({ url: '/pages/search/search' }) }, diff --git a/miniprogram/pages/chapters/chapters.wxml b/miniprogram/pages/chapters/chapters.wxml index 996d0e81..2daf930f 100644 --- a/miniprogram/pages/chapters/chapters.wxml +++ b/miniprogram/pages/chapters/chapters.wxml @@ -40,6 +40,31 @@ + + + + 每日新增 + +{{dailyChapters.length}} + + + + + + {{item.title}} + {{item.dateStr}} · ¥{{item.price}} + + + + + + @@ -83,6 +108,7 @@ {{section.isFree || isVip || (!section.isPremium && hasFullBook) || purchasedSections.indexOf(section.id) > -1 ? '○' : '●'}} {{section.id}} {{section.title}} NEW + 增值 免费 diff --git a/miniprogram/pages/chapters/chapters.wxss b/miniprogram/pages/chapters/chapters.wxss index 7ae8ac8b..dbf5d2b7 100644 --- a/miniprogram/pages/chapters/chapters.wxss +++ b/miniprogram/pages/chapters/chapters.wxss @@ -174,6 +174,89 @@ box-sizing: border-box; } +/* ===== 每日新增 ===== */ +.daily-section { + margin-bottom: 32rpx; + padding: 24rpx; + background: #1c1c1e; + border-radius: 24rpx; + border: 2rpx solid rgba(255, 255, 255, 0.05); +} + +.daily-header { + display: flex; + align-items: center; + gap: 16rpx; + margin-bottom: 24rpx; +} + +.daily-title { + font-size: 30rpx; + font-weight: 600; + color: #ffffff; +} + +.daily-badge { + font-size: 22rpx; + padding: 4rpx 12rpx; + background: #F6AD55; + color: #ffffff; + border-radius: 20rpx; +} + +.daily-list { + display: flex; + flex-direction: column; + gap: 0; +} + +.daily-item { + display: flex; + align-items: center; + padding: 16rpx 0; + border-bottom: 1rpx solid rgba(255, 255, 255, 0.06); +} + +.daily-item:last-child { + border-bottom: none; +} + +.daily-dot { + width: 12rpx; + height: 12rpx; + border-radius: 50%; + background: rgba(0, 206, 209, 0.6); + margin-right: 20rpx; + flex-shrink: 0; +} + +.daily-content { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 4rpx; +} + +.daily-item-title { + font-size: 26rpx; + color: #ffffff; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.daily-item-meta { + font-size: 22rpx; + color: rgba(255, 255, 255, 0.4); +} + +.daily-arrow { + font-size: 28rpx; + color: rgba(255, 255, 255, 0.4); + margin-left: 16rpx; +} + /* ===== 章节项 ===== */ .chapter-item { display: flex; diff --git a/miniprogram/pages/gift-pay/detail.js b/miniprogram/pages/gift-pay/detail.js index 8f3ab2af..427c5b09 100644 --- a/miniprogram/pages/gift-pay/detail.js +++ b/miniprogram/pages/gift-pay/detail.js @@ -10,7 +10,8 @@ Page({ requestSn: '', detail: null, loading: true, - paying: false + paying: false, + isInitiator: false // 是否发起人,发起人看到「分享给好友」UI,好友看到「帮他付款」 }, onLoad(options) { @@ -32,7 +33,9 @@ Page({ try { const res = await app.request(`/api/miniprogram/gift-pay/detail?requestSn=${encodeURIComponent(requestSn)}`) if (res && res.success) { - this.setData({ detail: res, loading: false }) + const myId = app.globalData.userInfo?.id || '' + const isInitiator = !!myId && res.initiatorUserId === myId + this.setData({ detail: res, loading: false, isInitiator }) } else { this.setData({ loading: false }) wx.showToast({ title: res?.error || '加载失败', icon: 'none' }) diff --git a/miniprogram/pages/gift-pay/detail.wxml b/miniprogram/pages/gift-pay/detail.wxml index 8ca99450..08e5b1f5 100644 --- a/miniprogram/pages/gift-pay/detail.wxml +++ b/miniprogram/pages/gift-pay/detail.wxml @@ -1,4 +1,4 @@ - + @@ -6,7 +6,7 @@ - 帮他付款 + {{isInitiator ? '找朋友代付' : '帮他付款'}} @@ -20,15 +20,22 @@ + + + {{detail.sectionTitle || detail.description || '代付商品'}} + {{detail.contentPreview}} + - 代付订单 - {{detail.initiatorNickname || '好友'}} 请你帮忙付款 + 代付订单 + {{detail.initiatorNickname || '好友'}} 请你帮忙付款 + 分享给好友,好友帮你付款 + - + 商品 - {{detail.description || '-'}} + {{detail.sectionTitle || detail.description || '-'}} 金额 @@ -36,12 +43,27 @@ - - 付款后,{{detail.initiatorNickname || '好友'}}将获得对应权益 - - + + + + 💡 + 分享给好友,好友打开后点击「帮他付款」即可为你代付 + + + + + + + + 付款后,{{detail.initiatorNickname || '好友'}}将获得对应权益 + + + diff --git a/miniprogram/pages/gift-pay/detail.wxss b/miniprogram/pages/gift-pay/detail.wxss index 69c07d09..94dee09d 100644 --- a/miniprogram/pages/gift-pay/detail.wxss +++ b/miniprogram/pages/gift-pay/detail.wxss @@ -1,7 +1,7 @@ /* Soul创业派对 - 代付详情页 */ .page { min-height: 100vh; - background: #000; + background: linear-gradient(180deg, #0a0a0a 0%, #000 40%, #000 100%); } .nav-bar { @@ -10,8 +10,10 @@ left: 0; right: 0; z-index: 100; - background: rgba(0, 0, 0, 0.9); - border-bottom: 1rpx solid rgba(255, 255, 255, 0.08); + background: rgba(0, 0, 0, 0.92); + backdrop-filter: blur(20rpx); + -webkit-backdrop-filter: blur(20rpx); + border-bottom: 1rpx solid rgba(255, 255, 255, 0.06); } .nav-content { @@ -26,25 +28,31 @@ width: 72rpx; height: 72rpx; border-radius: 50%; - background: #1c1c1e; + background: rgba(255, 255, 255, 0.06); display: flex; align-items: center; justify-content: center; + transition: opacity 0.2s; +} + +.nav-back:active { + opacity: 0.7; } .back-arrow { font-size: 36rpx; - color: rgba(255, 255, 255, 0.8); + color: rgba(255, 255, 255, 0.9); } .nav-title { - font-size: 32rpx; + font-size: 34rpx; font-weight: 600; color: #fff; + letter-spacing: 0.5rpx; } .content { - padding: 32rpx; + padding: 20rpx; } .loading-box { @@ -58,7 +66,7 @@ .loading-spinner { width: 48rpx; height: 48rpx; - border: 4rpx solid rgba(0, 206, 209, 0.3); + border: 4rpx solid rgba(0, 206, 209, 0.2); border-top-color: #00CED1; border-radius: 50%; animation: spin 0.8s linear infinite; @@ -71,43 +79,86 @@ .loading-text { margin-top: 24rpx; font-size: 28rpx; - color: rgba(255, 255, 255, 0.5); + color: rgba(255, 255, 255, 0.45); } +/* 营销:章节标题+内容预览,与订单卡片统一风格 */ +.article-preview { + background: linear-gradient(145deg, #1a1a1c 0%, #141416 100%); + border-radius: 24rpx; + padding: 24rpx; + margin-bottom: 16rpx; + border: 1rpx solid rgba(0, 206, 209, 0.1); +} + +.article-title { + display: block; + font-size: 30rpx; + font-weight: 600; + color: #fff; + line-height: 1.5; + margin-bottom: 12rpx; +} + +.article-content { + font-size: 26rpx; + color: rgba(255, 255, 255, 0.6); + line-height: 1.65; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 4; + -webkit-box-orient: vertical; +} + +/* 订单卡片:与文章预览统一圆角、边距 */ .card { - background: #1c1c1e; + background: linear-gradient(145deg, #1a1a1c 0%, #141416 100%); border-radius: 24rpx; overflow: hidden; - margin-bottom: 32rpx; + margin-bottom: 24rpx; + border: 1rpx solid rgba(0, 206, 209, 0.1); } .card-header { - padding: 32rpx; - border-bottom: 1rpx solid rgba(255, 255, 255, 0.06); + padding: 24rpx; } -.card-title { - display: block; - font-size: 28rpx; - color: rgba(255, 255, 255, 0.5); - margin-bottom: 8rpx; +.card-badge { + display: inline-block; + font-size: 22rpx; + color: rgba(0, 206, 209, 0.9); + background: rgba(0, 206, 209, 0.08); + padding: 6rpx 14rpx; + border-radius: 8rpx; + margin-bottom: 12rpx; + letter-spacing: 0.5rpx; } .initiator { - font-size: 34rpx; + display: block; + font-size: 32rpx; font-weight: 600; color: #fff; + line-height: 1.4; + letter-spacing: 0.3rpx; +} + +.card-divider { + height: 1rpx; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.08), transparent); + margin: 0 24rpx; } .card-body { - padding: 32rpx; + padding: 20rpx 24rpx 24rpx; } .row { display: flex; justify-content: space-between; - align-items: center; - margin-bottom: 24rpx; + align-items: flex-start; + margin-bottom: 16rpx; } .row:last-child { @@ -115,46 +166,99 @@ } .label { - font-size: 28rpx; - color: rgba(255, 255, 255, 0.5); + font-size: 26rpx; + color: rgba(255, 255, 255, 0.45); + flex-shrink: 0; + width: 80rpx; } -.value { +.product-row .value { + flex: 1; + text-align: right; font-size: 28rpx; - color: #fff; + color: rgba(255, 255, 255, 0.95); + line-height: 1.5; + word-break: break-all; +} + +.product-desc { + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; +} + +.amount-row { + align-items: center; } .amount-row .amount { - font-size: 40rpx; + font-size: 44rpx; font-weight: 700; color: #00CED1; + letter-spacing: 1rpx; + text-shadow: 0 0 24rpx rgba(0, 206, 209, 0.3); } +/* 提示文案 */ .tips { - padding: 0 8rpx 32rpx; - font-size: 24rpx; - color: rgba(255, 255, 255, 0.4); + display: flex; + align-items: flex-start; + gap: 10rpx; + padding: 0 4rpx 24rpx; + font-size: 26rpx; + color: rgba(255, 255, 255, 0.5); + line-height: 1.5; } +.tips-icon { + flex-shrink: 0; + font-size: 28rpx; + opacity: 0.8; +} + +/* 主按钮 */ .pay-btn { width: 100%; height: 96rpx; line-height: 96rpx; - background: linear-gradient(90deg, #00CED1 0%, #20B2AA 100%); + background: linear-gradient(135deg, #00CED1 0%, #18a8a8 50%, #20B2AA 100%); color: #fff; - font-size: 32rpx; + font-size: 34rpx; font-weight: 600; - border-radius: 48rpx; + border-radius: 50rpx; border: none; + box-shadow: 0 8rpx 24rpx rgba(0, 206, 209, 0.35); + transition: opacity 0.2s, transform 0.1s; +} + +.pay-btn:active { + opacity: 0.92; + transform: scale(0.99); } .pay-btn[disabled] { opacity: 0.6; + transform: none; +} + +.share-btn { + display: flex; + align-items: center; + justify-content: center; + gap: 16rpx; +} + +.btn-icon-img { + width: 40rpx; + height: 40rpx; + filter: brightness(0) invert(1); } .empty { text-align: center; padding: 120rpx 0; font-size: 28rpx; - color: rgba(255, 255, 255, 0.5); + color: rgba(255, 255, 255, 0.45); } diff --git a/miniprogram/pages/gift-pay/list.js b/miniprogram/pages/gift-pay/list.js index 9c5a7856..6ce56d7b 100644 --- a/miniprogram/pages/gift-pay/list.js +++ b/miniprogram/pages/gift-pay/list.js @@ -50,7 +50,20 @@ Page({ } }, + goToDetail(e) { + const requestSn = e.currentTarget.dataset.sn + if (requestSn) { + wx.navigateTo({ url: `/pages/gift-pay/detail?requestSn=${encodeURIComponent(requestSn)}` }) + } + }, + + shareRequest(e) { + e.stopPropagation() + wx.showToast({ title: '请点击右上角「...」分享给好友', icon: 'none', duration: 2500 }) + }, + async cancelRequest(e) { + e.stopPropagation() const requestSn = e.currentTarget.dataset.sn if (!requestSn) return const ok = await new Promise(r => { @@ -74,11 +87,6 @@ Page({ } }, - shareRequest(e) { - const requestSn = e.currentTarget.dataset.sn - wx.showToast({ title: '请点击右上角「...」分享给好友', icon: 'none', duration: 2500 }) - }, - goBack() { app.goBackOrToHome() }, diff --git a/miniprogram/pages/gift-pay/list.wxml b/miniprogram/pages/gift-pay/list.wxml index 27d852be..c319fee6 100644 --- a/miniprogram/pages/gift-pay/list.wxml +++ b/miniprogram/pages/gift-pay/list.wxml @@ -29,7 +29,7 @@ 暂无发起的代付 - + {{item.description}} ¥{{item.amount}} @@ -49,7 +49,7 @@ 暂无帮付记录 - + {{item.description}} ¥{{item.amount}} diff --git a/miniprogram/pages/read/read.js b/miniprogram/pages/read/read.js index b6ad9eaf..7c419fc4 100644 --- a/miniprogram/pages/read/read.js +++ b/miniprogram/pages/read/read.js @@ -17,6 +17,7 @@ import accessManager from '../../utils/chapterAccessManager' import readingTracker from '../../utils/readingTracker' const { parseScene } = require('../../utils/scene.js') const contentParser = require('../../utils/contentParser.js') +const { trackClick } = require('../../utils/trackClick') const app = getApp() @@ -65,9 +66,6 @@ Page({ // 弹窗 showShareModal: false, - showGiftShareModal: false, - shareMode: '', // 'gift' = 代付分享,onShareAppMessage 返回 gift-pay/detail - giftRequestSn: '', // 代付请求号,分享时用 showLoginModal: false, agreeProtocol: false, showPosterModal: false, @@ -97,13 +95,15 @@ Page({ // 支持 scene(扫码)、mid、id、ref、gift(代付) 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 const isGift = options.gift === '1' || options.gift === 'true' + // 代付统一到代付页:gift=1&ref=requestSn 时直接跳转,禁止在阅读页代付 if (isGift && ref) { - wx.setStorageSync('gift_for_ref', ref) // 代付模式:好友打开后,购买时传 giftFor(后端待支持) + wx.redirectTo({ url: `/pages/gift-pay/detail?requestSn=${encodeURIComponent(ref)}` }) + return } + const mid = options.mid ? parseInt(options.mid, 10) : (parsed.mid || app.globalData.initialSectionMid || 0) + let id = options.id || parsed.id || app.globalData.initialSectionId if (app.globalData.initialSectionMid) delete app.globalData.initialSectionMid if (app.globalData.initialSectionId) delete app.globalData.initialSectionId @@ -657,7 +657,7 @@ Page({ this.setData({ showShareModal: false }) }, - // 代付分享弹窗:创建代付请求后分享到代付页面 + // 找好友代付:创建代付请求后跳转代付页(美团式,在代付页分享) async showGiftShareModal() { if (!app.globalData.userInfo?.id) { wx.showToast({ title: '请先登录', icon: 'none' }) @@ -682,7 +682,7 @@ Page({ }) wx.hideLoading() if (res && res.success && res.requestSn) { - this.setData({ showGiftShareModal: true, giftRequestSn: res.requestSn }) + wx.navigateTo({ url: `/pages/gift-pay/detail?requestSn=${res.requestSn}` }) } else { wx.showToast({ title: res?.error || '创建失败', icon: 'none' }) } @@ -692,20 +692,6 @@ Page({ } }, - closeGiftShareModal() { - this.setData({ showGiftShareModal: false }) - }, - - // 分享给好友(代付):引导用户点右上角,分享到代付详情页 - shareGiftToFriend() { - this.setData({ shareMode: 'gift', showGiftShareModal: false }) - wx.showToast({ - title: '请点击右上角「...」→ 发送给好友', - icon: 'none', - duration: 2500 - }) - }, - // 复制链接 copyLink() { const userInfo = app.globalData.userInfo @@ -741,29 +727,17 @@ Page({ }) }, - // 分享到微信 - 自动带分享人ID;shareMode=gift 时分享到代付详情页 + // 分享到微信 - 自动带分享人ID onShareAppMessage() { - const { section, sectionId, sectionMid, shareMode, giftRequestSn } = this.data + trackClick('read', 'btn_click', '分享_' + this.data.sectionId) + const { section, sectionId, sectionMid } = this.data const ref = app.getMyReferralCode() const q = sectionMid ? `mid=${sectionMid}` : `id=${sectionId}` - let path = ref ? `/pages/read/read?${q}&ref=${ref}` : `/pages/read/read?${q}` - let title = section?.title + const path = ref ? `/pages/read/read?${q}&ref=${ref}` : `/pages/read/read?${q}` + const title = section?.title ? `📚 ${section.title.length > 20 ? section.title.slice(0, 20) + '...' : section.title}` : '📚 Soul创业派对 - 真实商业故事' - if (shareMode === 'gift' && giftRequestSn) { - path = `/pages/gift-pay/detail?requestSn=${giftRequestSn}` - title = '好友请你帮忙代付 - Soul创业派对' - this.setData({ shareMode: '', giftRequestSn: '' }) - } else { - title = section?.title - ? `📚 ${section.title.length > 20 ? section.title.slice(0, 20) + '...' : section.title}` - : '📚 Soul创业派对 - 真实商业故事' - } - return { - title, - path - // 不设置 imageUrl,使用当前阅读页截图作为分享卡片中间图片 - } + return { title, path } }, // 底部「分享到朋友圈」按钮点击:微信不支持 button open-type=shareTimeline,只能通过右上角菜单分享,点击时引导用户 @@ -775,16 +749,12 @@ Page({ }) }, - // 分享到朋友圈:带文章标题,过长时截断;shareMode=gift 时 query 带 gift=1 + // 分享到朋友圈:带文章标题,过长时截断 onShareTimeline() { - const { section, sectionId, sectionMid, chapterTitle, shareMode } = this.data + const { section, sectionId, sectionMid, chapterTitle } = this.data const ref = app.getMyReferralCode() const q = sectionMid ? `mid=${sectionMid}` : `id=${sectionId}` - let query = ref ? `${q}&ref=${ref}` : q - if (shareMode === 'gift' && ref) { - query = `${q}&ref=${ref}&gift=1` - this.setData({ shareMode: '' }) - } + const query = ref ? `${q}&ref=${ref}` : q const articleTitle = (section?.title || chapterTitle || '').trim() const title = articleTitle ? (articleTitle.length > 28 ? articleTitle.slice(0, 28) + '...' : articleTitle) @@ -918,6 +888,7 @@ Page({ // 购买章节 - 直接调起支付 async handlePurchaseSection() { + trackClick('read', 'btn_click', '购买章节_' + this.data.sectionId) console.log('[Pay] 点击购买章节按钮') wx.showLoading({ title: '处理中...', mask: true }) diff --git a/miniprogram/pages/read/read.wxml b/miniprogram/pages/read/read.wxml index 409efc69..a1542697 100644 --- a/miniprogram/pages/read/read.wxml +++ b/miniprogram/pages/read/read.wxml @@ -298,23 +298,6 @@ - - - - - diff --git a/miniprogram/project.private.config.json b/miniprogram/project.private.config.json index b75b4c26..b79d1327 100644 --- a/miniprogram/project.private.config.json +++ b/miniprogram/project.private.config.json @@ -23,12 +23,19 @@ "condition": { "miniprogram": { "list": [ + { + "name": "pages/gift-pay/detail", + "pathName": "pages/gift-pay/detail", + "query": "requestSn=GPRMP20260317114238341300", + "scene": null, + "launchMode": "default" + }, { "name": "pages/read/read", "pathName": "pages/read/read", "query": "mid=219", - "scene": null, - "launchMode": "default" + "launchMode": "default", + "scene": null }, { "name": "唤醒", diff --git a/soul-api/internal/handler/gift_pay.go b/soul-api/internal/handler/gift_pay.go index 3f0d935c..393c29a9 100644 --- a/soul-api/internal/handler/gift_pay.go +++ b/soul-api/internal/handler/gift_pay.go @@ -6,6 +6,7 @@ import ( "net/http" "strings" "time" + "unicode/utf8" "soul-api/internal/database" "soul-api/internal/model" @@ -16,6 +17,26 @@ import ( const giftPayExpireHours = 24 +// giftPayPreviewContent 取内容前 20%,用于代付页营销展示 +func giftPayPreviewContent(content string) string { + n := utf8.RuneCountInString(content) + if n == 0 { + return "" + } + limit := n * 20 / 100 + if limit < 50 { + limit = 50 + } + if limit > n { + limit = n + } + runes := []rune(content) + if limit >= n { + return string(runes) + } + return string(runes[:limit]) + "……" +} + // GiftPayCreate POST /api/miniprogram/gift-pay/create 创建代付请求 func GiftPayCreate(c *gin.Context) { var req struct { @@ -188,15 +209,31 @@ func GiftPayDetail(c *gin.Context) { nickname = n } + // 营销:章节类型时返回标题和内容预览,吸引代付人 + sectionTitle := gpr.Description + contentPreview := "" + if gpr.ProductType == "section" && gpr.ProductID != "" { + var ch model.Chapter + if err := db.Select("section_title", "content").Where("id = ?", gpr.ProductID).First(&ch).Error; err == nil { + if ch.SectionTitle != "" { + sectionTitle = ch.SectionTitle + } + contentPreview = giftPayPreviewContent(ch.Content) + } + } + c.JSON(http.StatusOK, gin.H{ - "success": true, - "requestSn": gpr.RequestSN, - "productType": gpr.ProductType, - "productId": gpr.ProductID, - "amount": gpr.Amount, - "description": gpr.Description, + "success": true, + "requestSn": gpr.RequestSN, + "productType": gpr.ProductType, + "productId": gpr.ProductID, + "amount": gpr.Amount, + "description": gpr.Description, + "sectionTitle": sectionTitle, + "contentPreview": contentPreview, "initiatorNickname": nickname, - "expireAt": gpr.ExpireAt.Format(time.RFC3339), + "initiatorUserId": gpr.InitiatorUserID, + "expireAt": gpr.ExpireAt.Format(time.RFC3339), }) } diff --git a/soul-api/internal/handler/miniprogram.go b/soul-api/internal/handler/miniprogram.go index c7d1221b..f1ceb180 100644 --- a/soul-api/internal/handler/miniprogram.go +++ b/soul-api/internal/handler/miniprogram.go @@ -597,6 +597,12 @@ func MiniprogramPayNotify(c *gin.Context) { } // 代付订单:更新 gift_pay_request、订单 payer_user_id + // 权益归属与分佣:代付时归发起人(order.UserID),普通订单归 buyerUserID + beneficiaryUserID := buyerUserID + if attach.GiftPayRequestSn != "" && order.UserID != "" { + beneficiaryUserID = order.UserID + fmt.Printf("[PayNotify] 代付订单,权益归属发起人: %s\n", beneficiaryUserID) + } if attach.GiftPayRequestSn != "" { var payerUserID string if openID != "" { @@ -615,36 +621,35 @@ func MiniprogramPayNotify(c *gin.Context) { }) } - if buyerUserID != "" && attach.ProductType != "" { + if beneficiaryUserID != "" && attach.ProductType != "" { if attach.ProductType == "fullbook" { - db.Model(&model.User{}).Where("id = ?", buyerUserID).Update("has_full_book", true) - fmt.Printf("[PayNotify] 用户已购全书: %s\n", buyerUserID) + db.Model(&model.User{}).Where("id = ?", beneficiaryUserID).Update("has_full_book", true) + fmt.Printf("[PayNotify] 用户已购全书: %s\n", beneficiaryUserID) } else if attach.ProductType == "vip" { - // V4.2 修复:续费时累加剩余天数(从 max(now, vip_expire_date) 加 365 天) vipActivatedAt := time.Now() if order.PayTime != nil { vipActivatedAt = *order.PayTime } - expireDate := activateVIP(db, buyerUserID, 365, vipActivatedAt) - fmt.Printf("[VIP] 设置方式=支付设置, userId=%s, orderSn=%s, 过期日=%s, activatedAt=%s\n", buyerUserID, orderSn, expireDate.Format("2006-01-02"), vipActivatedAt.Format("2006-01-02 15:04:05")) + expireDate := activateVIP(db, beneficiaryUserID, 365, vipActivatedAt) + fmt.Printf("[VIP] 设置方式=支付设置, userId=%s, orderSn=%s, 过期日=%s, activatedAt=%s\n", beneficiaryUserID, orderSn, expireDate.Format("2006-01-02"), vipActivatedAt.Format("2006-01-02 15:04:05")) } else if attach.ProductType == "match" { - fmt.Printf("[PayNotify] 用户购买匹配次数: %s,订单 %s\n", buyerUserID, orderSn) + fmt.Printf("[PayNotify] 用户购买匹配次数: %s,订单 %s\n", beneficiaryUserID, orderSn) } else if attach.ProductType == "balance_recharge" { if err := ConfirmBalanceRechargeByOrder(db, &order); err != nil { fmt.Printf("[PayNotify] 余额充值确认失败: %s, err=%v\n", orderSn, err) } else { - fmt.Printf("[PayNotify] 余额充值成功: %s, 金额 %.2f\n", buyerUserID, totalAmount) + fmt.Printf("[PayNotify] 余额充值成功: %s, 金额 %.2f\n", beneficiaryUserID, totalAmount) } } else if attach.ProductType == "section" && attach.ProductID != "" { var count int64 db.Model(&model.Order{}).Where( "user_id = ? AND product_type = 'section' AND product_id = ? AND status = 'paid' AND order_sn != ?", - buyerUserID, attach.ProductID, orderSn, + beneficiaryUserID, attach.ProductID, orderSn, ).Count(&count) if count == 0 { - fmt.Printf("[PayNotify] 用户首次购买章节: %s - %s\n", buyerUserID, attach.ProductID) + fmt.Printf("[PayNotify] 用户首次购买章节: %s - %s\n", beneficiaryUserID, attach.ProductID) } else { - fmt.Printf("[PayNotify] 用户已有该章节的其他已支付订单: %s - %s\n", buyerUserID, attach.ProductID) + fmt.Printf("[PayNotify] 用户已有该章节的其他已支付订单: %s - %s\n", beneficiaryUserID, attach.ProductID) } } productID := attach.ProductID @@ -653,9 +658,9 @@ func MiniprogramPayNotify(c *gin.Context) { } db.Where( "user_id = ? AND product_type = ? AND product_id = ? AND status = 'created' AND order_sn != ?", - buyerUserID, attach.ProductType, productID, orderSn, + beneficiaryUserID, attach.ProductType, productID, orderSn, ).Delete(&model.Order{}) - processReferralCommission(db, buyerUserID, totalAmount, orderSn, &order) + processReferralCommission(db, beneficiaryUserID, totalAmount, orderSn, &order) } return nil }) diff --git a/开发文档/10、项目管理/运营与变更.md b/开发文档/10、项目管理/运营与变更.md index 0181e47f..589dc9a5 100644 --- a/开发文档/10、项目管理/运营与变更.md +++ b/开发文档/10、项目管理/运营与变更.md @@ -295,3 +295,95 @@ Mycontent-temp/miniprogram 为样式预览分支,miniprogram 为线上主线 - `开发文档/1、需求/链接人与事-所有同步需求.md` - `开发文档/存客宝对接逻辑图.md` + +--- + +# 第十一部分:代付美团式流程与 PayNotify 完善(2026-03-17 橙子同步) + +## 代付流程改造 + +| 环节 | 变更 | +|------|------| +| 读页「找好友代付」 | 创建请求后**跳转代付详情页**,不再弹窗 | +| 代付详情页 | 发起人看到「分享给好友」UI,好友看到「帮他付款」;后端 detail 返回 `initiatorUserId` 供前端区分 | +| 订单与分佣 | 代付支付后并入 orders、分销;权益归属发起人 | + +## PayNotify 权益归属修复 + +- **问题**:代付时 `buyerUserID` 来自 openID(代付人),权益错误给到代付人 +- **修复**:引入 `beneficiaryUserID`,代付时 = `order.UserID`(发起人),普通订单 = `buyerUserID` +- **影响范围**:全书、VIP、章节、余额充值、分佣、取消未支付订单 + +## 其他同步 + +- 目录页:`book/parts` 加载增加 loading +- 首页最新新增:默认 5 条、折叠状态,点击展开 +- 导师预约支付:暂不处理,保持与 new-soul 一致 + +## 相关文档 + +- `开发文档/代付功能-美团式方案与场景清单.md` 第九章实现状态 +- `开发文档/迁移完成度与待办清单.md` 迁移完成度与剩余待办 + +--- + +# 第十二部分:乘风吸收经验与迁移完成度(2026-03-17) + +## 吸收范围 + +- agent 经验清单、各角色 evolution、项目索引 +- 新版迁移方案、稳定版适配清单、代付美团式方案 +- 开发文档、需求汇总、运营与变更 + +## 迁移完成度结论 + +**核心迁移已全部完成**。详见 `开发文档/迁移完成度与待办清单.md`。 + +**剩余为可选/搁置**:导师预约支付(暂不处理)、阅读统计埋点、目录每日新增、P4 设计稿对齐、富文本/打包引导/存客宝(搁置)。 + +--- + +# 第十三部分:代付统一到代付页(2026-03-17 橙子同步) + +## 新需求 + +**代付不允许在阅读页完成,统一到代付页。** 代付页可显示部分文章信息。 + +## 实现 + +- 读页:`gift=1&ref=requestSn` 打开时,`redirectTo` 到 `/pages/gift-pay/detail?requestSn=xxx`,不展示阅读页 +- 代付页:已展示商品描述(章节标题等)、金额、发起人昵称 + +## 相关文档 + +- `开发文档/找朋友代付-流程与配置.md` 已更新为美团式流程 + +--- + +# 第十四部分:乘风同步进度(2026-03-17) + +## 代付页营销与体验 + +- **章节内容预览**:代付页展示 20% 章节内容 + 标题,吸引代付人 +- **我的代付列表**:点击卡片进入代付详情页 +- **页面协调**:统一圆角、间距、视觉风格 + +## 迁移完成度 + +详见 `开发文档/迁移完成度与待办清单.md`。**核心迁移已全部完成**,剩余为可选/搁置项。 + +--- + +# 第十五部分:富文本/打包/存客宝 分析结论(2026-03-17) + +## 分析结论 + +| 项 | 结论 | +|----|------| +| **富文本渲染** | 按稳定版处理,用户已落地 | +| **打包购买引导** | 稳定版与 new-soul 一致:purchasedCount≥3 时显示「解锁全书」按钮 | +| **存客宝对接** | 稳定版已有且更完善:ckb/lead、index-lead、match、join;稳定版多限频、linkTag ckb | + +## 文档同步 + +- `开发文档/迁移完成度与待办清单.md`:搁置项更新为「稳定版已有」,无新增搁置 diff --git a/开发文档/1、需求/需求汇总.md b/开发文档/1、需求/需求汇总.md index 20833cbb..7be99212 100644 --- a/开发文档/1、需求/需求汇总.md +++ b/开发文档/1、需求/需求汇总.md @@ -54,4 +54,5 @@ IP 设定、风格、输出规范(见原卡若角色设定)。 | 2026-03-16 | 链接人与事列表:table 布局、planId/apiKey 列、apiKey 复制图标、删除 Dialog 二次确认 | 已完成 | ContentPage.tsx | | 2026-03-16 | 存客宝创建计划参数:planType=1、sceneId=9、status=1 | 已完成 | db_person.go | | 2026-03-16 | @mention 存储格式:span 必须含 data-label,否则 TipTap 显示 token | 已完成 | ParseAutoLinkContent、autolink.go | +| 2026-03-17 | 代付统一到代付页:gift=1&ref 打开 read 页时 redirectTo 代付页,禁止在阅读页代付;代付页显示部分文章信息 | 已完成 | read.js onLoad;代付页已有 description | | - | 链接人与事所有同步需求汇总 | - | 链接人与事-所有同步需求.md | diff --git a/开发文档/README.md b/开发文档/README.md index ffedcebe..b8e9645a 100644 --- a/开发文档/README.md +++ b/开发文档/README.md @@ -23,6 +23,7 @@ | [1、需求/需求汇总](1、需求/需求汇总.md) | 需求清单、业务需求 | | [10、项目管理/项目落地推进表](10、项目管理/项目落地推进表.md) | 里程碑、永平落地 | | [10、项目管理/运营与变更](10、项目管理/运营与变更.md) | 近期讨论、变更记录 | +| [迁移完成度与待办清单](迁移完成度与待办清单.md) | 迁移完成度、剩余待办、搁置项 | ### 架构与规范 diff --git a/开发文档/代付功能-美团式方案与场景清单.md b/开发文档/代付功能-美团式方案与场景清单.md index e08cc612..1c4f9b6e 100644 --- a/开发文档/代付功能-美团式方案与场景清单.md +++ b/开发文档/代付功能-美团式方案与场景清单.md @@ -155,10 +155,10 @@ - **Tab**:我发起的 / 我帮付的 - **列表**:请求状态、商品、金额、时间、操作(取消/查看) -### 3. 读页入口改造 +### 3. 读页入口改造(已实现 2026-03-17) -- 当前「找好友代付」→ 弹窗分享 -- 改造:点击后先调 `gift-pay/create` 创建请求,再弹窗分享带 `requestSn` 的链接 +- ~~当前「找好友代付」→ 弹窗分享~~ +- **已落地**:点击后调 `gift-pay/create` 创建请求 → **跳转代付详情页** → 发起人在代付页点击「分享给好友」分享 --- @@ -190,3 +190,28 @@ - **商品类型**:section(章节)、fullbook(全书)、vip(年度会员)、match(匹配次数)、balance_recharge(余额充值) - **价格**:从 `chapters`、`system_config` 等取,创建请求时锁定 - **权益激活**:与现有 `PayNotify` 逻辑一致,仅 `user_id` 改为发起人 + +--- + +## 九、实现状态(2026-03-17 橙子同步) + +### 1. 美团式流程已落地 + +| 环节 | 实现 | +|------|------| +| 读页入口 | 点击「找好友代付」→ 创建请求 → **跳转代付详情页**(不再弹窗) | +| 代付详情页 | 发起人:标题「找朋友代付」+「分享给好友」按钮(open-type=share);好友:标题「帮他付款」+「帮他付款」按钮 | +| 后端 detail | 返回 `initiatorUserId`,前端据此区分发起人/好友 UI | +| 订单与分佣 | 代付支付后并入 orders、分销;权益归属发起人 | + +### 2. PayNotify 权益归属修复 + +- **问题**:代付时 `buyerUserID` 来自 openID(代付人),权益错误给到代付人 +- **修复**:引入 `beneficiaryUserID`,代付时 = `order.UserID`(发起人),普通订单 = `buyerUserID` +- **影响**:全书/VIP/章节/余额充值/分佣/取消未支付订单,均按 `beneficiaryUserID` 处理 + +### 3. 其他本次同步 + +- 目录页:`book/parts` 加载增加 loading +- 首页最新新增:默认 5 条、折叠状态,点击展开 +- 导师预约支付:暂不处理,保持与 new-soul 一致 diff --git a/开发文档/找朋友代付-流程与配置.md b/开发文档/找朋友代付-流程与配置.md index 2d2bd5dd..9cba43c9 100644 --- a/开发文档/找朋友代付-流程与配置.md +++ b/开发文档/找朋友代付-流程与配置.md @@ -1,60 +1,70 @@ # 找朋友代付 - 流程与配置说明 -## 一、当前实现概览 - -### 1. 入口与弹窗 - -| 位置 | 入口 | 弹窗 | -|------|------|------| -| 读页(read) | 付费墙下方「找好友代付」 | 代付分享弹窗 | -| 个人中心(my) | 菜单「代付链接」 | 跳转 gift-link 页 | - -### 2. 代付分享弹窗(读页内) - -用户点击「找好友代付」后弹出,包含两个操作: - -| 按钮 | 功能 | 实现 | -|------|------|------| -| **复制代付链接** | 复制到剪贴板 | `copyGiftLink()` → `wx.setClipboardData` | -| **分享给好友** | 引导用户点右上角分享 | `shareGiftToFriend()` → 设置 `shareMode=gift` | +> 美团式流程:代付统一在代付页完成,禁止在阅读页代付。 +> 更新日期:2026-03-17 --- -## 二、复制链接的本质与限制 +## 一、当前实现概览 -### 当前复制的内容 +### 1. 主流程(美团式) -- **读页弹窗**:`/pages/read/read?mid=xxx&ref=xxx&gift=1`(带本章节信息) -- **gift-link 页**:`pages/gift-link/gift-link?ref=xxx&gift=1`(通用入口) +| 步骤 | 说明 | +|------|------| +| 1 | 发起人在 read 页点击「找好友代付」→ 创建代付请求 → **跳转 gift-pay/detail** | +| 2 | 发起人在代付页看到订单信息,点击「分享给好友」→ 唤起微信分享 | +| 3 | 好友收到小程序卡片,打开 **gift-pay/detail** → 看到「帮他付款」→ 完成支付 | +| 4 | 权益归属发起人,订单并入 orders、分佣 | -这是**小程序内部 path**,不是可点击的 H5 链接。 +### 2. 入口与页面 -### 微信小程序的限制 +| 位置 | 入口 | 跳转 | +|------|------|------| +| 读页(read) | 付费墙下方「找好友代付」 | 创建请求 → gift-pay/detail | +| 个人中心(my) | 菜单「我的代付」 | gift-pay/list | + +### 3. 代付页双态 + +| 身份 | 标题 | 主按钮 | +|------|------|--------| +| 发起人 | 找朋友代付 | 分享给好友(open-type=share) | +| 好友 | 帮他付款 | 帮他付款(支付) | + +--- + +## 二、统一到代付页(禁止阅读页代付) + +### 规则 + +**代付必须在代付页完成,禁止在阅读页代付。** + +### 实现 + +当用户通过 `gift=1&ref=requestSn` 打开阅读页时: + +- **之前**:留在阅读页,可在阅读页购买(逻辑不完整) +- **现**:`redirectTo` 到 `/pages/gift-pay/detail?requestSn=xxx`,统一在代付页代付 + +### 示例 + +- 好友收到链接:`/pages/read/read?mid=123&ref=GPR_xxx&gift=1` +- 打开后立即跳转:`/pages/gift-pay/detail?requestSn=GPR_xxx` +- 在代付页完成支付 + +### 代付页文章信息 + +代付页已展示:商品描述(章节标题/全书/VIP)、金额、发起人昵称(脱敏)。 + +--- + +## 三、微信分享限制 | 方式 | 好友能否直接打开 | |------|------------------| | 复制 path 发到聊天 | ❌ 不能,收到的是纯文本 | | 分享小程序卡片 | ✅ 能,点击卡片即可打开 | -**结论**:复制 path 后,好友**无法**像 H5 链接那样点击跳转,只能通过**分享小程序卡片**直接打开。 - ---- - -## 三、推荐流程设置 - -### 主流程:分享给好友(优先) - -1. 用户点击「找好友代付」→ 弹出代付分享弹窗 -2. 点击「分享给好友」→ 提示「请点击右上角「...」→ 发送给好友」 -3. 用户点右上角「...」→ 选择「发送给好友」 -4. 好友收到**小程序卡片**,点击即可打开,自动带 `ref` 和 `gift=1` - -### 辅助流程:复制链接(备用) - -1. 用户点击「复制代付链接」→ path 已复制到剪贴板 -2. 用户粘贴到聊天发给好友 -3. 好友收到文本,需**手动**打开「Soul创业派对」小程序 -4. 建议在弹窗或复制成功提示中说明:「好友需打开 Soul 创业派对小程序,或请优先使用分享给好友」 +**结论**:复制 path 后好友无法直接打开,主流程为「分享给好友」发小程序卡片。 --- @@ -62,53 +72,15 @@ | 文件 | 说明 | |------|------| -| `miniprogram/pages/read/read.wxml` | 代付分享弹窗 UI、入口「找好友代付」 | -| `miniprogram/pages/read/read.js` | `showGiftShareModal`、`copyGiftLink`、`shareGiftToFriend`、`onShareAppMessage` | -| `miniprogram/pages/gift-link/gift-link.js` | 通用代付链接页,调 `/api/miniprogram/gift/link` | -| `soul-api/internal/handler/miniprogram.go` | `GiftLinkGet` 返回 path、ref、scene | +| `miniprogram/pages/read/read.js` | gift=1&ref 时 redirectTo gift-pay/detail | +| `miniprogram/pages/read/read.js` | showGiftShareModal:创建请求 → navigateTo gift-pay/detail | +| `miniprogram/pages/gift-pay/detail.js` | 代付详情页,发起人/好友双态 | +| `miniprogram/pages/gift-pay/list.js` | 我的代付列表 | +| `soul-api/internal/handler/gift_pay.go` | create/detail/pay/cancel/my-requests/my-payments | --- -## 五、可选优化方向 +## 五、相关文档 -### 1. 优化复制成功提示 - -当前:`wx.showToast({ title: '代付链接已复制', icon: 'success' })` - -可改为更明确的引导,例如: - -```js -wx.showToast({ - title: '已复制,发给好友后请让对方打开 Soul 创业派对小程序', - icon: 'none', - duration: 3000 -}) -``` - -### 2. 弹窗内增加说明 - -在「将链接分享给好友,好友可为你代付购买本章」下方补充: - -> 建议使用「分享给好友」,好友点击卡片即可打开;复制链接需好友手动打开小程序。 - -### 3. 若需「可点击链接」(进阶) - -要让好友**点击链接直接打开小程序**,需要: - -- **URL Link**:后端调微信接口生成 `https://wxaurl.cn/xxx` 形式链接 -- **URL Scheme**:生成 `weixin://dl/business/?t=xxx`,有效期有限 -- **H5 中转页**:部署 H5 页,用 `wx-open-launch-weapp` 打开小程序 - -以上均需在微信公众平台配置域名、并在后端实现对应接口。 - ---- - -## 六、代付模式参数说明 - -| 参数 | 含义 | 来源 | -|------|------|------| -| `ref` | 推荐码,标识「谁在找代付」 | `app.getMyReferralCode()` | -| `gift=1` | 代付模式标记 | path 或 query 中携带 | -| `gift_for_ref` | 存到 storage,购买时传 `giftFor` | `wx.setStorageSync('gift_for_ref', ref)` | - -好友通过分享打开后,`onLoad` 会解析 `ref` 和 `gift=1`,写入 `gift_for_ref`,后续支付流程需后端支持 `giftFor` 参数完成代付逻辑。 +- `开发文档/代付功能-美团式方案与场景清单.md` +- `开发文档/迁移完成度与待办清单.md` diff --git a/开发文档/迁移完成度与待办清单.md b/开发文档/迁移完成度与待办清单.md new file mode 100644 index 00000000..b95a7b23 --- /dev/null +++ b/开发文档/迁移完成度与待办清单.md @@ -0,0 +1,110 @@ +# 迁移完成度与待办清单 + +> 乘风吸收当前经验与交互后整理。基于 new-soul 迁移方案、稳定版适配清单、agent 经验库。 +> 更新日期:2026-03-17(乘风同步进度) + +--- + +## 一、已迁移完成(✅) + +### 新版迁移方案(R1~R11) + +| 需求 | 说明 | 状态 | +|------|------|:----:| +| R1~R4 | 余额体系:充值、消费、交易记录、退款 | ✅ | +| R5 | 我的页「我的余额」入口 | ✅ | +| R2 | 阅读页余额购买(章节/全书) | ✅ | +| R6~R8 | 首页精选/最新展开、按热度、book/hot?limit=50 | ✅ | +| R9~R10 | 代付(我的代付 + 阅读页代付分享) | ✅ | +| R11 | 埋点 trackClick | ✅ | +| VIP 余额 | VIP 页支持余额购买 | ✅ | +| 管理端 | 订单支付方式、用户详情余额 | ✅ | + +### 稳定版适配(P1~P3) + +| 项 | 说明 | 状态 | +|----|------|:----:| +| P1 | 首页「阅读进度」卡 | ✅ | +| P2 | 我的页「编辑」入口 → profile-show | ✅ | +| P3 | 代付功能闭环(美团式:读页→代付页→分享) | ✅ | + +### 代付美团式(2026-03-17) + +| 项 | 说明 | 状态 | +|----|------|:----:| +| 读页入口 | 创建请求 → 跳转代付详情页 | ✅ | +| 代付详情页 | 发起人「分享给好友」/ 好友「帮他付款」双态 | ✅ | +| PayNotify | beneficiaryUserID 权益归发起人 | ✅ | +| 订单与分佣 | 并入 orders、分销 | ✅ | + +### 体验优化(2026-03-17) + +| 项 | 说明 | 状态 | +|----|------|:----:| +| 目录页 | book/parts 加载 loading | ✅ | +| 首页最新新增 | 默认 5 条、折叠,点击展开 | ✅ | + +### 代付营销与体验(近期) + +| 项 | 说明 | 状态 | +|----|------|:----:| +| 代付页营销 | 章节标题+内容预览(20%),吸引代付人 | ✅ | +| 我的代付列表 | 点击卡片进入代付详情页 | ✅ | +| 代付页布局 | 页面协调、间距统一、视觉统一 | ✅ | + +--- + +## 二、未迁移 / 待办 + +### 明确暂不处理 + +| 项 | 说明 | 决策 | +|----|------|------| +| 导师预约支付 | mentor-detail 预约成功后调起支付 | 暂不处理,保持与 new-soul 一致,弹「请联系客服」 | + +### 边缘场景(可选) + +| 项 | 说明 | 优先级 | +|----|------|:------:| +| ~~阅读页代付直链~~ | **已禁止**:gift=1&ref 打开 read 页时直接 redirectTo 代付页,统一在代付页代付 | ✅ | +| ~~阅读统计埋点~~ | chapters/read 页补充 trackClick(篇章展开、章节点击、搜索、分享、购买) | ✅ | +| ~~目录页「每日新增」~~ | latest-chapters 拉取 + 每日新增区块展示 | ✅ | +| ~~P4 目录页与设计稿~~ | 免费/NEW/¥1/增值标签、每日新增区块 | ✅ | + +### 稳定版已有(2026-03-17 分析) + +| 项 | 说明 | 与 new-soul 对比 | +|----|------|-----------------| +| ~~富文本渲染~~ | 按稳定版处理,用户已落地 | ✅ | +| ~~打包购买引导~~ | 购买 ≥3 章显示「解锁全书」按钮,两端一致 | 稳定版 = new-soul | +| ~~存客宝对接~~ | ckb/lead、index-lead、match、join 均已对接 | 稳定版更完善:限频、linkTag ckb | + +### 搁置项(无) + +原搁置项富文本/打包引导/存客宝均已确认稳定版已有,无新增搁置。 + +### 规则与埋点(待补充,2026-03-17) + +| 项 | 当前状态 | 待办 | +|----|----------|------| +| **埋点 trackClick** | 已接入:chapters、read、wallet | 遗漏:index、my、match、vip、search、referral 等页的关键操作 | +| **规则引擎 ruleEngine** | 迁移方案「不迁移」,稳定版无 | 若需迁移:ruleEngine.js + GET /api/miniprogram/user-rules(soul-api 需新增 miniprogram 路由) | + +**埋点遗漏页**:match(加好友、加入提交)、index(链接卡若、VIP 等)、my、vip、search、referral、gift-pay 等。 + +--- + +## 三、文档待更新 + +| 文档 | 待更新内容 | +|------|------------| +| 找朋友代付-流程与配置.md | 当前仍写 gift-link、弹窗分享,需改为 gift-pay 美团式流程 | +| 稳定版-小程序与API对比.md | wallet 已迁移,可更新为 ✓ | + +--- + +## 四、总结 + +**核心迁移已全部完成**:余额体系、代付美团式、埋点、首页/目录/阅读/VIP 相关功能均已落地。 + +**剩余**:导师预约支付(暂不处理);规则与埋点待补充(见上表)。富文本、打包购买引导、存客宝对接均已确认稳定版已有。