hl-api-changelog/changelogs/2026-05/24_headsup_mini_share_chain_adminid.md

8.7 KiB

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, 这部分已经对)

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)

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: 同样没拼)

onShareTimeline() {
  ...
  return {
    title: ...,
    query: `id=${productId}&shareOpenId=${openId}`,  // ← 缺 adminId
    imageUrl: ...
  };
}

🛠️ 前端推荐改造 (链式继承 adminId)

最简方案 — 不需要调任何后端接口, 沿用 storage 桥接的 adminId:

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 已上线):

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 响应结构:

{
  "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
  • roleKeysCUSTOMIZER
  • 通过 → 订单 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 wx/HL#2977 + 工单 wx/HL#2976