kindergarten_java/docs/dev-logs/2026-03-21.md
En b361b1885b fix: 教师端首页今日课程 courseName 和 className 关联查询
问题:
- 今日课程功能只查询了 lesson 表,没有 JOIN 关联表
- TeacherLessonVO 的 courseName 和 className 字段为 null
- 前端无法显示课程名称和班级名称

修复:
- LessonMapper 新增 selectTodayLessonsWithDetails() 方法
- 通过 LEFT JOIN course_package 和 clazz 表获取名称
- TeacherStatsServiceImpl 重写 getTodayLessons() 方法
- 添加类型转换辅助方法 (getLong/getString/getLocalDate/getLocalTime/getLocalDateTime)

影响范围:
- 教师端首页 - 今日课程模块
- API: GET /api/v1/teacher/today-lessons
- API: GET /api/v1/teacher/dashboard

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 18:43:47 +08:00

11 KiB
Raw Blame History

开发日志 2026-03-21

学校端 - 课程使用统计功能实现

需求背景

学校端 Dashboard 页面已有"课程使用统计"卡片组件,但后端返回空数据。需要实现该功能,让学校管理员能够查看本校各课程包的使用情况。

实现内容

1. 后端修改

Controller 层 (SchoolStatsController.java)

  • 添加日期范围参数支持,允许前端传入 startDateendDate
  • 不传参数时默认统计本月数据
@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 页面,查看"课程使用统计"卡片
  • 验证日期范围筛选功能
  • 验证数据显示正确性

后续优化建议

  1. 增加更多统计维度:按班级、按教师统计
  2. 可视化增强:趋势图、热力图
  3. 导出功能Excel 导出课程使用明细
  4. 性能优化:大数据量时考虑缓存

学校端 - 数据导出功能实现

需求背景

学校端数据概览页面DashboardView.vue已有数据导出的 UI 界面和前端调用逻辑,但后端 SchoolExportController.java 中的 4 个接口只返回占位数据,没有实际的 Excel 导出功能。需要实现学校端数据概览的导出功能,包括:

  1. 授课记录导出 - 导出指定时间范围内的授课记录
  2. 教师绩效导出 - 导出教师绩效统计数据
  3. 学生统计导出 - 导出学生统计数据

实现内容

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 - 授课记录导出 VO9 个字段)
  • dto/response/TeacherPerformanceExportVO.java - 教师绩效导出 VO7 个字段)
  • dto/response/StudentStatExportVO.java - 学生统计导出 VO6 个字段)

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 响应

技术要点

  1. EasyExcel 导出:使用 @ExcelProperty 注解定义 Excel 列名,支持自动列宽
  2. 空数据处理:当没有数据时返回 JSON 响应 Result.error(404, "暂无数据")
  3. 响应类型判断:前端通过 content-type 头区分 Excel 和 JSON 响应
  4. 中文文件名:使用 URLEncoder 编码确保中文文件名正确下载
  5. 权限控制:通过 @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)

问题修复

  1. lesson_type 字段不存在

    • 问题:lesson 表没有 lesson_type 字段
    • 解决:改用 l.statusl.notes
  2. 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 个导出接口)