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>
This commit is contained in:
parent
ad0204a59a
commit
2f5ad32820
268
docs/dev-logs/2026-03-12.md
Normal file
268
docs/dev-logs/2026-03-12.md
Normal file
@ -0,0 +1,268 @@
|
||||
# 开发日志 - 2026-03-12
|
||||
|
||||
## 今日任务:代码重构(规范化)
|
||||
|
||||
根据 `docs/统一开发规范.md` 和 `docs/前端项目规范.md` 进行代码重构。
|
||||
|
||||
---
|
||||
|
||||
## 后端重构阶段
|
||||
|
||||
### 阶段 1:添加公共组件(基础设施) ✅
|
||||
|
||||
**新增文件:**
|
||||
|
||||
1. `src/common/dto/result.dto.ts` - 统一响应格式 DTO
|
||||
- `ResultDto<T>` - 统一响应格式 `{ code, message, data }`
|
||||
- `PageResultDto<T>` - 分页响应格式 `{ items, total, page, pageSize, totalPages }`
|
||||
|
||||
2. `src/common/dto/page-query.dto.ts` - 分页查询 DTO 基类
|
||||
- `PageQueryDto` - 基础分页查询(page, pageSize)
|
||||
- `PageSearchDto` - 带搜索关键词
|
||||
- `PageWithStatusDto` - 带状态筛选
|
||||
- `PageWithDateRangeDto` - 带日期范围
|
||||
|
||||
3. `src/common/interceptors/transform.interceptor.ts` - 响应转换拦截器
|
||||
- 自动将 Controller 返回数据包装为统一格式
|
||||
- 支持 `@SkipTransform()` 跳过转换
|
||||
|
||||
4. `src/common/utils/json.util.ts` - JSON 字段解析工具
|
||||
- `parseJsonField()` - 安全解析 JSON 字段
|
||||
- `parseJsonArray()` - 解析 JSON 数组
|
||||
- `parseJsonObject()` - 解析 JSON 对象
|
||||
|
||||
5. `src/common/utils/pagination.util.ts` - 分页计算工具
|
||||
- `calculatePagination()` - 计算分页参数
|
||||
- `createPageResponse()` - 创建分页响应
|
||||
- `calculateTotalPages()` - 计算总页数
|
||||
- `validatePagination()` - 验证分页参数
|
||||
|
||||
**修改文件:**
|
||||
|
||||
1. `package.json` - 添加 `@nestjs/swagger` 依赖(v11.x)
|
||||
2. `src/main.ts` - 配置 Swagger 文档和响应拦截器
|
||||
|
||||
**Swagger 配置:**
|
||||
- 文档访问地址:`http://localhost:3000/api-docs`
|
||||
- 添加了 JWT Bearer 认证支持
|
||||
- 添加了 API 标签分类
|
||||
|
||||
### 阶段 2:DTO 规范化(Tenant 模块示例) ✅
|
||||
|
||||
**修改文件:**
|
||||
|
||||
1. `src/modules/tenant/dto/tenant.dto.ts`
|
||||
- 为所有 DTO 添加 `@ApiProperty` 装饰器
|
||||
- 添加 description、example、required 等属性
|
||||
|
||||
2. `src/modules/tenant/tenant.controller.ts`
|
||||
- 添加 `@ApiTags`、`@ApiOperation`、`@ApiResponse` 装饰器
|
||||
- 添加 `@ApiBearerAuth` 认证装饰器
|
||||
|
||||
---
|
||||
|
||||
## 前端重构阶段
|
||||
|
||||
### 阶段 1:添加 Orval 配置 ✅
|
||||
|
||||
**新增文件:**
|
||||
|
||||
1. `orval.config.ts` - Orval 配置文件
|
||||
2. `src/api/generated/mutator.ts` - 自定义请求拦截器
|
||||
3. `src/api/client.ts` - API 客户端统一入口
|
||||
4. `src/api/teacher.adapter.ts` - 教师端 API 适配层
|
||||
|
||||
**修改文件:**
|
||||
|
||||
1. `package.json` - 添加 orval 依赖和生成脚本
|
||||
- `api:update` - 生成 API 客户端
|
||||
- `api:watch` - 监听模式生成
|
||||
|
||||
2. `vite.config.ts` - 添加文件路由插件
|
||||
|
||||
**已安装依赖:**
|
||||
- orval (^8.5.3)
|
||||
- unplugin-vue-router (^0.19.2)
|
||||
|
||||
### 阶段 2:配置文件路由 ✅
|
||||
|
||||
**配置内容:**
|
||||
- 使用 `src/views` 作为路由文件夹
|
||||
- 支持 `.vue` 扩展名
|
||||
- 同步导入模式
|
||||
|
||||
---
|
||||
|
||||
## 待完成任务
|
||||
|
||||
### 后端
|
||||
|
||||
- [ ] 阶段 3:全面推广 DTO 规范化
|
||||
- [ ] 添加 @ApiQuery 装饰器(Course、SchoolCourse、Lesson 等模块)
|
||||
- [ ] 添加 @ApiBody 装饰器(定义请求体类型)
|
||||
- [ ] Auth 模块
|
||||
- [ ] Course 模块
|
||||
- [ ] Lesson 模块
|
||||
- [ ] SchoolCourse 模块
|
||||
- [ ] 其他模块
|
||||
|
||||
### 前端
|
||||
|
||||
- [x] 运行 `npm run api:update` 生成 API 客户端
|
||||
- [x] 修复 PrepareModeView.vue 中的 API 调用错误
|
||||
- [x] 迁移课程模块(Course)使用新 API 客户端
|
||||
- [x] 迁移校本课程模块(SchoolCourse)使用新 API 客户端
|
||||
- [x] 迁移教师模块(Teacher)使用新 API 客户端
|
||||
- [x] 修复 school-course.ts 中的类型错误(string vs number)
|
||||
- [x] 清理未使用的 teacher.adapter.ts 文件
|
||||
- [ ] 迁移其他 API 模块(lesson, auth 等)
|
||||
|
||||
---
|
||||
|
||||
## 新增完成
|
||||
|
||||
### 后端
|
||||
|
||||
- **CourseLesson 控制器重构** ✅
|
||||
- 移除类级路径参数 `@Controller('admin/courses/:courseId/lessons')`
|
||||
- 改为在每个方法中显式声明完整路径 `@Get(':courseId/lessons')`
|
||||
- 修复了 Orval 验证错误(PUT/DELETE 方法的路径参数问题)
|
||||
|
||||
- **后端 DTO 规范化** ✅
|
||||
- **Course 控制器**:添加 @ApiQuery、@ApiBody、@ApiOperation 装饰器
|
||||
- **Lesson 控制器**:添加 @ApiQuery、@ApiBody、@ApiOperation 装饰器
|
||||
- **TeacherCourse 控制器**:添加 @ApiQuery、@ApiBody、@ApiOperation 装饰器
|
||||
- **SchoolCourse 控制器**:添加 @ApiQuery、@ApiBody、@ApiOperation 装饰器
|
||||
|
||||
### 前端
|
||||
|
||||
- **Orval API 客户端生成成功** ✅
|
||||
- 生成文件:`src/api/generated/index.ts` (90KB+)
|
||||
- 生成模型:`src/api/generated/model/` (56个参数类型文件)
|
||||
- Mutator:`src/api/generated/mutator.ts`
|
||||
- TypeScript 编译通过
|
||||
- **API 方法现在包含完整的参数定义** ✅
|
||||
|
||||
- **课程模块迁移到新 API 客户端** ✅
|
||||
- 更新 `src/api/course.ts` 使用 Orval 生成的 API
|
||||
- 导入参数类型:`CourseControllerFindAllParams`
|
||||
- 保持向后兼容的接口
|
||||
|
||||
- **校本课程模块迁移到新 API 客户端** ✅
|
||||
- 更新 `src/api/school-course.ts` 使用 Orval 生成的 API
|
||||
- 支持学校端和教师端两套接口
|
||||
|
||||
- **教师模块迁移到新 API 客户端** ✅
|
||||
- 更新 `src/api/teacher.ts` 使用 Orval 生成的 API
|
||||
- 覆盖教师课程、授课记录、首页、反馈、进度追踪、排课管理、阅读任务等所有接口
|
||||
- 修复类型兼容性问题(DTO 索引签名、参数类型转换等)
|
||||
|
||||
- **修复 school-course.ts 类型错误** ✅
|
||||
- 移除所有 `String()` 类型转换
|
||||
- 生成的 SchoolCourse API 期望 `number` 类型 ID,与 Teacher API 不同(期望 `string`)
|
||||
- 修复所有学校端和教师端校本课程接口的类型问题
|
||||
|
||||
- **清理 teacher.adapter.ts** ✅
|
||||
- 移除不存在的类型导入(TeacherCourse, Lesson, LessonFeedback, TeacherTask)
|
||||
- 添加弃用注释,建议直接使用 teacher.ts 中的函数
|
||||
- 改为重新导出 teacher.ts,保持向后兼容
|
||||
|
||||
- **修复 client.ts API 客户端** ✅
|
||||
- 修复导入错误的函数名(`getReadingPlatformAPI` → `getApi`)
|
||||
- 简化导出结构,直接使用 `api` 作为 API 客户端实例
|
||||
- 修复类型守卫和类型断言问题
|
||||
- 改进 `unwrapData` 和 `unwrapPageData` 函数的类型安全
|
||||
|
||||
- **修复其他 API 相关文件** ✅
|
||||
- 移除 mutator.ts 中未使用的 `ResultDto` 导入
|
||||
- 修复 lesson.ts 中未使用的 `lessonId` 参数
|
||||
|
||||
- **创建文件路由目录结构** ✅
|
||||
- 更新 vite.config.ts 使用正确的 `unplugin-vue-router` 插件导入
|
||||
- 创建动态路由配置 `src/router/index.ts`
|
||||
- 保留传统路由配置作为 `src/router/manual-routes.ts` 备份
|
||||
- 支持自动路由和传统路由的平滑切换
|
||||
|
||||
---
|
||||
|
||||
## 验证步骤
|
||||
|
||||
### 后端验证
|
||||
|
||||
```bash
|
||||
cd /Users/retirado/Program/ccProgram_0312/reading-platform-backend
|
||||
npm start
|
||||
# 访问 http://localhost:3000/api-docs 查看 Swagger 文档
|
||||
```
|
||||
|
||||
### 前端验证
|
||||
|
||||
```bash
|
||||
cd /Users/retirado/Program/ccProgram_0312/reading-platform-frontend
|
||||
npm run api:update
|
||||
npm run dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 遇到的问题
|
||||
|
||||
1. **@nestjs/swagger 版本冲突**
|
||||
- 问题:@nestjs/swagger@11.x 需要 @nestjs/common@11.x
|
||||
- 解决:使用 `--legacy-peer-deps` 安装
|
||||
|
||||
2. **unplugin-vue-router 已弃用**
|
||||
- 问题:该包已合并到 vuejs/router
|
||||
- 解决:暂时使用当前版本,后续可升级到 Vue Router 5
|
||||
|
||||
3. **CourseLesson 控制器路径参数问题**
|
||||
- 问题:类级路径参数在 PUT/DELETE 方法中无法被 Orval 正确识别
|
||||
- 解决:移除类级路径参数,在每个方法中显式声明完整路径
|
||||
|
||||
4. **Orval 生成的 API 方法缺少参数**
|
||||
- 问题:后端控制器缺少 @ApiQuery、@ApiBody 装饰器,导致生成的 API 方法没有参数
|
||||
- 解决方案:需要先完成后端 DTO 规范化,为所有控制器添加完整的 Swagger 装饰器
|
||||
|
||||
5. **PrepareModeView.vue API 调用错误**
|
||||
- 问题:从 `@/api/school-course` 导入 `getTeacherSchoolCourseFullDetail`,但错误地通过 `teacherApi` 调用
|
||||
- 解决:直接调用导入的函数 `getTeacherSchoolCourseFullDetail()`
|
||||
|
||||
6. **Teacher.ts API 迁移类型问题**
|
||||
- 问题:本地 DTO 接口与生成类型不兼容(缺少索引签名、参数类型不匹配)
|
||||
- 解决:使用 `as any` 类型断言进行适配,保持向后兼容的函数签名
|
||||
- 问题:部分生成的 API 方法缺少参数定义(如 getTeacherTasks、getTaskTemplates)
|
||||
- 解决:移除不支持的参数,在后续后端更新后恢复
|
||||
- 问题:saveLessonProgress 的 lessonIds 类型为 number[] 而生成 API 需要 string[]
|
||||
- 解决:在调用时进行类型转换 `.map(String)`
|
||||
|
||||
7. **SchoolCourse.ts API 类型问题**
|
||||
- 问题:生成的 SchoolCourse API 期望 `number` 类型 ID,但代码传递 `String(id)`
|
||||
- 解决:移除所有 `String()` 类型转换,直接传递 `number` 类型
|
||||
- 注意:与 Teacher API 不同(Teacher API 期望 `string`),需要根据实际生成的 API 签名调整
|
||||
|
||||
8. **Teacher.adapter.ts 导入错误**
|
||||
- 问题:导入了不存在的类型(TeacherCourse, Lesson, LessonFeedback, TeacherTask)
|
||||
- 解决:移除不存在的类型导入,添加弃用注释
|
||||
|
||||
9. **Client.ts API 客户端结构错误**
|
||||
- 问题:尝试导入不存在的 `getReadingPlatformAPI` 函数
|
||||
- 解决:使用正确的 `getApi` 函数,简化导出结构
|
||||
- 问题:类型守卫 `isSuccess` 导致 TypeScript 无法正确推断 `else` 分支的类型
|
||||
- 解决:改用 `asserts` 类型断言,让 TypeScript 理解类型收窄
|
||||
- 问题:`unwrapData` 函数的类型安全问题
|
||||
- 解决:添加显式类型断言和类型检查
|
||||
|
||||
10. **文件路由配置问题**
|
||||
- 问题:`unplugin-vue-router` 插件导入名称错误 (`FileSystemRouter` 不存在)
|
||||
- 解决:使用默认导入 `fileRouter from 'unplugin-vue-router/vite'`
|
||||
- 问题:文件路由虚拟模块路径不确定
|
||||
- 解决:创建动态路由配置,先尝试加载 `vue-router/auto-routes`,失败则回退到传统路由
|
||||
- 保留了 `manual-routes.ts` 作为备用,确保系统稳定性
|
||||
|
||||
---
|
||||
|
||||
## 备注
|
||||
|
||||
- 本次重构遵循渐进式原则,保持向后兼容
|
||||
- 所有新增文件都遵循规范要求的目录结构
|
||||
- 适配层确保旧代码可以继续工作
|
||||
266
docs/前端项目规范.md
Normal file
266
docs/前端项目规范.md
Normal file
@ -0,0 +1,266 @@
|
||||
# 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` 或适配层引入调用,避免散落直连请求
|
||||
3493
docs/统一开发规范.md
Normal file
3493
docs/统一开发规范.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -23,6 +23,7 @@
|
||||
"@nestjs/passport": "^10.0.3",
|
||||
"@nestjs/platform-express": "^10.4.22",
|
||||
"@nestjs/schedule": "^6.1.1",
|
||||
"@nestjs/swagger": "^11.2.6",
|
||||
"@nestjs/throttler": "^5.2.0",
|
||||
"@prisma/client": "^5.22.0",
|
||||
"@types/multer": "^2.0.0",
|
||||
@ -44,6 +45,7 @@
|
||||
"@nestjs/cli": "^10.3.0",
|
||||
"@nestjs/schematics": "^10.1.0",
|
||||
"@nestjs/testing": "^10.3.0",
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/jest": "^29.5.11",
|
||||
|
||||
97
reading-platform-backend/src/common/dto/page-query.dto.ts
Normal file
97
reading-platform-backend/src/common/dto/page-query.dto.ts
Normal file
@ -0,0 +1,97 @@
|
||||
import { IsInt, IsOptional, Max, Min } from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
/**
|
||||
* 分页查询 DTO 基类
|
||||
*
|
||||
* 所有分页查询接口都应继承此类,确保分页参数的一致性
|
||||
*/
|
||||
export class PageQueryDto {
|
||||
@ApiProperty({
|
||||
description: '当前页码(从 1 开始)',
|
||||
example: 1,
|
||||
required: false,
|
||||
default: 1,
|
||||
})
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt({ message: '页码必须是整数' })
|
||||
@Min(1, { message: '页码最小为 1' })
|
||||
page?: number = 1;
|
||||
|
||||
@ApiProperty({
|
||||
description: '每页数量',
|
||||
example: 10,
|
||||
required: false,
|
||||
default: 10,
|
||||
minimum: 1,
|
||||
maximum: 100,
|
||||
})
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt({ message: '每页数量必须是整数' })
|
||||
@Min(1, { message: '每页数量最小为 1' })
|
||||
@Max(100, { message: '每页数量最大为 100' })
|
||||
pageSize?: number = 10;
|
||||
|
||||
/**
|
||||
* 计算 skip 值(用于 Prisma 分页)
|
||||
*/
|
||||
getSkip(): number {
|
||||
return ((this.page ?? 1) - 1) * (this.pageSize ?? 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 take 值(用于 Prisma 分页)
|
||||
*/
|
||||
getTake(): number {
|
||||
return this.pageSize ?? 10;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 带搜索关键词的分页查询 DTO
|
||||
*/
|
||||
export class PageSearchDto extends PageQueryDto {
|
||||
@ApiProperty({
|
||||
description: '搜索关键词',
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
keyword?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 带状态筛选的分页查询 DTO
|
||||
*/
|
||||
export class PageWithStatusDto extends PageQueryDto {
|
||||
@ApiProperty({
|
||||
description: '状态筛选',
|
||||
required: false,
|
||||
enum: ['ACTIVE', 'INACTIVE', 'DRAFT', 'PUBLISHED'],
|
||||
})
|
||||
@IsOptional()
|
||||
status?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 带日期范围筛选的分页查询 DTO
|
||||
*/
|
||||
export class PageWithDateRangeDto extends PageQueryDto {
|
||||
@ApiProperty({
|
||||
description: '开始日期(YYYY-MM-DD)',
|
||||
required: false,
|
||||
example: '2026-01-01',
|
||||
})
|
||||
@IsOptional()
|
||||
startDate?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '结束日期(YYYY-MM-DD)',
|
||||
required: false,
|
||||
example: '2026-12-31',
|
||||
})
|
||||
@IsOptional()
|
||||
endDate?: string;
|
||||
}
|
||||
102
reading-platform-backend/src/common/dto/result.dto.ts
Normal file
102
reading-platform-backend/src/common/dto/result.dto.ts
Normal file
@ -0,0 +1,102 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
/**
|
||||
* 统一响应格式 DTO
|
||||
*
|
||||
* @template T - 数据类型
|
||||
*/
|
||||
export class ResultDto<T> {
|
||||
@ApiProperty({
|
||||
description: '响应状态码',
|
||||
example: 200,
|
||||
})
|
||||
code: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: '响应消息',
|
||||
example: '操作成功',
|
||||
})
|
||||
message: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '响应数据',
|
||||
})
|
||||
data: T;
|
||||
|
||||
/**
|
||||
* 创建成功响应
|
||||
*/
|
||||
static success<T>(data: T, message = '操作成功'): ResultDto<T> {
|
||||
return {
|
||||
code: 200,
|
||||
message,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建失败响应
|
||||
*/
|
||||
static error<T>(message: string, code = 500, data?: T): ResultDto<T> {
|
||||
return {
|
||||
code,
|
||||
message,
|
||||
data: data as T,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页响应数据 DTO
|
||||
*
|
||||
* @template T - 列表项类型
|
||||
*/
|
||||
export class PageResultDto<T> {
|
||||
@ApiProperty({
|
||||
description: '数据列表',
|
||||
isArray: true,
|
||||
})
|
||||
items: T[];
|
||||
|
||||
@ApiProperty({
|
||||
description: '总记录数',
|
||||
example: 100,
|
||||
})
|
||||
total: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: '当前页码',
|
||||
example: 1,
|
||||
})
|
||||
page: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: '每页数量',
|
||||
example: 10,
|
||||
})
|
||||
pageSize: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: '总页数',
|
||||
example: 10,
|
||||
})
|
||||
totalPages: number;
|
||||
|
||||
/**
|
||||
* 创建分页响应
|
||||
*/
|
||||
static create<T>(
|
||||
items: T[],
|
||||
total: number,
|
||||
page: number,
|
||||
pageSize: number,
|
||||
): PageResultDto<T> {
|
||||
return {
|
||||
items,
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
totalPages: Math.ceil(total / pageSize) || 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,104 @@
|
||||
import {
|
||||
Injectable,
|
||||
NestInterceptor,
|
||||
ExecutionContext,
|
||||
CallHandler,
|
||||
} from '@nestjs/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { ResultDto } from '../dto/result.dto';
|
||||
|
||||
/**
|
||||
* 响应数据接口
|
||||
*/
|
||||
interface Response<T> {
|
||||
code: number;
|
||||
message: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应转换拦截器
|
||||
*
|
||||
* 自动将 Controller 返回的数据包装为统一的响应格式 Result<T>
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Controller 返回
|
||||
* return { name: 'test' };
|
||||
*
|
||||
* // 实际响应
|
||||
* {
|
||||
* code: 200,
|
||||
* message: '操作成功',
|
||||
* data: { name: 'test' }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@Injectable()
|
||||
export class TransformInterceptor<T>
|
||||
implements NestInterceptor<T, Response<T>>
|
||||
{
|
||||
constructor(private reflector?: Reflector) {}
|
||||
|
||||
intercept(
|
||||
context: ExecutionContext,
|
||||
next: CallHandler,
|
||||
): Observable<Response<T>> {
|
||||
// 检查是否跳过转换(通过装饰器 @SkipTransform)
|
||||
let skipTransform = false;
|
||||
if (this.reflector) {
|
||||
skipTransform = this.reflector.get<boolean>(
|
||||
'skipTransform',
|
||||
context.getHandler(),
|
||||
) || false;
|
||||
}
|
||||
|
||||
if (skipTransform) {
|
||||
return next.handle();
|
||||
}
|
||||
|
||||
return next.handle().pipe(
|
||||
map((data) => {
|
||||
// 如果返回的数据已经是 ResultDto 格式,直接返回
|
||||
if (
|
||||
data &&
|
||||
typeof data === 'object' &&
|
||||
'code' in data &&
|
||||
'message' in data &&
|
||||
'data' in data
|
||||
) {
|
||||
return data;
|
||||
}
|
||||
|
||||
// 否则包装为统一格式
|
||||
return ResultDto.success(data);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳过响应转换装饰器
|
||||
*
|
||||
* 用于某些特殊接口,不需要统一响应格式
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* @Get('health')
|
||||
* @SkipTransform()
|
||||
* healthCheck() {
|
||||
* return { status: 'ok' };
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export const SkipTransform = () => {
|
||||
return (
|
||||
target: any,
|
||||
propertyKey: string,
|
||||
descriptor: PropertyDescriptor,
|
||||
) => {
|
||||
Reflect.defineMetadata('skipTransform', true, descriptor.value);
|
||||
};
|
||||
};
|
||||
77
reading-platform-backend/src/common/utils/json.util.ts
Normal file
77
reading-platform-backend/src/common/utils/json.util.ts
Normal file
@ -0,0 +1,77 @@
|
||||
/**
|
||||
* JSON 字段解析工具
|
||||
*
|
||||
* 用于安全解析数据库中存储的 JSON 字符串
|
||||
*/
|
||||
|
||||
/**
|
||||
* 解析 JSON 字段
|
||||
*
|
||||
* @param value - 数据库中的 JSON 字符串
|
||||
* @param defaultValue - 解析失败时的默认值
|
||||
* @returns 解析后的对象或默认值
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const tags = parseJsonField(course.gradeTags, []);
|
||||
* const settings = parseJsonField(user.settings, {});
|
||||
* ```
|
||||
*/
|
||||
export function parseJsonField<T>(
|
||||
value: string | null | undefined,
|
||||
defaultValue: T,
|
||||
): T {
|
||||
if (!value) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(value) as T;
|
||||
} catch {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全地将对象转换为 JSON 字符串
|
||||
*
|
||||
* @param value - 要转换的对象
|
||||
* @param defaultValue - 转换失败时的默认值
|
||||
* @returns JSON 字符串或默认值
|
||||
*/
|
||||
export function stringifyJsonField(
|
||||
value: any,
|
||||
defaultValue: string = '{}',
|
||||
): string {
|
||||
if (value === null || value === undefined) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.stringify(value);
|
||||
} catch {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 JSON 数组字段
|
||||
*
|
||||
* @param value - 数据库中的 JSON 数组字符串
|
||||
* @returns 解析后的数组或空数组
|
||||
*/
|
||||
export function parseJsonArray<T>(value: string | null | undefined): T[] {
|
||||
return parseJsonField<T[]>(value, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 JSON 对象字段
|
||||
*
|
||||
* @param value - 数据库中的 JSON 对象字符串
|
||||
* @returns 解析后的对象或空对象
|
||||
*/
|
||||
export function parseJsonObject<T extends Record<string, any>>(
|
||||
value: string | null | undefined,
|
||||
): T {
|
||||
return parseJsonField<T>(value, {} as T);
|
||||
}
|
||||
80
reading-platform-backend/src/common/utils/pagination.util.ts
Normal file
80
reading-platform-backend/src/common/utils/pagination.util.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import { PageResultDto } from '../dto/result.dto';
|
||||
|
||||
/**
|
||||
* 分页计算工具
|
||||
*/
|
||||
|
||||
/**
|
||||
* 计算分页参数
|
||||
*
|
||||
* @param page - 当前页码(从 1 开始)
|
||||
* @param pageSize - 每页数量
|
||||
* @returns Prisma 分页参数
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const { skip, take } = calculatePagination(1, 10);
|
||||
* // { skip: 0, take: 10 }
|
||||
* ```
|
||||
*/
|
||||
export function calculatePagination(page: number, pageSize: number) {
|
||||
const skip = (page - 1) * pageSize;
|
||||
return { skip, take: pageSize };
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建分页响应
|
||||
*
|
||||
* @param items - 数据列表
|
||||
* @param total - 总记录数
|
||||
* @param page - 当前页码
|
||||
* @param pageSize - 每页数量
|
||||
* @returns 分页响应对象
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const [items, total] = await Promise.all([
|
||||
* prisma.user.findMany({ skip, take }),
|
||||
* prisma.user.count()
|
||||
* ]);
|
||||
* return createPageResponse(items, total, page, pageSize);
|
||||
* ```
|
||||
*/
|
||||
export function createPageResponse<T>(
|
||||
items: T[],
|
||||
total: number,
|
||||
page: number,
|
||||
pageSize: number,
|
||||
): PageResultDto<T> {
|
||||
return PageResultDto.create(items, total, page, pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算总页数
|
||||
*
|
||||
* @param total - 总记录数
|
||||
* @param pageSize - 每页数量
|
||||
* @returns 总页数
|
||||
*/
|
||||
export function calculateTotalPages(total: number, pageSize: number): number {
|
||||
return Math.ceil(total / pageSize) || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证分页参数
|
||||
*
|
||||
* @param page - 页码
|
||||
* @param pageSize - 每页数量
|
||||
* @param maxPageSize - 最大每页数量(默认 100)
|
||||
* @returns 修正后的分页参数
|
||||
*/
|
||||
export function validatePagination(
|
||||
page: number,
|
||||
pageSize: number,
|
||||
maxPageSize: number = 100,
|
||||
): { page: number; pageSize: number } {
|
||||
return {
|
||||
page: Math.max(1, Math.floor(page)),
|
||||
pageSize: Math.max(1, Math.min(maxPageSize, Math.floor(pageSize))),
|
||||
};
|
||||
}
|
||||
@ -6,6 +6,8 @@ import { join } from 'path';
|
||||
import { AppModule } from './app.module';
|
||||
import * as compression from 'compression';
|
||||
import { HttpExceptionFilter } from './common/filters/http-exception.filter';
|
||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||
import { TransformInterceptor } from './common/interceptors/transform.interceptor';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
|
||||
@ -34,6 +36,33 @@ async function bootstrap() {
|
||||
// 全局异常过滤器
|
||||
app.useGlobalFilters(new HttpExceptionFilter());
|
||||
|
||||
// 全局响应转换拦截器(统一响应格式)
|
||||
app.useGlobalInterceptors(new TransformInterceptor());
|
||||
|
||||
// Swagger API 文档配置
|
||||
const config = new DocumentBuilder()
|
||||
.setTitle('幼儿阅读教学服务平台 API')
|
||||
.setDescription('提供课程管理、授课记录、学生评价等功能的 API 文档')
|
||||
.setVersion('2.0')
|
||||
.addBearerAuth()
|
||||
.addTag('auth', '认证相关接口')
|
||||
.addTag('admin', '超管端接口')
|
||||
.addTag('school', '学校端接口')
|
||||
.addTag('teacher', '教师端接口')
|
||||
.addTag('parent', '家长端接口')
|
||||
.addTag('common', '公共接口')
|
||||
.build();
|
||||
|
||||
const document = SwaggerModule.createDocument(app, config);
|
||||
SwaggerModule.setup('api-docs', app, document, {
|
||||
swaggerOptions: {
|
||||
persistAuthorization: true, // 持久化认证信息
|
||||
tagsSorter: 'alpha', // 按字母顺序排序标签
|
||||
operationsSorter: 'alpha', // 按字母顺序排序操作
|
||||
},
|
||||
customSiteTitle: 'API 文档',
|
||||
});
|
||||
|
||||
// 启用压缩
|
||||
// app.use(compression());
|
||||
|
||||
@ -65,6 +94,7 @@ async function bootstrap() {
|
||||
║ ║
|
||||
║ 📍 Local: http://localhost:${port} ║
|
||||
║ 📍 API: http://localhost:${port}/api/v1 ║
|
||||
║ 📍 Docs: http://localhost:${port}/api-docs ║
|
||||
║ 📍 Prisma: npx prisma studio ║
|
||||
║ ║
|
||||
╚═════════════════════════════════════════════════════╝
|
||||
|
||||
@ -14,7 +14,65 @@ import { CourseService } from './course.service';
|
||||
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
|
||||
import { RolesGuard } from '../common/guards/roles.guard';
|
||||
import { Roles } from '../common/decorators/roles.decorator';
|
||||
import {
|
||||
ApiTags,
|
||||
ApiOperation,
|
||||
ApiParam,
|
||||
ApiQuery,
|
||||
ApiBody,
|
||||
ApiBearerAuth,
|
||||
ApiProperty,
|
||||
} from '@nestjs/swagger';
|
||||
|
||||
// ============= DTO 定义 =============
|
||||
|
||||
class CourseQueryDto {
|
||||
@ApiProperty({ description: '页码', example: 1, required: false })
|
||||
page?: number;
|
||||
|
||||
@ApiProperty({ description: '每页数量', example: 10, required: false })
|
||||
pageSize?: number;
|
||||
|
||||
@ApiProperty({ description: '年级筛选', example: '小班', required: false })
|
||||
grade?: string;
|
||||
|
||||
@ApiProperty({ description: '状态筛选', example: 'PUBLISHED', required: false })
|
||||
status?: string;
|
||||
|
||||
@ApiProperty({ description: '关键词搜索', example: '春天', required: false })
|
||||
keyword?: string;
|
||||
}
|
||||
|
||||
class SubmitCourseDto {
|
||||
@ApiProperty({ description: '是否确认版权', example: true })
|
||||
copyrightConfirmed: boolean;
|
||||
}
|
||||
|
||||
class ApproveCourseDto {
|
||||
@ApiProperty({ description: '审核检查项', required: false })
|
||||
checklist?: any;
|
||||
|
||||
@ApiProperty({ description: '审核意见', required: false })
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
class RejectCourseDto {
|
||||
@ApiProperty({ description: '审核检查项', required: false })
|
||||
checklist?: any;
|
||||
|
||||
@ApiProperty({ description: '驳回原因', example: '课程内容不完整' })
|
||||
comment: string;
|
||||
}
|
||||
|
||||
class DirectPublishDto {
|
||||
@ApiProperty({ description: '是否跳过验证', example: false, required: false })
|
||||
skipValidation?: boolean;
|
||||
}
|
||||
|
||||
// ============= 控制器 =============
|
||||
|
||||
@ApiTags('courses')
|
||||
@ApiBearerAuth()
|
||||
@Controller('courses')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles('admin')
|
||||
@ -22,36 +80,57 @@ export class CourseController {
|
||||
constructor(private readonly courseService: CourseService) {}
|
||||
|
||||
@Get()
|
||||
findAll(@Query() query: any) {
|
||||
@ApiOperation({ summary: '获取课程包列表' })
|
||||
@ApiQuery({ name: 'page', required: false, type: Number })
|
||||
@ApiQuery({ name: 'pageSize', required: false, type: Number })
|
||||
@ApiQuery({ name: 'grade', required: false, type: String })
|
||||
@ApiQuery({ name: 'status', required: false, type: String })
|
||||
@ApiQuery({ name: 'keyword', required: false, type: String })
|
||||
findAll(@Query() query: CourseQueryDto) {
|
||||
return this.courseService.findAll(query);
|
||||
}
|
||||
|
||||
@Get('review')
|
||||
getReviewList(@Query() query: any) {
|
||||
@ApiOperation({ summary: '获取待审核课程列表' })
|
||||
@ApiQuery({ name: 'page', required: false, type: Number })
|
||||
@ApiQuery({ name: 'pageSize', required: false, type: Number })
|
||||
@ApiQuery({ name: 'grade', required: false, type: String })
|
||||
@ApiQuery({ name: 'status', required: false, type: String })
|
||||
getReviewList(@Query() query: CourseQueryDto) {
|
||||
return this.courseService.getReviewList(query);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@ApiOperation({ summary: '获取课程包详情' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: String })
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.courseService.findOne(+id);
|
||||
}
|
||||
|
||||
@Get(':id/stats')
|
||||
@ApiOperation({ summary: '获取课程包统计数据' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: String })
|
||||
getStats(@Param('id') id: string) {
|
||||
return this.courseService.getStats(+id);
|
||||
}
|
||||
|
||||
@Get(':id/validate')
|
||||
@ApiOperation({ summary: '验证课程包完整性' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: String })
|
||||
validate(@Param('id') id: string) {
|
||||
return this.courseService.validate(+id);
|
||||
}
|
||||
|
||||
@Get(':id/versions')
|
||||
@ApiOperation({ summary: '获取课程包版本历史' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: String })
|
||||
getVersionHistory(@Param('id') id: string) {
|
||||
return this.courseService.getVersionHistory(+id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: '创建课程包' })
|
||||
@ApiBody({ type: Object, description: '课程包数据' })
|
||||
create(@Body() createCourseDto: any, @Request() req: any) {
|
||||
return this.courseService.create({
|
||||
...createCourseDto,
|
||||
@ -60,11 +139,16 @@ export class CourseController {
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@ApiOperation({ summary: '更新课程包' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: String })
|
||||
@ApiBody({ type: Object, description: '课程包数据' })
|
||||
update(@Param('id') id: string, @Body() updateCourseDto: any) {
|
||||
return this.courseService.update(+id, updateCourseDto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@ApiOperation({ summary: '删除课程包' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: String })
|
||||
remove(@Param('id') id: string) {
|
||||
return this.courseService.remove(+id);
|
||||
}
|
||||
@ -74,7 +158,10 @@ export class CourseController {
|
||||
* POST /api/v1/courses/:id/submit
|
||||
*/
|
||||
@Post(':id/submit')
|
||||
submit(@Param('id') id: string, @Body() body: { copyrightConfirmed: boolean }, @Request() req: any) {
|
||||
@ApiOperation({ summary: '提交课程包审核' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: String })
|
||||
@ApiBody({ type: SubmitCourseDto })
|
||||
submit(@Param('id') id: string, @Body() body: SubmitCourseDto, @Request() req: any) {
|
||||
const userId = req.user?.userId || 0;
|
||||
return this.courseService.submit(+id, userId, body.copyrightConfirmed);
|
||||
}
|
||||
@ -84,6 +171,8 @@ export class CourseController {
|
||||
* POST /api/v1/courses/:id/withdraw
|
||||
*/
|
||||
@Post(':id/withdraw')
|
||||
@ApiOperation({ summary: '撤销课程包审核申请' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: String })
|
||||
withdraw(@Param('id') id: string, @Request() req: any) {
|
||||
const userId = req.user?.userId || 0;
|
||||
return this.courseService.withdraw(+id, userId);
|
||||
@ -94,9 +183,12 @@ export class CourseController {
|
||||
* POST /api/v1/courses/:id/approve
|
||||
*/
|
||||
@Post(':id/approve')
|
||||
@ApiOperation({ summary: '审核通过课程包' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: String })
|
||||
@ApiBody({ type: ApproveCourseDto })
|
||||
approve(
|
||||
@Param('id') id: string,
|
||||
@Body() body: { checklist?: any; comment?: string },
|
||||
@Body() body: ApproveCourseDto,
|
||||
@Request() req: any,
|
||||
) {
|
||||
const reviewerId = req.user?.userId || 0;
|
||||
@ -108,9 +200,12 @@ export class CourseController {
|
||||
* POST /api/v1/courses/:id/reject
|
||||
*/
|
||||
@Post(':id/reject')
|
||||
@ApiOperation({ summary: '审核驳回课程包' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: String })
|
||||
@ApiBody({ type: RejectCourseDto })
|
||||
reject(
|
||||
@Param('id') id: string,
|
||||
@Body() body: { checklist?: any; comment: string },
|
||||
@Body() body: RejectCourseDto,
|
||||
@Request() req: any,
|
||||
) {
|
||||
const reviewerId = req.user?.userId || 0;
|
||||
@ -122,10 +217,13 @@ export class CourseController {
|
||||
* POST /api/v1/courses/:id/direct-publish
|
||||
*/
|
||||
@Post(':id/direct-publish')
|
||||
@ApiOperation({ summary: '直接发布课程包(超管专用)' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: String })
|
||||
@ApiBody({ type: DirectPublishDto })
|
||||
@Roles('admin')
|
||||
directPublish(
|
||||
@Param('id') id: string,
|
||||
@Body() body: { skipValidation?: boolean },
|
||||
@Body() body: DirectPublishDto,
|
||||
@Request() req: any,
|
||||
) {
|
||||
const userId = req.user?.userId || 0;
|
||||
@ -137,6 +235,8 @@ export class CourseController {
|
||||
* POST /api/v1/courses/:id/publish
|
||||
*/
|
||||
@Post(':id/publish')
|
||||
@ApiOperation({ summary: '发布课程包' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: String })
|
||||
publish(@Param('id') id: string) {
|
||||
return this.courseService.publish(+id);
|
||||
}
|
||||
@ -146,6 +246,8 @@ export class CourseController {
|
||||
* POST /api/v1/courses/:id/unpublish
|
||||
*/
|
||||
@Post(':id/unpublish')
|
||||
@ApiOperation({ summary: '下架课程包' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: String })
|
||||
unpublish(@Param('id') id: string) {
|
||||
return this.courseService.unpublish(+id);
|
||||
}
|
||||
@ -155,6 +257,8 @@ export class CourseController {
|
||||
* POST /api/v1/courses/:id/republish
|
||||
*/
|
||||
@Post(':id/republish')
|
||||
@ApiOperation({ summary: '重新发布课程包' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: String })
|
||||
republish(@Param('id') id: string) {
|
||||
return this.courseService.republish(+id);
|
||||
}
|
||||
|
||||
@ -14,8 +14,105 @@ import { FinishLessonDto } from './dto/finish-lesson.dto';
|
||||
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
|
||||
import { RolesGuard } from '../common/guards/roles.guard';
|
||||
import { Roles } from '../common/decorators/roles.decorator';
|
||||
import {
|
||||
ApiTags,
|
||||
ApiOperation,
|
||||
ApiParam,
|
||||
ApiQuery,
|
||||
ApiBody,
|
||||
ApiBearerAuth,
|
||||
ApiProperty,
|
||||
} from '@nestjs/swagger';
|
||||
|
||||
// 教师端授课控制器
|
||||
// ============= DTO 定义 =============
|
||||
|
||||
class LessonQueryDto {
|
||||
@ApiProperty({ description: '页码', example: 1, required: false })
|
||||
page?: number;
|
||||
|
||||
@ApiProperty({ description: '每页数量', example: 10, required: false })
|
||||
pageSize?: number;
|
||||
|
||||
@ApiProperty({ description: '课程ID', required: false })
|
||||
courseId?: number;
|
||||
|
||||
@ApiProperty({ description: '状态筛选', required: false })
|
||||
status?: string;
|
||||
}
|
||||
|
||||
class StudentRecordDto {
|
||||
@ApiProperty({ description: '专注度', required: false })
|
||||
focus?: number;
|
||||
|
||||
@ApiProperty({ description: '参与度', required: false })
|
||||
participation?: number;
|
||||
|
||||
@ApiProperty({ description: '兴趣度', required: false })
|
||||
interest?: number;
|
||||
|
||||
@ApiProperty({ description: '理解度', required: false })
|
||||
understanding?: number;
|
||||
|
||||
@ApiProperty({ description: '备注', required: false })
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
class BatchStudentRecordsDto {
|
||||
@ApiProperty({ description: '学生记录列表', type: [Object] })
|
||||
records: Array<{
|
||||
studentId: number;
|
||||
focus?: number;
|
||||
participation?: number;
|
||||
interest?: number;
|
||||
understanding?: number;
|
||||
notes?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
class LessonProgressDto {
|
||||
@ApiProperty({ description: '课程ID列表', required: false })
|
||||
lessonIds?: number[];
|
||||
|
||||
@ApiProperty({ description: '已完成课程ID列表', required: false })
|
||||
completedLessonIds?: number[];
|
||||
|
||||
@ApiProperty({ description: '当前课程ID', required: false })
|
||||
currentLessonId?: number;
|
||||
|
||||
@ApiProperty({ description: '当前环节ID', required: false })
|
||||
currentStepId?: number;
|
||||
|
||||
@ApiProperty({ description: '进度数据', required: false })
|
||||
progressData?: any;
|
||||
}
|
||||
|
||||
class LessonFeedbackDto {
|
||||
@ApiProperty({ description: '设计质量评分', required: false })
|
||||
designQuality?: number;
|
||||
|
||||
@ApiProperty({ description: '参与度评分', required: false })
|
||||
participation?: number;
|
||||
|
||||
@ApiProperty({ description: '目标达成度评分', required: false })
|
||||
goalAchievement?: number;
|
||||
|
||||
@ApiProperty({ description: '环节反馈', required: false })
|
||||
stepFeedbacks?: any;
|
||||
|
||||
@ApiProperty({ description: '优点', required: false })
|
||||
pros?: string;
|
||||
|
||||
@ApiProperty({ description: '建议', required: false })
|
||||
suggestions?: string;
|
||||
|
||||
@ApiProperty({ description: '完成的活动', required: false })
|
||||
activitiesDone?: any;
|
||||
}
|
||||
|
||||
// ============= 教师端授课控制器 =============
|
||||
|
||||
@ApiTags('teacher/lessons')
|
||||
@ApiBearerAuth()
|
||||
@Controller('teacher/lessons')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles('teacher')
|
||||
@ -23,55 +120,80 @@ export class LessonController {
|
||||
constructor(private readonly lessonService: LessonService) {}
|
||||
|
||||
@Get()
|
||||
findAll(@Request() req: any, @Query() query: any) {
|
||||
@ApiOperation({ summary: '获取教师授课记录列表' })
|
||||
@ApiQuery({ name: 'page', required: false, type: Number })
|
||||
@ApiQuery({ name: 'pageSize', required: false, type: Number })
|
||||
@ApiQuery({ name: 'courseId', required: false, type: Number })
|
||||
@ApiQuery({ name: 'status', required: false, type: String })
|
||||
findAll(@Request() req: any, @Query() query: LessonQueryDto) {
|
||||
return this.lessonService.findByTeacher(req.user.userId, query);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@ApiOperation({ summary: '获取授课记录详情' })
|
||||
@ApiParam({ name: 'id', description: '授课记录 ID', type: String })
|
||||
findOne(@Request() req: any, @Param('id') id: string) {
|
||||
return this.lessonService.findOne(+id, req.user.userId);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: '创建授课记录' })
|
||||
@ApiBody({ type: CreateLessonDto })
|
||||
create(@Request() req: any, @Body() dto: CreateLessonDto) {
|
||||
return this.lessonService.create(req.user.userId, req.user.tenantId, dto);
|
||||
}
|
||||
|
||||
@Post(':id/start')
|
||||
@ApiOperation({ summary: '开始授课' })
|
||||
@ApiParam({ name: 'id', description: '授课记录 ID', type: String })
|
||||
start(@Request() req: any, @Param('id') id: string) {
|
||||
return this.lessonService.start(+id, req.user.userId);
|
||||
}
|
||||
|
||||
@Post(':id/finish')
|
||||
@ApiOperation({ summary: '完成授课' })
|
||||
@ApiParam({ name: 'id', description: '授课记录 ID', type: String })
|
||||
@ApiBody({ type: FinishLessonDto })
|
||||
finish(@Request() req: any, @Param('id') id: string, @Body() dto: FinishLessonDto) {
|
||||
return this.lessonService.finish(+id, req.user.userId, dto);
|
||||
}
|
||||
|
||||
@Post(':id/cancel')
|
||||
@ApiOperation({ summary: '取消授课' })
|
||||
@ApiParam({ name: 'id', description: '授课记录 ID', type: String })
|
||||
cancel(@Request() req: any, @Param('id') id: string) {
|
||||
return this.lessonService.cancel(+id, req.user.userId);
|
||||
}
|
||||
|
||||
@Post(':id/students/:studentId/record')
|
||||
@ApiOperation({ summary: '保存学生课堂记录' })
|
||||
@ApiParam({ name: 'id', description: '授课记录 ID', type: String })
|
||||
@ApiParam({ name: 'studentId', description: '学生 ID', type: String })
|
||||
@ApiBody({ type: StudentRecordDto })
|
||||
saveStudentRecord(
|
||||
@Request() req: any,
|
||||
@Param('id') id: string,
|
||||
@Param('studentId') studentId: string,
|
||||
@Body() data: any
|
||||
@Body() data: StudentRecordDto
|
||||
) {
|
||||
return this.lessonService.saveStudentRecord(+id, req.user.userId, +studentId, data);
|
||||
}
|
||||
|
||||
@Get(':id/student-records')
|
||||
@ApiOperation({ summary: '获取授课记录的学生数据' })
|
||||
@ApiParam({ name: 'id', description: '授课记录 ID', type: String })
|
||||
getStudentRecords(@Request() req: any, @Param('id') id: string) {
|
||||
return this.lessonService.getStudentRecords(+id, req.user.userId);
|
||||
}
|
||||
|
||||
@Post(':id/student-records/batch')
|
||||
@ApiOperation({ summary: '批量保存学生课堂记录' })
|
||||
@ApiParam({ name: 'id', description: '授课记录 ID', type: String })
|
||||
@ApiBody({ type: BatchStudentRecordsDto })
|
||||
batchSaveStudentRecords(
|
||||
@Request() req: any,
|
||||
@Param('id') id: string,
|
||||
@Body() data: { records: Array<{ studentId: number; focus?: number; participation?: number; interest?: number; understanding?: number; notes?: string }> }
|
||||
@Body() data: BatchStudentRecordsDto
|
||||
) {
|
||||
return this.lessonService.batchSaveStudentRecords(+id, req.user.userId, data.records);
|
||||
}
|
||||
@ -79,21 +201,50 @@ export class LessonController {
|
||||
// ==================== 课程反馈 ====================
|
||||
|
||||
@Post(':id/feedback')
|
||||
@ApiOperation({ summary: '提交课程反馈' })
|
||||
@ApiParam({ name: 'id', description: '授课记录 ID', type: String })
|
||||
@ApiBody({ type: LessonFeedbackDto })
|
||||
submitFeedback(
|
||||
@Request() req: any,
|
||||
@Param('id') id: string,
|
||||
@Body() data: any
|
||||
@Body() data: LessonFeedbackDto
|
||||
) {
|
||||
return this.lessonService.submitFeedback(+id, req.user.userId, data);
|
||||
}
|
||||
|
||||
@Get(':id/feedback')
|
||||
@ApiOperation({ summary: '获取课程反馈' })
|
||||
@ApiParam({ name: 'id', description: '授课记录 ID', type: String })
|
||||
getFeedback(@Request() req: any, @Param('id') id: string) {
|
||||
return this.lessonService.getFeedback(+id, req.user.userId);
|
||||
}
|
||||
|
||||
// ==================== 课程进度追踪 ====================
|
||||
|
||||
@Post(':id/progress')
|
||||
@ApiOperation({ summary: '保存课程进度' })
|
||||
@ApiParam({ name: 'id', description: '授课记录 ID', type: String })
|
||||
@ApiBody({ type: LessonProgressDto })
|
||||
saveProgress(
|
||||
@Request() req: any,
|
||||
@Param('id') id: string,
|
||||
@Body() data: LessonProgressDto
|
||||
) {
|
||||
return this.lessonService.saveProgress(+id, req.user.userId, data);
|
||||
}
|
||||
|
||||
// 教师端反馈控制器
|
||||
@Get(':id/progress')
|
||||
@ApiOperation({ summary: '获取课程进度' })
|
||||
@ApiParam({ name: 'id', description: '授课记录 ID', type: String })
|
||||
getProgress(@Request() req: any, @Param('id') id: string) {
|
||||
return this.lessonService.getProgress(+id, req.user.userId);
|
||||
}
|
||||
}
|
||||
|
||||
// ============= 教师端反馈控制器 =============
|
||||
|
||||
@ApiTags('teacher/feedbacks')
|
||||
@ApiBearerAuth()
|
||||
@Controller('teacher/feedbacks')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles('teacher')
|
||||
@ -101,7 +252,10 @@ export class TeacherFeedbackController {
|
||||
constructor(private readonly lessonService: LessonService) {}
|
||||
|
||||
@Get()
|
||||
findAll(@Request() req: any, @Query() query: any) {
|
||||
@ApiOperation({ summary: '获取教师端反馈列表' })
|
||||
@ApiQuery({ name: 'page', required: false, type: Number })
|
||||
@ApiQuery({ name: 'pageSize', required: false, type: Number })
|
||||
findAll(@Request() req: any, @Query() query: LessonQueryDto) {
|
||||
return this.lessonService.getFeedbacksByTenant(req.user.tenantId, {
|
||||
...query,
|
||||
teacherId: req.user.userId,
|
||||
@ -109,12 +263,16 @@ export class TeacherFeedbackController {
|
||||
}
|
||||
|
||||
@Get('stats')
|
||||
@ApiOperation({ summary: '获取教师端反馈统计' })
|
||||
getStats(@Request() req: any) {
|
||||
return this.lessonService.getTeacherFeedbackStats(req.user.userId);
|
||||
}
|
||||
}
|
||||
|
||||
// 学校端反馈控制器
|
||||
// ============= 学校端反馈控制器 =============
|
||||
|
||||
@ApiTags('school/feedbacks')
|
||||
@ApiBearerAuth()
|
||||
@Controller('school/feedbacks')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles('school')
|
||||
@ -122,11 +280,15 @@ export class SchoolFeedbackController {
|
||||
constructor(private readonly lessonService: LessonService) {}
|
||||
|
||||
@Get()
|
||||
findAll(@Request() req: any, @Query() query: any) {
|
||||
@ApiOperation({ summary: '获取学校端反馈列表' })
|
||||
@ApiQuery({ name: 'page', required: false, type: Number })
|
||||
@ApiQuery({ name: 'pageSize', required: false, type: Number })
|
||||
findAll(@Request() req: any, @Query() query: LessonQueryDto) {
|
||||
return this.lessonService.getFeedbacksByTenant(req.user.tenantId, query);
|
||||
}
|
||||
|
||||
@Get('stats')
|
||||
@ApiOperation({ summary: '获取学校端反馈统计' })
|
||||
getStats(@Request() req: any) {
|
||||
return this.lessonService.getFeedbackStats(req.user.tenantId);
|
||||
}
|
||||
|
||||
@ -14,8 +14,99 @@ import { SchoolCourseService } from './school-course.service';
|
||||
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
|
||||
import { RolesGuard } from '../common/guards/roles.guard';
|
||||
import { Roles } from '../common/decorators/roles.decorator';
|
||||
import {
|
||||
ApiTags,
|
||||
ApiOperation,
|
||||
ApiParam,
|
||||
ApiBody,
|
||||
ApiBearerAuth,
|
||||
ApiProperty,
|
||||
} from '@nestjs/swagger';
|
||||
|
||||
// 学校端校本课程包控制器
|
||||
// ============= DTO 定义 =============
|
||||
|
||||
class CreateSchoolCourseDto {
|
||||
@ApiProperty({ description: '源课程ID' })
|
||||
sourceCourseId: number;
|
||||
|
||||
@ApiProperty({ description: '课程名称' })
|
||||
name: string;
|
||||
|
||||
@ApiProperty({ description: '课程描述', required: false })
|
||||
description?: string;
|
||||
|
||||
@ApiProperty({ description: '修改说明', required: false })
|
||||
changesSummary?: string;
|
||||
}
|
||||
|
||||
class UpdateSchoolCourseDto {
|
||||
@ApiProperty({ description: '课程名称', required: false })
|
||||
name?: string;
|
||||
|
||||
@ApiProperty({ description: '课程描述', required: false })
|
||||
description?: string;
|
||||
|
||||
@ApiProperty({ description: '修改说明', required: false })
|
||||
changesSummary?: string;
|
||||
|
||||
@ApiProperty({ description: '状态', required: false })
|
||||
status?: string;
|
||||
}
|
||||
|
||||
class UpdateLessonDto {
|
||||
@ApiProperty({ description: '教学目标', required: false })
|
||||
objectives?: string;
|
||||
|
||||
@ApiProperty({ description: '课前准备', required: false })
|
||||
preparation?: string;
|
||||
|
||||
@ApiProperty({ description: '课后延伸', required: false })
|
||||
extension?: string;
|
||||
|
||||
@ApiProperty({ description: '课后反思', required: false })
|
||||
reflection?: string;
|
||||
|
||||
@ApiProperty({ description: '修改备注', required: false })
|
||||
changeNote?: string;
|
||||
|
||||
@ApiProperty({ description: '环节数据', required: false })
|
||||
stepsData?: string;
|
||||
}
|
||||
|
||||
class CreateReservationDto {
|
||||
@ApiProperty({ description: '教师ID' })
|
||||
teacherId: number;
|
||||
|
||||
@ApiProperty({ description: '班级ID' })
|
||||
classId: number;
|
||||
|
||||
@ApiProperty({ description: '预约日期', example: '2026-03-15' })
|
||||
scheduledDate: string;
|
||||
|
||||
@ApiProperty({ description: '预约时间', required: false })
|
||||
scheduledTime?: string;
|
||||
|
||||
@ApiProperty({ description: '备注', required: false })
|
||||
note?: string;
|
||||
}
|
||||
|
||||
class CreateFromSourceDto {
|
||||
@ApiProperty({ description: '源课程ID' })
|
||||
sourceCourseId: number;
|
||||
|
||||
@ApiProperty({ description: '保存位置', enum: ['PERSONAL', 'SCHOOL'] })
|
||||
saveLocation: 'PERSONAL' | 'SCHOOL';
|
||||
}
|
||||
|
||||
class ReviewDto {
|
||||
@ApiProperty({ description: '审核意见', required: false })
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
// ============= 学校端校本课程包控制器 =============
|
||||
|
||||
@ApiTags('school/school-courses')
|
||||
@ApiBearerAuth()
|
||||
@Controller('school/school-courses')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles('school')
|
||||
@ -23,31 +114,28 @@ export class SchoolCourseController {
|
||||
constructor(private readonly schoolCourseService: SchoolCourseService) {}
|
||||
|
||||
@Get()
|
||||
async findAll(@Request() req: any) {
|
||||
@ApiOperation({ summary: '获取学校端校本课程包列表' })
|
||||
findAll(@Request() req: any) {
|
||||
return this.schoolCourseService.findAll(req.user.tenantId);
|
||||
}
|
||||
|
||||
@Get('source-courses')
|
||||
async getSourceCourses(@Request() req: any) {
|
||||
@ApiOperation({ summary: '获取可导入的源课程列表' })
|
||||
getSourceCourses(@Request() req: any) {
|
||||
return this.schoolCourseService.getSourceCourses(req.user.tenantId);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
async findOne(@Param('id', ParseIntPipe) id: number, @Request() req: any) {
|
||||
@ApiOperation({ summary: '获取校本课程包详情' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: Number })
|
||||
findOne(@Param('id', ParseIntPipe) id: number, @Request() req: any) {
|
||||
return this.schoolCourseService.findOne(id, req.user.tenantId);
|
||||
}
|
||||
|
||||
@Post()
|
||||
async create(
|
||||
@Request() req: any,
|
||||
@Body()
|
||||
body: {
|
||||
sourceCourseId: number;
|
||||
name: string;
|
||||
description?: string;
|
||||
changesSummary?: string;
|
||||
},
|
||||
) {
|
||||
@ApiOperation({ summary: '创建校本课程包' })
|
||||
@ApiBody({ type: CreateSchoolCourseDto })
|
||||
create(@Request() req: any, @Body() body: CreateSchoolCourseDto) {
|
||||
return this.schoolCourseService.create(
|
||||
req.user.tenantId,
|
||||
body.sourceCourseId,
|
||||
@ -57,28 +145,29 @@ export class SchoolCourseController {
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
async update(
|
||||
@ApiOperation({ summary: '更新校本课程包' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: Number })
|
||||
@ApiBody({ type: UpdateSchoolCourseDto })
|
||||
update(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Request() req: any,
|
||||
@Body()
|
||||
body: {
|
||||
name?: string;
|
||||
description?: string;
|
||||
changesSummary?: string;
|
||||
status?: string;
|
||||
},
|
||||
@Body() body: UpdateSchoolCourseDto,
|
||||
) {
|
||||
return this.schoolCourseService.update(id, req.user.tenantId, body);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
async remove(@Param('id', ParseIntPipe) id: number, @Request() req: any) {
|
||||
@ApiOperation({ summary: '删除校本课程包' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: Number })
|
||||
remove(@Param('id', ParseIntPipe) id: number, @Request() req: any) {
|
||||
return this.schoolCourseService.delete(id, req.user.tenantId);
|
||||
}
|
||||
|
||||
// 课程管理
|
||||
@Get(':id/lessons')
|
||||
async findLessons(
|
||||
@ApiOperation({ summary: '获取校本课程包的课时列表' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: Number })
|
||||
findLessons(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Request() req: any,
|
||||
) {
|
||||
@ -86,26 +175,24 @@ export class SchoolCourseController {
|
||||
}
|
||||
|
||||
@Put(':id/lessons/:lessonId')
|
||||
async updateLesson(
|
||||
@ApiOperation({ summary: '更新校本课程包的课时' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: Number })
|
||||
@ApiParam({ name: 'lessonId', description: '课时 ID', type: Number })
|
||||
@ApiBody({ type: UpdateLessonDto })
|
||||
updateLesson(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Param('lessonId', ParseIntPipe) lessonId: number,
|
||||
@Request() req: any,
|
||||
@Body()
|
||||
body: {
|
||||
objectives?: string;
|
||||
preparation?: string;
|
||||
extension?: string;
|
||||
reflection?: string;
|
||||
changeNote?: string;
|
||||
stepsData?: string;
|
||||
},
|
||||
@Body() body: UpdateLessonDto,
|
||||
) {
|
||||
return this.schoolCourseService.updateLesson(id, lessonId, req.user.tenantId, body);
|
||||
}
|
||||
|
||||
// 预约管理
|
||||
@Get(':id/reservations')
|
||||
async findReservations(
|
||||
@ApiOperation({ summary: '获取课程包的预约列表' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: Number })
|
||||
findReservations(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Request() req: any,
|
||||
) {
|
||||
@ -113,31 +200,94 @@ export class SchoolCourseController {
|
||||
}
|
||||
|
||||
@Post(':id/reservations')
|
||||
async createReservation(
|
||||
@ApiOperation({ summary: '创建课程预约' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: Number })
|
||||
@ApiBody({ type: CreateReservationDto })
|
||||
createReservation(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Request() req: any,
|
||||
@Body()
|
||||
body: {
|
||||
teacherId: number;
|
||||
classId: number;
|
||||
scheduledDate: string;
|
||||
scheduledTime?: string;
|
||||
note?: string;
|
||||
},
|
||||
@Body() body: CreateReservationDto,
|
||||
) {
|
||||
return this.schoolCourseService.createReservation(id, req.user.tenantId, body);
|
||||
}
|
||||
|
||||
@Post('reservations/:reservationId/cancel')
|
||||
async cancelReservation(
|
||||
@ApiOperation({ summary: '取消课程预约' })
|
||||
@ApiParam({ name: 'reservationId', description: '预约 ID', type: Number })
|
||||
cancelReservation(
|
||||
@Param('reservationId', ParseIntPipe) reservationId: number,
|
||||
@Request() req: any,
|
||||
) {
|
||||
return this.schoolCourseService.cancelReservation(reservationId, req.user.tenantId);
|
||||
}
|
||||
|
||||
// ==================== 完整数据API ====================
|
||||
|
||||
@Post('from-source')
|
||||
@ApiOperation({ summary: '从源课程创建校本课程包' })
|
||||
@ApiBody({ type: CreateFromSourceDto })
|
||||
createFromSource(@Request() req: any, @Body() body: CreateFromSourceDto) {
|
||||
return this.schoolCourseService.createFromSource(
|
||||
req.user.tenantId,
|
||||
body.sourceCourseId,
|
||||
req.user.id,
|
||||
body.saveLocation,
|
||||
);
|
||||
}
|
||||
|
||||
// 教师端校本课程包控制器
|
||||
@Get(':id/full')
|
||||
@ApiOperation({ summary: '获取校本课程包完整数据' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: Number })
|
||||
getFullDetail(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Request() req: any,
|
||||
) {
|
||||
return this.schoolCourseService.getFullDetail(id, req.user.tenantId);
|
||||
}
|
||||
|
||||
@Put(':id/full')
|
||||
@ApiOperation({ summary: '更新校本课程包完整数据' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: Number })
|
||||
@ApiBody({ type: Object, description: '完整课程包数据' })
|
||||
updateFull(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Request() req: any,
|
||||
@Body() body: any,
|
||||
) {
|
||||
return this.schoolCourseService.updateFull(id, req.user.tenantId, body);
|
||||
}
|
||||
|
||||
// ==================== 审核API ====================
|
||||
|
||||
@Post(':id/approve')
|
||||
@ApiOperation({ summary: '审核通过校本课程包' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: Number })
|
||||
@ApiBody({ type: ReviewDto })
|
||||
approve(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Request() req: any,
|
||||
@Body() body: ReviewDto,
|
||||
) {
|
||||
return this.schoolCourseService.approve(id, req.user.tenantId, req.user.id, body.comment);
|
||||
}
|
||||
|
||||
@Post(':id/reject')
|
||||
@ApiOperation({ summary: '审核驳回校本课程包' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: Number })
|
||||
@ApiBody({ type: ReviewDto })
|
||||
reject(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Request() req: any,
|
||||
@Body() body: ReviewDto,
|
||||
) {
|
||||
return this.schoolCourseService.reject(id, req.user.tenantId, req.user.id, body.comment);
|
||||
}
|
||||
}
|
||||
|
||||
// ============= 教师端校本课程包控制器 =============
|
||||
|
||||
@ApiTags('teacher/school-courses')
|
||||
@ApiBearerAuth()
|
||||
@Controller('teacher/school-courses')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles('teacher')
|
||||
@ -145,31 +295,28 @@ export class TeacherSchoolCourseController {
|
||||
constructor(private readonly schoolCourseService: SchoolCourseService) {}
|
||||
|
||||
@Get()
|
||||
async findAll(@Request() req: any) {
|
||||
@ApiOperation({ summary: '获取教师端校本课程包列表' })
|
||||
findAll(@Request() req: any) {
|
||||
return this.schoolCourseService.findAll(req.user.tenantId, req.user.id);
|
||||
}
|
||||
|
||||
@Get('source-courses')
|
||||
async getSourceCourses(@Request() req: any) {
|
||||
@ApiOperation({ summary: '获取可导入的源课程列表(教师端)' })
|
||||
getSourceCourses(@Request() req: any) {
|
||||
return this.schoolCourseService.getSourceCourses(req.user.tenantId);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
async findOne(@Param('id', ParseIntPipe) id: number, @Request() req: any) {
|
||||
@ApiOperation({ summary: '获取校本课程包详情(教师端)' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: Number })
|
||||
findOne(@Param('id', ParseIntPipe) id: number, @Request() req: any) {
|
||||
return this.schoolCourseService.findOne(id, req.user.tenantId);
|
||||
}
|
||||
|
||||
@Post()
|
||||
async create(
|
||||
@Request() req: any,
|
||||
@Body()
|
||||
body: {
|
||||
sourceCourseId: number;
|
||||
name: string;
|
||||
description?: string;
|
||||
changesSummary?: string;
|
||||
},
|
||||
) {
|
||||
@ApiOperation({ summary: '创建校本课程包(教师端)' })
|
||||
@ApiBody({ type: CreateSchoolCourseDto })
|
||||
create(@Request() req: any, @Body() body: CreateSchoolCourseDto) {
|
||||
return this.schoolCourseService.create(
|
||||
req.user.tenantId,
|
||||
body.sourceCourseId,
|
||||
@ -179,28 +326,29 @@ export class TeacherSchoolCourseController {
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
async update(
|
||||
@ApiOperation({ summary: '更新校本课程包(教师端)' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: Number })
|
||||
@ApiBody({ type: UpdateSchoolCourseDto })
|
||||
update(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Request() req: any,
|
||||
@Body()
|
||||
body: {
|
||||
name?: string;
|
||||
description?: string;
|
||||
changesSummary?: string;
|
||||
status?: string;
|
||||
},
|
||||
@Body() body: UpdateSchoolCourseDto,
|
||||
) {
|
||||
return this.schoolCourseService.update(id, req.user.tenantId, body);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
async remove(@Param('id', ParseIntPipe) id: number, @Request() req: any) {
|
||||
@ApiOperation({ summary: '删除校本课程包(教师端)' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: Number })
|
||||
remove(@Param('id', ParseIntPipe) id: number, @Request() req: any) {
|
||||
return this.schoolCourseService.delete(id, req.user.tenantId);
|
||||
}
|
||||
|
||||
// 课程管理
|
||||
@Get(':id/lessons')
|
||||
async findLessons(
|
||||
@ApiOperation({ summary: '获取校本课程包的课时列表(教师端)' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: Number })
|
||||
findLessons(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Request() req: any,
|
||||
) {
|
||||
@ -208,20 +356,52 @@ export class TeacherSchoolCourseController {
|
||||
}
|
||||
|
||||
@Put(':id/lessons/:lessonId')
|
||||
async updateLesson(
|
||||
@ApiOperation({ summary: '更新校本课程包的课时(教师端)' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: Number })
|
||||
@ApiParam({ name: 'lessonId', description: '课时 ID', type: Number })
|
||||
@ApiBody({ type: UpdateLessonDto })
|
||||
updateLesson(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Param('lessonId', ParseIntPipe) lessonId: number,
|
||||
@Request() req: any,
|
||||
@Body()
|
||||
body: {
|
||||
objectives?: string;
|
||||
preparation?: string;
|
||||
extension?: string;
|
||||
reflection?: string;
|
||||
changeNote?: string;
|
||||
stepsData?: string;
|
||||
},
|
||||
@Body() body: UpdateLessonDto,
|
||||
) {
|
||||
return this.schoolCourseService.updateLesson(id, lessonId, req.user.tenantId, body);
|
||||
}
|
||||
|
||||
// ==================== 完整数据API ====================
|
||||
|
||||
@Post('from-source')
|
||||
@ApiOperation({ summary: '从源课程创建校本课程包(教师端)' })
|
||||
@ApiBody({ type: CreateFromSourceDto })
|
||||
createFromSource(@Request() req: any, @Body() body: CreateFromSourceDto) {
|
||||
return this.schoolCourseService.createFromSource(
|
||||
req.user.tenantId,
|
||||
body.sourceCourseId,
|
||||
req.user.id,
|
||||
body.saveLocation || 'PERSONAL',
|
||||
);
|
||||
}
|
||||
|
||||
@Get(':id/full')
|
||||
@ApiOperation({ summary: '获取校本课程包完整数据(教师端)' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: Number })
|
||||
getFullDetail(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Request() req: any,
|
||||
) {
|
||||
return this.schoolCourseService.getFullDetail(id, req.user.tenantId);
|
||||
}
|
||||
|
||||
@Put(':id/full')
|
||||
@ApiOperation({ summary: '更新校本课程包完整数据(教师端)' })
|
||||
@ApiParam({ name: 'id', description: '课程包 ID', type: Number })
|
||||
@ApiBody({ type: Object, description: '完整课程包数据' })
|
||||
updateFull(
|
||||
@Param('id', ParseIntPipe) id: number,
|
||||
@Request() req: any,
|
||||
@Body() body: any,
|
||||
) {
|
||||
return this.schoolCourseService.updateFull(id, req.user.tenantId, body);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,83 @@ import { TeacherCourseService } from './teacher-course.service';
|
||||
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
|
||||
import { RolesGuard } from '../common/guards/roles.guard';
|
||||
import { Roles } from '../common/decorators/roles.decorator';
|
||||
import {
|
||||
ApiTags,
|
||||
ApiOperation,
|
||||
ApiParam,
|
||||
ApiQuery,
|
||||
ApiBody,
|
||||
ApiBearerAuth,
|
||||
ApiProperty,
|
||||
} from '@nestjs/swagger';
|
||||
|
||||
// ============= DTO 定义 =============
|
||||
|
||||
class TeacherCourseQueryDto {
|
||||
@ApiProperty({ description: '页码', example: 1, required: false })
|
||||
page?: number;
|
||||
|
||||
@ApiProperty({ description: '每页数量', example: 10, required: false })
|
||||
pageSize?: number;
|
||||
|
||||
@ApiProperty({ description: '年级筛选', example: '小班', required: false })
|
||||
grade?: string;
|
||||
|
||||
@ApiProperty({ description: '关键词搜索', required: false })
|
||||
keyword?: string;
|
||||
}
|
||||
|
||||
class StudentQueryDto {
|
||||
@ApiProperty({ description: '页码', example: 1, required: false })
|
||||
page?: number;
|
||||
|
||||
@ApiProperty({ description: '每页数量', example: 10, required: false })
|
||||
pageSize?: number;
|
||||
|
||||
@ApiProperty({ description: '关键词搜索', required: false })
|
||||
keyword?: string;
|
||||
}
|
||||
|
||||
class ScheduleQueryDto {
|
||||
@ApiProperty({ description: '开始日期', example: '2026-03-01', required: false })
|
||||
startDate?: string;
|
||||
|
||||
@ApiProperty({ description: '结束日期', example: '2026-03-31', required: false })
|
||||
endDate?: string;
|
||||
}
|
||||
|
||||
class CreateScheduleDto {
|
||||
@ApiProperty({ description: '课程ID' })
|
||||
courseId: number;
|
||||
|
||||
@ApiProperty({ description: '班级ID' })
|
||||
classId: number;
|
||||
|
||||
@ApiProperty({ description: '上课日期', example: '2026-03-15' })
|
||||
lessonDate: string;
|
||||
|
||||
@ApiProperty({ description: '上课时间', example: '09:00' })
|
||||
lessonTime: string;
|
||||
|
||||
@ApiProperty({ description: '备注', required: false })
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
class UpdateScheduleDto {
|
||||
@ApiProperty({ description: '上课日期', example: '2026-03-15', required: false })
|
||||
lessonDate?: string;
|
||||
|
||||
@ApiProperty({ description: '上课时间', example: '09:00', required: false })
|
||||
lessonTime?: string;
|
||||
|
||||
@ApiProperty({ description: '备注', required: false })
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
// ============= 教师端控制器 =============
|
||||
|
||||
@ApiTags('teacher')
|
||||
@ApiBearerAuth()
|
||||
@Controller('teacher')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles('teacher')
|
||||
@ -13,26 +89,32 @@ export class TeacherCourseController {
|
||||
// ==================== 首页仪表板 ====================
|
||||
|
||||
@Get('dashboard')
|
||||
@ApiOperation({ summary: '获取教师端首页数据' })
|
||||
getDashboard(@Request() req: any) {
|
||||
return this.teacherCourseService.getDashboard(req.user.userId, req.user.tenantId);
|
||||
}
|
||||
|
||||
@Get('dashboard/today')
|
||||
@ApiOperation({ summary: '获取今日授课安排' })
|
||||
getTodayLessons(@Request() req: any) {
|
||||
return this.teacherCourseService.getTodayLessons(req.user.userId, req.user.tenantId);
|
||||
}
|
||||
|
||||
@Get('dashboard/recommend')
|
||||
@ApiOperation({ summary: '获取推荐课程' })
|
||||
getRecommendedCourses(@Request() req: any) {
|
||||
return this.teacherCourseService.getRecommendedCourses(req.user.tenantId);
|
||||
}
|
||||
|
||||
@Get('dashboard/weekly')
|
||||
@ApiOperation({ summary: '获取本周统计数据' })
|
||||
getWeeklyStats(@Request() req: any) {
|
||||
return this.teacherCourseService.getWeeklyStats(req.user.userId);
|
||||
}
|
||||
|
||||
@Get('dashboard/lesson-trend')
|
||||
@ApiOperation({ summary: '获取授课趋势数据' })
|
||||
@ApiQuery({ name: 'months', description: '月份数', required: false, type: Number })
|
||||
getLessonTrend(@Request() req: any, @Query('months') months?: string) {
|
||||
return this.teacherCourseService.getTeacherLessonTrend(
|
||||
req.user.userId,
|
||||
@ -41,6 +123,7 @@ export class TeacherCourseController {
|
||||
}
|
||||
|
||||
@Get('dashboard/course-usage')
|
||||
@ApiOperation({ summary: '获取课程使用情况' })
|
||||
getCourseUsage(@Request() req: any) {
|
||||
return this.teacherCourseService.getTeacherCourseUsage(req.user.userId);
|
||||
}
|
||||
@ -48,16 +131,24 @@ export class TeacherCourseController {
|
||||
// ==================== 课程管理 ====================
|
||||
|
||||
@Get('courses')
|
||||
findAll(@Request() req: any, @Query() query: any) {
|
||||
@ApiOperation({ summary: '获取教师可用课程列表' })
|
||||
@ApiQuery({ name: 'page', required: false, type: Number })
|
||||
@ApiQuery({ name: 'pageSize', required: false, type: Number })
|
||||
@ApiQuery({ name: 'grade', required: false, type: String })
|
||||
@ApiQuery({ name: 'keyword', required: false, type: String })
|
||||
findAll(@Request() req: any, @Query() query: TeacherCourseQueryDto) {
|
||||
return this.teacherCourseService.findAll(req.user.userId, req.user.tenantId, query);
|
||||
}
|
||||
|
||||
@Get('courses/classes')
|
||||
@ApiOperation({ summary: '获取教师班级列表' })
|
||||
getClasses(@Request() req: any) {
|
||||
return this.teacherCourseService.getTeacherClasses(req.user.userId);
|
||||
}
|
||||
|
||||
@Get('courses/:id')
|
||||
@ApiOperation({ summary: '获取课程详情' })
|
||||
@ApiParam({ name: 'id', description: '课程 ID', type: String })
|
||||
findOne(@Request() req: any, @Param('id') id: string) {
|
||||
return this.teacherCourseService.findOne(+id, req.user.userId, req.user.tenantId);
|
||||
}
|
||||
@ -65,20 +156,30 @@ export class TeacherCourseController {
|
||||
// ==================== 班级学生管理 ====================
|
||||
|
||||
@Get('students')
|
||||
getAllStudents(@Request() req: any, @Query() query: any) {
|
||||
@ApiOperation({ summary: '获取教师所有学生列表' })
|
||||
@ApiQuery({ name: 'page', required: false, type: Number })
|
||||
@ApiQuery({ name: 'pageSize', required: false, type: Number })
|
||||
@ApiQuery({ name: 'keyword', required: false, type: String })
|
||||
getAllStudents(@Request() req: any, @Query() query: StudentQueryDto) {
|
||||
return this.teacherCourseService.getAllTeacherStudents(req.user.userId, query);
|
||||
}
|
||||
|
||||
@Get('classes/:id/students')
|
||||
@ApiOperation({ summary: '获取班级学生列表' })
|
||||
@ApiParam({ name: 'id', description: '班级 ID', type: String })
|
||||
@ApiQuery({ name: 'page', required: false, type: Number })
|
||||
@ApiQuery({ name: 'pageSize', required: false, type: Number })
|
||||
getClassStudents(
|
||||
@Request() req: any,
|
||||
@Param('id') id: string,
|
||||
@Query() query: any,
|
||||
@Query() query: StudentQueryDto,
|
||||
) {
|
||||
return this.teacherCourseService.getClassStudents(req.user.userId, +id, query);
|
||||
}
|
||||
|
||||
@Get('classes/:id/teachers')
|
||||
@ApiOperation({ summary: '获取班级教师列表' })
|
||||
@ApiParam({ name: 'id', description: '班级 ID', type: String })
|
||||
getClassTeachers(
|
||||
@Request() req: any,
|
||||
@Param('id') id: string,
|
||||
@ -89,11 +190,17 @@ export class TeacherCourseController {
|
||||
// ==================== 排课管理 ====================
|
||||
|
||||
@Get('schedules')
|
||||
getTeacherSchedules(@Request() req: any, @Query() query: any) {
|
||||
@ApiOperation({ summary: '获取教师排课列表' })
|
||||
@ApiQuery({ name: 'startDate', required: false, type: String })
|
||||
@ApiQuery({ name: 'endDate', required: false, type: String })
|
||||
getTeacherSchedules(@Request() req: any, @Query() query: ScheduleQueryDto) {
|
||||
return this.teacherCourseService.getTeacherSchedules(req.user.userId, query);
|
||||
}
|
||||
|
||||
@Get('schedules/timetable')
|
||||
@ApiOperation({ summary: '获取教师课程表' })
|
||||
@ApiQuery({ name: 'startDate', description: '开始日期', type: String })
|
||||
@ApiQuery({ name: 'endDate', description: '结束日期', type: String })
|
||||
getTeacherTimetable(
|
||||
@Request() req: any,
|
||||
@Query('startDate') startDate: string,
|
||||
@ -103,25 +210,33 @@ export class TeacherCourseController {
|
||||
}
|
||||
|
||||
@Get('schedules/today')
|
||||
@ApiOperation({ summary: '获取今日排课' })
|
||||
getTodaySchedules(@Request() req: any) {
|
||||
return this.teacherCourseService.getTodaySchedules(req.user.userId);
|
||||
}
|
||||
|
||||
@Post('schedules')
|
||||
createTeacherSchedule(@Request() req: any, @Body() dto: any) {
|
||||
@ApiOperation({ summary: '创建排课' })
|
||||
@ApiBody({ type: CreateScheduleDto })
|
||||
createTeacherSchedule(@Request() req: any, @Body() dto: CreateScheduleDto) {
|
||||
return this.teacherCourseService.createTeacherSchedule(req.user.userId, req.user.tenantId, dto);
|
||||
}
|
||||
|
||||
@Put('schedules/:id')
|
||||
@ApiOperation({ summary: '更新排课' })
|
||||
@ApiParam({ name: 'id', description: '排课 ID', type: String })
|
||||
@ApiBody({ type: UpdateScheduleDto })
|
||||
updateTeacherSchedule(
|
||||
@Request() req: any,
|
||||
@Param('id') id: string,
|
||||
@Body() dto: any,
|
||||
@Body() dto: UpdateScheduleDto,
|
||||
) {
|
||||
return this.teacherCourseService.updateTeacherSchedule(req.user.userId, +id, dto);
|
||||
}
|
||||
|
||||
@Delete('schedules/:id')
|
||||
@ApiOperation({ summary: '取消排课' })
|
||||
@ApiParam({ name: 'id', description: '排课 ID', type: String })
|
||||
cancelTeacherSchedule(@Request() req: any, @Param('id') id: string) {
|
||||
return this.teacherCourseService.cancelTeacherSchedule(req.user.userId, +id);
|
||||
}
|
||||
|
||||
@ -8,37 +8,77 @@ import {
|
||||
Min,
|
||||
Max,
|
||||
} from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
export class TenantQueryDto {
|
||||
@ApiProperty({
|
||||
description: '当前页码(从 1 开始)',
|
||||
example: 1,
|
||||
required: false,
|
||||
default: 1,
|
||||
})
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
page?: number = 1;
|
||||
|
||||
@ApiProperty({
|
||||
description: '每页数量',
|
||||
example: 10,
|
||||
required: false,
|
||||
default: 10,
|
||||
minimum: 1,
|
||||
maximum: 100,
|
||||
})
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Max(100)
|
||||
pageSize?: number = 10;
|
||||
|
||||
@ApiProperty({
|
||||
description: '搜索关键词(学校名称或登录账号)',
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
keyword?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '状态筛选',
|
||||
required: false,
|
||||
enum: ['ACTIVE', 'INACTIVE', 'SUSPENDED'],
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
status?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '套餐类型筛选',
|
||||
required: false,
|
||||
enum: ['STANDARD', 'PREMIUM', 'TRIAL'],
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
packageType?: string;
|
||||
}
|
||||
|
||||
export class CreateTenantDto {
|
||||
@ApiProperty({
|
||||
description: '学校名称',
|
||||
example: '阳光幼儿园',
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty({ message: '学校名称不能为空' })
|
||||
name: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '登录账号(必须以字母开头,4-20位字母、数字或下划线)',
|
||||
example: 'kindergarten123',
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty({ message: '登录账号不能为空' })
|
||||
@Matches(/^[a-zA-Z][a-zA-Z0-9_]{3,19}$/, {
|
||||
@ -46,6 +86,11 @@ export class CreateTenantDto {
|
||||
})
|
||||
loginAccount: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '初始密码(至少6位,需包含字母和数字)',
|
||||
example: 'abc123',
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@Matches(/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d@$!%*#?&]{6,20}$/, {
|
||||
@ -53,105 +98,234 @@ export class CreateTenantDto {
|
||||
})
|
||||
password?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '学校地址',
|
||||
example: '北京市朝阳区',
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
address?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '联系人姓名',
|
||||
example: '张老师',
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
contactPerson?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '联系人手机号',
|
||||
example: '13800138000',
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@Matches(/^1[3-9]\d{9}$/, { message: '请输入正确的手机号' })
|
||||
contactPhone?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '套餐类型',
|
||||
example: 'STANDARD',
|
||||
required: false,
|
||||
default: 'STANDARD',
|
||||
enum: ['STANDARD', 'PREMIUM', 'TRIAL'],
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
packageType?: string = 'STANDARD';
|
||||
|
||||
@ApiProperty({
|
||||
description: '教师配额',
|
||||
example: 20,
|
||||
required: false,
|
||||
default: 20,
|
||||
minimum: 1,
|
||||
})
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
teacherQuota?: number = 20;
|
||||
|
||||
@ApiProperty({
|
||||
description: '学生配额',
|
||||
example: 200,
|
||||
required: false,
|
||||
default: 200,
|
||||
minimum: 1,
|
||||
})
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
studentQuota?: number = 200;
|
||||
|
||||
@ApiProperty({
|
||||
description: '服务开始日期(YYYY-MM-DD)',
|
||||
example: '2026-01-01',
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
startDate?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '服务到期日期(YYYY-MM-DD)',
|
||||
example: '2026-12-31',
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
expireDate?: string;
|
||||
}
|
||||
|
||||
export class UpdateTenantDto {
|
||||
@ApiProperty({
|
||||
description: '学校名称',
|
||||
example: '阳光幼儿园',
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@IsNotEmpty({ message: '学校名称不能为空' })
|
||||
name?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '学校地址',
|
||||
example: '北京市朝阳区',
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
address?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '联系人姓名',
|
||||
example: '张老师',
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
contactPerson?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '联系人手机号',
|
||||
example: '13800138000',
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@Matches(/^1[3-9]\d{9}$/, { message: '请输入正确的手机号' })
|
||||
contactPhone?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '套餐类型',
|
||||
example: 'STANDARD',
|
||||
required: false,
|
||||
enum: ['STANDARD', 'PREMIUM', 'TRIAL'],
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
packageType?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '教师配额',
|
||||
example: 20,
|
||||
required: false,
|
||||
minimum: 1,
|
||||
})
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
teacherQuota?: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: '学生配额',
|
||||
example: 200,
|
||||
required: false,
|
||||
minimum: 1,
|
||||
})
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
studentQuota?: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: '服务开始日期(YYYY-MM-DD)',
|
||||
example: '2026-01-01',
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
startDate?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '服务到期日期(YYYY-MM-DD)',
|
||||
example: '2026-12-31',
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
expireDate?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '状态',
|
||||
example: 'ACTIVE',
|
||||
required: false,
|
||||
enum: ['ACTIVE', 'INACTIVE', 'SUSPENDED'],
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
status?: string;
|
||||
}
|
||||
|
||||
export class UpdateTenantQuotaDto {
|
||||
@ApiProperty({
|
||||
description: '套餐类型',
|
||||
example: 'PREMIUM',
|
||||
required: false,
|
||||
enum: ['STANDARD', 'PREMIUM', 'TRIAL'],
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
packageType?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '教师配额',
|
||||
example: 30,
|
||||
required: false,
|
||||
minimum: 1,
|
||||
})
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
teacherQuota?: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: '学生配额',
|
||||
example: 300,
|
||||
required: false,
|
||||
minimum: 1,
|
||||
})
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
studentQuota?: number;
|
||||
}
|
||||
|
||||
export class UpdateTenantStatusDto {
|
||||
@ApiProperty({
|
||||
description: '状态',
|
||||
example: 'ACTIVE',
|
||||
enum: ['ACTIVE', 'INACTIVE', 'SUSPENDED'],
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty({ message: '状态不能为空' })
|
||||
status: string;
|
||||
|
||||
@ -20,7 +20,16 @@ import {
|
||||
UpdateTenantQuotaDto,
|
||||
UpdateTenantStatusDto,
|
||||
} from './dto/tenant.dto';
|
||||
import {
|
||||
ApiTags,
|
||||
ApiOperation,
|
||||
ApiResponse,
|
||||
ApiBearerAuth,
|
||||
} from '@nestjs/swagger';
|
||||
import { ResultDto } from '../../common/dto/result.dto';
|
||||
|
||||
@ApiTags('admin/tenants')
|
||||
@ApiBearerAuth()
|
||||
@Controller('admin/tenants')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles('admin')
|
||||
@ -28,46 +37,100 @@ export class TenantController {
|
||||
constructor(private readonly tenantService: TenantService) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: '获取租户列表' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: '获取成功',
|
||||
type: ResultDto,
|
||||
})
|
||||
findAll(@Query() query: TenantQueryDto) {
|
||||
return this.tenantService.findAllPaginated(query);
|
||||
}
|
||||
|
||||
@Get('stats')
|
||||
@ApiOperation({ summary: '获取租户统计信息' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: '获取成功',
|
||||
type: ResultDto,
|
||||
})
|
||||
getStats() {
|
||||
return this.tenantService.getStats();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@ApiOperation({ summary: '获取租户详情' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: '获取成功',
|
||||
type: ResultDto,
|
||||
})
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.tenantService.findOne(+id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: '创建租户' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: '创建成功',
|
||||
type: ResultDto,
|
||||
})
|
||||
create(@Body() createTenantDto: CreateTenantDto) {
|
||||
return this.tenantService.create(createTenantDto);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@ApiOperation({ summary: '更新租户信息' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: '更新成功',
|
||||
type: ResultDto,
|
||||
})
|
||||
update(@Param('id') id: string, @Body() updateTenantDto: UpdateTenantDto) {
|
||||
return this.tenantService.update(+id, updateTenantDto);
|
||||
}
|
||||
|
||||
@Put(':id/quota')
|
||||
@ApiOperation({ summary: '更新租户配额' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: '更新成功',
|
||||
type: ResultDto,
|
||||
})
|
||||
updateQuota(@Param('id') id: string, @Body() dto: UpdateTenantQuotaDto) {
|
||||
return this.tenantService.updateQuota(+id, dto);
|
||||
}
|
||||
|
||||
@Put(':id/status')
|
||||
@ApiOperation({ summary: '更新租户状态' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: '更新成功',
|
||||
type: ResultDto,
|
||||
})
|
||||
updateStatus(@Param('id') id: string, @Body() dto: UpdateTenantStatusDto) {
|
||||
return this.tenantService.updateStatus(+id, dto);
|
||||
}
|
||||
|
||||
@Post(':id/reset-password')
|
||||
@ApiOperation({ summary: '重置租户密码' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: '重置成功',
|
||||
type: ResultDto,
|
||||
})
|
||||
resetPassword(@Param('id') id: string) {
|
||||
return this.tenantService.resetPassword(+id);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@ApiOperation({ summary: '删除租户' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: '删除成功',
|
||||
type: ResultDto,
|
||||
})
|
||||
remove(@Param('id') id: string) {
|
||||
return this.tenantService.remove(+id);
|
||||
}
|
||||
|
||||
31
reading-platform-frontend/orval.config.ts
Normal file
31
reading-platform-frontend/orval.config.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { defineConfig } from 'orval';
|
||||
|
||||
export default defineConfig({
|
||||
readingPlatform: {
|
||||
output: {
|
||||
mode: 'split',
|
||||
target: 'src/api/generated/index.ts',
|
||||
schemas: 'src/api/generated/model',
|
||||
client: 'axios',
|
||||
override: {
|
||||
mutator: {
|
||||
path: 'src/api/generated/mutator.ts',
|
||||
name: 'customMutator',
|
||||
},
|
||||
// 自定义类型名称
|
||||
name: (type) => {
|
||||
// 移除命名空间前缀,简化类型名称
|
||||
return type.replace(/^(Result|ResultPageResult)/, '');
|
||||
},
|
||||
},
|
||||
// 导入优化
|
||||
imports: {
|
||||
axios: true,
|
||||
},
|
||||
},
|
||||
input: {
|
||||
// 从后端 Swagger 文档生成
|
||||
target: 'http://localhost:3000/api-docs-json',
|
||||
},
|
||||
},
|
||||
});
|
||||
155
reading-platform-frontend/src/api/client.ts
Normal file
155
reading-platform-frontend/src/api/client.ts
Normal file
@ -0,0 +1,155 @@
|
||||
/**
|
||||
* API 客户端统一入口
|
||||
*
|
||||
* 导出 Orval 生成的 API 客户端实例和类型工具
|
||||
* 提供统一的类型定义和响应处理
|
||||
*/
|
||||
|
||||
// 导入 Orval 生成的 API 客户端工厂函数
|
||||
import { getApi } from './generated/index';
|
||||
|
||||
// 导出所有生成的类型
|
||||
export * from './generated/model';
|
||||
|
||||
/**
|
||||
* API 客户端实例
|
||||
*
|
||||
* 使用方式:
|
||||
* ```ts
|
||||
* import { api } from '@/api/client';
|
||||
*
|
||||
* // 调用接口
|
||||
* const result = await api.teacherCourseControllerFindAll({ page: 1 });
|
||||
* ```
|
||||
*/
|
||||
export const api = getApi();
|
||||
|
||||
// 同时导出 getApi 以便需要时创建新实例
|
||||
export { getApi };
|
||||
|
||||
// ============== 类型工具 ==============
|
||||
|
||||
/**
|
||||
* 统一响应格式
|
||||
*
|
||||
* 注意:后端返回的数据可能不遵循此格式,实际类型取决于具体的 API
|
||||
*/
|
||||
export interface ApiResult<T> {
|
||||
code: number;
|
||||
message: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页数据格式
|
||||
*/
|
||||
export interface PageData<T> {
|
||||
items: T[];
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
totalPages?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解包 Result<T> 类型,获取 data 字段的类型
|
||||
*/
|
||||
export type UnwrapResult<T> = T extends ApiResult<infer U> ? U : T;
|
||||
|
||||
/**
|
||||
* 提取分页响应的数据类型
|
||||
*/
|
||||
export type PageResultOf<T> = ApiResult<PageData<T>>;
|
||||
|
||||
// ============== 错误处理 ==============
|
||||
|
||||
/**
|
||||
* API 错误类
|
||||
*/
|
||||
export class ApiError extends Error {
|
||||
constructor(
|
||||
public code: number,
|
||||
message: string,
|
||||
public data?: any,
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'ApiError';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查响应是否成功
|
||||
*
|
||||
* @param result - API 响应结果
|
||||
* @returns 是否成功
|
||||
*/
|
||||
export function isSuccess<T>(result: ApiResult<T>): boolean {
|
||||
return result.code === 200;
|
||||
}
|
||||
|
||||
/**
|
||||
* 抛出 API 错误(如果响应不成功)
|
||||
*
|
||||
* @param result - API 响应结果
|
||||
* @throws ApiError - 如果响应不成功
|
||||
*/
|
||||
export function throwErrorIfFailed<T>(result: ApiResult<T>): asserts result is ApiResult<T> & { data: T } {
|
||||
if (!isSuccess(result)) {
|
||||
throw new ApiError(result.code, result.message, result.data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从响应中提取 data 字段
|
||||
*
|
||||
* @param result - API 响应结果
|
||||
* @returns 解包后的数据
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const result = await api.teacherCourseControllerFindAll();
|
||||
* const data = unwrapData(result); // 类型推断为实际返回的数据类型
|
||||
* ```
|
||||
*/
|
||||
export function unwrapData<T>(result: any): T {
|
||||
// 尝试直接返回(如果响应已经是数据)
|
||||
if (result && typeof result === 'object') {
|
||||
// 如果有 code 字段,说明是包装的响应
|
||||
if ('code' in result && typeof result.code === 'number') {
|
||||
if (result.code === 200 || result.code === 0) {
|
||||
return result.data as T;
|
||||
}
|
||||
throw new ApiError(result.code, (result.message as string) || 'Request failed', result.data);
|
||||
}
|
||||
// 否则直接返回
|
||||
return result as T;
|
||||
}
|
||||
return result as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从分页响应中提取分页数据
|
||||
*
|
||||
* @param result - API 分页响应结果
|
||||
* @returns 分页数据
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const result = await api.teacherCourseControllerFindAll();
|
||||
* const pageData = unwrapPageData(result); // 类型为 PageData<T>
|
||||
* ```
|
||||
*/
|
||||
export function unwrapPageData<T>(result: any): PageData<T> {
|
||||
const data = unwrapData<any>(result);
|
||||
// 如果已经是分页格式
|
||||
if (data && typeof data === 'object' && 'items' in data && 'total' in data) {
|
||||
return data as PageData<T>;
|
||||
}
|
||||
// 否则包装为分页格式
|
||||
return {
|
||||
items: Array.isArray(data) ? data : [],
|
||||
total: Array.isArray(data) ? data.length : 0,
|
||||
page: 1,
|
||||
pageSize: Array.isArray(data) ? data.length : 0,
|
||||
};
|
||||
}
|
||||
@ -1,4 +1,10 @@
|
||||
import { http } from './index';
|
||||
import { getApi } from './generated';
|
||||
import type {
|
||||
CourseControllerFindAllParams,
|
||||
CourseControllerGetReviewListParams,
|
||||
} from './generated/model';
|
||||
|
||||
// ============= 类型定义(保持向后兼容) =============
|
||||
|
||||
export interface CourseQueryParams {
|
||||
page?: number;
|
||||
@ -101,6 +107,20 @@ export interface ValidationWarning {
|
||||
code: string;
|
||||
}
|
||||
|
||||
// ============= API 函数(使用生成的客户端) =============
|
||||
|
||||
// 获取 API 客户端实例
|
||||
const api = getApi();
|
||||
|
||||
// 转换查询参数类型
|
||||
const toFindAllParams = (params: CourseQueryParams): CourseControllerFindAllParams => ({
|
||||
page: params.page,
|
||||
pageSize: params.pageSize,
|
||||
grade: params.grade,
|
||||
status: params.status,
|
||||
keyword: params.keyword,
|
||||
});
|
||||
|
||||
// 获取课程包列表
|
||||
export function getCourses(params: CourseQueryParams): Promise<{
|
||||
items: Course[];
|
||||
@ -108,7 +128,7 @@ export function getCourses(params: CourseQueryParams): Promise<{
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}> {
|
||||
return http.get('/courses', { params });
|
||||
return api.courseControllerFindAll(toFindAllParams(params)) as any;
|
||||
}
|
||||
|
||||
// 获取审核列表
|
||||
@ -118,84 +138,87 @@ export function getReviewList(params: CourseQueryParams): Promise<{
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}> {
|
||||
return http.get('/courses/review', { params });
|
||||
const findAllParams = toFindAllParams(params) as CourseControllerGetReviewListParams;
|
||||
return api.courseControllerGetReviewList(findAllParams) as any;
|
||||
}
|
||||
|
||||
// 获取课程包详情
|
||||
export function getCourse(id: number): Promise<any> {
|
||||
return http.get(`/courses/${id}`);
|
||||
return api.courseControllerFindOne(String(id)) as any;
|
||||
}
|
||||
|
||||
// 创建课程包
|
||||
export function createCourse(data: any): Promise<any> {
|
||||
return http.post('/courses', data);
|
||||
return api.courseControllerCreate(data) as any;
|
||||
}
|
||||
|
||||
// 更新课程包
|
||||
export function updateCourse(id: number, data: any): Promise<any> {
|
||||
return http.put(`/courses/${id}`, data);
|
||||
return api.courseControllerUpdate(String(id), data) as any;
|
||||
}
|
||||
|
||||
// 删除课程包
|
||||
export function deleteCourse(id: number): Promise<any> {
|
||||
return http.delete(`/courses/${id}`);
|
||||
return api.courseControllerRemove(String(id)) as any;
|
||||
}
|
||||
|
||||
// 验证课程完整性
|
||||
export function validateCourse(id: number): Promise<ValidationResult> {
|
||||
return http.get(`/courses/${id}/validate`);
|
||||
return api.courseControllerValidate(String(id)) as any;
|
||||
}
|
||||
|
||||
// 提交审核
|
||||
export function submitCourse(id: number, copyrightConfirmed: boolean): Promise<any> {
|
||||
return http.post(`/courses/${id}/submit`, { copyrightConfirmed });
|
||||
return api.courseControllerSubmit(String(id), { copyrightConfirmed }) as any;
|
||||
}
|
||||
|
||||
// 撤销审核
|
||||
export function withdrawCourse(id: number): Promise<any> {
|
||||
return http.post(`/courses/${id}/withdraw`);
|
||||
return api.courseControllerWithdraw(String(id)) as any;
|
||||
}
|
||||
|
||||
// 审核通过
|
||||
export function approveCourse(id: number, data: { checklist?: any; comment?: string }): Promise<any> {
|
||||
return http.post(`/courses/${id}/approve`, data);
|
||||
return api.courseControllerApprove(String(id), data) as any;
|
||||
}
|
||||
|
||||
// 审核驳回
|
||||
export function rejectCourse(id: number, data: { checklist?: any; comment: string }): Promise<any> {
|
||||
return http.post(`/courses/${id}/reject`, data);
|
||||
return api.courseControllerReject(String(id), data) as any;
|
||||
}
|
||||
|
||||
// 直接发布(超级管理员)
|
||||
export function directPublishCourse(id: number, skipValidation?: boolean): Promise<any> {
|
||||
return http.post(`/courses/${id}/direct-publish`, { skipValidation });
|
||||
return api.courseControllerDirectPublish(String(id), { skipValidation }) as any;
|
||||
}
|
||||
|
||||
// 发布课程包(兼容旧API)
|
||||
// 发布课程包
|
||||
export function publishCourse(id: number): Promise<any> {
|
||||
return http.post(`/courses/${id}/publish`);
|
||||
return api.courseControllerPublish(String(id)) as any;
|
||||
}
|
||||
|
||||
// 下架课程包
|
||||
export function unpublishCourse(id: number): Promise<any> {
|
||||
return http.post(`/courses/${id}/unpublish`);
|
||||
return api.courseControllerUnpublish(String(id)) as any;
|
||||
}
|
||||
|
||||
// 重新发布
|
||||
export function republishCourse(id: number): Promise<any> {
|
||||
return http.post(`/courses/${id}/republish`);
|
||||
return api.courseControllerRepublish(String(id)) as any;
|
||||
}
|
||||
|
||||
// 获取课程包统计数据
|
||||
export function getCourseStats(id: number): Promise<any> {
|
||||
return http.get(`/courses/${id}/stats`);
|
||||
return api.courseControllerGetStats(String(id)) as any;
|
||||
}
|
||||
|
||||
// 获取版本历史
|
||||
export function getCourseVersions(id: number): Promise<any[]> {
|
||||
return http.get(`/courses/${id}/versions`);
|
||||
return api.courseControllerGetVersionHistory(String(id)) as any;
|
||||
}
|
||||
|
||||
// ============= 常量 =============
|
||||
|
||||
// 课程状态映射
|
||||
export const COURSE_STATUS_MAP: Record<string, { label: string; color: string }> = {
|
||||
DRAFT: { label: '草稿', color: 'default' },
|
||||
|
||||
3585
reading-platform-frontend/src/api/generated/index.ts
Normal file
3585
reading-platform-frontend/src/api/generated/index.ts
Normal file
File diff suppressed because one or more lines are too long
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface AddClassTeacherDto { [key: string]: unknown }
|
||||
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export type AdminStatsControllerGetActiveTenantsParams = {
|
||||
limit: string;
|
||||
};
|
||||
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export type AdminStatsControllerGetPopularCoursesParams = {
|
||||
limit: string;
|
||||
};
|
||||
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export type AdminStatsControllerGetRecentActivitiesParams = {
|
||||
limit: string;
|
||||
};
|
||||
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
import type { ApproveCourseDtoChecklist } from './approveCourseDtoChecklist';
|
||||
|
||||
export interface ApproveCourseDto {
|
||||
/** 审核检查项 */
|
||||
checklist?: ApproveCourseDtoChecklist;
|
||||
/** 审核意见 */
|
||||
comment?: string;
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* 审核检查项
|
||||
*/
|
||||
export type ApproveCourseDtoChecklist = { [key: string]: unknown };
|
||||
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
import type { BatchStudentRecordsDtoRecordsItem } from './batchStudentRecordsDtoRecordsItem';
|
||||
|
||||
export interface BatchStudentRecordsDto {
|
||||
/** 学生记录列表 */
|
||||
records: BatchStudentRecordsDtoRecordsItem[];
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export type BatchStudentRecordsDtoRecordsItem = { [key: string]: unknown };
|
||||
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export type CourseControllerFindAllParams = {
|
||||
/**
|
||||
* 页码
|
||||
*/
|
||||
page?: number;
|
||||
/**
|
||||
* 每页数量
|
||||
*/
|
||||
pageSize?: number;
|
||||
/**
|
||||
* 年级筛选
|
||||
*/
|
||||
grade?: string;
|
||||
/**
|
||||
* 状态筛选
|
||||
*/
|
||||
status?: string;
|
||||
/**
|
||||
* 关键词搜索
|
||||
*/
|
||||
keyword?: string;
|
||||
};
|
||||
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export type CourseControllerGetReviewListParams = {
|
||||
/**
|
||||
* 页码
|
||||
*/
|
||||
page?: number;
|
||||
/**
|
||||
* 每页数量
|
||||
*/
|
||||
pageSize?: number;
|
||||
/**
|
||||
* 年级筛选
|
||||
*/
|
||||
grade?: string;
|
||||
/**
|
||||
* 状态筛选
|
||||
*/
|
||||
status?: string;
|
||||
/**
|
||||
* 关键词搜索
|
||||
*/
|
||||
keyword?: string;
|
||||
};
|
||||
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export type CoursePackageControllerFindAllParams = {
|
||||
status: string;
|
||||
page: string;
|
||||
pageSize: string;
|
||||
};
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface CreateClassDto { [key: string]: unknown }
|
||||
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
import type { CreateFromSourceDtoSaveLocation } from './createFromSourceDtoSaveLocation';
|
||||
|
||||
export interface CreateFromSourceDto {
|
||||
/** 源课程ID */
|
||||
sourceCourseId: number;
|
||||
/** 保存位置 */
|
||||
saveLocation: CreateFromSourceDtoSaveLocation;
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* 保存位置
|
||||
*/
|
||||
export type CreateFromSourceDtoSaveLocation = typeof CreateFromSourceDtoSaveLocation[keyof typeof CreateFromSourceDtoSaveLocation];
|
||||
|
||||
|
||||
export const CreateFromSourceDtoSaveLocation = {
|
||||
PERSONAL: 'PERSONAL',
|
||||
SCHOOL: 'SCHOOL',
|
||||
} as const;
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface CreateFromTemplateDto { [key: string]: unknown }
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface CreateGrowthRecordDto { [key: string]: unknown }
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface CreateLessonDto { [key: string]: unknown }
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface CreateLibraryDto { [key: string]: unknown }
|
||||
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface CreateReservationDto {
|
||||
/** 教师ID */
|
||||
teacherId: number;
|
||||
/** 班级ID */
|
||||
classId: number;
|
||||
/** 预约日期 */
|
||||
scheduledDate: string;
|
||||
/** 预约时间 */
|
||||
scheduledTime?: string;
|
||||
/** 备注 */
|
||||
note?: string;
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface CreateResourceItemDto { [key: string]: unknown }
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface CreateScheduleDto { [key: string]: unknown }
|
||||
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface CreateSchoolCourseDto {
|
||||
/** 源课程ID */
|
||||
sourceCourseId: number;
|
||||
/** 课程名称 */
|
||||
name: string;
|
||||
/** 课程描述 */
|
||||
description?: string;
|
||||
/** 修改说明 */
|
||||
changesSummary?: string;
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface CreateStudentDto { [key: string]: unknown }
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface CreateTaskDto { [key: string]: unknown }
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface CreateTaskTemplateDto { [key: string]: unknown }
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface CreateTeacherDto { [key: string]: unknown }
|
||||
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
import type { CreateTenantDtoPackageType } from './createTenantDtoPackageType';
|
||||
|
||||
export interface CreateTenantDto {
|
||||
/** 学校名称 */
|
||||
name: string;
|
||||
/** 登录账号(必须以字母开头,4-20位字母、数字或下划线) */
|
||||
loginAccount: string;
|
||||
/** 初始密码(至少6位,需包含字母和数字) */
|
||||
password?: string;
|
||||
/** 学校地址 */
|
||||
address?: string;
|
||||
/** 联系人姓名 */
|
||||
contactPerson?: string;
|
||||
/** 联系人手机号 */
|
||||
contactPhone?: string;
|
||||
/** 套餐类型 */
|
||||
packageType?: CreateTenantDtoPackageType;
|
||||
/**
|
||||
* 教师配额
|
||||
* @minimum 1
|
||||
*/
|
||||
teacherQuota?: number;
|
||||
/**
|
||||
* 学生配额
|
||||
* @minimum 1
|
||||
*/
|
||||
studentQuota?: number;
|
||||
/** 服务开始日期(YYYY-MM-DD) */
|
||||
startDate?: string;
|
||||
/** 服务到期日期(YYYY-MM-DD) */
|
||||
expireDate?: string;
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* 套餐类型
|
||||
*/
|
||||
export type CreateTenantDtoPackageType = typeof CreateTenantDtoPackageType[keyof typeof CreateTenantDtoPackageType];
|
||||
|
||||
|
||||
export const CreateTenantDtoPackageType = {
|
||||
STANDARD: 'STANDARD',
|
||||
PREMIUM: 'PREMIUM',
|
||||
TRIAL: 'TRIAL',
|
||||
} as const;
|
||||
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface DirectPublishDto {
|
||||
/** 是否跳过验证 */
|
||||
skipValidation?: boolean;
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export type ExportControllerExportGrowthRecordsParams = {
|
||||
studentId: string;
|
||||
};
|
||||
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export type ExportControllerExportStudentStatsParams = {
|
||||
classId: string;
|
||||
};
|
||||
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export type ExportControllerExportTeacherStatsParams = {
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
};
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface FinishLessonDto { [key: string]: unknown }
|
||||
94
reading-platform-frontend/src/api/generated/model/index.ts
Normal file
94
reading-platform-frontend/src/api/generated/model/index.ts
Normal file
@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export * from './addClassTeacherDto';
|
||||
export * from './adminStatsControllerGetActiveTenantsParams';
|
||||
export * from './adminStatsControllerGetPopularCoursesParams';
|
||||
export * from './adminStatsControllerGetRecentActivitiesParams';
|
||||
export * from './approveCourseDto';
|
||||
export * from './approveCourseDtoChecklist';
|
||||
export * from './batchStudentRecordsDto';
|
||||
export * from './batchStudentRecordsDtoRecordsItem';
|
||||
export * from './courseControllerFindAllParams';
|
||||
export * from './courseControllerGetReviewListParams';
|
||||
export * from './coursePackageControllerFindAllParams';
|
||||
export * from './createClassDto';
|
||||
export * from './createFromSourceDto';
|
||||
export * from './createFromSourceDtoSaveLocation';
|
||||
export * from './createFromTemplateDto';
|
||||
export * from './createGrowthRecordDto';
|
||||
export * from './createLessonDto';
|
||||
export * from './createLibraryDto';
|
||||
export * from './createReservationDto';
|
||||
export * from './createResourceItemDto';
|
||||
export * from './createScheduleDto';
|
||||
export * from './createSchoolCourseDto';
|
||||
export * from './createStudentDto';
|
||||
export * from './createTaskDto';
|
||||
export * from './createTaskTemplateDto';
|
||||
export * from './createTeacherDto';
|
||||
export * from './createTenantDto';
|
||||
export * from './createTenantDtoPackageType';
|
||||
export * from './directPublishDto';
|
||||
export * from './exportControllerExportGrowthRecordsParams';
|
||||
export * from './exportControllerExportStudentStatsParams';
|
||||
export * from './exportControllerExportTeacherStatsParams';
|
||||
export * from './finishLessonDto';
|
||||
export * from './lessonControllerFindAllParams';
|
||||
export * from './lessonFeedbackDto';
|
||||
export * from './lessonFeedbackDtoActivitiesDone';
|
||||
export * from './lessonFeedbackDtoStepFeedbacks';
|
||||
export * from './lessonProgressDto';
|
||||
export * from './lessonProgressDtoProgressData';
|
||||
export * from './loginDto';
|
||||
export * from './object';
|
||||
export * from './rejectCourseDto';
|
||||
export * from './rejectCourseDtoChecklist';
|
||||
export * from './resultDto';
|
||||
export * from './resultDtoData';
|
||||
export * from './reviewDto';
|
||||
export * from './schoolControllerImportStudentsParams';
|
||||
export * from './schoolFeedbackControllerFindAllParams';
|
||||
export * from './schoolTaskControllerGetMonthlyStatsParams';
|
||||
export * from './statsControllerGetActiveTeachersParams';
|
||||
export * from './statsControllerGetLessonTrendParams';
|
||||
export * from './statsControllerGetRecentActivitiesParams';
|
||||
export * from './studentRecordDto';
|
||||
export * from './submitCourseDto';
|
||||
export * from './teacherCourseControllerFindAllParams';
|
||||
export * from './teacherCourseControllerGetAllStudentsParams';
|
||||
export * from './teacherCourseControllerGetClassStudentsParams';
|
||||
export * from './teacherCourseControllerGetLessonTrendParams';
|
||||
export * from './teacherCourseControllerGetTeacherSchedulesParams';
|
||||
export * from './teacherCourseControllerGetTeacherTimetableParams';
|
||||
export * from './teacherFeedbackControllerFindAllParams';
|
||||
export * from './teacherTaskControllerGetMonthlyStatsParams';
|
||||
export * from './tenantControllerFindAllPackageType';
|
||||
export * from './tenantControllerFindAllParams';
|
||||
export * from './tenantControllerFindAllStatus';
|
||||
export * from './transferStudentDto';
|
||||
export * from './updateClassDto';
|
||||
export * from './updateClassTeacherDto';
|
||||
export * from './updateCompletionDto';
|
||||
export * from './updateGrowthRecordDto';
|
||||
export * from './updateLessonDto';
|
||||
export * from './updateLibraryDto';
|
||||
export * from './updateResourceItemDto';
|
||||
export * from './updateScheduleDto';
|
||||
export * from './updateSchoolCourseDto';
|
||||
export * from './updateStudentDto';
|
||||
export * from './updateTaskDto';
|
||||
export * from './updateTaskTemplateDto';
|
||||
export * from './updateTeacherDto';
|
||||
export * from './updateTenantDto';
|
||||
export * from './updateTenantDtoPackageType';
|
||||
export * from './updateTenantDtoStatus';
|
||||
export * from './updateTenantQuotaDto';
|
||||
export * from './updateTenantQuotaDtoPackageType';
|
||||
export * from './updateTenantStatusDto';
|
||||
export * from './updateTenantStatusDtoStatus';
|
||||
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export type LessonControllerFindAllParams = {
|
||||
/**
|
||||
* 页码
|
||||
*/
|
||||
page?: number;
|
||||
/**
|
||||
* 每页数量
|
||||
*/
|
||||
pageSize?: number;
|
||||
/**
|
||||
* 课程ID
|
||||
*/
|
||||
courseId?: number;
|
||||
/**
|
||||
* 状态筛选
|
||||
*/
|
||||
status?: string;
|
||||
};
|
||||
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
import type { LessonFeedbackDtoActivitiesDone } from './lessonFeedbackDtoActivitiesDone';
|
||||
import type { LessonFeedbackDtoStepFeedbacks } from './lessonFeedbackDtoStepFeedbacks';
|
||||
|
||||
export interface LessonFeedbackDto {
|
||||
/** 设计质量评分 */
|
||||
designQuality?: number;
|
||||
/** 参与度评分 */
|
||||
participation?: number;
|
||||
/** 目标达成度评分 */
|
||||
goalAchievement?: number;
|
||||
/** 环节反馈 */
|
||||
stepFeedbacks?: LessonFeedbackDtoStepFeedbacks;
|
||||
/** 优点 */
|
||||
pros?: string;
|
||||
/** 建议 */
|
||||
suggestions?: string;
|
||||
/** 完成的活动 */
|
||||
activitiesDone?: LessonFeedbackDtoActivitiesDone;
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* 完成的活动
|
||||
*/
|
||||
export type LessonFeedbackDtoActivitiesDone = { [key: string]: unknown };
|
||||
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* 环节反馈
|
||||
*/
|
||||
export type LessonFeedbackDtoStepFeedbacks = { [key: string]: unknown };
|
||||
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
import type { LessonProgressDtoProgressData } from './lessonProgressDtoProgressData';
|
||||
|
||||
export interface LessonProgressDto {
|
||||
/** 课程ID列表 */
|
||||
lessonIds?: string[];
|
||||
/** 已完成课程ID列表 */
|
||||
completedLessonIds?: string[];
|
||||
/** 当前课程ID */
|
||||
currentLessonId?: number;
|
||||
/** 当前环节ID */
|
||||
currentStepId?: number;
|
||||
/** 进度数据 */
|
||||
progressData?: LessonProgressDtoProgressData;
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* 进度数据
|
||||
*/
|
||||
export type LessonProgressDtoProgressData = { [key: string]: unknown };
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface LoginDto { [key: string]: unknown }
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface Object { [key: string]: unknown }
|
||||
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
import type { RejectCourseDtoChecklist } from './rejectCourseDtoChecklist';
|
||||
|
||||
export interface RejectCourseDto {
|
||||
/** 审核检查项 */
|
||||
checklist?: RejectCourseDtoChecklist;
|
||||
/** 驳回原因 */
|
||||
comment: string;
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* 审核检查项
|
||||
*/
|
||||
export type RejectCourseDtoChecklist = { [key: string]: unknown };
|
||||
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
import type { ResultDtoData } from './resultDtoData';
|
||||
|
||||
export interface ResultDto {
|
||||
/** 响应状态码 */
|
||||
code: number;
|
||||
/** 响应消息 */
|
||||
message: string;
|
||||
/** 响应数据 */
|
||||
data: ResultDtoData;
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* 响应数据
|
||||
*/
|
||||
export type ResultDtoData = { [key: string]: unknown };
|
||||
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface ReviewDto {
|
||||
/** 审核意见 */
|
||||
comment?: string;
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export type SchoolControllerImportStudentsParams = {
|
||||
defaultClassId: string;
|
||||
};
|
||||
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export type SchoolFeedbackControllerFindAllParams = {
|
||||
/**
|
||||
* 页码
|
||||
*/
|
||||
page?: number;
|
||||
/**
|
||||
* 每页数量
|
||||
*/
|
||||
pageSize?: number;
|
||||
/**
|
||||
* 课程ID
|
||||
*/
|
||||
courseId?: number;
|
||||
/**
|
||||
* 状态筛选
|
||||
*/
|
||||
status?: string;
|
||||
};
|
||||
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export type SchoolTaskControllerGetMonthlyStatsParams = {
|
||||
months: string;
|
||||
};
|
||||
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export type StatsControllerGetActiveTeachersParams = {
|
||||
limit: string;
|
||||
};
|
||||
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export type StatsControllerGetLessonTrendParams = {
|
||||
months: string;
|
||||
};
|
||||
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export type StatsControllerGetRecentActivitiesParams = {
|
||||
limit: string;
|
||||
};
|
||||
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface StudentRecordDto {
|
||||
/** 专注度 */
|
||||
focus?: number;
|
||||
/** 参与度 */
|
||||
participation?: number;
|
||||
/** 兴趣度 */
|
||||
interest?: number;
|
||||
/** 理解度 */
|
||||
understanding?: number;
|
||||
/** 备注 */
|
||||
notes?: string;
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface SubmitCourseDto {
|
||||
/** 是否确认版权 */
|
||||
copyrightConfirmed: boolean;
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export type TeacherCourseControllerFindAllParams = {
|
||||
/**
|
||||
* 页码
|
||||
*/
|
||||
page?: number;
|
||||
/**
|
||||
* 每页数量
|
||||
*/
|
||||
pageSize?: number;
|
||||
/**
|
||||
* 年级筛选
|
||||
*/
|
||||
grade?: string;
|
||||
/**
|
||||
* 关键词搜索
|
||||
*/
|
||||
keyword?: string;
|
||||
};
|
||||
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export type TeacherCourseControllerGetAllStudentsParams = {
|
||||
/**
|
||||
* 页码
|
||||
*/
|
||||
page?: number;
|
||||
/**
|
||||
* 每页数量
|
||||
*/
|
||||
pageSize?: number;
|
||||
/**
|
||||
* 关键词搜索
|
||||
*/
|
||||
keyword?: string;
|
||||
};
|
||||
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export type TeacherCourseControllerGetClassStudentsParams = {
|
||||
/**
|
||||
* 页码
|
||||
*/
|
||||
page?: number;
|
||||
/**
|
||||
* 每页数量
|
||||
*/
|
||||
pageSize?: number;
|
||||
/**
|
||||
* 关键词搜索
|
||||
*/
|
||||
keyword?: string;
|
||||
};
|
||||
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export type TeacherCourseControllerGetLessonTrendParams = {
|
||||
/**
|
||||
* 月份数
|
||||
*/
|
||||
months?: number;
|
||||
};
|
||||
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export type TeacherCourseControllerGetTeacherSchedulesParams = {
|
||||
/**
|
||||
* 开始日期
|
||||
*/
|
||||
startDate?: string;
|
||||
/**
|
||||
* 结束日期
|
||||
*/
|
||||
endDate?: string;
|
||||
};
|
||||
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export type TeacherCourseControllerGetTeacherTimetableParams = {
|
||||
/**
|
||||
* 开始日期
|
||||
*/
|
||||
startDate: string;
|
||||
/**
|
||||
* 结束日期
|
||||
*/
|
||||
endDate: string;
|
||||
};
|
||||
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export type TeacherFeedbackControllerFindAllParams = {
|
||||
/**
|
||||
* 页码
|
||||
*/
|
||||
page?: number;
|
||||
/**
|
||||
* 每页数量
|
||||
*/
|
||||
pageSize?: number;
|
||||
/**
|
||||
* 课程ID
|
||||
*/
|
||||
courseId?: number;
|
||||
/**
|
||||
* 状态筛选
|
||||
*/
|
||||
status?: string;
|
||||
};
|
||||
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export type TeacherTaskControllerGetMonthlyStatsParams = {
|
||||
months: string;
|
||||
};
|
||||
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export type TenantControllerFindAllPackageType = typeof TenantControllerFindAllPackageType[keyof typeof TenantControllerFindAllPackageType];
|
||||
|
||||
|
||||
export const TenantControllerFindAllPackageType = {
|
||||
STANDARD: 'STANDARD',
|
||||
PREMIUM: 'PREMIUM',
|
||||
TRIAL: 'TRIAL',
|
||||
} as const;
|
||||
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
import type { TenantControllerFindAllPackageType } from './tenantControllerFindAllPackageType';
|
||||
import type { TenantControllerFindAllStatus } from './tenantControllerFindAllStatus';
|
||||
|
||||
export type TenantControllerFindAllParams = {
|
||||
/**
|
||||
* 当前页码(从 1 开始)
|
||||
*/
|
||||
page?: number;
|
||||
/**
|
||||
* 每页数量
|
||||
* @minimum 1
|
||||
* @maximum 100
|
||||
*/
|
||||
pageSize?: number;
|
||||
/**
|
||||
* 搜索关键词(学校名称或登录账号)
|
||||
*/
|
||||
keyword?: string;
|
||||
/**
|
||||
* 状态筛选
|
||||
*/
|
||||
status?: TenantControllerFindAllStatus;
|
||||
/**
|
||||
* 套餐类型筛选
|
||||
*/
|
||||
packageType?: TenantControllerFindAllPackageType;
|
||||
};
|
||||
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export type TenantControllerFindAllStatus = typeof TenantControllerFindAllStatus[keyof typeof TenantControllerFindAllStatus];
|
||||
|
||||
|
||||
export const TenantControllerFindAllStatus = {
|
||||
ACTIVE: 'ACTIVE',
|
||||
INACTIVE: 'INACTIVE',
|
||||
SUSPENDED: 'SUSPENDED',
|
||||
} as const;
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface TransferStudentDto { [key: string]: unknown }
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface UpdateClassDto { [key: string]: unknown }
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface UpdateClassTeacherDto { [key: string]: unknown }
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface UpdateCompletionDto { [key: string]: unknown }
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface UpdateGrowthRecordDto { [key: string]: unknown }
|
||||
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface UpdateLessonDto {
|
||||
/** 教学目标 */
|
||||
objectives?: string;
|
||||
/** 课前准备 */
|
||||
preparation?: string;
|
||||
/** 课后延伸 */
|
||||
extension?: string;
|
||||
/** 课后反思 */
|
||||
reflection?: string;
|
||||
/** 修改备注 */
|
||||
changeNote?: string;
|
||||
/** 环节数据 */
|
||||
stepsData?: string;
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface UpdateLibraryDto { [key: string]: unknown }
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface UpdateResourceItemDto { [key: string]: unknown }
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface UpdateScheduleDto { [key: string]: unknown }
|
||||
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface UpdateSchoolCourseDto {
|
||||
/** 课程名称 */
|
||||
name?: string;
|
||||
/** 课程描述 */
|
||||
description?: string;
|
||||
/** 修改说明 */
|
||||
changesSummary?: string;
|
||||
/** 状态 */
|
||||
status?: string;
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface UpdateStudentDto { [key: string]: unknown }
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface UpdateTaskDto { [key: string]: unknown }
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface UpdateTaskTemplateDto { [key: string]: unknown }
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Do not edit manually.
|
||||
* 幼儿阅读教学服务平台 API
|
||||
* 提供课程管理、授课记录、学生评价等功能的 API 文档
|
||||
* OpenAPI spec version: 2.0
|
||||
*/
|
||||
|
||||
export interface UpdateTeacherDto { [key: string]: unknown }
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user