# AI 开发规范（media-science-frontend）

本文档基于当前仓库的真实配置与现有代码风格整理，目标是让 **AI/新同学**在不“自作主张改风格/改架构”的前提下，稳定产出可合并的代码。

## 技术栈与关键约束

- **框架**：Vue 3 + TypeScript + Vite（`type: module`
- **路由**：`unplugin-vue-router` 文件路由（`vue-router/auto`），Hash 模式（`createWebHashHistory()`）
- **状态**：Pinia（setup store 风格）
- **图表**：图表：echarts
- **UI**：Ant Design Vue
- **样式**：UnoCSS（`virtual:uno.css`），允许在模板里大量使用原子类（复杂样式例如动画，渐变等使用class声明，其余使用原子样式）
- **国际化**：`@intlify/unplugin-vue-i18n`（`src/locales/*.json`）
- **格式化门禁**：Husky + lint-staged + Prettier（提交前会对 `*.{vue,ts,tsx,js,jsx,md,css,less,json}` 自动 `prettier --write`）
- **TypeScript 严格性**：`strict: true`，并开启 `noUnusedLocals/noUnusedParameters`
- **自动导入**：`unplugin-auto-import`（`vue`、`pinia`、`@vueuse/core`、router auto imports），因此代码里可能**未显式 import** `ref/computed/watch/...`

## 项目架构（目录分层）

以 `src/` 为根：

- **入口**：`src/main.ts`（创建 app、挂载 router/pinia/Antd、全局指令）
- **根组件**：`src/App.vue`（全局初始化逻辑、主题/locale、监听生命周期等）
- **页面**：`src/pages/**`（文件路由：一个目录通常对应一个页面模块；页面内可用 `definePage({ alias: [...] })`）
- **通用组件**：`src/components/**`（跨页面复用的 UI/布局组件，如 `Layout.vue`）
- **状态管理**：`src/store/**`（Pinia stores）
- **接口层**：`src/api/**`
  - `src/api/generated/**`：Orval 自动生成（接口类型与路径的唯一真源，禁止手改）
  - `src/api/client.ts`：项目侧统一入口/别名层（导出客户端实例与类型工具）
  - `src/api/*.ts`：业务适配层（可选：解包 Result、分页扁平化、兼容旧页面结构等）
- **工具**：`src/utils/**`（如 `useRouteUtil.ts`、`useLocalStorage.ts`、`useWebSocket.ts` 等）
- **类型声明**：`src/**/*.d.ts`、`src/global.d.ts`、`src/vite-env.d.ts`

### 仓库根目录速览（常用）

```
.
├─ src/                  # 应用源码
├─ public/               # 静态资源（按 Vite 约定）
├─ vite/                 # Vite 插件/构建辅助（如 LocaleType）
├─ vite.config.ts        # 构建与插件配置（含 proxy、自动路由、自动导入等）
├─ uno.config.ts         # UnoCSS 主题/shortcuts
├─ tsconfig*.json        # TS 严格配置（strict + noUnused*）
├─ .prettierrc           # 代码格式标准（提交前会强制执行）
├─ .husky/pre-commit     # 提交钩子：lint-staged
├─ .env*                 # 环境变量（通过 import.meta.env 读取）
└─ AI_DEV_GUIDE.md       # 本规范
```

## 编码风格（必须遵守）

### 代码格式（Prettier 为准）

来自 `.prettierrc`：

- **单引号**：`'...'`
- **缩进**：2 空格，禁用 tab
- **行宽**：100

> 任何“手动对齐/自定义格式化习惯”都会被提交前的 Prettier 重写，AI 不要和格式化对抗。

### Vue SFC 约定

- **优先使用**：`<script lang="ts" setup>`（当前仓库主流写法）
- **模板类名**：允许 UnoCSS 原子类；尽量复用 `uno.config.ts` 里的 shortcuts（如 `flex-center`）
- **样式块**：
  - 页面级样式：优先 `scoped`
  - 全局样式：放在明确的全局入口（项目当前也存在在 `App.vue` 内写全局样式的情况；新增时优先放到统一样式文件，除非确实需要跟随根组件）

### TypeScript/类型策略

- **避免**：`any`（除非是三方库/遗留代码无法避免，且要把 `any` 限制在最小范围）
- **参数与返回值**：对外导出的工具函数、store action、请求函数调用处尽量补齐类型
- **未使用变量/参数**：因为启用了 `noUnusedLocals/noUnusedParameters`，新增代码必须确保无未使用项

## 路由规范（文件路由 + definePage）

- **页面文件位置**：放在 `src/pages/...` 下，按业务模块分目录
- **页面元信息**：在页面组件的 `setup` 中使用：

```ts
import { definePage } from 'vue-router/auto';

definePage({
  alias: ['/xxx', '/yyy'],
});
```

- **路由工具**：推荐使用 `src/utils/useRouteUtil.ts` 获取 `route/router/params<T>()`

## 状态管理规范（Pinia setup store）

- **位置**：`src/store/*.ts`
- **风格**：`defineStore('name', () => { ... })`，对外返回 `ref/computed` 等
- **与请求层鉴权同步**：涉及登录态/鉴权时，确保“状态层的 token”与“API 客户端实际使用的鉴权头/拦截器/存储”一致，避免出现“界面认为已登录但请求未携带 token”或相反的情况

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

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

### 1. 目录与职责边界

- **`src/api/generated/`**：Orval 自动生成目录，**禁止手改**。
  - `api.ts`：`getReadingPlatformAPI()` 工厂函数，返回包含全部接口方法的对象。
  - `model/`：OpenAPI 生成的 DTO/VO/Result/PageResult/Params 类型。
- **`src/api/client.ts`**：项目侧的“统一入口/别名层”，导出 `readingApi`（完整客户端实例）以及常用的类型工具（解包、分页别名等）。
- **`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
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`，并在适配层维持页面不改
- **长期**：页面全面只依赖适配层/生成客户端，减少重复封装

## 本地存储规范

- **Key 统一**：只在 `src/utils/useLocalStorage.ts` 的 `LocalStorageKey` 中声明与复用
- **登出清理**：使用 `outLoginClearStorage()`（如需扩展清理范围，优先扩展 `LocalStorageKey`）

## 组件与文件命名

- **Vue 组件**：
  - 通用组件：`src/components/PascalCase.vue`（现有仓库既有 PascalCase 也有小写文件名；新增时优先 PascalCase，避免混乱扩大）
  - 页面组件：`src/pages/<module>/index.vue`（仓库已有此习惯）
  - 页面子组件：`src/pages/<module>/components/*.vue`
- **TS 工具**：`src/utils/camelCase.ts` 或 `useXxx.ts`（hook/组合式函数）
- **Store**：`src/store/useXxx.ts` 或 `src/store/xxx.ts`（与现有文件保持一致，避免引入第三种命名体系）

## 变更边界（AI 必须遵守）

- **不做无关重构**：只改与需求相关的文件/代码；不要“顺便”换写法、换目录结构、统一命名
- **不引入新依赖**：除非需求明确且必要；新增依赖要同步更新 `package.json` 并说明原因
- **不改公共行为**：如 `request`、token 同步、路由生成规则、构建配置（除非需求明确）

## 环境变量与配置（不要绕开）

- **读取方式**：统一用 `src/utils/env.ts` 的 `getAppEnvConfig()`，不要在业务代码里到处直接读 `import.meta.env.xxx`
- **常用字段**：
  - `VITE_PREFIX_API`：请求前缀（当前 `request` 使用的是 \(`${VITE_PREFIX_API}${url}`\)）
  - `VITE_BASE_API`：存在但当前请求实现未拼接（历史上有注释；如需启用必须全局评估影响）
- **注意**：`vite.config.ts` 已配置 proxy（如 `/scienceApi`、`/localhostApi`）；开发环境应优先通过前缀 + proxy 工作，而不是写死域名

## 提交与协作

- **提交前**：确保 `pnpm dev` 能启动、关键页面可访问（至少覆盖改动路径）
- **格式化**：提交时会自动跑 Prettier；如果出现大量无关格式 diff，说明你改动触发了更大范围格式化，需收敛修改范围
- **变更说明**：PR/提交信息优先解释“为什么”而不是“改了什么”

## AI 变更自检清单（提交前必过）

- **范围**：是否只改了需求相关文件？是否避免“顺手重构/改命名/大面积格式化”？
- **类型**：`tsconfig` 已启用 `strict` 与 `noUnused*`，新增代码是否无未使用变量/参数？
- **路由**：新增页面是否放在 `src/pages/` 并使用 `definePage`（如需 alias）？
- **请求**：是否遵守 Orval 规范（生成代码只读、调用集中在 `src/api/client.ts`/适配层，避免页面散落 axios/fetch）？
- **鉴权**：涉及登录态/鉴权时，是否确保 store 中的 token 与 API 客户端实际使用的鉴权头/拦截器/存储一致？
- **存储**：localStorage key 是否只使用 `LocalStorageKey`？
- **格式**：是否符合 `.prettierrc`（单引号、2 空格、行宽 100），并接受提交前会被 Prettier 重写？

## 常见开发模板（给 AI 直接套用）

### 新增页面（文件路由）

1. 新建 `src/pages/<biz>/index.vue`
2. 在 `script setup` 中写 `definePage({ alias: [...] })`
3. 使用 `RouterView`/组件拼装页面

### 新增接口并调用

1. 后端更新 OpenAPI（确保该接口可被导出）
2. 前端执行 `npm run api:update` 重新生成（产物在 `src/api/generated/**`）
3. 在 `src/api/client.ts` 或 `src/api/*.ts`（适配层）封装稳定接口
4. 页面/组件只从 `src/api/client.ts` 或适配层引入调用，避免散落直连请求
