From f3d74ce94af8f2b3bdc2745fe6a83243be3165fd Mon Sep 17 00:00:00 2001
From: Alex-larget <33240357+Alex-larget@users.noreply.github.com>
Date: Tue, 24 Mar 2026 18:45:32 +0800
Subject: [PATCH] =?UTF-8?q?=E5=90=8C=E6=AD=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../agent/产品经理/evolution/2026-03-24.md | 5 +
.../agent/后端工程师/evolution/2026-03-24.md | 5 +
.cursor/agent/团队/evolution/2026-03-24.md | 10 +
.../小程序开发工程师/evolution/2026-03-24.md | 13 +
.cursor/agent/开发助理/项目索引/产品.md | 1 +
.cursor/agent/开发助理/项目索引/后端.md | 1 +
.cursor/agent/开发助理/项目索引/团队.md | 3 +-
.cursor/agent/开发助理/项目索引/小程序.md | 2 +
.cursor/agent/开发助理/项目索引/测试.md | 1 +
.cursor/agent/开发助理/项目索引/管理端.md | 1 +
.../管理端开发工程师/evolution/2026-03-24.md | 5 +
.../agent/软件测试/evolution/2026-03-24.md | 5 +
.../2026-03-24_需求与进度及三端闭环评审.md | 117 ++
.cursor/meeting/README.md | 1 +
.cursor/rules/soul-change-checklist.mdc | 6 +
.cursor/skills/assistant-doc-sync/sync-log.md | 2 +
.cursor/skills/change-checklist/SKILL.md | 11 +
.cursor/skills/miniprogram-dev/SKILL.md | 2 +-
.cursor/skills/role-flow-control/SKILL.md | 11 +-
miniprogram/app.js | 97 +-
.../components/env-switch/env-switch.js | 82 --
.../components/env-switch/env-switch.json | 3 -
.../components/env-switch/env-switch.wxml | 13 -
.../components/env-switch/env-switch.wxss | 30 -
.../pages/avatar-nickname/avatar-nickname.js | 20 +
.../avatar-nickname/avatar-nickname.wxml | 4 +-
.../avatar-nickname/avatar-nickname.wxss | 14 +
miniprogram/pages/chapters/chapters.js | 19 +-
miniprogram/pages/chapters/chapters.wxml | 3 +-
miniprogram/pages/chapters/chapters.wxss | 7 +
.../pages/member-detail/member-detail.js | 107 +-
miniprogram/pages/my/my.js | 12 +-
.../pages/profile-edit/profile-edit.wxss | 97 +-
miniprogram/pages/read/read.js | 78 +-
miniprogram/pages/referral/referral.js | 29 +-
miniprogram/utils/contentParser.js | 16 +-
miniprogram/utils/soulBridge.js | 22 +-
.../test/__pycache__/config.cpython-311.pyc | Bin 6107 -> 6107 bytes
.../conftest.cpython-311-pytest-9.0.2.pyc | Bin 3096 -> 3096 bytes
scripts/test/__pycache__/util.cpython-311.pyc | Bin 600 -> 600 bytes
.../conftest.cpython-311-pytest-9.0.2.pyc | Bin 373 -> 373 bytes
...tion_ckb_flow.cpython-311-pytest-9.0.2.pyc | Bin 39585 -> 39585 bytes
...s_ckb_api_key.cpython-311-pytest-9.0.2.pyc | Bin 12561 -> 12561 bytes
.../test_health.cpython-311-pytest-9.0.2.pyc | Bin 1607 -> 1607 bytes
.../测试报告-2026-03-24-本地核心功能场景.md | 165 +++
soul-admin/dist/assets/index-D2vksFq_.css | 1 +
soul-admin/dist/assets/index-DQUjJh41.js | 914 ---------------
soul-admin/dist/assets/index-Dk6CvQRe.js | 1006 ----------------
soul-admin/dist/assets/index-akpRWHlb.css | 1 -
soul-admin/dist/assets/index-l3L5iIYa.js | 1010 +++++++++++++++++
soul-admin/dist/assets/index-qjssBjc3.css | 1 -
soul-admin/dist/index.html | 26 +-
soul-admin/src/pages/content/ChapterTree.tsx | 41 +-
soul-admin/src/pages/content/ContentPage.tsx | 69 +-
.../src/pages/settings/SettingsPage.tsx | 39 +-
soul-admin/src/pages/users/UsersPage.tsx | 242 +++-
.../src/pages/withdrawals/WithdrawalsPage.tsx | 50 +-
soul-api/internal/database/database.go | 33 +
.../internal/handler/admin_withdrawals.go | 12 +-
soul-api/internal/handler/book.go | 38 +-
soul-api/internal/handler/ckb.go | 357 ++++--
soul-api/internal/handler/cron.go | 22 +
soul-api/internal/handler/db.go | 9 +-
soul-api/internal/handler/db_book.go | 39 +-
soul-api/internal/handler/db_ckb_leads.go | 35 +-
soul-api/internal/handler/db_person.go | 2 +-
soul-api/internal/model/ckb_lead.go | 26 +-
soul-api/internal/router/router.go | 3 +
68 files changed, 2461 insertions(+), 2535 deletions(-)
create mode 100644 .cursor/meeting/2026-03-24_需求与进度及三端闭环评审.md
delete mode 100644 miniprogram/components/env-switch/env-switch.js
delete mode 100644 miniprogram/components/env-switch/env-switch.json
delete mode 100644 miniprogram/components/env-switch/env-switch.wxml
delete mode 100644 miniprogram/components/env-switch/env-switch.wxss
create mode 100644 scripts/test/测试报告-2026-03-24-本地核心功能场景.md
create mode 100644 soul-admin/dist/assets/index-D2vksFq_.css
delete mode 100644 soul-admin/dist/assets/index-DQUjJh41.js
delete mode 100644 soul-admin/dist/assets/index-Dk6CvQRe.js
delete mode 100644 soul-admin/dist/assets/index-akpRWHlb.css
create mode 100644 soul-admin/dist/assets/index-l3L5iIYa.js
delete mode 100644 soul-admin/dist/assets/index-qjssBjc3.css
diff --git a/.cursor/agent/产品经理/evolution/2026-03-24.md b/.cursor/agent/产品经理/evolution/2026-03-24.md
index 81bc94fe..4820706f 100644
--- a/.cursor/agent/产品经理/evolution/2026-03-24.md
+++ b/.cursor/agent/产品经理/evolution/2026-03-24.md
@@ -9,3 +9,8 @@
### 当前状态
- 2026-03-20 需求(提现、我的收益、推广设置等)已与实现对齐。
- 主需求、落地推进表已基本同步;项目索引已补齐至 2026-03-24。
+
+## 需求与进度及三端闭环评审
+
+- 区分「用户主路径闭环」与「规则/风控闭环」;后者缺口见《产品意图与功能闭环分析》,用清单驱动排期而非推翻里程碑。
+- 待确认:VIP 支付前是否强制完善头像昵称(与当前 vip.js 策略二选一)。
diff --git a/.cursor/agent/后端工程师/evolution/2026-03-24.md b/.cursor/agent/后端工程师/evolution/2026-03-24.md
index f5cc56ad..66e0c449 100644
--- a/.cursor/agent/后端工程师/evolution/2026-03-24.md
+++ b/.cursor/agent/后端工程师/evolution/2026-03-24.md
@@ -15,3 +15,8 @@
### 待办
- router 补齐:users/rfm、users/journey-stats、shensheshou 共 5 个。
- 确认 /api/admin/settings 是否支持 ossConfig。
+
+## 需求与进度及三端闭环评审
+
+- 三端路由分组未被破坏;缺口为业务校验:提现 wechat_id、CKBJoin investor 付费、MatchUsers 发起者资料。
+- 失败响应需带 `needBindWechat` / `errorCode` 等稳定字段,配合小程序 `err.response` 约定(详见团队 evolution 与 meeting 纪要)。
diff --git a/.cursor/agent/团队/evolution/2026-03-24.md b/.cursor/agent/团队/evolution/2026-03-24.md
index 8677aaf8..8322cfa9 100644
--- a/.cursor/agent/团队/evolution/2026-03-24.md
+++ b/.cursor/agent/团队/evolution/2026-03-24.md
@@ -10,3 +10,13 @@
### 落地建议
- 各角色在完成功能开发或吸收经验时,主动更新项目索引。
- 橙子收尾时统一检查并补齐索引。
+
+## 需求与进度及三端闭环评审(跨角色)
+
+### 结论摘要
+- **主业务闭环**:阅读/付费/代付/分销/提现主链路在文档与里程碑上成立;**规则闭环**有已知缺口,以《产品意图与功能闭环分析》《需求未补齐清单》为准。
+- **三端配套**:小程序页面域与管理端路由域整体对齐;**miniprogram/admin/db 边界未被破坏**。
+- **工程契约**:统一「失败时 body 可解析」约定(`err.response`),否则后端 errorCode 设计无法在前端落地。
+
+### 详见
+- `.cursor/meeting/2026-03-24_需求与进度及三端闭环评审.md`
diff --git a/.cursor/agent/小程序开发工程师/evolution/2026-03-24.md b/.cursor/agent/小程序开发工程师/evolution/2026-03-24.md
index 419fc7a1..47bbb626 100644
--- a/.cursor/agent/小程序开发工程师/evolution/2026-03-24.md
+++ b/.cursor/agent/小程序开发工程师/evolution/2026-03-24.md
@@ -8,3 +8,16 @@
### 技术债
- 富文本渲染(rich-text)待实施,需确认 DB 格式后再推进。
+
+## 需求与进度及三端闭环评审
+
+- `app.request` 在 success:false 时应在 reject 前挂载 `err.response = data`,否则 needBindWechat/errorCode 分支失效。
+- 提现:my 与 referral 需统一微信号预检与 catch 处理。
+
+## 代码优化落地(2026-03-24)
+
+- **已实现**:`app.js` `_requestOnce` 对 `success:false` 与非 401 错误 `reject` 时挂载 `err.response`;`request` 外层 catch 重抛时保留 `response`。
+- **获客**:`soulBridge.submitCkbLead` 支持 `targetMemberId`/`targetMemberName`;`read.js` @某人、`member-detail` 全局/人物留资统一走 soulBridge;`targetNickname` 全局留资允许空串。
+- **分销**:阅读页余额/微信下单 `referralCode` 统一 `getReferralCodeForPay`。
+- **提现**:`referral.js` / `my.js` 在 `catch` 中识别 `e.response.needBindWechat`(原 success:false 分支实际不可达)。
+- **文档**:`miniprogram-dev/SKILL.md` 已补充 `err.response` 说明。
diff --git a/.cursor/agent/开发助理/项目索引/产品.md b/.cursor/agent/开发助理/项目索引/产品.md
index 61bee57b..e479d269 100644
--- a/.cursor/agent/开发助理/项目索引/产品.md
+++ b/.cursor/agent/开发助理/项目索引/产品.md
@@ -27,6 +27,7 @@ Soul 创业派对产品定位:面向创业者的社区/工具型小程序。
| 2026-03-18 | 文档归档整理:以《以界面定需求》为基准,重整需求口径/验收点/分享 singlePage 约束,写入产品经验库 | 已完成 |
| 2026-03-18 | 会议:超级个体开通后自动创建@人与支付前资料引导(头像+昵称) | 已完成 |
| 2026-03-24 | 会议:开发进度同步;项目索引补齐 2026-03-19/20 及之后记录;文档同步原则确认 | 已完成 |
+| 2026-03-24 | 会议:需求与进度及三端闭环评审;规则/风控缺口清单化;VIP 支付前资料规则待拍板 | 已完成 |
> **格式说明**:每次开发后在此追加一行,日期格式 YYYY-MM-DD,状态用:已完成 / 进行中 / 待续 / 搁置
diff --git a/.cursor/agent/开发助理/项目索引/后端.md b/.cursor/agent/开发助理/项目索引/后端.md
index 59948dd5..55d10908 100644
--- a/.cursor/agent/开发助理/项目索引/后端.md
+++ b/.cursor/agent/开发助理/项目索引/后端.md
@@ -40,6 +40,7 @@ soul-api(Go + Gin + GORM + MySQL)提供三组路由:`/api/miniprogram/*`
| 2026-03-18 | 会议:超级个体开通后自动创建@人(Person 绑定 userId 幂等)与资料完善 flags 方案 | 已完成 |
| 2026-03-20 | 提现:审批逻辑修复、fail_reason/error_message 落库、referral_config withdrawFee/enableAutoWithdraw | 已完成 |
| 2026-03-24 | 会议:开发进度同步;项目索引补齐;router/ossConfig 待确认 | 已完成 |
+| 2026-03-24 | 会议:三端闭环评审;提现/CKB/匹配后端校验与 errorCode 为 P0/P1 | 已完成 |
> **格式说明**:每次开发后在此追加一行,日期格式 YYYY-MM-DD,状态用:已完成 / 进行中 / 待续 / 搁置
diff --git a/.cursor/agent/开发助理/项目索引/团队.md b/.cursor/agent/开发助理/项目索引/团队.md
index 03f77197..8af82f44 100644
--- a/.cursor/agent/开发助理/项目索引/团队.md
+++ b/.cursor/agent/开发助理/项目索引/团队.md
@@ -31,9 +31,10 @@ Soul 创业派对全项目架构与约定:路由隔离(miniprogram/admin/db
| 2026-03-17 | 性能优化与 Redis 缓存方案落地:Redis 容灾回退 DB、OSS 上传容灾;/health 返回 database/redis 状态 | 已完成 |
| 2026-03-18 | 吸收经验:分享进入链路需兼容朋友圈 singlePage;单页模式不执行支付/自动领取等强动作并引导“前往小程序” | 已完成 |
| 2026-03-18 | 会议:超级个体开通后自动创建@人统一走 Person;幂等键绑定 userId;默认资料 flags 后端输出 | 已完成 |
+| 2026-03-24 | 会议:需求与进度及三端闭环评审;主路径闭环成立、规则闭环待加固;失败响应与 err.response 契约 | 已完成 |
> **格式说明**:每次架构级讨论后在此追加一行,日期格式 YYYY-MM-DD
---
-**最后更新**:2026-03-18
+**最后更新**:2026-03-24
diff --git a/.cursor/agent/开发助理/项目索引/小程序.md b/.cursor/agent/开发助理/项目索引/小程序.md
index 0960c021..d63e2650 100644
--- a/.cursor/agent/开发助理/项目索引/小程序.md
+++ b/.cursor/agent/开发助理/项目索引/小程序.md
@@ -44,6 +44,8 @@
| 2026-03-19 | 原生按钮覆盖定位(cover-view)经验入库 | 已完成 |
| 2026-03-20 | 手机号一键登录、login-modal 公用组件(read/my/gift-pay 引入);我的收益取 availableEarnings | 已完成 |
| 2026-03-24 | 会议:开发进度同步;项目索引补齐;富文本 rich-text 技术债待续 | 已完成 |
+| 2026-03-24 | 会议:三端闭环评审;app.request err.response;提现双入口与后端契约对齐 | 已完成 |
+| 2026-03-24 | 落地:app.request 挂 err.response;soulBridge 留资扩展;阅读页带码与 referral/my 提现 catch 对齐 | 已完成 |
> **格式说明**:每次开发后在此追加一行,日期格式 YYYY-MM-DD,状态用:已完成 / 进行中 / 待续 / 搁置
diff --git a/.cursor/agent/开发助理/项目索引/测试.md b/.cursor/agent/开发助理/项目索引/测试.md
index 1abd9de4..120cd795 100644
--- a/.cursor/agent/开发助理/项目索引/测试.md
+++ b/.cursor/agent/开发助理/项目索引/测试.md
@@ -33,6 +33,7 @@
| 2026-03-18 | 文档归档整理:按界面驱动口径统一验收;补充分享 singlePage 降级与引导为必测项 | 已完成 |
| 2026-03-18 | 会议:新增用例(资料默认阻断支付、Person 自动创建幂等、昵称变更同步回归) | 已完成 |
| 2026-03-24 | 会议:开发进度同步;项目索引补齐;提现/登录/收益/推广设置用例补充;singlePage、getPhoneNumber 边界 | 已完成 |
+| 2026-03-24 | 会议:三端闭环评审;API 绕过负例与双入口提现回归待契约落地后执行 | 已完成 |
---
diff --git a/.cursor/agent/开发助理/项目索引/管理端.md b/.cursor/agent/开发助理/项目索引/管理端.md
index 8068170d..e0477190 100644
--- a/.cursor/agent/开发助理/项目索引/管理端.md
+++ b/.cursor/agent/开发助理/项目索引/管理端.md
@@ -45,6 +45,7 @@
| 2026-03-18 | 会议:超级个体开通后自动创建@人;管理端可选展示 userId/来源以便排查重名 | 已完成 |
| 2026-03-20 | 提现审核:备注列(fail_reason/error_message)、自动审批开关;推广设置提现手续费 | 已完成 |
| 2026-03-24 | 会议:开发进度同步;项目索引补齐;DistributionPage Order.description 待修 | 已完成 |
+| 2026-03-24 | 会议:三端闭环评审;管理端与小程序配套整体齐全;类型债择机修 | 已完成 |
> **格式说明**:每次开发后在此追加一行,日期格式 YYYY-MM-DD,状态用:已完成 / 进行中 / 待续 / 搁置
diff --git a/.cursor/agent/管理端开发工程师/evolution/2026-03-24.md b/.cursor/agent/管理端开发工程师/evolution/2026-03-24.md
index 4414d4a4..fb42ca0d 100644
--- a/.cursor/agent/管理端开发工程师/evolution/2026-03-24.md
+++ b/.cursor/agent/管理端开发工程师/evolution/2026-03-24.md
@@ -8,3 +8,8 @@
### 待办
- DistributionPage Order.description 类型错误待修(与本次迁移无关,有空即修)。
+
+## 需求与进度及三端闭环评审
+
+- 管理端路由域与小程序 Tab/子业务整体配套(审核、配置、导师、匹配、存客宝相关)。
+- C 端绕过接口的风险主要靠后端 + 小程序请求层闭环,非管理端菜单能单独解决。
diff --git a/.cursor/agent/软件测试/evolution/2026-03-24.md b/.cursor/agent/软件测试/evolution/2026-03-24.md
index fbbb5873..80867342 100644
--- a/.cursor/agent/软件测试/evolution/2026-03-24.md
+++ b/.cursor/agent/软件测试/evolution/2026-03-24.md
@@ -6,3 +6,8 @@
- 提现审批、手机号登录、我的收益、推广设置、提现审核等新增功能需补充/更新用例。
- 边界必测:singlePage 分享进入、getPhoneNumber 隐私协议同意流程。
- 下次回归前完成用例更新。
+
+## 需求与进度及三端闭环评审
+
+- 补齐「仅调 API、绕过前端」负例:提现、CKBJoin、MatchUsers。
+- 后端与 err.response 契约落地后,做双入口提现(我的 / 推广)回归。
diff --git a/.cursor/meeting/2026-03-24_需求与进度及三端闭环评审.md b/.cursor/meeting/2026-03-24_需求与进度及三端闭环评审.md
new file mode 100644
index 00000000..74c145a6
--- /dev/null
+++ b/.cursor/meeting/2026-03-24_需求与进度及三端闭环评审.md
@@ -0,0 +1,117 @@
+# 会议纪要 - 2026-03-24 | 需求与进度及三端闭环评审
+
+> 本文件由**助理橙子**在会议结束后自动生成。
+
+---
+
+## 基本信息
+
+- **时间**:2026-03-24
+- **议题**:重新评估需求与开发进度;业务流程是否闭环;核心基础功能是否完整、有无被破坏;小程序与管理端是否配套齐全
+- **触发方式**:开个会议 + 分析报告
+- **参与角色**:产品经理、后端开发、管理端开发工程师、小程序开发工程师、测试人员
+- **主持人**:乘风(老板分身)
+
+---
+
+## 各角色发言
+
+### 【产品经理】
+
+- **需求基线**:《以界面定需求》《项目落地推进表》与代码大体一致;里程碑上阅读付费、代付、分销提现、分润配置等标记为已完成,与当前推进表描述一致。
+- **用户价值闭环**:C 端「看内容—买章节/VIP—推广收益—提现」主链路在文档层面已闭环;**风控与规则闭环**仍有缺口(见《产品意图与功能闭环分析》):提现微信号、资源对接购买校验、匹配发起者资料等依赖后端与请求层配合,否则存在「前端拦得住、接口拦不住」的灰区。
+- **待产品拍板**:2026-03-18 会议曾提「VIP 支付前需完善头像昵称」与当前 `vip.js`「购买前不拦截」不一致,需明确最终规则并同步三端。
+
+### 【后端开发】
+
+- **路由与分组**:`/api/miniprogram/*`、`/api/admin/*`、`/api/db/*` 分工清晰;导师、余额、提现确认收款、存客宝线索、dashboard 等能力在代码注释与 handler 中可核对,**三端隔离未被破坏**。
+- **闭环缺口(实现层)**:`WithdrawPost` 未强制校验 `wechat_id`;`CKBJoin`(investor)未校验付费;`MatchUsers` 未强制发起者联系方式;与《需求未补齐清单》《产品意图与功能闭环分析》一致。
+- **建议**:优先 P0 提现校验 + **统一失败体**(`errorCode` / `needBindWechat`),便于小程序在 `app.request` reject 后仍能从 `err.response` 分支引导(见文档 6.1 节)。
+
+### 【管理端开发工程师】
+
+- **配套情况**:`App.tsx` 路由覆盖 dashboard、orders、users、distribution、withdrawals、content、referral-settings、vip-roles、mentors、mentor-consultations、settings、payment、find-partner、match、match-records 等,与小程序 Tab(首页/目录/找伙伴/我的)及子业务(VIP、导师、代付、推广、提现审核)**总体配套齐全**。
+- **运营侧闭环**:提现审核、推广设置、内容/用户/订单管理支撑 C 端主链路;存客宝相关能力在管理端有配置入口(如 find-partner / CKB)。
+- **已知技术债**:索引中曾记 `DistributionPage Order.description` 类型问题,与本次「功能被破坏」无直接等价关系,但影响类型安全与维护。
+
+### 【小程序开发工程师】
+
+- **页面与能力**:`app.json` 注册页覆盖阅读、章节、找伙伴、我的、推广、代付、VIP、导师、资料、钱包、提现记录等,与后端 miniprogram 接口及管理端运营能力**方向一致**。
+- **闭环断裂点(工程层)**:`app.request` 在 `success: false` 时直接 reject,导致 `needBindWechat`、`errorCode` 等字段在部分页面成为**死分支**;`my` 与 `referral` 提现前对微信号要求不一致。
+- **技术债**:TipTap HTML → `rich-text` 与 @mention 仍为待确认/待实现项(需求未补齐清单)。
+
+### 【测试人员】
+
+- **回归重点**:在补齐后端校验与 `err.response` 后,需增加「绕过前端直接调 API」的负例;提现(含我的页与推广页两条入口)、资源对接、找伙伴提交各一条闭环用例。
+- **三端联调**:管理端改推广/手续费/自动提现开关后,小程序侧分润展示与提现流程需对照 `scripts/test/功能测试流程.md` 做一轮回归。
+
+---
+
+## 讨论过程
+
+- **【管理端开发工程师】回复【产品经理】**:管理端菜单已覆盖审核与配置,不单独挡 C 端绕过接口的风险;规则闭环必须后端 + 小程序请求层一致。
+- **【后端开发】回复【小程序开发工程师】**:仅加后端校验不够,若 reject 不携带 body,前端无法引导;建议约定 `err.response` 与文档 6.1~6.4 的调整一致后再测。
+- **【产品经理】回复【全员】**:「核心基础完整」指主链路可用;「未被破坏」指三端边界与里程碑仍在;当前缺口属**加固与一致性**,不推翻已完成里程碑,但应列入下一迭代 P0/P1。
+
+---
+
+## 会议决议
+
+1. **总评**:核心业务闭环在「产品—运营—用户主路径」上成立;**安全与规则闭环**存在已知缺口,以《产品意图与功能闭环分析》《需求未补齐清单》为执行清单,**不构成「核心功能整段被删」类事故**。
+2. **三端配套**:小程序主要场景与管理端审核/配置模块**整体配套**;后端 miniprogram/admin/db 分组保持,**无互窜型破坏**。
+3. **优先动作**:P0 提现 `wechat_id` 后端校验 + 小程序 `app.request` 失败时挂载 `err.response`;P1 CKBJoin / MatchUsers 校验与 `errorCode`;产品确认 VIP 支付前是否拦截头像昵称。
+4. **文档**:本评审结论与同日《开发进度同步会议》互补;详细技术项仍以 `开发文档/10、项目管理/` 下两份分析文档为准。
+
+---
+
+## 待办事项
+
+| 责任角色 | 任务 | 优先级 | 截止建议 |
+|---------|------|--------|---------|
+| 后端开发 | WithdrawPost 无 wechat_id 返回 needBindWechat;CKBJoin/MatchUsers 按文档补校验与 errorCode | P0/P1 | 下一迭代首周 |
+| 小程序开发工程师 | app.request reject 前设置 err.response;统一 my/referral 提现微信号与 catch 分支 | P0 | 与后端同学对齐后 |
+| 产品经理 | 确认 VIP 支付前是否必须完善头像昵称,并更新需求口径 | 中 | 本周 |
+| 测试人员 | 补齐绕过前端的 API 负例与双入口提现用例 | 中 | 联调后 |
+| 管理端开发工程师 | DistributionPage 类型债择机修复 | 低 | 排期内 |
+
+---
+
+## 问题与作答区
+
+| # | 问题 | 责任角色 | 作答 |
+|---|------|---------|------|
+| 1 | VIP(超级个体)支付前是否强制完善头像+昵称? | 产品经理 | (待补充) |
+| 2 | 富文本正文在 DB 中的存储格式是否已冻结(HTML 片段 / JSON)? | 产品经理 + 后端开发 | (待补充) |
+| 3 | ossConfig、router 扩展项是否仍阻塞环境切换? | 后端开发 | (待补充) |
+
+---
+
+## 各角色经验与业务理解更新
+
+### 产品经理
+
+- 闭环分两层:用户动线闭环 vs 规则/风控闭环;当前缺口集中在后者,应用清单文档驱动排期。
+
+### 后端开发
+
+- 失败响应需兼顾「HTTP 层」与「业务体字段」,便于小程序在统一 request 封装下做引导。
+
+### 管理端开发工程师
+
+- 配套评估以路由与业务域对照小程序页面域,存客宝/找伙伴/匹配记录等已覆盖运营侧。
+
+### 小程序开发工程师
+
+- reject 若不携带 body,所有「按字段分支」的后端设计都会落空;与后端约定同一错误契约。
+
+### 测试人员
+
+- 闭环测试需覆盖「仅调 API」场景,与纯 UI 点击路径同等重要。
+
+### 团队共享
+
+- 三端边界未被破坏;当前工作重点是**契约加固**(校验 + errorCode + err.response)与产品规则二选一(VIP 前资料)。
+
+---
+
+*会议纪要由助理橙子生成 | 各角色经验已同步至 `agent/{角色}/evolution/2026-03-24.md`*
diff --git a/.cursor/meeting/README.md b/.cursor/meeting/README.md
index 70427633..751eb581 100644
--- a/.cursor/meeting/README.md
+++ b/.cursor/meeting/README.md
@@ -81,3 +81,4 @@ YYYY-MM-DD_会议主题.md
| 2026-03-17 | 性能优化与 Redis 缓存方案落地 | 后端、管理端、小程序、测试、助理橙子 | [2026-03-17_性能优化与Redis缓存方案落地.md](2026-03-17_性能优化与Redis缓存方案落地.md) |
| 2026-03-18 | 超级个体开通后自动创建@人与资料引导 | 产品、后端、管理端、小程序、测试 | [2026-03-18_超级个体开通后自动创建@人与资料引导.md](2026-03-18_超级个体开通后自动创建@人与资料引导.md) |
| 2026-03-24 | 开发进度同步会议(查看代码与开发文档对齐) | 产品、后端、管理端、小程序、测试 | [2026-03-24_开发进度同步会议.md](2026-03-24_开发进度同步会议.md) |
+| 2026-03-24 | 需求与进度及三端闭环评审(业务闭环、核心功能、小程序与管理端配套) | 产品、后端、管理端、小程序、测试 | [2026-03-24_需求与进度及三端闭环评审.md](2026-03-24_需求与进度及三端闭环评审.md) |
diff --git a/.cursor/rules/soul-change-checklist.mdc b/.cursor/rules/soul-change-checklist.mdc
index 0d774e0d..4d55b4ff 100644
--- a/.cursor/rules/soul-change-checklist.mdc
+++ b/.cursor/rules/soul-change-checklist.mdc
@@ -8,6 +8,12 @@ alwaysApply: false
在 **miniprogram/**、**soul-admin/** 或 **soul-api/** 下做任何**修改、优化、新增**后,必须按下列项过一遍,确认关联层已同步,避免只改一端导致数据不一致或功能缺管理入口。
+## 零漏改原则(强制)
+
+- 任一端出现**新功能/新字段/新文案/新状态/新交互**,必须联想另外两端是否需要补齐。
+- 验收至少满足四项闭环:**DB 可存 + API 可读写 + 管理端可配置/可管理 + 小程序可渲染/可使用**。
+- 禁止只在小程序硬编码可运营内容(如角标、标签、按钮文案);默认应有管理端入口或配置来源。
+
## 一、按「你改了什么」对表检查
| 你改的是… | 必须同时检查/修改的关联 |
diff --git a/.cursor/skills/assistant-doc-sync/sync-log.md b/.cursor/skills/assistant-doc-sync/sync-log.md
index fff5e8d7..cdf9517d 100644
--- a/.cursor/skills/assistant-doc-sync/sync-log.md
+++ b/.cursor/skills/assistant-doc-sync/sync-log.md
@@ -10,3 +10,5 @@
- Skill 优化:assistant-doc-sync 新增 sync-log 记忆机制、assets 模板、Skill 撰写原则
- 会议收尾:开发进度同步会议;项目索引五角色补齐;纪要 `.cursor/meeting/2026-03-24_开发进度同步会议.md`
+- 会议收尾:需求与进度及三端闭环评审;纪要 `.cursor/meeting/2026-03-24_需求与进度及三端闭环评审.md`;经验追加至各角色 evolution 与项目索引
+- 小程序:app.request err.response 落地;soulBridge 留资统一 read/member-detail;阅读页 getReferralCodeForPay;referral/my 提现 catch;miniprogram-dev SKILL 更新
diff --git a/.cursor/skills/change-checklist/SKILL.md b/.cursor/skills/change-checklist/SKILL.md
index b025efa1..02118987 100644
--- a/.cursor/skills/change-checklist/SKILL.md
+++ b/.cursor/skills/change-checklist/SKILL.md
@@ -22,6 +22,16 @@ description: Soul 创业派对变更关联检查。miniprogram/soul-admin/soul-a
## 2. 核心思路:按「改动点」扫关联层
+### 2.0 强制补齐原则(新增)
+
+- 任一端(小程序 / 管理端 / 后端)出现**新展示、新字段、新交互、新文案、新状态**,必须主动联想另外两端是否需要补齐。
+- **禁止只修当前端**就结束:必须补到「可运营、可配置、可联调」闭环。
+- 典型漏项(必须防):
+ - 小程序新增角标/标签文案,但管理端没有输入入口;
+ - 管理端新增配置项,但小程序未读取该配置;
+ - 后端新增字段,但前端未映射、未展示、未提交。
+- 验收口径:至少满足 **数据可存(DB)+ 接口可读写(API)+ 管理端可改(Admin)+ 小程序可渲染(MP)** 四项。
+
### 2.1 改了前端(小程序或 soul-admin)
- **新增/改了展示或提交的字段**
@@ -32,6 +42,7 @@ description: Soul 创业派对变更关联检查。miniprogram/soul-admin/soul-a
- **小程序**新增了一个**完整功能**(如新活动、新入口)
- soul-api 的 **miniprogram** 下是否需要新接口?需要则加。
- **管理端**是否需要**配置、开关、审核、统计**?需要则在 soul-admin 加页面/菜单,并在 soul-api 的 **admin/db** 下加对应接口。
+ - 若涉及 UI 文案(如角标、按钮文案、标题),优先补到管理端可配置项;不可硬编码在小程序。
### 2.2 改了 soul-api(接口、model、表)
diff --git a/.cursor/skills/miniprogram-dev/SKILL.md b/.cursor/skills/miniprogram-dev/SKILL.md
index ed73dec1..e442c0e8 100644
--- a/.cursor/skills/miniprogram-dev/SKILL.md
+++ b/.cursor/skills/miniprogram-dev/SKILL.md
@@ -24,7 +24,7 @@ description: Soul 创业派对小程序开发规范。在 miniprogram/ 下编辑
- 正确:`app.request('/api/miniprogram/book/all-chapters')`、`app.request('/api/miniprogram/login', { method: 'POST', data: { code } })`
- 错误:`app.request('/api/book/all-chapters')`、`app.request('/api/admin/xxx')`、`/api/vip/status`(若 soul-api 未提供 miniprogram 下等价接口则视为错误,需统一走 miniprogram 组)。
- **静默请求**:不弹窗的请求(如推荐访问、统计)传 `silent: true`:`app.request(url, { ..., silent: true })`。
-- **错误处理**:`request` 已统一处理 200 且 `success: false`、401、4xx/5xx 与网络错误;页面只需 `try/catch` 或 `.then/.catch`,用 `_getApiErrorMsg` 的文案即可,勿重复造轮子。
+- **错误处理**:`request` 已统一处理 200 且 `success: false`、401、4xx/5xx 与网络错误;失败时 `reject` 的 `Error` 上会挂 **`err.response`**(即接口返回的 JSON 体),便于页面按 `needBindWechat`、`errorCode` 等字段分支。页面 `catch (e)` 中优先读 `e.response` 再回退 `e.message`。
---
diff --git a/.cursor/skills/role-flow-control/SKILL.md b/.cursor/skills/role-flow-control/SKILL.md
index 50be3083..8b6c02fa 100644
--- a/.cursor/skills/role-flow-control/SKILL.md
+++ b/.cursor/skills/role-flow-control/SKILL.md
@@ -18,6 +18,15 @@ description: Soul 创业派对开发协同流程。小程序开发工程师、
**开发风格约定**:各角色在各自端内开发时,**必须**按上表对应的 Skill 与 boundary 执行,保持代码风格、目录结构、命名与接口约定一致。不得跨路径调用(详见 soul-project-boundary)。
+### 1.1 跨端补齐责任(新增)
+
+- 三方工程师(小程序/管理端/后端)对同一功能负有**联想补齐**责任,不得只完成自己这一端。
+- 任一端新增「用户可见能力」(角标、文案、状态、入口、规则)时,需主动检查并补齐:
+ - 后端:是否有可持久化字段与读写接口;
+ - 管理端:是否有可运营输入或管理入口;
+ - 小程序:是否按接口/配置渲染而非硬编码。
+- 默认交付口径:**可配置、可运营、可联调、可验收**,否则视为未完成。
+
---
## 2. 协同流程总览(线框图)
@@ -189,7 +198,7 @@ sequenceDiagram
| 阶段 | 动作 |
|------|------|
-| **需求分析(关键)** | 分析该功能在管理端是否需要:
• 字段调整(列表/表单新增或修改)
• 配置页、开关、审核、统计
• 输出「需要」或「不需要」及具体项 |
+| **需求分析(关键)** | 分析该功能在管理端是否需要:
• 字段调整(列表/表单新增或修改)
• 配置页、开关、审核、统计
• 输出「需要」或「不需要」及具体项(如角标文案需有输入入口) |
| 并行开发 | **暂不开发**,等待小程序完成 |
| 管理端启动 | 小程序变更完成后,若需要则开始管理端调整 |
| 收尾 | 参与联调,过 soul-change-checklist |
diff --git a/miniprogram/app.js b/miniprogram/app.js
index 2c1f4a66..6fcdd7dc 100644
--- a/miniprogram/app.js
+++ b/miniprogram/app.js
@@ -9,7 +9,9 @@ const { checkAndExecute } = require('./utils/ruleEngine.js')
const DEFAULT_APP_ID = 'wxb8bbb2b10dec74aa'
const DEFAULT_MCH_ID = '1318592501'
const DEFAULT_WITHDRAW_TMPL_ID = 'u3MbZGPRkrZIk-I7QdpwzFxnO_CeQPaCWF2FkiIablE'
-const PRODUCTION_BASE_URL = 'https://soulapi.quwanzhi.com'
+// baseUrl 手动切换(注释方式):
+const API_BASE_URL = 'http://localhost:8080'
+// const API_BASE_URL = 'https://soulapi.quwanzhi.com'
const CONFIG_CACHE_KEY = 'mpConfigCacheV1'
// 与上传版本号对齐;设置页展示优先用 wx.getAccountInfoSync().miniProgram.version(正式版),否则用本字段
const APP_DISPLAY_VERSION = '1.7.2'
@@ -19,8 +21,8 @@ App({
// 与微信后台上传版本号一致,供设置页等展示(避免与线上 version 字段混淆)
appDisplayVersion: APP_DISPLAY_VERSION,
- // API:仓库默认生产;release 强制生产;develop/trial 可读 storage「apiBaseUrl」或用 env-switch
- baseUrl: 'https://soulapi.quwanzhi.com',
+ // API:仅使用代码常量手动切换,不再使用运行时开关
+ baseUrl: API_BASE_URL,
// 小程序配置 - 真实AppID
appId: DEFAULT_APP_ID,
@@ -98,39 +100,17 @@ App({
lastVipContactCheck: 0,
// 头像昵称检测:上次检测时间戳(与 VIP 检测同周期刷新)
lastAvatarNicknameCheck: 0,
+ // 登录后规则引擎触发时间(用于与本地引导去重,避免短时间二次弹窗)
+ lastAfterLoginRuleCheck: 0,
/** MBTI → 默认头像 URL(/api/miniprogram/config/mbti-avatars),供推广海报等 */
mbtiAvatarsMap: {},
mbtiAvatarsExpires: 0,
},
- /** 正式版强制生产 API,避免误传 localhost 导致审核/线上全挂 */
- initApiBaseUrl() {
- const KEY = 'apiBaseUrl'
- try {
- const info = wx.getAccountInfoSync?.()
- const env = info?.miniProgram?.envVersion || 'release'
- if (env === 'release') {
- this.globalData.baseUrl = PRODUCTION_BASE_URL
- try {
- const saved = wx.getStorageSync(KEY)
- if (saved && saved !== PRODUCTION_BASE_URL) wx.removeStorageSync(KEY)
- } catch (_) {}
- return
- }
- const saved = wx.getStorageSync(KEY)
- if (saved && typeof saved === 'string' && /^https?:\/\//.test(saved)) {
- this.globalData.baseUrl = String(saved).replace(/\/$/, '')
- } else {
- this.globalData.baseUrl = PRODUCTION_BASE_URL
- }
- } catch (_) {
- this.globalData.baseUrl = PRODUCTION_BASE_URL
- }
- },
-
onLaunch(options) {
- this.initApiBaseUrl()
+ // baseUrl 固定取 API_BASE_URL(通过注释切换)
+ this.globalData.baseUrl = API_BASE_URL
// 昵称等隐私组件需先授权:input type="nickname" 不会主动触发,需配合 wx.requirePrivacyAuthorize 使用
if (typeof wx.onNeedPrivacyAuthorization === 'function') {
wx.onNeedPrivacyAuthorization((resolve) => {
@@ -616,7 +596,12 @@ App({
const isVip = vipRes?.data?.isVip || this.globalData.isVip || false
this.globalData.isVip = isVip
if (!isVip) {
- this.checkAvatarNicknameAndGuide()
+ const now = Date.now()
+ const lastRuleCheck = Number(this.globalData.lastAfterLoginRuleCheck || 0)
+ // 登录后若规则引擎刚触发过 after_login,引导由规则引擎负责,避免与本地提示连弹
+ if (!lastRuleCheck || now - lastRuleCheck > 5000) {
+ this.checkAvatarNicknameAndGuide()
+ }
return
}
@@ -933,27 +918,6 @@ App({
return (msg && String(msg).trim()) ? String(msg).trim() : defaultMsg
},
- _shouldFallbackToProduction(err) {
- const msg = String((err && err.errMsg) || (err && err.message) || '').toLowerCase()
- return (
- msg.includes('connection_failed') ||
- msg.includes('err_proxy_connection_failed') ||
- msg.includes('dns') ||
- msg.includes('name not resolved') ||
- msg.includes('failed to fetch') ||
- msg.includes('econnrefused') ||
- msg.includes('network') ||
- msg.includes('timeout')
- )
- },
-
- _switchBaseUrlToProduction() {
- this.globalData.baseUrl = PRODUCTION_BASE_URL
- try {
- wx.setStorageSync('apiBaseUrl', PRODUCTION_BASE_URL)
- } catch (_) {}
- },
-
_requestOnce(url, options = {}, silent = false) {
const showError = (msg) => {
if (!silent && msg) wx.showToast({ title: msg, icon: 'none', duration: 2500 })
@@ -972,6 +936,11 @@ App({
},
success: (res) => {
const data = res.data
+ const rejectWithBody = (message, body) => {
+ const err = new Error(message)
+ if (body != null && typeof body === 'object') err.response = body
+ reject(err)
+ }
if (res.statusCode === 200) {
if (data && data.success === false) {
const msg = this._getApiErrorMsg(data, '操作失败')
@@ -979,7 +948,7 @@ App({
this.logout()
}
showError(msg)
- reject(new Error(msg))
+ rejectWithBody(msg, data)
return
}
resolve(data)
@@ -988,12 +957,14 @@ App({
if (res.statusCode === 401) {
this.logout()
showError('未授权,请重新登录')
- reject(new Error('未授权'))
+ const err = new Error('未授权')
+ if (data != null && typeof data === 'object') err.response = data
+ reject(err)
return
}
const msg = this._getApiErrorMsg(data, res.statusCode >= 500 ? '服务器异常,请稍后重试' : '请求失败')
showError(msg)
- reject(new Error(msg))
+ rejectWithBody(msg, data && typeof data === 'object' ? data : undefined)
},
fail: (err) => {
const msg = (err && err.errMsg)
@@ -1033,13 +1004,10 @@ App({
}
const promise = this._requestOnce(url, options, silent).catch(async (err) => {
- const currentBase = String(this.globalData.baseUrl || '').replace(/\/$/, '')
- if (currentBase !== PRODUCTION_BASE_URL && this._shouldFallbackToProduction(err)) {
- this._switchBaseUrlToProduction()
- return this._requestOnce(url, options, silent)
- }
const msg = (err && err.message) ? err.message : '网络异常,请重试'
- throw new Error(msg)
+ const next = new Error(msg)
+ if (err && err.response != null) next.response = err.response
+ throw next
})
if (method === 'GET') {
@@ -1102,8 +1070,9 @@ App({
this.globalData.vipExpireDate = user.vipExpireDate || ''
// 首次登录注册:强制跳转 avatar-nickname 修改头像昵称(不弹窗)
if (res.isNewUser === true && this._needsAvatarNickname(user)) {
- setTimeout(() => wx.redirectTo({ url: '/pages/avatar-nickname/avatar-nickname' }), 1000)
+ setTimeout(() => wx.redirectTo({ url: '/pages/avatar-nickname/avatar-nickname?from=new_user' }), 1000)
} else {
+ this.globalData.lastAfterLoginRuleCheck = Date.now()
checkAndExecute('after_login', null)
setTimeout(() => this.checkVipContactRequiredAndGuide(), 1200)
setTimeout(() => this.connectWsHeartbeat(), 2000)
@@ -1173,8 +1142,9 @@ App({
this.globalData.vipExpireDate = user.vipExpireDate || ''
// 首次登录注册:强制跳转 avatar-nickname
if (res.isNewUser === true && this._needsAvatarNickname(user)) {
- setTimeout(() => wx.redirectTo({ url: '/pages/avatar-nickname/avatar-nickname' }), 1000)
+ setTimeout(() => wx.redirectTo({ url: '/pages/avatar-nickname/avatar-nickname?from=new_user' }), 1000)
} else {
+ this.globalData.lastAfterLoginRuleCheck = Date.now()
checkAndExecute('after_login', null)
setTimeout(() => this.checkVipContactRequiredAndGuide(), 1200)
}
@@ -1234,8 +1204,9 @@ App({
// 首次登录注册:强制跳转 avatar-nickname
if (res.isNewUser === true && this._needsAvatarNickname(user)) {
- setTimeout(() => wx.redirectTo({ url: '/pages/avatar-nickname/avatar-nickname' }), 1000)
+ setTimeout(() => wx.redirectTo({ url: '/pages/avatar-nickname/avatar-nickname?from=new_user' }), 1000)
} else {
+ this.globalData.lastAfterLoginRuleCheck = Date.now()
checkAndExecute('after_login', null)
setTimeout(() => this.checkVipContactRequiredAndGuide(), 1200)
}
diff --git a/miniprogram/components/env-switch/env-switch.js b/miniprogram/components/env-switch/env-switch.js
deleted file mode 100644
index 1aa3fa51..00000000
--- a/miniprogram/components/env-switch/env-switch.js
+++ /dev/null
@@ -1,82 +0,0 @@
-/**
- * 开发环境专用:可拖拽的 baseURL 切换悬浮按钮
- * 正式环境(release)不显示
- */
-const PRODUCTION_URL = 'https://soulapi.quwanzhi.com'
-const STORAGE_KEY = 'apiBaseUrl'
-const POSITION_KEY = 'envSwitchPosition'
-
-const URL_OPTIONS = [
- { label: '生产', url: PRODUCTION_URL },
- { label: '本地', url: 'http://localhost:8080' },
- { label: '测试', url: 'https://souldev.quwanzhi.com' },
-]
-
-Component({
- data: {
- visible: false,
- x: 20,
- y: 120,
- currentLabel: '生产',
- areaWidth: 375,
- areaHeight: 812,
- },
-
- lifetimes: {
- attached() {
- try {
- const accountInfo = wx.getAccountInfoSync?.()
- const envVersion = accountInfo?.miniProgram?.envVersion || 'release'
- if (envVersion === 'release') {
- return
- }
- const sys = wx.getSystemInfoSync?.() || {}
- const areaWidth = sys.windowWidth || 375
- const areaHeight = sys.windowHeight || 812
- const saved = wx.getStorageSync(POSITION_KEY)
- const pos = saved ? JSON.parse(saved) : { x: 20, y: 120 }
- // 与 app.js 一致:storage 优先,否则用 globalData(已按 env 自动切换)
- const current = wx.getStorageSync(STORAGE_KEY) || getApp().globalData?.baseUrl || PRODUCTION_URL
- const opt = URL_OPTIONS.find(o => o.url === current) || URL_OPTIONS[0]
- this.setData({
- visible: true,
- x: pos.x ?? 20,
- y: pos.y ?? 120,
- currentLabel: opt.label,
- areaWidth,
- areaHeight,
- })
- } catch (_) {
- this.setData({ visible: false })
- }
- },
- },
-
- methods: {
- onTap() {
- const items = URL_OPTIONS.map(o => o.label)
- const current = wx.getStorageSync(STORAGE_KEY) || PRODUCTION_URL
- const idx = URL_OPTIONS.findIndex(o => o.url === current)
- wx.showActionSheet({
- itemList: items,
- success: (res) => {
- const opt = URL_OPTIONS[res.tapIndex]
- wx.setStorageSync(STORAGE_KEY, opt.url)
- const app = getApp()
- if (app && app.globalData) {
- app.globalData.baseUrl = opt.url
- }
- this.setData({ currentLabel: opt.label })
- wx.showToast({ title: `已切到${opt.label}`, icon: 'none', duration: 1500 })
- },
- })
- },
-
- onMovableChange(e) {
- const { x, y } = e.detail
- if (typeof x === 'number' && typeof y === 'number') {
- wx.setStorageSync(POSITION_KEY, JSON.stringify({ x, y }))
- }
- },
- },
-})
diff --git a/miniprogram/components/env-switch/env-switch.json b/miniprogram/components/env-switch/env-switch.json
deleted file mode 100644
index 467ce294..00000000
--- a/miniprogram/components/env-switch/env-switch.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "component": true
-}
diff --git a/miniprogram/components/env-switch/env-switch.wxml b/miniprogram/components/env-switch/env-switch.wxml
deleted file mode 100644
index 6b33bbf9..00000000
--- a/miniprogram/components/env-switch/env-switch.wxml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
- {{currentLabel}}
-
-
diff --git a/miniprogram/components/env-switch/env-switch.wxss b/miniprogram/components/env-switch/env-switch.wxss
deleted file mode 100644
index 1e970d6f..00000000
--- a/miniprogram/components/env-switch/env-switch.wxss
+++ /dev/null
@@ -1,30 +0,0 @@
-.env-area {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- pointer-events: none;
- z-index: 9999;
-}
-
-.env-btn {
- width: 72rpx;
- height: 72rpx;
- pointer-events: auto;
-}
-
-.env-btn-inner {
- width: 100%;
- height: 100%;
- background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 22rpx;
- font-weight: 600;
- color: #fff;
- box-shadow: 0 4rpx 12rpx rgba(34, 197, 94, 0.4);
- border: 2rpx solid rgba(255, 255, 255, 0.3);
-}
diff --git a/miniprogram/pages/avatar-nickname/avatar-nickname.js b/miniprogram/pages/avatar-nickname/avatar-nickname.js
index 5293a5b5..e6bb1dc9 100644
--- a/miniprogram/pages/avatar-nickname/avatar-nickname.js
+++ b/miniprogram/pages/avatar-nickname/avatar-nickname.js
@@ -3,6 +3,7 @@
* 登录后若仍为默认头像/昵称,在此修改;仅头像与昵称两项
*/
const app = getApp()
+const { trackClick } = require('../../utils/trackClick')
Page({
data: {
@@ -14,15 +15,21 @@ Page({
nicknameInputFocus: false,
/** 规则引擎传入:avatar | nickname,用于高亮对应区块 */
uiFocus: '',
+ fromNewUser: false,
},
onLoad(options) {
this.setData({ statusBarHeight: app.globalData.statusBarHeight || 44 })
+ const fromNewUser = String(options.from || '').toLowerCase() === 'new_user'
const focus = String(options.focus || '').toLowerCase()
if (focus === 'avatar' || focus === 'nickname') {
this.setData({ uiFocus: focus })
}
+ this.setData({ fromNewUser })
this.loadFromUser()
+ if (fromNewUser) {
+ trackClick('avatar_nickname', 'page_view', '新注册引导页')
+ }
if (focus === 'nickname') {
setTimeout(() => {
if (typeof wx.requirePrivacyAuthorize === 'function') {
@@ -92,6 +99,7 @@ Page({
async onChooseAvatar(e) {
const tempAvatarUrl = e.detail?.avatarUrl
if (!tempAvatarUrl) return
+ trackClick('avatar_nickname', 'btn_click', '选择头像')
await this.uploadAndSaveAvatar(tempAvatarUrl)
},
@@ -132,6 +140,9 @@ Page({
}
wx.hideLoading()
wx.showToast({ title: '头像已更新', icon: 'success' })
+ if (this.data.fromNewUser) {
+ trackClick('avatar_nickname', 'form_step_done', '头像更新完成')
+ }
} catch (e) {
wx.hideLoading()
wx.showToast({ title: e.message || '上传失败', icon: 'none' })
@@ -150,6 +161,7 @@ Page({
wx.showToast({ title: '请输入昵称', icon: 'none' })
return
}
+ trackClick('avatar_nickname', 'btn_click', '完成保存')
this.setData({ saving: true })
try {
await app.request({
@@ -162,6 +174,13 @@ Page({
if (avatar) app.globalData.userInfo.avatar = avatar
wx.setStorageSync('userInfo', app.globalData.userInfo)
}
+ if (this.data.fromNewUser) {
+ wx.setStorageSync('new_user_guide_done_at', Date.now())
+ trackClick('avatar_nickname', 'form_submit', '新注册引导完成', {
+ hasAvatar: !!avatar,
+ nicknameLen: nickname.length,
+ })
+ }
wx.showToast({ title: '保存成功', icon: 'success' })
setTimeout(() => getApp().goBackOrToHome(), 800)
} catch (e) {
@@ -171,6 +190,7 @@ Page({
},
goToFullProfile() {
+ trackClick('avatar_nickname', 'nav_click', '编辑完整档案')
wx.navigateTo({ url: '/pages/profile-edit/profile-edit' })
},
})
diff --git a/miniprogram/pages/avatar-nickname/avatar-nickname.wxml b/miniprogram/pages/avatar-nickname/avatar-nickname.wxml
index 6a920990..6bcd8c62 100644
--- a/miniprogram/pages/avatar-nickname/avatar-nickname.wxml
+++ b/miniprogram/pages/avatar-nickname/avatar-nickname.wxml
@@ -10,10 +10,12 @@
- 设置对外展示信息
+ {{fromNewUser ? '欢迎加入,先完成基础信息' : '设置对外展示信息'}}
+ 新用户引导
请先换一张清晰头像,伙伴更容易认出你。
请改一个真实好记的昵称,方便伙伴称呼你。
头像与昵称会出现在名片与匹配卡片上,方便伙伴认出你。
+ 完成后可继续编辑完整档案(手机号、行业、MBTI 等)。
diff --git a/miniprogram/pages/avatar-nickname/avatar-nickname.wxss b/miniprogram/pages/avatar-nickname/avatar-nickname.wxss
index 8b179dce..532378e3 100644
--- a/miniprogram/pages/avatar-nickname/avatar-nickname.wxss
+++ b/miniprogram/pages/avatar-nickname/avatar-nickname.wxss
@@ -64,11 +64,25 @@
color: #5EEAD4;
margin-bottom: 12rpx;
}
+.guide-badge {
+ margin-bottom: 10rpx;
+ padding: 6rpx 16rpx;
+ font-size: 20rpx;
+ color: #22d3ee;
+ border-radius: 999rpx;
+ border: 1rpx solid rgba(34, 211, 238, 0.4);
+ background: rgba(34, 211, 238, 0.1);
+}
.guide-desc {
font-size: 26rpx;
color: rgba(148, 163, 184, 0.95);
line-height: 1.5;
}
+.guide-desc-sub {
+ margin-top: 8rpx;
+ color: rgba(148, 163, 184, 0.85);
+ font-size: 24rpx;
+}
.avatar-section {
display: flex;
diff --git a/miniprogram/pages/chapters/chapters.js b/miniprogram/pages/chapters/chapters.js
index 0a4e0fdd..25730185 100644
--- a/miniprogram/pages/chapters/chapters.js
+++ b/miniprogram/pages/chapters/chapters.js
@@ -54,7 +54,8 @@ Page({
// mp_config.mpUi.chaptersPage
chaptersBookTitle: '一场SOUL的创业实验场',
- chaptersBookSubtitle: '来自Soul派对房的真实商业故事'
+ chaptersBookSubtitle: '来自Soul派对房的真实商业故事',
+ chaptersNewBadgeText: 'NEW'
},
onLoad() {
@@ -71,13 +72,20 @@ Page({
_applyChaptersMpUi() {
const c = app.globalData.configCache?.mpConfig?.mpUi?.chaptersPage || {}
+ const h = app.globalData.configCache?.mpConfig?.mpUi?.homePage || {}
+ const newBadgeText = String(c.newBadgeText || c.sectionNewBadgeText || h.latestSectionTitle || 'NEW').trim() || 'NEW'
this.setData({
chaptersBookTitle: String(c.bookTitle || '一场SOUL的创业实验场').trim() || '一场SOUL的创业实验场',
chaptersBookSubtitle: String(c.bookSubtitle || '来自Soul派对房的真实商业故事').trim() ||
- '来自Soul派对房的真实商业故事'
+ '来自Soul派对房的真实商业故事',
+ chaptersNewBadgeText: newBadgeText
})
},
+ _normalizeBadgeText(v) {
+ return String(v || '').trim().slice(0, 8)
+ },
+
async loadFeatureConfig() {
try {
if (app.globalData.features && typeof app.globalData.features.searchEnabled === 'boolean') {
@@ -122,10 +130,14 @@ Page({
let icon = String(p.icon || '').trim()
if (icon && !isSafeImageSrc(icon)) icon = ''
const iconEmoji = icon ? '' : partEmojiForBodyIndex(idx)
+ const partBadgeText = this._normalizeBadgeText(
+ p.badgeText || p.badge_text || p.partBadgeText || p.part_badge_text
+ )
return {
id: p.id,
icon,
iconEmoji,
+ iconText: partBadgeText,
title: p.title,
subtitle: p.subtitle || '',
chapterCount: p.chapterCount || 0,
@@ -176,6 +188,9 @@ Page({
isFree: r.isFree === true || (r.price !== undefined && r.price === 0),
price: r.price ?? 1,
isNew: r.isNew === true || r.is_new === true,
+ newBadgeText: this._normalizeBadgeText(
+ r.newBadgeText || r.new_badge_text || r.sectionBadgeText || r.section_badge_text || r.badgeText || r.badge_text
+ ),
isPremium
})
})
diff --git a/miniprogram/pages/chapters/chapters.wxml b/miniprogram/pages/chapters/chapters.wxml
index b3a972fe..17538071 100644
--- a/miniprogram/pages/chapters/chapters.wxml
+++ b/miniprogram/pages/chapters/chapters.wxml
@@ -73,6 +73,7 @@