# 前端 API 开发规范（Orval 生成代码）

本规范面向 `reading-platform-frontend`，以 `src/api/generated/` 为**接口类型与路径的唯一真源**，通过 Orval 从后端 OpenAPI 自动生成 TypeScript 类型 + 客户端方法。

## 1. 目录与职责边界

- **`reading-platform-frontend/src/api/generated/`**：Orval 自动生成目录，**禁止手改**。
  - `api.ts`：`getReadingPlatformAPI()` 工厂函数，返回包含全部接口方法的对象。
  - `model/`：OpenAPI 生成的 DTO/VO/Result/PageResult/Params 类型。
- **`reading-platform-frontend/src/api/client.ts`**：项目侧的“统一入口/别名层”，导出 `readingApi`（完整客户端实例）以及常用的类型工具（解包、分页别名等）。
- **`reading-platform-frontend/src/api/*.ts`**：业务侧“适配层”（可选），用于：
  - 兼容既有页面期望的“扁平结构/字段名/返回形态”
  - 补齐 OpenAPI 暂未覆盖的历史接口（短期过渡）
  - 汇聚跨接口的业务逻辑（例如组合请求、额外校验）

## 2. 基本原则（必须遵守）

- **生成代码只读**：不得在 `src/api/generated/**` 内做任何手工修改（包括修复类型、改路径、加字段）。
- **以生成类型为准**：参数/返回类型优先使用 `src/api/generated/model` 导出的类型，避免手写 `interface` 漂移。
- **对外只暴露稳定的业务接口**：页面/组件尽量通过 `src/api/*.ts`（适配层）或 `src/api/client.ts`（直接调用）访问，避免散落调用方式导致难以迁移。

## 3. 推荐调用方式

### 3.1 直接使用 Orval 客户端

- 统一从 `src/api/client.ts` 引入：
  - `readingApi`: `getReadingPlatformAPI()` 的实例
  - `ApiResultOf` / `UnwrapResult` / `PageDataOf` 等类型工具

示例（以 `Result<T>` 为包裹结构）：

```ts
import { readingApi } from "@/api/client";
import type { ResultTenant } from "@/api/generated/model";

async function loadTenant(id: number) {
  const res = (await readingApi.getTenant(id)) as ResultTenant;
  return res.data; // T（可能为 undefined，取决于后端返回与类型定义）
}
```

### 3.2 使用“适配层”稳定返回结构

当页面已经依赖历史返回结构（例如直接要 `items/total/page/pageSize`），在 `src/api/*.ts` 内做一次性适配，页面只消费适配后的结构。

分页适配建议统一输出：

- `items: T[]`
- `total: number`
- `page: number`
- `pageSize: number`

## 4. Result / PageResult 约定与解包

后端统一响应通常为：

- **普通接口**：`Result<T>`，字段一般为 `code/message/data`
- **分页接口**：`Result<PageResult<T>>`，字段一般为 `items/total/page/pageSize`

在生成代码中常见类型形态：

- `ResultXXX`（如 `ResultTenant`、`ResultUserInfoResponse`）
- `ResultPageResultXXX`（如 `ResultPageResultTenant`）
- `PageResultXXX`（如 `PageResultTenant`）

建议做法：

- **组件/页面层尽量不要直接处理 `ResultXXX`**，而是由适配层解包并做兜底（空数组、默认分页参数等）。
- **严禁在页面散落 `as any`**；确需兼容时，集中在 `src/api/*.ts` 适配层进行，并在适配层内把“最终对页面返回的类型”定义清楚。

## 5. 命名与重复接口（`getXxx`/`getXxx1`/`getXxx2`）

由于不同角色端点（teacher/school/parent/admin）可能存在同名资源，Orval 在生成时会用 `1/2/3` 后缀消歧，例如：

- `getTask`（teacher） vs `getTask1`（school） vs `getTask2`（parent）

规范建议：

- **业务层不要直接暴露带数字后缀的方法名**；
- 在 `src/api/*.ts` 中封装为语义化名称，例如：
  - `teacherGetTask` / `schoolGetTask` / `parentGetTask`
  - 或按模块拆分到 `src/api/teacher/task.ts` 等（如后续重构允许）

## 6. 何时需要更新生成代码

当后端 Controller 或 DTO/VO 发生变更：

1. 后端更新 OpenAPI（Knife4j/SpringDoc）
2. 前端更新规范并重新生成（项目已有脚本）：

```bash
cd reading-platform-frontend
npm run api:update
```

3. 提交生成物（通常包含 `api-spec.*` 与 `src/api/generated/**`）

> 注意：如果某接口在后端已存在但 OpenAPI 未导出（例如缺少注解/返回类型不规范），应优先修后端文档，而不是在前端“硬编码路径”长期绕过。

## 7. 禁止事项（高频踩坑）

- **禁止**：手改 `src/api/generated/**`（下次生成会被覆盖，且会引入不可追踪差异）。
- **禁止**：页面里手写 axios 调用去访问 `/api/v1/...`（除非 OpenAPI 暂缺且已在适配层集中兜底）。
- **禁止**：在业务代码中扩散 `any` 来“快速通过类型检查”。

## 8. 迁移策略（从旧 `http` 到 Orval）

若已有模块使用 `src/api/index.ts` 的 `http.get/post/...`：

- **短期**：保留旧实现，但新增/变更接口优先走 `readingApi`
- **中期**：逐模块把旧 `http` 调用替换为 `readingApi`，并在适配层维持页面不改
- **长期**：页面全面只依赖适配层/生成客户端，减少重复封装

