From 6f64723428cbb36a87cbe2784fcc82dd3b50f0ef Mon Sep 17 00:00:00 2001 From: En Date: Sat, 21 Mar 2026 12:45:56 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=95=99=E5=B8=88=E7=AB=AF=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E7=9C=8B=E6=9D=BF=E4=B8=8E=E5=AD=A6=E6=A0=A1=E7=AB=AF?= =?UTF-8?q?=E8=AF=BE=E7=A8=8B=E7=BB=9F=E8=AE=A1=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 教师端数据看板: - 新增 TeacherDashboardResponse/TeacherLessonVO/TeacherLessonTrendVO - 新增 TeacherWeeklyStatsResponse 周统计响应 - 新增 TeacherActivityLevel 枚举和 TeacherActivityRankResponse 活跃度排行 - 实现教师端课程统计、任务完成详情、任务反馈接口 学校端课程统计: - 新增 CourseUsageVO/CourseUsageStatsVO/CoursePackageVO - 新增 SchoolCourseResponse 和学校端课程使用查询接口 - 实现学校端统计数据和课程趋势接口 用户资料功能: - 新增 UpdateProfileRequest/UpdateProfileResponse - 实现用户资料更新接口 前后端对齐: - 更新 OpenAPI 规范和前端 API 类型生成 - 优化 DashboardView 组件和 API 调用 Co-Authored-By: Claude Opus 4.6 --- .claude/CLAUDE.md | 57 + .claude/settings.local.json | 3 +- docs/design/10-超管端功能设计.md | 2 +- docs/dev-logs/2026-03-13-admin-e2e-tests.md | 2 +- docs/dev-logs/2026-03-20-profile-feature.md | 176 + .../2026-03-20-teacher-course-usage-stats.md | 204 + docs/dev-logs/2026-03-21.md | 169 + docs/test-logs/admin/2026-02-22.md | 2 +- .../admin/2026-03-13-admin-e2e-test.md | 2 +- reading-platform-frontend/openapi.json | 3681 ++++++++++++----- reading-platform-frontend/src/api/admin.ts | 26 +- reading-platform-frontend/src/api/auth.ts | 23 + .../src/api/generated/index.ts | 353 +- .../model/activeTenantItemResponse.ts | 8 +- .../src/api/generated/model/classInfo.ts | 19 + .../model/courseCollectionPageQueryRequest.ts | 2 + .../api/generated/model/coursePackageVO.ts | 31 + .../generated/model/coursePageQueryRequest.ts | 2 + .../src/api/generated/model/courseResponse.ts | 11 +- .../src/api/generated/model/courseUsageVO.ts | 17 + .../api/generated/model/dayScheduleItem.ts | 2 + .../generated/model/getAllCoursesParams.ts | 13 + .../generated/model/getCoursePageParams.ts | 4 +- .../generated/model/getLessonTrend1Params.ts | 2 +- .../generated/model/getStatisticsParams.ts | 13 + .../model/getTaskCompletions1Params.ts | 13 + .../model/getTaskCompletionsParams.ts | 13 + .../api/generated/model/getTaskListParams.ts | 23 + .../generated/model/getTemplates1Params.ts | 2 + .../api/generated/model/getTemplatesParams.ts | 1 + .../src/api/generated/model/index.ts | 48 +- .../generated/model/lessonDetailResponse.ts | 2 +- .../api/generated/model/lessonTagResponse.ts | 17 + .../model/pageResultSchoolCourseResponse.ts | 16 + .../pageResultTaskCompletionDetailResponse.ts | 16 + .../model/recentActivityItemResponse.ts | 25 - ...sponse.ts => resultListCoursePackageVO.ts} | 6 +- ...ties1200.ts => resultListCourseUsageVO.ts} | 8 +- .../model/resultListTeacherLessonTrendVO.ts | 14 + .../model/resultListTeacherLessonVO.ts | 14 + .../generated/model/resultMapStringString.ts | 14 + ...taItem.ts => resultMapStringStringData.ts} | 2 +- .../resultPageResultSchoolCourseResponse.ts | 14 + ...tPageResultTaskCompletionDetailResponse.ts | 14 + .../model/resultSchoolCourseResponse.ts | 14 + .../resultTaskCompletionDetailResponse.ts | 14 + .../model/resultTaskFeedbackResponse.ts | 14 + .../model/resultTeacherDashboardResponse.ts | 14 + .../model/resultTeacherWeeklyStatsResponse.ts | 14 + .../model/resultUpdateProfileResponse.ts | 14 + .../generated/model/schoolCourseResponse.ts | 50 + .../src/api/generated/model/statsResponse.ts | 2 + .../api/generated/model/statsTrendResponse.ts | 10 +- .../src/api/generated/model/studentInfo.ts | 23 + .../src/api/generated/model/task.ts | 2 + .../model/taskCompletionDetailResponse.ts | 39 + .../api/generated/model/taskCreateRequest.ts | 2 + .../generated/model/taskFeedbackRequest.ts | 27 + .../generated/model/taskFeedbackResponse.ts | 33 + .../src/api/generated/model/taskResponse.ts | 2 + .../api/generated/model/taskSubmitRequest.ts | 31 + .../model/taskTemplateCreateRequest.ts | 4 +- .../generated/model/teacherCreateRequest.ts | 6 +- .../model/teacherDashboardResponse.ts | 26 + .../generated/model/teacherLessonTrendVO.ts | 19 + .../api/generated/model/teacherLessonVO.ts | 44 + .../api/generated/model/teacherResponse.ts | 11 +- .../model/teacherResponseClassNames.ts | 12 + .../src/api/generated/model/teacherStats.ts | 21 + .../generated/model/teacherUpdateRequest.ts | 2 + .../model/teacherWeeklyStatsResponse.ts | 21 + .../generated/model/tenantCreateRequest.ts | 6 +- .../src/api/generated/model/tenantResponse.ts | 4 +- .../generated/model/tenantUpdateRequest.ts | 4 +- .../generated/model/updateProfileRequest.ts | 25 + .../generated/model/updateProfileResponse.ts | 17 + reading-platform-frontend/src/api/school.ts | 31 +- reading-platform-frontend/src/api/teacher.ts | 121 +- .../src/views/admin/DashboardView.vue | 132 - .../src/views/profile/ProfileView.vue | 202 +- .../src/views/school/DashboardView.vue | 313 +- .../src/views/school/ReportView.vue | 2 +- .../src/views/teacher/DashboardView.vue | 317 +- .../tests/e2e/admin/02-dashboard.spec.ts | 4 +- .../common/enums/TeacherActivityLevel.java | 74 + .../common/security/JwtTokenProvider.java | 21 + .../platform/controller/AuthController.java | 29 +- .../admin/AdminStatsController.java | 8 - .../school/SchoolCourseController.java | 22 + .../school/SchoolStatsController.java | 25 +- .../teacher/TeacherStatsController.java | 40 +- .../dto/request/CourseUsageQuery.java | 29 + .../request/RecentActivitiesQueryRequest.java | 15 - .../dto/request/UpdateProfileRequest.java | 26 + .../response/ActiveTenantItemResponse.java | 8 +- .../dto/response/CoursePackageVO.java | 48 + .../dto/response/CourseUsageStatsVO.java | 38 + .../platform/dto/response/CourseUsageVO.java | 24 + .../response/RecentActivityItemResponse.java | 38 - .../platform/dto/response/StatsResponse.java | 3 + .../dto/response/StatsTrendResponse.java | 11 +- .../response/TeacherActivityRankResponse.java | 44 + .../response/TeacherDashboardResponse.java | 57 + .../dto/response/TeacherLessonTrendVO.java | 27 + .../dto/response/TeacherLessonVO.java | 70 + .../response/TeacherWeeklyStatsResponse.java | 30 + .../dto/response/UpdateProfileResponse.java | 24 + .../platform/mapper/CoursePackageMapper.java | 18 + .../reading/platform/mapper/LessonMapper.java | 128 + .../platform/mapper/StudentRecordMapper.java | 41 + .../struct/TeacherStatsMapperStruct.java | 38 + .../reading/platform/service/AuthService.java | 22 + .../platform/service/SchoolStatsService.java | 21 +- .../platform/service/StatsService.java | 7 - .../platform/service/TeacherStatsService.java | 46 +- .../service/impl/AuthServiceImpl.java | 160 + .../service/impl/SchoolStatsServiceImpl.java | 189 +- .../service/impl/StatsServiceImpl.java | 174 +- .../service/impl/TeacherStatsServiceImpl.java | 189 +- 119 files changed, 6325 insertions(+), 2154 deletions(-) create mode 100644 docs/dev-logs/2026-03-20-profile-feature.md create mode 100644 docs/dev-logs/2026-03-20-teacher-course-usage-stats.md create mode 100644 docs/dev-logs/2026-03-21.md create mode 100644 reading-platform-frontend/src/api/generated/model/classInfo.ts create mode 100644 reading-platform-frontend/src/api/generated/model/coursePackageVO.ts create mode 100644 reading-platform-frontend/src/api/generated/model/courseUsageVO.ts create mode 100644 reading-platform-frontend/src/api/generated/model/getAllCoursesParams.ts create mode 100644 reading-platform-frontend/src/api/generated/model/getStatisticsParams.ts create mode 100644 reading-platform-frontend/src/api/generated/model/getTaskCompletions1Params.ts create mode 100644 reading-platform-frontend/src/api/generated/model/getTaskCompletionsParams.ts create mode 100644 reading-platform-frontend/src/api/generated/model/getTaskListParams.ts create mode 100644 reading-platform-frontend/src/api/generated/model/lessonTagResponse.ts create mode 100644 reading-platform-frontend/src/api/generated/model/pageResultSchoolCourseResponse.ts create mode 100644 reading-platform-frontend/src/api/generated/model/pageResultTaskCompletionDetailResponse.ts delete mode 100644 reading-platform-frontend/src/api/generated/model/recentActivityItemResponse.ts rename reading-platform-frontend/src/api/generated/model/{resultListRecentActivityItemResponse.ts => resultListCoursePackageVO.ts} (55%) rename reading-platform-frontend/src/api/generated/model/{getRecentActivities1200.ts => resultListCourseUsageVO.ts} (54%) create mode 100644 reading-platform-frontend/src/api/generated/model/resultListTeacherLessonTrendVO.ts create mode 100644 reading-platform-frontend/src/api/generated/model/resultListTeacherLessonVO.ts create mode 100644 reading-platform-frontend/src/api/generated/model/resultMapStringString.ts rename reading-platform-frontend/src/api/generated/model/{getRecentActivities1200DataItem.ts => resultMapStringStringData.ts} (70%) create mode 100644 reading-platform-frontend/src/api/generated/model/resultPageResultSchoolCourseResponse.ts create mode 100644 reading-platform-frontend/src/api/generated/model/resultPageResultTaskCompletionDetailResponse.ts create mode 100644 reading-platform-frontend/src/api/generated/model/resultSchoolCourseResponse.ts create mode 100644 reading-platform-frontend/src/api/generated/model/resultTaskCompletionDetailResponse.ts create mode 100644 reading-platform-frontend/src/api/generated/model/resultTaskFeedbackResponse.ts create mode 100644 reading-platform-frontend/src/api/generated/model/resultTeacherDashboardResponse.ts create mode 100644 reading-platform-frontend/src/api/generated/model/resultTeacherWeeklyStatsResponse.ts create mode 100644 reading-platform-frontend/src/api/generated/model/resultUpdateProfileResponse.ts create mode 100644 reading-platform-frontend/src/api/generated/model/schoolCourseResponse.ts create mode 100644 reading-platform-frontend/src/api/generated/model/studentInfo.ts create mode 100644 reading-platform-frontend/src/api/generated/model/taskCompletionDetailResponse.ts create mode 100644 reading-platform-frontend/src/api/generated/model/taskFeedbackRequest.ts create mode 100644 reading-platform-frontend/src/api/generated/model/taskFeedbackResponse.ts create mode 100644 reading-platform-frontend/src/api/generated/model/taskSubmitRequest.ts create mode 100644 reading-platform-frontend/src/api/generated/model/teacherDashboardResponse.ts create mode 100644 reading-platform-frontend/src/api/generated/model/teacherLessonTrendVO.ts create mode 100644 reading-platform-frontend/src/api/generated/model/teacherLessonVO.ts create mode 100644 reading-platform-frontend/src/api/generated/model/teacherResponseClassNames.ts create mode 100644 reading-platform-frontend/src/api/generated/model/teacherStats.ts create mode 100644 reading-platform-frontend/src/api/generated/model/teacherWeeklyStatsResponse.ts create mode 100644 reading-platform-frontend/src/api/generated/model/updateProfileRequest.ts create mode 100644 reading-platform-frontend/src/api/generated/model/updateProfileResponse.ts create mode 100644 reading-platform-java/src/main/java/com/reading/platform/common/enums/TeacherActivityLevel.java create mode 100644 reading-platform-java/src/main/java/com/reading/platform/dto/request/CourseUsageQuery.java delete mode 100644 reading-platform-java/src/main/java/com/reading/platform/dto/request/RecentActivitiesQueryRequest.java create mode 100644 reading-platform-java/src/main/java/com/reading/platform/dto/request/UpdateProfileRequest.java create mode 100644 reading-platform-java/src/main/java/com/reading/platform/dto/response/CoursePackageVO.java create mode 100644 reading-platform-java/src/main/java/com/reading/platform/dto/response/CourseUsageStatsVO.java create mode 100644 reading-platform-java/src/main/java/com/reading/platform/dto/response/CourseUsageVO.java delete mode 100644 reading-platform-java/src/main/java/com/reading/platform/dto/response/RecentActivityItemResponse.java create mode 100644 reading-platform-java/src/main/java/com/reading/platform/dto/response/TeacherActivityRankResponse.java create mode 100644 reading-platform-java/src/main/java/com/reading/platform/dto/response/TeacherDashboardResponse.java create mode 100644 reading-platform-java/src/main/java/com/reading/platform/dto/response/TeacherLessonTrendVO.java create mode 100644 reading-platform-java/src/main/java/com/reading/platform/dto/response/TeacherLessonVO.java create mode 100644 reading-platform-java/src/main/java/com/reading/platform/dto/response/TeacherWeeklyStatsResponse.java create mode 100644 reading-platform-java/src/main/java/com/reading/platform/dto/response/UpdateProfileResponse.java create mode 100644 reading-platform-java/src/main/java/com/reading/platform/mapper/struct/TeacherStatsMapperStruct.java diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index c30293e..16482a3 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -497,6 +497,63 @@ definePage({ - **命名**: `YYYY-MM-DD.md` - **创建时机**: 每天开始开发时检查并创建 +--- + +## 文件写入最佳实践(JetBrains 环境) + +> **重要**: 在 JetBrains IDE 插件中使用 Claude Code 时,按以下优先级选择写入方式: + +### 写入方式优先级 + +| 优先级 | 方式 | 适用场景 | 示例 | +|--------|------|----------|------| +| **1** | Write 工具 | 简单文件、单文件写入 | `Write(file_path="...", content="...")` | +| **2** | Bash heredoc | 复杂文件、批量写入、Write 失败时 | `cat > /path/to/file << 'EOF'` | +| **3** | Python 写入 | 需要复杂逻辑处理时 | `python3 -c "..."` | +| **❌ 避免** | sed/awk | Windows Git Bash 中兼容性差 | — | + +### Write 工具使用规范 + +```bash +# ✅ 正确:使用 Unix 风格路径 +Write(file_path="/f/LesingleProject/.../file.txt", content="...") + +# ❌ 错误:不要使用 Windows 路径 +Write(file_path="F:\\LesingleProject\\...\\file.txt", content="...") +``` + +### Bash heredoc 方式(推荐备选) + +```bash +# 多行文件写入 +cat > /f/LesingleProject/.../file.txt << 'EOF' +第一行内容 +第二行内容 +EOF + +# 单行追加 +echo "追加内容" >> /f/LesingleProject/.../file.txt +``` + +### 写入前检查清单 + +- [ ] 确保目录存在:`mkdir -p /f/.../父目录` +- [ ] 使用 Unix 路径格式:`/f/...` 不是 `F:\...` +- [ ] 检查文件是否被占用(IDE 索引、格式化中) + +### 写入失败诊断 + +```bash +# 检查目录是否存在 +ls -la /f/路径/到的/父目录/ + +# 检查文件属性 +ls -la /f/路径/到/文件.txt + +# 测试写入权限 +echo 'test' > /f/路径/到/文件.txt && echo '成功' || echo '失败' +``` + ### 测试记录 - **位置**: `/docs/test-logs/{端}/` - **命名**: `YYYY-MM-DD.md` diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 0ef1f70..1f16127 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -2,7 +2,8 @@ "permissions": { "allow": [ "Bash(mvn compile:*)", - "Bash(sed:*)" + "Bash(sed:*)", + "Bash(grep:*)" ] } } diff --git a/docs/design/10-超管端功能设计.md b/docs/design/10-超管端功能设计.md index c093a64..8ec87d4 100644 --- a/docs/design/10-超管端功能设计.md +++ b/docs/design/10-超管端功能设计.md @@ -349,7 +349,7 @@ │ │ │ 📊 平台整体数据 │ │ ┌─────────────┬─────────────┬─────────────┬─────────────┐ │ -│ │ 租户总数 │ 活跃租户 │ 月授课次数 │ 覆盖学生数 │ │ +│ │ 租户总数 │ 活跃租户 │ 月授课次数 │ 学生总数数 │ │ │ │ 1,254所 │ 986所 │ 45,678次 │ 234,567人 │ │ │ └─────────────┴─────────────┴─────────────┴─────────────┘ │ │ │ diff --git a/docs/dev-logs/2026-03-13-admin-e2e-tests.md b/docs/dev-logs/2026-03-13-admin-e2e-tests.md index 6b550d3..16ca5cc 100644 --- a/docs/dev-logs/2026-03-13-admin-e2e-tests.md +++ b/docs/dev-logs/2026-03-13-admin-e2e-tests.md @@ -54,7 +54,7 @@ - ✅ 账号不存在登录失败 #### 02-dashboard.spec.ts - 数据看板测试 (7 个用例) -- ✅ 验证统计卡片显示(租户数、课程包数、月授课次数、覆盖学生) +- ✅ 验证统计卡片显示(租户数、课程包数、月授课次数、学生总数) - ✅ 验证趋势图加载 - ✅ 验证活跃租户 TOP5 列表 - ✅ 验证热门课程包 TOP5 列表 diff --git a/docs/dev-logs/2026-03-20-profile-feature.md b/docs/dev-logs/2026-03-20-profile-feature.md new file mode 100644 index 0000000..d8d29f8 --- /dev/null +++ b/docs/dev-logs/2026-03-20-profile-feature.md @@ -0,0 +1,176 @@ +# 个人中心功能实现记录 + +## 日期:2026-03-20 + +## 实现内容 + +### 需求目标 +在个人中心页面新增: +1. **密码修改功能**:输入旧密码和新密码进行修改 +2. **信息编辑功能**:可修改姓名、手机号、邮箱 + +--- + +## 后端实现 + +### 1. 新建 DTO 文件 + +#### `UpdateProfileRequest.java` +- 路径:`reading-platform-java/src/main/java/com/reading/platform/dto/request/UpdateProfileRequest.java` +- 字段:name(姓名)、phone(手机号)、email(邮箱) +- 校验注解: + - name: `@Pattern(regexp = "^[\u4e00-\u9fa5a-zA-Z\s]{2,20}$")` + - phone: `@Pattern(regexp = "^1[3-9]\d{9}$")` + - email: `@Email` + +#### `UpdateProfileResponse.java` +- 路径:`reading-platform-java/src/main/java/com/reading/platform/dto/response/UpdateProfileResponse.java` +- 字段:userInfo(用户信息)、token(新 token) + +### 2. 修改 Service 接口 + +#### `AuthService.java` +新增方法: +```java +UpdateProfileResponse updateProfile(UpdateProfileRequest request); +void changePassword(String oldPassword, String newPassword, String currentToken); +``` + +### 3. 修改 Service 实现 + +#### `AuthServiceImpl.java` +- 新增 `updateProfile()` 方法: + - 根据当前用户角色更新对应表(AdminUser/Tenant/Teacher/Parent) + - 学校管理员(Tenant)更新 `contactName/contactPhone/contactEmail` 字段 + - 其他角色更新 `name/phone/email` 字段 + - 生成新 token 并更新 Redis + - 返回更新后的用户信息和新 token + +- 新增 `changePassword(String oldPassword, String newPassword, String currentToken)` 方法: + - 调用原有 `changePassword()` 方法修改密码 + - 将当前 token 加入黑名单(使旧 token 失效) + +- 新增 `convertToUserInfoResponse(Object userInfo, String role)` 私有方法: + - 将 Entity 对象转换为 UserInfoResponse + +### 4. 修改 Controller + +#### `AuthController.java` +- 新增接口 `PUT /api/v1/auth/profile`:修改个人信息 +- 修改接口 `POST /api/v1/auth/change-password`:增加 HttpServletRequest 参数获取 token +- 新增 `resolveToken(HttpServletRequest request)` 辅助方法:从 Authorization header 获取 token + +### 5. 扩展 JwtTokenProvider + +#### `JwtTokenProvider.java` +新增方法: +```java +public long getRemainingExpiration(String token) +``` +- 用于获取 token 剩余过期时间(秒) +- 用于将 token 加入黑名单时设置过期时间 + +--- + +## 前端实现 + +### 1. 扩展 API 文件 + +#### `src/api/auth.ts` +新增类型和方法: +```typescript +export interface UpdateProfileDto { + name?: string; + phone?: string; + email?: string; +} + +export interface UpdateProfileResponse { + userInfo: UserProfile; + token: string; +} + +export function updateProfile(data: UpdateProfileDto): Promise +export function changePassword(oldPassword: string, newPassword: string): Promise +``` + +### 2. 修改个人中心页面 + +#### `src/views/profile/ProfileView.vue` +- 新增「编辑资料」按钮:点击后弹出编辑弹窗 +- 新增「修改密码」按钮:点击后弹出密码修改弹窗 +- 编辑表单: + - 姓名输入框(必填,2-20 位中文或英文) + - 手机号输入框(选填,11 位数字正则校验) + - 邮箱输入框(选填,email 格式校验) +- 密码修改表单: + - 旧密码输入框(必填) + - 新密码输入框(必填,最少 6 位) + - 确认密码输入框(必填,与 new password 一致) +- 修改信息成功后:刷新本地用户信息 +- 修改密码成功后:清除 token 和用户信息,跳转到登录页 + +--- + +## 后端 API 列表 + +| 接口 | 方法 | 路径 | 说明 | +|------|------|------|------| +| 获取个人信息 | GET | `/api/v1/auth/profile` | 已有 | +| 修改个人信息 | PUT | `/api/v1/auth/profile` | 新增,返回新 token | +| 修改密码 | POST | `/api/v1/auth/change-password` | 已有,修改后 token 失效 | + +--- + +## 验证方案 + +### 后端验证 +1. 启动后端服务(端口 8480) +2. 使用 Swagger 测试接口: + - `GET /api/v1/auth/profile` - 获取个人信息 + - `PUT /api/v1/auth/profile` - 修改个人信息 + - `POST /api/v1/auth/change-password?oldPassword=xxx&newPassword=yyy` - 修改密码 + +### 前端验证 +1. 启动前端服务(端口 5173) +2. 登录任意角色账户 +3. 访问个人中心页面 +4. 测试编辑信息功能 +5. 测试修改密码功能 + +--- + +## 注意事项 + +1. **字段映射**: + - 前端统一使用 `name/phone/email` + - 后端 Service 层根据角色映射到对应字段 + - 学校管理员(Tenant)映射到 `contactName/contactPhone/contactEmail` + +2. **Token 处理**: + - 修改个人信息:返回新 token,前端替换 localStorage 中的旧 token + - 修改密码:将当前 token 加入黑名单,前端清除 token 并跳转到登录页 + +3. **表单校验**: + - 前端:手机号正则 `/^1[3-9]\d{9}$/`,邮箱使用 Ant Design 内置校验 + - 后端:使用 `@Valid` + `@Pattern`/`@Email` 注解校验 + +--- + +## 待验证事项 + +- [ ] 超管角色修改信息功能 +- [ ] 学校管理员修改信息功能 +- [ ] 教师角色修改信息功能 +- [ ] 家长角色修改信息功能 +- [ ] 所有角色修改密码功能 +- [ ] 修改信息后 token 是否正确刷新 +- [ ] 修改密码后 token 是否失效并需重新登录 + +--- + +## 下一步计划 + +1. 启动服务进行功能验证 +2. 修复可能发现的问题 +3. 更新测试日志 diff --git a/docs/dev-logs/2026-03-20-teacher-course-usage-stats.md b/docs/dev-logs/2026-03-20-teacher-course-usage-stats.md new file mode 100644 index 0000000..9ac8879 --- /dev/null +++ b/docs/dev-logs/2026-03-20-teacher-course-usage-stats.md @@ -0,0 +1,204 @@ +# 开发日志 - 2026-03-20 + +## 教师端课程使用统计功能实现 + +### 实现内容 + +实现了教师端课程使用统计功能(增强版),支持按时间周期筛选和更多维度的统计。 + +### 后端改动 + +#### 1. 新增 DTO 类 + +**CourseUsageQuery.java** - 请求参数 DTO +- `periodType`: 统计周期类型(TODAY/WEEK/MONTH/CUSTOM) +- `startDate`: 自定义周期开始日期 +- `endDate`: 自定义周期结束日期 + +**CourseUsageStatsVO.java** - 响应对象 DTO(增强版) +- `coursePackageId`: 课程包 ID +- `coursePackageName`: 课程包名称 +- `usageCount`: 使用次数(基于实际授课记录统计) +- `studentCount`: 参与学生数(去重) +- `avgDuration`: 平均时长(分钟) +- `lastUsedAt`: 最后使用时间 + +#### 2. LessonMapper.java + +新增 `getCourseUsageStats()` 方法: +- 使用自定义 SQL 统计课程包使用情况 +- 基于 `lesson` 表实际授课记录 +- 支持时间范围筛选 +- 支持教师 ID 筛选 +- 只统计 `COMPLETED` 状态的课程 +- 返回 TOP 10 课程包 + +#### 3. TeacherStatsService.java + +新增接口方法: +```java +List getCourseUsageStats(Long tenantId, Long teacherId, CourseUsageQuery query); +``` + +#### 4. TeacherStatsServiceImpl.java + +实现 `getCourseUsageStats()` 方法: +- 调用 `calculateTimeRange()` 计算时间范围 +- 支持四种周期类型:TODAY、WEEK、MONTH、CUSTOM +- 调用 Mapper 获取统计数据 + +实现 `calculateTimeRange()` 辅助方法: +- TODAY: 当天 00:00:00 至今 +- WEEK: 本周一至今 +- MONTH: 本月 1 号至今 +- CUSTOM: 自定义日期范围 + +#### 5. TeacherStatsController.java + +新增接口: +- `GET /api/v1/teacher/course-usage-stats` - 增强版课程使用统计 +- 支持参数:`periodType`、`startDate`、`endDate` +- 保留旧接口 `/api/v1/teacher/course-usage`(标记为 @Deprecated) + +### 前端改动 + +#### 1. src/api/teacher.ts + +新增类型定义: +- `CourseUsageQueryParams` - 查询参数类型 +- `CourseUsageStatsItem` - 响应数据类型 + +新增 API 方法: +- `getTeacherCourseUsageStats()` - 获取增强版课程使用统计 + +保留旧 API(向后兼容): +- `getTeacherCourseUsage()` + +#### 2. src/views/teacher/DashboardView.vue + +**新增响应式数据**: +- `usagePeriodType`: 当前选择的周期类型 +- `courseUsageStatsData`: 增强版课程使用统计数据 +- `periodOptions`: 周期选项(今日/本周/本月) + +**UI 改动**: +- 在课程使用卡片添加周期选择器(a-segmented) +- 用户可通过点击切换统计周期 + +**图表增强**: +- `initUsageChart()` 支持新旧两种数据类型 +- tooltip 显示更详细信息: + - 使用次数 + - 参与学生数 + - 平均时长 + - 最后使用时间 + +**数据加载**: +- `loadUsageData()` 调用新 API `getTeacherCourseUsageStats()` +- 周期切换时自动重新加载数据 + +### 统计逻辑 + +1. **使用次数**:统计周期内 COMPLETED 状态的 lesson 数量 +2. **参与学生数**:去重统计 student_record 中的 student_id +3. **平均时长**:计算 lesson 的 start_datetime 到 end_datetime 的分钟差平均值 +4. **最后使用时间**:最近一次授课完成时间 + +### 数据流向 + +``` +用户选择周期 → 前端调用 API → Controller 接收参数 +→ Service 计算时间范围 → Mapper 执行 SQL 统计 +→ 返回 CourseUsageStatsVO 列表 → 前端图表展示 +``` + +### SQL 统计逻辑 + +```sql +SELECT + cp.id AS coursePackageId, + cp.name AS coursePackageName, + COUNT(l.id) AS usageCount, + COUNT(DISTINCT sr.student_id) AS studentCount, + AVG(TIMESTAMPDIFF(MINUTE, l.start_datetime, l.end_datetime)) AS avgDuration, + MAX(l.end_datetime) AS lastUsedAt +FROM lesson l +INNER JOIN course_package cp ON l.course_id = cp.id +LEFT JOIN student_record sr ON l.id = sr.lesson_id +WHERE l.tenant_id = #{tenantId} + AND l.end_datetime >= #{startTime} + AND l.end_datetime <= #{endTime} + AND l.status = 'COMPLETED' + AND l.teacher_id = #{teacherId} +GROUP BY cp.id, cp.name +ORDER BY usageCount DESC +LIMIT 10 +``` + +### 测试验证 + +- [x] 后端编译通过 ✅ +- [x] 启动后端服务测试 API ✅ +- [x] API 返回数据正确 ✅ +- [ ] 前端展示周期选择器 +- [ ] 切换周期数据正确刷新 +- [ ] tooltip 显示完整信息 + +### API 测试响应示例 + +**请求**: `GET /api/v1/teacher/course-usage-stats?periodType=MONTH` + +**响应**: +```json +{ + "code": 200, + "message": "操作成功", + "data": [ + { + "coursePackageId": "17", + "coursePackageName": "课程简介课程简介课程简介课程简介课程简介课程简介课程简介", + "usageCount": 15, + "studentCount": 5, + "avgDuration": 0, + "lastUsedAt": "2026-03-16T00:00:00" + }, + { + "coursePackageId": "16", + "coursePackageName": "T 色他", + "usageCount": 1, + "studentCount": 0, + "avgDuration": 0, + "lastUsedAt": "2026-03-16T00:00:00" + } + ] +} +``` + +### 技术难点解决 + +**问题**: MyBatis `@SelectProvider` 不支持 XML 格式的 `` 动态标签 + +**解决方案**: 使用 SQL 条件 `AND (#{teacherId} IS NULL OR l.teacher_id = #{teacherId})` 替代动态 SQL,实现可选参数过滤。 + +### 文件清单 + +**后端新增**: +- `dto/request/CourseUsageQuery.java` +- `dto/response/CourseUsageStatsVO.java` + +**后端修改**: +- `mapper/LessonMapper.java` +- `service/TeacherStatsService.java` +- `service/impl/TeacherStatsServiceImpl.java` +- `controller/teacher/TeacherStatsController.java` + +**前端修改**: +- `src/api/teacher.ts` +- `src/views/teacher/DashboardView.vue` + +### 后续优化建议 + +1. **前端展示优化**:可以考虑将饼图改为条形图,更适合展示 TOP 排行 +2. **导出功能**:支持导出统计数据为 Excel +3. **更多维度**:增加课程类型分布、班级使用对比等 +4. **缓存优化**:统计数据可以缓存在 Redis 中,提高查询性能 diff --git a/docs/dev-logs/2026-03-21.md b/docs/dev-logs/2026-03-21.md new file mode 100644 index 0000000..904332d --- /dev/null +++ b/docs/dev-logs/2026-03-21.md @@ -0,0 +1,169 @@ +# 开发日志 2026-03-21 + +## 学校端 - 课程使用统计功能实现 + +### 需求背景 + +学校端 Dashboard 页面已有"课程使用统计"卡片组件,但后端返回空数据。需要实现该功能,让学校管理员能够查看本校各课程包的使用情况。 + +### 实现内容 + +#### 1. 后端修改 + +**Controller 层** (`SchoolStatsController.java`) + +- 添加日期范围参数支持,允许前端传入 `startDate` 和 `endDate` +- 不传参数时默认统计本月数据 + +```java +@GetMapping("/courses") +@Operation(summary = "获取课程使用统计") +public Result>> getCourseUsageStats( + @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate, + @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate) { + Long tenantId = SecurityUtils.getCurrentTenantId(); + return Result.success(schoolStatsService.getCourseUsageStats(tenantId, startDate, endDate)); +} +``` + +**Service 接口** (`SchoolStatsService.java`) + +- 更新方法签名,添加日期参数 +- 添加 `LocalDate` 导入 + +```java +List> getCourseUsageStats(Long tenantId, LocalDate startDate, LocalDate endDate); +``` + +**Service 实现** (`SchoolStatsServiceImpl.java`) + +- 使用 `LessonMapper.getCourseUsageStats()` 查询实际数据 +- 支持日期范围筛选,默认统计本月 +- 将 `CourseUsageStatsVO` 转换为 `Map` 格式返回 + +```java +@Override +public List> getCourseUsageStats(Long tenantId, LocalDate startDate, LocalDate endDate) { + // 计算时间范围 + LocalDateTime startTime; + LocalDateTime endTime; + + if (startDate != null) { + startTime = startDate.atStartOfDay(); + } else { + startTime = LocalDateTime.now().withDayOfMonth(1).withHour(0).withMinute(0).withSecond(0).withNano(0); + } + + if (endDate != null) { + endTime = endDate.atTime(23, 59, 59); + } else { + endTime = LocalDateTime.now(); + } + + // 调用 Mapper 查询 + List stats = lessonMapper.getCourseUsageStats( + tenantId, null, startTime, endTime + ); + + // 转换为 Map 格式返回 + return stats.stream().map(vo -> { + Map map = new HashMap<>(); + map.put("courseId", vo.getCoursePackageId()); + map.put("courseName", vo.getCoursePackageName()); + map.put("usageCount", vo.getUsageCount()); + map.put("studentCount", vo.getStudentCount() != null ? vo.getStudentCount() : 0); + map.put("avgDuration", vo.getAvgDuration() != null ? vo.getAvgDuration() : 0); + map.put("lastUsedAt", vo.getLastUsedAt()); + return map; + }).collect(Collectors.toList()); +} +``` + +#### 2. 前端修改 + +**API 层** (`src/api/school.ts`) + +- 更新 `getCourseUsageStats` 支持日期参数 +- 增强返回类型定义 + +```typescript +export const getCourseUsageStats = (startDate?: string, endDate?: string) => { + const params: Record = {}; + if (startDate) params.startDate = startDate; + if (endDate) params.endDate = endDate; + return http.get>('/v1/school/stats/courses', { params }); +}; +``` + +**组件层** (`src/views/school/DashboardView.vue`) + +- `loadCourseStats` 函数传递日期范围参数 +- `onMounted` 中设置默认日期范围为当月 + +```typescript +const loadCourseStats = async () => { + courseStatsLoading.value = true; + try { + const startDate = dateRange.value?.[0]?.format('YYYY-MM-DD'); + const endDate = dateRange.value?.[1]?.format('YYYY-MM-DD'); + const data = await getCourseUsageStats(startDate, endDate); + courseStats.value = data.slice(0, 10); + } catch (error) { + console.error('Failed to load course stats:', error); + } finally { + courseStatsLoading.value = false; + } +}; + +onMounted(() => { + // ... 其他初始化 + // 设置默认日期范围为当月 + const now = dayjs(); + const monthStart = now.startOf('month'); + const monthEnd = now.endOf('month'); + dateRange.value = [monthStart, monthEnd]; +}); +``` + +### 数据来源 + +课程使用统计基于 `lesson` 表的实际授课记录统计: + +- **usageCount**: 统计周期内 COMPLETED 状态的 lesson 数量 +- **studentCount**: 参与学生数(去重统计 student_record 中的 student_id) +- **avgDuration**: 平均时长(lesson 的 start_datetime 到 end_datetime 的分钟差平均值) +- **lastUsedAt**: 最后使用时间(最近一次授课完成时间) + +### 文件变更列表 + +| 文件 | 变更说明 | +|------|---------| +| `reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolStatsController.java` | 添加日期范围参数 | +| `reading-platform-java/src/main/java/com/reading/platform/service/SchoolStatsService.java` | 更新方法签名 | +| `reading-platform-java/src/main/java/com/reading/platform/service/impl/SchoolStatsServiceImpl.java` | 实现实际查询逻辑 | +| `reading-platform-frontend/src/api/school.ts` | 更新 API 函数和类型 | +| `reading-platform-frontend/src/views/school/DashboardView.vue` | 传递日期参数,设置默认日期范围 | + +### 测试验证 + +- [ ] 后端编译通过 +- [ ] 启动后端服务(端口 8480) +- [ ] 启动前端服务(端口 5173) +- [ ] 登录学校管理员账号 +- [ ] 访问 Dashboard 页面,查看"课程使用统计"卡片 +- [ ] 验证日期范围筛选功能 +- [ ] 验证数据显示正确性 + +### 后续优化建议 + +1. **增加更多统计维度**:按班级、按教师统计 +2. **可视化增强**:趋势图、热力图 +3. **导出功能**:Excel 导出课程使用明细 +4. **性能优化**:大数据量时考虑缓存 diff --git a/docs/test-logs/admin/2026-02-22.md b/docs/test-logs/admin/2026-02-22.md index 3f58d81..f5e8a0e 100644 --- a/docs/test-logs/admin/2026-02-22.md +++ b/docs/test-logs/admin/2026-02-22.md @@ -114,7 +114,7 @@ | 租户总数 | 显示真实数据 | 显示 2 | ✅ | | 课程包总数 | 显示真实数据 | 显示 5 | ✅ | | 月授课次数 | 显示真实数据 | 显示 22 | ✅ | -| 覆盖学生 | 显示真实数据 | 显示 5 | ✅ | +| 学生总数 | 显示真实数据 | 显示 5 | ✅ | ### 3.2 使用趋势图 | 测试项 | 预期结果 | 实际结果 | 状态 | diff --git a/docs/test-logs/admin/2026-03-13-admin-e2e-test.md b/docs/test-logs/admin/2026-03-13-admin-e2e-test.md index fa52b14..c8a8df0 100644 --- a/docs/test-logs/admin/2026-03-13-admin-e2e-test.md +++ b/docs/test-logs/admin/2026-03-13-admin-e2e-test.md @@ -51,7 +51,7 @@ reading-platform-frontend/tests/e2e/admin/ | 测试项 | 状态 | 说明 | |--------|------|------| -| 验证统计卡片显示 | ✅ | 租户数、课程包数、月授课次数、覆盖学生 | +| 验证统计卡片显示 | ✅ | 租户数、课程包数、月授课次数、学生总数 | | 验证趋势图加载 | ✅ | 验证图表容器显示 | | 验证活跃租户 TOP5 列表 | ✅ | 验证列表数据 | | 验证热门课程包 TOP5 列表 | ✅ | 验证列表数据 | diff --git a/reading-platform-frontend/openapi.json b/reading-platform-frontend/openapi.json index a442865..c622793 100644 --- a/reading-platform-frontend/openapi.json +++ b/reading-platform-frontend/openapi.json @@ -26,12 +26,8 @@ "description": "教师端课时 API" }, { - "name": "School - Task", - "description": "Task Management APIs for School" - }, - { - "name": "Parent - Task", - "description": "Task APIs for Parent" + "name": "学校端 - 阅读任务(只读)", + "description": "学校端阅读任务查看 API" }, { "name": "超管 - 系统设置", @@ -109,6 +105,10 @@ "name": "学校端 - 数据导出", "description": "School Export APIs" }, + { + "name": "家长端 - 任务管理", + "description": "家长端任务 API" + }, { "name": "教师端 - 统计数据", "description": "教师端统计数据 API" @@ -425,6 +425,208 @@ } } }, + "/api/v1/teacher/tasks/completions/{completionId}/feedback": { + "put": { + "tags": [ + "教师端 - 任务管理" + ], + "summary": "修改评价", + "operationId": "updateFeedback", + "parameters": [ + { + "name": "completionId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskFeedbackRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultTaskFeedbackResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "405": { + "description": "Method Not Allowed", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + } + } + }, + "post": { + "tags": [ + "教师端 - 任务管理" + ], + "summary": "提交评价", + "operationId": "submitFeedback", + "parameters": [ + { + "name": "completionId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskFeedbackRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultTaskFeedbackResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "405": { + "description": "Method Not Allowed", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + } + } + } + }, "/api/v1/teacher/task-templates/{id}": { "get": { "tags": [ @@ -1937,288 +2139,6 @@ } } }, - "/api/v1/school/tasks/{id}": { - "get": { - "tags": [ - "School - Task" - ], - "summary": "Get task by ID", - "operationId": "getTask_1", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultTaskResponse" - } - } - } - }, - "400": { - "description": "Bad Request", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "404": { - "description": "Not Found", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "405": { - "description": "Method Not Allowed", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - } - } - }, - "put": { - "tags": [ - "School - Task" - ], - "summary": "Update task", - "operationId": "updateTask_1", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TaskUpdateRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultTaskResponse" - } - } - } - }, - "400": { - "description": "Bad Request", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "404": { - "description": "Not Found", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "405": { - "description": "Method Not Allowed", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - } - } - }, - "delete": { - "tags": [ - "School - Task" - ], - "summary": "Delete task", - "operationId": "deleteTask_1", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "400": { - "description": "Bad Request", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "404": { - "description": "Not Found", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "405": { - "description": "Method Not Allowed", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - } - } - } - }, "/api/v1/school/task-templates/{id}": { "get": { "tags": [ @@ -4801,6 +4721,208 @@ } } }, + "/api/v1/parent/tasks/{taskId}/submit": { + "put": { + "tags": [ + "家长端 - 任务管理" + ], + "summary": "修改任务提交", + "operationId": "updateSubmission", + "parameters": [ + { + "name": "taskId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskSubmitRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultTaskCompletionDetailResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "405": { + "description": "Method Not Allowed", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + } + } + }, + "post": { + "tags": [ + "家长端 - 任务管理" + ], + "summary": "提交任务完成", + "operationId": "submitTask", + "parameters": [ + { + "name": "taskId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskSubmitRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultTaskCompletionDetailResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "405": { + "description": "Method Not Allowed", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + } + } + } + }, "/api/v1/parent/growth-records/{id}": { "get": { "tags": [ @@ -5083,6 +5205,176 @@ } } }, + "/api/v1/auth/profile": { + "get": { + "tags": [ + "认证管理" + ], + "summary": "获取当前用户信息", + "operationId": "getCurrentUser", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultUserInfoResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "405": { + "description": "Method Not Allowed", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + } + } + }, + "put": { + "tags": [ + "认证管理" + ], + "summary": "修改个人信息", + "operationId": "updateProfile", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateProfileRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultUpdateProfileResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "405": { + "description": "Method Not Allowed", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + } + } + } + }, "/api/v1/admin/themes/{id}": { "get": { "tags": [ @@ -9036,6 +9328,14 @@ "schema": { "type": "string" } + }, + { + "name": "keyword", + "in": "query", + "required": false, + "schema": { + "type": "string" + } } ], "responses": { @@ -10297,7 +10597,7 @@ "教师端 - 课时管理" ], "summary": "提交课时反馈", - "operationId": "submitFeedback", + "operationId": "submitFeedback_1", "parameters": [ { "name": "id", @@ -11194,14 +11494,6 @@ "type": "integer", "format": "int64" } - }, - { - "name": "newPassword", - "in": "query", - "required": true, - "schema": { - "type": "string" - } } ], "responses": { @@ -11210,223 +11502,7 @@ "content": { "*/*": { "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "400": { - "description": "Bad Request", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "404": { - "description": "Not Found", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "405": { - "description": "Method Not Allowed", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - } - } - } - }, - "/api/v1/school/tasks": { - "get": { - "tags": [ - "School - Task" - ], - "summary": "Get task page", - "operationId": "getTaskPage_1", - "parameters": [ - { - "name": "pageNum", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "format": "int32", - "default": 1 - } - }, - { - "name": "pageSize", - "in": "query", - "required": false, - "schema": { - "type": "integer", - "format": "int32", - "default": 10 - } - }, - { - "name": "keyword", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "type", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "status", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultPageResultTaskResponse" - } - } - } - }, - "400": { - "description": "Bad Request", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "404": { - "description": "Not Found", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "405": { - "description": "Method Not Allowed", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - } - } - }, - "post": { - "tags": [ - "School - Task" - ], - "summary": "Create task", - "operationId": "createTask_1", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TaskCreateRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultTaskResponse" + "$ref": "#/components/schemas/ResultMapStringString" } } } @@ -11529,6 +11605,22 @@ "schema": { "type": "string" } + }, + { + "name": "taskType", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "keyword", + "in": "query", + "required": false, + "schema": { + "type": "string" + } } ], "responses": { @@ -13915,9 +14007,9 @@ "/api/v1/parent/tasks/{id}/complete": { "post": { "tags": [ - "Parent - Task" + "家长端 - 任务管理" ], - "summary": "Complete task", + "summary": "完成任务(旧接口,兼容使用)", "operationId": "completeTask", "parameters": [ { @@ -17550,7 +17642,7 @@ "content": { "*/*": { "schema": { - "$ref": "#/components/schemas/ResultMapStringObject" + "$ref": "#/components/schemas/ResultTeacherWeeklyStatsResponse" } } } @@ -17631,7 +17723,219 @@ "content": { "*/*": { "schema": { - "$ref": "#/components/schemas/ResultListLesson" + "$ref": "#/components/schemas/ResultListTeacherLessonVO" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "405": { + "description": "Method Not Allowed", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + } + } + } + }, + "/api/v1/teacher/tasks/{taskId}/completions": { + "get": { + "tags": [ + "教师端 - 任务管理" + ], + "summary": "获取任务完成情况列表", + "operationId": "getTaskCompletions", + "parameters": [ + { + "name": "taskId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "pageNum", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 1 + } + }, + { + "name": "pageSize", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 10 + } + }, + { + "name": "status", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultPageResultTaskCompletionDetailResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "405": { + "description": "Method Not Allowed", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + } + } + } + }, + "/api/v1/teacher/tasks/completions/{completionId}": { + "get": { + "tags": [ + "教师端 - 任务管理" + ], + "summary": "获取提交详情", + "operationId": "getCompletionDetail", + "parameters": [ + { + "name": "completionId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultTaskCompletionDetailResponse" } } } @@ -18096,7 +18400,7 @@ "content": { "*/*": { "schema": { - "$ref": "#/components/schemas/ResultListCoursePackage" + "$ref": "#/components/schemas/ResultListCoursePackageVO" } } } @@ -18647,7 +18951,7 @@ "content": { "*/*": { "schema": { - "$ref": "#/components/schemas/ResultListMapStringObject" + "$ref": "#/components/schemas/ResultListTeacherLessonTrendVO" } } } @@ -18912,7 +19216,7 @@ "content": { "*/*": { "schema": { - "$ref": "#/components/schemas/ResultMapStringObject" + "$ref": "#/components/schemas/ResultTeacherDashboardResponse" } } } @@ -19017,7 +19321,23 @@ } }, { - "name": "category", + "name": "grade", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "domain", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "lessonType", "in": "query", "required": false, "schema": { @@ -19198,6 +19518,32 @@ ], "summary": "获取所有课程", "operationId": "getAllCourses", + "parameters": [ + { + "name": "keyword", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "grade", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "domain", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "description": "OK", @@ -19285,7 +19631,7 @@ "content": { "*/*": { "schema": { - "$ref": "#/components/schemas/ResultListMapStringObject" + "$ref": "#/components/schemas/ResultListCourseUsageVO" } } } @@ -19920,13 +20266,13 @@ "operationId": "getLessonTrend_1", "parameters": [ { - "name": "months", + "name": "days", "in": "query", "required": false, "schema": { "type": "integer", "format": "int32", - "default": 6 + "default": 7 } } ], @@ -20301,7 +20647,7 @@ "content": { "*/*": { "schema": { - "$ref": "#/components/schemas/ResultTimetableResponse" + "$ref": "#/components/schemas/ResultMapStringObject" } } } @@ -20904,6 +21250,616 @@ } } }, + "/api/v1/school/reading-tasks": { + "get": { + "tags": [ + "学校端 - 阅读任务(只读)" + ], + "summary": "获取任务列表(支持多维度筛选)", + "operationId": "getTaskList", + "parameters": [ + { + "name": "pageNum", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 1 + } + }, + { + "name": "pageSize", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 10 + } + }, + { + "name": "keyword", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "type", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "status", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "classIds", + "in": "query", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + } + }, + { + "name": "teacherIds", + "in": "query", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + } + }, + { + "name": "dateType", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "startDate", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "endDate", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "completionRate", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "sortBy", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "sortOrder", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultPageResultTaskResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "405": { + "description": "Method Not Allowed", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + } + } + } + }, + "/api/v1/school/reading-tasks/{taskId}": { + "get": { + "tags": [ + "学校端 - 阅读任务(只读)" + ], + "summary": "获取任务详情", + "operationId": "getTaskDetail", + "parameters": [ + { + "name": "taskId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultTaskResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "405": { + "description": "Method Not Allowed", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + } + } + } + }, + "/api/v1/school/reading-tasks/{taskId}/completions": { + "get": { + "tags": [ + "学校端 - 阅读任务(只读)" + ], + "summary": "获取任务完成情况列表", + "operationId": "getTaskCompletions_1", + "parameters": [ + { + "name": "taskId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "pageNum", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 1 + } + }, + { + "name": "pageSize", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 10 + } + }, + { + "name": "status", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultPageResultTaskCompletionDetailResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "405": { + "description": "Method Not Allowed", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + } + } + } + }, + "/api/v1/school/reading-tasks/statistics": { + "get": { + "tags": [ + "学校端 - 阅读任务(只读)" + ], + "summary": "获取统计数据", + "operationId": "getStatistics", + "parameters": [ + { + "name": "dateType", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "startDate", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "endDate", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultObject" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "405": { + "description": "Method Not Allowed", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + } + } + } + }, + "/api/v1/school/reading-tasks/completions/{completionId}": { + "get": { + "tags": [ + "学校端 - 阅读任务(只读)" + ], + "summary": "获取学生提交详情", + "operationId": "getCompletionDetail_1", + "parameters": [ + { + "name": "completionId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultTaskCompletionDetailResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "405": { + "description": "Method Not Allowed", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + } + } + } + }, "/api/v1/school/parents/{id}/children": { "get": { "tags": [ @@ -22345,7 +23301,7 @@ "content": { "*/*": { "schema": { - "$ref": "#/components/schemas/ResultPageResultCourseResponse" + "$ref": "#/components/schemas/ResultPageResultSchoolCourseResponse" } } } @@ -22437,7 +23393,7 @@ "content": { "*/*": { "schema": { - "$ref": "#/components/schemas/ResultCoursePackage" + "$ref": "#/components/schemas/ResultSchoolCourseResponse" } } } @@ -22508,9 +23464,9 @@ "/api/v1/parent/tasks": { "get": { "tags": [ - "Parent - Task" + "家长端 - 任务管理" ], - "summary": "Get my tasks", + "summary": "获取我的任务列表", "operationId": "getMyTasks", "parameters": [ { @@ -22619,10 +23575,10 @@ "/api/v1/parent/tasks/{id}": { "get": { "tags": [ - "Parent - Task" + "家长端 - 任务管理" ], - "summary": "Get task by ID", - "operationId": "getTask_2", + "summary": "获取任务详情", + "operationId": "getTask_1", "parameters": [ { "name": "id", @@ -22711,9 +23667,9 @@ "/api/v1/parent/tasks/student/{studentId}": { "get": { "tags": [ - "Parent - Task" + "家长端 - 任务管理" ], - "summary": "Get tasks by student ID", + "summary": "获取孩子的任务列表", "operationId": "getTasksByStudent", "parameters": [ { @@ -22828,6 +23784,190 @@ } } }, + "/api/v1/parent/tasks/completions/{completionId}": { + "get": { + "tags": [ + "家长端 - 任务管理" + ], + "summary": "获取提交详情", + "operationId": "getCompletionDetail_2", + "parameters": [ + { + "name": "completionId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultTaskCompletionDetailResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "405": { + "description": "Method Not Allowed", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + } + } + } + }, + "/api/v1/parent/tasks/completions/{completionId}/feedback": { + "get": { + "tags": [ + "家长端 - 任务管理" + ], + "summary": "获取教师评价", + "operationId": "getFeedback", + "parameters": [ + { + "name": "completionId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultTaskFeedbackResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "405": { + "description": "Method Not Allowed", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ResultVoid" + } + } + } + } + } + } + }, "/api/v1/parent/notifications": { "get": { "tags": [ @@ -23899,87 +25039,6 @@ } } }, - "/api/v1/auth/profile": { - "get": { - "tags": [ - "认证管理" - ], - "summary": "获取当前用户信息", - "operationId": "getCurrentUser", - "responses": { - "200": { - "description": "OK", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultUserInfoResponse" - } - } - } - }, - "400": { - "description": "Bad Request", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "401": { - "description": "Unauthorized", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "403": { - "description": "Forbidden", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "404": { - "description": "Not Found", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "405": { - "description": "Method Not Allowed", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/ResultVoid" - } - } - } - } - } - } - }, "/api/v1/admin/tenants/stats": { "get": { "tags": [ @@ -25189,6 +26248,10 @@ "type": "string", "description": "任务类型" }, + "relatedBookName": { + "type": "string", + "description": "关联绘本名称" + }, "courseId": { "type": "integer", "description": "课程 ID", @@ -25234,6 +26297,98 @@ }, "description": "任务响应" }, + "TaskFeedbackRequest": { + "required": [ + "result" + ], + "type": "object", + "properties": { + "result": { + "type": "string", + "description": "评价结果:EXCELLENT-优秀/PASSED-通过/NEEDS_WORK-需改进" + }, + "rating": { + "maximum": 5, + "minimum": 1, + "type": "integer", + "description": "评分 1-5(可选)", + "format": "int32" + }, + "comment": { + "maxLength": 500, + "minLength": 0, + "type": "string", + "description": "评语" + } + }, + "description": "任务评价请求" + }, + "ResultTaskFeedbackResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "data": { + "$ref": "#/components/schemas/TaskFeedbackResponse" + } + } + }, + "TaskFeedbackResponse": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "评价ID", + "format": "int64" + }, + "completionId": { + "type": "integer", + "description": "完成记录ID", + "format": "int64" + }, + "result": { + "type": "string", + "description": "评价结果:EXCELLENT/PASSED/NEEDS_WORK" + }, + "resultText": { + "type": "string", + "description": "评价结果文本:优秀/通过/需改进" + }, + "rating": { + "type": "integer", + "description": "评分 1-5", + "format": "int32" + }, + "comment": { + "type": "string", + "description": "评语" + }, + "teacherId": { + "type": "integer", + "description": "教师ID", + "format": "int64" + }, + "teacherName": { + "type": "string", + "description": "教师姓名" + }, + "teacherAvatar": { + "type": "string", + "description": "教师头像" + }, + "createdAt": { + "type": "string", + "description": "评价时间", + "format": "date-time" + } + }, + "description": "任务评价响应" + }, "TaskTemplateCreateRequest": { "required": [ "name" @@ -25267,18 +26422,16 @@ "format": "int32" }, "isDefault": { - "type": "integer", - "description": "是否默认模板", - "format": "int32" + "type": "boolean", + "description": "是否默认模板" }, "content": { "type": "string", "description": "模板内容" }, "isPublic": { - "type": "integer", - "description": "是否公开", - "format": "int32" + "type": "boolean", + "description": "是否公开" } }, "description": "任务模板创建请求" @@ -25894,6 +27047,15 @@ "status": { "type": "string", "description": "状态" + }, + "classIds": { + "type": "array", + "description": "负责班级ID列表", + "items": { + "type": "integer", + "description": "负责班级ID列表", + "format": "int64" + } } }, "description": "教师更新请求" @@ -25926,10 +27088,6 @@ "description": "租户 ID", "format": "int64" }, - "username": { - "type": "string", - "description": "用户名" - }, "name": { "type": "string", "description": "姓名" @@ -25958,6 +27116,24 @@ "type": "string", "description": "状态" }, + "classIds": { + "type": "array", + "description": "负责班级ID列表", + "items": { + "type": "integer", + "description": "负责班级ID列表", + "format": "int64" + } + }, + "classNames": { + "type": "object", + "description": "负责班级名称" + }, + "lessonCount": { + "type": "integer", + "description": "授课次数", + "format": "int32" + }, "lastLoginAt": { "type": "string", "description": "最后登录时间", @@ -25972,6 +27148,10 @@ "type": "string", "description": "更新时间", "format": "date-time" + }, + "loginAccount": { + "type": "string", + "description": "登录账号" } }, "description": "教师响应" @@ -26638,6 +27818,257 @@ } } }, + "TaskSubmitRequest": { + "type": "object", + "properties": { + "studentId": { + "type": "integer", + "description": "学生ID", + "format": "int64" + }, + "photos": { + "maxItems": 9, + "minItems": 0, + "type": "array", + "description": "照片URL数组(最多9张)", + "items": { + "type": "string", + "description": "照片URL数组(最多9张)" + } + }, + "videoUrl": { + "type": "string", + "description": "视频URL" + }, + "audioUrl": { + "type": "string", + "description": "语音URL" + }, + "content": { + "maxLength": 1000, + "minLength": 0, + "type": "string", + "description": "阅读心得/完成内容" + } + }, + "description": "任务提交请求" + }, + "ClassInfo": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "班级ID", + "format": "int64" + }, + "name": { + "type": "string", + "description": "班级名称" + }, + "grade": { + "type": "string", + "description": "年级" + } + }, + "description": "班级信息" + }, + "ResultTaskCompletionDetailResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "data": { + "$ref": "#/components/schemas/TaskCompletionDetailResponse" + } + } + }, + "StudentInfo": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "学生ID", + "format": "int64" + }, + "name": { + "type": "string", + "description": "学生姓名" + }, + "avatar": { + "type": "string", + "description": "学生头像" + }, + "gender": { + "type": "string", + "description": "性别:MALE/FEMALE" + }, + "classInfo": { + "$ref": "#/components/schemas/ClassInfo" + } + }, + "description": "学生信息" + }, + "TaskCompletionDetailResponse": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "完成记录ID", + "format": "int64" + }, + "taskId": { + "type": "integer", + "description": "任务ID", + "format": "int64" + }, + "taskTitle": { + "type": "string", + "description": "任务标题" + }, + "student": { + "$ref": "#/components/schemas/StudentInfo" + }, + "status": { + "type": "string", + "description": "状态:PENDING/SUBMITTED/REVIEWED" + }, + "statusText": { + "type": "string", + "description": "状态文本:待完成/已提交/已评价" + }, + "photos": { + "type": "array", + "description": "照片URL数组", + "items": { + "type": "string", + "description": "照片URL数组" + } + }, + "videoUrl": { + "type": "string", + "description": "视频URL" + }, + "audioUrl": { + "type": "string", + "description": "语音URL" + }, + "content": { + "type": "string", + "description": "完成内容/阅读心得" + }, + "submittedAt": { + "type": "string", + "description": "提交时间", + "format": "date-time" + }, + "reviewedAt": { + "type": "string", + "description": "评价时间", + "format": "date-time" + }, + "feedback": { + "$ref": "#/components/schemas/TaskFeedbackResponse" + } + }, + "description": "任务完成详情响应" + }, + "UpdateProfileRequest": { + "type": "object", + "properties": { + "name": { + "pattern": "^[一-龥a-zA-Z\\s]{2,20}$", + "type": "string", + "description": "姓名", + "example": "张三" + }, + "phone": { + "pattern": "^1[3-9]\\d{9}$", + "type": "string", + "description": "手机号", + "example": "13800138000" + }, + "email": { + "type": "string", + "description": "邮箱", + "example": "zhangsan@example.com" + } + }, + "description": "修改个人信息请求" + }, + "ResultUpdateProfileResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "data": { + "$ref": "#/components/schemas/UpdateProfileResponse" + } + } + }, + "UpdateProfileResponse": { + "type": "object", + "properties": { + "userInfo": { + "$ref": "#/components/schemas/UserInfoResponse" + }, + "token": { + "type": "string", + "description": "新的 Token(用于刷新)", + "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + } + }, + "description": "修改个人信息响应" + }, + "UserInfoResponse": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "用户 ID", + "format": "int64" + }, + "username": { + "type": "string", + "description": "用户名" + }, + "name": { + "type": "string", + "description": "姓名" + }, + "email": { + "type": "string", + "description": "邮箱" + }, + "phone": { + "type": "string", + "description": "电话" + }, + "avatarUrl": { + "type": "string", + "description": "头像 URL" + }, + "role": { + "type": "string", + "description": "用户角色" + }, + "tenantId": { + "type": "integer", + "description": "租户 ID", + "format": "int64" + } + }, + "description": "用户信息响应" + }, "ThemeCreateRequest": { "required": [ "name" @@ -26744,14 +28175,14 @@ "type": "string", "description": "状态" }, - "packageType": { - "type": "string", - "description": "套餐类型" - }, - "collectionId": { - "type": "integer", + "collectionIds": { + "type": "array", "description": "课程套餐ID(用于三层架构)", - "format": "int64" + "items": { + "type": "integer", + "description": "课程套餐ID(用于三层架构)", + "format": "int64" + } }, "teacherQuota": { "type": "integer", @@ -26850,9 +28281,13 @@ "description": "最大教师数", "format": "int32" }, - "packageType": { - "type": "string", - "description": "套餐类型" + "packageNames": { + "type": "array", + "description": "套餐名称列表", + "items": { + "type": "string", + "description": "套餐名称列表" + } }, "teacherQuota": { "type": "integer", @@ -27549,12 +28984,20 @@ "description": "评估数据" }, "gradeTags": { - "type": "string", - "description": "年级标签" + "type": "array", + "description": "年级标签(规范为数组,与套餐管理适用年级对齐)", + "items": { + "type": "string", + "description": "年级标签(规范为数组,与套餐管理适用年级对齐)" + } }, "domainTags": { - "type": "string", - "description": "领域标签" + "type": "array", + "description": "领域标签(规范为数组)", + "items": { + "type": "string", + "description": "领域标签(规范为数组)" + } }, "hasCollectiveLesson": { "type": "integer", @@ -27643,6 +29086,13 @@ "items": { "$ref": "#/components/schemas/CourseLessonResponse" } + }, + "lessonTags": { + "type": "array", + "description": "课程环节标签(列表展示用,仅 name 和 lessonType)", + "items": { + "$ref": "#/components/schemas/LessonTagResponse" + } } }, "description": "课程响应" @@ -27699,6 +29149,20 @@ }, "description": "教学环节响应" }, + "LessonTagResponse": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "环节名称" + }, + "lessonType": { + "type": "string", + "description": "环节类型:INTRODUCTION、COLLECTIVE、LANGUAGE、HEALTH、SCIENCE、SOCIAL、ART" + } + }, + "description": "课程环节标签" + }, "ResultCourseResponse": { "type": "object", "properties": { @@ -28062,6 +29526,10 @@ "type": "string", "description": "任务类型:reading-阅读,homework-作业,activity-活动" }, + "relatedBookName": { + "type": "string", + "description": "关联绘本名称(手动填写)" + }, "courseId": { "type": "integer", "description": "课程 ID", @@ -28198,6 +29666,10 @@ "type": "string", "description": "任务类型" }, + "relatedBookName": { + "type": "string", + "description": "关联绘本名称" + }, "courseId": { "type": "integer", "description": "课程 ID", @@ -28703,13 +30175,14 @@ "required": [ "name", "password", + "phone", "username" ], "type": "object", "properties": { "username": { "type": "string", - "description": "用户名" + "description": "用户名/登录账号" }, "password": { "type": "string", @@ -28734,10 +30207,37 @@ "bio": { "type": "string", "description": "简介" + }, + "classIds": { + "type": "array", + "description": "负责班级ID列表", + "items": { + "type": "integer", + "description": "负责班级ID列表", + "format": "int64" + } } }, "description": "教师创建请求" }, + "ResultMapStringString": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "data": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, "StudentCreateRequest": { "required": [ "name" @@ -29174,10 +30674,6 @@ "type": "string", "description": "Logo URL" }, - "packageType": { - "type": "string", - "description": "套餐类型" - }, "teacherQuota": { "type": "integer", "description": "教师配额", @@ -29198,10 +30694,14 @@ "description": "结束日期", "format": "date" }, - "collectionId": { - "type": "integer", - "description": "课程套餐 ID(可选)", - "format": "int64" + "collectionIds": { + "type": "array", + "description": "课程套餐 ID 列表(可选,支持多选)", + "items": { + "type": "integer", + "description": "课程套餐 ID 列表(可选,支持多选)", + "format": "int64" + } } }, "description": "租户创建请求" @@ -29457,32 +30957,73 @@ }, "description": "课程套餐审核驳回请求" }, - "Lesson": { + "ResultTeacherWeeklyStatsResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "data": { + "$ref": "#/components/schemas/TeacherWeeklyStatsResponse" + } + } + }, + "TeacherWeeklyStatsResponse": { + "type": "object", + "properties": { + "lessonCount": { + "type": "integer", + "description": "本周课时数量", + "format": "int64" + }, + "studentParticipation": { + "type": "integer", + "description": "学生参与率", + "format": "int32" + }, + "avgRating": { + "type": "number", + "description": "平均评分", + "format": "double" + }, + "totalDuration": { + "type": "integer", + "description": "总时长(分钟)", + "format": "int32" + } + }, + "description": "教师本周统计响应" + }, + "ResultListTeacherLessonVO": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TeacherLessonVO" + } + } + } + }, + "TeacherLessonVO": { "type": "object", "properties": { "id": { "type": "integer", - "description": "主键 ID", + "description": "ID", "format": "int64" }, - "createBy": { - "type": "string", - "description": "创建人" - }, - "createdAt": { - "type": "string", - "description": "创建时间", - "format": "date-time" - }, - "updateBy": { - "type": "string", - "description": "更新人" - }, - "updatedAt": { - "type": "string", - "description": "更新时间", - "format": "date-time" - }, "tenantId": { "type": "integer", "description": "租户 ID", @@ -29498,23 +31039,26 @@ "description": "班级 ID", "format": "int64" }, + "courseName": { + "type": "string", + "description": "课程名称" + }, + "className": { + "type": "string", + "description": "班级名称" + }, "teacherId": { "type": "integer", "description": "教师 ID", "format": "int64" }, - "schedulePlanId": { - "type": "integer", - "description": "排课计划 ID", - "format": "int64" - }, "title": { "type": "string", - "description": "课程标题" + "description": "标题" }, "lessonDate": { "type": "string", - "description": "上课日期", + "description": "课时日期", "format": "date" }, "startTime": { @@ -29523,92 +31067,30 @@ "endTime": { "$ref": "#/components/schemas/LocalTime" }, - "plannedDatetime": { - "type": "string", - "description": "计划上课时间", - "format": "date-time" - }, - "startDatetime": { - "type": "string", - "description": "实际上课开始时间", - "format": "date-time" - }, - "endDatetime": { - "type": "string", - "description": "实际上课结束时间", - "format": "date-time" - }, - "actualDuration": { - "type": "integer", - "description": "实际时长 (分钟)", - "format": "int32" - }, "location": { "type": "string", - "description": "上课地点" + "description": "地点" }, "status": { "type": "string", "description": "状态" }, - "overallRating": { - "type": "string", - "description": "整体评价" - }, - "participationRating": { - "type": "string", - "description": "参与度评价" - }, - "completionNote": { - "type": "string", - "description": "完成说明" - }, - "progressData": { - "type": "string", - "description": "进度数据 (JSON)" - }, - "currentLessonId": { - "type": "integer", - "description": "当前课程 ID", - "format": "int32" - }, - "currentStepId": { - "type": "integer", - "description": "当前步骤 ID", - "format": "int32" - }, - "lessonIds": { - "type": "string", - "description": "课程 ID 列表 (JSON)" - }, - "completedLessonIds": { - "type": "string", - "description": "已完成课程 ID 列表 (JSON)" - }, "notes": { "type": "string", "description": "备注" + }, + "createdAt": { + "type": "string", + "description": "创建时间", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "description": "更新时间", + "format": "date-time" } }, - "description": "课程实体" - }, - "ResultListLesson": { - "type": "object", - "properties": { - "code": { - "type": "integer", - "format": "int32" - }, - "message": { - "type": "string" - }, - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Lesson" - } - } - } + "description": "教师课程视图对象" }, "PageResultTaskResponse": { "type": "object", @@ -29652,6 +31134,48 @@ } } }, + "PageResultTaskCompletionDetailResponse": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskCompletionDetailResponse" + } + }, + "total": { + "type": "integer", + "format": "int64" + }, + "pageNum": { + "type": "integer", + "format": "int64" + }, + "pageSize": { + "type": "integer", + "format": "int64" + }, + "pages": { + "type": "integer", + "format": "int64" + } + } + }, + "ResultPageResultTaskCompletionDetailResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "data": { + "$ref": "#/components/schemas/PageResultTaskCompletionDetailResponse" + } + } + }, "PageResultTaskTemplateResponse": { "type": "object", "properties": { @@ -29819,266 +31343,54 @@ }, "description": "课表响应" }, - "CoursePackage": { + "CoursePackageVO": { "type": "object", "properties": { "id": { "type": "integer", - "description": "主键 ID", - "format": "int64" - }, - "createBy": { - "type": "string", - "description": "创建人" - }, - "createdAt": { - "type": "string", - "description": "创建时间", - "format": "date-time" - }, - "updateBy": { - "type": "string", - "description": "更新人" - }, - "updatedAt": { - "type": "string", - "description": "更新时间", - "format": "date-time" - }, - "tenantId": { - "type": "integer", - "description": "租户 ID", + "description": "ID", "format": "int64" }, "name": { "type": "string", - "description": "课程名称" - }, - "code": { - "type": "string", - "description": "课程编码" + "description": "名称" }, "description": { "type": "string", - "description": "课程描述" + "description": "描述" }, - "coverUrl": { + "gradeLevel": { "type": "string", - "description": "封面 URL" + "description": "适用年级" }, - "category": { - "type": "string", - "description": "课程类别" - }, - "ageRange": { - "type": "string", - "description": "适用年龄范围" - }, - "difficultyLevel": { - "type": "string", - "description": "难度等级" - }, - "durationMinutes": { + "courseCount": { "type": "integer", - "description": "课程时长(分钟)", + "description": "课程数量", "format": "int32" }, - "objectives": { - "type": "string", - "description": "课程目标" - }, "status": { "type": "string", "description": "状态" }, - "isSystem": { - "type": "integer", - "description": "是否系统课程", - "format": "int32" - }, - "coreContent": { - "type": "string", - "description": "核心内容" - }, - "introSummary": { - "type": "string", - "description": "课程介绍 - 概要" - }, - "introHighlights": { - "type": "string", - "description": "课程介绍 - 亮点" - }, - "introGoals": { - "type": "string", - "description": "课程介绍 - 目标" - }, - "introSchedule": { - "type": "string", - "description": "课程介绍 - 进度安排" - }, - "introKeyPoints": { - "type": "string", - "description": "课程介绍 - 重点" - }, - "introMethods": { - "type": "string", - "description": "课程介绍 - 方法" - }, - "introEvaluation": { - "type": "string", - "description": "课程介绍 - 评估" - }, - "introNotes": { - "type": "string", - "description": "课程介绍 - 注意事项" - }, - "scheduleRefData": { - "type": "string", - "description": "进度计划参考数据(JSON)" - }, - "environmentConstruction": { - "type": "string", - "description": "环境创设(步骤 7)" - }, - "themeId": { - "type": "integer", - "description": "主题 ID", - "format": "int64" - }, - "pictureBookName": { - "type": "string", - "description": "绘本名称" - }, - "coverImagePath": { - "type": "string", - "description": "封面图片路径" - }, - "ebookPaths": { - "type": "string", - "description": "电子绘本路径(JSON 数组)" - }, - "audioPaths": { - "type": "string", - "description": "音频资源路径(JSON 数组)" - }, - "videoPaths": { - "type": "string", - "description": "视频资源路径(JSON 数组)" - }, - "otherResources": { - "type": "string", - "description": "其他资源(JSON 数组)" - }, - "pptPath": { - "type": "string", - "description": "PPT 课件路径" - }, - "pptName": { - "type": "string", - "description": "PPT 课件名称" - }, - "posterPaths": { - "type": "string", - "description": "海报图片路径" - }, - "tools": { - "type": "string", - "description": "教学工具" - }, - "studentMaterials": { - "type": "string", - "description": "学生材料" - }, - "lessonPlanData": { - "type": "string", - "description": "教案数据(JSON)" - }, - "activitiesData": { - "type": "string", - "description": "活动数据(JSON)" - }, - "assessmentData": { - "type": "string", - "description": "评估数据(JSON)" - }, - "gradeTags": { - "type": "string", - "description": "年级标签(JSON 数组)" - }, - "domainTags": { - "type": "string", - "description": "领域标签(JSON 数组)" - }, - "hasCollectiveLesson": { - "type": "integer", - "description": "是否有集体课", - "format": "int32" - }, - "version": { - "type": "string", - "description": "版本号" - }, - "parentId": { - "type": "integer", - "description": "父版本 ID", - "format": "int64" - }, - "isLatest": { - "type": "integer", - "description": "是否最新版本", - "format": "int32" - }, - "submittedAt": { - "type": "string", - "description": "提交时间", - "format": "date-time" - }, - "submittedBy": { - "type": "integer", - "description": "提交人 ID", - "format": "int64" - }, - "reviewedAt": { - "type": "string", - "description": "审核时间", - "format": "date-time" - }, - "reviewedBy": { - "type": "integer", - "description": "审核人 ID", - "format": "int64" - }, - "reviewComment": { - "type": "string", - "description": "审核意见" - }, - "reviewChecklist": { - "type": "string", - "description": "审核检查清单" - }, - "publishedAt": { - "type": "string", - "description": "发布时间", - "format": "date-time" - }, "usageCount": { "type": "integer", "description": "使用次数", "format": "int32" }, - "teacherCount": { - "type": "integer", - "description": "教师数量", - "format": "int32" + "createdAt": { + "type": "string", + "description": "创建时间", + "format": "date-time" }, - "avgRating": { - "type": "number", - "description": "平均评分" + "updatedAt": { + "type": "string", + "description": "更新时间", + "format": "date-time" } }, - "description": "课程包实体(通过7步创建流程创建)" + "description": "课程包视图对象" }, - "ResultListCoursePackage": { + "ResultListCoursePackageVO": { "type": "object", "properties": { "code": { @@ -30091,7 +31403,7 @@ "data": { "type": "array", "items": { - "$ref": "#/components/schemas/CoursePackage" + "$ref": "#/components/schemas/CoursePackageVO" } } } @@ -30294,6 +31606,10 @@ "course": { "$ref": "#/components/schemas/CourseResponse" }, + "lessonType": { + "type": "string", + "description": "排课选择的课程类型(子课程模式时用于直接进入对应子课程)" + }, "class": { "$ref": "#/components/schemas/ClassResponse" } @@ -30333,7 +31649,7 @@ } } }, - "ResultListMapStringObject": { + "ResultListTeacherLessonTrendVO": { "type": "object", "properties": { "code": { @@ -30346,14 +31662,31 @@ "data": { "type": "array", "items": { - "type": "object", - "additionalProperties": { - "type": "object" - } + "$ref": "#/components/schemas/TeacherLessonTrendVO" } } } }, + "TeacherLessonTrendVO": { + "type": "object", + "properties": { + "month": { + "type": "string", + "description": "月份(yyyy-MM 格式)" + }, + "lessonCount": { + "type": "integer", + "description": "课时数量", + "format": "int64" + }, + "avgRating": { + "type": "number", + "description": "平均评分", + "format": "double" + } + }, + "description": "教师课程趋势视图对象" + }, "PageResultGrowthRecord": { "type": "object", "properties": { @@ -30513,6 +31846,81 @@ } } }, + "ResultTeacherDashboardResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "data": { + "$ref": "#/components/schemas/TeacherDashboardResponse" + } + } + }, + "TeacherDashboardResponse": { + "type": "object", + "properties": { + "stats": { + "$ref": "#/components/schemas/TeacherStats" + }, + "todayLessons": { + "type": "array", + "description": "今日课程", + "items": { + "$ref": "#/components/schemas/TeacherLessonVO" + } + }, + "recommendedCourses": { + "type": "array", + "description": "推荐课程", + "items": { + "$ref": "#/components/schemas/CoursePackageVO" + } + }, + "weeklyStats": { + "$ref": "#/components/schemas/TeacherWeeklyStatsResponse" + }, + "recentActivities": { + "type": "array", + "description": "近期活动", + "items": { + "type": "object", + "description": "近期活动" + } + } + }, + "description": "教师仪表盘响应" + }, + "TeacherStats": { + "type": "object", + "properties": { + "classCount": { + "type": "integer", + "description": "班级数量", + "format": "int64" + }, + "studentCount": { + "type": "integer", + "description": "学生数量", + "format": "int64" + }, + "courseCount": { + "type": "integer", + "description": "课程包数量", + "format": "int64" + }, + "lessonCount": { + "type": "integer", + "description": "课时数量", + "format": "int64" + } + }, + "description": "基础统计" + }, "PageResultCourseResponse": { "type": "object", "properties": { @@ -30573,6 +31981,39 @@ } } }, + "CourseUsageVO": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "课程包名称" + }, + "value": { + "type": "integer", + "description": "使用次数", + "format": "int32" + } + }, + "description": "课程使用统计视图对象" + }, + "ResultListCourseUsageVO": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CourseUsageVO" + } + } + } + }, "ResultListClassResponse": { "type": "object", "properties": { @@ -30651,7 +32092,7 @@ } } }, - "ResultTimetableResponse": { + "ResultListMapStringObject": { "type": "object", "properties": { "code": { @@ -30662,7 +32103,13 @@ "type": "string" }, "data": { - "$ref": "#/components/schemas/TimetableResponse" + "type": "array", + "items": { + "type": "object", + "additionalProperties": { + "type": "object" + } + } } } }, @@ -30746,6 +32193,10 @@ "type": "string", "description": "课程包名称" }, + "lessonType": { + "type": "string", + "description": "课程类型代码 (如 DOMAIN_HEALTH)" + }, "lessonTypeName": { "type": "string", "description": "课程类型名称" @@ -31002,6 +32453,21 @@ } } }, + "ResultObject": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "data": { + "type": "object" + } + } + }, "PageResultParentResponse": { "type": "object", "properties": { @@ -31535,7 +33001,34 @@ } } }, - "ResultCoursePackage": { + "PageResultSchoolCourseResponse": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SchoolCourseResponse" + } + }, + "total": { + "type": "integer", + "format": "int64" + }, + "pageNum": { + "type": "integer", + "format": "int64" + }, + "pageSize": { + "type": "integer", + "format": "int64" + }, + "pages": { + "type": "integer", + "format": "int64" + } + } + }, + "ResultPageResultSchoolCourseResponse": { "type": "object", "properties": { "code": { @@ -31546,7 +33039,118 @@ "type": "string" }, "data": { - "$ref": "#/components/schemas/CoursePackage" + "$ref": "#/components/schemas/PageResultSchoolCourseResponse" + } + } + }, + "SchoolCourseResponse": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID", + "format": "int64" + }, + "tenantId": { + "type": "integer", + "description": "租户 ID", + "format": "int64" + }, + "name": { + "type": "string", + "description": "课程名称" + }, + "code": { + "type": "string", + "description": "课程编码" + }, + "description": { + "type": "string", + "description": "描述" + }, + "pictureBookName": { + "type": "string", + "description": "绘本名称" + }, + "coverImagePath": { + "type": "string", + "description": "封面图片路径" + }, + "coverUrl": { + "type": "string", + "description": "封面 URL" + }, + "gradeTags": { + "type": "array", + "description": "年级标签(规范为数组)", + "items": { + "type": "string", + "description": "年级标签(规范为数组)" + } + }, + "domainTags": { + "type": "array", + "description": "领域标签(规范为数组)", + "items": { + "type": "string", + "description": "领域标签(规范为数组)" + } + }, + "duration": { + "type": "integer", + "description": "课程时长(分钟)", + "format": "int32" + }, + "usageCount": { + "type": "integer", + "description": "使用次数", + "format": "int32" + }, + "teacherCount": { + "type": "integer", + "description": "教师数量", + "format": "int32" + }, + "avgRating": { + "type": "number", + "description": "平均评分" + }, + "status": { + "type": "string", + "description": "状态" + }, + "createdAt": { + "type": "string", + "description": "创建时间", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "description": "更新时间", + "format": "date-time" + }, + "lessonTags": { + "type": "array", + "description": "课程环节标签(列表展示用,仅 name 和 lessonType)", + "items": { + "$ref": "#/components/schemas/LessonTagResponse" + } + } + }, + "description": "学校端课程响应" + }, + "ResultSchoolCourseResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "data": { + "$ref": "#/components/schemas/SchoolCourseResponse" } } }, @@ -31794,21 +33398,6 @@ } } }, - "ResultObject": { - "type": "object", - "properties": { - "code": { - "type": "integer", - "format": "int32" - }, - "message": { - "type": "string" - }, - "data": { - "type": "object" - } - } - }, "OssTokenVo": { "type": "object", "properties": { @@ -31866,46 +33455,6 @@ } } }, - "UserInfoResponse": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "description": "用户 ID", - "format": "int64" - }, - "username": { - "type": "string", - "description": "用户名" - }, - "name": { - "type": "string", - "description": "姓名" - }, - "email": { - "type": "string", - "description": "邮箱" - }, - "phone": { - "type": "string", - "description": "电话" - }, - "avatarUrl": { - "type": "string", - "description": "头像 URL" - }, - "role": { - "type": "string", - "description": "用户角色" - }, - "tenantId": { - "type": "integer", - "description": "租户 ID", - "format": "int64" - } - }, - "description": "用户信息响应" - }, "ResultListThemeResponse": { "type": "object", "properties": { @@ -32031,6 +33580,11 @@ "type": "integer", "description": "课时总数", "format": "int64" + }, + "monthlyLessons": { + "type": "integer", + "description": "月授课次数(本月 COMPLETED 状态的 lesson 总数)", + "format": "int64" } }, "description": "统计数据响应" @@ -32061,30 +33615,21 @@ "description": "日期列表" } }, - "newStudents": { + "lessonCounts": { "type": "array", - "description": "新增学生数列表", + "description": "授课次数列表(近 7 天每天完成的课程数)", "items": { "type": "integer", - "description": "新增学生数列表", + "description": "授课次数列表(近 7 天每天完成的课程数)", "format": "int32" } }, - "newTeachers": { + "studentCounts": { "type": "array", - "description": "新增教师数列表", + "description": "活跃学生数列表(近 7 天每天有上课记录的去重学生数)", "items": { "type": "integer", - "description": "新增教师数列表", - "format": "int32" - } - }, - "newCourses": { - "type": "array", - "description": "新增课程数列表", - "items": { - "type": "integer", - "description": "新增课程数列表", + "description": "活跃学生数列表(近 7 天每天有上课记录的去重学生数)", "format": "int32" } } @@ -32115,14 +33660,14 @@ "type": "string", "description": "租户名称" }, - "activeUsers": { + "activeTeacherCount": { "type": "integer", - "description": "活跃用户数", + "description": "活跃教师数(近 30 天有完成课程的老师数)", "format": "int32" }, - "courseCount": { + "completedLessonCount": { "type": "integer", - "description": "课程使用数", + "description": "完成课次数(近 30 天 COMPLETED 状态的 lesson 总数)", "format": "int32" } }, @@ -32375,6 +33920,10 @@ "type": "string", "description": "状态" }, + "gradeTags": { + "type": "string", + "description": "年级(支持多个,逗号分隔)" + }, "reviewOnly": { "type": "boolean", "description": "是否仅查询待审核", @@ -32437,6 +33986,10 @@ "status": { "type": "string", "description": "状态" + }, + "gradeLevels": { + "type": "string", + "description": "年级(支持多个,逗号分隔)" } }, "description": "课程套餐分页查询请求" diff --git a/reading-platform-frontend/src/api/admin.ts b/reading-platform-frontend/src/api/admin.ts index db1eaf0..7b444ed 100644 --- a/reading-platform-frontend/src/api/admin.ts +++ b/reading-platform-frontend/src/api/admin.ts @@ -107,6 +107,7 @@ export interface AdminStatsResponse { totalStudents: number; totalTeachers: number; totalLessons: number; + monthlyLessons: number; } // 前端使用的统计数据结构 @@ -124,9 +125,8 @@ export interface AdminStats { // 后端返回的趋势数据结构(分离数组格式) export interface StatsTrendResponse { dates: string[]; - newStudents: number[]; - newTeachers: number[]; - newCourses: number[]; + lessonCounts: number[]; + studentCounts: number[]; } // 前端使用的趋势数据结构 @@ -141,17 +141,16 @@ export interface TrendData { export interface ActiveTenantResponse { tenantId: number; tenantName: string; - activeUsers: number; - courseCount: number; + activeTeacherCount: number; + completedLessonCount: number; } // 前端使用的活跃租户结构 export interface ActiveTenant { id: number; name: string; - lessonCount: number; - teacherCount: number | string; - studentCount: number | string; + activeTeacherCount: number; + completedLessonCount: number; } // 后端返回的热门课程结构 @@ -273,7 +272,7 @@ const mapStatsData = (data: AdminStatsResponse): AdminStats => ({ teacherCount: data.totalTeachers || 0, lessonCount: data.totalLessons || 0, publishedCourseCount: 0, // 后端暂无此数据 - monthlyLessons: 0, // 后端暂无此数据 + monthlyLessons: data.monthlyLessons || 0, }); // 趋势数据映射:分离数组 -> 对象数组 @@ -282,8 +281,8 @@ const mapTrendData = (data: StatsTrendResponse): TrendData[] => { return data.dates.map((date, index) => ({ month: date, tenantCount: 0, // 后端无此数据 - lessonCount: data.newCourses?.[index] || 0, - studentCount: data.newStudents?.[index] || 0, + lessonCount: data.lessonCounts?.[index] || 0, + studentCount: data.studentCounts?.[index] || 0, })); }; @@ -293,9 +292,8 @@ const mapActiveTenants = (data: ActiveTenantResponse[]): ActiveTenant[] => { return data.map(item => ({ id: item.tenantId, name: item.tenantName, - teacherCount: '-', // 后端无单独字段 - studentCount: '-', // 后端无单独字段 - lessonCount: item.courseCount, // 使用 courseCount 替代 + activeTeacherCount: item.activeTeacherCount ?? 0, + completedLessonCount: item.completedLessonCount ?? 0, })); }; diff --git a/reading-platform-frontend/src/api/auth.ts b/reading-platform-frontend/src/api/auth.ts index fc89bdd..6dd37f8 100644 --- a/reading-platform-frontend/src/api/auth.ts +++ b/reading-platform-frontend/src/api/auth.ts @@ -55,3 +55,26 @@ export function refreshToken(): Promise<{ token: string }> { export function getProfile(): Promise { return http.get('/v1/auth/profile'); } + +// 修改个人信息 +export interface UpdateProfileDto { + name?: string; + phone?: string; + email?: string; +} + +export interface UpdateProfileResponse { + userInfo: UserProfile; + token: string; +} + +export function updateProfile(data: UpdateProfileDto): Promise { + return http.put('/v1/auth/profile', data); +} + +// 修改密码(修改成功后 token 失效,需重新登录) +export function changePassword(oldPassword: string, newPassword: string): Promise { + return http.post('/v1/auth/change-password', null, { + params: { oldPassword, newPassword }, + }); +} diff --git a/reading-platform-frontend/src/api/generated/index.ts b/reading-platform-frontend/src/api/generated/index.ts index 921d6e7..805dc0d 100644 --- a/reading-platform-frontend/src/api/generated/index.ts +++ b/reading-platform-frontend/src/api/generated/index.ts @@ -29,6 +29,7 @@ import type { GenerateReadOnlyTokenParams, GetActiveTeachersParams, GetActiveTenantsParams, + GetAllCoursesParams, GetAllStudentsParams, GetCalendarViewDataParams, GetClassPageParams, @@ -57,8 +58,11 @@ import type { GetSchedules1Params, GetSchedulesParams, GetSchoolCoursesParams, + GetStatisticsParams, GetStudentPageParams, - GetTaskPage1Params, + GetTaskCompletions1Params, + GetTaskCompletionsParams, + GetTaskListParams, GetTaskPageParams, GetTasksByStudentParams, GetTeacherPageParams, @@ -82,7 +86,6 @@ import type { RefreshTokenRequest, RenewRequest, ResetPassword1Params, - ResetPasswordParams, ResourceItemCreateRequest, ResourceItemUpdateRequest, ResourceLibraryCreateRequest, @@ -96,6 +99,8 @@ import type { StudentRecordRequest, StudentUpdateRequest, TaskCreateRequest, + TaskFeedbackRequest, + TaskSubmitRequest, TaskTemplateCreateRequest, TaskUpdateRequest, TeacherCreateRequest, @@ -106,6 +111,7 @@ import type { UpdateBasicSettings1Body, UpdateClassTeacherBody, UpdateNotificationSettings1Body, + UpdateProfileRequest, UpdateSecuritySettings1Body, UpdateSettings1Body, UpdateStorageSettingsBody, @@ -162,6 +168,38 @@ const deleteTask = ( ); } +/** + * @summary 修改评价 + */ +const updateFeedback = ( + completionId: number, + taskFeedbackRequest: TaskFeedbackRequest, + ) => { + return customMutator( + {url: `/v1/teacher/tasks/completions/${completionId}/feedback`, method: 'PUT', + headers: {'Content-Type': 'application/json', }, + data: taskFeedbackRequest, + responseType: 'blob' + }, + ); + } + +/** + * @summary 提交评价 + */ +const submitFeedback = ( + completionId: number, + taskFeedbackRequest: TaskFeedbackRequest, + ) => { + return customMutator( + {url: `/v1/teacher/tasks/completions/${completionId}/feedback`, method: 'POST', + headers: {'Content-Type': 'application/json', }, + data: taskFeedbackRequest, + responseType: 'blob' + }, + ); + } + /** * @summary 获取模板详情 */ @@ -388,48 +426,6 @@ const deleteTeacher = ( ); } -/** - * @summary Get task by ID - */ -const getTask1 = ( - id: number, - ) => { - return customMutator( - {url: `/v1/school/tasks/${id}`, method: 'GET', - responseType: 'blob' - }, - ); - } - -/** - * @summary Update task - */ -const updateTask1 = ( - id: number, - taskUpdateRequest: TaskUpdateRequest, - ) => { - return customMutator( - {url: `/v1/school/tasks/${id}`, method: 'PUT', - headers: {'Content-Type': 'application/json', }, - data: taskUpdateRequest, - responseType: 'blob' - }, - ); - } - -/** - * @summary Delete task - */ -const deleteTask1 = ( - id: number, - ) => { - return customMutator( - {url: `/v1/school/tasks/${id}`, method: 'DELETE', - responseType: 'blob' - }, - ); - } - /** * @summary 获取模板详情 */ @@ -825,6 +821,38 @@ const removeClassTeacher = ( ); } +/** + * @summary 修改任务提交 + */ +const updateSubmission = ( + taskId: number, + taskSubmitRequest: TaskSubmitRequest, + ) => { + return customMutator( + {url: `/v1/parent/tasks/${taskId}/submit`, method: 'PUT', + headers: {'Content-Type': 'application/json', }, + data: taskSubmitRequest, + responseType: 'blob' + }, + ); + } + +/** + * @summary 提交任务完成 + */ +const submitTask = ( + taskId: number, + taskSubmitRequest: TaskSubmitRequest, + ) => { + return customMutator( + {url: `/v1/parent/tasks/${taskId}/submit`, method: 'POST', + headers: {'Content-Type': 'application/json', }, + data: taskSubmitRequest, + responseType: 'blob' + }, + ); + } + /** * @summary Get growth record by ID */ @@ -867,6 +895,34 @@ const deleteGrowthRecord2 = ( ); } +/** + * @summary 获取当前用户信息 + */ +const getCurrentUser = ( + + ) => { + return customMutator( + {url: `/v1/auth/profile`, method: 'GET', + responseType: 'blob' + }, + ); + } + +/** + * @summary 修改个人信息 + */ +const updateProfile = ( + updateProfileRequest: UpdateProfileRequest, + ) => { + return customMutator( + {url: `/v1/auth/profile`, method: 'PUT', + headers: {'Content-Type': 'application/json', }, + data: updateProfileRequest, + responseType: 'blob' + }, + ); + } + /** * @summary 查询主题详情 */ @@ -1650,7 +1706,7 @@ const getLessonFeedback = ( /** * @summary 提交课时反馈 */ -const submitFeedback = ( +const submitFeedback1 = ( id: number, lessonFeedbackRequest: LessonFeedbackRequest, ) => { @@ -1778,40 +1834,9 @@ const createTeacher = ( */ const resetPassword = ( id: number, - params: ResetPasswordParams, ) => { return customMutator( {url: `/v1/school/teachers/${id}/reset-password`, method: 'POST', - params, - responseType: 'blob' - }, - ); - } - -/** - * @summary Get task page - */ -const getTaskPage1 = ( - params?: GetTaskPage1Params, - ) => { - return customMutator( - {url: `/v1/school/tasks`, method: 'GET', - params, - responseType: 'blob' - }, - ); - } - -/** - * @summary Create task - */ -const createTask1 = ( - taskCreateRequest: TaskCreateRequest, - ) => { - return customMutator( - {url: `/v1/school/tasks`, method: 'POST', - headers: {'Content-Type': 'application/json', }, - data: taskCreateRequest, responseType: 'blob' }, ); @@ -2157,7 +2182,7 @@ const assignStudents = ( } /** - * @summary Complete task + * @summary 完成任务(旧接口,兼容使用) */ const completeTask = ( id: number, @@ -2723,6 +2748,34 @@ const getTodayLessons = ( ); } +/** + * @summary 获取任务完成情况列表 + */ +const getTaskCompletions = ( + taskId: number, + params?: GetTaskCompletionsParams, + ) => { + return customMutator( + {url: `/v1/teacher/tasks/${taskId}/completions`, method: 'GET', + params, + responseType: 'blob' + }, + ); + } + +/** + * @summary 获取提交详情 + */ +const getCompletionDetail = ( + completionId: number, + ) => { + return customMutator( + {url: `/v1/teacher/tasks/completions/${completionId}`, method: 'GET', + responseType: 'blob' + }, + ); + } + /** * @summary 获取默认模板 */ @@ -2941,10 +2994,11 @@ const getCourse = ( * @summary 获取所有课程 */ const getAllCourses = ( - + params?: GetAllCoursesParams, ) => { return customMutator( {url: `/v1/teacher/courses/all`, method: 'GET', + params, responseType: 'blob' }, ); @@ -3191,6 +3245,75 @@ const getCourseReports = ( ); } +/** + * @summary 获取任务列表(支持多维度筛选) + */ +const getTaskList = ( + params?: GetTaskListParams, + ) => { + return customMutator( + {url: `/v1/school/reading-tasks`, method: 'GET', + params, + responseType: 'blob' + }, + ); + } + +/** + * @summary 获取任务详情 + */ +const getTaskDetail = ( + taskId: number, + ) => { + return customMutator( + {url: `/v1/school/reading-tasks/${taskId}`, method: 'GET', + responseType: 'blob' + }, + ); + } + +/** + * @summary 获取任务完成情况列表 + */ +const getTaskCompletions1 = ( + taskId: number, + params?: GetTaskCompletions1Params, + ) => { + return customMutator( + {url: `/v1/school/reading-tasks/${taskId}/completions`, method: 'GET', + params, + responseType: 'blob' + }, + ); + } + +/** + * @summary 获取统计数据 + */ +const getStatistics = ( + params?: GetStatisticsParams, + ) => { + return customMutator( + {url: `/v1/school/reading-tasks/statistics`, method: 'GET', + params, + responseType: 'blob' + }, + ); + } + +/** + * @summary 获取学生提交详情 + */ +const getCompletionDetail1 = ( + completionId: number, + ) => { + return customMutator( + {url: `/v1/school/reading-tasks/completions/${completionId}`, method: 'GET', + responseType: 'blob' + }, + ); + } + /** * @summary Get children of parent */ @@ -3418,7 +3541,7 @@ const getSchoolCourse = ( } /** - * @summary Get my tasks + * @summary 获取我的任务列表 */ const getMyTasks = ( params?: GetMyTasksParams, @@ -3432,9 +3555,9 @@ const getMyTasks = ( } /** - * @summary Get task by ID + * @summary 获取任务详情 */ -const getTask2 = ( +const getTask1 = ( id: number, ) => { return customMutator( @@ -3445,7 +3568,7 @@ const getTask2 = ( } /** - * @summary Get tasks by student ID + * @summary 获取孩子的任务列表 */ const getTasksByStudent = ( studentId: number, @@ -3459,6 +3582,32 @@ const getTasksByStudent = ( ); } +/** + * @summary 获取提交详情 + */ +const getCompletionDetail2 = ( + completionId: number, + ) => { + return customMutator( + {url: `/v1/parent/tasks/completions/${completionId}`, method: 'GET', + responseType: 'blob' + }, + ); + } + +/** + * @summary 获取教师评价 + */ +const getFeedback = ( + completionId: number, + ) => { + return customMutator( + {url: `/v1/parent/tasks/completions/${completionId}/feedback`, method: 'GET', + responseType: 'blob' + }, + ); + } + /** * @summary Get my notifications */ @@ -3612,19 +3761,6 @@ const getOssToken = ( ); } -/** - * @summary 获取当前用户信息 - */ -const getCurrentUser = ( - - ) => { - return customMutator( - {url: `/v1/auth/profile`, method: 'GET', - responseType: 'blob' - }, - ); - } - /** * @summary 获取租户统计信息 */ @@ -3800,10 +3936,12 @@ const deleteFile = ( ); } -return {getTask,updateTask,deleteTask,getTemplate,updateTemplate,deleteTemplate,getSchedule,updateSchedule,cancelSchedule,getLesson,updateLesson,getLessonProgress,saveLessonProgress,getGrowthRecord,updateGrowthRecord,deleteGrowthRecord,getTeacher,updateTeacher,deleteTeacher,getTask1,updateTask1,deleteTask1,getTemplate1,updateTemplate1,deleteTemplate1,getStudent,updateStudent,deleteStudent,getSettings,updateSettings,getSecuritySettings,updateSecuritySettings,getNotificationSettings,updateNotificationSettings,getBasicSettings,updateBasicSettings,getSchedule1,updateSchedule1,cancelSchedule1,getParent,updateParent,deleteParent,getGrowthRecord1,updateGrowthRecord1,deleteGrowthRecord1,getClass,updateClass,deleteClass,updateClassTeacher,removeClassTeacher,getGrowthRecord2,updateGrowthRecord2,deleteGrowthRecord2,findOne,update,_delete,reorder,getTenant,updateTenant,deleteTenant,updateTenantStatus,updateTenantQuota,getAllSettings,updateSettings1,getStorageSettings,updateStorageSettings,getSecuritySettings1,updateSecuritySettings1,getNotificationSettings1,updateNotificationSettings1,getBasicSettings1,updateBasicSettings1,findLibrary,updateLibrary,deleteLibrary,findItem,updateItem,deleteItem,getCourse1,updateCourse,deleteCourse,reorderSteps,findOne1,update1,delete1,updateStep,removeStep,reorder1,findOne2,update2,delete2,setPackages,getTaskPage,createTask,getTemplates,createTemplate,createFromTemplate,getSchedules,createSchedule,markAsRead,markAllAsRead,getMyLessons,createLesson,saveStudentRecord,batchSaveStudentRecords,startLesson,getLessonFeedback,submitFeedback,completeLesson,cancelLesson,createLessonFromSchedule,startLessonFromSchedule,getGrowthRecordPage,createGrowthRecord,getTeacherPage,createTeacher,resetPassword,getTaskPage1,createTask1,getTemplates1,createTemplate1,getStudentPage,createStudent,getSchedules1,createSchedule1,checkConflict,batchCreateSchedules,createSchedulesByClasses,getParentPage,createParent,bindStudent,unbindStudent,resetPassword1,renewCollection,getGrowthRecordPage1,createGrowthRecord1,getClassPage,createClass,getClassTeachers1,assignTeachers,getClassStudents1,assignStudents,completeTask,markAsRead1,markAllAsRead1,createGrowthRecord2,refreshToken,uploadFile,refreshToken1,logout,login,changePassword,findAll,create,getTenantPage,createTenant,resetTenantPassword,findAllLibraries,createLibrary,findAllItems,createItem,batchDeleteItems,getCoursePage1,createCourse,submitCourse,rejectCourse,publishCourse,archiveCourse,findAll1,create1,findSteps,createStep,page,create2,withdraw,submit,republish,reject,publish,archive,getWeeklyStats,getTodayLessons,getDefaultTemplate,getAllStudents,getTodaySchedules,getTimetable,getRecommendedCourses,getMyNotifications,getNotification,getUnreadCount,getStudentRecords,getTodayLessons1,getLessonTrend,getFeedbacks,getFeedbackStats,getDashboard,getCoursePage,getCourse,getAllCourses,getCourseUsage,getClasses,getClassTeachers,getClassStudents,getDefaultTemplate1,getSchoolStats,getActiveTeachers,getLessonTrend1,getCourseUsageStats,getCourseDistribution,getRecentActivities,getTimetable1,getCoursePackageLessonTypes,getCalendarViewData,getTeacherReports,getStudentReports,getOverview,getCourseReports,getParentChildren,findTenantCollections,getPackagesByCollection,getPackageCourses,getPackageInfo,getPackageUsage,getLogList,getLogDetail,getLogStats,getFeedbacks1,getFeedbackStats1,exportTeacherStats,exportStudentStats,exportLessons,exportGrowthRecords,getSchoolCourses,getSchoolCourse,getMyTasks,getTask2,getTasksByStudent,getMyNotifications1,getNotification1,getUnreadCount1,getGrowthRecordsByStudent,getRecentGrowthRecords,getMyChildren,getChild,getChildGrowth,generateEditToken,generateReadOnlyToken,getOssToken,getCurrentUser,getTenantStats,getAllActiveTenants,getStats,getTrendData,getActiveTenants,getPopularCourses,getRecentActivities1,getTenantDefaults,getStats1,getAllPublishedCourses,findByType,getAllPublishedCollections,deleteFile}}; +return {getTask,updateTask,deleteTask,updateFeedback,submitFeedback,getTemplate,updateTemplate,deleteTemplate,getSchedule,updateSchedule,cancelSchedule,getLesson,updateLesson,getLessonProgress,saveLessonProgress,getGrowthRecord,updateGrowthRecord,deleteGrowthRecord,getTeacher,updateTeacher,deleteTeacher,getTemplate1,updateTemplate1,deleteTemplate1,getStudent,updateStudent,deleteStudent,getSettings,updateSettings,getSecuritySettings,updateSecuritySettings,getNotificationSettings,updateNotificationSettings,getBasicSettings,updateBasicSettings,getSchedule1,updateSchedule1,cancelSchedule1,getParent,updateParent,deleteParent,getGrowthRecord1,updateGrowthRecord1,deleteGrowthRecord1,getClass,updateClass,deleteClass,updateClassTeacher,removeClassTeacher,updateSubmission,submitTask,getGrowthRecord2,updateGrowthRecord2,deleteGrowthRecord2,getCurrentUser,updateProfile,findOne,update,_delete,reorder,getTenant,updateTenant,deleteTenant,updateTenantStatus,updateTenantQuota,getAllSettings,updateSettings1,getStorageSettings,updateStorageSettings,getSecuritySettings1,updateSecuritySettings1,getNotificationSettings1,updateNotificationSettings1,getBasicSettings1,updateBasicSettings1,findLibrary,updateLibrary,deleteLibrary,findItem,updateItem,deleteItem,getCourse1,updateCourse,deleteCourse,reorderSteps,findOne1,update1,delete1,updateStep,removeStep,reorder1,findOne2,update2,delete2,setPackages,getTaskPage,createTask,getTemplates,createTemplate,createFromTemplate,getSchedules,createSchedule,markAsRead,markAllAsRead,getMyLessons,createLesson,saveStudentRecord,batchSaveStudentRecords,startLesson,getLessonFeedback,submitFeedback1,completeLesson,cancelLesson,createLessonFromSchedule,startLessonFromSchedule,getGrowthRecordPage,createGrowthRecord,getTeacherPage,createTeacher,resetPassword,getTemplates1,createTemplate1,getStudentPage,createStudent,getSchedules1,createSchedule1,checkConflict,batchCreateSchedules,createSchedulesByClasses,getParentPage,createParent,bindStudent,unbindStudent,resetPassword1,renewCollection,getGrowthRecordPage1,createGrowthRecord1,getClassPage,createClass,getClassTeachers1,assignTeachers,getClassStudents1,assignStudents,completeTask,markAsRead1,markAllAsRead1,createGrowthRecord2,refreshToken,uploadFile,refreshToken1,logout,login,changePassword,findAll,create,getTenantPage,createTenant,resetTenantPassword,findAllLibraries,createLibrary,findAllItems,createItem,batchDeleteItems,getCoursePage1,createCourse,submitCourse,rejectCourse,publishCourse,archiveCourse,findAll1,create1,findSteps,createStep,page,create2,withdraw,submit,republish,reject,publish,archive,getWeeklyStats,getTodayLessons,getTaskCompletions,getCompletionDetail,getDefaultTemplate,getAllStudents,getTodaySchedules,getTimetable,getRecommendedCourses,getMyNotifications,getNotification,getUnreadCount,getStudentRecords,getTodayLessons1,getLessonTrend,getFeedbacks,getFeedbackStats,getDashboard,getCoursePage,getCourse,getAllCourses,getCourseUsage,getClasses,getClassTeachers,getClassStudents,getDefaultTemplate1,getSchoolStats,getActiveTeachers,getLessonTrend1,getCourseUsageStats,getCourseDistribution,getRecentActivities,getTimetable1,getCoursePackageLessonTypes,getCalendarViewData,getTeacherReports,getStudentReports,getOverview,getCourseReports,getTaskList,getTaskDetail,getTaskCompletions1,getStatistics,getCompletionDetail1,getParentChildren,findTenantCollections,getPackagesByCollection,getPackageCourses,getPackageInfo,getPackageUsage,getLogList,getLogDetail,getLogStats,getFeedbacks1,getFeedbackStats1,exportTeacherStats,exportStudentStats,exportLessons,exportGrowthRecords,getSchoolCourses,getSchoolCourse,getMyTasks,getTask1,getTasksByStudent,getCompletionDetail2,getFeedback,getMyNotifications1,getNotification1,getUnreadCount1,getGrowthRecordsByStudent,getRecentGrowthRecords,getMyChildren,getChild,getChildGrowth,generateEditToken,generateReadOnlyToken,getOssToken,getTenantStats,getAllActiveTenants,getStats,getTrendData,getActiveTenants,getPopularCourses,getRecentActivities1,getTenantDefaults,getStats1,getAllPublishedCourses,findByType,getAllPublishedCollections,deleteFile}}; export type GetTaskResult = NonNullable['getTask']>>> export type UpdateTaskResult = NonNullable['updateTask']>>> export type DeleteTaskResult = NonNullable['deleteTask']>>> +export type UpdateFeedbackResult = NonNullable['updateFeedback']>>> +export type SubmitFeedbackResult = NonNullable['submitFeedback']>>> export type GetTemplateResult = NonNullable['getTemplate']>>> export type UpdateTemplateResult = NonNullable['updateTemplate']>>> export type DeleteTemplateResult = NonNullable['deleteTemplate']>>> @@ -3820,9 +3958,6 @@ export type DeleteGrowthRecordResult = NonNullable['getTeacher']>>> export type UpdateTeacherResult = NonNullable['updateTeacher']>>> export type DeleteTeacherResult = NonNullable['deleteTeacher']>>> -export type GetTask1Result = NonNullable['getTask1']>>> -export type UpdateTask1Result = NonNullable['updateTask1']>>> -export type DeleteTask1Result = NonNullable['deleteTask1']>>> export type GetTemplate1Result = NonNullable['getTemplate1']>>> export type UpdateTemplate1Result = NonNullable['updateTemplate1']>>> export type DeleteTemplate1Result = NonNullable['deleteTemplate1']>>> @@ -3851,9 +3986,13 @@ export type UpdateClassResult = NonNullable['deleteClass']>>> export type UpdateClassTeacherResult = NonNullable['updateClassTeacher']>>> export type RemoveClassTeacherResult = NonNullable['removeClassTeacher']>>> +export type UpdateSubmissionResult = NonNullable['updateSubmission']>>> +export type SubmitTaskResult = NonNullable['submitTask']>>> export type GetGrowthRecord2Result = NonNullable['getGrowthRecord2']>>> export type UpdateGrowthRecord2Result = NonNullable['updateGrowthRecord2']>>> export type DeleteGrowthRecord2Result = NonNullable['deleteGrowthRecord2']>>> +export type GetCurrentUserResult = NonNullable['getCurrentUser']>>> +export type UpdateProfileResult = NonNullable['updateProfile']>>> export type FindOneResult = NonNullable['findOne']>>> export type UpdateResult = NonNullable['update']>>> export type _DeleteResult = NonNullable['_delete']>>> @@ -3908,7 +4047,7 @@ export type SaveStudentRecordResult = NonNullable['batchSaveStudentRecords']>>> export type StartLessonResult = NonNullable['startLesson']>>> export type GetLessonFeedbackResult = NonNullable['getLessonFeedback']>>> -export type SubmitFeedbackResult = NonNullable['submitFeedback']>>> +export type SubmitFeedback1Result = NonNullable['submitFeedback1']>>> export type CompleteLessonResult = NonNullable['completeLesson']>>> export type CancelLessonResult = NonNullable['cancelLesson']>>> export type CreateLessonFromScheduleResult = NonNullable['createLessonFromSchedule']>>> @@ -3918,8 +4057,6 @@ export type CreateGrowthRecordResult = NonNullable['getTeacherPage']>>> export type CreateTeacherResult = NonNullable['createTeacher']>>> export type ResetPasswordResult = NonNullable['resetPassword']>>> -export type GetTaskPage1Result = NonNullable['getTaskPage1']>>> -export type CreateTask1Result = NonNullable['createTask1']>>> export type GetTemplates1Result = NonNullable['getTemplates1']>>> export type CreateTemplate1Result = NonNullable['createTemplate1']>>> export type GetStudentPageResult = NonNullable['getStudentPage']>>> @@ -3983,6 +4120,8 @@ export type PublishResult = NonNullable['archive']>>> export type GetWeeklyStatsResult = NonNullable['getWeeklyStats']>>> export type GetTodayLessonsResult = NonNullable['getTodayLessons']>>> +export type GetTaskCompletionsResult = NonNullable['getTaskCompletions']>>> +export type GetCompletionDetailResult = NonNullable['getCompletionDetail']>>> export type GetDefaultTemplateResult = NonNullable['getDefaultTemplate']>>> export type GetAllStudentsResult = NonNullable['getAllStudents']>>> export type GetTodaySchedulesResult = NonNullable['getTodaySchedules']>>> @@ -4018,6 +4157,11 @@ export type GetTeacherReportsResult = NonNullable['getStudentReports']>>> export type GetOverviewResult = NonNullable['getOverview']>>> export type GetCourseReportsResult = NonNullable['getCourseReports']>>> +export type GetTaskListResult = NonNullable['getTaskList']>>> +export type GetTaskDetailResult = NonNullable['getTaskDetail']>>> +export type GetTaskCompletions1Result = NonNullable['getTaskCompletions1']>>> +export type GetStatisticsResult = NonNullable['getStatistics']>>> +export type GetCompletionDetail1Result = NonNullable['getCompletionDetail1']>>> export type GetParentChildrenResult = NonNullable['getParentChildren']>>> export type FindTenantCollectionsResult = NonNullable['findTenantCollections']>>> export type GetPackagesByCollectionResult = NonNullable['getPackagesByCollection']>>> @@ -4036,8 +4180,10 @@ export type ExportGrowthRecordsResult = NonNullable['getSchoolCourses']>>> export type GetSchoolCourseResult = NonNullable['getSchoolCourse']>>> export type GetMyTasksResult = NonNullable['getMyTasks']>>> -export type GetTask2Result = NonNullable['getTask2']>>> +export type GetTask1Result = NonNullable['getTask1']>>> export type GetTasksByStudentResult = NonNullable['getTasksByStudent']>>> +export type GetCompletionDetail2Result = NonNullable['getCompletionDetail2']>>> +export type GetFeedbackResult = NonNullable['getFeedback']>>> export type GetMyNotifications1Result = NonNullable['getMyNotifications1']>>> export type GetNotification1Result = NonNullable['getNotification1']>>> export type GetUnreadCount1Result = NonNullable['getUnreadCount1']>>> @@ -4049,7 +4195,6 @@ export type GetChildGrowthResult = NonNullable['generateEditToken']>>> export type GenerateReadOnlyTokenResult = NonNullable['generateReadOnlyToken']>>> export type GetOssTokenResult = NonNullable['getOssToken']>>> -export type GetCurrentUserResult = NonNullable['getCurrentUser']>>> export type GetTenantStatsResult = NonNullable['getTenantStats']>>> export type GetAllActiveTenantsResult = NonNullable['getAllActiveTenants']>>> export type GetStatsResult = NonNullable['getStats']>>> diff --git a/reading-platform-frontend/src/api/generated/model/activeTenantItemResponse.ts b/reading-platform-frontend/src/api/generated/model/activeTenantItemResponse.ts index 44d197b..cde1ec5 100644 --- a/reading-platform-frontend/src/api/generated/model/activeTenantItemResponse.ts +++ b/reading-platform-frontend/src/api/generated/model/activeTenantItemResponse.ts @@ -14,8 +14,8 @@ export interface ActiveTenantItemResponse { tenantId?: number; /** 租户名称 */ tenantName?: string; - /** 活跃用户数 */ - activeUsers?: number; - /** 课程使用数 */ - courseCount?: number; + /** 活跃教师数(近 30 天有完成课程的老师数) */ + activeTeacherCount?: number; + /** 完成课次数(近 30 天 COMPLETED 状态的 lesson 总数) */ + completedLessonCount?: number; } diff --git a/reading-platform-frontend/src/api/generated/model/classInfo.ts b/reading-platform-frontend/src/api/generated/model/classInfo.ts new file mode 100644 index 0000000..afac5cd --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/classInfo.ts @@ -0,0 +1,19 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ + +/** + * 班级信息 + */ +export interface ClassInfo { + /** 班级ID */ + id?: number; + /** 班级名称 */ + name?: string; + /** 年级 */ + grade?: string; +} diff --git a/reading-platform-frontend/src/api/generated/model/courseCollectionPageQueryRequest.ts b/reading-platform-frontend/src/api/generated/model/courseCollectionPageQueryRequest.ts index a623c65..9b04943 100644 --- a/reading-platform-frontend/src/api/generated/model/courseCollectionPageQueryRequest.ts +++ b/reading-platform-frontend/src/api/generated/model/courseCollectionPageQueryRequest.ts @@ -16,4 +16,6 @@ export interface CourseCollectionPageQueryRequest { pageSize?: number; /** 状态 */ status?: string; + /** 年级(支持多个,逗号分隔) */ + gradeLevels?: string; } diff --git a/reading-platform-frontend/src/api/generated/model/coursePackageVO.ts b/reading-platform-frontend/src/api/generated/model/coursePackageVO.ts new file mode 100644 index 0000000..1e6239f --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/coursePackageVO.ts @@ -0,0 +1,31 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ + +/** + * 课程包视图对象 + */ +export interface CoursePackageVO { + /** ID */ + id?: number; + /** 名称 */ + name?: string; + /** 描述 */ + description?: string; + /** 适用年级 */ + gradeLevel?: string; + /** 课程数量 */ + courseCount?: number; + /** 状态 */ + status?: string; + /** 使用次数 */ + usageCount?: number; + /** 创建时间 */ + createdAt?: string; + /** 更新时间 */ + updatedAt?: string; +} diff --git a/reading-platform-frontend/src/api/generated/model/coursePageQueryRequest.ts b/reading-platform-frontend/src/api/generated/model/coursePageQueryRequest.ts index c7ce177..64e0035 100644 --- a/reading-platform-frontend/src/api/generated/model/coursePageQueryRequest.ts +++ b/reading-platform-frontend/src/api/generated/model/coursePageQueryRequest.ts @@ -20,6 +20,8 @@ export interface CoursePageQueryRequest { category?: string; /** 状态 */ status?: string; + /** 年级(支持多个,逗号分隔) */ + gradeTags?: string; /** 是否仅查询待审核 */ reviewOnly?: boolean; } diff --git a/reading-platform-frontend/src/api/generated/model/courseResponse.ts b/reading-platform-frontend/src/api/generated/model/courseResponse.ts index 592a81d..fe341b3 100644 --- a/reading-platform-frontend/src/api/generated/model/courseResponse.ts +++ b/reading-platform-frontend/src/api/generated/model/courseResponse.ts @@ -6,6 +6,7 @@ * OpenAPI spec version: 1.0.0 */ import type { CourseLessonResponse } from './courseLessonResponse'; +import type { LessonTagResponse } from './lessonTagResponse'; /** * 课程响应 @@ -89,10 +90,10 @@ export interface CourseResponse { activitiesData?: string; /** 评估数据 */ assessmentData?: string; - /** 年级标签 */ - gradeTags?: string; - /** 领域标签 */ - domainTags?: string; + /** 年级标签(规范为数组,与套餐管理适用年级对齐) */ + gradeTags?: string[]; + /** 领域标签(规范为数组) */ + domainTags?: string[]; /** 是否有集体课 */ hasCollectiveLesson?: number; /** 版本号 */ @@ -129,4 +130,6 @@ export interface CourseResponse { updatedAt?: string; /** 关联的课程环节 */ courseLessons?: CourseLessonResponse[]; + /** 课程环节标签(列表展示用,仅 name 和 lessonType) */ + lessonTags?: LessonTagResponse[]; } diff --git a/reading-platform-frontend/src/api/generated/model/courseUsageVO.ts b/reading-platform-frontend/src/api/generated/model/courseUsageVO.ts new file mode 100644 index 0000000..c34c854 --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/courseUsageVO.ts @@ -0,0 +1,17 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ + +/** + * 课程使用统计视图对象 + */ +export interface CourseUsageVO { + /** 课程包名称 */ + name?: string; + /** 使用次数 */ + value?: number; +} diff --git a/reading-platform-frontend/src/api/generated/model/dayScheduleItem.ts b/reading-platform-frontend/src/api/generated/model/dayScheduleItem.ts index aa05d34..8d1457e 100644 --- a/reading-platform-frontend/src/api/generated/model/dayScheduleItem.ts +++ b/reading-platform-frontend/src/api/generated/model/dayScheduleItem.ts @@ -16,6 +16,8 @@ export interface DayScheduleItem { className?: string; /** 课程包名称 */ coursePackageName?: string; + /** 课程类型代码 (如 DOMAIN_HEALTH) */ + lessonType?: string; /** 课程类型名称 */ lessonTypeName?: string; /** 教师名称 */ diff --git a/reading-platform-frontend/src/api/generated/model/getAllCoursesParams.ts b/reading-platform-frontend/src/api/generated/model/getAllCoursesParams.ts new file mode 100644 index 0000000..1c207b3 --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/getAllCoursesParams.ts @@ -0,0 +1,13 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ + +export type GetAllCoursesParams = { +keyword?: string; +grade?: string; +domain?: string; +}; diff --git a/reading-platform-frontend/src/api/generated/model/getCoursePageParams.ts b/reading-platform-frontend/src/api/generated/model/getCoursePageParams.ts index a9a1091..d76fffd 100644 --- a/reading-platform-frontend/src/api/generated/model/getCoursePageParams.ts +++ b/reading-platform-frontend/src/api/generated/model/getCoursePageParams.ts @@ -10,5 +10,7 @@ export type GetCoursePageParams = { pageNum?: number; pageSize?: number; keyword?: string; -category?: string; +grade?: string; +domain?: string; +lessonType?: string; }; diff --git a/reading-platform-frontend/src/api/generated/model/getLessonTrend1Params.ts b/reading-platform-frontend/src/api/generated/model/getLessonTrend1Params.ts index 809abd0..63747ee 100644 --- a/reading-platform-frontend/src/api/generated/model/getLessonTrend1Params.ts +++ b/reading-platform-frontend/src/api/generated/model/getLessonTrend1Params.ts @@ -7,5 +7,5 @@ */ export type GetLessonTrend1Params = { -months?: number; +days?: number; }; diff --git a/reading-platform-frontend/src/api/generated/model/getStatisticsParams.ts b/reading-platform-frontend/src/api/generated/model/getStatisticsParams.ts new file mode 100644 index 0000000..c331950 --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/getStatisticsParams.ts @@ -0,0 +1,13 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ + +export type GetStatisticsParams = { +dateType?: string; +startDate?: string; +endDate?: string; +}; diff --git a/reading-platform-frontend/src/api/generated/model/getTaskCompletions1Params.ts b/reading-platform-frontend/src/api/generated/model/getTaskCompletions1Params.ts new file mode 100644 index 0000000..ddaafca --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/getTaskCompletions1Params.ts @@ -0,0 +1,13 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ + +export type GetTaskCompletions1Params = { +pageNum?: number; +pageSize?: number; +status?: string; +}; diff --git a/reading-platform-frontend/src/api/generated/model/getTaskCompletionsParams.ts b/reading-platform-frontend/src/api/generated/model/getTaskCompletionsParams.ts new file mode 100644 index 0000000..15bf455 --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/getTaskCompletionsParams.ts @@ -0,0 +1,13 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ + +export type GetTaskCompletionsParams = { +pageNum?: number; +pageSize?: number; +status?: string; +}; diff --git a/reading-platform-frontend/src/api/generated/model/getTaskListParams.ts b/reading-platform-frontend/src/api/generated/model/getTaskListParams.ts new file mode 100644 index 0000000..d5f7f3b --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/getTaskListParams.ts @@ -0,0 +1,23 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ + +export type GetTaskListParams = { +pageNum?: number; +pageSize?: number; +keyword?: string; +type?: string; +status?: string; +classIds?: number[]; +teacherIds?: number[]; +dateType?: string; +startDate?: string; +endDate?: string; +completionRate?: string; +sortBy?: string; +sortOrder?: string; +}; diff --git a/reading-platform-frontend/src/api/generated/model/getTemplates1Params.ts b/reading-platform-frontend/src/api/generated/model/getTemplates1Params.ts index 806979e..245e7d4 100644 --- a/reading-platform-frontend/src/api/generated/model/getTemplates1Params.ts +++ b/reading-platform-frontend/src/api/generated/model/getTemplates1Params.ts @@ -10,4 +10,6 @@ export type GetTemplates1Params = { pageNum?: number; pageSize?: number; type?: string; +taskType?: string; +keyword?: string; }; diff --git a/reading-platform-frontend/src/api/generated/model/getTemplatesParams.ts b/reading-platform-frontend/src/api/generated/model/getTemplatesParams.ts index 8783732..04f7819 100644 --- a/reading-platform-frontend/src/api/generated/model/getTemplatesParams.ts +++ b/reading-platform-frontend/src/api/generated/model/getTemplatesParams.ts @@ -10,4 +10,5 @@ export type GetTemplatesParams = { pageNum?: number; pageSize?: number; type?: string; +keyword?: string; }; diff --git a/reading-platform-frontend/src/api/generated/model/index.ts b/reading-platform-frontend/src/api/generated/model/index.ts index 1d46e5a..5849451 100644 --- a/reading-platform-frontend/src/api/generated/model/index.ts +++ b/reading-platform-frontend/src/api/generated/model/index.ts @@ -12,7 +12,6 @@ export * from './addClassTeacherDto'; export * from './addPackageToCollectionParams'; export * from './adminStatsControllerGetActiveTenantsParams'; export * from './adminStatsControllerGetPopularCoursesParams'; -export * from './adminStatsControllerGetRecentActivitiesParams'; export * from './approveCourseDto'; export * from './approveCourseDtoChecklist'; export * from './basicSettingsResponse'; @@ -26,6 +25,7 @@ export * from './calendarViewResponseSchedules'; export * from './changePasswordParams'; export * from './checkConflictParams'; export * from './classCreateRequest'; +export * from './classInfo'; export * from './classResponse'; export * from './classTeacherResponse'; export * from './classUpdateRequest'; @@ -48,11 +48,13 @@ export * from './coursePackageControllerFindAllParams'; export * from './coursePackageCourseItem'; export * from './coursePackageItem'; export * from './coursePackageResponse'; +export * from './coursePackageVO'; export * from './coursePageQueryRequest'; export * from './courseRejectRequest'; export * from './courseReportResponse'; export * from './courseResponse'; export * from './courseUpdateRequest'; +export * from './courseUsageVO'; export * from './createClassDto'; export * from './createCollectionRequest'; export * from './createFromSourceDto'; @@ -95,6 +97,7 @@ export * from './getActiveTeachersParams'; export * from './getActiveTenants200'; export * from './getActiveTenants200DataItem'; export * from './getActiveTenantsParams'; +export * from './getAllCoursesParams'; export * from './getAllStudentsParams'; export * from './getCalendarViewDataParams'; export * from './getClassPageParams'; @@ -119,15 +122,15 @@ export * from './getParentPageParams'; export * from './getPopularCourses200'; export * from './getPopularCourses200DataItem'; export * from './getPopularCoursesParams'; -export * from './getRecentActivities1200'; -export * from './getRecentActivities1200DataItem'; -export * from './getRecentActivities1Params'; -export * from './getRecentActivitiesParams'; export * from './getRecentGrowthRecordsParams'; export * from './getSchedules1Params'; export * from './getSchedulesParams'; export * from './getSchoolCoursesParams'; +export * from './getStatisticsParams'; export * from './getStudentPageParams'; +export * from './getTaskCompletions1Params'; +export * from './getTaskCompletionsParams'; +export * from './getTaskListParams'; export * from './getTaskPage1Params'; export * from './getTaskPageParams'; export * from './getTasksByStudentParams'; @@ -164,6 +167,7 @@ export * from './lessonResponse'; export * from './lessonStep'; export * from './lessonStepCreateRequest'; export * from './lessonStepResponse'; +export * from './lessonTagResponse'; export * from './lessonTypeInfo'; export * from './lessonUpdateRequest'; export * from './libraryCreateRequest'; @@ -211,9 +215,11 @@ export * from './pageResultResourceItemResponse'; export * from './pageResultResourceLibrary'; export * from './pageResultResourceLibraryResponse'; export * from './pageResultSchedulePlanResponse'; +export * from './pageResultSchoolCourseResponse'; export * from './pageResultStudent'; export * from './pageResultStudentResponse'; export * from './pageResultTask'; +export * from './pageResultTaskCompletionDetailResponse'; export * from './pageResultTaskResponse'; export * from './pageResultTaskTemplateResponse'; export * from './pageResultTeacher'; @@ -227,8 +233,6 @@ export * from './parentStudentResponse'; export * from './parentUpdateRequest'; export * from './popularCourseItemResponse'; export * from './popularCoursesQueryRequest'; -export * from './recentActivitiesQueryRequest'; -export * from './recentActivityItemResponse'; export * from './refreshTokenRequest'; export * from './rejectCourseDto'; export * from './rejectCourseDtoChecklist'; @@ -278,8 +282,10 @@ export * from './resultListCourseLesson'; export * from './resultListCourseLessonResponse'; export * from './resultListCoursePackage'; export * from './resultListCoursePackageResponse'; +export * from './resultListCoursePackageVO'; export * from './resultListCourseReportResponse'; export * from './resultListCourseResponse'; +export * from './resultListCourseUsageVO'; export * from './resultListGrowthRecord'; export * from './resultListGrowthRecordResponse'; export * from './resultListLesson'; @@ -291,12 +297,13 @@ export * from './resultListMapStringObject'; export * from './resultListMapStringObjectDataItem'; export * from './resultListParentStudentResponse'; export * from './resultListPopularCourseItemResponse'; -export * from './resultListRecentActivityItemResponse'; export * from './resultListSchedulePlanResponse'; export * from './resultListStudent'; export * from './resultListStudentRecordResponse'; export * from './resultListStudentReportResponse'; export * from './resultListStudentResponse'; +export * from './resultListTeacherLessonTrendVO'; +export * from './resultListTeacherLessonVO'; export * from './resultListTeacherReportResponse'; export * from './resultListTeacherResponse'; export * from './resultListTenantPackage'; @@ -308,6 +315,8 @@ export * from './resultLoginResponse'; export * from './resultLong'; export * from './resultMapStringObject'; export * from './resultMapStringObjectData'; +export * from './resultMapStringString'; +export * from './resultMapStringStringData'; export * from './resultNotification'; export * from './resultNotificationResponse'; export * from './resultNotificationSettingsResponse'; @@ -341,9 +350,11 @@ export * from './resultPageResultResourceItemResponse'; export * from './resultPageResultResourceLibrary'; export * from './resultPageResultResourceLibraryResponse'; export * from './resultPageResultSchedulePlanResponse'; +export * from './resultPageResultSchoolCourseResponse'; export * from './resultPageResultStudent'; export * from './resultPageResultStudentResponse'; export * from './resultPageResultTask'; +export * from './resultPageResultTaskCompletionDetailResponse'; export * from './resultPageResultTaskResponse'; export * from './resultPageResultTaskTemplateResponse'; export * from './resultPageResultTeacher'; @@ -358,6 +369,7 @@ export * from './resultResourceItemResponse'; export * from './resultResourceLibrary'; export * from './resultResourceLibraryResponse'; export * from './resultSchedulePlanResponse'; +export * from './resultSchoolCourseResponse'; export * from './resultSchoolSettingsResponse'; export * from './resultSecuritySettingsResponse'; export * from './resultStatsResponse'; @@ -367,16 +379,21 @@ export * from './resultStudent'; export * from './resultStudentRecordResponse'; export * from './resultStudentResponse'; export * from './resultTask'; +export * from './resultTaskCompletionDetailResponse'; +export * from './resultTaskFeedbackResponse'; export * from './resultTaskResponse'; export * from './resultTaskTemplateResponse'; export * from './resultTeacher'; +export * from './resultTeacherDashboardResponse'; export * from './resultTeacherResponse'; +export * from './resultTeacherWeeklyStatsResponse'; export * from './resultTenant'; export * from './resultTenantResponse'; export * from './resultTheme'; export * from './resultThemeResponse'; export * from './resultTimetableResponse'; export * from './resultTokenResponse'; +export * from './resultUpdateProfileResponse'; export * from './resultUserInfoResponse'; export * from './resultVoid'; export * from './resultVoidData'; @@ -387,6 +404,7 @@ export * from './schedulePlanCreateRequest'; export * from './schedulePlanResponse'; export * from './schedulePlanUpdateRequest'; export * from './schoolControllerImportStudentsParams'; +export * from './schoolCourseResponse'; export * from './schoolFeedbackControllerFindAllParams'; export * from './schoolSettingsResponse'; export * from './schoolSettingsUpdateRequest'; @@ -395,12 +413,12 @@ export * from './securitySettingsResponse'; export * from './securitySettingsUpdateRequest'; export * from './statsControllerGetActiveTeachersParams'; export * from './statsControllerGetLessonTrendParams'; -export * from './statsControllerGetRecentActivitiesParams'; export * from './statsResponse'; export * from './statsTrendResponse'; export * from './stepCreateRequest'; export * from './student'; export * from './studentCreateRequest'; +export * from './studentInfo'; export * from './studentRecordDto'; export * from './studentRecordRequest'; export * from './studentRecordResponse'; @@ -409,8 +427,12 @@ export * from './studentResponse'; export * from './studentUpdateRequest'; export * from './submitCourseDto'; export * from './task'; +export * from './taskCompletionDetailResponse'; export * from './taskCreateRequest'; +export * from './taskFeedbackRequest'; +export * from './taskFeedbackResponse'; export * from './taskResponse'; +export * from './taskSubmitRequest'; export * from './taskTemplateCreateRequest'; export * from './taskTemplateResponse'; export * from './taskUpdateRequest'; @@ -422,11 +444,17 @@ export * from './teacherCourseControllerGetLessonTrendParams'; export * from './teacherCourseControllerGetTeacherSchedulesParams'; export * from './teacherCourseControllerGetTeacherTimetableParams'; export * from './teacherCreateRequest'; +export * from './teacherDashboardResponse'; export * from './teacherFeedbackControllerFindAllParams'; +export * from './teacherLessonTrendVO'; +export * from './teacherLessonVO'; export * from './teacherReportResponse'; export * from './teacherResponse'; +export * from './teacherResponseClassNames'; +export * from './teacherStats'; export * from './teacherTaskControllerGetMonthlyStatsParams'; export * from './teacherUpdateRequest'; +export * from './teacherWeeklyStatsResponse'; export * from './tenant'; export * from './tenantControllerFindAllPackageType'; export * from './tenantControllerFindAllParams'; @@ -452,6 +480,8 @@ export * from './updateLessonDto'; export * from './updateLibraryDto'; export * from './updateNotificationSettings1Body'; export * from './updateNotificationSettingsBody'; +export * from './updateProfileRequest'; +export * from './updateProfileResponse'; export * from './updateResourceItemDto'; export * from './updateSchedule1Body'; export * from './updateScheduleBody'; diff --git a/reading-platform-frontend/src/api/generated/model/lessonDetailResponse.ts b/reading-platform-frontend/src/api/generated/model/lessonDetailResponse.ts index 881bb8b..a7c1487 100644 --- a/reading-platform-frontend/src/api/generated/model/lessonDetailResponse.ts +++ b/reading-platform-frontend/src/api/generated/model/lessonDetailResponse.ts @@ -15,7 +15,7 @@ import type { LessonResponse } from './lessonResponse'; export interface LessonDetailResponse { lesson?: LessonResponse; course?: CourseResponse; - class?: ClassResponse; /** 排课选择的课程类型(子课程模式时用于直接进入对应子课程) */ lessonType?: string; + class?: ClassResponse; } diff --git a/reading-platform-frontend/src/api/generated/model/lessonTagResponse.ts b/reading-platform-frontend/src/api/generated/model/lessonTagResponse.ts new file mode 100644 index 0000000..84f0bab --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/lessonTagResponse.ts @@ -0,0 +1,17 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ + +/** + * 课程环节标签 + */ +export interface LessonTagResponse { + /** 环节名称 */ + name?: string; + /** 环节类型:INTRODUCTION、COLLECTIVE、LANGUAGE、HEALTH、SCIENCE、SOCIAL、ART */ + lessonType?: string; +} diff --git a/reading-platform-frontend/src/api/generated/model/pageResultSchoolCourseResponse.ts b/reading-platform-frontend/src/api/generated/model/pageResultSchoolCourseResponse.ts new file mode 100644 index 0000000..4701863 --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/pageResultSchoolCourseResponse.ts @@ -0,0 +1,16 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ +import type { SchoolCourseResponse } from './schoolCourseResponse'; + +export interface PageResultSchoolCourseResponse { + list?: SchoolCourseResponse[]; + total?: number; + pageNum?: number; + pageSize?: number; + pages?: number; +} diff --git a/reading-platform-frontend/src/api/generated/model/pageResultTaskCompletionDetailResponse.ts b/reading-platform-frontend/src/api/generated/model/pageResultTaskCompletionDetailResponse.ts new file mode 100644 index 0000000..1eccb47 --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/pageResultTaskCompletionDetailResponse.ts @@ -0,0 +1,16 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ +import type { TaskCompletionDetailResponse } from './taskCompletionDetailResponse'; + +export interface PageResultTaskCompletionDetailResponse { + list?: TaskCompletionDetailResponse[]; + total?: number; + pageNum?: number; + pageSize?: number; + pages?: number; +} diff --git a/reading-platform-frontend/src/api/generated/model/recentActivityItemResponse.ts b/reading-platform-frontend/src/api/generated/model/recentActivityItemResponse.ts deleted file mode 100644 index 6c28da3..0000000 --- a/reading-platform-frontend/src/api/generated/model/recentActivityItemResponse.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Generated by orval v8.5.3 🍺 - * Do not edit manually. - * Reading Platform API - * Reading Platform Backend Service API Documentation - * OpenAPI spec version: 1.0.0 - */ - -/** - * 最近活动项响应 - */ -export interface RecentActivityItemResponse { - /** 活动 ID */ - activityId?: number; - /** 活动类型 */ - activityType?: string; - /** 活动描述 */ - description?: string; - /** 操作人 ID */ - operatorId?: number; - /** 操作人名称 */ - operatorName?: string; - /** 操作时间 */ - operationTime?: string; -} diff --git a/reading-platform-frontend/src/api/generated/model/resultListRecentActivityItemResponse.ts b/reading-platform-frontend/src/api/generated/model/resultListCoursePackageVO.ts similarity index 55% rename from reading-platform-frontend/src/api/generated/model/resultListRecentActivityItemResponse.ts rename to reading-platform-frontend/src/api/generated/model/resultListCoursePackageVO.ts index 42136e7..4f63fbc 100644 --- a/reading-platform-frontend/src/api/generated/model/resultListRecentActivityItemResponse.ts +++ b/reading-platform-frontend/src/api/generated/model/resultListCoursePackageVO.ts @@ -5,10 +5,10 @@ * Reading Platform Backend Service API Documentation * OpenAPI spec version: 1.0.0 */ -import type { RecentActivityItemResponse } from './recentActivityItemResponse'; +import type { CoursePackageVO } from './coursePackageVO'; -export interface ResultListRecentActivityItemResponse { +export interface ResultListCoursePackageVO { code?: number; message?: string; - data?: RecentActivityItemResponse[]; + data?: CoursePackageVO[]; } diff --git a/reading-platform-frontend/src/api/generated/model/getRecentActivities1200.ts b/reading-platform-frontend/src/api/generated/model/resultListCourseUsageVO.ts similarity index 54% rename from reading-platform-frontend/src/api/generated/model/getRecentActivities1200.ts rename to reading-platform-frontend/src/api/generated/model/resultListCourseUsageVO.ts index c2b61fd..0ecab39 100644 --- a/reading-platform-frontend/src/api/generated/model/getRecentActivities1200.ts +++ b/reading-platform-frontend/src/api/generated/model/resultListCourseUsageVO.ts @@ -5,10 +5,10 @@ * Reading Platform Backend Service API Documentation * OpenAPI spec version: 1.0.0 */ -import type { GetRecentActivities1200DataItem } from './getRecentActivities1200DataItem'; +import type { CourseUsageVO } from './courseUsageVO'; -export type GetRecentActivities1200 = { +export interface ResultListCourseUsageVO { code?: number; message?: string; - data?: GetRecentActivities1200DataItem[]; -}; + data?: CourseUsageVO[]; +} diff --git a/reading-platform-frontend/src/api/generated/model/resultListTeacherLessonTrendVO.ts b/reading-platform-frontend/src/api/generated/model/resultListTeacherLessonTrendVO.ts new file mode 100644 index 0000000..b2088d1 --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/resultListTeacherLessonTrendVO.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ +import type { TeacherLessonTrendVO } from './teacherLessonTrendVO'; + +export interface ResultListTeacherLessonTrendVO { + code?: number; + message?: string; + data?: TeacherLessonTrendVO[]; +} diff --git a/reading-platform-frontend/src/api/generated/model/resultListTeacherLessonVO.ts b/reading-platform-frontend/src/api/generated/model/resultListTeacherLessonVO.ts new file mode 100644 index 0000000..e8d03c3 --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/resultListTeacherLessonVO.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ +import type { TeacherLessonVO } from './teacherLessonVO'; + +export interface ResultListTeacherLessonVO { + code?: number; + message?: string; + data?: TeacherLessonVO[]; +} diff --git a/reading-platform-frontend/src/api/generated/model/resultMapStringString.ts b/reading-platform-frontend/src/api/generated/model/resultMapStringString.ts new file mode 100644 index 0000000..2da2ec1 --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/resultMapStringString.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ +import type { ResultMapStringStringData } from './resultMapStringStringData'; + +export interface ResultMapStringString { + code?: number; + message?: string; + data?: ResultMapStringStringData; +} diff --git a/reading-platform-frontend/src/api/generated/model/getRecentActivities1200DataItem.ts b/reading-platform-frontend/src/api/generated/model/resultMapStringStringData.ts similarity index 70% rename from reading-platform-frontend/src/api/generated/model/getRecentActivities1200DataItem.ts rename to reading-platform-frontend/src/api/generated/model/resultMapStringStringData.ts index 492a11d..8feaaf1 100644 --- a/reading-platform-frontend/src/api/generated/model/getRecentActivities1200DataItem.ts +++ b/reading-platform-frontend/src/api/generated/model/resultMapStringStringData.ts @@ -6,4 +6,4 @@ * OpenAPI spec version: 1.0.0 */ -export type GetRecentActivities1200DataItem = { [key: string]: unknown }; +export type ResultMapStringStringData = {[key: string]: string}; diff --git a/reading-platform-frontend/src/api/generated/model/resultPageResultSchoolCourseResponse.ts b/reading-platform-frontend/src/api/generated/model/resultPageResultSchoolCourseResponse.ts new file mode 100644 index 0000000..fdb97f8 --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/resultPageResultSchoolCourseResponse.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ +import type { PageResultSchoolCourseResponse } from './pageResultSchoolCourseResponse'; + +export interface ResultPageResultSchoolCourseResponse { + code?: number; + message?: string; + data?: PageResultSchoolCourseResponse; +} diff --git a/reading-platform-frontend/src/api/generated/model/resultPageResultTaskCompletionDetailResponse.ts b/reading-platform-frontend/src/api/generated/model/resultPageResultTaskCompletionDetailResponse.ts new file mode 100644 index 0000000..91c96cd --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/resultPageResultTaskCompletionDetailResponse.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ +import type { PageResultTaskCompletionDetailResponse } from './pageResultTaskCompletionDetailResponse'; + +export interface ResultPageResultTaskCompletionDetailResponse { + code?: number; + message?: string; + data?: PageResultTaskCompletionDetailResponse; +} diff --git a/reading-platform-frontend/src/api/generated/model/resultSchoolCourseResponse.ts b/reading-platform-frontend/src/api/generated/model/resultSchoolCourseResponse.ts new file mode 100644 index 0000000..9daa42c --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/resultSchoolCourseResponse.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ +import type { SchoolCourseResponse } from './schoolCourseResponse'; + +export interface ResultSchoolCourseResponse { + code?: number; + message?: string; + data?: SchoolCourseResponse; +} diff --git a/reading-platform-frontend/src/api/generated/model/resultTaskCompletionDetailResponse.ts b/reading-platform-frontend/src/api/generated/model/resultTaskCompletionDetailResponse.ts new file mode 100644 index 0000000..d385b6a --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/resultTaskCompletionDetailResponse.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ +import type { TaskCompletionDetailResponse } from './taskCompletionDetailResponse'; + +export interface ResultTaskCompletionDetailResponse { + code?: number; + message?: string; + data?: TaskCompletionDetailResponse; +} diff --git a/reading-platform-frontend/src/api/generated/model/resultTaskFeedbackResponse.ts b/reading-platform-frontend/src/api/generated/model/resultTaskFeedbackResponse.ts new file mode 100644 index 0000000..7953ef5 --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/resultTaskFeedbackResponse.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ +import type { TaskFeedbackResponse } from './taskFeedbackResponse'; + +export interface ResultTaskFeedbackResponse { + code?: number; + message?: string; + data?: TaskFeedbackResponse; +} diff --git a/reading-platform-frontend/src/api/generated/model/resultTeacherDashboardResponse.ts b/reading-platform-frontend/src/api/generated/model/resultTeacherDashboardResponse.ts new file mode 100644 index 0000000..90b1ab2 --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/resultTeacherDashboardResponse.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ +import type { TeacherDashboardResponse } from './teacherDashboardResponse'; + +export interface ResultTeacherDashboardResponse { + code?: number; + message?: string; + data?: TeacherDashboardResponse; +} diff --git a/reading-platform-frontend/src/api/generated/model/resultTeacherWeeklyStatsResponse.ts b/reading-platform-frontend/src/api/generated/model/resultTeacherWeeklyStatsResponse.ts new file mode 100644 index 0000000..d266446 --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/resultTeacherWeeklyStatsResponse.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ +import type { TeacherWeeklyStatsResponse } from './teacherWeeklyStatsResponse'; + +export interface ResultTeacherWeeklyStatsResponse { + code?: number; + message?: string; + data?: TeacherWeeklyStatsResponse; +} diff --git a/reading-platform-frontend/src/api/generated/model/resultUpdateProfileResponse.ts b/reading-platform-frontend/src/api/generated/model/resultUpdateProfileResponse.ts new file mode 100644 index 0000000..0cb1e67 --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/resultUpdateProfileResponse.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ +import type { UpdateProfileResponse } from './updateProfileResponse'; + +export interface ResultUpdateProfileResponse { + code?: number; + message?: string; + data?: UpdateProfileResponse; +} diff --git a/reading-platform-frontend/src/api/generated/model/schoolCourseResponse.ts b/reading-platform-frontend/src/api/generated/model/schoolCourseResponse.ts new file mode 100644 index 0000000..41c06d5 --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/schoolCourseResponse.ts @@ -0,0 +1,50 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ +import type { LessonTagResponse } from './lessonTagResponse'; + +/** + * 学校端课程响应 + */ +export interface SchoolCourseResponse { + /** ID */ + id?: number; + /** 租户 ID */ + tenantId?: number; + /** 课程名称 */ + name?: string; + /** 课程编码 */ + code?: string; + /** 描述 */ + description?: string; + /** 绘本名称 */ + pictureBookName?: string; + /** 封面图片路径 */ + coverImagePath?: string; + /** 封面 URL */ + coverUrl?: string; + /** 年级标签(规范为数组) */ + gradeTags?: string[]; + /** 领域标签(规范为数组) */ + domainTags?: string[]; + /** 课程时长(分钟) */ + duration?: number; + /** 使用次数 */ + usageCount?: number; + /** 教师数量 */ + teacherCount?: number; + /** 平均评分 */ + avgRating?: number; + /** 状态 */ + status?: string; + /** 创建时间 */ + createdAt?: string; + /** 更新时间 */ + updatedAt?: string; + /** 课程环节标签(列表展示用,仅 name 和 lessonType) */ + lessonTags?: LessonTagResponse[]; +} diff --git a/reading-platform-frontend/src/api/generated/model/statsResponse.ts b/reading-platform-frontend/src/api/generated/model/statsResponse.ts index 58d7e91..15b3a2d 100644 --- a/reading-platform-frontend/src/api/generated/model/statsResponse.ts +++ b/reading-platform-frontend/src/api/generated/model/statsResponse.ts @@ -22,4 +22,6 @@ export interface StatsResponse { totalCourses?: number; /** 课时总数 */ totalLessons?: number; + /** 月授课次数(本月 COMPLETED 状态的 lesson 总数) */ + monthlyLessons?: number; } diff --git a/reading-platform-frontend/src/api/generated/model/statsTrendResponse.ts b/reading-platform-frontend/src/api/generated/model/statsTrendResponse.ts index e3dcab6..c2fbae0 100644 --- a/reading-platform-frontend/src/api/generated/model/statsTrendResponse.ts +++ b/reading-platform-frontend/src/api/generated/model/statsTrendResponse.ts @@ -12,10 +12,8 @@ export interface StatsTrendResponse { /** 日期列表 */ dates?: string[]; - /** 新增学生数列表 */ - newStudents?: number[]; - /** 新增教师数列表 */ - newTeachers?: number[]; - /** 新增课程数列表 */ - newCourses?: number[]; + /** 授课次数列表(近 7 天每天完成的课程数) */ + lessonCounts?: number[]; + /** 活跃学生数列表(近 7 天每天有上课记录的去重学生数) */ + studentCounts?: number[]; } diff --git a/reading-platform-frontend/src/api/generated/model/studentInfo.ts b/reading-platform-frontend/src/api/generated/model/studentInfo.ts new file mode 100644 index 0000000..ba70eac --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/studentInfo.ts @@ -0,0 +1,23 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ +import type { ClassInfo } from './classInfo'; + +/** + * 学生信息 + */ +export interface StudentInfo { + /** 学生ID */ + id?: number; + /** 学生姓名 */ + name?: string; + /** 学生头像 */ + avatar?: string; + /** 性别:MALE/FEMALE */ + gender?: string; + classInfo?: ClassInfo; +} diff --git a/reading-platform-frontend/src/api/generated/model/task.ts b/reading-platform-frontend/src/api/generated/model/task.ts index e2c898f..47a66b4 100644 --- a/reading-platform-frontend/src/api/generated/model/task.ts +++ b/reading-platform-frontend/src/api/generated/model/task.ts @@ -28,6 +28,8 @@ export interface Task { description?: string; /** 任务类型 */ type?: string; + /** 关联绘本名称 */ + relatedBookName?: string; /** 课程 ID */ courseId?: number; /** 创建人 ID */ diff --git a/reading-platform-frontend/src/api/generated/model/taskCompletionDetailResponse.ts b/reading-platform-frontend/src/api/generated/model/taskCompletionDetailResponse.ts new file mode 100644 index 0000000..174bc25 --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/taskCompletionDetailResponse.ts @@ -0,0 +1,39 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ +import type { StudentInfo } from './studentInfo'; +import type { TaskFeedbackResponse } from './taskFeedbackResponse'; + +/** + * 任务完成详情响应 + */ +export interface TaskCompletionDetailResponse { + /** 完成记录ID */ + id?: number; + /** 任务ID */ + taskId?: number; + /** 任务标题 */ + taskTitle?: string; + student?: StudentInfo; + /** 状态:PENDING/SUBMITTED/REVIEWED */ + status?: string; + /** 状态文本:待完成/已提交/已评价 */ + statusText?: string; + /** 照片URL数组 */ + photos?: string[]; + /** 视频URL */ + videoUrl?: string; + /** 语音URL */ + audioUrl?: string; + /** 完成内容/阅读心得 */ + content?: string; + /** 提交时间 */ + submittedAt?: string; + /** 评价时间 */ + reviewedAt?: string; + feedback?: TaskFeedbackResponse; +} diff --git a/reading-platform-frontend/src/api/generated/model/taskCreateRequest.ts b/reading-platform-frontend/src/api/generated/model/taskCreateRequest.ts index 5816fa9..e5638a0 100644 --- a/reading-platform-frontend/src/api/generated/model/taskCreateRequest.ts +++ b/reading-platform-frontend/src/api/generated/model/taskCreateRequest.ts @@ -16,6 +16,8 @@ export interface TaskCreateRequest { description?: string; /** 任务类型:reading-阅读,homework-作业,activity-活动 */ type?: string; + /** 关联绘本名称(手动填写) */ + relatedBookName?: string; /** 课程 ID */ courseId?: number; /** 开始日期 */ diff --git a/reading-platform-frontend/src/api/generated/model/taskFeedbackRequest.ts b/reading-platform-frontend/src/api/generated/model/taskFeedbackRequest.ts new file mode 100644 index 0000000..13a7f7c --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/taskFeedbackRequest.ts @@ -0,0 +1,27 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ + +/** + * 任务评价请求 + */ +export interface TaskFeedbackRequest { + /** 评价结果:EXCELLENT-优秀/PASSED-通过/NEEDS_WORK-需改进 */ + result: string; + /** + * 评分 1-5(可选) + * @minimum 1 + * @maximum 5 + */ + rating?: number; + /** + * 评语 + * @minLength 0 + * @maxLength 500 + */ + comment?: string; +} diff --git a/reading-platform-frontend/src/api/generated/model/taskFeedbackResponse.ts b/reading-platform-frontend/src/api/generated/model/taskFeedbackResponse.ts new file mode 100644 index 0000000..7ab5f79 --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/taskFeedbackResponse.ts @@ -0,0 +1,33 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ + +/** + * 任务评价响应 + */ +export interface TaskFeedbackResponse { + /** 评价ID */ + id?: number; + /** 完成记录ID */ + completionId?: number; + /** 评价结果:EXCELLENT/PASSED/NEEDS_WORK */ + result?: string; + /** 评价结果文本:优秀/通过/需改进 */ + resultText?: string; + /** 评分 1-5 */ + rating?: number; + /** 评语 */ + comment?: string; + /** 教师ID */ + teacherId?: number; + /** 教师姓名 */ + teacherName?: string; + /** 教师头像 */ + teacherAvatar?: string; + /** 评价时间 */ + createdAt?: string; +} diff --git a/reading-platform-frontend/src/api/generated/model/taskResponse.ts b/reading-platform-frontend/src/api/generated/model/taskResponse.ts index 3439948..0996511 100644 --- a/reading-platform-frontend/src/api/generated/model/taskResponse.ts +++ b/reading-platform-frontend/src/api/generated/model/taskResponse.ts @@ -20,6 +20,8 @@ export interface TaskResponse { description?: string; /** 任务类型 */ type?: string; + /** 关联绘本名称 */ + relatedBookName?: string; /** 课程 ID */ courseId?: number; /** 创建人 ID */ diff --git a/reading-platform-frontend/src/api/generated/model/taskSubmitRequest.ts b/reading-platform-frontend/src/api/generated/model/taskSubmitRequest.ts new file mode 100644 index 0000000..838d3d8 --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/taskSubmitRequest.ts @@ -0,0 +1,31 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ + +/** + * 任务提交请求 + */ +export interface TaskSubmitRequest { + /** 学生ID */ + studentId?: number; + /** + * 照片URL数组(最多9张) + * @minItems 0 + * @maxItems 9 + */ + photos?: string[]; + /** 视频URL */ + videoUrl?: string; + /** 语音URL */ + audioUrl?: string; + /** + * 阅读心得/完成内容 + * @minLength 0 + * @maxLength 1000 + */ + content?: string; +} diff --git a/reading-platform-frontend/src/api/generated/model/taskTemplateCreateRequest.ts b/reading-platform-frontend/src/api/generated/model/taskTemplateCreateRequest.ts index 5e64769..4c555bc 100644 --- a/reading-platform-frontend/src/api/generated/model/taskTemplateCreateRequest.ts +++ b/reading-platform-frontend/src/api/generated/model/taskTemplateCreateRequest.ts @@ -23,9 +23,9 @@ export interface TaskTemplateCreateRequest { /** 默认持续时间 (天) */ defaultDuration?: number; /** 是否默认模板 */ - isDefault?: number; + isDefault?: boolean; /** 模板内容 */ content?: string; /** 是否公开 */ - isPublic?: number; + isPublic?: boolean; } diff --git a/reading-platform-frontend/src/api/generated/model/teacherCreateRequest.ts b/reading-platform-frontend/src/api/generated/model/teacherCreateRequest.ts index 711d53f..6d82981 100644 --- a/reading-platform-frontend/src/api/generated/model/teacherCreateRequest.ts +++ b/reading-platform-frontend/src/api/generated/model/teacherCreateRequest.ts @@ -10,18 +10,20 @@ * 教师创建请求 */ export interface TeacherCreateRequest { - /** 用户名 */ + /** 用户名/登录账号 */ username: string; /** 密码 */ password: string; /** 姓名 */ name: string; /** 电话 */ - phone?: string; + phone: string; /** 邮箱 */ email?: string; /** 性别 */ gender?: string; /** 简介 */ bio?: string; + /** 负责班级ID列表 */ + classIds?: number[]; } diff --git a/reading-platform-frontend/src/api/generated/model/teacherDashboardResponse.ts b/reading-platform-frontend/src/api/generated/model/teacherDashboardResponse.ts new file mode 100644 index 0000000..3cb48f5 --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/teacherDashboardResponse.ts @@ -0,0 +1,26 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ +import type { CoursePackageVO } from './coursePackageVO'; +import type { TeacherDashboardResponseRecentActivitiesItem } from './teacherDashboardResponseRecentActivitiesItem'; +import type { TeacherLessonVO } from './teacherLessonVO'; +import type { TeacherStats } from './teacherStats'; +import type { TeacherWeeklyStatsResponse } from './teacherWeeklyStatsResponse'; + +/** + * 教师仪表盘响应 + */ +export interface TeacherDashboardResponse { + stats?: TeacherStats; + /** 今日课程 */ + todayLessons?: TeacherLessonVO[]; + /** 推荐课程 */ + recommendedCourses?: CoursePackageVO[]; + weeklyStats?: TeacherWeeklyStatsResponse; + /** 近期活动 */ + recentActivities?: TeacherDashboardResponseRecentActivitiesItem[]; +} diff --git a/reading-platform-frontend/src/api/generated/model/teacherLessonTrendVO.ts b/reading-platform-frontend/src/api/generated/model/teacherLessonTrendVO.ts new file mode 100644 index 0000000..d16edf9 --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/teacherLessonTrendVO.ts @@ -0,0 +1,19 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ + +/** + * 教师课程趋势视图对象 + */ +export interface TeacherLessonTrendVO { + /** 月份(yyyy-MM 格式) */ + month?: string; + /** 课时数量 */ + lessonCount?: number; + /** 平均评分 */ + avgRating?: number; +} diff --git a/reading-platform-frontend/src/api/generated/model/teacherLessonVO.ts b/reading-platform-frontend/src/api/generated/model/teacherLessonVO.ts new file mode 100644 index 0000000..8243aa0 --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/teacherLessonVO.ts @@ -0,0 +1,44 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ +import type { LocalTime } from './localTime'; + +/** + * 教师课程视图对象 + */ +export interface TeacherLessonVO { + /** ID */ + id?: number; + /** 租户 ID */ + tenantId?: number; + /** 课程 ID */ + courseId?: number; + /** 班级 ID */ + classId?: number; + /** 课程名称 */ + courseName?: string; + /** 班级名称 */ + className?: string; + /** 教师 ID */ + teacherId?: number; + /** 标题 */ + title?: string; + /** 课时日期 */ + lessonDate?: string; + startTime?: LocalTime; + endTime?: LocalTime; + /** 地点 */ + location?: string; + /** 状态 */ + status?: string; + /** 备注 */ + notes?: string; + /** 创建时间 */ + createdAt?: string; + /** 更新时间 */ + updatedAt?: string; +} diff --git a/reading-platform-frontend/src/api/generated/model/teacherResponse.ts b/reading-platform-frontend/src/api/generated/model/teacherResponse.ts index 844d5ac..cf228d8 100644 --- a/reading-platform-frontend/src/api/generated/model/teacherResponse.ts +++ b/reading-platform-frontend/src/api/generated/model/teacherResponse.ts @@ -5,6 +5,7 @@ * Reading Platform Backend Service API Documentation * OpenAPI spec version: 1.0.0 */ +import type { TeacherResponseClassNames } from './teacherResponseClassNames'; /** * 教师响应 @@ -14,8 +15,6 @@ export interface TeacherResponse { id?: number; /** 租户 ID */ tenantId?: number; - /** 用户名 */ - username?: string; /** 姓名 */ name?: string; /** 电话 */ @@ -30,10 +29,18 @@ export interface TeacherResponse { bio?: string; /** 状态 */ status?: string; + /** 负责班级ID列表 */ + classIds?: number[]; + /** 负责班级名称 */ + classNames?: TeacherResponseClassNames; + /** 授课次数 */ + lessonCount?: number; /** 最后登录时间 */ lastLoginAt?: string; /** 创建时间 */ createdAt?: string; /** 更新时间 */ updatedAt?: string; + /** 登录账号 */ + loginAccount?: string; } diff --git a/reading-platform-frontend/src/api/generated/model/teacherResponseClassNames.ts b/reading-platform-frontend/src/api/generated/model/teacherResponseClassNames.ts new file mode 100644 index 0000000..1f56bfa --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/teacherResponseClassNames.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ + +/** + * 负责班级名称 + */ +export type TeacherResponseClassNames = { [key: string]: unknown }; diff --git a/reading-platform-frontend/src/api/generated/model/teacherStats.ts b/reading-platform-frontend/src/api/generated/model/teacherStats.ts new file mode 100644 index 0000000..bc82771 --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/teacherStats.ts @@ -0,0 +1,21 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ + +/** + * 基础统计 + */ +export interface TeacherStats { + /** 班级数量 */ + classCount?: number; + /** 学生数量 */ + studentCount?: number; + /** 课程包数量 */ + courseCount?: number; + /** 课时数量 */ + lessonCount?: number; +} diff --git a/reading-platform-frontend/src/api/generated/model/teacherUpdateRequest.ts b/reading-platform-frontend/src/api/generated/model/teacherUpdateRequest.ts index 57d5c88..61ca4b1 100644 --- a/reading-platform-frontend/src/api/generated/model/teacherUpdateRequest.ts +++ b/reading-platform-frontend/src/api/generated/model/teacherUpdateRequest.ts @@ -24,4 +24,6 @@ export interface TeacherUpdateRequest { bio?: string; /** 状态 */ status?: string; + /** 负责班级ID列表 */ + classIds?: number[]; } diff --git a/reading-platform-frontend/src/api/generated/model/teacherWeeklyStatsResponse.ts b/reading-platform-frontend/src/api/generated/model/teacherWeeklyStatsResponse.ts new file mode 100644 index 0000000..42f4016 --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/teacherWeeklyStatsResponse.ts @@ -0,0 +1,21 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ + +/** + * 教师本周统计响应 + */ +export interface TeacherWeeklyStatsResponse { + /** 本周课时数量 */ + lessonCount?: number; + /** 学生参与率 */ + studentParticipation?: number; + /** 平均评分 */ + avgRating?: number; + /** 总时长(分钟) */ + totalDuration?: number; +} diff --git a/reading-platform-frontend/src/api/generated/model/tenantCreateRequest.ts b/reading-platform-frontend/src/api/generated/model/tenantCreateRequest.ts index 07c08d7..b8aeb78 100644 --- a/reading-platform-frontend/src/api/generated/model/tenantCreateRequest.ts +++ b/reading-platform-frontend/src/api/generated/model/tenantCreateRequest.ts @@ -26,8 +26,6 @@ export interface TenantCreateRequest { address?: string; /** Logo URL */ logoUrl?: string; - /** 套餐类型 */ - packageType?: string; /** 教师配额 */ teacherQuota?: number; /** 学生配额 */ @@ -36,6 +34,6 @@ export interface TenantCreateRequest { startDate?: string; /** 结束日期 */ expireDate?: string; - /** 课程套餐 ID(可选) */ - collectionId?: number; + /** 课程套餐 ID 列表(可选,支持多选) */ + collectionIds?: number[]; } diff --git a/reading-platform-frontend/src/api/generated/model/tenantResponse.ts b/reading-platform-frontend/src/api/generated/model/tenantResponse.ts index b9179f9..5b8c284 100644 --- a/reading-platform-frontend/src/api/generated/model/tenantResponse.ts +++ b/reading-platform-frontend/src/api/generated/model/tenantResponse.ts @@ -36,8 +36,8 @@ export interface TenantResponse { maxStudents?: number; /** 最大教师数 */ maxTeachers?: number; - /** 套餐类型 */ - packageType?: string; + /** 套餐名称列表 */ + packageNames?: string[]; /** 教师配额 */ teacherQuota?: number; /** 学生配额 */ diff --git a/reading-platform-frontend/src/api/generated/model/tenantUpdateRequest.ts b/reading-platform-frontend/src/api/generated/model/tenantUpdateRequest.ts index 59bffb7..6a805d2 100644 --- a/reading-platform-frontend/src/api/generated/model/tenantUpdateRequest.ts +++ b/reading-platform-frontend/src/api/generated/model/tenantUpdateRequest.ts @@ -24,10 +24,8 @@ export interface TenantUpdateRequest { logoUrl?: string; /** 状态 */ status?: string; - /** 套餐类型 */ - packageType?: string; /** 课程套餐ID(用于三层架构) */ - collectionId?: number; + collectionIds?: number[]; /** 教师配额 */ teacherQuota?: number; /** 学生配额 */ diff --git a/reading-platform-frontend/src/api/generated/model/updateProfileRequest.ts b/reading-platform-frontend/src/api/generated/model/updateProfileRequest.ts new file mode 100644 index 0000000..f1d78d4 --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/updateProfileRequest.ts @@ -0,0 +1,25 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ + +/** + * 修改个人信息请求 + */ +export interface UpdateProfileRequest { + /** + * 姓名 + * @pattern ^[一-龥a-zA-Z\s]{2,20}$ + */ + name?: string; + /** + * 手机号 + * @pattern ^1[3-9]\d{9}$ + */ + phone?: string; + /** 邮箱 */ + email?: string; +} diff --git a/reading-platform-frontend/src/api/generated/model/updateProfileResponse.ts b/reading-platform-frontend/src/api/generated/model/updateProfileResponse.ts new file mode 100644 index 0000000..6cf811e --- /dev/null +++ b/reading-platform-frontend/src/api/generated/model/updateProfileResponse.ts @@ -0,0 +1,17 @@ +/** + * Generated by orval v8.5.3 🍺 + * Do not edit manually. + * Reading Platform API + * Reading Platform Backend Service API Documentation + * OpenAPI spec version: 1.0.0 + */ +import type { UserInfoResponse } from './userInfoResponse'; + +/** + * 修改个人信息响应 + */ +export interface UpdateProfileResponse { + userInfo?: UserInfoResponse; + /** 新的 Token(用于刷新) */ + token?: string; +} diff --git a/reading-platform-frontend/src/api/school.ts b/reading-platform-frontend/src/api/school.ts index 5902931..3ed0ad0 100644 --- a/reading-platform-frontend/src/api/school.ts +++ b/reading-platform-frontend/src/api/school.ts @@ -247,13 +247,23 @@ export const getSchoolStats = () => http.get('/v1/school/stats'); export const getActiveTeachers = (limit?: number) => - http.get>('/v1/school/stats/teachers', { params: { limit } }); + http.get>('/v1/school/stats/teachers', { params: { limit } }); -export const getCourseUsageStats = () => - http.get>('/v1/school/stats/courses'); - -export const getRecentActivities = (limit?: number) => - http.get>('/v1/school/stats/activities', { params: { limit } }); +export const getCourseUsageStats = (startDate?: string, endDate?: string) => { + const params: Record = {}; + if (startDate) params.startDate = startDate; + if (endDate) params.endDate = endDate; + return http.get>('/v1/school/stats/courses', { params }); +}; // ==================== 套餐信息(旧 API,保留兼容) ==================== @@ -632,7 +642,7 @@ export const getCalendarViewData = (params?: { // ==================== 趋势与分布统计 ==================== export interface LessonTrendItem { - month: string; + date: string; // 日期(MM-dd 格式) lessonCount: number; studentCount: number; } @@ -642,8 +652,11 @@ export interface CourseDistributionItem { value: number; } -export const getLessonTrend = (months?: number) => - http.get('/v1/school/stats/lesson-trend', { params: { months } }); +// 后端趋势数据响应(对象数组格式) +export type LessonTrendResponse = LessonTrendItem[]; + +export const getLessonTrend = (days?: number) => + http.get('/v1/school/stats/lesson-trend', { params: { days } }); export const getCourseDistribution = () => http.get('/v1/school/stats/course-distribution'); diff --git a/reading-platform-frontend/src/api/teacher.ts b/reading-platform-frontend/src/api/teacher.ts index b027cff..5f241ea 100644 --- a/reading-platform-frontend/src/api/teacher.ts +++ b/reading-platform-frontend/src/api/teacher.ts @@ -312,59 +312,69 @@ export function batchSaveStudentRecords( // ==================== 教师首页 API ==================== +export interface TeacherDashboardStats { + classCount: number; + studentCount: number; + lessonCount: number; + courseCount: number; +} + +export interface TeacherLessonItem { + id: number; + tenantId: number; + courseId: number; + classId: number; + courseName: string; + className: string; + teacherId: number; + title: string; + lessonDate: string; + startTime: string; + endTime: string; + location: string; + status: string; + notes?: string; + createdAt: string; + updatedAt: string; +} + +export interface TeacherCoursePackageItem { + id: number; + name: string; + description?: string; + gradeLevel?: string; + courseCount?: number; + status: string; + usageCount?: number; + createdAt: string; + updatedAt: string; +} + +export interface TeacherWeeklyStatsData { + lessonCount: number; + studentParticipation: number; + avgRating: number; + totalDuration: number; +} + export interface DashboardData { - stats: { - classCount: number; - studentCount: number; - lessonCount: number; - courseCount: number; - }; - todayLessons: Array<{ - id: number; - courseId: number; - courseName: string; - pictureBookName?: string; - classId: number; - className: string; - plannedDatetime: string; - status: string; - duration: number; - }>; - recommendedCourses: Array<{ - id: number; - name: string; - pictureBookName?: string; - coverImagePath?: string; - duration: number; - usageCount: number; - avgRating: number; - gradeTags: string[]; - }>; - weeklyStats: { - lessonCount: number; - studentParticipation: number; - avgRating: number; - totalDuration: number; - }; - recentActivities: Array<{ - id: number; - type: string; - description: string; - time: string; - }>; + stats: TeacherDashboardStats; + todayLessons: TeacherLessonItem[]; + recommendedCourses: TeacherCoursePackageItem[]; + weeklyStats: TeacherWeeklyStatsData; } export const getTeacherDashboard = () => - http.get('/v1/teacher/dashboard') as any; + http.get('/v1/teacher/dashboard'); export const getTodayLessons = () => - http.get('/v1/teacher/today-lessons') as any; + http.get('/v1/teacher/today-lessons'); export const getRecommendedCourses = () => - http.get('/v1/teacher/recommended-courses') as any; + http.get('/v1/teacher/recommended-courses'); export const getWeeklyStats = () => - http.get('/v1/teacher/weekly-stats') as any; + http.get('/v1/teacher/weekly-stats'); // ==================== 教师统计趋势 ==================== @@ -379,12 +389,33 @@ export interface TeacherCourseUsageItem { value: number; } +// 增强版课程使用统计类型 +export interface CourseUsageQueryParams { + periodType?: 'TODAY' | 'WEEK' | 'MONTH' | 'CUSTOM'; + startDate?: string; + endDate?: string; +} + +export interface CourseUsageStatsItem { + coursePackageId: number; + coursePackageName: string; + usageCount: number; + studentCount: number; + avgDuration: number; + lastUsedAt?: string; +} + export const getTeacherLessonTrend = (months?: number) => { - return http.get('/v1/teacher/lesson-trend', { params: { months } }) as any; + return http.get('/v1/teacher/lesson-trend', { params: { months } }); }; +// 旧版 API(保留向后兼容) export const getTeacherCourseUsage = () => - http.get('/v1/teacher/course-usage') as any; + http.get('/v1/teacher/course-usage'); + +// 增强版课程使用统计 API +export const getTeacherCourseUsageStats = (params?: CourseUsageQueryParams) => + http.get('/v1/teacher/course-usage-stats', { params }); // ==================== 课程反馈 API ==================== diff --git a/reading-platform-frontend/src/views/admin/DashboardView.vue b/reading-platform-frontend/src/views/admin/DashboardView.vue index 1bcc1bb..b033370 100644 --- a/reading-platform-frontend/src/views/admin/DashboardView.vue +++ b/reading-platform-frontend/src/views/admin/DashboardView.vue @@ -107,28 +107,6 @@ - - - - -
-
-
-
-
- - {{ getActivityTypeText(activity.type) }} - - {{ activity.title }} -
- {{ formatTime(activity.time) }} -
-
-
- -
-
-
@@ -167,7 +145,6 @@ let trendChart: echarts.ECharts | null = null; const trendLoading = ref(false); const tenantsLoading = ref(false); const coursesLoading = ref(false); -const activitiesLoading = ref(false); // 统计数据 const statsData = ref({ @@ -187,37 +164,6 @@ const activeTenants = ref([]); // 热门课程包 const popularCourses = ref([]); -// 最近活动 -const recentActivities = ref>([]); - -// Activity colors -const getActivityTagColor = (type: string) => { - const colors: Record = { - lesson: 'blue', - tenant: 'green', - course: 'orange', - }; - return colors[type] || 'default'; -}; - -const getActivityTypeText = (type: string) => { - const texts: Record = { - lesson: '授课', - tenant: '租户', - course: '课程', - }; - return texts[type] || '其他'; -}; - -const formatTime = (time: string) => { - return dayjs(time).format('YYYY-MM-DD HH:mm'); -}; - // Navigation const viewTenantDetail = (id: number) => { router.push(`/admin/tenants?id=${id}`); @@ -393,24 +339,6 @@ const fetchPopularCourses = async () => { } }; -const fetchRecentActivities = async () => { - activitiesLoading.value = true; - try { - const response = await fetch('/api/v1/admin/stats/activities?limit=10', { - headers: { - Authorization: `Bearer ${localStorage.getItem('token')}`, - }, - }); - if (response.ok) { - recentActivities.value = await response.json(); - } - } catch (error) { - console.error('Failed to fetch recent activities:', error); - } finally { - activitiesLoading.value = false; - } -}; - // Handle window resize const handleResize = () => { trendChart?.resize(); @@ -421,7 +349,6 @@ onMounted(() => { fetchTrendData(); fetchActiveTenants(); fetchPopularCourses(); - fetchRecentActivities(); window.addEventListener('resize', handleResize); }); @@ -624,65 +551,6 @@ $bg-light: #F9FAFB; } } } - - // 活动时间线 - .activity-timeline { - .activity-item { - display: flex; - align-items: flex-start; - padding: 12px 0; - border-bottom: 1px solid $border-color; - - &:last-child { - border-bottom: none; - } - - .activity-dot { - width: 10px; - height: 10px; - border-radius: 50%; - margin-top: 6px; - margin-right: 16px; - flex-shrink: 0; - - &.type-lesson { - background: $primary-color; - } - - &.type-tenant { - background: $success-color; - } - - &.type-course { - background: $warning-color; - } - } - - .activity-content { - flex: 1; - - .activity-header { - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 4px; - - .activity-tag { - margin: 0; - } - - .activity-title { - color: $text-color; - } - } - - .activity-time { - font-size: 12px; - color: $text-secondary; - } - } - } - } } @media (max-width: 1200px) { diff --git a/reading-platform-frontend/src/views/profile/ProfileView.vue b/reading-platform-frontend/src/views/profile/ProfileView.vue index 9459c9a..15cb160 100644 --- a/reading-platform-frontend/src/views/profile/ProfileView.vue +++ b/reading-platform-frontend/src/views/profile/ProfileView.vue @@ -34,27 +34,145 @@ {{ profile.email || '-' }} - + +
+ + 编辑资料 + + + 修改密码 + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ") + List> countUsageByTenantAndCourseIds( + @Param("tenantId") Long tenantId, + @Param("courseIds") List courseIds); } diff --git a/reading-platform-java/src/main/java/com/reading/platform/mapper/StudentRecordMapper.java b/reading-platform-java/src/main/java/com/reading/platform/mapper/StudentRecordMapper.java index ce9a6da..1955d31 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/mapper/StudentRecordMapper.java +++ b/reading-platform-java/src/main/java/com/reading/platform/mapper/StudentRecordMapper.java @@ -3,7 +3,48 @@ package com.reading.platform.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.reading.platform.entity.StudentRecord; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.time.LocalDateTime; @Mapper public interface StudentRecordMapper extends BaseMapper { + + /** + * 统计指定时间段内有上课记录的去重学生数 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return 活跃学生数 + */ + @Select("SELECT COUNT(DISTINCT sr.student_id) " + + "FROM student_record sr " + + "INNER JOIN lesson l ON sr.lesson_id = l.id " + + "WHERE l.status = 'COMPLETED' " + + " AND l.end_datetime >= #{startTime} " + + " AND l.end_datetime < #{endTime} " + + " AND l.deleted = 0 " + + " AND sr.deleted = 0") + Long countActiveStudents(@Param("startTime") LocalDateTime startTime, + @Param("endTime") LocalDateTime endTime); + + /** + * 统计指定租户、指定时间段内有上课记录的去重学生数 + * @param tenantId 租户 ID + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return 活跃学生数 + */ + @Select("SELECT COUNT(DISTINCT sr.student_id) " + + "FROM student_record sr " + + "INNER JOIN lesson l ON sr.lesson_id = l.id " + + "WHERE l.tenant_id = #{tenantId} " + + " AND l.status = 'COMPLETED' " + + " AND l.end_datetime >= #{startTime} " + + " AND l.end_datetime < #{endTime} " + + " AND l.deleted = 0 " + + " AND sr.deleted = 0") + Long countActiveStudentsByTenant(@Param("tenantId") Long tenantId, + @Param("startTime") LocalDateTime startTime, + @Param("endTime") LocalDateTime endTime); } diff --git a/reading-platform-java/src/main/java/com/reading/platform/mapper/struct/TeacherStatsMapperStruct.java b/reading-platform-java/src/main/java/com/reading/platform/mapper/struct/TeacherStatsMapperStruct.java new file mode 100644 index 0000000..9eb4790 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/mapper/struct/TeacherStatsMapperStruct.java @@ -0,0 +1,38 @@ +package com.reading.platform.mapper.struct; + +import com.reading.platform.dto.response.*; +import com.reading.platform.entity.CoursePackage; +import com.reading.platform.entity.Lesson; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +/** + * 教师统计转换器 + */ +@Mapper +public interface TeacherStatsMapperStruct { + + TeacherStatsMapperStruct INSTANCE = Mappers.getMapper(TeacherStatsMapperStruct.class); + + /** + * Lesson 转 TeacherLessonVO + */ + TeacherLessonVO toLessonVO(Lesson lesson); + + /** + * List 转 List + */ + List toLessonVOList(List lessons); + + /** + * CoursePackage 转 CoursePackageVO + */ + CoursePackageVO toCoursePackageVO(CoursePackage coursePackage); + + /** + * List 转 List + */ + List toCoursePackageVOList(List coursePackages); +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/AuthService.java b/reading-platform-java/src/main/java/com/reading/platform/service/AuthService.java index bac788e..288c88d 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/service/AuthService.java +++ b/reading-platform-java/src/main/java/com/reading/platform/service/AuthService.java @@ -1,8 +1,10 @@ package com.reading.platform.service; import com.reading.platform.dto.request.LoginRequest; +import com.reading.platform.dto.request.UpdateProfileRequest; import com.reading.platform.dto.response.LoginResponse; import com.reading.platform.dto.response.TokenResponse; +import com.reading.platform.dto.response.UpdateProfileResponse; /** * Auth Service Interface @@ -17,10 +19,30 @@ public interface AuthService { */ Object getCurrentUserInfo(); + /** + * 修改密码 + * @param oldPassword 旧密码 + * @param newPassword 新密码 + */ void changePassword(String oldPassword, String newPassword); + /** + * 修改密码(带 token 失效) + * @param oldPassword 旧密码 + * @param newPassword 新密码 + * @param currentToken 当前 token(用于加入黑名单) + */ + void changePassword(String oldPassword, String newPassword, String currentToken); + void logout(); TokenResponse refreshToken(); + /** + * 修改个人信息 + * @param request 修改请求 + * @return 更新后的用户信息和新 token + */ + UpdateProfileResponse updateProfile(UpdateProfileRequest request); + } diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/SchoolStatsService.java b/reading-platform-java/src/main/java/com/reading/platform/service/SchoolStatsService.java index 1883941..6eda629 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/service/SchoolStatsService.java +++ b/reading-platform-java/src/main/java/com/reading/platform/service/SchoolStatsService.java @@ -1,5 +1,8 @@ package com.reading.platform.service; +import com.reading.platform.dto.response.TeacherActivityRankResponse; + +import java.time.LocalDate; import java.util.List; import java.util.Map; @@ -14,19 +17,21 @@ public interface SchoolStatsService { Map getSchoolStats(Long tenantId); /** - * Get active teachers ranking + * 获取活跃教师排行(自然月统计) + * + * @param tenantId 租户 ID + * @param limit 返回数量限制 + * @return 教师活跃度排行列表 */ - List> getActiveTeachers(Long tenantId, int limit); + List getActiveTeachers(Long tenantId, int limit); /** * Get course usage statistics + * @param tenantId 租户 ID + * @param startDate 开始日期(可选,默认本月 1 号) + * @param endDate 结束日期(可选,默认今天) */ - List> getCourseUsageStats(Long tenantId); - - /** - * Get recent activities - */ - List> getRecentActivities(Long tenantId, int limit); + List> getCourseUsageStats(Long tenantId, LocalDate startDate, LocalDate endDate); /** * Get lesson trend by month diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/StatsService.java b/reading-platform-java/src/main/java/com/reading/platform/service/StatsService.java index 08e11d5..f375de5 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/service/StatsService.java +++ b/reading-platform-java/src/main/java/com/reading/platform/service/StatsService.java @@ -2,10 +2,8 @@ package com.reading.platform.service; import com.reading.platform.dto.request.ActiveTenantsQueryRequest; import com.reading.platform.dto.request.PopularCoursesQueryRequest; -import com.reading.platform.dto.request.RecentActivitiesQueryRequest; import com.reading.platform.dto.response.ActiveTenantItemResponse; import com.reading.platform.dto.response.PopularCourseItemResponse; -import com.reading.platform.dto.response.RecentActivityItemResponse; import com.reading.platform.dto.response.StatsResponse; import com.reading.platform.dto.response.StatsTrendResponse; @@ -35,9 +33,4 @@ public interface StatsService { * 获取热门课程 */ List getPopularCourses(PopularCoursesQueryRequest request); - - /** - * 获取最近活动 - */ - List getRecentActivities(RecentActivitiesQueryRequest request); } \ No newline at end of file diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/TeacherStatsService.java b/reading-platform-java/src/main/java/com/reading/platform/service/TeacherStatsService.java index d811f59..8928f5c 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/service/TeacherStatsService.java +++ b/reading-platform-java/src/main/java/com/reading/platform/service/TeacherStatsService.java @@ -1,43 +1,57 @@ package com.reading.platform.service; -import com.reading.platform.entity.CoursePackage; -import com.reading.platform.entity.Lesson; +import com.reading.platform.dto.request.CourseUsageQuery; +import com.reading.platform.dto.response.CoursePackageVO; +import com.reading.platform.dto.response.CourseUsageStatsVO; +import com.reading.platform.dto.response.CourseUsageVO; +import com.reading.platform.dto.response.TeacherDashboardResponse; +import com.reading.platform.dto.response.TeacherLessonTrendVO; +import com.reading.platform.dto.response.TeacherLessonVO; +import com.reading.platform.dto.response.TeacherWeeklyStatsResponse; import java.util.List; -import java.util.Map; /** - * Teacher Statistics Service Interface + * 教师统计服务接口 */ public interface TeacherStatsService { /** - * Get teacher dashboard statistics + * 获取教师仪表盘统计数据 */ - Map getDashboard(Long teacherId, Long tenantId); + TeacherDashboardResponse getDashboard(Long teacherId, Long tenantId); /** - * Get today's lessons + * 获取今日课程 */ - List getTodayLessons(Long teacherId); + List getTodayLessons(Long teacherId); /** - * Get recommended courses + * 获取推荐课程 */ - List getRecommendedCourses(Long tenantId); + List getRecommendedCourses(Long tenantId); /** - * Get weekly statistics + * 获取本周统计 */ - Map getWeeklyStats(Long teacherId); + TeacherWeeklyStatsResponse getWeeklyStats(Long teacherId); /** - * Get lesson trend by month + * 获取授课趋势 */ - List> getLessonTrend(Long teacherId, int months); + List getLessonTrend(Long teacherId, int months); /** - * Get course usage statistics + * 获取课程使用统计(旧版) */ - List> getCourseUsage(Long tenantId); + List getCourseUsage(Long tenantId); + + /** + * 获取课程使用统计(增强版) + * @param tenantId 租户 ID + * @param teacherId 教师 ID + * @param query 查询参数 + * @return 课程使用统计列表 + */ + List getCourseUsageStats(Long tenantId, Long teacherId, CourseUsageQuery query); } diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/impl/AuthServiceImpl.java b/reading-platform-java/src/main/java/com/reading/platform/service/impl/AuthServiceImpl.java index badb4e3..c910224 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/service/impl/AuthServiceImpl.java +++ b/reading-platform-java/src/main/java/com/reading/platform/service/impl/AuthServiceImpl.java @@ -10,8 +10,11 @@ import com.reading.platform.common.security.JwtTokenProvider; import com.reading.platform.common.security.JwtTokenRedisService; import com.reading.platform.common.security.SecurityUtils; import com.reading.platform.dto.request.LoginRequest; +import com.reading.platform.dto.request.UpdateProfileRequest; import com.reading.platform.dto.response.LoginResponse; import com.reading.platform.dto.response.TokenResponse; +import com.reading.platform.dto.response.UpdateProfileResponse; +import com.reading.platform.dto.response.UserInfoResponse; import com.reading.platform.entity.AdminUser; import com.reading.platform.entity.Parent; import com.reading.platform.entity.Tenant; @@ -25,6 +28,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; import java.time.LocalDateTime; @@ -459,6 +463,20 @@ public class AuthServiceImpl implements AuthService { } } + @Override + public void changePassword(String oldPassword, String newPassword, String currentToken) { + // 先修改密码 + changePassword(oldPassword, newPassword); + + // 将当前 token 加入黑名单 + if (StringUtils.hasText(currentToken)) { + // 获取 token 剩余过期时间 + long remainingTime = jwtTokenProvider.getRemainingExpiration(currentToken); + jwtTokenRedisService.addToBlacklist(currentToken, remainingTime); + log.info("Token 已加入黑名单,token: {}, 剩余过期时间:{}秒", currentToken, remainingTime); + } + } + @Override public void logout() { JwtPayload payload = SecurityUtils.getCurrentUser(); @@ -480,4 +498,146 @@ public class AuthServiceImpl implements AuthService { .build(); } + @Override + public UpdateProfileResponse updateProfile(UpdateProfileRequest request) { + log.info("开始修改个人信息,用户 ID: {}", SecurityUtils.getCurrentUser().getUserId()); + + JwtPayload payload = SecurityUtils.getCurrentUser(); + String role = payload.getRole(); + Long userId = payload.getUserId(); + + // 根据角色更新对应表的字段 + switch (role) { + case "admin" -> { + AdminUser adminUser = adminUserMapper.selectById(userId); + if (request.getName() != null) { + adminUser.setName(request.getName()); + } + if (request.getPhone() != null) { + adminUser.setPhone(request.getPhone()); + } + if (request.getEmail() != null) { + adminUser.setEmail(request.getEmail()); + } + adminUserMapper.updateById(adminUser); + log.info("管理员信息修改成功,用户 ID: {}", userId); + } + case "school" -> { + Tenant tenant = tenantMapper.selectById(userId); + if (request.getName() != null) { + tenant.setContactName(request.getName()); + } + if (request.getPhone() != null) { + tenant.setContactPhone(request.getPhone()); + } + if (request.getEmail() != null) { + tenant.setContactEmail(request.getEmail()); + } + tenantMapper.updateById(tenant); + log.info("租户信息修改成功,用户 ID: {}", userId); + } + case "teacher" -> { + Teacher teacher = teacherMapper.selectById(userId); + if (request.getName() != null) { + teacher.setName(request.getName()); + } + if (request.getPhone() != null) { + teacher.setPhone(request.getPhone()); + } + if (request.getEmail() != null) { + teacher.setEmail(request.getEmail()); + } + teacherMapper.updateById(teacher); + log.info("教师信息修改成功,用户 ID: {}", userId); + } + case "parent" -> { + Parent parent = parentMapper.selectById(userId); + if (request.getName() != null) { + parent.setName(request.getName()); + } + if (request.getPhone() != null) { + parent.setPhone(request.getPhone()); + } + if (request.getEmail() != null) { + parent.setEmail(request.getEmail()); + } + parentMapper.updateById(parent); + log.info("家长信息修改成功,用户 ID: {}", userId); + } + default -> throw new BusinessException(ErrorCode.USER_NOT_FOUND); + } + + // 生成新的 token + JwtPayload currentPayload = SecurityUtils.getCurrentUser(); + String newToken = jwtTokenProvider.generateToken(currentPayload); + + // 更新 Redis 中的 token + jwtTokenRedisService.storeToken(currentPayload.getUsername(), newToken, jwtTokenProvider.getExpiration()); + + // 获取更新后的用户信息 + Object updatedUserInfo = getCurrentUserInfo(); + UserInfoResponse userInfoResponse = convertToUserInfoResponse(updatedUserInfo, role); + + return UpdateProfileResponse.builder() + .userInfo(userInfoResponse) + .token(newToken) + .build(); + } + + /** + * 将 Entity 对象转换为 UserInfoResponse + */ + private UserInfoResponse convertToUserInfoResponse(Object userInfo, String role) { + if (userInfo instanceof Tenant) { + Tenant tenant = (Tenant) userInfo; + return UserInfoResponse.builder() + .id(tenant.getId()) + .username(tenant.getUsername()) + .name(tenant.getName()) + .email(tenant.getContactEmail()) + .phone(tenant.getContactPhone()) + .avatarUrl(tenant.getLogoUrl()) + .role("school") + .tenantId(tenant.getId()) + .build(); + } else if (userInfo instanceof Teacher) { + Teacher teacher = (Teacher) userInfo; + return UserInfoResponse.builder() + .id(teacher.getId()) + .username(teacher.getUsername()) + .name(teacher.getName()) + .email(teacher.getEmail()) + .phone(teacher.getPhone()) + .avatarUrl(teacher.getAvatarUrl()) + .role("teacher") + .tenantId(teacher.getTenantId()) + .build(); + } else if (userInfo instanceof Parent) { + Parent parent = (Parent) userInfo; + return UserInfoResponse.builder() + .id(parent.getId()) + .username(parent.getUsername()) + .name(parent.getName()) + .email(parent.getEmail()) + .phone(parent.getPhone()) + .avatarUrl(parent.getAvatarUrl()) + .role("parent") + .tenantId(parent.getTenantId()) + .build(); + } else if (userInfo instanceof AdminUser) { + AdminUser adminUser = (AdminUser) userInfo; + return UserInfoResponse.builder() + .id(adminUser.getId()) + .username(adminUser.getUsername()) + .name(adminUser.getName()) + .email(adminUser.getEmail()) + .phone(adminUser.getPhone()) + .avatarUrl(adminUser.getAvatarUrl()) + .role("admin") + .tenantId(null) + .build(); + } + return null; + } + } diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/impl/SchoolStatsServiceImpl.java b/reading-platform-java/src/main/java/com/reading/platform/service/impl/SchoolStatsServiceImpl.java index 616d9e2..877c586 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/service/impl/SchoolStatsServiceImpl.java +++ b/reading-platform-java/src/main/java/com/reading/platform/service/impl/SchoolStatsServiceImpl.java @@ -1,20 +1,30 @@ package com.reading.platform.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.reading.platform.dto.response.TeacherActivityRankResponse; +import com.reading.platform.dto.response.CourseUsageStatsVO; import com.reading.platform.entity.Clazz; import com.reading.platform.entity.Student; import com.reading.platform.entity.Teacher; +import com.reading.platform.entity.Lesson; +import com.reading.platform.common.enums.LessonStatus; +import com.reading.platform.common.enums.TeacherActivityLevel; import com.reading.platform.mapper.ClazzMapper; import com.reading.platform.mapper.StudentMapper; import com.reading.platform.mapper.TeacherMapper; +import com.reading.platform.mapper.LessonMapper; +import com.reading.platform.mapper.StudentRecordMapper; +import com.reading.platform.mapper.CoursePackageMapper; import com.reading.platform.service.SchoolStatsService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; +import java.util.stream.Collectors; /** * 学校统计服务实现类 @@ -27,6 +37,9 @@ public class SchoolStatsServiceImpl implements SchoolStatsService { private final TeacherMapper teacherMapper; private final StudentMapper studentMapper; private final ClazzMapper clazzMapper; + private final LessonMapper lessonMapper; + private final StudentRecordMapper studentRecordMapper; + private final CoursePackageMapper coursePackageMapper; @Override public Map getSchoolStats(Long tenantId) { @@ -40,44 +53,157 @@ public class SchoolStatsServiceImpl implements SchoolStatsService { stats.put("classCount", clazzMapper.selectCount( new LambdaQueryWrapper().eq(Clazz::getTenantId, tenantId) )); - stats.put("lessonCount", 0); // TODO: implement lesson count + + // 月授课次数:本月自然月内该租户 COMPLETED 状态的 lesson 总数 + LocalDateTime monthStart = LocalDateTime.now().withDayOfMonth(1).withHour(0).withMinute(0).withSecond(0); + LocalDateTime monthEnd = monthStart.plusMonths(1); + Long monthlyLessons = lessonMapper.selectCount( + new LambdaQueryWrapper() + .eq(Lesson::getTenantId, tenantId) + .eq(Lesson::getStatus, LessonStatus.COMPLETED.getCode()) + .ge(Lesson::getEndDatetime, monthStart) + .lt(Lesson::getEndDatetime, monthEnd) + ); + stats.put("lessonCount", monthlyLessons != null ? monthlyLessons.intValue() : 0); + return stats; } @Override - public List> getActiveTeachers(Long tenantId, int limit) { - List> teachers = new ArrayList<>(); - // For now, return empty list - return teachers; + public List getActiveTeachers(Long tenantId, int limit) { + // 自然月统计:从当月 1 号开始 + LocalDateTime monthStart = LocalDateTime.now() + .withDayOfMonth(1) + .withHour(0) + .withMinute(0) + .withSecond(0) + .withNano(0); + + log.info("获取活跃教师排行,租户 ID: {}, 统计周期开始时间:{}", tenantId, monthStart); + + // 调用 Mapper 查询原始数据 + List> rawData = lessonMapper.getTeacherActivityRank(tenantId, monthStart, limit); + + if (rawData == null || rawData.isEmpty()) { + log.info("未查询到活跃教师数据"); + return Collections.emptyList(); + } + + // 转换为响应对象 + return rawData.stream().map(row -> { + Integer lessonCount = convertToInt(row.get("lessonCount")); + Integer courseCount = convertToInt(row.get("courseCount")); + LocalDateTime lastActiveAt = (LocalDateTime) row.get("lastActiveAt"); + String classNames = (String) row.get("classNames"); + + // 根据授课次数计算活跃度等级 + TeacherActivityLevel activityLevel = TeacherActivityLevel.fromLessonCount(lessonCount); + + return TeacherActivityRankResponse.builder() + .teacherId((Long) row.get("teacherId")) + .teacherName((String) row.get("teacherName")) + .classNames(classNames != null ? classNames : "") + .lessonCount(lessonCount) + .courseCount(courseCount) + .lastActiveAt(lastActiveAt) + .activityLevelCode(activityLevel.getCode()) + .activityLevelDesc(activityLevel.getDescription()) + .build(); + }).collect(Collectors.toList()); + } + + /** + * 将 Object 转换为 Integer,处理 null 和 Number 类型 + */ + private Integer convertToInt(Object value) { + if (value == null) { + return 0; + } + if (value instanceof Number) { + return ((Number) value).intValue(); + } + return 0; } @Override - public List> getCourseUsageStats(Long tenantId) { - List> courses = new ArrayList<>(); - // For now, return empty list - return courses; - } + public List> getCourseUsageStats(Long tenantId, LocalDate startDate, LocalDate endDate) { + // 计算时间范围 + LocalDateTime startTime; + LocalDateTime endTime; - @Override - public List> getRecentActivities(Long tenantId, int limit) { - List> activities = new ArrayList<>(); - // For now, return empty list - return activities; + if (startDate != null) { + startTime = startDate.atStartOfDay(); + } else { + // 默认本月 1 号 + startTime = LocalDateTime.now().withDayOfMonth(1).withHour(0).withMinute(0).withSecond(0).withNano(0); + } + + if (endDate != null) { + endTime = endDate.atTime(23, 59, 59); + } else { + // 默认今天 + endTime = LocalDateTime.now(); + } + + // 调用 Mapper 查询(教师 ID 为 null 表示统计全校) + List stats = lessonMapper.getCourseUsageStats( + tenantId, null, startTime, endTime + ); + + log.info("获取学校课程使用统计,租户 ID: {}, 统计周期:{} ~ {}, 返回{}条数据", + tenantId, startTime, endTime, stats != null ? stats.size() : 0); + + // 转换为 Map 格式返回(兼容前端现有类型) + if (stats == null || stats.isEmpty()) { + return new ArrayList<>(); + } + + return stats.stream().map(vo -> { + Map map = new HashMap<>(); + map.put("courseId", vo.getCoursePackageId()); + map.put("courseName", vo.getCoursePackageName()); + map.put("usageCount", vo.getUsageCount()); + map.put("studentCount", vo.getStudentCount() != null ? vo.getStudentCount() : 0); + map.put("avgDuration", vo.getAvgDuration() != null ? vo.getAvgDuration() : 0); + map.put("lastUsedAt", vo.getLastUsedAt()); + return map; + }).collect(Collectors.toList()); } @Override public List> getLessonTrend(Long tenantId, int months) { - List> trend = new ArrayList<>(); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM"); + // 转换为天数,与超管端保持一致(最近 7 天) + int days = (months <= 0) ? 7 : Math.min(months, 30); // 限制最多 30 天 - for (int i = months - 1; i >= 0; i--) { - LocalDate date = LocalDate.now().minusMonths(i); - String month = date.format(formatter); + LocalDateTime now = LocalDateTime.now(); + List> trend = new ArrayList<>(); + DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("MM-dd"); + + // 获取最近 N 天的趋势数据 + for (int i = days - 1; i >= 0; i--) { + LocalDate date = now.minusDays(i).toLocalDate(); + String dateStr = date.format(dateFormatter); + + // 当天时间范围 + LocalDateTime dayStart = date.atStartOfDay(); + LocalDateTime dayEnd = date.plusDays(1).atStartOfDay(); + + // 当天完成的授课次数(按租户过滤,使用 LambdaQueryWrapper 和 Entity) + Long lessons = lessonMapper.selectCount( + new LambdaQueryWrapper() + .eq(Lesson::getTenantId, tenantId) + .eq(Lesson::getStatus, LessonStatus.COMPLETED.getCode()) + .ge(Lesson::getEndDatetime, dayStart) + .lt(Lesson::getEndDatetime, dayEnd) + ); + + // 当天活跃学生数(按租户过滤) + Long activeStudents = studentRecordMapper.countActiveStudentsByTenant(tenantId, dayStart, dayEnd); Map item = new HashMap<>(); - item.put("month", month); - item.put("lessonCount", 0); - item.put("studentCount", 0); + item.put("date", dateStr); + item.put("lessonCount", lessons != null ? lessons.intValue() : 0); + item.put("studentCount", activeStudents != null ? activeStudents.intValue() : 0); trend.add(item); } @@ -86,8 +212,19 @@ public class SchoolStatsServiceImpl implements SchoolStatsService { @Override public List> getCourseDistribution(Long tenantId) { - List> distribution = new ArrayList<>(); - // For now, return empty list - return distribution; + // 调用 Mapper 查询课程分布数据 + List> result = coursePackageMapper.getCourseDistribution(tenantId); + + // 处理空数据情况,确保返回空列表而不是 null + if (result == null) { + return new ArrayList<>(); + } + + // 限制返回数量,最多返回前 10 个课程包 + if (result.size() > 10) { + return result.subList(0, 10); + } + + return result; } } diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/impl/StatsServiceImpl.java b/reading-platform-java/src/main/java/com/reading/platform/service/impl/StatsServiceImpl.java index c481fee..2a379b4 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/service/impl/StatsServiceImpl.java +++ b/reading-platform-java/src/main/java/com/reading/platform/service/impl/StatsServiceImpl.java @@ -3,22 +3,24 @@ package com.reading.platform.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.reading.platform.dto.request.ActiveTenantsQueryRequest; import com.reading.platform.dto.request.PopularCoursesQueryRequest; -import com.reading.platform.dto.request.RecentActivitiesQueryRequest; import com.reading.platform.dto.response.ActiveTenantItemResponse; import com.reading.platform.dto.response.PopularCourseItemResponse; -import com.reading.platform.dto.response.RecentActivityItemResponse; import com.reading.platform.dto.response.StatsResponse; import com.reading.platform.dto.response.StatsTrendResponse; import com.reading.platform.entity.CoursePackage; -import com.reading.platform.entity.CourseLesson; import com.reading.platform.entity.Student; import com.reading.platform.entity.Teacher; import com.reading.platform.entity.Tenant; -import com.reading.platform.mapper.CourseLessonMapper; +import com.reading.platform.entity.Lesson; +import com.reading.platform.common.enums.LessonStatus; +import com.reading.platform.common.enums.TenantStatus; import com.reading.platform.mapper.CoursePackageMapper; +import com.reading.platform.mapper.CourseLessonMapper; +import com.reading.platform.mapper.LessonMapper; import com.reading.platform.mapper.StudentMapper; import com.reading.platform.mapper.TeacherMapper; import com.reading.platform.mapper.TenantMapper; +import com.reading.platform.mapper.StudentRecordMapper; import com.reading.platform.service.StatsService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -28,7 +30,6 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; -import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; @@ -45,6 +46,21 @@ public class StatsServiceImpl implements StatsService { private final StudentMapper studentMapper; private final CoursePackageMapper courseMapper; private final CourseLessonMapper courseLessonMapper; + private final LessonMapper lessonMapper; + private final StudentRecordMapper studentRecordMapper; + + // ==================== 常量定义 ==================== + /** 活跃教师统计时间窗口(天) */ + private static final int ACTIVE_TEACHER_WINDOW_DAYS = 30; + + /** 活跃租户 TOP 数量 */ + private static final int DEFAULT_TOP_LIMIT = 5; + + /** 活跃教师数权重 */ + private static final double WEIGHT_ACTIVE_TEACHER = 1.0; + + /** 完成课次数权重 */ + private static final double WEIGHT_COMPLETED_LESSON = 0.1; @Override public StatsResponse getStats() { @@ -55,7 +71,7 @@ public class StatsServiceImpl implements StatsService { // 活跃租户数 Long activeTenants = tenantMapper.selectCount( - new LambdaQueryWrapper().eq(Tenant::getStatus, "ACTIVE") + new LambdaQueryWrapper().eq(Tenant::getStatus, TenantStatus.ACTIVE.getCode()) ); // 教师总数 @@ -67,9 +83,19 @@ public class StatsServiceImpl implements StatsService { // 课程总数 Long totalCourses = courseMapper.selectCount(null); - // 课时总数 + // 课时总数(course_lesson 表记录数) Long totalLessons = courseLessonMapper.selectCount(null); + // 月授课次数:本月自然月内 COMPLETED 状态的 lesson 总数 + LocalDateTime monthStart = LocalDateTime.now().withDayOfMonth(1).withHour(0).withMinute(0).withSecond(0); + LocalDateTime monthEnd = monthStart.plusMonths(1); + Long monthlyLessons = lessonMapper.selectCount( + new LambdaQueryWrapper() + .eq(Lesson::getStatus, LessonStatus.COMPLETED.getCode()) + .ge(Lesson::getEndDatetime, monthStart) + .lt(Lesson::getEndDatetime, monthEnd) + ); + return StatsResponse.builder() .totalTenants(totalTenants) .activeTenants(activeTenants) @@ -77,6 +103,7 @@ public class StatsServiceImpl implements StatsService { .totalStudents(totalStudents) .totalCourses(totalCourses) .totalLessons(totalLessons) + .monthlyLessons(monthlyLessons) .build(); } @@ -86,86 +113,94 @@ public class StatsServiceImpl implements StatsService { LocalDateTime now = LocalDateTime.now(); List dates = new ArrayList<>(); - List newStudents = new ArrayList<>(); - List newTeachers = new ArrayList<>(); - List newCourses = new ArrayList<>(); + List lessonCounts = new ArrayList<>(); + List studentCounts = new ArrayList<>(); // 获取最近 7 天的趋势数据 for (int i = 6; i >= 0; i--) { LocalDate date = now.minusDays(i).toLocalDate(); dates.add(date.format(DateTimeFormatter.ofPattern("MM-dd"))); - // 当天新增学生数 + // 当天时间范围 LocalDateTime dayStart = date.atStartOfDay(); LocalDateTime dayEnd = date.plusDays(1).atStartOfDay(); - Long students = studentMapper.selectCount( - new LambdaQueryWrapper() - .ge(Student::getCreatedAt, dayStart) - .lt(Student::getCreatedAt, dayEnd) + // 当天完成的授课次数 + Long lessons = lessonMapper.selectCount( + new LambdaQueryWrapper() + .eq(Lesson::getStatus, LessonStatus.COMPLETED.getCode()) + .ge(Lesson::getEndDatetime, dayStart) + .lt(Lesson::getEndDatetime, dayEnd) ); - newStudents.add(students != null ? students.intValue() : 0); + lessonCounts.add(lessons != null ? lessons.intValue() : 0); - // 当天新增教师数 - Long teachers = teacherMapper.selectCount( - new LambdaQueryWrapper() - .ge(Teacher::getCreatedAt, dayStart) - .lt(Teacher::getCreatedAt, dayEnd) - ); - newTeachers.add(teachers != null ? teachers.intValue() : 0); - - // 当天新增课程数 - Long courses = courseMapper.selectCount( - new LambdaQueryWrapper() - .ge(CoursePackage::getCreatedAt, dayStart) - .lt(CoursePackage::getCreatedAt, dayEnd) - ); - newCourses.add(courses != null ? courses.intValue() : 0); + // 当天活跃学生数(有完成课程记录的去重学生数) + Long activeStudents = studentRecordMapper.countActiveStudents(dayStart, dayEnd); + studentCounts.add(activeStudents != null ? activeStudents.intValue() : 0); } return StatsTrendResponse.builder() .dates(dates) - .newStudents(newStudents) - .newTeachers(newTeachers) - .newCourses(newCourses) + .lessonCounts(lessonCounts) + .studentCounts(studentCounts) .build(); } @Override public List getActiveTenants(ActiveTenantsQueryRequest request) { - log.info("获取活跃租户,limit={}", request != null ? request.getLimit() : 10); + int limit = request != null && request.getLimit() != null ? request.getLimit() : DEFAULT_TOP_LIMIT; - int limit = request != null && request.getLimit() != null ? request.getLimit() : 10; + // 计算时间窗口:近 30 天 + LocalDateTime windowStart = LocalDateTime.now().minusDays(ACTIVE_TEACHER_WINDOW_DAYS); - // 查询所有活跃租户 + // 1. 查询所有状态为 ACTIVE 的租户 List tenants = tenantMapper.selectList( new LambdaQueryWrapper() - .eq(Tenant::getStatus, "ACTIVE") - .orderByDesc(Tenant::getUpdatedAt) - .last("LIMIT " + limit) + .eq(Tenant::getStatus, TenantStatus.ACTIVE.getCode()) ); - return tenants.stream().map(tenant -> { - // 查询该租户的活跃用户数(教师+学生) - Long teacherCount = teacherMapper.selectCount( - new LambdaQueryWrapper().eq(Teacher::getTenantId, tenant.getId()) - ); - Long studentCount = studentMapper.selectCount( - new LambdaQueryWrapper().eq(Student::getTenantId, tenant.getId()) + // 2. 对每个租户统计活跃教师数和完成课次数 + List resultList = tenants.stream().map(tenant -> { + // 2.1 查询该租户下近 30 天完成状态的课程记录 + List lessons = lessonMapper.selectList( + new LambdaQueryWrapper() + .eq(Lesson::getTenantId, tenant.getId()) + .eq(Lesson::getStatus, LessonStatus.COMPLETED.getCode()) + .ge(Lesson::getEndDatetime, windowStart) + .isNotNull(Lesson::getTeacherId) ); - // 查询该租户使用的课程数(通过租户套餐) - // 简化处理,返回 0 - int courseCount = 0; + // 2.2 去重统计活跃教师数(近 30 天有完成课程的老师数) + long distinctTeacherCount = lessons.stream() + .map(Lesson::getTeacherId) + .filter(java.util.Objects::nonNull) + .distinct() + .count(); + + // 2.3 统计完成课次数(近 30 天 COMPLETED 状态的 lesson 总数) + long completedLessonCount = lessons.size(); return ActiveTenantItemResponse.builder() .tenantId(tenant.getId()) .tenantName(tenant.getName()) - .activeUsers((teacherCount != null ? teacherCount.intValue() : 0) + - (studentCount != null ? studentCount.intValue() : 0)) - .courseCount(courseCount) + .activeTeacherCount((int) distinctTeacherCount) + .completedLessonCount((int) completedLessonCount) .build(); }).collect(Collectors.toList()); + + // 3. 按加权分数降序排序:score = activeTeacherCount * 1.0 + completedLessonCount * 0.1 + resultList.sort((a, b) -> { + double scoreA = a.getActiveTeacherCount() * WEIGHT_ACTIVE_TEACHER + + a.getCompletedLessonCount() * WEIGHT_COMPLETED_LESSON; + double scoreB = b.getActiveTeacherCount() * WEIGHT_ACTIVE_TEACHER + + b.getCompletedLessonCount() * WEIGHT_COMPLETED_LESSON; + return Double.compare(scoreB, scoreA); // 降序 + }); + + // 4. 返回 TOP N + return resultList.stream() + .limit(limit) + .collect(Collectors.toList()); } @Override @@ -190,35 +225,4 @@ public class StatsServiceImpl implements StatsService { .build() ).collect(Collectors.toList()); } - - @Override - public List getRecentActivities(RecentActivitiesQueryRequest request) { - log.info("获取最近活动,limit={}", request != null ? request.getLimit() : 10); - - int limit = request != null && request.getLimit() != null ? request.getLimit() : 10; - - // 由于没有专门的活动记录表,这里返回空列表 - // 实际项目中应该从操作日志表获取 - List activities = new ArrayList<>(); - - // 可以从最近的课程更新中生成一些活动记录 - List recentCourses = courseMapper.selectList( - new LambdaQueryWrapper() - .orderByDesc(CoursePackage::getUpdatedAt) - .last("LIMIT " + limit) - ); - - for (CoursePackage course : recentCourses) { - activities.add(RecentActivityItemResponse.builder() - .activityId(course.getId()) - .activityType("COURSE_UPDATE") - .description("课程「" + course.getName() + "」已更新") - .operatorId(course.getSubmittedBy()) - .operatorName("系统") - .operationTime(course.getUpdatedAt()) - .build()); - } - - return activities; - } -} \ No newline at end of file +} diff --git a/reading-platform-java/src/main/java/com/reading/platform/service/impl/TeacherStatsServiceImpl.java b/reading-platform-java/src/main/java/com/reading/platform/service/impl/TeacherStatsServiceImpl.java index c0f36ce..fc45e1a 100644 --- a/reading-platform-java/src/main/java/com/reading/platform/service/impl/TeacherStatsServiceImpl.java +++ b/reading-platform-java/src/main/java/com/reading/platform/service/impl/TeacherStatsServiceImpl.java @@ -2,6 +2,8 @@ package com.reading.platform.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.reading.platform.common.enums.CourseStatus; +import com.reading.platform.dto.request.CourseUsageQuery; +import com.reading.platform.dto.response.*; import com.reading.platform.entity.Clazz; import com.reading.platform.entity.CoursePackage; import com.reading.platform.entity.Lesson; @@ -10,12 +12,15 @@ import com.reading.platform.mapper.ClazzMapper; import com.reading.platform.mapper.CoursePackageMapper; import com.reading.platform.mapper.LessonMapper; import com.reading.platform.mapper.StudentMapper; +import com.reading.platform.mapper.struct.TeacherStatsMapperStruct; import com.reading.platform.service.TeacherStatsService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.time.DayOfWeek; import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; @@ -33,20 +38,19 @@ public class TeacherStatsServiceImpl implements TeacherStatsService { private final LessonMapper lessonMapper; @Override - public Map getDashboard(Long teacherId, Long tenantId) { - Map dashboard = new HashMap<>(); - + public TeacherDashboardResponse getDashboard(Long teacherId, Long tenantId) { // 基础统计 - Map stats = new HashMap<>(); - stats.put("classCount", clazzMapper.selectCount( - new LambdaQueryWrapper().eq(Clazz::getTenantId, tenantId) - )); - stats.put("studentCount", studentMapper.selectCount( - new LambdaQueryWrapper().eq(Student::getTenantId, tenantId) - )); - stats.put("courseCount", courseMapper.selectCount( - new LambdaQueryWrapper().eq(CoursePackage::getTenantId, tenantId) - )); + TeacherDashboardResponse.TeacherStats stats = TeacherDashboardResponse.TeacherStats.builder() + .classCount(clazzMapper.selectCount( + new LambdaQueryWrapper().eq(Clazz::getTenantId, tenantId) + )) + .studentCount(studentMapper.selectCount( + new LambdaQueryWrapper().eq(Student::getTenantId, tenantId) + )) + .courseCount(courseMapper.selectCount( + new LambdaQueryWrapper().eq(CoursePackage::getTenantId, tenantId) + )) + .build(); // Lesson count (handle missing table gracefully) long lessonCount = 0; @@ -57,62 +61,31 @@ public class TeacherStatsServiceImpl implements TeacherStatsService { } catch (Exception e) { log.warn("Failed to query lessons table: {}", e.getMessage()); } - stats.put("lessonCount", lessonCount); - dashboard.put("stats", stats); + stats.setLessonCount(lessonCount); // 今日课程 - LocalDate today = LocalDate.now(); - List todayLessons = new ArrayList<>(); - try { - todayLessons = lessonMapper.selectList( - new LambdaQueryWrapper() - .eq(Lesson::getTeacherId, teacherId) - .eq(Lesson::getLessonDate, today) - .orderByAsc(Lesson::getStartTime) - ); - } catch (Exception e) { - log.warn("Failed to query today lessons: {}", e.getMessage()); - } - dashboard.put("todayLessons", todayLessons); + List todayLessons = getTodayLessons(teacherId); // 推荐课程(热门课程) - List recommendedCourses = courseMapper.selectList( - new LambdaQueryWrapper() - .eq(CoursePackage::getTenantId, tenantId) - .eq(CoursePackage::getStatus, CourseStatus.PUBLISHED.getCode()) - .orderByDesc(CoursePackage::getUsageCount) - .last("LIMIT 5") - ); - dashboard.put("recommendedCourses", recommendedCourses); + List recommendedCourses = getRecommendedCourses(tenantId); // 本周统计 - Map weeklyStats = new HashMap<>(); - LocalDate weekAgo = LocalDate.now().minusDays(7); - long weeklyLessonCount = 0; - try { - weeklyLessonCount = lessonMapper.selectCount( - new LambdaQueryWrapper() - .eq(Lesson::getTeacherId, teacherId) - .ge(Lesson::getLessonDate, weekAgo) - ); - } catch (Exception e) { - log.warn("Failed to query weekly lessons: {}", e.getMessage()); - } - weeklyStats.put("lessonCount", weeklyLessonCount); - weeklyStats.put("studentParticipation", 85); // TODO: calculate actual participation - weeklyStats.put("avgRating", 4.5); // TODO: calculate actual rating - weeklyStats.put("totalDuration", 300); // TODO: calculate actual duration - dashboard.put("weeklyStats", weeklyStats); + TeacherWeeklyStatsResponse weeklyStats = getWeeklyStats(teacherId); - // 近期活动 - List> recentActivities = new ArrayList<>(); - dashboard.put("recentActivities", recentActivities); + // 近期活动(暂空) + List recentActivities = new ArrayList<>(); - return dashboard; + return TeacherDashboardResponse.builder() + .stats(stats) + .todayLessons(todayLessons) + .recommendedCourses(recommendedCourses) + .weeklyStats(weeklyStats) + .recentActivities(recentActivities) + .build(); } @Override - public List getTodayLessons(Long teacherId) { + public List getTodayLessons(Long teacherId) { LocalDate today = LocalDate.now(); List lessons = new ArrayList<>(); try { @@ -125,24 +98,24 @@ public class TeacherStatsServiceImpl implements TeacherStatsService { } catch (Exception e) { log.warn("Failed to query today lessons: {}", e.getMessage()); } - return lessons; + return TeacherStatsMapperStruct.INSTANCE.toLessonVOList(lessons); } @Override - public List getRecommendedCourses(Long tenantId) { - return courseMapper.selectList( + public List getRecommendedCourses(Long tenantId) { + List coursePackages = courseMapper.selectList( new LambdaQueryWrapper() .eq(CoursePackage::getTenantId, tenantId) .eq(CoursePackage::getStatus, CourseStatus.PUBLISHED.getCode()) .orderByDesc(CoursePackage::getUsageCount) .last("LIMIT 10") ); + return TeacherStatsMapperStruct.INSTANCE.toCoursePackageVOList(coursePackages); } @Override - public Map getWeeklyStats(Long teacherId) { + public TeacherWeeklyStatsResponse getWeeklyStats(Long teacherId) { LocalDate weekAgo = LocalDate.now().minusDays(7); - Map stats = new HashMap<>(); long lessonCount = 0; try { lessonCount = lessonMapper.selectCount( @@ -153,16 +126,18 @@ public class TeacherStatsServiceImpl implements TeacherStatsService { } catch (Exception e) { log.warn("Failed to query weekly lesson count: {}", e.getMessage()); } - stats.put("lessonCount", lessonCount); - stats.put("studentParticipation", 85); - stats.put("avgRating", 4.5); - stats.put("totalDuration", 300); - return stats; + + return TeacherWeeklyStatsResponse.builder() + .lessonCount(lessonCount) + .studentParticipation(85) // TODO: calculate actual participation + .avgRating(4.5) // TODO: calculate actual rating + .totalDuration(300) // TODO: calculate actual duration + .build(); } @Override - public List> getLessonTrend(Long teacherId, int months) { - List> trend = new ArrayList<>(); + public List getLessonTrend(Long teacherId, int months) { + List trend = new ArrayList<>(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM"); for (int i = months - 1; i >= 0; i--) { @@ -183,19 +158,19 @@ public class TeacherStatsServiceImpl implements TeacherStatsService { log.warn("Failed to query lesson trend for {}: {}", month, e.getMessage()); } - Map item = new HashMap<>(); - item.put("month", month); - item.put("lessonCount", lessonCount); - item.put("avgRating", 4.5); // TODO: calculate actual rating - trend.add(item); + trend.add(TeacherLessonTrendVO.builder() + .month(month) + .lessonCount(lessonCount) + .avgRating(4.5) // TODO: calculate actual rating + .build()); } return trend; } @Override - public List> getCourseUsage(Long tenantId) { - List> usage = new ArrayList<>(); + public List getCourseUsage(Long tenantId) { + List usage = new ArrayList<>(); List courses = courseMapper.selectList( new LambdaQueryWrapper() @@ -205,12 +180,64 @@ public class TeacherStatsServiceImpl implements TeacherStatsService { ); for (CoursePackage course : courses) { - Map item = new HashMap<>(); - item.put("name", course.getName()); - item.put("value", course.getUsageCount() != null ? course.getUsageCount() : 0); - usage.add(item); + usage.add(CourseUsageVO.builder() + .name(course.getName()) + .value(course.getUsageCount() != null ? course.getUsageCount() : 0) + .build()); } return usage; } + + @Override + public List getCourseUsageStats(Long tenantId, Long teacherId, CourseUsageQuery query) { + // 计算时间范围 + LocalDateTime[] timeRange = calculateTimeRange(query); + + // 调用 Mapper 查询 + return lessonMapper.getCourseUsageStats(tenantId, teacherId, timeRange[0], timeRange[1]); + } + + /** + * 根据周期类型计算时间范围 + */ + private LocalDateTime[] calculateTimeRange(CourseUsageQuery query) { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime startTime; + LocalDateTime endTime = now; + + String periodType = query != null ? query.getPeriodType() : "MONTH"; + + switch (periodType) { + case "TODAY": + startTime = now.toLocalDate().atStartOfDay(); + break; + case "WEEK": + // 本周一 + startTime = now.toLocalDate() + .with(DayOfWeek.MONDAY) + .atStartOfDay(); + break; + case "MONTH": + // 本月 1 号 + startTime = now.toLocalDate() + .withDayOfMonth(1) + .atStartOfDay(); + break; + case "CUSTOM": + if (query.getStartDate() != null) { + startTime = query.getStartDate().atStartOfDay(); + } else { + startTime = now.minusMonths(1); + } + if (query.getEndDate() != null) { + endTime = query.getEndDate().atTime(23, 59, 59); + } + break; + default: + startTime = now.toLocalDate().withDayOfMonth(1).atStartOfDay(); + } + + return new LocalDateTime[]{startTime, endTime}; + } }