diff --git a/docs/dev-logs/2026-03-21.md b/docs/dev-logs/2026-03-21.md index 208ac34..0994576 100644 --- a/docs/dev-logs/2026-03-21.md +++ b/docs/dev-logs/2026-03-21.md @@ -304,3 +304,225 @@ onMounted(() => { --- **今日完成**: 学校端数据导出功能(3 个导出接口) + +--- + +## 学校端 - 数据报告功能实现 + +### 需求背景 + +学校端"数据中心"菜单下的"数据报告"子菜单功能之前未实现。前端 `ReportView.vue` 页面已存在,但调用的 API 返回空数据。需要实现完整的数据报告功能,让学校管理员能够查看: +1. **整体概览** - 教师总数、学生总数、班级总数、本月授课次数 +2. **教师报告** - 教师教学数据统计(授课次数、任务数、评分) +3. **课程报告** - 课程使用排行(授课次数、学生数、完成率) +4. **学生报告** - 学生学习数据(完成任务、成长记录、出勤率) + +### 实现内容 + +#### 1. 新建后端 Mapper + +**ReportMapper.java** - 数据报告 Mapper + +```java +@Mapper +public interface ReportMapper extends BaseMapper { + // 4 个统计查询方法 + Map getOverviewStats(...) // 概览统计 + List> getTeacherReports(...) // 教师报告 + List> getCourseReports(...) // 课程报告 + List> getStudentReports(...) // 学生报告 +} +``` + +**SQL 查询逻辑**: +- **概览统计**: 统计教师数、学生数、班级数、本月授课次数、本月任务完成数 +- **教师报告**: 按教师分组,统计授课次数、任务完成数、平均评分、最后活跃时间 +- **课程报告**: 按课程分组,统计授课次数、学生数、平均评分、完成率 +- **学生报告**: 按学生分组,统计任务完成数、成长记录数、出勤率 + +#### 2. 新建后端 Service + +**SchoolReportService.java** - 服务接口 + +```java +public interface SchoolReportService { + ReportOverviewResponse getOverview(Long tenantId, LocalDate startDate, LocalDate endDate); + List getTeacherReports(Long tenantId, LocalDate startDate, LocalDate endDate, int limit); + List getCourseReports(Long tenantId, LocalDate startDate, LocalDate endDate, int limit); + List getStudentReports(Long tenantId, LocalDate startDate, LocalDate endDate, int limit); +} +``` + +**SchoolReportServiceImpl.java** - 服务实现 + +```java +@Slf4j +@Service +@RequiredArgsConstructor +public class SchoolReportServiceImpl implements SchoolReportService { + private final ReportMapper reportMapper; + + @Override + public ReportOverviewResponse getOverview(Long tenantId, LocalDate startDate, LocalDate endDate) { + // 计算时间范围(默认本月) + // 调用 Mapper 查询 + // 转换为 ReportOverviewResponse 返回 + } + // ... 其他方法 +} +``` + +#### 3. 修改 Controller + +**SchoolReportController.java** - 添加日期范围参数 + +```java +@GetMapping("/overview") +public Result getOverview( + @RequestParam(required = false) LocalDate startDate, + @RequestParam(required = false) LocalDate endDate) { + return Result.success(schoolReportService.getOverview(tenantId, startDate, endDate)); +} +// ... 其他接口 +``` + +#### 4. 前端 API 对齐 + +**src/api/school.ts** - 更新类型定义和 API 函数 + +```typescript +// 更新接口定义 +export interface ReportOverview { + reportDate: string; + totalTeachers: number; + totalStudents: number; + totalClasses: number; + monthlyLessons: number; + monthlyTasksCompleted: number; +} + +export interface TeacherReport { + teacherId: number; + teacherName: string; + lessonCount: number; + taskCount: number; + averageRating: number; + lastLessonTime?: string; +} +// ... CourseReport, StudentReport + +// 更新 API 函数支持日期参数 +export const getReportOverview = (startDate?: string, endDate?: string) => { + const params: Record = {}; + if (startDate) params.startDate = startDate; + if (endDate) params.endDate = endDate; + return http.get('/v1/school/reports/overview', { params }); +}; +// ... 其他 API +``` + +#### 5. 前端页面完善 + +**ReportView.vue** - 更新数据绑定和显示 + +```typescript +// 概览数据 +const overviewData = ref({ + reportDate: '', + totalTeachers: 0, + totalStudents: 0, + totalClasses: 0, + monthlyLessons: 0, + monthlyTasksCompleted: 0, +}); + +// 加载数据时传递日期参数 +const loadData = async () => { + const startDate = dateRange.value?.[0]?.format('YYYY-MM-DD'); + const endDate = dateRange.value?.[1]?.format('YYYY-MM-DD'); + + const [overview, teachers, courses, students] = await Promise.all([ + getReportOverview(startDate, endDate), + getTeacherReports(startDate, endDate), + getCourseReports(startDate, endDate), + getStudentReports(startDate, endDate), + ]); + // ... +}; + +// 设置默认日期范围为当月 +onMounted(() => { + setDefaultDateRange(); + loadData(); +}); +``` + +### 字段映射 + +#### 概览卡片 +| 显示项 | 数据字段 | +|--------|---------| +| 教师总数 | totalTeachers | +| 学生总数 | totalStudents | +| 班级总数 | totalClasses | +| 本月授课 | monthlyLessons | + +#### 教师报告 +| 显示项 | 数据字段 | +|--------|---------| +| 教师姓名 | teacherName | +| 授课次数 | lessonCount | +| 任务完成数 | taskCount | +| 平均评分 | averageRating | +| 最后活跃 | lastLessonTime | + +#### 课程报告 +| 显示项 | 数据字段 | +|--------|---------| +| 课程名称 | courseName | +| 授课次数 | lessonCount | +| 参与学生 | studentCount | +| 完成率 | completionRate | +| 平均评分 | averageRating | + +#### 学生报告 +| 显示项 | 数据字段 | +|--------|---------| +| 学生姓名 | studentName | +| 班级 | className | +| 完成任务 | taskCount | +| 成长记录 | growthRecordCount | +| 出勤率 | attendanceRate | + +### 功能特性 + +1. **日期范围筛选**: 支持选择日期范围,默认统计当月数据 +2. **实时刷新**: 选择日期范围后自动刷新数据 +3. **详情弹窗**: 点击教师/课程可查看详细信息 +4. **响应式设计**: 支持不同屏幕尺寸 + +### 文件变更列表 + +| 文件 | 变更说明 | +|------|---------| +| `reading-platform-java/src/main/java/com/reading/platform/mapper/ReportMapper.java` | 新建,4 个统计查询方法 | +| `reading-platform-java/src/main/java/com/reading/platform/service/SchoolReportService.java` | 新建,服务接口 | +| `reading-platform-java/src/main/java/com/reading/platform/service/impl/SchoolReportServiceImpl.java` | 新建,服务实现 | +| `reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolReportController.java` | 修改,调用 Service | +| `reading-platform-frontend/src/api/school.ts` | 更新,类型定义和 API 函数 | +| `reading-platform-frontend/src/views/school/ReportView.vue` | 更新,数据绑定和显示 | + +### 测试验证 + +- [x] 后端编译通过 +- [x] ReportView.vue 类型检查通过 +- [ ] 启动后端服务(端口 8480) +- [ ] 启动前端服务(端口 5173) +- [ ] 登录学校管理员账号 +- [ ] 访问数据报告页面 +- [ ] 验证日期范围筛选 +- [ ] 验证各 Tab 数据显示 + +--- + +**今日完成**: 学校端数据报告功能(4 个统计接口 + 前端页面) diff --git a/docs/test-logs/school/2026-03-22-report-date-range.md b/docs/test-logs/school/2026-03-22-report-date-range.md new file mode 100644 index 0000000..ac3caf2 --- /dev/null +++ b/docs/test-logs/school/2026-03-22-report-date-range.md @@ -0,0 +1,156 @@ +# 测试记录 2026-03-22 - 学校端数据报告日期范围筛选功能 + +## 测试背景 + +学校端数据中心 - 数据报告页面的时间范围筛选器之前不影响"课程使用趋势"和"教师活跃度"图表数据。本次修复实现了这两个图表根据日期范围筛选器动态加载数据的功能。 + +## 修改内容 + +### 后端修改 + +1. **SchoolStatsController.java** + - `getLessonTrend()` - 添加 `startDate` 和 `endDate` 参数 + - `getActiveTeachers()` - 添加 `startDate` 和 `endDate` 参数 + - 添加 `@Parameter` 注解导入 + +2. **SchoolStatsService.java** + - 更新 `getLessonTrend()` 方法签名,使用日期范围参数 + - 更新 `getActiveTeachers()` 方法签名,使用日期范围参数 + +3. **SchoolStatsServiceImpl.java** + - 重写 `getLessonTrend()` 方法,支持日期范围遍历统计 + - 重写 `getActiveTeachers()` 方法,使用日期范围过滤 + +4. **LessonMapper.java** + - 更新 `getTeacherActivityRank()` 方法,添加 `endTime` 参数 + - 更新 SQL 查询,添加 `l.end_datetime <= #{endTime}` 条件 + +### 前端修改 + +1. **src/api/school.ts** + - `getLessonTrend()` - 改为接受 `startDate` 和 `endDate` 参数 + - `getTeacherStats()` - 添加 `startDate` 和 `endDate` 参数 + +2. **src/views/school/ReportView.vue** + - `loadData()` 函数 - 传递日期范围参数给图表 API + +## 测试验证 + +### 1. 后端编译测试 + +```bash +export JAVA_HOME="/f/Java/jdk-17" +cd reading-platform-java +mvn clean compile -DskipTests +``` + +**结果**: ✅ 编译成功 + +### 2. API 接口测试 + +#### 课程趋势 API(带日期参数) + +```bash +curl "http://localhost:8481/api/v1/school/stats/lesson-trend?startDate=2026-03-01&endDate=2026-03-22" +``` + +**响应**: +```json +{ + "code": 200, + "data": [ + {"date": "03-01", "lessonCount": 0, "studentCount": 0}, + {"date": "03-02", "lessonCount": 0, "studentCount": 0}, + ... + {"date": "03-16", "lessonCount": 2, "studentCount": 5}, + ... + {"date": "03-21", "lessonCount": 0, "studentCount": 0} + ] +} +``` + +**结果**: ✅ 返回指定日期范围内的数据 + +#### 教师活跃度 API(带日期参数) + +```bash +curl "http://localhost:8481/api/v1/school/stats/teachers?startDate=2026-03-01&endDate=2026-03-22&limit=10" +``` + +**响应**: +```json +{ + "code": 200, + "data": [ + { + "teacherId": "1", + "teacherName": "李老师", + "classNames": "小一班", + "lessonCount": 2, + "courseCount": 2, + "lastActiveAt": "2026-03-16T00:00:00", + "activityLevelCode": "LOW", + "activityLevelDesc": "低频" + } + ] +} +``` + +**结果**: ✅ 返回指定日期范围内的数据 + +#### 默认参数测试 + +```bash +# 课程趋势(默认最近 7 天) +curl "http://localhost:8481/api/v1/school/stats/lesson-trend" + +# 教师活跃度(默认本月 1 号至今) +curl "http://localhost:8481/api/v1/school/stats/teachers?limit=10" +``` + +**结果**: ✅ 两个接口在不传参数时都使用默认值 + +### 3. 前端编译测试 + +```bash +cd reading-platform-frontend +npm run build +``` + +**结果**: ✅ ReportView.vue 无编译错误 + +### 4. 前端服务测试 + +- 后端服务端口:8481 ✅ 运行中 +- 前端服务端口:5179 ✅ 运行中 +- 访问地址:http://localhost:5179/school/reports + +## 功能验证清单 + +| 测试项 | 状态 | 说明 | +|--------|------|------| +| 后端编译 | ✅ | 无编译错误 | +| 课程趋势 API | ✅ | 支持日期范围参数 | +| 教师活跃度 API | ✅ | 支持日期范围参数 | +| 默认参数 | ✅ | 不传参数时使用默认值 | +| 前端编译 | ✅ | ReportView.vue 无错误 | +| 前端服务 | ✅ | 正常运行 | + +## 已知问题 + +无 + +## 测试结论 + +✅ **测试通过** - 日期范围筛选功能已正确实现,课程使用趋势和教师活跃度图表现在会根据用户选择的日期范围动态加载数据。 + +## 后续建议 + +1. 可以在日期选择器旁边添加"快捷选项",如"最近 7 天"、"最近 30 天"、"本月"等 +2. 考虑添加数据导出功能,允许用户导出选定日期范围内的报告数据 + +--- + +**测试人员**: AI Assistant +**测试日期**: 2026-03-22 +**测试环境**: Windows 10, JDK 17, Node.js diff --git a/reading-platform-frontend/src/api/school.ts b/reading-platform-frontend/src/api/school.ts index 288173e..ba6ac47 100644 --- a/reading-platform-frontend/src/api/school.ts +++ b/reading-platform-frontend/src/api/school.ts @@ -673,8 +673,12 @@ export interface CourseDistributionItem { // 后端趋势数据响应(对象数组格式) export type LessonTrendResponse = LessonTrendItem[]; -export const getLessonTrend = (days?: number) => - http.get('/v1/school/stats/lesson-trend', { params: { days } }); +export const getLessonTrend = (startDate?: string, endDate?: string) => { + const params: Record = {}; + if (startDate) params.startDate = startDate; + if (endDate) params.endDate = endDate; + return http.get('/v1/school/stats/lesson-trend', { params }); +}; export const getCourseDistribution = () => http.get('/v1/school/stats/course-distribution'); @@ -1210,50 +1214,92 @@ export const getSchoolClasses = () => // ==================== 数据报告 API ==================== export interface ReportOverview { - totalLessons: number; - activeTeacherCount: number; - usedCourseCount: number; - avgRating: number; + reportDate: string; + totalTeachers: number; + totalStudents: number; + totalClasses: number; + monthlyLessons: number; + monthlyTasksCompleted: number; } export interface TeacherReport { - id: number; - name: string; + teacherId: number; + teacherName: string; lessonCount: number; - courseCount: number; - feedbackCount: number; - avgRating: number; + taskCount: number; + averageRating: number; + lastLessonTime?: string; } export interface CourseReport { - id: number; - name: string; + courseId: number; + courseName: string; lessonCount: number; - teacherCount: number; studentCount: number; - avgRating: number; + averageRating: number; + completionRate: number; } export interface StudentReport { - id: number; - name: string; + studentId: number; + studentName: string; className: string; - lessonCount: number; - avgFocus: number; - avgParticipation: number; + taskCount: number; + readingCount: number; + growthRecordCount: number; + attendanceRate: number; } -export const getReportOverview = () => - http.get('/v1/school/reports/overview'); +export const getReportOverview = (startDate?: string, endDate?: string) => { + const params: Record = {}; + if (startDate) params.startDate = startDate; + if (endDate) params.endDate = endDate; + return http.get('/v1/school/reports/overview', { params }); +}; -export const getTeacherReports = () => - http.get('/v1/school/reports/teachers'); +export const getTeacherReports = (startDate?: string, endDate?: string, limit: number = 50) => { + const params: Record = { limit }; + if (startDate) params.startDate = startDate; + if (endDate) params.endDate = endDate; + return http.get('/v1/school/reports/teachers', { params }); +}; -export const getCourseReports = () => - http.get('/v1/school/reports/courses'); +export const getCourseReports = (startDate?: string, endDate?: string, limit: number = 50) => { + const params: Record = { limit }; + if (startDate) params.startDate = startDate; + if (endDate) params.endDate = endDate; + return http.get('/v1/school/reports/courses', { params }); +}; -export const getStudentReports = () => - http.get('/v1/school/reports/students'); +export const getStudentReports = (startDate?: string, endDate?: string, limit: number = 50) => { + const params: Record = { limit }; + if (startDate) params.startDate = startDate; + if (endDate) params.endDate = endDate; + return http.get('/v1/school/reports/students', { params }); +}; + +// ==================== 数据报告 - 图表 API ==================== + +export interface TeacherActivityItem { + teacherId: number; + teacherName: string; + classNames: string; + lessonCount: number; + courseCount: number; + lastActiveAt?: string; + activityLevelCode: string; + activityLevelDesc: string; +} + +/** + * 获取活跃教师排行 + */ +export const getTeacherStats = (startDate?: string, endDate?: string, limit: number = 10) => { + const params: Record = { limit }; + if (startDate) params.startDate = startDate; + if (endDate) params.endDate = endDate; + return http.get('/v1/school/stats/teachers', { params }); +}; // ==================== 家长管理 ==================== diff --git a/reading-platform-frontend/src/views/school/ReportView.vue b/reading-platform-frontend/src/views/school/ReportView.vue index 8662c66..99af4e8 100644 --- a/reading-platform-frontend/src/views/school/ReportView.vue +++ b/reading-platform-frontend/src/views/school/ReportView.vue @@ -13,8 +13,13 @@
- - + + 导出报告 @@ -27,38 +32,38 @@
- +
-
{{ totalLessons }}
-
总授课次数
+
{{ overviewData.totalTeachers }}
+
教师总数
- +
-
{{ activeTeacherCount }}
-
活跃教师
+
{{ overviewData.totalStudents }}
+
学生总数
- +
-
{{ usedCourseCount }}
-
使用课程
+
{{ overviewData.totalClasses }}
+
班级总数
- +
-
{{ avgRating }}
-
平均评分
+
{{ overviewData.monthlyLessons }}
+
本月授课
@@ -80,63 +85,92 @@
-
-
-
- -

课程使用趋势

-
-
-
-
+ +
+
+
+ +

课程使用趋势

-
- 周{{ day }} +
+
+
+
{{ item.lessonCount }}
+
+
{{ item.date }}
+
+
-
-
-
- -

教师活跃度

-
-
-
- - - - - - - - - - -
- 75% - 活跃率 +
+
+ +

教师活跃度

+
+
+
+ + + + + + + + + + +
+ {{ teacherActiveRate }}% + 活跃率 +
+
+
+
+ 活跃教师 + {{ teacherStats.filter(t => t.lessonCount > 0).length }}人 +
+
+ 教师总数 + {{ overviewData.totalTeachers }}人 +
-
+
-
+
-
{{ teacher.name }}
+
{{ teacher.teacherName }}
- {{ teacher.avgRating.toFixed(1) }} + :class="{ 'filled': i <= Math.round(teacher.averageRating) }" /> + {{ teacher.averageRating.toFixed(1) }}
@@ -148,13 +182,13 @@
- {{ teacher.courseCount }} - 使用课程 + {{ teacher.taskCount }} + 任务数
- - {{ teacher.feedbackCount }} - 反馈次数 + + {{ teacher.lastLessonTime ? '活跃' : '未活跃' }} + 最后活跃
@@ -171,28 +205,28 @@
-
+
{{ index + 1 }}
-
{{ course.name }}
+
{{ course.courseName }}
授课{{ course.lessonCount }}次 - {{ course.teacherCount }}位教师 + {{ course.studentCount }}名学生 - {{ course.studentCount }}名学生 + 完成率{{ course.completionRate?.toFixed(0) || 0 }}%
- {{ course.avgRating.toFixed(1) }} + {{ course.averageRating.toFixed(1) }}
@@ -207,40 +241,37 @@
-
+
-
{{ student.name }}
+
{{ student.studentName }}
{{ student.className }}
- 参与课程 + 完成任务 - {{ student.lessonCount }} 次 + {{ student.taskCount }} 次
- 专注度 + 成长记录 + + {{ student.growthRecordCount }} 条 +
+
+ + 出勤率
-
+
- {{ student.avgFocus }}/5 -
-
- - 参与度 - -
-
-
- {{ student.avgParticipation }}/5 + {{ student.attendanceRate?.toFixed(0) || 0 }}%
@@ -251,7 +282,7 @@
-
@@ -259,10 +290,10 @@
-

{{ selectedTeacher.name }}

+

{{ selectedTeacher.teacherName }}

- - {{ selectedTeacher.avgRating.toFixed(1) }} 分 + + {{ selectedTeacher.averageRating.toFixed(1) }} 分
@@ -273,12 +304,12 @@
授课次数
-
{{ selectedTeacher.courseCount }}
-
使用课程数
+
{{ selectedTeacher.taskCount }}
+
任务完成数
-
{{ selectedTeacher.feedbackCount }}
-
反馈次数
+
{{ selectedTeacher.lastLessonTime ? '活跃' : '未活跃' }}
+
最后活跃
@@ -287,12 +318,11 @@ 教学概况

- {{ selectedTeacher.name }} 老师共完成 {{ selectedTeacher.lessonCount }} 次授课, - 使用了 {{ selectedTeacher.courseCount }} 门不同的课程, - 累计获得 {{ selectedTeacher.feedbackCount }} 次教学反馈。 -