fix: 课后记录页面前后端对齐 - 修改 getStudentRecords 返回 { lesson, students } 结构 - 修改 batchSaveStudentRecords 接受 { records } 请求体 - 新增 StudentRecordsResponse/StudentWithRecordResponse/BatchStudentRecordsRequest - 前端增强数据容错

Made-with: Cursor
This commit is contained in:
zhonghua 2026-03-20 16:11:08 +08:00
parent 463c3d9922
commit c93d325cee
9 changed files with 184 additions and 19 deletions

View File

@ -19,7 +19,7 @@
</div>
<a-spin :spinning="loading">
<div class="records-content" v-if="students.length > 0">
<div class="records-content" v-if="(students ?? []).length > 0">
<!-- 提示信息 -->
<div class="tip-banner">
<BulbOutlined />
@ -292,24 +292,26 @@ const loadRecords = async () => {
loading.value = true;
try {
const data = await getStudentRecords(lessonId.value);
lessonInfo.value = data.lesson;
students.value = data.students;
// { lesson, students }
const studentList = Array.isArray(data?.students) ? data.students : [];
lessonInfo.value = data?.lesson ?? null;
students.value = studentList;
//
for (const student of data.students) {
for (const student of studentList) {
records[student.id] = {
focus: student.record?.focus || 0,
participation: student.record?.participation || 0,
interest: student.record?.interest || 0,
understanding: student.record?.understanding || 0,
notes: student.record?.notes || '',
focus: student.record?.focus ?? 0,
participation: student.record?.participation ?? 0,
interest: student.record?.interest ?? 0,
understanding: student.record?.understanding ?? 0,
notes: student.record?.notes ?? '',
};
}
//
await loadLessonDetail();
} catch (error: any) {
message.error(error.message || '获取学生记录失败');
message.error(error?.message || '获取学生记录失败');
} finally {
loading.value = false;
}

View File

@ -10,11 +10,13 @@ import com.reading.platform.common.security.SecurityUtils;
import com.reading.platform.dto.request.LessonCreateRequest;
import com.reading.platform.dto.request.LessonProgressRequest;
import com.reading.platform.dto.request.LessonUpdateRequest;
import com.reading.platform.dto.request.BatchStudentRecordsRequest;
import com.reading.platform.dto.request.StudentRecordRequest;
import com.reading.platform.dto.response.ClassResponse;
import com.reading.platform.dto.response.CourseResponse;
import com.reading.platform.dto.response.LessonDetailResponse;
import com.reading.platform.dto.response.LessonResponse;
import com.reading.platform.dto.response.StudentRecordsResponse;
import com.reading.platform.dto.response.StudentRecordResponse;
import com.reading.platform.entity.Clazz;
import com.reading.platform.entity.CoursePackage;
@ -156,10 +158,9 @@ public class TeacherLessonController {
@Operation(summary = "获取学生记录")
@GetMapping("/{id}/students/records")
public Result<List<StudentRecordResponse>> getStudentRecords(@PathVariable Long id) {
List<StudentRecord> records = lessonService.getStudentRecords(id);
List<StudentRecordResponse> voList = studentRecordMapper.toVO(records);
return Result.success(voList);
public Result<StudentRecordsResponse> getStudentRecords(@PathVariable Long id) {
StudentRecordsResponse response = lessonService.getStudentRecords(id);
return Result.success(response);
}
@Operation(summary = "保存学生记录")
@ -176,7 +177,8 @@ public class TeacherLessonController {
@PostMapping("/{id}/students/batch-records")
public Result<List<StudentRecordResponse>> batchSaveStudentRecords(
@PathVariable Long id,
@RequestBody List<StudentRecordRequest> requests) {
@RequestBody BatchStudentRecordsRequest request) {
List<StudentRecordRequest> requests = request.getRecords() != null ? request.getRecords() : List.of();
List<StudentRecord> records = lessonService.batchSaveStudentRecords(id, requests);
List<StudentRecordResponse> voList = studentRecordMapper.toVO(records);
return Result.success(voList);

View File

@ -0,0 +1,19 @@
package com.reading.platform.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import lombok.Data;
import java.util.List;
/**
* 批量保存学生记录请求与前端 { records } 结构对齐
*/
@Data
@Schema(description = "批量保存学生记录请求")
public class BatchStudentRecordsRequest {
@Valid
@Schema(description = "记录列表", required = true)
private List<StudentRecordRequest> records;
}

View File

@ -0,0 +1,34 @@
package com.reading.platform.dto.response;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
import java.util.List;
/**
* 课后记录列表响应lesson + students 结构与前端对齐
*/
@Data
@Builder
@Schema(description = "课后记录列表响应")
public class StudentRecordsResponse {
@Schema(description = "课时信息")
private LessonInfo lesson;
@Schema(description = "学生列表(含记录)")
private List<StudentWithRecordResponse> students;
@Data
@Builder
@Schema(description = "课时简要信息")
public static class LessonInfo {
@Schema(description = "课时 ID")
private Long id;
@Schema(description = "状态")
private String status;
@Schema(description = "班级名称")
private String className;
}
}

View File

@ -0,0 +1,26 @@
package com.reading.platform.dto.response;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
/**
* 学生及其课后记录用于课后记录页面
*/
@Data
@Builder
@Schema(description = "学生及其课后记录")
public class StudentWithRecordResponse {
@Schema(description = "学生 ID")
private Long id;
@Schema(description = "学生姓名")
private String name;
@Schema(description = "性别")
private String gender;
@Schema(description = "课后记录,未记录时为 null")
private StudentRecordResponse record;
}

View File

@ -5,6 +5,7 @@ import com.reading.platform.dto.request.LessonCreateRequest;
import com.reading.platform.dto.request.LessonUpdateRequest;
import com.reading.platform.dto.request.LessonProgressRequest;
import com.reading.platform.dto.request.StudentRecordRequest;
import com.reading.platform.dto.response.StudentRecordsResponse;
import com.reading.platform.entity.Lesson;
import com.reading.platform.entity.LessonFeedback;
import com.reading.platform.entity.StudentRecord;
@ -38,9 +39,9 @@ public interface LessonService extends com.baomidou.mybatisplus.extension.servic
List<Lesson> getTodayLessons(Long tenantId);
/**
* 获取学生记录列表
* 获取学生记录列表含课时信息班级学生及记录与前端结构对齐
*/
List<StudentRecord> getStudentRecords(Long lessonId);
StudentRecordsResponse getStudentRecords(Long lessonId);
/**
* 保存学生记录

View File

@ -47,6 +47,11 @@ public interface StudentService extends com.baomidou.mybatisplus.extension.servi
*/
Page<Student> getStudentsByClassId(Long classId, Integer pageNum, Integer pageSize);
/**
* 根据班级 ID 查询学生列表不分页用于课后记录等场景
*/
List<Student> getStudentListByClassId(Long classId);
/**
* 根据班级 ID 列表查询学生
*/

View File

@ -10,17 +10,23 @@ import com.reading.platform.dto.request.LessonCreateRequest;
import com.reading.platform.dto.request.LessonUpdateRequest;
import com.reading.platform.dto.request.LessonProgressRequest;
import com.reading.platform.dto.request.StudentRecordRequest;
import com.reading.platform.dto.response.StudentRecordsResponse;
import com.reading.platform.dto.response.StudentWithRecordResponse;
import com.reading.platform.common.enums.LessonStatus;
import com.reading.platform.entity.Clazz;
import com.reading.platform.entity.Lesson;
import com.reading.platform.entity.Student;
import com.reading.platform.entity.LessonFeedback;
import com.reading.platform.entity.SchedulePlan;
import com.reading.platform.entity.StudentRecord;
import com.reading.platform.mapper.ClazzMapper;
import com.reading.platform.mapper.LessonFeedbackMapper;
import com.reading.platform.mapper.CoursePackageMapper;
import com.reading.platform.mapper.LessonMapper;
import com.reading.platform.mapper.SchedulePlanMapper;
import com.reading.platform.mapper.StudentRecordMapper;
import com.reading.platform.service.LessonService;
import com.reading.platform.service.StudentService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -32,6 +38,8 @@ import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
@Service
@ -44,6 +52,9 @@ public class LessonServiceImpl extends ServiceImpl<LessonMapper, Lesson>
private final LessonFeedbackMapper lessonFeedbackMapper;
private final SchedulePlanMapper schedulePlanMapper;
private final CoursePackageMapper coursePackageMapper;
private final ClazzMapper clazzMapper;
private final StudentService studentService;
private final com.reading.platform.common.mapper.StudentRecordMapper studentRecordVoMapper;
@Override
@Transactional
@ -222,13 +233,48 @@ public class LessonServiceImpl extends ServiceImpl<LessonMapper, Lesson>
}
@Override
public List<StudentRecord> getStudentRecords(Long lessonId) {
public StudentRecordsResponse getStudentRecords(Long lessonId) {
log.debug("获取学生记录列表,课程 ID: {}", lessonId);
return studentRecordMapper.selectList(
Lesson lesson = lessonMapper.selectById(lessonId);
if (lesson == null) {
throw new BusinessException(ErrorCode.NOT_FOUND, "授课记录不存在");
}
String className = "";
if (lesson.getClassId() != null) {
Clazz clazz = clazzMapper.selectById(lesson.getClassId());
className = clazz != null ? clazz.getName() : "";
}
List<Student> students = lesson.getClassId() != null
? studentService.getStudentListByClassId(lesson.getClassId())
: List.of();
List<StudentRecord> records = studentRecordMapper.selectList(
new LambdaQueryWrapper<StudentRecord>()
.eq(StudentRecord::getLessonId, lessonId)
);
Map<Long, com.reading.platform.dto.response.StudentRecordResponse> recordMap = records.stream()
.collect(Collectors.toMap(StudentRecord::getStudentId, studentRecordVoMapper::toVO, (a, b) -> a));
List<StudentWithRecordResponse> studentWithRecords = students.stream()
.map(s -> StudentWithRecordResponse.builder()
.id(s.getId())
.name(s.getName())
.gender(s.getGender())
.record(recordMap.get(s.getId()))
.build())
.toList();
return StudentRecordsResponse.builder()
.lesson(StudentRecordsResponse.LessonInfo.builder()
.id(lesson.getId())
.status(lesson.getStatus())
.className(className)
.build())
.students(studentWithRecords)
.build();
}
@Override

View File

@ -197,6 +197,36 @@ public class StudentServiceImpl extends com.baomidou.mybatisplus.extension.servi
return studentMapper.selectPage(page, wrapper);
}
@Override
public List<Student> getStudentListByClassId(Long classId) {
log.debug("根据班级查询学生列表,班级 ID: {}", classId);
List<StudentClassHistory> histories = studentClassHistoryMapper.selectList(
new LambdaQueryWrapper<StudentClassHistory>()
.eq(StudentClassHistory::getClassId, classId)
.eq(StudentClassHistory::getStatus, "active")
.isNull(StudentClassHistory::getEndDate)
.or()
.ge(StudentClassHistory::getEndDate, LocalDate.now())
);
if (histories.isEmpty()) {
return List.of();
}
List<Long> studentIds = histories.stream()
.map(StudentClassHistory::getStudentId)
.distinct()
.toList();
return studentMapper.selectList(
new LambdaQueryWrapper<Student>()
.in(Student::getId, studentIds)
.eq(Student::getStatus, "active")
.orderByAsc(Student::getName)
);
}
@Override
@Transactional
public void deleteStudent(Long id) {