diff --git a/changelogs/2026-05/24_headsup_mini_share_chain_adminid.md b/changelogs/2026-05/24_headsup_mini_share_chain_adminid.md new file mode 100644 index 0000000..14c887a --- /dev/null +++ b/changelogs/2026-05/24_headsup_mini_share_chain_adminid.md @@ -0,0 +1,230 @@ +# heads-up(小程序分享): onShareAppMessage / onShareTimeline 需链式继承 adminId + +> **类型**: heads-up + 前端待办 (后端无需改动) +> **关联 PR/Issue**: 后端 PR #2977 (Closes #2976) 配套 +> **日期**: 2026-05-24 +> **影响范围**: 小程序内原生分享渠道(分享给朋友 / 分享到朋友圈)的客人下单定制师归属 +> **接收方**: mmg (前端 hl-mini 仓库) +> **前端**: **需要改造** (本仓后端已就绪,等前端补齐链路) + +--- + +## 🎯 背景 + +wx 实测发现:**admin (id=1001 SUPER_ADMIN, 非定制师) 在管理端「分享到小程序」生成的 URL Scheme,客人扫码下单后订单的「定制师」被错绑成 admin 自己,不是中间转发的真定制师 wx**。 + +根因: URL Scheme 的 t 参数在微信 wxa/generatescheme 时**加密锁死**,中间转发不能改写,query 里的 `adminId=` 永远是"生成人"。 + +后端已修 (PR #2977 已合 dev,测试服真测拒绝路径通过): +- `POST /admin/wxapp/share/url-link` 和 `/url-scheme` 加 guard +- 仅 `status=ACTIVE` + `roleKeys 含 CUSTOMIZER` + `wechatUserid 非空` 的 admin 可生成 +- 错误码 `200312 SHARE_REQUIRES_BOUND_CUSTOMIZER`, 文案: **"仅持定制师角色且已绑定企业微信的成员可生成分享链接"** + +但这只覆盖了**链路 A** (admin 端管理后台「分享 URL Scheme」按钮)。**链路 B / C 还没覆盖**,需要 mmg 前端配合。 + +--- + +## 📊 三个分享渠道现状 + +| # | 渠道 | 入口 | 当前前端实现 | 客人下单归属 | +|---|---|---|---|---| +| **A** | admin/定制师 在管理端点「分享 URL Scheme」 | hl-ui MiniappShareModal.vue | URL query 含 `adminId=<生成人id>`, 小程序 onLoad 接到存 storage | ✅ PR #2977 后只有定制师能生成, customizer 正确 | +| **B** | 定制师在小程序产品详情页点右上角「分享给朋友」 | `onShareAppMessage()` | **只传 `shareOpenId`, 没传 adminId** | ❌ 客人下单 customizerId 空 → 兜底随机 | +| **C** | 定制师在小程序产品详情页点「分享到朋友圈」 | `onShareTimeline()` | **只传 `shareOpenId`, 没传 adminId** | ❌ 同上 | + +--- + +## 🔬 代码定位 + +仓库 `mmg/hl-mini` main 分支, 文件 `packages/product/core/detail/detail.js`: + +### L191-199 onLoad (接收链路 A 的 adminId, 这部分已经对) + +```js +onLoad(options) { + const productId = options.id; + this._shareOpenId = options.shareOpenId || ''; + this._adminId = options.adminId || ''; + // 链路 A 的 adminId 已写入 storage 桥接订单详情页 + if (this._adminId) { + storage.setShareCustomizerId(this._adminId); + } + ... +} +``` + +### L1464-1472 onShareAppMessage (BUG: 没拼 adminId) + +```js +onShareAppMessage() { + const { product, productId, shareCardImage, heroBgImage } = this.data; + const openId = storage.get('openId') || ''; + return { + title: product.title || '呼籁旅行 ...', + path: `/packages/product/core/detail/detail?id=${productId}&shareOpenId=${openId}`, // ← 缺 adminId + imageUrl: shareCardImage || heroBgImage || '' + }; +} +``` + +### L1474-1482 onShareTimeline (BUG: 同样没拼) + +```js +onShareTimeline() { + ... + return { + title: ..., + query: `id=${productId}&shareOpenId=${openId}`, // ← 缺 adminId + imageUrl: ... + }; +} +``` + +--- + +## 🛠️ 前端推荐改造 (链式继承 adminId) + +**最简方案 — 不需要调任何后端接口, 沿用 storage 桥接的 adminId**: + +```js +onShareAppMessage() { + const { product, productId, shareCardImage, heroBgImage } = this.data; + const openId = storage.get('openId') || ''; + // 链式继承: 当前页面是从分享链接进入的话, _adminId 不为空; 否则从 storage 兜底 + const adminId = this._adminId || storage.get('shareCustomizerId') || ''; + // path 拼上 adminId, 这样客人扫码进店 onLoad 时 options.adminId 仍是原始定制师 + const adminPart = adminId ? `&adminId=${adminId}` : ''; + return { + title: product.title || '呼籁旅行 ...', + path: `/packages/product/core/detail/detail?id=${productId}&shareOpenId=${openId}${adminPart}`, + imageUrl: shareCardImage || heroBgImage || '' + }; +} + +onShareTimeline() { + const { product, productId, shareCardImage, heroBgImage } = this.data; + const openId = storage.get('openId') || ''; + const adminId = this._adminId || storage.get('shareCustomizerId') || ''; + const adminPart = adminId ? `&adminId=${adminId}` : ''; + return { + title: product.title || '呼籁旅行 ...', + query: `id=${productId}&shareOpenId=${openId}${adminPart}`, + imageUrl: shareCardImage || heroBgImage || '' + }; +} +``` + +### 链式继承的业务效果 + +1. wx (定制师) 通过 admin 端生成首个链接 `?adminId=wx_id` 发给客人 A +2. A 进店 onLoad → `_adminId = wx_id` → 存 storage +3. **A 在小程序里再点「分享给朋友」给客人 B** → onShareAppMessage 沿用 wx_id → path 含 `adminId=wx_id` +4. B 扫码进店下单 → customizerId=wx_id → customizer 归 wx ✅ +5. 第 N 次转发都归原始定制师, **营销链路追溯完整** + +--- + +## 🔧 升级方案 (可选, 若定制师自己直接打开小程序产品页要分享) + +如果定制师 wx **没有经过 admin 端链接, 自己直接进入小程序** 想要分享产品 → `_adminId` 为空, storage 也无 → 链式继承拿不到 adminId → 客人下单兜底随机。 + +解决方法: 页面 onLoad 调后端 `GET /mp/customizer/me` (PR #2913 已上线): + +```js +onLoad(options) { + this._shareOpenId = options.shareOpenId || ''; + this._adminId = options.adminId || ''; + if (this._adminId) { + storage.setShareCustomizerId(this._adminId); + } + // 升级: 如果当前用户是定制师本人, 把自己 adminId 也写入 storage 用于分享 + if (!this._adminId) { + customizerApi.me().then(res => { + if (res?.data?.isCustomizer && res?.data?.adminId) { + this._adminId = res.data.adminId; + storage.setShareCustomizerId(this._adminId); + } + }); + } + ... +} +``` + +`/mp/customizer/me` 响应结构: +```json +{ + "code": 200, + "data": { + "isCustomizer": true, + "adminId": "1760000000000099", + "customizerName": "王骁", + "avatar": "..." + } +} +``` + +--- + +## 📋 改造范围 (其他可分享页面) + +类似的分享 hook 在以下页面也需要同步加 adminId (基于 grep 结果): + +- ✅ `packages/product/core/detail/detail.js` (重点, 上述代码) +- `packages/product/mengma/detail/detail.js` (小蒙马产品详情) +- `packages/scenic/detail/detail.js` (景区详情) +- `packages/hotel/detail/detail.js` (酒店详情) +- `packages/restaurant/detail/detail.js` (餐厅详情) +- `packages/guide/detail/detail.js` (向导详情) +- `packages/user/travel-guide/travel-guide.js` (旅行攻略) +- `packages/product/compare/compare.js` (对比页) +- `pages/index/index.js` (首页) +- `pages/trip/trip.js` (行程页) + +建议**至少先改 4 个核心入口**: 核心产品详情 / 小蒙马产品详情 / 主题(产品线)详情 / 首页。其他后续补。 + +--- + +## ✅ 验证方式 (前端改完后) + +1. wx (定制师) 在管理端「分享 URL Scheme」生成 → 给客人 A +2. A 进店 → 在小程序内再点产品页右上角「分享给朋友」 → 给客人 B +3. B 进店 onLoad → `console.log(this._adminId)` 应等于 wx 的 admin_id +4. B 下单 → 后端订单的 `customizer_id` 字段应等于 wx 的 admin_id +5. 朋友圈分享: A 点「分享到朋友圈」, 别人通过朋友圈进店, 同样验证 `_adminId` 链式继承 + +--- + +## 🔗 后端接口清单 (mmg 不需要改, 已就绪) + +| 接口 | 路径 | 用途 | +|---|---|---| +| 获取当前定制师身份 | `GET /mp/customizer/me` | 当前 C 端用户是定制师? 返 adminId | +| 客人下单 | `POST /mp/order/create` | 接受 `customizerId` (字符串透传雪花 ID) + `sharerOpenid` (溯源用) | +| Admin 生成 URL Link / Scheme | `POST /admin/wxapp/share/url-link` `/url-scheme` | PR #2977 后限定持 CUSTOMIZER + 绑企微的 admin | + +`customizerId` 在订单创建时由后端 `CustomizerValidator.resolve()` 校验: +- `status = ACTIVE` +- `roleKeys` 含 `CUSTOMIZER` +- 通过 → 订单 customizer = 该 adminId +- 不通过 → 订单 customizer 走 `assignRandomCustomizer` 兜底随机 + +--- + +## 📅 时间线建议 + +| # | 任务 | 负责人 | 状态 | +|---|---|---|---| +| 1 | 后端 admin 端 guard PR #2977 | wx | ✅ 已合 dev / 测试服真测拒绝路径通过 | +| 2 | 前端 onShareAppMessage / Timeline 链式继承 adminId (核心产品页) | **mmg** | ⏳ | +| 3 | 前端其他分享入口同步改 (景区/酒店/...等) | mmg | ⏳ 可分多 PR | +| 4 | 前端调 `/mp/customizer/me` 兜底定制师本人首次进入 (可选升级) | mmg | ⏳ 可选 | +| 5 | 后端 PR #2977 合 main → 上正式 | wx | ⏳ | + +--- + +## 联系人 + +后端: wx (呼籁旅行) +前端: mmg + +如有疑问看后端 PR https://git.1814.love:8443/wx/HL/pulls/2977 + 工单 https://git.1814.love:8443/wx/HL/issues/2976