feat(exam): 实现考试阶段单词判卷与学生水平智能诊断
- 新增ActionType枚举定义系统动作类型 - 新增DiagnosisResult和ZoneStats数据模型支持诊断结果及区域统计 - 优化ExamWordsJudgeServiceImpl判卷逻辑,支持识别图片、更新考试判卷结果 - 基于分区词汇掌握情况,实现学生当前水平年级的智能判定 - 实现基于多分区准确率的升级、降级、复习和触发重测等动作建议 - 更新学生实际年级actualGradeId并展示在学生详情页面 - 修正ExamWordsConstant年级常量及年级名称映射方法 - 优化前端生成试题对年级和难度的校验逻辑,简化参数传递 - 修改服务端端口及API代理配置,保持一致性 - 调整相关数据库Mapper,支持批量查询和更新实际年级字段 - 修改错误信息字段命名,统一为msg - 增删改代码注释与日志,提升容错性和可读性
This commit is contained in:
@@ -9,9 +9,19 @@ public class ExamWordsConstant {
|
|||||||
public static final int GRADE_3 = 3;
|
public static final int GRADE_3 = 3;
|
||||||
public static final int GRADE_4 = 4;
|
public static final int GRADE_4 = 4;
|
||||||
public static final int GRADE_5 = 5;
|
public static final int GRADE_5 = 5;
|
||||||
public static final int GRADE_6 = 7;
|
public static final int GRADE_6 = 6;
|
||||||
public static final int GRADE_7 = 8;
|
public static final int GRADE_7 = 7;
|
||||||
public static final int GRADE_8 = 9;
|
public static final int GRADE_8 = 8;
|
||||||
|
|
||||||
|
public static final String GRADE_1_NAME = "一年级";
|
||||||
|
public static final String GRADE_2_NAME = "二年级";
|
||||||
|
public static final String GRADE_3_NAME = "三年级";
|
||||||
|
public static final String GRADE_4_NAME = "四年级";
|
||||||
|
public static final String GRADE_5_NAME = "五年级";
|
||||||
|
public static final String GRADE_6_NAME = "六年级";
|
||||||
|
public static final String GRADE_7_NAME = "初一";
|
||||||
|
public static final String GRADE_8_NAME = "初二";
|
||||||
|
|
||||||
|
|
||||||
public static final int ZONE_A_SIZE = 10;
|
public static final int ZONE_A_SIZE = 10;
|
||||||
public static final int ZONE_B_SIZE = 20;
|
public static final int ZONE_B_SIZE = 20;
|
||||||
@@ -105,4 +115,18 @@ public class ExamWordsConstant {
|
|||||||
default -> 0;
|
default -> 0;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getGradeName(int gradeId) {
|
||||||
|
return switch (gradeId) {
|
||||||
|
case GRADE_1 -> GRADE_1_NAME;
|
||||||
|
case GRADE_2 -> GRADE_2_NAME;
|
||||||
|
case GRADE_3 -> GRADE_3_NAME;
|
||||||
|
case GRADE_4 -> GRADE_4_NAME;
|
||||||
|
case GRADE_5 -> GRADE_5_NAME;
|
||||||
|
case GRADE_6 -> GRADE_6_NAME;
|
||||||
|
case GRADE_7 -> GRADE_7_NAME;
|
||||||
|
case GRADE_8 -> GRADE_8_NAME;
|
||||||
|
default -> "";
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ public class ExamWordsController {
|
|||||||
.correctWordCount(examWordsJudgeResultDO.getCorrectWordCount())
|
.correctWordCount(examWordsJudgeResultDO.getCorrectWordCount())
|
||||||
.wrongWordCount(examWordsJudgeResultDO.getWrongWordCount())
|
.wrongWordCount(examWordsJudgeResultDO.getWrongWordCount())
|
||||||
.isFinished(examWordsJudgeResultDO.getIsFinished())
|
.isFinished(examWordsJudgeResultDO.getIsFinished())
|
||||||
.errorMsg(examWordsJudgeResultDO.getMsg())
|
.msg(examWordsJudgeResultDO.getMsg())
|
||||||
.build()
|
.build()
|
||||||
).toList();
|
).toList();
|
||||||
return PageResponse.success(list, page, total, size);
|
return PageResponse.success(list, page, total, size);
|
||||||
|
|||||||
@@ -60,12 +60,13 @@ public class StudentController {
|
|||||||
StudentDO studentById = studentService.getStudentById(studentId);
|
StudentDO studentById = studentService.getStudentById(studentId);
|
||||||
ClassDO classById = classService.findClassById(studentById.getClassId());
|
ClassDO classById = classService.findClassById(studentById.getClassId());
|
||||||
GradeDO byClassId = gradeService.findByClassId(studentById.getGradeId());
|
GradeDO byClassId = gradeService.findByClassId(studentById.getGradeId());
|
||||||
|
GradeDO actualGradeById = gradeService.findByClassId(studentById.getActualGradeId());
|
||||||
FindStudentDetailRspVO findStudentDetailRspVO = FindStudentDetailRspVO.builder()
|
FindStudentDetailRspVO findStudentDetailRspVO = FindStudentDetailRspVO.builder()
|
||||||
.id(studentById.getId())
|
.id(studentById.getId())
|
||||||
.name(studentById.getName())
|
.name(studentById.getName())
|
||||||
.className(classById.getTitle())
|
.className(classById.getTitle())
|
||||||
.gradeName(byClassId.getTitle())
|
.gradeName(byClassId.getTitle())
|
||||||
|
.actualGrade(actualGradeById != null ? actualGradeById.getTitle() : "")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
return Response.success(findStudentDetailRspVO);
|
return Response.success(findStudentDetailRspVO);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import java.util.List;
|
|||||||
@Data
|
@Data
|
||||||
@Builder
|
@Builder
|
||||||
public class ExamWordsJudgeResultDO {
|
public class ExamWordsJudgeResultDO {
|
||||||
|
|
||||||
private Integer id;
|
private Integer id;
|
||||||
|
|
||||||
private String ansSheetPath;
|
private String ansSheetPath;
|
||||||
@@ -27,8 +28,6 @@ public class ExamWordsJudgeResultDO {
|
|||||||
|
|
||||||
private Integer isFinished;
|
private Integer isFinished;
|
||||||
|
|
||||||
private Integer ansGradeId;
|
|
||||||
|
|
||||||
private LocalDateTime startDate;
|
private LocalDateTime startDate;
|
||||||
|
|
||||||
private List<Integer> correctWordIds;
|
private List<Integer> correctWordIds;
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ public class StudentDO {
|
|||||||
|
|
||||||
private Integer gradeId;
|
private Integer gradeId;
|
||||||
|
|
||||||
|
private Integer actualGradeId;
|
||||||
|
|
||||||
private Integer isDeleted;
|
private Integer isDeleted;
|
||||||
|
|
||||||
private LocalDateTime startTime;
|
private LocalDateTime startTime;
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ public interface GradeUnitDOMapper {
|
|||||||
|
|
||||||
GradeUnitDO selectByUnitId(@Param("unitId") Integer unitId);
|
GradeUnitDO selectByUnitId(@Param("unitId") Integer unitId);
|
||||||
|
|
||||||
|
List<GradeUnitDO> selectByUnitIds(@Param("unitIds") List<Integer> unitIds);
|
||||||
|
|
||||||
int insert(GradeUnitDO record);
|
int insert(GradeUnitDO record);
|
||||||
|
|
||||||
int deleteByUnitId(@Param("unitId") Integer unitId);
|
int deleteByUnitId(@Param("unitId") Integer unitId);
|
||||||
|
|||||||
@@ -21,4 +21,6 @@ public interface StudentDOMapper {
|
|||||||
void deleteById(Integer id);
|
void deleteById(Integer id);
|
||||||
|
|
||||||
int selectStudentCountByClassId(@Param("classId") Integer classId);
|
int selectStudentCountByClassId(@Param("classId") Integer classId);
|
||||||
|
|
||||||
|
int updateStudentActualGradeId(@Param("studentId") Integer studentId, @Param("gradeId") Integer gradeId);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.bo.exam;
|
||||||
|
|
||||||
|
public enum ActionType {
|
||||||
|
PASS, // 保持当前进度
|
||||||
|
UPGRADE, // 升级
|
||||||
|
DOWNGRADE, // 降级回填
|
||||||
|
STAY_AND_REVIEW, // 保持年级但进入复习模式
|
||||||
|
TRIGGER_RETEST // 触发熔断二测
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.bo.exam;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class DiagnosisResult {
|
||||||
|
|
||||||
|
int determinedLevel; // 系统判定的真实等级
|
||||||
|
ActionType actionType; // 建议的系统动作
|
||||||
|
String message; // 展示给用户的文案
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.bo.exam;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class ZoneStats {
|
||||||
|
|
||||||
|
private int gradeId;
|
||||||
|
private int totalCount;
|
||||||
|
private int correctCount;
|
||||||
|
private double accuracy;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -28,5 +28,5 @@ public class ExamWordsResultRspVO {
|
|||||||
|
|
||||||
private LocalDateTime startDate;
|
private LocalDateTime startDate;
|
||||||
|
|
||||||
private String errorMsg;
|
private String msg;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,4 +15,5 @@ public class FindStudentDetailRspVO {
|
|||||||
private String name;
|
private String name;
|
||||||
private String className;
|
private String className;
|
||||||
private String gradeName;
|
private String gradeName;
|
||||||
|
private String actualGrade;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public class ExamWordsServiceImpl implements ExamWordsService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = RuntimeException.class)
|
@Transactional(rollbackFor = RuntimeException.class)
|
||||||
public ExamWordsDO generateExamWords(Integer gradeId, Integer level, Integer studentId, Integer type) {
|
public ExamWordsDO generateExamWords(Integer gradeId, Integer level, Integer studentId, Integer type) {
|
||||||
|
|
||||||
|
|
||||||
ExamWordsDO examWordsDO;
|
ExamWordsDO examWordsDO;
|
||||||
@@ -64,7 +64,8 @@ public class ExamWordsServiceImpl implements ExamWordsService {
|
|||||||
|
|
||||||
private ExamWordsDO generateBaselineExamWords(Integer studentId) {
|
private ExamWordsDO generateBaselineExamWords(Integer studentId) {
|
||||||
StudentDO studentDO = studentDOMapper.selectStudentById(studentId);
|
StudentDO studentDO = studentDOMapper.selectStudentById(studentId);
|
||||||
Integer gradeId = studentDO.getGradeId();
|
|
||||||
|
Integer gradeId = studentDO.getActualGradeId();
|
||||||
|
|
||||||
int zoneA = ExamWordsConstant.getZoneA(gradeId);
|
int zoneA = ExamWordsConstant.getZoneA(gradeId);
|
||||||
int zoneASize = ExamWordsConstant.ZONE_A_SIZE;
|
int zoneASize = ExamWordsConstant.ZONE_A_SIZE;
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
package com.yinlihupo.enlish.service.service.judge;
|
package com.yinlihupo.enlish.service.service.judge;
|
||||||
|
|
||||||
import com.yinlihupo.enlish.service.domain.dataobject.ExamWordsDO;
|
import com.yinlihupo.enlish.service.constant.ExamWordsConstant;
|
||||||
import com.yinlihupo.enlish.service.domain.dataobject.ExamWordsJudgeResultDO;
|
import com.yinlihupo.enlish.service.domain.dataobject.*;
|
||||||
import com.yinlihupo.enlish.service.domain.dataobject.StudentExamWordsDO;
|
import com.yinlihupo.enlish.service.domain.mapper.*;
|
||||||
import com.yinlihupo.enlish.service.domain.dataobject.WordMasteryLogDO;
|
|
||||||
import com.yinlihupo.enlish.service.domain.mapper.ExamWordsDOMapper;
|
|
||||||
import com.yinlihupo.enlish.service.domain.mapper.ExamWordsJudgeResultDOMapper;
|
|
||||||
import com.yinlihupo.enlish.service.domain.mapper.StudentExamWordsDOMapper;
|
|
||||||
import com.yinlihupo.enlish.service.domain.mapper.WordMasteryLogDOMapper;
|
|
||||||
import com.yinlihupo.enlish.service.model.bo.CoordinatesXY;
|
import com.yinlihupo.enlish.service.model.bo.CoordinatesXY;
|
||||||
import com.yinlihupo.enlish.service.model.bo.StudentExamId;
|
import com.yinlihupo.enlish.service.model.bo.StudentExamId;
|
||||||
|
import com.yinlihupo.enlish.service.model.bo.exam.ActionType;
|
||||||
|
import com.yinlihupo.enlish.service.model.bo.exam.DiagnosisResult;
|
||||||
|
import com.yinlihupo.enlish.service.model.bo.exam.ZoneStats;
|
||||||
import com.yinlihupo.enlish.service.service.ExamWordsJudgeService;
|
import com.yinlihupo.enlish.service.service.ExamWordsJudgeService;
|
||||||
import com.yinlihupo.enlish.service.utils.PngUtil;
|
import com.yinlihupo.enlish.service.utils.PngUtil;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
@@ -20,8 +18,8 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.List;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -36,6 +34,12 @@ public class ExamWordsJudgeServiceImpl implements ExamWordsJudgeService {
|
|||||||
private ExamWordsDOMapper examWordsDOMapper;
|
private ExamWordsDOMapper examWordsDOMapper;
|
||||||
@Resource
|
@Resource
|
||||||
private WordMasteryLogDOMapper wordMasteryLogDOMapper;
|
private WordMasteryLogDOMapper wordMasteryLogDOMapper;
|
||||||
|
@Resource
|
||||||
|
private VocabularyBankDOMapper vocabularyBankDOMapper;
|
||||||
|
@Resource
|
||||||
|
private GradeUnitDOMapper gradeUnitDOMapper;
|
||||||
|
@Resource
|
||||||
|
private StudentDOMapper studentDOMapper;
|
||||||
|
|
||||||
@Value("${templates.data}")
|
@Value("${templates.data}")
|
||||||
private String tessdataPath;
|
private String tessdataPath;
|
||||||
@@ -45,91 +49,266 @@ public class ExamWordsJudgeServiceImpl implements ExamWordsJudgeService {
|
|||||||
public void judgeExamWords(int count) {
|
public void judgeExamWords(int count) {
|
||||||
List<ExamWordsJudgeResultDO> examWordsJudgeResultDOS = examWordsJudgeResultDOMapper.selectUnfinishedExamWordsJudgeResultDOList(count);
|
List<ExamWordsJudgeResultDO> examWordsJudgeResultDOS = examWordsJudgeResultDOMapper.selectUnfinishedExamWordsJudgeResultDOList(count);
|
||||||
for (ExamWordsJudgeResultDO examWordsJudgeResultDO : examWordsJudgeResultDOS) {
|
for (ExamWordsJudgeResultDO examWordsJudgeResultDO : examWordsJudgeResultDOS) {
|
||||||
String ansSheetPath = examWordsJudgeResultDO.getAnsSheetPath();
|
try {
|
||||||
List<CoordinatesXY> coordinatesXIES = PngUtil.analysisXY(ansSheetPath);
|
String ansSheetPath = examWordsJudgeResultDO.getAnsSheetPath();
|
||||||
// 从图片中获取学生 id 和考试 id
|
List<CoordinatesXY> coordinatesXIES = PngUtil.analysisXY(ansSheetPath);
|
||||||
StudentExamId studentExamId = PngUtil.analyzeExamWordsIdAndStudentId(ansSheetPath, tessdataPath, coordinatesXIES);
|
// 从图片中获取学生 id 和考试 id
|
||||||
Integer examWordsJudgeResultDOId = examWordsJudgeResultDO.getId();
|
StudentExamId studentExamId = PngUtil.analyzeExamWordsIdAndStudentId(ansSheetPath, tessdataPath, coordinatesXIES);
|
||||||
if (studentExamId == null) {
|
Integer examWordsJudgeResultDOId = examWordsJudgeResultDO.getId();
|
||||||
examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDOId, "未识别学生 id 和考试 id");
|
if (studentExamId == null) {
|
||||||
continue;
|
examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDOId, "未识别学生 id 和考试 id");
|
||||||
}
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
Integer studentId = studentExamId.getStudentId();
|
Integer studentId = studentExamId.getStudentId();
|
||||||
Integer examWordsId = studentExamId.getExamId();
|
Integer examWordsId = studentExamId.getExamId();
|
||||||
StudentExamWordsDO studentExamWordsDO = studentExamWordsDOMapper.selectByStudentIdAndExamWordsId(studentId, examWordsId);
|
StudentExamWordsDO studentExamWordsDO = studentExamWordsDOMapper.selectByStudentIdAndExamWordsId(studentId, examWordsId);
|
||||||
if (studentExamWordsDO == null) {
|
if (studentExamWordsDO == null) {
|
||||||
examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDOId, "未找到学生 id 和考试 id 对应的考试记录");
|
examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDOId, "未找到学生 id 和考试 id 对应的考试记录");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
log.info("studentId:{},examWordsId:{}", studentId, examWordsId);
|
log.info("studentId:{},examWordsId:{}", studentId, examWordsId);
|
||||||
if (studentExamWordsDO.getIsCompleted() == 1) {
|
if (studentExamWordsDO.getIsCompleted() == 1) {
|
||||||
examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDOId, "考试记录此前已识别");
|
examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDOId, "考试记录此前已识别");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ExamWordsDO examWordsDO = examWordsDOMapper.selectById(examWordsId);
|
ExamWordsDO examWordsDO = examWordsDOMapper.selectById(examWordsId);
|
||||||
if(examWordsDO == null) {
|
if(examWordsDO == null) {
|
||||||
examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDOId, "未找到考试");
|
examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDOId, "未找到考试");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Integer> wordIds = examWordsDO.getWordIds();
|
List<Integer> wordIds = examWordsDO.getWordIds();
|
||||||
List<Integer> unmemorizedWordIds = PngUtil.analyzePngForUnmemorizedWordIds(ansSheetPath, wordIds, coordinatesXIES);
|
List<Integer> unmemorizedWordIds = PngUtil.analyzePngForUnmemorizedWordIds(ansSheetPath, wordIds, coordinatesXIES);
|
||||||
List<Integer> memorizedWordIds = wordIds.stream().filter(wordId -> !unmemorizedWordIds.contains(wordId)).toList();
|
List<Integer> memorizedWordIds = wordIds.stream().filter(wordId -> !unmemorizedWordIds.contains(wordId)).toList();
|
||||||
|
|
||||||
ExamWordsJudgeResultDO wordsJudgeResultDO = ExamWordsJudgeResultDO.builder()
|
ExamWordsJudgeResultDO wordsJudgeResultDO = ExamWordsJudgeResultDO.builder()
|
||||||
.id(examWordsJudgeResultDOId)
|
.id(examWordsJudgeResultDOId)
|
||||||
.studentId(studentId)
|
.studentId(studentId)
|
||||||
.examWordsId(examWordsId)
|
.examWordsId(examWordsId)
|
||||||
.correctWordIds(memorizedWordIds)
|
.correctWordIds(memorizedWordIds)
|
||||||
.wrongWordIds(unmemorizedWordIds)
|
.wrongWordIds(unmemorizedWordIds)
|
||||||
.correctWordCount(memorizedWordIds.size())
|
.correctWordCount(memorizedWordIds.size())
|
||||||
.wrongWordCount(unmemorizedWordIds.size())
|
.wrongWordCount(unmemorizedWordIds.size())
|
||||||
.isFinished(1)
|
.isFinished(1)
|
||||||
.build();
|
.build();
|
||||||
int updated = examWordsJudgeResultDOMapper.updateExamWordsJudgeResultDO(wordsJudgeResultDO);
|
// 判断考试等级
|
||||||
if (updated != 1) {
|
judgeExamActualGrade(wordsJudgeResultDO, examWordsDO);
|
||||||
examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDOId, "更新考试记录失败");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
log.info("更新考试记录成功");
|
|
||||||
|
|
||||||
List<WordMasteryLogDO> wordMasteryLogDOS = new ArrayList<>(unmemorizedWordIds.stream().map(wordId -> WordMasteryLogDO.builder()
|
int updated = examWordsJudgeResultDOMapper.updateExamWordsJudgeResultDO(wordsJudgeResultDO);
|
||||||
.wordId(wordId)
|
if (updated != 1) {
|
||||||
.studentId(studentId)
|
examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDOId, "更新考试记录失败");
|
||||||
.reviewCount(1)
|
continue;
|
||||||
.memoryStrength(-0.5)
|
}
|
||||||
.update_time(LocalDateTime.now())
|
log.info("更新考试记录成功");
|
||||||
.build()
|
|
||||||
).toList());
|
|
||||||
|
|
||||||
wordMasteryLogDOS.addAll(memorizedWordIds.stream().map(wordId -> WordMasteryLogDO.builder()
|
List<WordMasteryLogDO> wordMasteryLogDOS = new ArrayList<>(unmemorizedWordIds.stream().map(wordId -> WordMasteryLogDO.builder()
|
||||||
.wordId(wordId)
|
.wordId(wordId)
|
||||||
.studentId(studentId)
|
.studentId(studentId)
|
||||||
.reviewCount(1)
|
.reviewCount(1)
|
||||||
.memoryStrength(0.5)
|
.memoryStrength(-0.5)
|
||||||
.update_time(LocalDateTime.now())
|
.update_time(LocalDateTime.now())
|
||||||
.build()
|
.build()
|
||||||
).toList());
|
).toList());
|
||||||
int batched = wordMasteryLogDOMapper.batchUpdateStudentMastery(wordMasteryLogDOS);
|
|
||||||
if (batched == 0) {
|
|
||||||
examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDOId, "更新学生记忆力记录失败");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int updateStudentExamWordsFinished = studentExamWordsDOMapper.updateStudentExamWordsFinished(studentId, examWordsId);
|
wordMasteryLogDOS.addAll(memorizedWordIds.stream().map(wordId -> WordMasteryLogDO.builder()
|
||||||
if (updateStudentExamWordsFinished != 1) {
|
.wordId(wordId)
|
||||||
examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDOId, "更新学生考试为结束时失败");
|
.studentId(studentId)
|
||||||
}
|
.reviewCount(1)
|
||||||
|
.memoryStrength(0.5)
|
||||||
|
.update_time(LocalDateTime.now())
|
||||||
|
.build()
|
||||||
|
).toList());
|
||||||
|
wordMasteryLogDOMapper.batchUpdateStudentMastery(wordMasteryLogDOS);
|
||||||
|
log.info("更新单词掌握记录成功");
|
||||||
|
|
||||||
boolean delete = new File(ansSheetPath).delete();
|
int updateStudentExamWordsFinished = studentExamWordsDOMapper.updateStudentExamWordsFinished(studentId, examWordsId);
|
||||||
if (delete) {
|
if (updateStudentExamWordsFinished != 1) {
|
||||||
log.info("删除文件成功:{}", ansSheetPath);
|
examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDOId, "更新学生考试为结束时失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean delete = new File(ansSheetPath).delete();
|
||||||
|
if (delete) {
|
||||||
|
log.info("删除文件成功:{}", ansSheetPath);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("识别考试失败 {}", e.getMessage());
|
||||||
|
examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDO.getId(), e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void judgeExamActualGrade(ExamWordsJudgeResultDO wordsJudgeResultDO, ExamWordsDO examWordsDO) {
|
||||||
|
List<VocabularyBankDO> vocabularyBankDOS = vocabularyBankDOMapper.selectVocabularyBankDOListByIds(examWordsDO.getWordIds());
|
||||||
|
Map<Integer, List<VocabularyBankDO>> unitId2Words = vocabularyBankDOS.stream().collect(Collectors.groupingBy(VocabularyBankDO::getUnitId));
|
||||||
|
|
||||||
|
List<GradeUnitDO> gradeUnitDOS = gradeUnitDOMapper.selectByUnitIds(unitId2Words.keySet().stream().toList());
|
||||||
|
|
||||||
|
// unitId -> gradeId
|
||||||
|
Map<Integer, Integer> unitId2GradeId = gradeUnitDOS.stream()
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
GradeUnitDO::getUnitId,
|
||||||
|
GradeUnitDO::getGradeId,
|
||||||
|
(existing, replacement) -> existing // 如果重复,保留第一个(或根据业务决定)
|
||||||
|
));
|
||||||
|
// gradeId -> List<VocabularyBankDO>
|
||||||
|
Map<Integer, List<VocabularyBankDO>> gradeId2Words = vocabularyBankDOS.stream()
|
||||||
|
.filter(vocab -> vocab.getUnitId() != null)
|
||||||
|
.filter(vocab -> unitId2GradeId.containsKey(vocab.getUnitId()))
|
||||||
|
.collect(Collectors.groupingBy(
|
||||||
|
vocab -> unitId2GradeId.get(vocab.getUnitId())
|
||||||
|
));
|
||||||
|
|
||||||
|
// 核心数据结构:GradeId -> List<WordId> (试卷里包含的每个年级的单词有哪些)
|
||||||
|
Map<Integer, Set<Integer>> gradeId2WordIdsMap = new HashMap<>();
|
||||||
|
|
||||||
|
for (VocabularyBankDO vocab : vocabularyBankDOS) {
|
||||||
|
if (vocab.getUnitId() != null && unitId2GradeId.containsKey(vocab.getUnitId())) {
|
||||||
|
Integer gradeId = unitId2GradeId.get(vocab.getUnitId());
|
||||||
|
gradeId2WordIdsMap.computeIfAbsent(gradeId, k -> new HashSet<>()).add(vocab.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 统计各区域正确率 (Calculate Accuracy per Zone)
|
||||||
|
// 锚点年级 (G_test),即这张卷子是按哪个年级生成的
|
||||||
|
Integer anchorGrade = examWordsDO.getGradeId();
|
||||||
|
// 挑战区
|
||||||
|
int zoneA = ExamWordsConstant.getZoneA(anchorGrade);
|
||||||
|
ZoneStats zoneAStats = calculateZoneStats(gradeId2WordIdsMap, wordsJudgeResultDO, zoneA);
|
||||||
|
// 当前核心
|
||||||
|
int zoneB = ExamWordsConstant.getZoneB(anchorGrade);
|
||||||
|
ZoneStats zoneBStats = calculateZoneStats(gradeId2WordIdsMap, wordsJudgeResultDO, zoneB);
|
||||||
|
// 回溯 Level 1
|
||||||
|
int zoneC = ExamWordsConstant.getZoneC(anchorGrade);
|
||||||
|
ZoneStats zoneCStats = calculateZoneStats(gradeId2WordIdsMap, wordsJudgeResultDO, zoneC);
|
||||||
|
// 回溯 Level 2
|
||||||
|
int zoneD = ExamWordsConstant.getZoneD(anchorGrade);
|
||||||
|
ZoneStats zoneDStats = calculateZoneStats(gradeId2WordIdsMap, wordsJudgeResultDO, zoneD);
|
||||||
|
// 回溯 Level 3
|
||||||
|
int zoneE = ExamWordsConstant.getZoneE(anchorGrade);
|
||||||
|
ZoneStats zoneEStats = calculateZoneStats(gradeId2WordIdsMap, wordsJudgeResultDO, zoneE);
|
||||||
|
// 地基兜底
|
||||||
|
int zoneF = ExamWordsConstant.getZoneF(anchorGrade);
|
||||||
|
ZoneStats zoneFStats = calculateZoneStats(gradeId2WordIdsMap, wordsJudgeResultDO, zoneF);
|
||||||
|
// 地基区
|
||||||
|
ZoneStats zoneFoundationStats = calculateFoundationStats(gradeId2WordIdsMap, wordsJudgeResultDO, zoneD);
|
||||||
|
|
||||||
|
DiagnosisResult diagnosisResult = diagnoseStudentLevel(anchorGrade, zoneAStats, zoneBStats, zoneCStats, zoneDStats, zoneFoundationStats);
|
||||||
|
|
||||||
|
studentDOMapper.updateStudentActualGradeId(wordsJudgeResultDO.getStudentId(), diagnosisResult.getDeterminedLevel());
|
||||||
|
log.info("判断结果:{}", diagnosisResult);
|
||||||
|
wordsJudgeResultDO.setMsg(diagnosisResult.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算特定年级的统计数据
|
||||||
|
*/
|
||||||
|
private ZoneStats calculateZoneStats(Map<Integer, Set<Integer>> map, ExamWordsJudgeResultDO result, Integer targetGrade) {
|
||||||
|
Set<Integer> totalWords = map.getOrDefault(targetGrade, Collections.emptySet());
|
||||||
|
if (totalWords.isEmpty()) return new ZoneStats(targetGrade, 0, 0, 0.0);
|
||||||
|
|
||||||
|
long correctCount = result.getCorrectWordIds().stream().filter(totalWords::contains).count();
|
||||||
|
double accuracy = (double) correctCount / totalWords.size();
|
||||||
|
|
||||||
|
return new ZoneStats(targetGrade, totalWords.size(), (int)correctCount, accuracy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算地基区(所有低于某年级)的统计数据
|
||||||
|
*/
|
||||||
|
private ZoneStats calculateFoundationStats(Map<Integer, Set<Integer>> map, ExamWordsJudgeResultDO result, Integer maxGradeThreshold) {
|
||||||
|
Set<Integer> foundationWords = new HashSet<>();
|
||||||
|
map.forEach((grade, words) -> {
|
||||||
|
if (grade <= maxGradeThreshold) {
|
||||||
|
foundationWords.addAll(words);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (foundationWords.isEmpty()) return new ZoneStats(-1, 0, 0, 0.0);
|
||||||
|
|
||||||
|
long correctCount = result.getCorrectWordIds().stream().filter(foundationWords::contains).count();
|
||||||
|
double accuracy = (double) correctCount / foundationWords.size();
|
||||||
|
|
||||||
|
return new ZoneStats(-1, foundationWords.size(), (int)correctCount, accuracy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 核心诊断逻辑矩阵
|
||||||
|
*/
|
||||||
|
private DiagnosisResult diagnoseStudentLevel(Integer anchorGrade, ZoneStats zoneA, ZoneStats zoneB, ZoneStats zoneC, ZoneStats zoneD, ZoneStats zoneFoundation) {
|
||||||
|
|
||||||
|
// --- 场景 1: 熔断机制 (Meltdown) ---
|
||||||
|
// 针对 G8/G9 等高年级,如果地基区(G4/G5及以下)错误率极高
|
||||||
|
// 阈值设为 20% (即 Zone F 正确率 < 20%)
|
||||||
|
if (zoneFoundation.getTotalCount() > 0 && zoneFoundation.getAccuracy() < 0.20 && anchorGrade >= 7) {
|
||||||
|
return new DiagnosisResult(
|
||||||
|
4, // 强制下沉到 G4 (根据文档逻辑,熔断通常锚定 G4)
|
||||||
|
ActionType.TRIGGER_RETEST,
|
||||||
|
"严重预警!检测到您的基础词汇(" + ExamWordsConstant.getGradeName(anchorGrade - 3) + "及以下)存在大面积坍塌。系统已为您启动【基础词汇专项排查】,请勿担心,这是为了更好地起跳。"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 场景 2: 进阶/跳级 (Upgrade) ---
|
||||||
|
// 必须满足:当前年级(B) > 80% 且 挑战年级(A) > 60% (如果有题的话)
|
||||||
|
boolean canUpgrade = zoneB.getAccuracy() >= 0.8 && (zoneA.getTotalCount() == 0 || zoneA.getAccuracy() >= 0.6);
|
||||||
|
if (canUpgrade) {
|
||||||
|
return new DiagnosisResult(
|
||||||
|
anchorGrade + 1,
|
||||||
|
ActionType.UPGRADE,
|
||||||
|
"恭喜!您对" + ExamWordsConstant.getGradeName(anchorGrade) + " 的掌握非常扎实,且具备挑战" + ExamWordsConstant.getGradeName(anchorGrade + 1) + " 的潜力。系统将为您解锁更高阶词库。"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 场景 3: 正常回溯诊断 (Standard Diagnosis) ---
|
||||||
|
|
||||||
|
// 3.1 当前年级崩盘 (Zone B < 60%)
|
||||||
|
if (zoneB.getAccuracy() < 0.6) {
|
||||||
|
|
||||||
|
// 检查上一级 (Zone C)
|
||||||
|
if (zoneC.getAccuracy() >= 0.8) {
|
||||||
|
// "中考空心病" / "基础扎实但新课未动"
|
||||||
|
// 定级:保持在当前年级,但侧重复习
|
||||||
|
return new DiagnosisResult(
|
||||||
|
anchorGrade,
|
||||||
|
ActionType.STAY_AND_REVIEW,
|
||||||
|
"基础尚可(" + ExamWordsConstant.getGradeName(zoneC.getGradeId()) + "掌握较好),但" + ExamWordsConstant.getGradeName(anchorGrade) + " 新词汇存在较大缺口。建议:重点攻克本年级核心动词。"
|
||||||
|
);
|
||||||
|
} else if (zoneC.getAccuracy() >= 0.6) {
|
||||||
|
// Zone C 勉强及格,Zone B 不及格 -> 降一级
|
||||||
|
return new DiagnosisResult(
|
||||||
|
anchorGrade - 1,
|
||||||
|
ActionType.DOWNGRADE,
|
||||||
|
"检测到" + ExamWordsConstant.getGradeName(anchorGrade) + " 学习吃力,且" + ExamWordsConstant.getGradeName(zoneC.getGradeId()) + " 存在模糊点。建议降级回溯,巩固 " + ExamWordsConstant.getGradeName(zoneC.getGradeId()) + "。"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Zone C 也崩了 (< 60%) -> 连降两级或更多
|
||||||
|
// 检查 Zone D
|
||||||
|
if (zoneD.getTotalCount() > 0 && zoneD.getAccuracy() >= 0.6) {
|
||||||
|
return new DiagnosisResult(
|
||||||
|
anchorGrade - 2,
|
||||||
|
ActionType.DOWNGRADE,
|
||||||
|
"您的" + ExamWordsConstant.getGradeName(zoneC.getGradeId()) + " 和 " + ExamWordsConstant.getGradeName(anchorGrade) + " 均存在脱节。建议从 " + ExamWordsConstant.getGradeName(zoneD.getGradeId()) + " 开始系统补漏。"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// 彻底崩盘,可能需要熔断,或者定级到更低
|
||||||
|
return new DiagnosisResult(
|
||||||
|
Math.max(1, anchorGrade - 3),
|
||||||
|
ActionType.DOWNGRADE,
|
||||||
|
"基础薄弱,建议暂停当前进度,从低年级核心高频词重新开始。"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3.2 正常达标 (Zone B 60% - 80%)
|
||||||
|
return new DiagnosisResult(
|
||||||
|
anchorGrade,
|
||||||
|
ActionType.PASS,
|
||||||
|
"当前年级达标。建议继续保持,并尝试在阅读中增加长难句练习。"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ExamWordsJudgeResultDO> getExamWordsJudgeResult(Integer page, Integer pageSize) {
|
public List<ExamWordsJudgeResultDO> getExamWordsJudgeResult(Integer page, Integer pageSize) {
|
||||||
return examWordsJudgeResultDOMapper.selectByPage((page - 1) * pageSize, pageSize);
|
return examWordsJudgeResultDOMapper.selectByPage((page - 1) * pageSize, pageSize);
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ public class StudentServiceImpl implements StudentService {
|
|||||||
.name(name)
|
.name(name)
|
||||||
.classId(classId)
|
.classId(classId)
|
||||||
.gradeId(gradeId)
|
.gradeId(gradeId)
|
||||||
|
.actualGradeId(gradeId)
|
||||||
.startTime(createTime)
|
.startTime(createTime)
|
||||||
.build();
|
.build();
|
||||||
studentDOMapper.insert(studentDO);
|
studentDOMapper.insert(studentDO);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
server:
|
server:
|
||||||
port: 8080 # 项目启动的端口
|
port: 8081 # 项目启动的端口
|
||||||
|
|
||||||
spring:
|
spring:
|
||||||
profiles:
|
profiles:
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
<result column="is_finished" jdbcType="INTEGER" property="isFinished" />
|
<result column="is_finished" jdbcType="INTEGER" property="isFinished" />
|
||||||
<result column="start_date" jdbcType="TIMESTAMP" property="startDate" />
|
<result column="start_date" jdbcType="TIMESTAMP" property="startDate" />
|
||||||
<result column="msg" jdbcType="VARCHAR" property="msg" />
|
<result column="msg" jdbcType="VARCHAR" property="msg" />
|
||||||
<result column="ans_grade_id" jdbcType="INTEGER" property="ansGradeId" />
|
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.yinlihupo.enlish.service.domain.dataobject.ExamWordsJudgeResultDO">
|
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.yinlihupo.enlish.service.domain.dataobject.ExamWordsJudgeResultDO">
|
||||||
@@ -34,8 +33,8 @@
|
|||||||
|
|
||||||
<update id="updateMsg">
|
<update id="updateMsg">
|
||||||
update exam_words_judge_result
|
update exam_words_judge_result
|
||||||
set msg = #{msg}
|
set msg = #{msg},
|
||||||
and is_finished = 2
|
is_finished = 2
|
||||||
where id = #{id}
|
where id = #{id}
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
@@ -47,7 +46,8 @@
|
|||||||
wrong_word_ids = #{examWordsJudgeResultDO.wrongWordIds, typeHandler=com.yinlihupo.enlish.service.config.ListWordIdTypeHandler},
|
wrong_word_ids = #{examWordsJudgeResultDO.wrongWordIds, typeHandler=com.yinlihupo.enlish.service.config.ListWordIdTypeHandler},
|
||||||
correct_word_count = #{examWordsJudgeResultDO.correctWordCount},
|
correct_word_count = #{examWordsJudgeResultDO.correctWordCount},
|
||||||
wrong_word_count = #{examWordsJudgeResultDO.wrongWordCount},
|
wrong_word_count = #{examWordsJudgeResultDO.wrongWordCount},
|
||||||
is_finished = 1
|
is_finished = 1,
|
||||||
|
msg = #{examWordsJudgeResultDO.msg}
|
||||||
where id = #{examWordsJudgeResultDO.id}
|
where id = #{examWordsJudgeResultDO.id}
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,17 @@
|
|||||||
where unit_id = #{unitId}
|
where unit_id = #{unitId}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="selectByUnitIds" resultMap="BaseResultMap">
|
||||||
|
select *
|
||||||
|
from grade_unit
|
||||||
|
<if test="unitIds != null">
|
||||||
|
where unit_id in
|
||||||
|
<foreach collection="unitIds" item="unitId" separator="," open="(" close=")">
|
||||||
|
#{unitId}
|
||||||
|
</foreach>
|
||||||
|
</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
<insert id="insert">
|
<insert id="insert">
|
||||||
insert into grade_unit (grade_id, unit_id)
|
insert into grade_unit (grade_id, unit_id)
|
||||||
values (#{gradeId}, #{unitId})
|
values (#{gradeId}, #{unitId})
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
<result column="current_vocabulary_size" jdbcType="INTEGER" property="currentVocabularySize" />
|
<result column="current_vocabulary_size" jdbcType="INTEGER" property="currentVocabularySize" />
|
||||||
<result column="is_deleted" jdbcType="INTEGER" property="isDeleted"/>
|
<result column="is_deleted" jdbcType="INTEGER" property="isDeleted"/>
|
||||||
<result column="start_time" jdbcType="TIMESTAMP" property="startTime" />
|
<result column="start_time" jdbcType="TIMESTAMP" property="startTime" />
|
||||||
|
<result column="actual_grade_id" jdbcType="INTEGER" property="actualGradeId" />
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<select id="selectStudentDOListByClassIdAndGradeId" resultMap="BaseResultMap">
|
<select id="selectStudentDOListByClassIdAndGradeId" resultMap="BaseResultMap">
|
||||||
@@ -52,8 +53,8 @@
|
|||||||
|
|
||||||
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||||
insert into student
|
insert into student
|
||||||
(name, class_id, grade_id, is_deleted, start_time)
|
(name, class_id, grade_id, is_deleted, start_time, actual_grade_id)
|
||||||
values (#{name}, #{classId}, #{gradeId}, 0, #{startTime})
|
values (#{name}, #{classId}, #{gradeId}, 0, #{startTime}, #{actualGradeId})
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
<update id="deleteById">
|
<update id="deleteById">
|
||||||
@@ -62,6 +63,12 @@
|
|||||||
where id = #{id}
|
where id = #{id}
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
|
<update id="updateStudentActualGradeId">
|
||||||
|
update student
|
||||||
|
set actual_grade_id = #{gradeId}
|
||||||
|
where id = #{studentId}
|
||||||
|
</update>
|
||||||
|
|
||||||
<select id="selectStudentCountByClassId" resultType="java.lang.Integer">
|
<select id="selectStudentCountByClassId" resultType="java.lang.Integer">
|
||||||
select count(1)
|
select count(1)
|
||||||
from student
|
from student
|
||||||
|
|||||||
@@ -117,6 +117,7 @@
|
|||||||
</if>
|
</if>
|
||||||
limit #{wordCount}
|
limit #{wordCount}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="selectWordTotal" resultType="java.lang.Integer">
|
<select id="selectWordTotal" resultType="java.lang.Integer">
|
||||||
select count(*)
|
select count(*)
|
||||||
from vocabulary_bank
|
from vocabulary_bank
|
||||||
|
|||||||
@@ -2,18 +2,6 @@
|
|||||||
<el-dialog v-model="visible" title="生成试题" width="520px" :close-on-click-modal="false">
|
<el-dialog v-model="visible" title="生成试题" width="520px" :close-on-click-modal="false">
|
||||||
<div class="space-y-4" v-loading="loading">
|
<div class="space-y-4" v-loading="loading">
|
||||||
<el-form label-width="80px">
|
<el-form label-width="80px">
|
||||||
<el-form-item label="年级">
|
|
||||||
<el-select v-model="gradeId" placeholder="请选择年级" style="width: 240px">
|
|
||||||
<el-option v-for="g in gradeOptions" :key="g.id" :label="g.title" :value="g.id" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="难度">
|
|
||||||
<el-select v-model="level" placeholder="请选择难度" style="width: 240px">
|
|
||||||
<el-option :label="'一级'" :value="1" />
|
|
||||||
<el-option :label="'二级'" :value="2" />
|
|
||||||
<el-option :label="'三级'" :value="3" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="类型">
|
<el-form-item label="类型">
|
||||||
<el-select v-model="type" placeholder="请选择类型" style="width: 240px">
|
<el-select v-model="type" placeholder="请选择类型" style="width: 240px">
|
||||||
<el-option :label="'摸底'" :value="1" />
|
<el-option :label="'摸底'" :value="1" />
|
||||||
@@ -28,7 +16,7 @@
|
|||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="flex justify-end gap-2">
|
<div class="flex justify-end gap-2">
|
||||||
<el-button @click="visible = false">取消</el-button>
|
<el-button @click="visible = false">取消</el-button>
|
||||||
<el-button type="primary" :disabled="!gradeId || !level || !type" @click="handleGenerate">生成并下载</el-button>
|
<el-button type="primary" :disabled="!type" @click="handleGenerate">生成并下载</el-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
@@ -71,7 +59,7 @@ async function fetchGrades() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleGenerate() {
|
async function handleGenerate() {
|
||||||
if (!gradeId.value || !level.value || !type.value || props.studentIds.length === 0) return
|
if (!type.value) return
|
||||||
await generateExamWords({
|
await generateExamWords({
|
||||||
gradeId: Number(gradeId.value),
|
gradeId: Number(gradeId.value),
|
||||||
level: Number(level.value),
|
level: Number(level.value),
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
<el-descriptions-item label="姓名">{{ detail.name }}</el-descriptions-item>
|
<el-descriptions-item label="姓名">{{ detail.name }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="班级">{{ detail.className }}</el-descriptions-item>
|
<el-descriptions-item label="班级">{{ detail.className }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="年级">{{ detail.gradeName }}</el-descriptions-item>
|
<el-descriptions-item label="年级">{{ detail.gradeName }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="学生实际水平年级">{{ detail.actualGrade }}</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="startDate" label="开始时间" min-width="160" />
|
<el-table-column prop="startDate" label="开始时间" min-width="160" />
|
||||||
<el-table-column prop="errorMsg" label="错误信息" min-width="160" />
|
<el-table-column prop="msg" label="判卷结算" min-width="160" />
|
||||||
</el-table>
|
</el-table>
|
||||||
<div class="mt-4 flex justify-end">
|
<div class="mt-4 flex justify-end">
|
||||||
<el-pagination
|
<el-pagination
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export default defineConfig({
|
|||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://localhost:8080',
|
target: 'http://localhost:8081',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace(/^\/api/, ''),
|
rewrite: (path) => path.replace(/^\/api/, ''),
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user