docs(frontend-notice): 服务管理编辑保存误调 POST 创建接口 (categoryCode 校验失败)

正式 admin 后台 service 编辑保存时报 'categoryCode: 分类编码不能为空',根因
是前端误调 POST /admin/service/item 而非 PUT /admin/service/item/{id} (后端
错误文案与 ServiceCreateRequest.categoryCode 的 @NotBlank message 字面完全
匹配)。后端 PATCH 语义正确,不动;通知 mmg 修前端,并自检 supplies/activity/
restaurant/cost/material 等同类模块。
这个提交包含在:
API Changelog Bot 2026-05-09 14:58:36 +08:00
父节点 1dd018fb57
当前提交 66a46d8cf4

查看文件

@ -0,0 +1,95 @@
# 前端 BUG 通知 — 服务管理编辑保存误调 POST 创建接口 (categoryCode 校验失败)
**日期**: 2026-05-09
**类型**: 前端 BUG (admin 后台 hl-ui)
**模块**: 资源管理 → 服务管理 (Service Item)
**通知**: @mmg
**后端是否需改动**: 否(本是误调创建接口,后端 PATCH 语义正确,不能为对齐而加 NotBlank)
**严重性**: 正式环境用户已遇
---
## 现象
正式 `admin.1814.love/service/list`,编辑列表里现有服务"蒙古族·礼仪接机"(已显示分类「机场接送」),进入编辑抽屉,切到「地理位置」tab 改了字段后点「确定」保存。
右上角红色 toast: **`categoryCode: 分类编码不能为空`**
服务在列表里**已经有 categoryCode**(列表显示「机场接送」标签),但保存却报"为空" — 不合常理,排查确认是前端误把 update 当 create 调了。
---
## 根因 (后端代码反推链)
后端 `ServiceItemController` 有两条接口:
| 方法 | 路径 | 入参 VO | categoryCode 校验 |
|------|------|--------|----|
| **POST** | `/admin/service/item` (创建) | `ServiceCreateRequest` | `@NotBlank(message="分类编码不能为空")` ← 与 toast 文案**字面完全匹配** |
| **PUT** | `/admin/service/item/{serviceId}` (更新) | `ServiceUpdateRequest` | 仅 `@Size(max=32)`,**无 NotBlank**,patch 语义 null 跳过 |
错误消息文案与 `ServiceCreateRequest.categoryCode``@NotBlank.message` 字面完全一致 → 前端编辑保存时打的是 **POST 创建**,而不是 **PUT 更新**;且请求体里没有 categoryCode (大概率只 pick 了改动 tab 的字段,即 dirty 提交) → 触发 Create 的 NotBlank 校验。
---
## 复现取证(请 mmg 在 DevTools 验证)
1. **Network**: 抓「服务编辑」保存按钮触发的请求,看 method 是 `POST` 还是 `PUT`,看 URL 是 `/admin/service/item` 还是 `/admin/service/item/{id}`
2. **Payload**: 看 body 里是否带了 `categoryCode` 字段
3. **Sources**: 搜 `service/item``serviceApi.update``serviceApi.create`,确认编辑场景调用了哪个 API method
4. **State**: 检查表单 store / form ref 是否在打开抽屉时把详情接口返的 `categoryCode` 字符串写进了表单 model
---
## 建议修复方向 (前端)
**优先级 1**: 编辑模式必须打 `PUT /admin/service/item/{serviceId}`,不能打 `POST /admin/service/item`
- 检查抽屉组件区分 create / edit 两种 mode 的逻辑(常见做法是看 form 里的 id/serviceId 是否存在)
- 编辑模式下保存按钮的 onClick 应进 update 分支
**优先级 2**: 表单 model 初始化时,把详情接口返的 `categoryCode` 显式写入 form data;保存时**整体提交** form data,而非只 pick 改动 tab 的字段
- 多 tab 表单常见坑 — "地理位置 tab 改了 → 只提交位置字段",但如果走 PUT 后端是 patch 语义这样也 OK,关键还是 method 别错
---
## 后端为什么不"对齐"加 NotBlank
有人会想:那把 `ServiceUpdateRequest.categoryCode` 也加 `@NotBlank` 不就早早暴露问题了?**不可以**。
`ServiceUpdateRequest` 是 PATCH 语义(`updateServiceItem` 实现里 null 跳过),前端**任意部分更新**(只改地理位置、只改媒体素材、只改描述)都不应被强制要求带 categoryCode。加 NotBlank 会让所有部分更新失败。
正确做法是:**前端调对接口**,后端保持 PATCH 语义不变。
---
## 连锁影响 — mmg 自检清单
资源管理下其他几个模块的 CreateRequest 都有相同的 `@NotBlank(message="分类编码不能为空")` 校验,UpdateRequest 多半也是 PATCH 语义没加。**如果前端这几个模块的编辑保存都是同一套"区分 create/edit"代码**,只要服务管理这里有 bug,其他模块大概率连锁:
- 备品管理 (supplies)
- 游玩项目管理 (activity / play-item)
- 餐厅管理 (restaurant)
- 额外成本 (cost)
- 素材库 (material)
请 mmg 一起自检:点这些列表里现有记录的「编辑」,改非 categoryCode 字段后保存,看是否同样报 categoryCode 不能为空。如果是,统一修复 update 路径。
---
## 后端 RespVO 是否够用 (无需改)
确认过:
- `ServiceItemVO` (详情 RespVO) 含 `categoryCode` 字段
- `ServiceItemListVO` (列表 RespVO) 含 `categoryCode` 字段
详情接口已经返了 `categoryCode` 字符串,前端编辑模式可直接拿来回填 form,**无需任何额外接口**。
---
## 验收
mmg 修完前端后,验收点:
1. 列表里点「编辑」打开抽屉 → 不改任何字段直接点「确定」 → 应保存成功(不应报 categoryCode 不能为空)
2. 列表里点「编辑」打开抽屉 → 只改「地理位置」tab → 应保存成功
3. 「添加服务」按钮 → 不填 categoryCode → 仍应报 categoryCode 不能为空(创建场景的校验不动)
4. 资源管理其他 5 个模块编辑场景同样 1/2/3 验证一遍