# 问题处理：性能测试项目内任务过滤不生效

> 生成时间：2026-08-21
> 关联文档：`_PRD_性能测试项目管理与批量执行.md`、`HANDOFF_性能测试.md`

---

## 问题描述

在 5.60 服务器的测试管理平台上，点击进入「会议预约」文件夹（项目），页面上显示的性能测试任务列表中**混入了其他项目/未归属的任务**，即项目内任务过滤不生效。

## 复现步骤

1. 登录 5.60 测试管理平台
2. 进入「性能测试」→「项目管理」
3. 点击「会议预约」项目卡片进入详情页
4. 观察：任务列表显示的不是该项目的专属任务，而是包含其他项目的任务

## 当前行为

项目详情页任务列表显示**全部任务**（不区分项目），与预期只显示该项目下任务的行为不符。

## 预期行为

进入项目详情页后，任务列表**只显示该项目的任务**（`project_id` 匹配），未归属任务和其他项目的任务不应出现。

---

## 根因分析

### 调用链回溯

```
ProjectDetail.vue:259
    ↓ 发送 params = { page: 1, pageSize: 20, projectId: "proj_xxx" }
    ↓
performance.ts:42  →  request.get('/api/performance/tasks', { params })
    ↓ Axios 默认序列化，参数名原样发送
    ↓
GET /api/performance/tasks?page=1&pageSize=20&projectId=proj_xxx
    ↓
performance.py:71  →  project_id: Optional[str] = Query(None)
    ↓ 参数名不匹配，project_id = None
    ↓
performance_service.py:113  →  if project_id is not None:
    ↓ project_id 为 None，条件不成立，跳过过滤
    ↓
返回全部任务（不区分项目）
```

### 根本原因

| 环节 | 文件 | 代码 | 参数名 |
|------|------|------|--------|
| 前端传参 | `ProjectDetail.vue:259` | `params.projectId = projectId.value` | `projectId`（驼峰） |
| 前端传参 | `TaskList.vue:484` | `params.projectId = projectFilter.value` | `projectId`（驼峰） |
| API 定义 | `performance.ts:35-43` | 参数类型声明为 `projectId?` | `projectId`（驼峰） |
| Axios 序列化 | `request.ts` | 无自定义 `paramsSerializer` | 参数名原样发送 |
| 后端路由 | `performance.py:71` | Query 参数名 `project_id` | `project_id`（蛇形） |
| 后端服务 | `performance_service.py:113` | `if project_id is not None:` | `project_id` 始终为 `None` |

**核心矛盾**：前端使用 JavaScript 驼峰命名 `projectId`，后端 FastAPI 使用 Python 蛇形命名 `project_id`。Axios 默认序列化参数名时不做转换，导致后端无法匹配到参数。

### 为什么其他功能正常？

- **分页参数**：前端 `page` / `pageSize` 与后端 `page` / `page_size` —— 注意后端路由 `page` 参数名也是 `page`（无下划线），所以匹配正常；`pageSize` 实际上后端 `page_size` 参数使用了 `Query(20, ...)` 的默认值，因为前端传的 `pageSize` 后端也不认识，但分页仍能工作是因为 `page_size` 有默认值 20
- **状态过滤**：前端 `status` 与后端 `status` 一致，所以正常工作

---

## 修复方案

### 方案一：前端 API 层映射（推荐，改动最小）

在 `frontend/src/api/performance.ts` 的 `listTasks` 函数中，将 `projectId` 显式映射为 `project_id`：

```typescript
export function listTasks(params?: {
  page?: number
  pageSize?: number
  status?: string
  projectId?: string | null
}): Promise<PerformanceTaskListResponse> {
  const queryParams: Record<string, any> = { ...params }
  // 将 camelCase 的 projectId 映射为 snake_case 的 project_id
  if (params?.projectId !== undefined) {
    queryParams.project_id = params.projectId
    delete queryParams.projectId
  }
  return request.get(`${BASE}/tasks`, { params: queryParams })
}
```

### 方案二：前端 Axios 全局参数序列化（全局修复，影响面大）

在 `request.ts` 中添加 `paramsSerializer`，将 camelCase 参数自动转为 snake_case。但可能影响其他模块的参数名，风险较高。

### 方案三：后端路由参数兼容驼峰（改动在后端）

在后端 `performance.py` 的 `list_tasks` 路由中，增加 `projectId` 的 Query 别名：

```python
project_id: Optional[str] = Query(None, alias="projectId", description="..."),
```

### 选定方案：方案一

改动最小、风险最低、不影响其他模块。仅影响 `listTasks` 一个函数。

---

## 影响范围

| 影响 | 说明 |
|------|------|
| `frontend/src/api/performance.ts` | `listTasks` 函数，需增加参数映射逻辑 |
| 后端 | 无需改动 |
| 其他前端文件 | 无需改动（`ProjectDetail.vue` 和 `TaskList.vue` 的传参方式不变） |
| 其他模块 | 无影响 |

## 验证方法

修复后执行以下验证：

1. **本地验证**：启动前端 + 后端，在浏览器中进入项目详情页 → 确认任务列表只显示该项目下的任务
2. **网络请求验证**：浏览器 DevTools → Network → 查看 `GET /api/performance/tasks` 请求 URL 应为 `?page=1&pageSize=20&project_id=proj_xxx`
3. **边界验证**：`projectId=null`（查未归属）→ 应发送 `project_id=null`；`projectId` 不传 → 应不传 `project_id` 参数（返回全部）

---

## 部署步骤

1. 修改 `frontend/src/api/performance.ts`
2. `npm run build` 构建前端
3. SCP 前端 `dist/` 到 5.60 服务器
4. 浏览器 **Ctrl+F5 强刷** 清除缓存
5. 验证修复

---

*本文档由 Claude Code 生成*