---
date: 2026-05-07
type: frontend-bug
module: resource-batch-delete
priority: high
notify: ["@mmg"]
status: pending
source: 测试用例汇总.xlsx (2026-05-07) + /@bug 实测
related_cases: [JQ-027, CT-010, BP-013, YW-013]
gitea_issue_closed: 1792
---

# 4 个资源模块批量删除失败 — 前端多包一层 data

## 原始现象

测试用例 JQ-027 / CT-010 / BP-013 / YW-013：景区/餐厅/备品/游玩项目（"物业"实为游玩项目）批量删除时，勾选数据后点删除"系统提示但无法删除"。

## /@bug 实测结论（admin 真 token round-trip 双向对照）

| 模块 | "正确 body" | "前端字面 body" | 结论 |
|------|------------|-----------------|------|
| 景区 | `{"scenicIds":[3001,3002]}` → code:200 | `{"data":{"scenicIds":[...]}}` → code:400 字段【scenicIds】不能为空 | 前端多包 |
| 餐厅 | `{"restaurantIds":[...]}` → code:200 | `{"data":{"restaurantIds":[...]}}` → code:400 | 前端多包 |
| 备品 | `{"suppliesIds":[...]}` → code:200 | `{"data":{"suppliesIds":[...]}}` → code:400 | 前端多包 |
| 游玩项目 | `{"playServiceIds":[...]}` → code:200 | `{"data":{"playServiceIds":[...]}}` → code:400 | 前端多包 |

**后端纯净，4 个模块前端 api 文件统一多包了一层 `data`**。

## 根因详情

| 文件:行号 | 现状 |
|----------|------|
| `hl-ui/src/api/scenic.js:421` | `http.delete(url, { data: { scenicIds } })` ❌ |
| `hl-ui/src/api/restaurant.js:78` | `http.delete(url, { data: { restaurantIds } })` ❌ |
| `hl-ui/src/api/spare.js:69` | `http.delete(url, { data: { suppliesIds } })` ❌ |
| `hl-ui/src/api/playservice.js:71` | `http.delete(url, { data: { playServiceIds } })` ❌ |

### 项目 wrapper 行为（`hl-ui/src/utils/request.js:582-583`）

项目自定义的 `http.delete(url, data, config)` 第二参数**直接当 body**，不像 axios 原生 `axios.delete(url, { data: body })` 那样从 config.data 取。

所以前端写 `http.delete(url, { data: { scenicIds } })` 时：
- 第二参数是 `{ data: { scenicIds } }`
- wrapper 直接当 body 发出去
- 后端拿到 `{ "data": { "scenicIds": [...] } }`
- 后端 DTO 是 `{ scenicIds: List<Long> }`，找不到顶层 `scenicIds` 字段 → @NotEmpty fail

### 正确写法

```js
// 错 ❌
export function batchDelete(scenicIds) {
  return http.delete('/admin/scenic/batch', { data: { scenicIds } })
}

// 对 ✅
export function batchDelete(scenicIds) {
  return http.delete('/admin/scenic/batch', { scenicIds })
  //                                          ^^^^^^^^^
  //                              第二参数直接是 body 对象，不要再包 { data: ... }
}
```

## 同源 anti-pattern（重要更新）

跟 memory `experience/backend-dev/frontend-http-delete-double-wrap-data-trap.md` 是**同一个坑的反转版本**：

- **9 天前**（PR #1494 时期）：项目 wrapper 第二参数当 body，前端按 axios 原生写 `{ data: payload }`，后端拿到多包一层 → 一样的 code:400
- **现在**：wrapper 仍然这样行为，但部分模块前端代码似乎曾经改对过又改错回去（或者一开始就错），症状一模一样

**建议 mmg**：
1. 修这 4 处当前确认的错误
2. 全局 grep `http\.delete\(.*\{.*data:` / `http\.put\(.*\{.*data:` 扫一遍，看历史遗留
3. 考虑把 wrapper 改成兼容 axios 原生用法（即从 config.data 取）减少这种坑

## 影响

- 4 个资源模块批量删除完全失效
- 单个删除（非批量）通常不走这个路径，可能正常

## 验收（前端）

- [ ] 4 个文件改完，传第二参数直接是 IDs 对象不要包 `{ data: ... }`
- [ ] 测试服 round-trip：景区/餐厅/备品/游玩项目 4 模块批量删除 5 条 → 列表 deleted_at 已写
- [ ] 全局 grep 同类 anti-pattern 一起改

## 关联工单

Gitea #1792 已关闭并留 closing comment（项目规则前端 BUG 不建工单）。
完整分析报告：`docs/tasks/20260507_bug_resource_analysis.md`

## 顺手另一个共性问题（建议 PM 复核）

/@bug 还发现：8 个资源服务（hotel/scenic/activity/restaurant/staff/supplies/cost/vehicle）的 `updateStatus` 方法**全部缺 checkResourceInUse 引用计数检查**（只有 `submitApproval` 路径有）。本批 PR 只修景区一个（JQ-011 #1794），其余 7 个建议 PM 决策是否扩大修复范围。
