kindergarten_java/docs/前端项目规范.md
Claude Opus 4.6 2f5ad32820 refactor: 代码重构 - API规范化和文件路由配置
## 后端重构

### 新增基础设施
- src/common/dto/ - 统一响应格式和分页查询DTO基类
- src/common/interceptors/ - 响应转换拦截器
- src/common/utils/ - JSON解析和分页计算工具函数

### DTO规范化
- Course、Lesson、TeacherCourse、SchoolCourse、Tenant控制器添加Swagger装饰器
- 添加@ApiQuery、@ApiBody、@ApiOperation完善API文档
- 修复CourseLesson控制器路径参数问题

## 前端重构

### Orval API客户端生成
- 添加orval配置和生成脚本
- 生成完整的类型安全API客户端 (src/api/generated/)
- 导入56个参数类型文件

### API模块迁移
- src/api/course.ts - 迁移使用Orval生成API
- src/api/school-course.ts - 修复类型错误(number vs string)
- src/api/teacher.ts - 完整迁移教师端API
- src/api/client.ts - 重构API客户端统一入口
- src/api/lesson.ts - 修复未使用参数

### 文件路由配置
- 配置unplugin-vue-router插件
- 创建动态路由配置支持自动路由和传统路由切换
- 添加路由守卫保留原有权限逻辑

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 13:05:20 +08:00

267 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# AI 开发规范media-science-frontend
本文档基于当前仓库的真实配置与现有代码风格整理,目标是让 **AI/新同学**在不“自作主张改风格/改架构”的前提下,稳定产出可合并的代码。
## 技术栈与关键约束
- **框架**Vue 3 + TypeScript + Vite`type: module`
- **路由**`unplugin-vue-router` 文件路由(`vue-router/auto`Hash 模式(`createWebHashHistory()`
- **状态**Piniasetup 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. 后端更新 OpenAPIKnife4j/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` 或适配层引入调用,避免散落直连请求