主要变更: 1. 新建 ReportMapper - 数据报告统计查询 - getOverviewStats: 概览统计(教师/学生/班级总数、本月授课次数) - getTeacherReports: 教师教学数据统计 - getCourseReports: 课程使用排行统计 - getStudentReports: 学生学习数据统计 2. 新建 SchoolReportService - 数据报告服务层 - 4 个报告查询接口实现 3. 修改 SchoolStatsController - 调整统计接口参数 - getLessonTrend 改为支持 startDate 和 endDate 参数 4. 前端更新 ReportView.vue - 对接 4 个报告接口 - 优化图表展示和数据表格 - 支持日期范围筛选 5. 更新开发日志和测试记录 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
18 KiB
开发日志 2026-03-21
学校端 - 课程使用统计功能实现
需求背景
学校端 Dashboard 页面已有"课程使用统计"卡片组件,但后端返回空数据。需要实现该功能,让学校管理员能够查看本校各课程包的使用情况。
实现内容
1. 后端修改
Controller 层 (SchoolStatsController.java)
- 添加日期范围参数支持,允许前端传入
startDate和endDate - 不传参数时默认统计本月数据
@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导入
List<Map<String, Object>> getCourseUsageStats(Long tenantId, LocalDate startDate, LocalDate endDate);
Service 实现 (SchoolStatsServiceImpl.java)
- 使用
LessonMapper.getCourseUsageStats()查询实际数据 - 支持日期范围筛选,默认统计本月
- 将
CourseUsageStatsVO转换为Map格式返回
@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支持日期参数 - 增强返回类型定义
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中设置默认日期范围为当月
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 页面,查看"课程使用统计"卡片
- 验证日期范围筛选功能
- 验证数据显示正确性
后续优化建议
- 增加更多统计维度:按班级、按教师统计
- 可视化增强:趋势图、热力图
- 导出功能:Excel 导出课程使用明细
- 性能优化:大数据量时考虑缓存
学校端 - 数据导出功能实现
需求背景
学校端数据概览页面(DashboardView.vue)已有数据导出的 UI 界面和前端调用逻辑,但后端 SchoolExportController.java 中的 4 个接口只返回占位数据,没有实际的 Excel 导出功能。需要实现学校端数据概览的导出功能,包括:
- 授课记录导出 - 导出指定时间范围内的授课记录
- 教师绩效导出 - 导出教师绩效统计数据
- 学生统计导出 - 导出学生统计数据
实现内容
1. 添加依赖
在 pom.xml 中添加 EasyExcel 3.3.4:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.4</version>
</dependency>
2. 新建 DTO 类
dto/response/LessonExportVO.java- 授课记录导出 VO(9 个字段)dto/response/TeacherPerformanceExportVO.java- 教师绩效导出 VO(7 个字段)dto/response/StudentStatExportVO.java- 学生统计导出 VO(6 个字段)
3. 新建 Service
service/SchoolExportService.java- 导出服务接口service/impl/SchoolExportServiceImpl.java- 导出服务实现
4. 扩展 Mapper
在 LessonMapper.java 中添加 3 个导出查询方法:
selectExportData()- 授课记录查询selectTeacherExportData()- 教师绩效查询selectStudentExportData()- 学生统计查询
5. 修改 Controller
controller/school/SchoolExportController.java - 实现 3 个导出接口:
GET /api/v1/school/export/lessons- 授课记录导出GET /api/v1/school/export/teacher-stats- 教师绩效导出GET /api/v1/school/export/student-stats- 学生统计导出
6. 修改前端 API
src/api/school.ts - 增强导出函数,处理空数据时返回的 JSON 响应
技术要点
- EasyExcel 导出:使用
@ExcelProperty注解定义 Excel 列名,支持自动列宽 - 空数据处理:当没有数据时返回 JSON 响应
Result.error(404, "暂无数据") - 响应类型判断:前端通过
content-type头区分 Excel 和 JSON 响应 - 中文文件名:使用
URLEncoder编码确保中文文件名正确下载 - 权限控制:通过
@RequireRole(UserRole.SCHOOL)确保只能导出当前租户数据
字段设计
授课记录导出
| Excel 列名 | 数据来源 |
|---|---|
| 授课日期 | lesson.lesson_date |
| 授课时间 | lesson.start_time ~ end_time |
| 班级名称 | clazz.name |
| 教师姓名 | teacher.name |
| 课程名称 | course_package.name |
| 学生人数 | COUNT(DISTINCT sr.student_id) |
| 平均参与度 | AVG(sr.participation) |
| 备注 | lesson.notes |
教师绩效导出
| Excel 列名 | 数据来源 |
|---|---|
| 教师姓名 | teacher.name |
| 所属班级 | GROUP_CONCAT(clazz.name) |
| 授课次数 | COUNT(lesson.id) |
| 课程数量 | COUNT(DISTINCT course_id) |
| 活跃等级 | CASE WHEN (HIGH/MEDIUM/LOW/INACTIVE) |
| 最后活跃时间 | MAX(end_datetime) |
| 平均参与度 | AVG(sr.participation) |
学生统计导出
| Excel 列名 | 数据来源 |
|---|---|
| 学生姓名 | student.name |
| 性别 | student.gender |
| 年级 | student.grade |
| 授课次数 | COUNT(DISTINCT lesson.id) |
| 平均参与度 | AVG(sr.participation) |
| 平均专注度 | AVG(sr.focus) |
问题修复
-
lesson_type 字段不存在
- 问题:
lesson表没有lesson_type字段 - 解决:改用
l.status和l.notes
- 问题:
-
student 表 class_id 字段不存在
- 问题:
student表没有class_id字段 - 解决:改用
s.grade作为className显示
- 问题:
测试结果
| 测试项 | 结果 |
|---|---|
| 编译通过 | ✅ |
| 授课记录导出 | ✅ HTTP 200 |
| 教师绩效导出 | ✅ HTTP 200 |
| 学生统计导出 | ✅ HTTP 200 |
| 空数据处理 | ✅ 返回 JSON |
文件变更列表
| 文件 | 变更说明 |
|---|---|
reading-platform-java/pom.xml |
添加 EasyExcel 依赖 |
reading-platform-java/src/main/java/com/reading/platform/dto/response/LessonExportVO.java |
新建 |
reading-platform-java/src/main/java/com/reading/platform/dto/response/TeacherPerformanceExportVO.java |
新建 |
reading-platform-java/src/main/java/com/reading/platform/dto/response/StudentStatExportVO.java |
新建 |
reading-platform-java/src/main/java/com/reading/platform/service/SchoolExportService.java |
新建 |
reading-platform-java/src/main/java/com/reading/platform/service/impl/SchoolExportServiceImpl.java |
新建 |
reading-platform-java/src/main/java/com/reading/platform/mapper/LessonMapper.java |
添加 3 个导出查询方法 |
reading-platform-java/src/main/java/com/reading/platform/controller/school/SchoolExportController.java |
实现导出接口 |
reading-platform-frontend/src/api/school.ts |
增强导出函数 |
测试验证
- 后端编译通过
- 启动后端服务(端口 8481)
- 授课记录导出接口测试通过
- 教师绩效导出接口测试通过
- 学生统计导出接口测试通过
- 空数据场景测试通过
- 前端服务运行正常(端口 5174)
今日完成: 学校端数据导出功能(3 个导出接口)
学校端 - 数据报告功能实现
需求背景
学校端"数据中心"菜单下的"数据报告"子菜单功能之前未实现。前端 ReportView.vue 页面已存在,但调用的 API 返回空数据。需要实现完整的数据报告功能,让学校管理员能够查看:
- 整体概览 - 教师总数、学生总数、班级总数、本月授课次数
- 教师报告 - 教师教学数据统计(授课次数、任务数、评分)
- 课程报告 - 课程使用排行(授课次数、学生数、完成率)
- 学生报告 - 学生学习数据(完成任务、成长记录、出勤率)
实现内容
1. 新建后端 Mapper
ReportMapper.java - 数据报告 Mapper
@Mapper
public interface ReportMapper extends BaseMapper<Lesson> {
// 4 个统计查询方法
Map<String, Object> getOverviewStats(...) // 概览统计
List<Map<String, Object>> getTeacherReports(...) // 教师报告
List<Map<String, Object>> getCourseReports(...) // 课程报告
List<Map<String, Object>> getStudentReports(...) // 学生报告
}
SQL 查询逻辑:
- 概览统计: 统计教师数、学生数、班级数、本月授课次数、本月任务完成数
- 教师报告: 按教师分组,统计授课次数、任务完成数、平均评分、最后活跃时间
- 课程报告: 按课程分组,统计授课次数、学生数、平均评分、完成率
- 学生报告: 按学生分组,统计任务完成数、成长记录数、出勤率
2. 新建后端 Service
SchoolReportService.java - 服务接口
public interface SchoolReportService {
ReportOverviewResponse getOverview(Long tenantId, LocalDate startDate, LocalDate endDate);
List<TeacherReportResponse> getTeacherReports(Long tenantId, LocalDate startDate, LocalDate endDate, int limit);
List<CourseReportResponse> getCourseReports(Long tenantId, LocalDate startDate, LocalDate endDate, int limit);
List<StudentReportResponse> getStudentReports(Long tenantId, LocalDate startDate, LocalDate endDate, int limit);
}
SchoolReportServiceImpl.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 - 添加日期范围参数
@GetMapping("/overview")
public Result<ReportOverviewResponse> 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 函数
// 更新接口定义
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<string, string> = {};
if (startDate) params.startDate = startDate;
if (endDate) params.endDate = endDate;
return http.get<ReportOverview>('/v1/school/reports/overview', { params });
};
// ... 其他 API
5. 前端页面完善
ReportView.vue - 更新数据绑定和显示
// 概览数据
const overviewData = ref<ReportOverview>({
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 |
功能特性
- 日期范围筛选: 支持选择日期范围,默认统计当月数据
- 实时刷新: 选择日期范围后自动刷新数据
- 详情弹窗: 点击教师/课程可查看详细信息
- 响应式设计: 支持不同屏幕尺寸
文件变更列表
| 文件 | 变更说明 |
|---|---|
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 |
更新,数据绑定和显示 |
测试验证
- 后端编译通过
- ReportView.vue 类型检查通过
- 启动后端服务(端口 8480)
- 启动前端服务(端口 5173)
- 登录学校管理员账号
- 访问数据报告页面
- 验证日期范围筛选
- 验证各 Tab 数据显示
今日完成: 学校端数据报告功能(4 个统计接口 + 前端页面)