From 2c0450737224d5ad8aad49efb0834b3a8caecd5c Mon Sep 17 00:00:00 2001 From: yaosutu <770858045@qq.com> Date: Thu, 18 Jun 2026 17:17:09 +0800 Subject: [PATCH] =?UTF-8?q?feat(collab):=20=E6=96=B0=E5=A2=9E=E8=AE=A2?= =?UTF-8?q?=E5=8D=95=E6=89=93=E6=A0=87=E7=AD=BE=E5=BC=B9=E7=AA=97=20tag-pi?= =?UTF-8?q?cker=20=E8=81=9A=E5=90=88=E6=8E=A5=E5=8F=A3=20+=20=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E6=9B=BF=E6=8D=A2=E6=A0=87=E7=AD=BE=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=EF=BC=88#3988=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...-tag-picker批量打标签-新增接口-管理后台.md | 274 ++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100644 changelogs-v2/2026-06/18_3982_order-tag-picker批量打标签-新增接口-管理后台.md diff --git a/changelogs-v2/2026-06/18_3982_order-tag-picker批量打标签-新增接口-管理后台.md b/changelogs-v2/2026-06/18_3982_order-tag-picker批量打标签-新增接口-管理后台.md new file mode 100644 index 0000000..e0f6033 --- /dev/null +++ b/changelogs-v2/2026-06/18_3982_order-tag-picker批量打标签-新增接口-管理后台.md @@ -0,0 +1,274 @@ +# 订单打标签弹窗 — 标签选择器聚合 + 批量替换接口(管理后台) + +- **日期**:2026-06-18 +- **端类型**:管理后台 +- **Issue**:[#3982](https://git.1814.love:8443/wx/HL/issues/3982) +- **PR**:[#3988](https://git.1814.love:8443/wx/HL/pulls/3988) +- **Commit**:[b8403032e9](https://git.1814.love:8443/wx/HL/commit/b8403032e94f3c73d9e13982619c1cacdebf98ea) +- **服务**:hl-order-service-v3(端口 8086) +- **后端负责人**:腰苏图 + +--- + +## ① 接口背景 + +订单打标签弹窗需要两个新接口支撑: + +1. **标签选择器**(tag-picker):打开弹窗时,聚合返回「当前管理员的标签库全集 + 每条是否已挂本订单 + 订单上已有的游离标签」,供前端渲染勾选状态。 +2. **批量全量替换标签**(replace-tags):弹窗点「确认」时,按入参列表全量替换订单标签(diff 软删/新增/改色),同步更新标签库使用计数。 + +原有 4 个增量操作接口(POST /tag 新增单条、DELETE /tag/:id 删单条、PATCH /tag/:id 改单条、GET /tags 查列表)**不变,不影响现有调用**。 + +--- + +## ② 变更清单 + +| 序号 | 变更类型 | 接口 | 说明 | +|------|---------|------|------| +| 1 | 新增接口 | `GET /v3/admin/order/{orderId}/tag-picker` | 打标签弹窗数据(标签库全集 + selected 标记 + 游离标签) | +| 2 | 新增接口 | `PUT /v3/admin/order/{orderId}/tags` | 批量全量替换订单标签 | + +--- + +## ③ 接口详情 + +### 接口一:标签选择器 + +| 项目 | 内容 | +|------|------| +| 方法 | GET | +| 路径 | `/v3/admin/order/{orderId}/tag-picker` | +| 接口名 | 打标签弹窗数据(标签库全集 + selected 标记 + 游离标签) | +| 认证 | 需要 JWT(`Authorization: Bearer `),userId 从 `X-Admin-Id` 请求头派生,前端不传 | +| 幂等性 | 只读,天然幂等 | +| 限流 | 无特殊限流 | + +### 接口二:批量全量替换标签 + +| 项目 | 内容 | +|------|------| +| 方法 | PUT | +| 路径 | `/v3/admin/order/{orderId}/tags` | +| 接口名 | 批量全量替换订单标签 | +| 认证 | 需要 JWT(`Authorization: Bearer `) | +| 幂等性 | 非幂等(diff 写入,重复调用以最后一次入参为准) | +| 限流 | 无特殊限流 | + +--- + +## ④ 接口入参 + +### 接口一:GET /v3/admin/order/{orderId}/tag-picker + +#### 4.1 路径参数 + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| orderId | Long | 是 | 订单 ID | + +无请求体。 + +### 接口二:PUT /v3/admin/order/{orderId}/tags + +#### 4.1 路径参数 + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| orderId | Long | 是 | 订单 ID | + +#### 4.2 请求体字段 + +| 字段名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| tags | List | 是(不可为 null) | 标签列表;传空数组 [] 表示清空该订单所有标签 | +| tags[].tagName | String | 是 | 标签名,长度 1~50 字 | +| tags[].tagColor | String | 否 | HEX 色值,格式 #RRGGBB(如 #FF6B6B);不传则使用默认色 #5B8FF9 | + +> **注意**:tags=null 会触发 400 校验失败。清空全部标签请传 {"tags":[]}。 + +--- + +## ⑤ 出参字段 + +### 接口一:GET /v3/admin/order/{orderId}/tag-picker + +返回类型:`Result>` + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| tagName | String | 标签名 | +| tagColor | String | HEX 色值,如 #5B8FF9 | +| tagScope | String | 作用域,见枚举说明;游离标签为 null | +| isPinned | Boolean | 是否置顶;游离标签为 false | +| sortOrder | Integer | 排序值;游离标签为 null | +| selected | Boolean | 是否已挂在当前订单;true 表示已挂 | +| inLibrary | Boolean | 是否来自标签库;false 表示游离标签(订单上存在但库中已删) | + +列表顺序:标签库条目优先(is_pinned DESC → sort_order ASC → last_used_at DESC),游离标签追加末尾(selected=true, inLibrary=false)。 + +### 接口二:PUT /v3/admin/order/{orderId}/tags + +返回类型:`Result`(替换后该订单的全量标签快照) + +`OrderTagListRespVO` 字段: + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| attached | `List` | 替换后该订单已挂标签列表 | + +`OrderTagVO` 字段: + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| id | Long(String 序列化) | tag 主键 | +| orderId | Long(String 序列化) | 关联订单 ID | +| tagName | String | 标签名 | +| tagColor | String | 标签色值,如 #FF6B6B | +| creator | String | 创建人姓名(定制师姓名或 SYSTEM) | +| createdAt | String(ISO-8601) | 创建时间,如 2026-06-18T10:30:00 | + +--- + +## ⑥ 枚举 / 数据字典 + +### tagScope(标签作用域) + +| 值 | 含义 | +|----|------| +| SYSTEM | 系统标签(全公司共享) | +| PERSONAL | 个人标签(仅创建人可见) | +| null | 游离标签(订单上存在但已从标签库中删除,已无作用域归属) | + +--- + +## ⑦ 错误码 + +以下为两个新接口涉及的错误码: + +| 错误码 | 含义 | 触发场景 | +|--------|------|---------| +| 581401 | 订单不存在 | orderId 在数据库中不存在(两个接口均会触发) | +| 581402 | 标签名为空或超长(≤50 字) | tagName 空白或超过 50 字(替换接口) | +| 581404 | tagColor 格式非法(必须 # + 6 位十六进制) | tagColor 传了非 #RRGGBB 格式值(替换接口) | + +其他已有错误码不变:581403 同名私人标签已存在(POST /tag 新增时触发,本次两个新接口不触发)。 + +--- + +## ⑧ 示例 + +### 8.1 典型成功 + +场景:打开打标签弹窗,标签库有 2 条,订单已挂其中 1 条 + 1 条游离标签。 + +tag-picker 请求:GET /v3/admin/order/1234567890123456789/tag-picker + +响应(3 条:1 条已选库标签、1 条未选库标签、1 条游离标签): + +```json +{"code":200,"data":[ + {"tagName":"VIP客户","tagColor":"#5B8FF9","tagScope":"SYSTEM","isPinned":true,"sortOrder":1,"selected":true,"inLibrary":true}, + {"tagName":"需要接送","tagColor":"#FF6B6B","tagScope":"PERSONAL","isPinned":false,"sortOrder":10,"selected":false,"inLibrary":true}, + {"tagName":"旧标签已删","tagColor":"#AAAAAA","tagScope":null,"isPinned":false,"sortOrder":null,"selected":true,"inLibrary":false} +]} +``` + +批量替换标签请求:PUT /v3/admin/order/1234567890123456789/tags + +请求体: +```json +{"tags":[{"tagName":"VIP客户","tagColor":"#5B8FF9"},{"tagName":"需要接送","tagColor":"#FF6B6B"}]} +``` + +响应: +```json +{"code":200,"data":{"attached":[ + {"id":"1881234567890000001","orderId":"1234567890123456789","tagName":"VIP客户","tagColor":"#5B8FF9","creator":"张三","createdAt":"2026-06-18T10:30:00"}, + {"id":"1881234567890000002","orderId":"1234567890123456789","tagName":"需要接送","tagColor":"#FF6B6B","creator":"张三","createdAt":"2026-06-18T10:30:01"} +]}} +``` + +### 8.2 边界情况 + +清空全部标签,请求体 {"tags":[]}, 响应 {"code":200,"data":{"attached":[]}} + +tag-picker 返回空列表(标签库为空且订单未挂标签):{"code":200,"data":[]} + +### 8.3 业务失败 + +场景一:订单不存在(581401) +请求:GET /v3/admin/order/9999999999999999999/tag-picker +响应:{"code":581401,"msg":"订单不存在"} + +场景二:标签名为空(581402) +最小复现请求体:{"tags":[{"tagName":""}]} +响应:{"code":581402,"msg":"标签名为空或超长(≤50 字)"} + +场景三:tagColor 格式非法(581404) +最小复现请求体:{"tags":[{"tagName":"VIP","tagColor":"red"}]} +响应:{"code":581404,"msg":"tagColor 格式非法(必须 # + 6 位十六进制)"} + +场景四:tags=null 触发 400 +请求体:{"tags":null} +响应:{"code":400,"msg":"tags 不能为 null(清空请传空数组 [])"} + +--- + +## ⑨ 业务边界 + +适用场景: +- GET tag-picker:打开打标签弹窗时调用。任意订单状态均可调用。 +- PUT tags:弹窗点「确认」时调用,全量替换(不是追加)。任意订单状态均可调用。 + +不适用场景: +- 两个接口均不做订单状态限制。 +- 公司隔离由后端 JWT 校验,前端不传公司 ID。 + +特殊边界: +- PUT tags 是全量替换语义:后端自动 diff(删除不在入参里的旧标签、新增入参里新出现的标签、更新颜色变化的标签)。 +- 游离标签(inLibrary=false)在 PUT tags 入参中可通过 tagName 匹配保留,也可不传从而删除。 +- 标签库使用计数(used_count / last_used_at):PUT tags 操作后,命中标签库的标签会自动更新使用计数,无需前端额外操作。 +- tagColor 不传时,新增标签默认色为 #5B8FF9;已有标签改色时以入参为准。 + +--- + +## ⑩ 修改前后对比 + +本次为纯新增接口,无历史接口修改,无对比项。 + +原有 4 个增量操作接口完全不变: + +| 接口 | 状态 | +|------|------| +| POST /v3/admin/order/{orderId}/tag | 不变 | +| DELETE /v3/admin/order/{orderId}/tag/{tagId} | 不变 | +| PATCH /v3/admin/order/{orderId}/tag/{tagId} | 不变 | +| GET /v3/admin/order/{orderId}/tags | 不变 | + +--- + +## ⑪ 影响评估 / 回滚 + +- **破坏兼容性**:无(纯新增,不影响现有接口) +- **前端同步上线**:无强制要求,新接口不上线不会影响现有功能 +- **回滚方案**:若需回滚,仅需回滚 hl-order-service-v3 至本 PR 前版本;无 DDL 变更,数据库无需操作 + +--- + +## ⑫ 注意事项 + +1. PUT tags 是全量替换,不是追加:每次调用以入参为最终状态全量覆盖,请前端在弹窗「确认」时一次性传完整列表。 +2. tags=null 触发 400:清空标签请传 {"tags":[]}, 不要传 {"tags":null}。 +3. Long 类型 ID 用字符串序列化:OrderTagVO.id 和 OrderTagVO.orderId 均为雪花 ID,JSON 序列化为 String,前端 JS 用字符串处理,不要转 Number。 +4. tag-picker 中游离标签排在末尾:inLibrary=false 的条目追加末尾,前端可据此做分组显示或特殊标注。 +5. 颜色默认值:tagColor 不传时后端默认 #5B8FF9,前端如需展示颜色预览可读此默认值。 +6. 零 DDL:本次无数据库结构变更,order-v3 重启后即生效。 + +--- + +## ⑬ 关联 / 联系人 + +- **Issue**:https://git.1814.love:8443/wx/HL/issues/3982 +- **PR**:https://git.1814.love:8443/wx/HL/pulls/3988 +- **Commit**:https://git.1814.love:8443/wx/HL/commit/b8403032e94f3c73d9e13982619c1cacdebf98ea +- **后端负责人**:腰苏图