kindergarten_java/docs/dev-logs/2026-03-21.md
En c1f5b5085e feat: 学校端数据报告功能实现
主要变更:
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>
2026-03-23 09:46:08 +08:00

18 KiB
Raw Permalink 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 个导出接口)


学校端 - 数据报告功能实现

需求背景

学校端"数据中心"菜单下的"数据报告"子菜单功能之前未实现。前端 ReportView.vue 页面已存在,但调用的 API 返回空数据。需要实现完整的数据报告功能,让学校管理员能够查看:

  1. 整体概览 - 教师总数、学生总数、班级总数、本月授课次数
  2. 教师报告 - 教师教学数据统计(授课次数、任务数、评分)
  3. 课程报告 - 课程使用排行(授课次数、学生数、完成率)
  4. 学生报告 - 学生学习数据(完成任务、成长记录、出勤率)

实现内容

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

功能特性

  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 更新,数据绑定和显示

测试验证

  • 后端编译通过
  • ReportView.vue 类型检查通过
  • 启动后端服务(端口 8480
  • 启动前端服务(端口 5173
  • 登录学校管理员账号
  • 访问数据报告页面
  • 验证日期范围筛选
  • 验证各 Tab 数据显示

今日完成: 学校端数据报告功能4 个统计接口 + 前端页面)