feat(collab): 新增订单打标签弹窗 tag-picker 聚合接口 + 批量替换标签接口(#3988)
这个提交包含在:
父节点
8ab53749fa
当前提交
2c04507372
@ -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 <token>`),userId 从 `X-Admin-Id` 请求头派生,前端不传 |
|
||||
| 幂等性 | 只读,天然幂等 |
|
||||
| 限流 | 无特殊限流 |
|
||||
|
||||
### 接口二:批量全量替换标签
|
||||
|
||||
| 项目 | 内容 |
|
||||
|------|------|
|
||||
| 方法 | PUT |
|
||||
| 路径 | `/v3/admin/order/{orderId}/tags` |
|
||||
| 接口名 | 批量全量替换订单标签 |
|
||||
| 认证 | 需要 JWT(`Authorization: Bearer <token>`) |
|
||||
| 幂等性 | 非幂等(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<List<TagPickerItemVO>>`
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| 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>`(替换后该订单的全量标签快照)
|
||||
|
||||
`OrderTagListRespVO` 字段:
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| attached | `List<OrderTagVO>` | 替换后该订单已挂标签列表 |
|
||||
|
||||
`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
|
||||
- **后端负责人**:腰苏图
|
||||
正在加载...
x
在新工单中引用
屏蔽一个用户