feat: 教师端数据看板与学校端课程统计功能
教师端数据看板: - 新增 TeacherDashboardResponse/TeacherLessonVO/TeacherLessonTrendVO - 新增 TeacherWeeklyStatsResponse 周统计响应 - 新增 TeacherActivityLevel 枚举和 TeacherActivityRankResponse 活跃度排行 - 实现教师端课程统计、任务完成详情、任务反馈接口 学校端课程统计: - 新增 CourseUsageVO/CourseUsageStatsVO/CoursePackageVO - 新增 SchoolCourseResponse 和学校端课程使用查询接口 - 实现学校端统计数据和课程趋势接口 用户资料功能: - 新增 UpdateProfileRequest/UpdateProfileResponse - 实现用户资料更新接口 前后端对齐: - 更新 OpenAPI 规范和前端 API 类型生成 - 优化 DashboardView 组件和 API 调用 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c93d325cee
commit
6f64723428
@ -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`
|
||||
|
||||
@ -2,7 +2,8 @@
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(mvn compile:*)",
|
||||
"Bash(sed:*)"
|
||||
"Bash(sed:*)",
|
||||
"Bash(grep:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -349,7 +349,7 @@
|
||||
│ │
|
||||
│ 📊 平台整体数据 │
|
||||
│ ┌─────────────┬─────────────┬─────────────┬─────────────┐ │
|
||||
│ │ 租户总数 │ 活跃租户 │ 月授课次数 │ 覆盖学生数 │ │
|
||||
│ │ 租户总数 │ 活跃租户 │ 月授课次数 │ 学生总数数 │ │
|
||||
│ │ 1,254所 │ 986所 │ 45,678次 │ 234,567人 │ │
|
||||
│ └─────────────┴─────────────┴─────────────┴─────────────┘ │
|
||||
│ │
|
||||
|
||||
@ -54,7 +54,7 @@
|
||||
- ✅ 账号不存在登录失败
|
||||
|
||||
#### 02-dashboard.spec.ts - 数据看板测试 (7 个用例)
|
||||
- ✅ 验证统计卡片显示(租户数、课程包数、月授课次数、覆盖学生)
|
||||
- ✅ 验证统计卡片显示(租户数、课程包数、月授课次数、学生总数)
|
||||
- ✅ 验证趋势图加载
|
||||
- ✅ 验证活跃租户 TOP5 列表
|
||||
- ✅ 验证热门课程包 TOP5 列表
|
||||
|
||||
176
docs/dev-logs/2026-03-20-profile-feature.md
Normal file
176
docs/dev-logs/2026-03-20-profile-feature.md
Normal file
@ -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<UpdateProfileResponse>
|
||||
export function changePassword(oldPassword: string, newPassword: string): Promise<void>
|
||||
```
|
||||
|
||||
### 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. 更新测试日志
|
||||
204
docs/dev-logs/2026-03-20-teacher-course-usage-stats.md
Normal file
204
docs/dev-logs/2026-03-20-teacher-course-usage-stats.md
Normal file
@ -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<CourseUsageStatsVO> 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 格式的 `<if>` 动态标签
|
||||
|
||||
**解决方案**: 使用 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 中,提高查询性能
|
||||
169
docs/dev-logs/2026-03-21.md
Normal file
169
docs/dev-logs/2026-03-21.md
Normal file
@ -0,0 +1,169 @@
|
||||
# 开发日志 2026-03-21
|
||||
|
||||
## 学校端 - 课程使用统计功能实现
|
||||
|
||||
### 需求背景
|
||||
|
||||
学校端 Dashboard 页面已有"课程使用统计"卡片组件,但后端返回空数据。需要实现该功能,让学校管理员能够查看本校各课程包的使用情况。
|
||||
|
||||
### 实现内容
|
||||
|
||||
#### 1. 后端修改
|
||||
|
||||
**Controller 层** (`SchoolStatsController.java`)
|
||||
|
||||
- 添加日期范围参数支持,允许前端传入 `startDate` 和 `endDate`
|
||||
- 不传参数时默认统计本月数据
|
||||
|
||||
```java
|
||||
@GetMapping("/courses")
|
||||
@Operation(summary = "获取课程使用统计")
|
||||
public Result<List<Map<String, Object>>> 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<Map<String, Object>> getCourseUsageStats(Long tenantId, LocalDate startDate, LocalDate endDate);
|
||||
```
|
||||
|
||||
**Service 实现** (`SchoolStatsServiceImpl.java`)
|
||||
|
||||
- 使用 `LessonMapper.getCourseUsageStats()` 查询实际数据
|
||||
- 支持日期范围筛选,默认统计本月
|
||||
- 将 `CourseUsageStatsVO` 转换为 `Map` 格式返回
|
||||
|
||||
```java
|
||||
@Override
|
||||
public List<Map<String, Object>> 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<CourseUsageStatsVO> stats = lessonMapper.getCourseUsageStats(
|
||||
tenantId, null, startTime, endTime
|
||||
);
|
||||
|
||||
// 转换为 Map 格式返回
|
||||
return stats.stream().map(vo -> {
|
||||
Map<String, Object> 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<string, string> = {};
|
||||
if (startDate) params.startDate = startDate;
|
||||
if (endDate) params.endDate = endDate;
|
||||
return http.get<Array<{
|
||||
courseId: number;
|
||||
courseName: string;
|
||||
usageCount: number;
|
||||
studentCount?: number;
|
||||
avgDuration?: number;
|
||||
lastUsedAt?: string;
|
||||
}>>('/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. **性能优化**:大数据量时考虑缓存
|
||||
@ -114,7 +114,7 @@
|
||||
| 租户总数 | 显示真实数据 | 显示 2 | ✅ |
|
||||
| 课程包总数 | 显示真实数据 | 显示 5 | ✅ |
|
||||
| 月授课次数 | 显示真实数据 | 显示 22 | ✅ |
|
||||
| 覆盖学生 | 显示真实数据 | 显示 5 | ✅ |
|
||||
| 学生总数 | 显示真实数据 | 显示 5 | ✅ |
|
||||
|
||||
### 3.2 使用趋势图
|
||||
| 测试项 | 预期结果 | 实际结果 | 状态 |
|
||||
|
||||
@ -51,7 +51,7 @@ reading-platform-frontend/tests/e2e/admin/
|
||||
|
||||
| 测试项 | 状态 | 说明 |
|
||||
|--------|------|------|
|
||||
| 验证统计卡片显示 | ✅ | 租户数、课程包数、月授课次数、覆盖学生 |
|
||||
| 验证统计卡片显示 | ✅ | 租户数、课程包数、月授课次数、学生总数 |
|
||||
| 验证趋势图加载 | ✅ | 验证图表容器显示 |
|
||||
| 验证活跃租户 TOP5 列表 | ✅ | 验证列表数据 |
|
||||
| 验证热门课程包 TOP5 列表 | ✅ | 验证列表数据 |
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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,
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
@ -55,3 +55,26 @@ export function refreshToken(): Promise<{ token: string }> {
|
||||
export function getProfile(): Promise<UserProfile> {
|
||||
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<UpdateProfileResponse> {
|
||||
return http.put('/v1/auth/profile', data);
|
||||
}
|
||||
|
||||
// 修改密码(修改成功后 token 失效,需重新登录)
|
||||
export function changePassword(oldPassword: string, newPassword: string): Promise<void> {
|
||||
return http.post('/v1/auth/change-password', null, {
|
||||
params: { oldPassword, newPassword },
|
||||
});
|
||||
}
|
||||
|
||||
@ -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<Blob>(
|
||||
{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<Blob>(
|
||||
{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<Blob>(
|
||||
{url: `/v1/school/tasks/${id}`, method: 'GET',
|
||||
responseType: 'blob'
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Update task
|
||||
*/
|
||||
const updateTask1 = (
|
||||
id: number,
|
||||
taskUpdateRequest: TaskUpdateRequest,
|
||||
) => {
|
||||
return customMutator<Blob>(
|
||||
{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<Blob>(
|
||||
{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<Blob>(
|
||||
{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<Blob>(
|
||||
{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<Blob>(
|
||||
{url: `/v1/auth/profile`, method: 'GET',
|
||||
responseType: 'blob'
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary 修改个人信息
|
||||
*/
|
||||
const updateProfile = (
|
||||
updateProfileRequest: UpdateProfileRequest,
|
||||
) => {
|
||||
return customMutator<Blob>(
|
||||
{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<Blob>(
|
||||
{url: `/v1/school/teachers/${id}/reset-password`, method: 'POST',
|
||||
params,
|
||||
responseType: 'blob'
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get task page
|
||||
*/
|
||||
const getTaskPage1 = (
|
||||
params?: GetTaskPage1Params,
|
||||
) => {
|
||||
return customMutator<Blob>(
|
||||
{url: `/v1/school/tasks`, method: 'GET',
|
||||
params,
|
||||
responseType: 'blob'
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Create task
|
||||
*/
|
||||
const createTask1 = (
|
||||
taskCreateRequest: TaskCreateRequest,
|
||||
) => {
|
||||
return customMutator<Blob>(
|
||||
{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<Blob>(
|
||||
{url: `/v1/teacher/tasks/${taskId}/completions`, method: 'GET',
|
||||
params,
|
||||
responseType: 'blob'
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary 获取提交详情
|
||||
*/
|
||||
const getCompletionDetail = (
|
||||
completionId: number,
|
||||
) => {
|
||||
return customMutator<Blob>(
|
||||
{url: `/v1/teacher/tasks/completions/${completionId}`, method: 'GET',
|
||||
responseType: 'blob'
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary 获取默认模板
|
||||
*/
|
||||
@ -2941,10 +2994,11 @@ const getCourse = (
|
||||
* @summary 获取所有课程
|
||||
*/
|
||||
const getAllCourses = (
|
||||
|
||||
params?: GetAllCoursesParams,
|
||||
) => {
|
||||
return customMutator<Blob>(
|
||||
{url: `/v1/teacher/courses/all`, method: 'GET',
|
||||
params,
|
||||
responseType: 'blob'
|
||||
},
|
||||
);
|
||||
@ -3191,6 +3245,75 @@ const getCourseReports = (
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary 获取任务列表(支持多维度筛选)
|
||||
*/
|
||||
const getTaskList = (
|
||||
params?: GetTaskListParams,
|
||||
) => {
|
||||
return customMutator<Blob>(
|
||||
{url: `/v1/school/reading-tasks`, method: 'GET',
|
||||
params,
|
||||
responseType: 'blob'
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary 获取任务详情
|
||||
*/
|
||||
const getTaskDetail = (
|
||||
taskId: number,
|
||||
) => {
|
||||
return customMutator<Blob>(
|
||||
{url: `/v1/school/reading-tasks/${taskId}`, method: 'GET',
|
||||
responseType: 'blob'
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary 获取任务完成情况列表
|
||||
*/
|
||||
const getTaskCompletions1 = (
|
||||
taskId: number,
|
||||
params?: GetTaskCompletions1Params,
|
||||
) => {
|
||||
return customMutator<Blob>(
|
||||
{url: `/v1/school/reading-tasks/${taskId}/completions`, method: 'GET',
|
||||
params,
|
||||
responseType: 'blob'
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary 获取统计数据
|
||||
*/
|
||||
const getStatistics = (
|
||||
params?: GetStatisticsParams,
|
||||
) => {
|
||||
return customMutator<Blob>(
|
||||
{url: `/v1/school/reading-tasks/statistics`, method: 'GET',
|
||||
params,
|
||||
responseType: 'blob'
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary 获取学生提交详情
|
||||
*/
|
||||
const getCompletionDetail1 = (
|
||||
completionId: number,
|
||||
) => {
|
||||
return customMutator<Blob>(
|
||||
{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<Blob>(
|
||||
@ -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<Blob>(
|
||||
{url: `/v1/parent/tasks/completions/${completionId}`, method: 'GET',
|
||||
responseType: 'blob'
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary 获取教师评价
|
||||
*/
|
||||
const getFeedback = (
|
||||
completionId: number,
|
||||
) => {
|
||||
return customMutator<Blob>(
|
||||
{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<Blob>(
|
||||
{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<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTask']>>>
|
||||
export type UpdateTaskResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateTask']>>>
|
||||
export type DeleteTaskResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['deleteTask']>>>
|
||||
export type UpdateFeedbackResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateFeedback']>>>
|
||||
export type SubmitFeedbackResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['submitFeedback']>>>
|
||||
export type GetTemplateResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTemplate']>>>
|
||||
export type UpdateTemplateResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateTemplate']>>>
|
||||
export type DeleteTemplateResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['deleteTemplate']>>>
|
||||
@ -3820,9 +3958,6 @@ export type DeleteGrowthRecordResult = NonNullable<Awaited<ReturnType<ReturnType
|
||||
export type GetTeacherResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTeacher']>>>
|
||||
export type UpdateTeacherResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateTeacher']>>>
|
||||
export type DeleteTeacherResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['deleteTeacher']>>>
|
||||
export type GetTask1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTask1']>>>
|
||||
export type UpdateTask1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateTask1']>>>
|
||||
export type DeleteTask1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['deleteTask1']>>>
|
||||
export type GetTemplate1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTemplate1']>>>
|
||||
export type UpdateTemplate1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateTemplate1']>>>
|
||||
export type DeleteTemplate1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['deleteTemplate1']>>>
|
||||
@ -3851,9 +3986,13 @@ export type UpdateClassResult = NonNullable<Awaited<ReturnType<ReturnType<typeof
|
||||
export type DeleteClassResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['deleteClass']>>>
|
||||
export type UpdateClassTeacherResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateClassTeacher']>>>
|
||||
export type RemoveClassTeacherResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['removeClassTeacher']>>>
|
||||
export type UpdateSubmissionResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateSubmission']>>>
|
||||
export type SubmitTaskResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['submitTask']>>>
|
||||
export type GetGrowthRecord2Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getGrowthRecord2']>>>
|
||||
export type UpdateGrowthRecord2Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateGrowthRecord2']>>>
|
||||
export type DeleteGrowthRecord2Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['deleteGrowthRecord2']>>>
|
||||
export type GetCurrentUserResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getCurrentUser']>>>
|
||||
export type UpdateProfileResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateProfile']>>>
|
||||
export type FindOneResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['findOne']>>>
|
||||
export type UpdateResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['update']>>>
|
||||
export type _DeleteResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['_delete']>>>
|
||||
@ -3908,7 +4047,7 @@ export type SaveStudentRecordResult = NonNullable<Awaited<ReturnType<ReturnType<
|
||||
export type BatchSaveStudentRecordsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['batchSaveStudentRecords']>>>
|
||||
export type StartLessonResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['startLesson']>>>
|
||||
export type GetLessonFeedbackResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getLessonFeedback']>>>
|
||||
export type SubmitFeedbackResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['submitFeedback']>>>
|
||||
export type SubmitFeedback1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['submitFeedback1']>>>
|
||||
export type CompleteLessonResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['completeLesson']>>>
|
||||
export type CancelLessonResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['cancelLesson']>>>
|
||||
export type CreateLessonFromScheduleResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['createLessonFromSchedule']>>>
|
||||
@ -3918,8 +4057,6 @@ export type CreateGrowthRecordResult = NonNullable<Awaited<ReturnType<ReturnType
|
||||
export type GetTeacherPageResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTeacherPage']>>>
|
||||
export type CreateTeacherResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['createTeacher']>>>
|
||||
export type ResetPasswordResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['resetPassword']>>>
|
||||
export type GetTaskPage1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTaskPage1']>>>
|
||||
export type CreateTask1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['createTask1']>>>
|
||||
export type GetTemplates1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTemplates1']>>>
|
||||
export type CreateTemplate1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['createTemplate1']>>>
|
||||
export type GetStudentPageResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getStudentPage']>>>
|
||||
@ -3983,6 +4120,8 @@ export type PublishResult = NonNullable<Awaited<ReturnType<ReturnType<typeof get
|
||||
export type ArchiveResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['archive']>>>
|
||||
export type GetWeeklyStatsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getWeeklyStats']>>>
|
||||
export type GetTodayLessonsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTodayLessons']>>>
|
||||
export type GetTaskCompletionsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTaskCompletions']>>>
|
||||
export type GetCompletionDetailResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getCompletionDetail']>>>
|
||||
export type GetDefaultTemplateResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getDefaultTemplate']>>>
|
||||
export type GetAllStudentsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getAllStudents']>>>
|
||||
export type GetTodaySchedulesResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTodaySchedules']>>>
|
||||
@ -4018,6 +4157,11 @@ export type GetTeacherReportsResult = NonNullable<Awaited<ReturnType<ReturnType<
|
||||
export type GetStudentReportsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getStudentReports']>>>
|
||||
export type GetOverviewResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getOverview']>>>
|
||||
export type GetCourseReportsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getCourseReports']>>>
|
||||
export type GetTaskListResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTaskList']>>>
|
||||
export type GetTaskDetailResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTaskDetail']>>>
|
||||
export type GetTaskCompletions1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTaskCompletions1']>>>
|
||||
export type GetStatisticsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getStatistics']>>>
|
||||
export type GetCompletionDetail1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getCompletionDetail1']>>>
|
||||
export type GetParentChildrenResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getParentChildren']>>>
|
||||
export type FindTenantCollectionsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['findTenantCollections']>>>
|
||||
export type GetPackagesByCollectionResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getPackagesByCollection']>>>
|
||||
@ -4036,8 +4180,10 @@ export type ExportGrowthRecordsResult = NonNullable<Awaited<ReturnType<ReturnTyp
|
||||
export type GetSchoolCoursesResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getSchoolCourses']>>>
|
||||
export type GetSchoolCourseResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getSchoolCourse']>>>
|
||||
export type GetMyTasksResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getMyTasks']>>>
|
||||
export type GetTask2Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTask2']>>>
|
||||
export type GetTask1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTask1']>>>
|
||||
export type GetTasksByStudentResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTasksByStudent']>>>
|
||||
export type GetCompletionDetail2Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getCompletionDetail2']>>>
|
||||
export type GetFeedbackResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getFeedback']>>>
|
||||
export type GetMyNotifications1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getMyNotifications1']>>>
|
||||
export type GetNotification1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getNotification1']>>>
|
||||
export type GetUnreadCount1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getUnreadCount1']>>>
|
||||
@ -4049,7 +4195,6 @@ export type GetChildGrowthResult = NonNullable<Awaited<ReturnType<ReturnType<typ
|
||||
export type GenerateEditTokenResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['generateEditToken']>>>
|
||||
export type GenerateReadOnlyTokenResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['generateReadOnlyToken']>>>
|
||||
export type GetOssTokenResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getOssToken']>>>
|
||||
export type GetCurrentUserResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getCurrentUser']>>>
|
||||
export type GetTenantStatsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTenantStats']>>>
|
||||
export type GetAllActiveTenantsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getAllActiveTenants']>>>
|
||||
export type GetStatsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getStats']>>>
|
||||
|
||||
@ -14,8 +14,8 @@ export interface ActiveTenantItemResponse {
|
||||
tenantId?: number;
|
||||
/** 租户名称 */
|
||||
tenantName?: string;
|
||||
/** 活跃用户数 */
|
||||
activeUsers?: number;
|
||||
/** 课程使用数 */
|
||||
courseCount?: number;
|
||||
/** 活跃教师数(近 30 天有完成课程的老师数) */
|
||||
activeTeacherCount?: number;
|
||||
/** 完成课次数(近 30 天 COMPLETED 状态的 lesson 总数) */
|
||||
completedLessonCount?: number;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -16,4 +16,6 @@ export interface CourseCollectionPageQueryRequest {
|
||||
pageSize?: number;
|
||||
/** 状态 */
|
||||
status?: string;
|
||||
/** 年级(支持多个,逗号分隔) */
|
||||
gradeLevels?: string;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -20,6 +20,8 @@ export interface CoursePageQueryRequest {
|
||||
category?: string;
|
||||
/** 状态 */
|
||||
status?: string;
|
||||
/** 年级(支持多个,逗号分隔) */
|
||||
gradeTags?: string;
|
||||
/** 是否仅查询待审核 */
|
||||
reviewOnly?: boolean;
|
||||
}
|
||||
|
||||
@ -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[];
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -16,6 +16,8 @@ export interface DayScheduleItem {
|
||||
className?: string;
|
||||
/** 课程包名称 */
|
||||
coursePackageName?: string;
|
||||
/** 课程类型代码 (如 DOMAIN_HEALTH) */
|
||||
lessonType?: string;
|
||||
/** 课程类型名称 */
|
||||
lessonTypeName?: string;
|
||||
/** 教师名称 */
|
||||
|
||||
@ -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;
|
||||
};
|
||||
@ -10,5 +10,7 @@ export type GetCoursePageParams = {
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
keyword?: string;
|
||||
category?: string;
|
||||
grade?: string;
|
||||
domain?: string;
|
||||
lessonType?: string;
|
||||
};
|
||||
|
||||
@ -7,5 +7,5 @@
|
||||
*/
|
||||
|
||||
export type GetLessonTrend1Params = {
|
||||
months?: number;
|
||||
days?: number;
|
||||
};
|
||||
|
||||
@ -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;
|
||||
};
|
||||
@ -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;
|
||||
};
|
||||
@ -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;
|
||||
};
|
||||
@ -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;
|
||||
};
|
||||
@ -10,4 +10,6 @@ export type GetTemplates1Params = {
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
type?: string;
|
||||
taskType?: string;
|
||||
keyword?: string;
|
||||
};
|
||||
|
||||
@ -10,4 +10,5 @@ export type GetTemplatesParams = {
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
type?: string;
|
||||
keyword?: string;
|
||||
};
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -15,7 +15,7 @@ import type { LessonResponse } from './lessonResponse';
|
||||
export interface LessonDetailResponse {
|
||||
lesson?: LessonResponse;
|
||||
course?: CourseResponse;
|
||||
class?: ClassResponse;
|
||||
/** 排课选择的课程类型(子课程模式时用于直接进入对应子课程) */
|
||||
lessonType?: string;
|
||||
class?: ClassResponse;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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[];
|
||||
}
|
||||
@ -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[];
|
||||
}
|
||||
@ -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[];
|
||||
}
|
||||
@ -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[];
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -6,4 +6,4 @@
|
||||
* OpenAPI spec version: 1.0.0
|
||||
*/
|
||||
|
||||
export type GetRecentActivities1200DataItem = { [key: string]: unknown };
|
||||
export type ResultMapStringStringData = {[key: string]: string};
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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[];
|
||||
}
|
||||
@ -22,4 +22,6 @@ export interface StatsResponse {
|
||||
totalCourses?: number;
|
||||
/** 课时总数 */
|
||||
totalLessons?: number;
|
||||
/** 月授课次数(本月 COMPLETED 状态的 lesson 总数) */
|
||||
monthlyLessons?: number;
|
||||
}
|
||||
|
||||
@ -12,10 +12,8 @@
|
||||
export interface StatsTrendResponse {
|
||||
/** 日期列表 */
|
||||
dates?: string[];
|
||||
/** 新增学生数列表 */
|
||||
newStudents?: number[];
|
||||
/** 新增教师数列表 */
|
||||
newTeachers?: number[];
|
||||
/** 新增课程数列表 */
|
||||
newCourses?: number[];
|
||||
/** 授课次数列表(近 7 天每天完成的课程数) */
|
||||
lessonCounts?: number[];
|
||||
/** 活跃学生数列表(近 7 天每天有上课记录的去重学生数) */
|
||||
studentCounts?: number[];
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -28,6 +28,8 @@ export interface Task {
|
||||
description?: string;
|
||||
/** 任务类型 */
|
||||
type?: string;
|
||||
/** 关联绘本名称 */
|
||||
relatedBookName?: string;
|
||||
/** 课程 ID */
|
||||
courseId?: number;
|
||||
/** 创建人 ID */
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -16,6 +16,8 @@ export interface TaskCreateRequest {
|
||||
description?: string;
|
||||
/** 任务类型:reading-阅读,homework-作业,activity-活动 */
|
||||
type?: string;
|
||||
/** 关联绘本名称(手动填写) */
|
||||
relatedBookName?: string;
|
||||
/** 课程 ID */
|
||||
courseId?: number;
|
||||
/** 开始日期 */
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -20,6 +20,8 @@ export interface TaskResponse {
|
||||
description?: string;
|
||||
/** 任务类型 */
|
||||
type?: string;
|
||||
/** 关联绘本名称 */
|
||||
relatedBookName?: string;
|
||||
/** 课程 ID */
|
||||
courseId?: number;
|
||||
/** 创建人 ID */
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -23,9 +23,9 @@ export interface TaskTemplateCreateRequest {
|
||||
/** 默认持续时间 (天) */
|
||||
defaultDuration?: number;
|
||||
/** 是否默认模板 */
|
||||
isDefault?: number;
|
||||
isDefault?: boolean;
|
||||
/** 模板内容 */
|
||||
content?: string;
|
||||
/** 是否公开 */
|
||||
isPublic?: number;
|
||||
isPublic?: boolean;
|
||||
}
|
||||
|
||||
@ -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[];
|
||||
}
|
||||
|
||||
@ -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[];
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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 };
|
||||
@ -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;
|
||||
}
|
||||
@ -24,4 +24,6 @@ export interface TeacherUpdateRequest {
|
||||
bio?: string;
|
||||
/** 状态 */
|
||||
status?: string;
|
||||
/** 负责班级ID列表 */
|
||||
classIds?: number[];
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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[];
|
||||
}
|
||||
|
||||
@ -36,8 +36,8 @@ export interface TenantResponse {
|
||||
maxStudents?: number;
|
||||
/** 最大教师数 */
|
||||
maxTeachers?: number;
|
||||
/** 套餐类型 */
|
||||
packageType?: string;
|
||||
/** 套餐名称列表 */
|
||||
packageNames?: string[];
|
||||
/** 教师配额 */
|
||||
teacherQuota?: number;
|
||||
/** 学生配额 */
|
||||
|
||||
@ -24,10 +24,8 @@ export interface TenantUpdateRequest {
|
||||
logoUrl?: string;
|
||||
/** 状态 */
|
||||
status?: string;
|
||||
/** 套餐类型 */
|
||||
packageType?: string;
|
||||
/** 课程套餐ID(用于三层架构) */
|
||||
collectionId?: number;
|
||||
collectionIds?: number[];
|
||||
/** 教师配额 */
|
||||
teacherQuota?: number;
|
||||
/** 学生配额 */
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -247,13 +247,23 @@ export const getSchoolStats = () =>
|
||||
http.get<SchoolStats>('/v1/school/stats');
|
||||
|
||||
export const getActiveTeachers = (limit?: number) =>
|
||||
http.get<Array<{ id: number; name: string; lessonCount: number }>>('/v1/school/stats/teachers', { params: { limit } });
|
||||
http.get<Array<{
|
||||
teacherId: number | string;
|
||||
teacherName: string;
|
||||
classNames: string;
|
||||
lessonCount: number;
|
||||
courseCount: number;
|
||||
lastActiveAt?: string;
|
||||
activityLevelCode: string;
|
||||
activityLevelDesc: string;
|
||||
}>>('/v1/school/stats/teachers', { params: { limit } });
|
||||
|
||||
export const getCourseUsageStats = () =>
|
||||
http.get<Array<{ courseId: number; courseName: string; usageCount: number }>>('/v1/school/stats/courses');
|
||||
|
||||
export const getRecentActivities = (limit?: number) =>
|
||||
http.get<Array<{ id: number; type: string; title: string; time: string }>>('/v1/school/stats/activities', { params: { limit } });
|
||||
export const getCourseUsageStats = (startDate?: string, endDate?: string) => {
|
||||
const params: Record<string, string> = {};
|
||||
if (startDate) params.startDate = startDate;
|
||||
if (endDate) params.endDate = endDate;
|
||||
return http.get<Array<{ courseId: number; courseName: string; usageCount: number; studentCount?: number; avgDuration?: number; lastUsedAt?: string }>>('/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<LessonTrendItem[]>('/v1/school/stats/lesson-trend', { params: { months } });
|
||||
// 后端趋势数据响应(对象数组格式)
|
||||
export type LessonTrendResponse = LessonTrendItem[];
|
||||
|
||||
export const getLessonTrend = (days?: number) =>
|
||||
http.get<LessonTrendResponse>('/v1/school/stats/lesson-trend', { params: { days } });
|
||||
|
||||
export const getCourseDistribution = () =>
|
||||
http.get<CourseDistributionItem[]>('/v1/school/stats/course-distribution');
|
||||
|
||||
@ -312,59 +312,69 @@ export function batchSaveStudentRecords(
|
||||
|
||||
// ==================== 教师首页 API ====================
|
||||
|
||||
export interface DashboardData {
|
||||
stats: {
|
||||
export interface TeacherDashboardStats {
|
||||
classCount: number;
|
||||
studentCount: number;
|
||||
lessonCount: number;
|
||||
courseCount: number;
|
||||
};
|
||||
todayLessons: Array<{
|
||||
}
|
||||
|
||||
export interface TeacherLessonItem {
|
||||
id: number;
|
||||
tenantId: number;
|
||||
courseId: number;
|
||||
courseName: string;
|
||||
pictureBookName?: string;
|
||||
classId: number;
|
||||
courseName: string;
|
||||
className: string;
|
||||
plannedDatetime: string;
|
||||
teacherId: number;
|
||||
title: string;
|
||||
lessonDate: string;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
location: string;
|
||||
status: string;
|
||||
duration: number;
|
||||
}>;
|
||||
recommendedCourses: Array<{
|
||||
notes?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface TeacherCoursePackageItem {
|
||||
id: number;
|
||||
name: string;
|
||||
pictureBookName?: string;
|
||||
coverImagePath?: string;
|
||||
duration: number;
|
||||
usageCount: number;
|
||||
avgRating: number;
|
||||
gradeTags: string[];
|
||||
}>;
|
||||
weeklyStats: {
|
||||
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;
|
||||
};
|
||||
recentActivities: Array<{
|
||||
id: number;
|
||||
type: string;
|
||||
description: string;
|
||||
time: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface DashboardData {
|
||||
stats: TeacherDashboardStats;
|
||||
todayLessons: TeacherLessonItem[];
|
||||
recommendedCourses: TeacherCoursePackageItem[];
|
||||
weeklyStats: TeacherWeeklyStatsData;
|
||||
}
|
||||
|
||||
export const getTeacherDashboard = () =>
|
||||
http.get('/v1/teacher/dashboard') as any;
|
||||
http.get<DashboardData>('/v1/teacher/dashboard');
|
||||
|
||||
export const getTodayLessons = () =>
|
||||
http.get('/v1/teacher/today-lessons') as any;
|
||||
http.get<TeacherLessonItem[]>('/v1/teacher/today-lessons');
|
||||
|
||||
export const getRecommendedCourses = () =>
|
||||
http.get('/v1/teacher/recommended-courses') as any;
|
||||
http.get<TeacherCoursePackageItem[]>('/v1/teacher/recommended-courses');
|
||||
|
||||
export const getWeeklyStats = () =>
|
||||
http.get('/v1/teacher/weekly-stats') as any;
|
||||
http.get<TeacherWeeklyStatsData>('/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<TeacherLessonTrendItem[]>('/v1/teacher/lesson-trend', { params: { months } });
|
||||
};
|
||||
|
||||
// 旧版 API(保留向后兼容)
|
||||
export const getTeacherCourseUsage = () =>
|
||||
http.get('/v1/teacher/course-usage') as any;
|
||||
http.get<TeacherCourseUsageItem[]>('/v1/teacher/course-usage');
|
||||
|
||||
// 增强版课程使用统计 API
|
||||
export const getTeacherCourseUsageStats = (params?: CourseUsageQueryParams) =>
|
||||
http.get<CourseUsageStatsItem[]>('/v1/teacher/course-usage-stats', { params });
|
||||
|
||||
// ==================== 课程反馈 API ====================
|
||||
|
||||
|
||||
@ -107,28 +107,6 @@
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 最近活动 -->
|
||||
<a-row :gutter="24" style="margin-top: 24px">
|
||||
<a-col :span="24">
|
||||
<a-card title="最近活动" :bordered="false" :loading="activitiesLoading" class="modern-card">
|
||||
<div v-if="recentActivities.length > 0" class="activity-timeline">
|
||||
<div v-for="activity in recentActivities" :key="activity.id" class="activity-item">
|
||||
<div class="activity-dot" :class="'type-' + activity.type"></div>
|
||||
<div class="activity-content">
|
||||
<div class="activity-header">
|
||||
<a-tag :color="getActivityTagColor(activity.type)" class="activity-tag">
|
||||
{{ getActivityTypeText(activity.type) }}
|
||||
</a-tag>
|
||||
<span class="activity-title">{{ activity.title }}</span>
|
||||
</div>
|
||||
<span class="activity-time">{{ formatTime(activity.time) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-empty v-else description="暂无活动记录" />
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -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<AdminStats>({
|
||||
@ -187,37 +164,6 @@ const activeTenants = ref<ActiveTenant[]>([]);
|
||||
// 热门课程包
|
||||
const popularCourses = ref<PopularCourse[]>([]);
|
||||
|
||||
// 最近活动
|
||||
const recentActivities = ref<Array<{
|
||||
id: number;
|
||||
type: string;
|
||||
title: string;
|
||||
time: string;
|
||||
}>>([]);
|
||||
|
||||
// Activity colors
|
||||
const getActivityTagColor = (type: string) => {
|
||||
const colors: Record<string, string> = {
|
||||
lesson: 'blue',
|
||||
tenant: 'green',
|
||||
course: 'orange',
|
||||
};
|
||||
return colors[type] || 'default';
|
||||
};
|
||||
|
||||
const getActivityTypeText = (type: string) => {
|
||||
const texts: Record<string, string> = {
|
||||
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) {
|
||||
|
||||
@ -34,27 +34,145 @@
|
||||
<a-descriptions-item label="邮箱">
|
||||
{{ profile.email || '-' }}
|
||||
</a-descriptions-item>
|
||||
<!-- <a-descriptions-item label="所属机构" v-if="profile.tenantId">
|
||||
{{ profile.tenantName || `租户ID: ${profile.tenantId}` }}
|
||||
</a-descriptions-item> -->
|
||||
</a-descriptions>
|
||||
</div>
|
||||
|
||||
<div class="action-section">
|
||||
<a-button type="primary" @click="enterEditMode">
|
||||
<EditOutlined /> 编辑资料
|
||||
</a-button>
|
||||
<a-button @click="showChangePasswordModal" style="margin-left: 12px">
|
||||
<LockOutlined /> 修改密码
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-empty v-else-if="!loading" description="加载失败,请刷新重试" />
|
||||
</a-spin>
|
||||
|
||||
<!-- 编辑资料弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="editModalOpen"
|
||||
title="编辑个人信息"
|
||||
@ok="submitEdit"
|
||||
:confirmLoading="editLoading"
|
||||
width="480px"
|
||||
>
|
||||
<a-form
|
||||
:model="editForm"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
>
|
||||
<a-form-item
|
||||
label="姓名"
|
||||
name="name"
|
||||
:rules="[
|
||||
{ required: true, message: '请输入姓名' },
|
||||
{ pattern: /^[\u4e00-\u9fa5a-zA-Z\s]{2,20}$/, message: '姓名长度为 2-20 位,只能包含中文或英文' }
|
||||
]"
|
||||
>
|
||||
<a-input v-model:value="editForm.name" placeholder="请输入姓名" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
label="手机号"
|
||||
name="phone"
|
||||
:rules="[
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号' }
|
||||
]"
|
||||
>
|
||||
<a-input v-model:value="editForm.phone" placeholder="请输入手机号" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
label="邮箱"
|
||||
name="email"
|
||||
:rules="[
|
||||
{ type: 'email', message: '请输入正确的邮箱格式' }
|
||||
]"
|
||||
>
|
||||
<a-input v-model:value="editForm.email" placeholder="请输入邮箱" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<!-- 修改密码弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="passwordModalOpen"
|
||||
title="修改密码"
|
||||
@ok="submitChangePassword"
|
||||
:confirmLoading="passwordLoading"
|
||||
width="480px"
|
||||
>
|
||||
<a-form
|
||||
:model="passwordForm"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
>
|
||||
<a-form-item
|
||||
label="旧密码"
|
||||
name="oldPassword"
|
||||
:rules="[{ required: true, message: '请输入旧密码' }]"
|
||||
>
|
||||
<a-input-password v-model:value="passwordForm.oldPassword" placeholder="请输入旧密码" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
label="新密码"
|
||||
name="newPassword"
|
||||
:rules="[
|
||||
{ required: true, message: '请输入新密码' },
|
||||
{ min: 6, message: '密码长度不能少于 6 位' }
|
||||
]"
|
||||
>
|
||||
<a-input-password v-model:value="passwordForm.newPassword" placeholder="请输入新密码" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
label="确认密码"
|
||||
name="confirmPassword"
|
||||
:rules="[
|
||||
{ required: true, message: '请确认新密码' },
|
||||
{ validator: validateConfirmPassword }
|
||||
]"
|
||||
>
|
||||
<a-input-password v-model:value="passwordForm.confirmPassword" placeholder="请再次输入新密码" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { UserOutlined } from '@ant-design/icons-vue';
|
||||
import { getProfile, type UserProfile } from '@/api/auth';
|
||||
import { UserOutlined, EditOutlined, LockOutlined } from '@ant-design/icons-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { getProfile, type UserProfile, updateProfile, changePassword, type UpdateProfileDto } from '@/api/auth';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
const loading = ref(false);
|
||||
const profile = ref<UserProfile | null>(null);
|
||||
|
||||
// 编辑资料相关
|
||||
const editModalOpen = ref(false);
|
||||
const editLoading = ref(false);
|
||||
const editForm = ref<UpdateProfileDto>({
|
||||
name: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
});
|
||||
|
||||
// 修改密码相关
|
||||
const passwordModalOpen = ref(false);
|
||||
const passwordLoading = ref(false);
|
||||
const passwordForm = ref({
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: '',
|
||||
});
|
||||
|
||||
const avatarUrl = computed(() => {
|
||||
const p = profile.value;
|
||||
if (!p) return '';
|
||||
@ -84,6 +202,73 @@ const loadProfile = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 进入编辑模式
|
||||
const enterEditMode = () => {
|
||||
if (profile.value) {
|
||||
editForm.value = {
|
||||
name: profile.value.name || '',
|
||||
phone: profile.value.phone || '',
|
||||
email: profile.value.email || '',
|
||||
};
|
||||
}
|
||||
editModalOpen.value = true;
|
||||
};
|
||||
|
||||
// 提交编辑
|
||||
const submitEdit = async () => {
|
||||
try {
|
||||
editLoading.value = true;
|
||||
await updateProfile(editForm.value);
|
||||
message.success('修改成功');
|
||||
editModalOpen.value = false;
|
||||
await loadProfile();
|
||||
} catch (error: any) {
|
||||
console.error('修改失败', error);
|
||||
message.error(error.message || '修改失败,请重试');
|
||||
} finally {
|
||||
editLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 显示修改密码弹窗
|
||||
const showChangePasswordModal = () => {
|
||||
passwordForm.value = {
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: '',
|
||||
};
|
||||
passwordModalOpen.value = true;
|
||||
};
|
||||
|
||||
// 验证确认密码
|
||||
const validateConfirmPassword = async (_rule: any, value: string) => {
|
||||
if (value !== passwordForm.value.newPassword) {
|
||||
throw new Error('两次输入的密码不一致');
|
||||
}
|
||||
};
|
||||
|
||||
// 提交修改密码
|
||||
const submitChangePassword = async () => {
|
||||
try {
|
||||
passwordLoading.value = true;
|
||||
await changePassword(passwordForm.value.oldPassword, passwordForm.value.newPassword);
|
||||
message.success('密码修改成功,请重新登录');
|
||||
passwordModalOpen.value = false;
|
||||
|
||||
// 清除本地存储并跳转到登录页
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('userInfo');
|
||||
setTimeout(() => {
|
||||
router.push('/login');
|
||||
}, 1000);
|
||||
} catch (error: any) {
|
||||
console.error('修改密码失败', error);
|
||||
message.error(error.message || '修改密码失败,请重试');
|
||||
} finally {
|
||||
passwordLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadProfile();
|
||||
});
|
||||
@ -139,4 +324,11 @@ onMounted(() => {
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.action-section {
|
||||
margin-top: 24px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -53,40 +53,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<div class="content-grid">
|
||||
<!-- 近期活动 -->
|
||||
<div class="content-card activities-card">
|
||||
<div class="card-header">
|
||||
<span class="card-icon"><CalendarOutlined /></span>
|
||||
<h3>近期课程活动</h3>
|
||||
</div>
|
||||
<div class="card-body" :class="{ 'is-loading': loading }">
|
||||
<a-spin v-if="loading" />
|
||||
<div v-else-if="recentActivities.length === 0" class="empty-state">
|
||||
<span class="empty-icon"><InboxOutlined /></span>
|
||||
<p>暂无近期活动</p>
|
||||
</div>
|
||||
<div v-else class="activity-list">
|
||||
<div
|
||||
v-for="item in recentActivities"
|
||||
:key="item.id"
|
||||
class="activity-item"
|
||||
>
|
||||
<div class="activity-avatar">
|
||||
<BookOutlined />
|
||||
</div>
|
||||
<div class="activity-content">
|
||||
<div class="activity-title">{{ item.title }}</div>
|
||||
<div class="activity-time">{{ formatTime(item.time) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 教师活跃度排行 -->
|
||||
<div class="content-card teachers-card">
|
||||
<!-- 教师活跃度排行(全宽) -->
|
||||
<div class="teachers-card-full">
|
||||
<div class="card-header">
|
||||
<span class="card-icon"><TrophyOutlined /></span>
|
||||
<h3>教师活跃度排行</h3>
|
||||
@ -100,17 +68,36 @@
|
||||
<div v-else class="teacher-list">
|
||||
<div
|
||||
v-for="(item, index) in activeTeachers"
|
||||
:key="item.id"
|
||||
:key="item.teacherId"
|
||||
class="teacher-item"
|
||||
>
|
||||
<div class="rank-badge" :class="'rank-' + (index + 1)">
|
||||
{{ index + 1 }}
|
||||
</div>
|
||||
<div class="teacher-info">
|
||||
<div class="teacher-name">{{ item.name }}</div>
|
||||
<div class="teacher-lessons">
|
||||
<span class="lesson-icon"><ReadOutlined /></span>
|
||||
<div class="teacher-name-row">
|
||||
<span class="teacher-name">{{ item.teacherName }}</span>
|
||||
<a-tag :color="getActivityLevelColor(item.activityLevelCode)" size="small">
|
||||
{{ item.activityLevelDesc }}
|
||||
</a-tag>
|
||||
</div>
|
||||
<div class="teacher-details">
|
||||
<span class="detail-item">
|
||||
<HomeOutlined />
|
||||
{{ item.classNames || '未分配班级' }}
|
||||
</span>
|
||||
<span class="detail-item">
|
||||
<ReadOutlined />
|
||||
授课 {{ item.lessonCount }} 次
|
||||
</span>
|
||||
<span class="detail-item">
|
||||
<BookOutlined />
|
||||
课程 {{ item.courseCount }} 个
|
||||
</span>
|
||||
<span class="detail-item" v-if="item.lastActiveAt">
|
||||
<ClockCircleOutlined />
|
||||
{{ formatLastActive(item.lastActiveAt) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="teacher-medal">
|
||||
@ -120,7 +107,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 课程使用统计 -->
|
||||
<div class="course-stats-card">
|
||||
@ -225,21 +211,19 @@ import {
|
||||
TeamOutlined,
|
||||
UserOutlined,
|
||||
ReadOutlined,
|
||||
CalendarOutlined,
|
||||
InboxOutlined,
|
||||
TrophyOutlined,
|
||||
TrophyFilled,
|
||||
StarFilled,
|
||||
BarChartOutlined,
|
||||
LineChartOutlined,
|
||||
DownloadOutlined,
|
||||
ClockCircleOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
import * as echarts from 'echarts';
|
||||
import { message } from 'ant-design-vue';
|
||||
import {
|
||||
getSchoolStats,
|
||||
getActiveTeachers,
|
||||
getRecentActivities,
|
||||
getCourseUsageStats,
|
||||
getLessonTrend,
|
||||
getCourseDistribution,
|
||||
@ -249,7 +233,7 @@ import {
|
||||
} from '@/api/school';
|
||||
import type { SchoolStats, LessonTrendItem, CourseDistributionItem } from '@/api/school';
|
||||
import type { Component } from 'vue';
|
||||
import { Dayjs } from 'dayjs';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
|
||||
const loading = ref(false);
|
||||
const courseStatsLoading = ref(false);
|
||||
@ -269,8 +253,16 @@ const stats = ref<SchoolStats>({
|
||||
lessonCount: 0,
|
||||
});
|
||||
|
||||
const recentActivities = ref<Array<{ id: number; type: string; title: string; time: string }>>([]);
|
||||
const activeTeachers = ref<Array<{ id: number; name: string; lessonCount: number }>>([]);
|
||||
const activeTeachers = ref<Array<{
|
||||
teacherId: number | string;
|
||||
teacherName: string;
|
||||
classNames: string;
|
||||
lessonCount: number;
|
||||
courseCount: number;
|
||||
lastActiveAt?: string;
|
||||
activityLevelCode: string;
|
||||
activityLevelDesc: string;
|
||||
}>>([]);
|
||||
|
||||
const courseStats = ref<Array<{ courseId: number; courseName: string; usageCount: number }>>([]);
|
||||
const dateRange = ref<[Dayjs, Dayjs] | undefined>(undefined);
|
||||
@ -331,6 +323,42 @@ const getMedalIcon = (index: number): Component => {
|
||||
return icons[index] || StarOutlined;
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据活跃度等级获取标签颜色
|
||||
*/
|
||||
const getActivityLevelColor = (code: string) => {
|
||||
const colors: Record<string, string> = {
|
||||
HIGH: 'red',
|
||||
MEDIUM: 'orange',
|
||||
LOW: 'blue',
|
||||
INACTIVE: 'default',
|
||||
};
|
||||
return colors[code] || 'default';
|
||||
};
|
||||
|
||||
/**
|
||||
* 格式化最后活跃时间
|
||||
*/
|
||||
const formatLastActive = (time: string) => {
|
||||
if (!time) return '';
|
||||
const date = new Date(time);
|
||||
const now = new Date();
|
||||
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
const yesterday = new Date(today.getTime() - 86400000);
|
||||
const dateOnly = new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
||||
|
||||
if (dateOnly.getTime() >= today.getTime()) {
|
||||
return `今天 ${date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })}`;
|
||||
} else if (dateOnly.getTime() >= yesterday.getTime()) {
|
||||
return `昨天 ${date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })}`;
|
||||
} else if (now.getTime() - date.getTime() < 7 * 86400000) {
|
||||
const days = Math.floor((now.getTime() - date.getTime()) / 86400000);
|
||||
return `${days} 天前`;
|
||||
} else {
|
||||
return date.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' });
|
||||
}
|
||||
};
|
||||
|
||||
const getMedalStyle = (index: number) => {
|
||||
const styles = [
|
||||
{ color: '#FFD700', fontSize: '20px' },
|
||||
@ -394,7 +422,7 @@ const initTrendChart = (data: LessonTrendItem[]) => {
|
||||
|
||||
// 确保数据格式正确
|
||||
const validData = data.map(d => ({
|
||||
month: d.month || '',
|
||||
date: d.date || '',
|
||||
lessonCount: d.lessonCount || 0,
|
||||
studentCount: d.studentCount || 0,
|
||||
}));
|
||||
@ -426,7 +454,7 @@ const initTrendChart = (data: LessonTrendItem[]) => {
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: validData.map((d) => d.month),
|
||||
data: validData.map((d) => d.date),
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#E5E7EB',
|
||||
@ -515,9 +543,16 @@ const initDistributionChart = (data: CourseDistributionItem[]) => {
|
||||
}
|
||||
|
||||
// 确保数据格式正确
|
||||
const validData = data.map((item, index) => ({
|
||||
name: item.name || `课程${index + 1}`,
|
||||
const validData = data.map((item, index) => {
|
||||
// 限制名称长度,超过 10 个字则缩略显示
|
||||
const displayName = item.name && item.name.length > 10
|
||||
? item.name.substring(0, 10) + '...'
|
||||
: (item.name || `课程${index + 1}`);
|
||||
|
||||
return {
|
||||
name: displayName,
|
||||
value: item.value || 0,
|
||||
fullName: item.name, // 保存完整名称用于 tooltip 显示
|
||||
itemStyle: {
|
||||
color: [
|
||||
'#FF8C42',
|
||||
@ -530,12 +565,17 @@ const initDistributionChart = (data: CourseDistributionItem[]) => {
|
||||
'#30cfd0',
|
||||
][index % 8],
|
||||
},
|
||||
}));
|
||||
};
|
||||
});
|
||||
|
||||
const option: echarts.EChartsOption = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{b}: {c}次 ({d}%)',
|
||||
formatter: (params: any) => {
|
||||
// 使用完整名称显示 tooltip
|
||||
const fullName = params.data.fullName || params.name;
|
||||
return `${fullName}: ${params.value}次 (${params.percent}%)`;
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
@ -584,15 +624,13 @@ const handleResize = () => {
|
||||
const loadData = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const [statsData, teachersData, activitiesData] = await Promise.all([
|
||||
const [statsData, teachersData] = await Promise.all([
|
||||
getSchoolStats(),
|
||||
getActiveTeachers(5),
|
||||
getRecentActivities(10),
|
||||
getActiveTeachers(10), // 获取 TOP10
|
||||
]);
|
||||
|
||||
stats.value = statsData;
|
||||
activeTeachers.value = teachersData;
|
||||
recentActivities.value = activitiesData;
|
||||
} catch (error) {
|
||||
console.error('Failed to load dashboard data:', error);
|
||||
} finally {
|
||||
@ -603,7 +641,10 @@ const loadData = async () => {
|
||||
const loadCourseStats = async () => {
|
||||
courseStatsLoading.value = true;
|
||||
try {
|
||||
const data = await getCourseUsageStats();
|
||||
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);
|
||||
@ -616,7 +657,7 @@ const loadCourseStats = async () => {
|
||||
const loadTrendData = async () => {
|
||||
trendLoading.value = true;
|
||||
try {
|
||||
const data = await getLessonTrend(6);
|
||||
const data = await getLessonTrend(7);
|
||||
lessonTrendData.value = data;
|
||||
} catch (error) {
|
||||
console.error('Failed to load trend data:', error);
|
||||
@ -685,6 +726,12 @@ onMounted(() => {
|
||||
loadTrendData();
|
||||
loadDistributionData();
|
||||
|
||||
// 设置默认日期范围为当月
|
||||
const now = dayjs();
|
||||
const monthStart = now.startOf('month');
|
||||
const monthEnd = now.endOf('month');
|
||||
dateRange.value = [monthStart, monthEnd];
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
@ -808,11 +855,20 @@ onUnmounted(() => {
|
||||
/* 内容网格 */
|
||||
.content-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-template-columns: 1fr;
|
||||
gap: 24px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
/* 教师活跃度排行(全宽) */
|
||||
.teachers-card-full {
|
||||
background: white;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
/* 图表网格 */
|
||||
.charts-grid {
|
||||
display: grid;
|
||||
@ -905,50 +961,6 @@ onUnmounted(() => {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 活动列表 */
|
||||
.activity-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.activity-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
background: #FAFAFA;
|
||||
border-radius: 12px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.activity-item:hover {
|
||||
background: #FFF8F0;
|
||||
}
|
||||
|
||||
.activity-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(135deg, #FF8C42 0%, #FFB347 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.activity-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #2D3436;
|
||||
}
|
||||
|
||||
.activity-time {
|
||||
font-size: 12px;
|
||||
color: #B2BEC3;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* 教师列表 */
|
||||
.teacher-list {
|
||||
display: flex;
|
||||
@ -991,26 +1003,37 @@ onUnmounted(() => {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.teacher-name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.teacher-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
font-weight: 600;
|
||||
color: #2D3436;
|
||||
}
|
||||
|
||||
.teacher-lessons {
|
||||
font-size: 12px;
|
||||
color: #636E72;
|
||||
margin-top: 4px;
|
||||
.teacher-details {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
color: #636E72;
|
||||
}
|
||||
|
||||
.lesson-icon {
|
||||
font-size: 14px;
|
||||
color: #636E72;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.detail-item .anticon {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.teacher-medal {
|
||||
|
||||
@ -331,7 +331,7 @@
|
||||
</div>
|
||||
<div class="detail-stat-item">
|
||||
<div class="stat-number">{{ selectedCourse.studentCount }}</div>
|
||||
<div class="stat-label">覆盖学生</div>
|
||||
<div class="stat-label">学生总数</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider />
|
||||
|
||||
@ -91,6 +91,12 @@
|
||||
</div>
|
||||
<span>课程使用</span>
|
||||
</div>
|
||||
<a-segmented
|
||||
v-model:value="usagePeriodType"
|
||||
:options="periodOptions"
|
||||
@change="loadUsageData"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
<div class="card-body" :class="{ 'is-loading': usageLoading }">
|
||||
<a-spin v-if="usageLoading" />
|
||||
@ -131,8 +137,8 @@
|
||||
:class="{ 'finished': lesson.status === 'FINISHED' }"
|
||||
>
|
||||
<div class="lesson-time">
|
||||
<div class="time-value">{{ formatTime(lesson.plannedDatetime) }}</div>
|
||||
<div class="time-duration">{{ lesson.duration }}分钟</div>
|
||||
<div class="time-value">{{ formatTime(lesson.startTime || lesson.lessonDate) }}</div>
|
||||
<div class="time-duration">30 分钟</div>
|
||||
</div>
|
||||
<div class="lesson-info">
|
||||
<div class="lesson-name">{{ lesson.courseName }}</div>
|
||||
@ -193,23 +199,23 @@
|
||||
>
|
||||
<div class="recommend-cover">
|
||||
<img
|
||||
v-if="course.coverImagePath"
|
||||
:src="getImageUrl(course.coverImagePath)"
|
||||
v-if="(course as any).coverImagePath || course.description"
|
||||
:src="getImageUrl((course as any).coverImagePath || course.description || '')"
|
||||
class="cover-img"
|
||||
/>
|
||||
<div v-else class="cover-placeholder">
|
||||
<BookFilled />
|
||||
</div>
|
||||
<div class="duration-tag">{{ course.duration || 30 }}分钟</div>
|
||||
<div class="duration-tag">{{ course.courseCount || 30 }}分钟</div>
|
||||
</div>
|
||||
<div class="recommend-info">
|
||||
<div class="recommend-name">{{ course.name }}</div>
|
||||
<div class="recommend-meta">
|
||||
<span class="meta-item">
|
||||
<FireOutlined /> {{ course.usageCount }}次使用
|
||||
<FireOutlined /> {{ course.usageCount || 0 }}次使用
|
||||
</span>
|
||||
<span v-if="course.avgRating > 0" class="meta-item">
|
||||
<StarFilled class="star-icon" /> {{ course.avgRating.toFixed(1) }}
|
||||
<span v-if="course.gradeLevel" class="meta-item">
|
||||
<StarFilled class="star-icon" /> {{ course.gradeLevel }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -219,46 +225,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 近期活动 -->
|
||||
<div class="activity-section">
|
||||
<div class="content-card activity-card">
|
||||
<div class="card-header">
|
||||
<div class="card-title">
|
||||
<div class="title-icon-wrapper list">
|
||||
<UnorderedListOutlined />
|
||||
</div>
|
||||
<span>近期活动</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body" :class="{ 'is-loading': loading }">
|
||||
<a-spin :spinning="loading">
|
||||
<div v-if="recentActivities.length === 0" class="empty-state-horizontal">
|
||||
<div class="empty-icon-wrapper small">
|
||||
<FileTextOutlined />
|
||||
</div>
|
||||
<p class="empty-text">暂无近期活动</p>
|
||||
</div>
|
||||
<div v-else class="activity-timeline">
|
||||
<div
|
||||
v-for="(item, index) in recentActivities"
|
||||
:key="item.id"
|
||||
class="activity-item"
|
||||
:class="'type-' + item.type"
|
||||
>
|
||||
<div class="activity-dot">
|
||||
<component :is="getActivityIcon(item.type)" />
|
||||
</div>
|
||||
<div class="activity-content">
|
||||
<div class="activity-text">{{ item.description }}</div>
|
||||
<div class="activity-time">{{ item.time }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -284,11 +250,6 @@ import {
|
||||
FireOutlined,
|
||||
InboxOutlined,
|
||||
RightOutlined,
|
||||
UnorderedListOutlined,
|
||||
FileTextOutlined,
|
||||
MessageOutlined,
|
||||
UserOutlined,
|
||||
FolderOutlined,
|
||||
ClockCircleOutlined,
|
||||
LineChartOutlined,
|
||||
PieChartOutlined,
|
||||
@ -296,9 +257,14 @@ import {
|
||||
import {
|
||||
getTeacherDashboard,
|
||||
getTeacherLessonTrend,
|
||||
getTeacherCourseUsage,
|
||||
getTeacherCourseUsageStats,
|
||||
type DashboardData,
|
||||
type TeacherLessonItem,
|
||||
type TeacherCoursePackageItem,
|
||||
type TeacherLessonTrendItem,
|
||||
type TeacherCourseUsageItem,
|
||||
type CourseUsageStatsItem,
|
||||
} from '@/api/teacher';
|
||||
import type { TeacherLessonTrendItem, TeacherCourseUsageItem } from '@/api/teacher';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
@ -314,15 +280,25 @@ let usageChart: echarts.ECharts | null = null;
|
||||
|
||||
// Chart data
|
||||
const lessonTrendData = ref<TeacherLessonTrendItem[]>([]);
|
||||
const courseUsageData = ref<TeacherCourseUsageItem[]>([]);
|
||||
|
||||
const stats = ref({
|
||||
const stats = ref<DashboardData['stats']>({
|
||||
classCount: 0,
|
||||
studentCount: 0,
|
||||
lessonCount: 0,
|
||||
courseCount: 0,
|
||||
});
|
||||
|
||||
// 课程使用统计周期选择
|
||||
const usagePeriodType = ref<'TODAY' | 'WEEK' | 'MONTH' | 'CUSTOM'>('MONTH');
|
||||
const periodOptions = [
|
||||
{ label: '今日', value: 'TODAY' },
|
||||
{ label: '本周', value: 'WEEK' },
|
||||
{ label: '本月', value: 'MONTH' },
|
||||
];
|
||||
|
||||
// 增强版课程使用统计数据
|
||||
const courseUsageStatsData = ref<CourseUsageStatsItem[]>([]);
|
||||
|
||||
const currentDate = computed(() => {
|
||||
const now = new Date();
|
||||
const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
|
||||
@ -332,39 +308,8 @@ const currentDate = computed(() => {
|
||||
return `${month}月${date}日 ${day}`;
|
||||
});
|
||||
|
||||
interface TodayLesson {
|
||||
id: number;
|
||||
courseId: number;
|
||||
courseName: string;
|
||||
pictureBookName?: string;
|
||||
classId: number;
|
||||
className: string;
|
||||
plannedDatetime: string;
|
||||
status: string;
|
||||
duration: number;
|
||||
}
|
||||
|
||||
interface RecommendedCourse {
|
||||
id: number;
|
||||
name: string;
|
||||
pictureBookName?: string;
|
||||
coverImagePath?: string;
|
||||
duration: number;
|
||||
usageCount: number;
|
||||
avgRating: number;
|
||||
gradeTags: string[];
|
||||
}
|
||||
|
||||
interface RecentActivity {
|
||||
id: number;
|
||||
type: string;
|
||||
description: string;
|
||||
time: string;
|
||||
}
|
||||
|
||||
const todayLessons = ref<TodayLesson[]>([]);
|
||||
const recommendedCourses = ref<RecommendedCourse[]>([]);
|
||||
const recentActivities = ref<RecentActivity[]>([]);
|
||||
const todayLessons = ref<TeacherLessonItem[]>([]);
|
||||
const recommendedCourses = ref<TeacherCoursePackageItem[]>([]);
|
||||
|
||||
const getImageUrl = (path: string) => {
|
||||
if (!path) return '';
|
||||
@ -379,36 +324,6 @@ const formatTime = (datetime: string) => {
|
||||
return `${hours}:${minutes}`;
|
||||
};
|
||||
|
||||
const formatActivityTime = (time: string) => {
|
||||
const date = new Date(time);
|
||||
const now = new Date();
|
||||
const diff = now.getTime() - date.getTime();
|
||||
|
||||
const minutes = Math.floor(diff / 60000);
|
||||
const hours = Math.floor(diff / 3600000);
|
||||
const days = Math.floor(diff / 86400000);
|
||||
|
||||
if (minutes < 60) {
|
||||
return `${minutes}分钟前`;
|
||||
} else if (hours < 24) {
|
||||
return `${hours}小时前`;
|
||||
} else if (days < 7) {
|
||||
return `${days}天前`;
|
||||
} else {
|
||||
return date.toLocaleDateString();
|
||||
}
|
||||
};
|
||||
|
||||
const getActivityIcon = (type: string) => {
|
||||
const iconMap: Record<string, any> = {
|
||||
lesson: ReadOutlined,
|
||||
feedback: MessageOutlined,
|
||||
student: UserOutlined,
|
||||
course: FolderOutlined,
|
||||
};
|
||||
return iconMap[type] || FileTextOutlined;
|
||||
};
|
||||
|
||||
// 初始化授课趋势图表
|
||||
const initTrendChart = (data: TeacherLessonTrendItem[]) => {
|
||||
if (!trendChartRef.value) return;
|
||||
@ -526,8 +441,8 @@ const initTrendChart = (data: TeacherLessonTrendItem[]) => {
|
||||
trendChart.setOption(option);
|
||||
};
|
||||
|
||||
// 初始化课程使用饼图
|
||||
const initUsageChart = (data: TeacherCourseUsageItem[]) => {
|
||||
// 初始化课程使用饼图(支持旧版数据类型)
|
||||
const initUsageChart = (data: TeacherCourseUsageItem[] | CourseUsageStatsItem[]) => {
|
||||
if (!usageChartRef.value) return;
|
||||
|
||||
if (usageChart) {
|
||||
@ -536,10 +451,45 @@ const initUsageChart = (data: TeacherCourseUsageItem[]) => {
|
||||
|
||||
usageChart = echarts.init(usageChartRef.value);
|
||||
|
||||
// 转换为统一的格式
|
||||
const chartData = data.map(item => {
|
||||
if ('coursePackageName' in item) {
|
||||
// CourseUsageStatsItem 类型
|
||||
return {
|
||||
name: (item as CourseUsageStatsItem).coursePackageName,
|
||||
value: (item as CourseUsageStatsItem).usageCount,
|
||||
studentCount: (item as CourseUsageStatsItem).studentCount,
|
||||
avgDuration: (item as CourseUsageStatsItem).avgDuration,
|
||||
lastUsedAt: (item as CourseUsageStatsItem).lastUsedAt,
|
||||
};
|
||||
} else {
|
||||
// TeacherCourseUsageItem 类型(旧版)
|
||||
return {
|
||||
name: (item as TeacherCourseUsageItem).name,
|
||||
value: (item as TeacherCourseUsageItem).value,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const option: echarts.EChartsOption = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{b}: {c}次 ({d}%)',
|
||||
formatter: (params: any) => {
|
||||
const data = params.data;
|
||||
let tooltip = `${data.name}: ${data.value}次 (${params.percent}%)`;
|
||||
if (data.studentCount !== undefined) {
|
||||
tooltip += `\n参与学生:${data.studentCount}人`;
|
||||
}
|
||||
if (data.avgDuration !== undefined) {
|
||||
tooltip += `\n平均时长:${data.avgDuration}分钟`;
|
||||
}
|
||||
if (data.lastUsedAt) {
|
||||
const lastUsed = new Date(data.lastUsedAt);
|
||||
const lastUsedStr = `${lastUsed.getMonth() + 1}月${lastUsed.getDate()}日 ${lastUsed.getHours().toString().padStart(2, '0')}:${lastUsed.getMinutes().toString().padStart(2, '0')}`;
|
||||
tooltip += `\n最后使用:${lastUsedStr}`;
|
||||
}
|
||||
return tooltip;
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
@ -571,7 +521,7 @@ const initUsageChart = (data: TeacherCourseUsageItem[]) => {
|
||||
labelLine: {
|
||||
show: false,
|
||||
},
|
||||
data: data.map((item, index) => ({
|
||||
data: chartData.map((item, index) => ({
|
||||
...item,
|
||||
itemStyle: {
|
||||
color: [
|
||||
@ -609,22 +559,20 @@ const loadTrendData = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 加载课程使用数据
|
||||
// 加载课程使用数据(增强版)
|
||||
const loadUsageData = async () => {
|
||||
usageLoading.value = true;
|
||||
try {
|
||||
const data = await getTeacherCourseUsage();
|
||||
courseUsageData.value = data;
|
||||
const data = await getTeacherCourseUsageStats({
|
||||
periodType: usagePeriodType.value
|
||||
});
|
||||
courseUsageStatsData.value = data;
|
||||
initUsageChart(courseUsageStatsData.value);
|
||||
} catch (error) {
|
||||
console.error('Failed to load usage data:', error);
|
||||
} finally {
|
||||
usageLoading.value = false;
|
||||
}
|
||||
// 在 loading 结束后初始化图表
|
||||
await nextTick();
|
||||
if (courseUsageData.value.length > 0) {
|
||||
initUsageChart(courseUsageData.value);
|
||||
}
|
||||
};
|
||||
|
||||
// 窗口大小变化时重绘图表
|
||||
@ -639,36 +587,8 @@ const loadDashboard = async () => {
|
||||
const data = await getTeacherDashboard();
|
||||
stats.value = data.stats || { classCount: 0, studentCount: 0, lessonCount: 0, courseCount: 0 };
|
||||
|
||||
// 映射今日课程数据,处理可能缺失的字段
|
||||
todayLessons.value = (data.todayLessons || []).map((lesson: any) => ({
|
||||
id: lesson.id,
|
||||
courseId: lesson.courseId,
|
||||
courseName: lesson.courseName || lesson.course?.name || '未命名课程',
|
||||
pictureBookName: lesson.pictureBookName,
|
||||
classId: lesson.classId,
|
||||
className: lesson.className || lesson.class?.name || '未命名班级',
|
||||
plannedDatetime: lesson.plannedDatetime || lesson.startDatetime || lesson.lessonDate,
|
||||
status: lesson.status,
|
||||
duration: lesson.duration || lesson.actualDuration || 30, // 默认30分钟
|
||||
}));
|
||||
|
||||
// 映射推荐课程数据,处理可能缺失的字段
|
||||
recommendedCourses.value = (data.recommendedCourses || []).map((course: any) => ({
|
||||
id: course.id,
|
||||
name: course.name || '未命名课程',
|
||||
pictureBookName: course.pictureBookName,
|
||||
coverImagePath: course.coverImagePath,
|
||||
duration: course.duration || 30, // 默认30分钟
|
||||
usageCount: course.usageCount || 0,
|
||||
avgRating: course.avgRating || 0, // 默认0分
|
||||
gradeTags: course.gradeTags || [],
|
||||
}));
|
||||
|
||||
// 处理近期活动数据
|
||||
recentActivities.value = (data.recentActivities || []).map((item: RecentActivity) => ({
|
||||
...item,
|
||||
time: formatActivityTime(item.time),
|
||||
}));
|
||||
todayLessons.value = data.todayLessons || [];
|
||||
recommendedCourses.value = data.recommendedCourses || [];
|
||||
} catch (error: any) {
|
||||
console.error('Failed to load dashboard:', error);
|
||||
message.error(error?.response?.data?.message || '加载数据失败');
|
||||
@ -677,11 +597,11 @@ const loadDashboard = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const startLesson = (lesson: TodayLesson) => {
|
||||
const startLesson = (lesson: TeacherLessonItem) => {
|
||||
router.push(`/teacher/lessons/${lesson.id}`);
|
||||
};
|
||||
|
||||
const viewCourse = (course: RecommendedCourse) => {
|
||||
const viewCourse = (course: TeacherCoursePackageItem) => {
|
||||
router.push(`/teacher/courses/${course.id}`);
|
||||
};
|
||||
|
||||
@ -1244,75 +1164,6 @@ onUnmounted(() => {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
/* 活动区域 */
|
||||
.activity-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.activity-card .card-body {
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.activity-timeline {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.activity-item {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.activity-dot {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.activity-item.type-lesson .activity-dot {
|
||||
background: linear-gradient(135deg, #FFE4C9 0%, #FFF0E0 100%);
|
||||
color: #FF8C42;
|
||||
}
|
||||
|
||||
.activity-item.type-feedback .activity-dot {
|
||||
background: linear-gradient(135deg, #D6E4FF 0%, #E8F0FF 100%);
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.activity-item.type-student .activity-dot {
|
||||
background: linear-gradient(135deg, #D9F7BE 0%, #E8FFD4 100%);
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.activity-item.type-course .activity-dot {
|
||||
background: linear-gradient(135deg, #FFE7BA 0%, #FFF1D6 100%);
|
||||
color: #FA8C16;
|
||||
}
|
||||
|
||||
.activity-content {
|
||||
flex: 1;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.activity-text {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.activity-time {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media (max-width: 1200px) {
|
||||
.stats-grid {
|
||||
|
||||
@ -27,8 +27,8 @@ test.describe('数据看板', () => {
|
||||
const lessonCard = page.getByText(/月授课|授课次数/).first();
|
||||
await expect(lessonCard).toBeVisible();
|
||||
|
||||
// 验证覆盖学生卡片
|
||||
const studentCard = page.getByText(/覆盖学生|学生数/).first();
|
||||
// 验证学生总数卡片
|
||||
const studentCard = page.getByText(/学生总数|学生数/).first();
|
||||
await expect(studentCard).toBeVisible();
|
||||
});
|
||||
|
||||
|
||||
@ -0,0 +1,74 @@
|
||||
package com.reading.platform.common.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 教师活跃度等级枚举
|
||||
* 根据统计周期内的授课次数划分活跃程度
|
||||
*/
|
||||
@Getter
|
||||
public enum TeacherActivityLevel {
|
||||
|
||||
/**
|
||||
* 高频活跃:统计周期内授课次数 >= 10 次
|
||||
*/
|
||||
HIGH("HIGH", "高频", 10),
|
||||
|
||||
/**
|
||||
* 中频活跃:统计周期内授课次数 5-9 次
|
||||
*/
|
||||
MEDIUM("MEDIUM", "中频", 5),
|
||||
|
||||
/**
|
||||
* 低频活跃:统计周期内授课次数 1-4 次
|
||||
*/
|
||||
LOW("LOW", "低频", 1),
|
||||
|
||||
/**
|
||||
* 未活跃:统计周期内授课次数为 0 次
|
||||
*/
|
||||
INACTIVE("INACTIVE", "未活跃", 0);
|
||||
|
||||
private final String code;
|
||||
private final String description;
|
||||
private final int minLessonCount;
|
||||
|
||||
TeacherActivityLevel(String code, String description, int minLessonCount) {
|
||||
this.code = code;
|
||||
this.description = description;
|
||||
this.minLessonCount = minLessonCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据授课次数获取活跃度等级
|
||||
*
|
||||
* @param lessonCount 授课次数
|
||||
* @return 活跃度等级
|
||||
*/
|
||||
public static TeacherActivityLevel fromLessonCount(int lessonCount) {
|
||||
if (lessonCount >= HIGH.minLessonCount) {
|
||||
return HIGH;
|
||||
} else if (lessonCount >= MEDIUM.minLessonCount) {
|
||||
return MEDIUM;
|
||||
} else if (lessonCount >= LOW.minLessonCount) {
|
||||
return LOW;
|
||||
} else {
|
||||
return INACTIVE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据编码获取活跃度等级
|
||||
*
|
||||
* @param code 编码
|
||||
* @return 活跃度等级
|
||||
*/
|
||||
public static TeacherActivityLevel fromCode(String code) {
|
||||
for (TeacherActivityLevel level : values()) {
|
||||
if (level.getCode().equals(code)) {
|
||||
return level;
|
||||
}
|
||||
}
|
||||
return INACTIVE;
|
||||
}
|
||||
}
|
||||
@ -119,4 +119,25 @@ public class JwtTokenProvider {
|
||||
.getSubject();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 token 剩余过期时间(秒)
|
||||
* @param token token 字符串
|
||||
* @return 剩余秒数,如果 token 无效则返回 0
|
||||
*/
|
||||
public long getRemainingExpiration(String token) {
|
||||
try {
|
||||
Date expiryDate = Jwts.parser()
|
||||
.verifyWith(secretKey)
|
||||
.build()
|
||||
.parseSignedClaims(token)
|
||||
.getPayload()
|
||||
.getExpiration();
|
||||
long remaining = (expiryDate.getTime() - System.currentTimeMillis()) / 1000;
|
||||
return remaining > 0 ? remaining : 0;
|
||||
} catch (Exception e) {
|
||||
log.warn("获取 token 剩余过期时间失败:{}", e.getMessage());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -6,8 +6,10 @@ import com.reading.platform.common.mapper.TenantMapper;
|
||||
import com.reading.platform.common.mapper.TeacherMapper;
|
||||
import com.reading.platform.common.response.Result;
|
||||
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;
|
||||
@ -16,8 +18,10 @@ import com.reading.platform.entity.Teacher;
|
||||
import com.reading.platform.service.AuthService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "认证管理", description = "Authentication APIs")
|
||||
@ -63,11 +67,21 @@ public class AuthController {
|
||||
@PostMapping("/change-password")
|
||||
public Result<Void> changePassword(
|
||||
@RequestParam String oldPassword,
|
||||
@RequestParam String newPassword) {
|
||||
authService.changePassword(oldPassword, newPassword);
|
||||
@RequestParam String newPassword,
|
||||
HttpServletRequest request) {
|
||||
String token = resolveToken(request);
|
||||
authService.changePassword(oldPassword, newPassword, token);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "修改个人信息")
|
||||
@PutMapping("/profile")
|
||||
public Result<UpdateProfileResponse> updateProfile(
|
||||
@Valid @RequestBody UpdateProfileRequest request) {
|
||||
UpdateProfileResponse response = authService.updateProfile(request);
|
||||
return Result.success(response);
|
||||
}
|
||||
|
||||
private UserInfoResponse convertToUserInfoResponse(Object userInfo) {
|
||||
if (userInfo instanceof Tenant) {
|
||||
Tenant tenant = (Tenant) userInfo;
|
||||
@ -121,4 +135,15 @@ public class AuthController {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求头获取 token
|
||||
*/
|
||||
private String resolveToken(HttpServletRequest request) {
|
||||
String bearerToken = request.getHeader("Authorization");
|
||||
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
|
||||
return bearerToken.substring(7);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -5,10 +5,8 @@ import com.reading.platform.common.enums.UserRole;
|
||||
import com.reading.platform.common.response.Result;
|
||||
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.service.StatsService;
|
||||
@ -58,10 +56,4 @@ public class AdminStatsController {
|
||||
return Result.success(statsService.getPopularCourses(request));
|
||||
}
|
||||
|
||||
@GetMapping("/activities")
|
||||
@Operation(summary = "获取最近活动")
|
||||
public Result<List<RecentActivityItemResponse>> getRecentActivities(@ModelAttribute RecentActivitiesQueryRequest request) {
|
||||
return Result.success(statsService.getRecentActivities(request));
|
||||
}
|
||||
|
||||
}
|
||||
@ -9,6 +9,7 @@ import com.reading.platform.dto.response.LessonTagResponse;
|
||||
import com.reading.platform.dto.response.SchoolCourseResponse;
|
||||
import com.reading.platform.entity.CourseLesson;
|
||||
import com.reading.platform.entity.CoursePackage;
|
||||
import com.reading.platform.mapper.LessonMapper;
|
||||
import com.reading.platform.service.CourseLessonService;
|
||||
import com.reading.platform.service.CoursePackageService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@ -19,6 +20,7 @@ import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@ -34,6 +36,7 @@ public class SchoolCourseController {
|
||||
|
||||
private final CoursePackageService courseService;
|
||||
private final CourseLessonService courseLessonService;
|
||||
private final LessonMapper lessonMapper;
|
||||
|
||||
@GetMapping
|
||||
@Operation(summary = "获取学校课程包列表(分页)")
|
||||
@ -56,6 +59,25 @@ public class SchoolCourseController {
|
||||
.map(SchoolCourseResponse::toSchoolCourseResponse)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 按租户 ID 统计每个课程的使用次数(已完成状态的 Lesson 数量)
|
||||
if (!list.isEmpty()) {
|
||||
List<Long> courseIds = list.stream().map(SchoolCourseResponse::getId).collect(Collectors.toList());
|
||||
List<Map<String, Object>> usageStats = lessonMapper.countUsageByTenantAndCourseIds(tenantId, courseIds);
|
||||
|
||||
// 构建课程 ID -> 使用次数的映射
|
||||
Map<Long, Integer> usageCountMap = usageStats.stream()
|
||||
.collect(Collectors.toMap(
|
||||
m -> ((Number) m.get("course_id")).longValue(),
|
||||
m -> ((Number) m.get("usageCount")).intValue(),
|
||||
(v1, v2) -> v1 // 如果有重复,取第一个值
|
||||
));
|
||||
|
||||
// 填充 usageCount,如果没有统计数据显示为 0
|
||||
for (SchoolCourseResponse vo : list) {
|
||||
vo.setUsageCount(usageCountMap.getOrDefault(vo.getId(), 0));
|
||||
}
|
||||
}
|
||||
|
||||
// 填充 lessonTags
|
||||
for (SchoolCourseResponse vo : list) {
|
||||
List<CourseLesson> lessons = courseLessonService.findByCourseId(vo.getId());
|
||||
|
||||
@ -2,12 +2,15 @@ package com.reading.platform.controller.school;
|
||||
|
||||
import com.reading.platform.common.response.Result;
|
||||
import com.reading.platform.common.security.SecurityUtils;
|
||||
import com.reading.platform.dto.response.TeacherActivityRankResponse;
|
||||
import com.reading.platform.service.SchoolStatsService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ -31,33 +34,27 @@ public class SchoolStatsController {
|
||||
|
||||
@GetMapping("/teachers")
|
||||
@Operation(summary = "获取活跃教师排行")
|
||||
public Result<List<Map<String, Object>>> getActiveTeachers(
|
||||
@RequestParam(defaultValue = "5") int limit) {
|
||||
public Result<List<TeacherActivityRankResponse>> getActiveTeachers(
|
||||
@RequestParam(defaultValue = "10") int limit) {
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
return Result.success(schoolStatsService.getActiveTeachers(tenantId, limit));
|
||||
}
|
||||
|
||||
@GetMapping("/courses")
|
||||
@Operation(summary = "获取课程使用统计")
|
||||
public Result<List<Map<String, Object>>> getCourseUsageStats() {
|
||||
public Result<List<Map<String, Object>>> 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));
|
||||
}
|
||||
|
||||
@GetMapping("/activities")
|
||||
@Operation(summary = "获取近期活动")
|
||||
public Result<List<Map<String, Object>>> getRecentActivities(
|
||||
@RequestParam(defaultValue = "10") int limit) {
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
return Result.success(schoolStatsService.getRecentActivities(tenantId, limit));
|
||||
return Result.success(schoolStatsService.getCourseUsageStats(tenantId, startDate, endDate));
|
||||
}
|
||||
|
||||
@GetMapping("/lesson-trend")
|
||||
@Operation(summary = "获取授课趋势")
|
||||
public Result<List<Map<String, Object>>> getLessonTrend(
|
||||
@RequestParam(defaultValue = "6") int months) {
|
||||
@RequestParam(defaultValue = "7") int days) {
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
return Result.success(schoolStatsService.getLessonTrend(tenantId, months));
|
||||
return Result.success(schoolStatsService.getLessonTrend(tenantId, days));
|
||||
}
|
||||
|
||||
@GetMapping("/course-distribution")
|
||||
|
||||
@ -2,16 +2,16 @@ package com.reading.platform.controller.teacher;
|
||||
|
||||
import com.reading.platform.common.response.Result;
|
||||
import com.reading.platform.common.security.SecurityUtils;
|
||||
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.*;
|
||||
import com.reading.platform.service.TeacherStatsService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 统计数据控制器(教师端)
|
||||
@ -26,7 +26,7 @@ public class TeacherStatsController {
|
||||
|
||||
@GetMapping("/dashboard")
|
||||
@Operation(summary = "获取教师端首页统计数据")
|
||||
public Result<Map<String, Object>> getDashboard() {
|
||||
public Result<TeacherDashboardResponse> getDashboard() {
|
||||
Long teacherId = SecurityUtils.getCurrentUserId();
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
return Result.success(teacherStatsService.getDashboard(teacherId, tenantId));
|
||||
@ -34,36 +34,56 @@ public class TeacherStatsController {
|
||||
|
||||
@GetMapping("/today-lessons")
|
||||
@Operation(summary = "获取今日课程")
|
||||
public Result<List<Lesson>> getTodayLessons() {
|
||||
public Result<List<TeacherLessonVO>> getTodayLessons() {
|
||||
Long teacherId = SecurityUtils.getCurrentUserId();
|
||||
return Result.success(teacherStatsService.getTodayLessons(teacherId));
|
||||
}
|
||||
|
||||
@GetMapping("/recommended-courses")
|
||||
@Operation(summary = "获取推荐课程")
|
||||
public Result<List<CoursePackage>> getRecommendedCourses() {
|
||||
public Result<List<CoursePackageVO>> getRecommendedCourses() {
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
return Result.success(teacherStatsService.getRecommendedCourses(tenantId));
|
||||
}
|
||||
|
||||
@GetMapping("/weekly-stats")
|
||||
@Operation(summary = "获取本周统计")
|
||||
public Result<Map<String, Object>> getWeeklyStats() {
|
||||
public Result<TeacherWeeklyStatsResponse> getWeeklyStats() {
|
||||
Long teacherId = SecurityUtils.getCurrentUserId();
|
||||
return Result.success(teacherStatsService.getWeeklyStats(teacherId));
|
||||
}
|
||||
|
||||
@GetMapping("/lesson-trend")
|
||||
@Operation(summary = "获取授课趋势")
|
||||
public Result<List<Map<String, Object>>> getLessonTrend(
|
||||
public Result<List<TeacherLessonTrendVO>> getLessonTrend(
|
||||
@RequestParam(defaultValue = "6") int months) {
|
||||
Long teacherId = SecurityUtils.getCurrentUserId();
|
||||
return Result.success(teacherStatsService.getLessonTrend(teacherId, months));
|
||||
}
|
||||
|
||||
@GetMapping("/course-usage-stats")
|
||||
@Operation(summary = "获取课程使用统计(增强版)")
|
||||
public Result<List<CourseUsageStatsVO>> getCourseUsageStats(
|
||||
@RequestParam(required = false) String periodType,
|
||||
@RequestParam(required = false) LocalDate startDate,
|
||||
@RequestParam(required = false) LocalDate endDate) {
|
||||
|
||||
Long teacherId = SecurityUtils.getCurrentUserId();
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
|
||||
CourseUsageQuery query = CourseUsageQuery.builder()
|
||||
.periodType(periodType)
|
||||
.startDate(startDate)
|
||||
.endDate(endDate)
|
||||
.build();
|
||||
|
||||
return Result.success(teacherStatsService.getCourseUsageStats(tenantId, teacherId, query));
|
||||
}
|
||||
|
||||
@GetMapping("/course-usage")
|
||||
@Operation(summary = "获取课程使用统计")
|
||||
public Result<List<Map<String, Object>>> getCourseUsage() {
|
||||
@Operation(summary = "获取课程使用统计(旧版)")
|
||||
@Deprecated
|
||||
public Result<List<CourseUsageVO>> getCourseUsage() {
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
return Result.success(teacherStatsService.getCourseUsage(tenantId));
|
||||
}
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
package com.reading.platform.dto.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.Builder;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 课程使用统计查询参数
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "课程使用统计查询参数")
|
||||
public class CourseUsageQuery {
|
||||
|
||||
@Schema(description = "统计周期类型:TODAY-今日,WEEK-本周,MONTH-本月,CUSTOM-自定义")
|
||||
private String periodType;
|
||||
|
||||
@Schema(description = "自定义周期开始日期")
|
||||
private LocalDate startDate;
|
||||
|
||||
@Schema(description = "自定义周期结束日期")
|
||||
private LocalDate endDate;
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
package com.reading.platform.dto.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 最近活动查询请求
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "最近活动查询请求")
|
||||
public class RecentActivitiesQueryRequest {
|
||||
|
||||
@Schema(description = "返回数量限制", example = "10")
|
||||
private Integer limit = 10;
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
package com.reading.platform.dto.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 修改个人信息请求 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "修改个人信息请求")
|
||||
public class UpdateProfileRequest {
|
||||
|
||||
@Schema(description = "姓名", example = "张三")
|
||||
@Pattern(regexp = "^[\u4e00-\u9fa5a-zA-Z\\s]{2,20}$", message = "姓名长度为 2-20 位,只能包含中文或英文")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "手机号", example = "13800138000")
|
||||
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
|
||||
private String phone;
|
||||
|
||||
@Schema(description = "邮箱", example = "zhangsan@example.com")
|
||||
@Email(message = "邮箱格式不正确")
|
||||
private String email;
|
||||
}
|
||||
@ -22,9 +22,9 @@ public class ActiveTenantItemResponse {
|
||||
@Schema(description = "租户名称")
|
||||
private String tenantName;
|
||||
|
||||
@Schema(description = "活跃用户数")
|
||||
private Integer activeUsers;
|
||||
@Schema(description = "活跃教师数(近 30 天有完成课程的老师数)")
|
||||
private Integer activeTeacherCount;
|
||||
|
||||
@Schema(description = "课程使用数")
|
||||
private Integer courseCount;
|
||||
@Schema(description = "完成课次数(近 30 天 COMPLETED 状态的 lesson 总数)")
|
||||
private Integer completedLessonCount;
|
||||
}
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
package com.reading.platform.dto.response;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 课程包视图对象
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "课程包视图对象")
|
||||
public class CoursePackageVO {
|
||||
|
||||
@Schema(description = "ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "适用年级")
|
||||
private String gradeLevel;
|
||||
|
||||
@Schema(description = "课程数量")
|
||||
private Integer courseCount;
|
||||
|
||||
@Schema(description = "状态")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "使用次数")
|
||||
private Integer usageCount;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Schema(description = "更新时间")
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package com.reading.platform.dto.response;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.Builder;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 课程使用统计响应对象(增强版)
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "课程使用统计响应对象")
|
||||
public class CourseUsageStatsVO {
|
||||
|
||||
@Schema(description = "课程包 ID")
|
||||
private Long coursePackageId;
|
||||
|
||||
@Schema(description = "课程包名称")
|
||||
private String coursePackageName;
|
||||
|
||||
@Schema(description = "使用次数")
|
||||
private Integer usageCount;
|
||||
|
||||
@Schema(description = "参与学生数(去重)")
|
||||
private Integer studentCount;
|
||||
|
||||
@Schema(description = "平均时长(分钟)")
|
||||
private Integer avgDuration;
|
||||
|
||||
@Schema(description = "最后使用时间")
|
||||
private LocalDateTime lastUsedAt;
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package com.reading.platform.dto.response;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
/**
|
||||
* 课程使用统计视图对象
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "课程使用统计视图对象")
|
||||
public class CourseUsageVO {
|
||||
|
||||
@Schema(description = "课程包名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "使用次数")
|
||||
private Integer value;
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
package com.reading.platform.dto.response;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 最近活动项响应
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "最近活动项响应")
|
||||
public class RecentActivityItemResponse {
|
||||
|
||||
@Schema(description = "活动 ID")
|
||||
private Long activityId;
|
||||
|
||||
@Schema(description = "活动类型")
|
||||
private String activityType;
|
||||
|
||||
@Schema(description = "活动描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "操作人 ID")
|
||||
private Long operatorId;
|
||||
|
||||
@Schema(description = "操作人名称")
|
||||
private String operatorName;
|
||||
|
||||
@Schema(description = "操作时间")
|
||||
private LocalDateTime operationTime;
|
||||
}
|
||||
@ -33,4 +33,7 @@ public class StatsResponse {
|
||||
|
||||
@Schema(description = "课时总数")
|
||||
private Long totalLessons;
|
||||
|
||||
@Schema(description = "月授课次数(本月 COMPLETED 状态的 lesson 总数)")
|
||||
private Long monthlyLessons;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user