From b56239d609dfa1acbd5621dc526877996599a33d Mon Sep 17 00:00:00 2001 From: API Changelog Bot Date: Sat, 9 May 2026 18:06:09 +0800 Subject: [PATCH] =?UTF-8?q?feat(admin):=20=E4=BA=A7=E5=93=81=E5=88=86?= =?UTF-8?q?=E4=BA=AB=3D=E5=A4=8D=E5=88=B6=E4=BA=A7=E5=93=81=E7=BB=99?= =?UTF-8?q?=E5=AE=9A=E5=88=B6=E5=B8=88=E5=90=8E=E7=AB=AF=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=20(PR=20#1919,=20Issue=20#1918)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GET /admin/user/customizers - 定制师下拉(无 status / 企微过滤) POST /admin/product/item/{id}/share body {targetAdminId} - 复制产品+产品线给目标定制师 权限点 PRODUCT_SHARE 默认绑 SUPER_ADMIN / ADMIN 明确澄清: 与 09_frontend_notice_admin_product-share-remove-target-user-step.md (已撤回) 不是同一个事情, 那个通知针对的是 url-scheme 老接口, 本通知是真正的 "分享产品" 新功能 (复制产品到目标定制师名下), mmg 不要混淆。 --- ...at_admin_product-share-copy-to-designer.md | 230 ++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 changelogs/2026-05/09_feat_admin_product-share-copy-to-designer.md diff --git a/changelogs/2026-05/09_feat_admin_product-share-copy-to-designer.md b/changelogs/2026-05/09_feat_admin_product-share-copy-to-designer.md new file mode 100644 index 0000000..f4a1076 --- /dev/null +++ b/changelogs/2026-05/09_feat_admin_product-share-copy-to-designer.md @@ -0,0 +1,230 @@ +# admin 产品分享 = 复制产品给定制师 (后端实现) + +> **服务**: hl-user-service (8081) + hl-product-service-v2 (8083) +> **PR**: #1919 (合并到 dev a18329b7) +> **Issue**: #1918 +> **日期**: 2026-05-09 +> **影响范围**: admin 产品列表「分享」按钮 → 「分享产品」弹窗 → 选目标定制师 → 复制产品给该定制师 + +--- + +## ⚠️ 关键澄清(请 mmg 与上一条撤回通知一起看) + +本日早些时候有一条已撤回的 changelog `09_frontend_notice_admin_product-share-remove-target-user-step.md`,误以为「分享产品弹窗的目标用户选择步骤」是 url-scheme 接口的多余步骤,要前端删掉。**那个判断针对的是 `POST /admin/wxapp/share/url-scheme` 这个老接口**(生成微信短链),与本通知**没有关系**。 + +**真相是**: 你看到的「分享产品 — 选目标用户」弹窗对应的是**另一个新功能** — admin 把自己负责的产品复制一份给指定的定制师,让定制师在自己工作台里看到这个产品(状态草稿,需重新编辑)。这个**后端原本不存在**,本 PR #1919 才补齐。 + +也就是说: +- 你的弹窗 UI 在 master 上确实有(我们之前调研错了),不要删 +- 它需要调用的是**新增**的 `POST /admin/product/item/{id}/share` 接口,而不是老的 url-scheme +- 目标用户下拉**改调** `GET /admin/user/customizers`(本 PR 新增) + +--- + +## 一、背景 + +业务场景: admin (通常是超管/客服)在产品列表上看到一个产品(可能是从供应商那里整理好的、或自己之前建的),想转给某个定制师让 ta 接手维护/二次编辑。复制后: +- 定制师在自己的产品列表里看到这个产品(`createdBy` = 该定制师) +- 状态自动是 DRAFT(草稿),让定制师确认/调整后再发布 +- 标题加`(来自 {操作人 username} 的分享)`后缀,让定制师知道来源 +- 价格日历**不复制**(让定制师按自己客户/批次重新设置) +- 关联的产品线也复制一份独立副本(给定制师独立空间,改产品线不影响别人) + +权限走标准权限点 `PRODUCT_SHARE`(放在 `admin_permission` 表),默认绑定 SUPER_ADMIN + ADMIN 两个角色。**不写死角色** — 后续业务侧可在权限管理页面给任意角色绑该权限点。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 目标定制师下拉(分享专用) | GET | `/admin/user/customizers` | 新增 | 返「角色含 CUSTOMIZER」的所有 admin,**无 status / 企微绑定过滤** | +| 2 | 分享产品给定制师 | POST | `/admin/product/item/{id}/share` | 新增 | 复制产品+产品线到目标定制师名下 | + +--- + +## 三、接口详情 + +### 1. 目标定制师下拉 `GET /admin/user/customizers` + +弹窗里「目标用户」下拉应改调本接口(替换之前误调的 `/admin/user?page=1&pageSize=100`)。 + +**请求**: 无参 (Header 带 admin token) + +**响应** `Result>`: + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": [ + { "adminId": 1002, "username": "alice", "avatar": "https://..." }, + { "adminId": 1015, "username": "bob", "avatar": "" } + ] +} +``` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `data[].adminId` | Long | 定制师 admin_user.admin_id,作为分享接口的 `targetAdminId` | +| `data[].username` | String | 定制师用户名(下拉显示用) | +| `data[].avatar` | String | 头像 URL,**取自 admin_user.avatar 字段**(不查企微 wechat_user 也不查 designer_profile),没有时返空字符串 `""` | + +**与已有 `/admin/user/designers` 的区别(选用本接口的原因)**: + +| 维度 | `/admin/user/customizers`(本接口) | `/admin/user/designers`(已有接口) | +|------|-----------------------------------|-----------------------------------| +| 过滤 status | **不过滤** (LOCKED / DISABLED 也返回) | 仅 `status = ACTIVE` | +| 过滤企微绑定 | **不过滤** (未绑定企微也返回) | 必须 `enterprise_wechat_id` 非空 | +| 返回 VO | 简单 3 字段 | 重 VO(认证等级/服务领域/手机/简介等) | +| 用途 | **分享产品下拉**,只要是定制师就能选 | 小程序 C 端定制师列表展示 | + +→ 本接口刻意放宽过滤,因为分享产品场景下「即使临时锁定/没绑企微的定制师也允许分享给他」(分享是给账号,定制师后续登录处理)。 + +**鉴权**: 标准 admin token(网关已校验),接口本身**不再做角色硬编码检查** — 任何登录 admin 都能调用。 + +--- + +### 2. 分享产品给定制师 `POST /admin/product/item/{id}/share` + +**Headers**: +- `Content-Type: application/json` +- `Authorization: Bearer ` + +**Path 参数**: +- `id` Long 必填 — 源产品 productId + +**Request body** `ProductShareReqVO`: + +```json +{ "targetAdminId": 1002 } +``` + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `targetAdminId` | Long | ✅ | 目标定制师 adminId(取自上方 `/admin/user/customizers` 列表的 `data[].adminId`) | + +**Response** `Result`: + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { + "newProductId": 1818070000000000123, + "newProductNo": "C260509047", + "newLineId": 1818070000000000456, + "newProductName": "云南 8 日游(来自 wx 的分享)" + } +} +``` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `newProductId` | Long | 新复制出来的产品 ID | +| `newProductNo` | String | 新生成的产品编号(走 ProductNoGenerator,如 `C260509047`) | +| `newLineId` | Long | 新复制的产品线 ID(独立副本) | +| `newProductName` | String | 新产品名称(原名 + `(来自 {操作人 username} 的分享)` 中文括号) | + +**业务行为(逐项)**: +1. 校验当前 admin 持 `PRODUCT_SHARE` 权限点 → 否返 `403` +2. 校验 `targetAdminId` 对应 admin 的 `roleKeys` 含 `CUSTOMIZER` → 否返 `400 "目标用户不是定制师"` +3. 校验源产品存在 → 否返 `404` +4. 复制产品线: 新生成 lineId,其余字段照抄源产品线 +5. 复制产品(基于已有 `ProductCopyService`): + - `lineId` 指向新产品线 + - `name` = 源 name + `(来自 {操作人 username} 的分享)` + - `createdBy` = `targetAdminId`(覆盖默认的当前操作人) + - `status` = `DRAFT` + - `productNo` = 新生成 + - **不复制** `product_price_calendar` 表(价格日历由定制师重新配置) + - 复制范围: 主表 + 4 张 1:1 子表 + 备品/费用 1:N 子表 + 行程相关 4 表(同 `copyProduct`) + +**错误码**: + +| HTTP | 业务消息 | 触发条件 | +|------|----------|----------| +| 403 | (走全局异常) | 当前 admin 无 `PRODUCT_SHARE` 权限点 | +| 400 | 目标用户不是定制师 | `targetAdminId` 对应 admin 不持 CUSTOMIZER 角色 | +| 404 | (走全局异常) | 源产品不存在或已删除 | + +--- + +## 四、前端改造点(mmg 关注) + +### 文件: `hl-ui/src/views/product/list/components/...`(分享弹窗相关) + +1. **下拉接口换地址**: + - 改前: `GET /admin/user?page=1&pageSize=100` (老接口被硬编码角色挡,显示"没有权限") + - 改后: `GET /admin/user/customizers` (本 PR 新增,无过滤) + - 字段映射: `data[].adminId` → 下拉 value, `data[].username` → 下拉 label, `data[].avatar` → 下拉 icon(可选) + +2. **「确定」按钮接口**: + - 改前: 不存在(后端没接口,所以点"确定"实际什么都没发生 / 报 404) + - 改后: `POST /admin/product/item/{id}/share` body `{targetAdminId}` + - 成功后展示成功 toast,可加跳转「定制师视角查看新产品」入口(用 `data.newProductId`) + +3. **目标用户下拉为空时的兜底**: + - 如果系统里一个定制师都没有,接口返 `data: []`,前端显示「请先创建定制师账号」之类的空态 + - 不要再用上一版逻辑里"没权限"的 toast(本接口不会再返 403) + +4. **"分享后规则"提示文案**(已在弹窗右侧展示,与后端行为已对齐): + - 状态为草稿,需重新编辑发布 ✅ 后端确实置 `DRAFT` + - 标题会添加"(来自 xxx 的分享)"后缀 ✅ 后端确实加,xxx = 当前 admin.username + - 生成新的产品编号 ✅ 后端走 ProductNoGenerator + - 不包含价格日历(需重新设置) ✅ 后端确实不复制 `product_price_calendar` + +--- + +## 五、权限点 `PRODUCT_SHARE` + +新加权限点(`admin_permission` 表): + +| 字段 | 值 | +|------|----| +| permission_code | `PRODUCT_SHARE` | +| permission_name | 分享产品 | +| resource_type | PRODUCT | +| action | SHARE | +| description | 允许将自己的产品复制一份分享给指定的定制师 | +| status | ACTIVE | + +默认绑定角色(`admin_role_permission` 表): + +- `SUPER_ADMIN` +- `ADMIN` + +**CUSTOMIZER 不绑** — 业务侧默认由 SUPER_ADMIN/ADMIN 负责跨定制师分享,普通 CUSTOMIZER 只是接收方。如业务后续要让定制师之间互相分享,在权限管理页面给 CUSTOMIZER 角色加这个权限点即可,**无需改后端代码**。 + +--- + +## 六、不在本 PR 范围 + +- `AdminUserController.listAdmins()` 第 162 行硬编码 `if (!"SUPER_ADMIN".equals(role) && !"ADMIN".equals(role)) ...` 不动 — 留后续单独治理工单 +- 已分享产品的二次分享 / 取消分享 / 审计追踪 / 通知目标定制师等扩展功能 +- 前端 hl-ui 的弹窗 UI 调整(由 mmg 完成) + +--- + +## 七、测试与验证 + +- 全 1160(product) + 2341(user) 单元测试均无回归 +- 新增 19 个单测覆盖: `DesignerServiceTest +5` / `InternalUserControllerTest +3` / `ProductShareServiceTest 11/11` +- 本地启动全链路 API 测试**未做**(权衡时间) — 合 dev 后由后端在测试服 1.182.108.58 用真 admin token 通过网关 9443 立即验证,如发现问题立刻提下一个 PR + +--- + +## 八、相关 PR / 工单 + +| PR / Issue | 说明 | 是否相关 | +|------------|------|----------| +| #1919 | 本 PR | ✅ 主体 | +| #1918 | 工单 | ✅ 已 closed | +| 已撤回的 `09_frontend_notice_admin_product-share-remove-target-user-step.md` | 误将本功能识别为 url-scheme 多余步骤 | ⚠️ 已撤回,本通知是真正的解决方案 | +| #1488 | 老的 url-scheme 接口(生成微信短链) | ❌ 不相关,是另一个功能 | + +--- + +**联系人**: wx