name: frontend-wire description: 阶段 5a。把前端从 mock 切到真后端 API。验证字段、状态、错误处理一致。当用户说"接真接口"、"切真 API"、"前端联调"时触发。前置:对应业务契约 status=implemented(后端已实现)。
阶段 5a:前端切真 API
前置检查
ls docs/api-contracts/*.yaml
读 status。只能处理 status: implemented 的契约。如果还是 confirmed → 后端未实现 → 拒绝:
❌ 无法切真接口。
原因:以下契约后端还未实现,只是 confirmed:
- updateTask.yaml
- listTasks.yaml
请先 api-implement skill 实现后端,再回来切真接口。
工作流程
1. 检查现状
# 现有 mock handlers
ls frontend/src/mocks/handlers/
# 现有 API client
ls frontend/src/api/
# 当前模式
grep VITE_MODE frontend/.env*
2. 确认后端可达
# 假设后端跑在 :8000
curl -s http://localhost:8000/api/v1/health || echo "后端没起,请 make backend-dev"
如果后端没起,提示用户启动后再继续。
3. 配置真 API base URL
frontend/.env.development:
VITE_API_BASE_URL=http://localhost:8000/api/v1
4. 切换模式
入口文件改成:
async function enableMocking() {
if (import.meta.env.VITE_USE_MOCK !== 'true') return
const { worker } = await import('./mocks/browser')
await worker.start({ onUnhandledRequest: 'bypass' })
}
新增 frontend/.env.mock:
VITE_USE_MOCK=true
package.json scripts:
{
"dev": "vite",
"dev:mock": "vite --mode mock"
}
5. 逐个验证页面
逐个(这一步不批量),按场景:
- 启动后端 + 前端
npm run dev - 打开页面 → F12 Network
- 验证清单:
- ✅ 请求 URL 正确(指向真后端,不是 MSW)
- ✅ 请求带 Authorization header
- ✅ 响应字段与前端 type 完全匹配
- ✅ 错误响应触发正确的 UI 状态
- ✅ Loading / Empty / Error 三态都正常
6. 处理发现的问题
6.1 字段对不上(但 mock 阶段没发现)
最常见的情况:mock 数据偷懒,没填 nullable 字段,真 API 返回 null,UI 崩。
修法:改 UI 处理 null,不改 API。
// 改前
<div>{task.assignee.name}</div>
// 改后
<div>{task.assignee?.name ?? '未指派'}</div>
6.2 真 API 行为与 mock 不一致
例:mock 删除直接 200,真 API 返回 204。
修法:对照 OpenAPI 检查谁对。OpenAPI 是事实源。
- mock 错 → 改 mock(顺手)
- UI 假设错 → 改 UI
6.3 真 API 与 OpenAPI 不一致
严重问题。这说明后端实现偏离了契约。
立即停下报告:
⚠️ 后端实现偏离 OpenAPI:
GET /api/v1/tasks/{id} 实际返回:
{ "id": "...", "title": "...", "owner": "..." } ← owner 字段
OpenAPI 定义:
{ "id": "...", "title": "...", "created_by": "..." } ← created_by
需要决定:
A. 后端改回 created_by(符合 OpenAPI)
B. 改 OpenAPI 为 owner(并通知前端 mock 重新对齐)
请决定。如选 A,需要 api-implement 修补;选 B,需要 api-design 修订后重跑 mock-align。
7. 改造 fetch 调用
如果之前 mock 阶段用了硬编码 fetch,统一改为生成的 client:
// 改前
const res = await fetch('/api/v1/tasks/' + id)
const task = await res.json()
// 改后(如果用了 orval/react-query)
const { data: task } = useGetTask({ id })
错误处理统一(src/api/client.ts 拦截器):
client.interceptors.response.use(
(res) => res,
(err) => {
if (err.response?.status === 401) {
// token 失效,跳登录
}
// 抛出统一的 APIError
throw new APIError(err.response?.data?.error)
}
)
8. 验证清单
每个页面跑一遍:
- 正常加载数据
- 创建 / 更新 / 删除 操作成功
- 错误响应 UI 友好(显示 error.message 或 i18n)
- Loading 骨架屏正常
- Empty 状态有 CTA
- 鉴权失败跳登录
- 类型检查
tsc --noEmit通过 - 浏览器 Console 0 错误
9. 输出切换总结
## 前端切真 API 总结
### 模式
- `npm run dev` → 真 API
- `npm run dev:mock` → MSW mock(保留)
### 改造文件
- frontend/.env.development
- frontend/.env.mock
- frontend/src/main.tsx(条件加载 MSW)
- frontend/src/api/client.ts(拦截器、错误处理)
- frontend/src/pages/* (各页面 fetch → react-query)
### 已验证页面
- ✅ /login 登录正常,401 提示
- ✅ /projects 列表加载,空状态正确
- ✅ /projects/:id/board 看板拖拽未实现(留作下个迭代)
- ✅ /tasks/:id 详情正常,删除→列表移除
### 发现的问题
✅ 已修复:
- TaskCard 没处理 assignee 为 null → 加默认值
- 错误响应没 trace_id 展示 → 加到 Toast
⚠️ 待决策:
- 无
### Verify
- tsc --noEmit: ✅
- vitest: ✅
- 浏览器手测: ✅ 4 个页面 0 错误
### 下一步
- 调用 e2e-test skill 写端到端测试
严禁
- ❌ 在后端未 implemented 时开工
- ❌ 删除 mock handlers(保留,用于 dev:mock 和测试)
- ❌ 修改 OpenAPI(发现真 API 偏离 → 停下报告)
- ❌ 修改 frontend/src/api/types.ts(机器生成)
- ❌ 在切换中"顺手"美化 UI
- ❌ 隐藏字段不匹配