headsup(mini): 小程序 onShareAppMessage/Timeline 需链式继承 adminId 才能正确归属定制师 (配合 PR #2977)

这个提交包含在:
API Changelog Bot 2026-05-24 15:46:12 +08:00
父节点 cb7898dc2d
当前提交 208f67b53a

查看文件

@ -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