Compare commits
10 Commits
f8169b453e
...
a50c9a2b16
| Author | SHA1 | Date | |
|---|---|---|---|
| a50c9a2b16 | |||
| eeeb48d048 | |||
| 26674ab8a9 | |||
| fd828442b1 | |||
| dbe7312633 | |||
| 49cd146bc3 | |||
| 07b9b56e8a | |||
| 7f41036193 | |||
| d027c9c7e6 | |||
| e5fbb445cf |
@@ -0,0 +1,10 @@
|
|||||||
|
package com.yinlihupo.enlish.service.constant;
|
||||||
|
|
||||||
|
public interface LessonPlanConstant {
|
||||||
|
|
||||||
|
String TITLE = "Title";
|
||||||
|
String PASSAGE = "The Passage";
|
||||||
|
String QUIZ = "Quiz";
|
||||||
|
String ANSWER_KEY_EXPLANATION = "Answer Key & Explanation";
|
||||||
|
String FULL_TRANSLATION = "Full Translation";
|
||||||
|
}
|
||||||
@@ -50,6 +50,9 @@ public class ExamWordsController {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
ExamWordsDO examWordsDO = examWordsService.generateExamWords(gradeId, level, studentIds);
|
ExamWordsDO examWordsDO = examWordsService.generateExamWords(gradeId, level, studentIds);
|
||||||
|
if (examWordsDO == null || examWordsDO.getWordIds().isEmpty()) {
|
||||||
|
throw new RuntimeException("没有单词");
|
||||||
|
}
|
||||||
List<VocabularyBankDO> vocabularyBankDOS = vocabularyService.findVocabularyBankDOListById(examWordsDO.getWordIds());
|
List<VocabularyBankDO> vocabularyBankDOS = vocabularyService.findVocabularyBankDOListById(examWordsDO.getWordIds());
|
||||||
List<Word> assessmentWords = vocabularyBankDOS.stream().map(vocabularyBankDO -> Word.builder()
|
List<Word> assessmentWords = vocabularyBankDOS.stream().map(vocabularyBankDO -> Word.builder()
|
||||||
.id(vocabularyBankDO.getId())
|
.id(vocabularyBankDO.getId())
|
||||||
@@ -138,4 +141,22 @@ public class ExamWordsController {
|
|||||||
|
|
||||||
return Response.success(examWordsDetailResultRspVO);
|
return Response.success(examWordsDetailResultRspVO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("student/history")
|
||||||
|
@ApiOperationLog(description = "获取学生历史考试结果")
|
||||||
|
Response<List<FindStudentExamWordsResultListRspVO>> getStudentExamWordsResultList(@RequestBody FindStudentExamWordsResultReqVO findStudentExamWordsResultReqVO) {
|
||||||
|
Integer studentId = findStudentExamWordsResultReqVO.getStudentId();
|
||||||
|
List<FindStudentExamWordsResultListRspVO> list = examWordsJudgeService.getStudentExamWordsResultList(studentId).stream().map(examWordsJudgeResultDO -> FindStudentExamWordsResultListRspVO.builder()
|
||||||
|
.id(examWordsJudgeResultDO.getId())
|
||||||
|
.studentId(examWordsJudgeResultDO.getStudentId())
|
||||||
|
.examWordsId(examWordsJudgeResultDO.getExamWordsId())
|
||||||
|
.correctWordCount(examWordsJudgeResultDO.getCorrectWordCount())
|
||||||
|
.wrongWordCount(examWordsJudgeResultDO.getWrongWordCount())
|
||||||
|
.startDate(examWordsJudgeResultDO.getStartDate())
|
||||||
|
.accuracy((double)examWordsJudgeResultDO.getCorrectWordCount() / (examWordsJudgeResultDO.getCorrectWordCount() + examWordsJudgeResultDO.getWrongWordCount()))
|
||||||
|
.build()
|
||||||
|
).toList();
|
||||||
|
|
||||||
|
return Response.success(list);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package com.yinlihupo.enlish.service.controller;
|
||||||
|
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.LessonPlansDO;
|
||||||
|
import com.yinlihupo.enlish.service.model.vo.plan.AddLessonPlanReqVO;
|
||||||
|
import com.yinlihupo.enlish.service.model.vo.plan.DownLoadLessonPlanReqVO;
|
||||||
|
import com.yinlihupo.enlish.service.service.LessonPlansService;
|
||||||
|
import com.yinlihupo.enlish.service.utils.WordExportUtil;
|
||||||
|
import com.yinlihupo.framework.biz.operationlog.aspect.ApiOperationLog;
|
||||||
|
import com.yinlihupo.framework.common.response.Response;
|
||||||
|
import com.yinlihupo.framework.common.util.JsonUtils;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
@RequestMapping("/plan/")
|
||||||
|
@RestController
|
||||||
|
@Slf4j
|
||||||
|
public class LessonPlanController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private LessonPlansService lessonPlanService;
|
||||||
|
|
||||||
|
@Resource(name = "taskExecutor")
|
||||||
|
private Executor taskExecutor;
|
||||||
|
|
||||||
|
@Value("${templates.plan.weekday}")
|
||||||
|
private String planWeekday;
|
||||||
|
@Value("${templates.plan.weekend}")
|
||||||
|
private String planWeekend;
|
||||||
|
|
||||||
|
@PostMapping("generate")
|
||||||
|
@ApiOperationLog(description = "生成学案")
|
||||||
|
public Response<String> generateLessonPlan(@RequestBody AddLessonPlanReqVO addLessonPlanReqVO) {
|
||||||
|
Integer studentId = addLessonPlanReqVO.getStudentId();
|
||||||
|
Integer unitId = addLessonPlanReqVO.getUnitId();
|
||||||
|
try {
|
||||||
|
taskExecutor.execute(() -> lessonPlanService.generateLessonPlans(studentId, unitId));
|
||||||
|
return Response.success("生成学案成功,请等待 10 分钟");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage());
|
||||||
|
return Response.fail("生成学案失败" + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("download")
|
||||||
|
public void downloadLessonPlan(@RequestBody DownLoadLessonPlanReqVO downLoadLessonPlanReqVO, HttpServletResponse response) {
|
||||||
|
Integer id = downLoadLessonPlanReqVO.getId();
|
||||||
|
LessonPlansDO lessonPlanById = lessonPlanService.findLessonPlanById(id);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Map<String, Object> map = JsonUtils.parseMap(lessonPlanById.getContentDetails(), String.class, Object.class);
|
||||||
|
if (!lessonPlanById.getTitle().contains("复习")) {
|
||||||
|
WordExportUtil.generateLessonPlanDocx(map, lessonPlanById.getTitle(), response, planWeekday, true);
|
||||||
|
} else {
|
||||||
|
WordExportUtil.generateLessonPlanDocx(map, lessonPlanById.getTitle(), response, planWeekend, false);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package com.yinlihupo.enlish.service.controller;
|
||||||
|
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.LessonPlansDO;
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.StudentLessonPlansDO;
|
||||||
|
import com.yinlihupo.enlish.service.model.bo.StudentDetail;
|
||||||
|
import com.yinlihupo.enlish.service.model.vo.plan.FindStudentPlansReqVO;
|
||||||
|
import com.yinlihupo.enlish.service.model.vo.plan.FindStudentPlansRspVO;
|
||||||
|
import com.yinlihupo.enlish.service.model.vo.plan.FinishStudentPlanReqVO;
|
||||||
|
import com.yinlihupo.enlish.service.model.vo.plan.LessonPlanItem;
|
||||||
|
import com.yinlihupo.enlish.service.service.LessonPlansService;
|
||||||
|
import com.yinlihupo.enlish.service.service.StudentLessonPlansService;
|
||||||
|
import com.yinlihupo.enlish.service.service.StudentService;
|
||||||
|
import com.yinlihupo.framework.biz.operationlog.aspect.ApiOperationLog;
|
||||||
|
import com.yinlihupo.framework.common.response.PageResponse;
|
||||||
|
import com.yinlihupo.framework.common.response.Response;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@RequestMapping("/studentLessonPlans/")
|
||||||
|
@RestController
|
||||||
|
public class StudentLessonPlansController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private StudentLessonPlansService studentLessonPlansService;
|
||||||
|
@Resource
|
||||||
|
private StudentService studentService;
|
||||||
|
@Resource
|
||||||
|
private LessonPlansService lessonPlansService;
|
||||||
|
|
||||||
|
@PostMapping("/list")
|
||||||
|
@ApiOperationLog(description = "查询学生学案")
|
||||||
|
public PageResponse<FindStudentPlansRspVO> findStudentPlans(@RequestBody FindStudentPlansReqVO findStudentPlansReqVO) {
|
||||||
|
|
||||||
|
Integer studentLessonPlanTotal = studentLessonPlansService.findStudentLessonPlanTotal();
|
||||||
|
|
||||||
|
String name = findStudentPlansReqVO.getName();
|
||||||
|
Integer page = findStudentPlansReqVO.getPage();
|
||||||
|
Integer size = findStudentPlansReqVO.getSize();
|
||||||
|
|
||||||
|
List<StudentLessonPlansDO> studentLessonPlansDOListPageSize = studentLessonPlansService.findStudentLessonPlansDOList(page, size, name);
|
||||||
|
Map<Integer, List<StudentLessonPlansDO>> studentId2StudentLessonPlansDOListMap = studentLessonPlansDOListPageSize.stream().collect(
|
||||||
|
Collectors.groupingBy(StudentLessonPlansDO::getStudentId)
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Integer> planIds = studentLessonPlansDOListPageSize.stream().map(StudentLessonPlansDO::getPlanId).toList();
|
||||||
|
List<LessonPlansDO> lessonPlans = lessonPlansService.findLessonPlans(planIds);
|
||||||
|
Map<Integer, LessonPlansDO> id2LessonPlansDO = lessonPlans.stream().collect(Collectors.toMap(
|
||||||
|
LessonPlansDO::getId,
|
||||||
|
lessonPlansDO -> lessonPlansDO
|
||||||
|
));
|
||||||
|
|
||||||
|
List<StudentDetail> studentDetailList = studentService.getStudentDetailList(new ArrayList<>(studentId2StudentLessonPlansDOListMap.keySet()));
|
||||||
|
|
||||||
|
List<FindStudentPlansRspVO> findStudentPlansRspVOList = studentDetailList.stream().map(studentDetail -> {
|
||||||
|
List<StudentLessonPlansDO> studentLessonPlansDOList = studentId2StudentLessonPlansDOListMap.get(studentDetail.getId());
|
||||||
|
return FindStudentPlansRspVO.builder()
|
||||||
|
.name(studentDetail.getName())
|
||||||
|
.id(studentDetail.getId())
|
||||||
|
.classId(studentDetail.getClassId())
|
||||||
|
.gradeId(studentDetail.getGradeId())
|
||||||
|
.gradeName(studentDetail.getGradeName())
|
||||||
|
.className(studentDetail.getClassName())
|
||||||
|
.plans(studentLessonPlansDOList.stream().map(studentLessonPlansDO ->
|
||||||
|
LessonPlanItem.builder().title(id2LessonPlansDO.get(studentLessonPlansDO.getPlanId()).getTitle()).id(studentLessonPlansDO.getPlanId()).isFinished(studentLessonPlansDO.getIsFinished()).build()
|
||||||
|
).toList())
|
||||||
|
.build();
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
return PageResponse.success(findStudentPlansRspVOList, page, studentLessonPlanTotal, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/finish")
|
||||||
|
@ApiOperationLog(description = "完成学案")
|
||||||
|
public Response<String> finishStudentPlan(@RequestBody FinishStudentPlanReqVO finishStudentPlanReqVO) {
|
||||||
|
Integer studentId = finishStudentPlanReqVO.getStudentId();
|
||||||
|
Integer planId = finishStudentPlanReqVO.getPlanId();
|
||||||
|
int lessonPlan = studentLessonPlansService.finishStudentLessonPlan(studentId, planId);
|
||||||
|
|
||||||
|
if (lessonPlan > 0) {
|
||||||
|
return Response.success("完成学案成功");
|
||||||
|
}
|
||||||
|
return Response.fail("完成学案失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package com.yinlihupo.enlish.service.controller;
|
||||||
|
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.UnitDO;
|
||||||
|
import com.yinlihupo.enlish.service.model.vo.unit.AddUnitReqVO;
|
||||||
|
import com.yinlihupo.enlish.service.model.vo.unit.DeleteUnitReqVO;
|
||||||
|
import com.yinlihupo.enlish.service.model.vo.unit.FindUnitListReqVO;
|
||||||
|
import com.yinlihupo.enlish.service.model.vo.unit.FindUnitListRspVO;
|
||||||
|
import com.yinlihupo.enlish.service.service.UnitService;
|
||||||
|
import com.yinlihupo.framework.biz.operationlog.aspect.ApiOperationLog;
|
||||||
|
import com.yinlihupo.framework.common.response.PageResponse;
|
||||||
|
import com.yinlihupo.framework.common.response.Response;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RequestMapping("/unit/")
|
||||||
|
@RestController
|
||||||
|
@Slf4j
|
||||||
|
public class UnitController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private UnitService unitService;
|
||||||
|
|
||||||
|
@PostMapping("/list")
|
||||||
|
@ApiOperationLog(description = "查询单元")
|
||||||
|
public PageResponse<FindUnitListRspVO> list(@RequestBody FindUnitListReqVO findUnitListReqVO) {
|
||||||
|
Integer page = findUnitListReqVO.getPage();
|
||||||
|
Integer size = findUnitListReqVO.getSize();
|
||||||
|
Integer unitDOListCount = unitService.findUnitDOListCount();
|
||||||
|
|
||||||
|
List<UnitDO> unitDOList = unitService.findUnitDOList(page, size);
|
||||||
|
List<FindUnitListRspVO> findUnitListRspVOS = unitDOList.stream().map(unitDO -> FindUnitListRspVO.builder()
|
||||||
|
.id(unitDO.getId())
|
||||||
|
.title(unitDO.getTitle())
|
||||||
|
.version(unitDO.getVersion())
|
||||||
|
.createAt(unitDO.getCreateAt())
|
||||||
|
.build()
|
||||||
|
).toList();
|
||||||
|
|
||||||
|
return PageResponse.success(findUnitListRspVOS, page, unitDOListCount, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/add")
|
||||||
|
@ApiOperationLog(description = "添加单元")
|
||||||
|
public Response<Void> add(@RequestBody AddUnitReqVO addUnitReqVO) {
|
||||||
|
try {
|
||||||
|
unitService.add(addUnitReqVO);
|
||||||
|
|
||||||
|
return Response.success();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage());
|
||||||
|
return Response.fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/delete")
|
||||||
|
@ApiOperationLog(description = "删除单元")
|
||||||
|
public Response<String> delete(@RequestBody DeleteUnitReqVO deleteUnitReqVO) {
|
||||||
|
try {
|
||||||
|
unitService.delete(deleteUnitReqVO.getId());
|
||||||
|
|
||||||
|
return Response.success();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage());
|
||||||
|
return Response.fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.yinlihupo.enlish.service.domain.dataobject;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class LessonPlansDO {
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
private String gradeId;
|
||||||
|
|
||||||
|
private Integer unitId;
|
||||||
|
|
||||||
|
private Date createdAt;
|
||||||
|
|
||||||
|
private String contentDetails;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.yinlihupo.enlish.service.domain.dataobject;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class StudentLessonPlansDO {
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
private Integer studentId;
|
||||||
|
|
||||||
|
private Integer planId;
|
||||||
|
|
||||||
|
private Date startTime;
|
||||||
|
|
||||||
|
private BigDecimal masteryRat;
|
||||||
|
|
||||||
|
private Integer totalCount;
|
||||||
|
|
||||||
|
private Integer isFinished;
|
||||||
|
|
||||||
|
private String memorizedWordsJson;
|
||||||
|
|
||||||
|
private String unmemorizedWordsJson;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import lombok.NoArgsConstructor;
|
|||||||
@Data
|
@Data
|
||||||
@Builder
|
@Builder
|
||||||
public class VocabularyBankDO {
|
public class VocabularyBankDO {
|
||||||
|
|
||||||
private Integer id;
|
private Integer id;
|
||||||
|
|
||||||
private String word;
|
private String word;
|
||||||
@@ -18,6 +19,8 @@ public class VocabularyBankDO {
|
|||||||
|
|
||||||
private String pronunciation;
|
private String pronunciation;
|
||||||
|
|
||||||
|
private String pos;
|
||||||
|
|
||||||
private Integer unitId;
|
private Integer unitId;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -20,4 +20,6 @@ public interface ExamWordsJudgeResultDOMapper {
|
|||||||
Integer selectCount();
|
Integer selectCount();
|
||||||
|
|
||||||
ExamWordsJudgeResultDO selectDetailById(@Param("id") Integer id);
|
ExamWordsJudgeResultDO selectDetailById(@Param("id") Integer id);
|
||||||
|
|
||||||
|
List<ExamWordsJudgeResultDO> selectByStudentId(@Param("studentId") Integer studentId);
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.yinlihupo.enlish.service.domain.mapper;
|
package com.yinlihupo.enlish.service.domain.mapper;
|
||||||
|
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.GradeUnitDO;
|
||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -7,4 +8,10 @@ import java.util.List;
|
|||||||
public interface GradeUnitDOMapper {
|
public interface GradeUnitDOMapper {
|
||||||
|
|
||||||
List<Integer> selectUnitIdsByGradeId(@Param("gradeId") Integer gradeId);
|
List<Integer> selectUnitIdsByGradeId(@Param("gradeId") Integer gradeId);
|
||||||
|
|
||||||
|
GradeUnitDO selectByUnitId(@Param("unitId") Integer unitId);
|
||||||
|
|
||||||
|
int insert(GradeUnitDO record);
|
||||||
|
|
||||||
|
int deleteByUnitId(@Param("unitId") Integer unitId);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.yinlihupo.enlish.service.domain.mapper;
|
||||||
|
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.LessonPlansDO;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface LessonPlansDOMapper {
|
||||||
|
|
||||||
|
void insert(LessonPlansDO lessonPlansDO);
|
||||||
|
|
||||||
|
LessonPlansDO selectById(Integer id);
|
||||||
|
|
||||||
|
List<LessonPlansDO> findLessonPlansByStudentId(@Param("ids") List<Integer> ids);
|
||||||
|
|
||||||
|
LessonPlansDO selectByLessonId(@Param("lessonId") Integer lessonId);
|
||||||
|
}
|
||||||
@@ -19,4 +19,6 @@ public interface StudentDOMapper {
|
|||||||
|
|
||||||
// 逻辑删除
|
// 逻辑删除
|
||||||
void deleteById(Integer id);
|
void deleteById(Integer id);
|
||||||
|
|
||||||
|
int selectStudentCountByClassId(@Param("classId") Integer classId);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.yinlihupo.enlish.service.domain.mapper;
|
||||||
|
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.StudentLessonPlansDO;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface StudentLessonPlansDOMapper {
|
||||||
|
|
||||||
|
void insert(StudentLessonPlansDO studentLessonPlansDO);
|
||||||
|
|
||||||
|
List<StudentLessonPlansDO> selectStudentLessonPlanList(@Param("startIndex") Integer startIndex, @Param("pageSize") Integer pageSize, @Param("name") String name);
|
||||||
|
|
||||||
|
Integer selectStudentPlanTotal();
|
||||||
|
|
||||||
|
Integer finfishStudentPlan(@Param("studentId") Integer studentId, @Param("planId") Integer planId, @Param("count") Integer count, @Param("mastery") double mastery);
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
package com.yinlihupo.enlish.service.domain.mapper;
|
package com.yinlihupo.enlish.service.domain.mapper;
|
||||||
|
|
||||||
import com.yinlihupo.enlish.service.domain.dataobject.UnitDO;
|
import com.yinlihupo.enlish.service.domain.dataobject.UnitDO;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public interface UnitDOMapper {
|
public interface UnitDOMapper {
|
||||||
int deleteByPrimaryKey(Integer id);
|
int deleteByPrimaryKey(Integer id);
|
||||||
@@ -18,4 +21,8 @@ public interface UnitDOMapper {
|
|||||||
int updateByPrimaryKey(UnitDO record);
|
int updateByPrimaryKey(UnitDO record);
|
||||||
|
|
||||||
UnitDO selectByTitle(String title);
|
UnitDO selectByTitle(String title);
|
||||||
|
|
||||||
|
List<UnitDO> selectUnitDOList(@Param("startIndex") Integer startIndex, @Param("pageSize") Integer pageSize);
|
||||||
|
|
||||||
|
Integer selectUnitDOListCount();
|
||||||
}
|
}
|
||||||
@@ -14,4 +14,12 @@ public interface VocabularyBankDOMapper {
|
|||||||
List<VocabularyBankDO> selectVocabularyBankDOListByIds(@Param("ids") List<Integer> ids);
|
List<VocabularyBankDO> selectVocabularyBankDOListByIds(@Param("ids") List<Integer> ids);
|
||||||
|
|
||||||
List<Integer> selectAllIds();
|
List<Integer> selectAllIds();
|
||||||
|
|
||||||
|
List<VocabularyBankDO> selectVocabularyBankDOAllByUnitId(@Param("unitId") Integer unitId);
|
||||||
|
|
||||||
|
List<VocabularyBankDO> selectVocabularyBankListStudentNotMaster(@Param("gradeId") Integer gradeId, @Param("studentId") Integer studentId);
|
||||||
|
|
||||||
|
List<VocabularyBankDO> selectVocabularyBankListSelfCheck(@Param("gradeId") Integer gradeId, @Param("studentId") Integer studentId, @Param("ids") List<Integer> ids, @Param("wordCount") Integer wordCount);
|
||||||
|
|
||||||
|
Integer selectWordTotal();
|
||||||
}
|
}
|
||||||
@@ -14,4 +14,6 @@ public interface WordMasteryLogDOMapper {
|
|||||||
int batchInsertInitialization(@Param("wordIds") List<Integer> wordIds, @Param("studentId") Integer studentId);
|
int batchInsertInitialization(@Param("wordIds") List<Integer> wordIds, @Param("studentId") Integer studentId);
|
||||||
|
|
||||||
int batchUpdateStudentMastery(@Param("wordMasteryLogDOs") List<WordMasteryLogDO> wordMasteryLogDOs);
|
int batchUpdateStudentMastery(@Param("wordMasteryLogDOs") List<WordMasteryLogDO> wordMasteryLogDOs);
|
||||||
|
|
||||||
|
int selectStudentStrengthCount(@Param("studentId") Integer studentId);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.exam;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class FindStudentExamWordsResultListRspVO {
|
||||||
|
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
private Integer studentId;
|
||||||
|
|
||||||
|
private Integer examWordsId;
|
||||||
|
|
||||||
|
private Integer correctWordCount;
|
||||||
|
|
||||||
|
private Integer wrongWordCount;
|
||||||
|
|
||||||
|
private Double accuracy;
|
||||||
|
|
||||||
|
private LocalDateTime startDate;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.exam;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class FindStudentExamWordsResultReqVO {
|
||||||
|
|
||||||
|
private Integer studentId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.plan;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Data
|
||||||
|
public class AddLessonPlanReqVO {
|
||||||
|
|
||||||
|
private Integer studentId;
|
||||||
|
private Integer unitId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.plan;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class DownLoadLessonPlanReqVO {
|
||||||
|
|
||||||
|
private Integer id;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.plan;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class FindStudentPlansReqVO {
|
||||||
|
|
||||||
|
String name;
|
||||||
|
Integer page;
|
||||||
|
Integer size;
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.plan;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class FindStudentPlansRspVO {
|
||||||
|
|
||||||
|
private Integer id;
|
||||||
|
private String name;
|
||||||
|
private Integer classId;
|
||||||
|
private String className;
|
||||||
|
private Integer gradeId;
|
||||||
|
private String gradeName;
|
||||||
|
|
||||||
|
List<LessonPlanItem> plans;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.plan;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Data
|
||||||
|
public class FinishStudentPlanReqVO {
|
||||||
|
|
||||||
|
private Integer studentId;
|
||||||
|
private Integer planId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.plan;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class LessonPlanItem {
|
||||||
|
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
private Integer isFinished;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.unit;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class AddUnitReqVO {
|
||||||
|
|
||||||
|
private String title;
|
||||||
|
private Integer gradeId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.unit;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class DeleteUnitReqVO {
|
||||||
|
|
||||||
|
private Integer id;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.unit;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class FindUnitListReqVO {
|
||||||
|
|
||||||
|
private Integer page;
|
||||||
|
private Integer size;
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.unit;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class FindUnitListRspVO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键
|
||||||
|
*/
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 年级/单元
|
||||||
|
*/
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 版本
|
||||||
|
*/
|
||||||
|
private String version;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime createAt;
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package com.yinlihupo.enlish.service.service;
|
|||||||
|
|
||||||
import com.yinlihupo.enlish.service.domain.dataobject.ExamWordsJudgeResultDO;
|
import com.yinlihupo.enlish.service.domain.dataobject.ExamWordsJudgeResultDO;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public interface ExamWordsJudgeService {
|
public interface ExamWordsJudgeService {
|
||||||
@@ -13,4 +14,6 @@ public interface ExamWordsJudgeService {
|
|||||||
Integer getExamWordsJudgeResultCount();
|
Integer getExamWordsJudgeResultCount();
|
||||||
|
|
||||||
ExamWordsJudgeResultDO getExamWordsJudgeResultDOById(Integer id);
|
ExamWordsJudgeResultDO getExamWordsJudgeResultDOById(Integer id);
|
||||||
|
|
||||||
|
List<ExamWordsJudgeResultDO> getStudentExamWordsResultList(Integer studentId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.yinlihupo.enlish.service.service;
|
||||||
|
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.LessonPlansDO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface LessonPlansService {
|
||||||
|
void generateLessonPlans(Integer studentId, Integer unitId);
|
||||||
|
|
||||||
|
List<LessonPlansDO> findLessonPlans(List<Integer> ids);
|
||||||
|
|
||||||
|
LessonPlansDO findLessonPlanById(Integer id);
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.yinlihupo.enlish.service.service;
|
||||||
|
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.StudentLessonPlansDO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface StudentLessonPlansService {
|
||||||
|
|
||||||
|
List<StudentLessonPlansDO> findStudentLessonPlansDOList(Integer page, Integer size, String name);
|
||||||
|
|
||||||
|
Integer findStudentLessonPlanTotal();
|
||||||
|
|
||||||
|
int finishStudentLessonPlan(Integer studentId, Integer planId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.yinlihupo.enlish.service.service;
|
||||||
|
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.UnitDO;
|
||||||
|
import com.yinlihupo.enlish.service.model.vo.unit.AddUnitReqVO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface UnitService {
|
||||||
|
|
||||||
|
List<UnitDO> findUnitDOList(Integer page, Integer size);
|
||||||
|
|
||||||
|
Integer findUnitDOListCount();
|
||||||
|
|
||||||
|
void add(AddUnitReqVO addUnitReqVO);
|
||||||
|
|
||||||
|
void delete(Integer id);
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import com.yinlihupo.enlish.service.domain.dataobject.GradeDO;
|
|||||||
import com.yinlihupo.enlish.service.domain.mapper.ClassDOMapper;
|
import com.yinlihupo.enlish.service.domain.mapper.ClassDOMapper;
|
||||||
import com.yinlihupo.enlish.service.domain.mapper.GradeClassDOMapper;
|
import com.yinlihupo.enlish.service.domain.mapper.GradeClassDOMapper;
|
||||||
import com.yinlihupo.enlish.service.domain.mapper.GradeDOMapper;
|
import com.yinlihupo.enlish.service.domain.mapper.GradeDOMapper;
|
||||||
|
import com.yinlihupo.enlish.service.domain.mapper.StudentDOMapper;
|
||||||
import com.yinlihupo.enlish.service.service.ClassService;
|
import com.yinlihupo.enlish.service.service.ClassService;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -24,6 +25,8 @@ public class ClassServiceImpl implements ClassService {
|
|||||||
private GradeClassDOMapper gradeClassDOMapper;
|
private GradeClassDOMapper gradeClassDOMapper;
|
||||||
@Resource
|
@Resource
|
||||||
private GradeDOMapper gradeDOMapper;
|
private GradeDOMapper gradeDOMapper;
|
||||||
|
@Resource
|
||||||
|
private StudentDOMapper studentDOMapper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ClassDO findClassById(Integer id) {
|
public ClassDO findClassById(Integer id) {
|
||||||
@@ -82,6 +85,10 @@ public class ClassServiceImpl implements ClassService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteClass(Integer classId) {
|
public void deleteClass(Integer classId) {
|
||||||
|
int selectStudentCountByClassId = studentDOMapper.selectStudentCountByClassId(classId);
|
||||||
|
if (selectStudentCountByClassId > 0) {
|
||||||
|
throw new RuntimeException("该班级下有学生,请先删除该班级下的学生");
|
||||||
|
}
|
||||||
classDOMapper.delete(classId);
|
classDOMapper.delete(classId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,4 +144,9 @@ public class ExamWordsJudgeServiceImpl implements ExamWordsJudgeService {
|
|||||||
public ExamWordsJudgeResultDO getExamWordsJudgeResultDOById(Integer id) {
|
public ExamWordsJudgeResultDO getExamWordsJudgeResultDOById(Integer id) {
|
||||||
return examWordsJudgeResultDOMapper.selectDetailById(id);
|
return examWordsJudgeResultDOMapper.selectDetailById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ExamWordsJudgeResultDO> getStudentExamWordsResultList(Integer studentId) {
|
||||||
|
return examWordsJudgeResultDOMapper.selectByStudentId(studentId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,300 @@
|
|||||||
|
package com.yinlihupo.enlish.service.service.plan;
|
||||||
|
|
||||||
|
import com.yinlihupo.enlish.service.constant.LessonPlanConstant;
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.*;
|
||||||
|
import com.yinlihupo.enlish.service.domain.mapper.*;
|
||||||
|
import com.yinlihupo.enlish.service.service.LessonPlansService;
|
||||||
|
import com.yinlihupo.enlish.service.utils.DifyArticleClient;
|
||||||
|
import com.yinlihupo.enlish.service.utils.StringToPlanMapUtil;
|
||||||
|
import com.yinlihupo.framework.common.util.JsonUtils;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class LessonPlansServiceImpl implements LessonPlansService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private LessonPlansDOMapper lessonPlansDOMapper;
|
||||||
|
@Resource
|
||||||
|
private StudentLessonPlansDOMapper studentLessonPlansDOMapper;
|
||||||
|
@Resource
|
||||||
|
private VocabularyBankDOMapper vocabularyBankDOMapper;
|
||||||
|
@Resource
|
||||||
|
private UnitDOMapper unitDOMapper;
|
||||||
|
@Resource
|
||||||
|
private GradeUnitDOMapper gradeUnitDOMapper;
|
||||||
|
@Resource
|
||||||
|
private GradeDOMapper gradeDOMapper;
|
||||||
|
@Resource
|
||||||
|
private DifyArticleClient difyArticleClient;
|
||||||
|
|
||||||
|
@Value("${templates.plan.weekday}")
|
||||||
|
private String planWeekday;
|
||||||
|
@Value("${templates.plan.weekend}")
|
||||||
|
private String planWeekend;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void generateLessonPlans(Integer studentId, Integer unitId) {
|
||||||
|
List<VocabularyBankDO> vocabularyBankDOS = vocabularyBankDOMapper.selectVocabularyBankDOAllByUnitId(unitId);
|
||||||
|
UnitDO unitDO = unitDOMapper.selectByPrimaryKey(unitId);
|
||||||
|
GradeUnitDO gradeUnitDO = gradeUnitDOMapper.selectByUnitId(unitId);
|
||||||
|
GradeDO gradeDO = gradeDOMapper.selectById(gradeUnitDO.getGradeId());
|
||||||
|
|
||||||
|
// 补差词汇所用词汇的
|
||||||
|
List<VocabularyBankDO> vocabularyBankListStudentNotMaster = getVocabListRandom(vocabularyBankDOMapper
|
||||||
|
.selectVocabularyBankListStudentNotMaster(gradeUnitDO.getGradeId(), studentId), 50);
|
||||||
|
int gapSize = vocabularyBankListStudentNotMaster.size();
|
||||||
|
int countGap = gapSize / 5;
|
||||||
|
|
||||||
|
int syncSize = vocabularyBankDOS.size();
|
||||||
|
int countSync = syncSize / 5;
|
||||||
|
int checkTotal = 50;
|
||||||
|
List<List<VocabularyBankDO>> weeksSync = new ArrayList<>();
|
||||||
|
List<List<VocabularyBankDO>> weeksGap = new ArrayList<>();
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
List<VocabularyBankDO> syncVocabList = vocabularyBankDOS.subList(i * countSync, Math.min((i + 1) * countSync, syncSize));
|
||||||
|
List<VocabularyBankDO> gapVocabList = vocabularyBankListStudentNotMaster.subList(i * countGap, Math.min(i * countGap + countGap, gapSize));
|
||||||
|
weeksSync.add(syncVocabList);
|
||||||
|
weeksGap.add(gapVocabList);
|
||||||
|
List<VocabularyBankDO> reviewVocabList = new ArrayList<>();
|
||||||
|
List<VocabularyBankDO> checkList = new ArrayList<>();
|
||||||
|
// 艾宾浩斯遗忘曲线
|
||||||
|
switch (i) {
|
||||||
|
case 1 -> reviewVocabList.addAll(syncVocabList);
|
||||||
|
case 2 -> {
|
||||||
|
reviewVocabList.addAll(weeksSync.get(0));
|
||||||
|
reviewVocabList.addAll(weeksSync.get(1));
|
||||||
|
|
||||||
|
checkList.addAll(weeksSync.get(1));
|
||||||
|
checkList.addAll(weeksSync.get(1));
|
||||||
|
}
|
||||||
|
case 3 -> {
|
||||||
|
reviewVocabList.addAll(weeksSync.get(1));
|
||||||
|
reviewVocabList.addAll(weeksGap.get(2));
|
||||||
|
|
||||||
|
checkList.addAll(weeksSync.get(1));
|
||||||
|
checkList.addAll(weeksSync.get(2));
|
||||||
|
}
|
||||||
|
case 4 -> {
|
||||||
|
reviewVocabList.addAll(weeksSync.get(2));
|
||||||
|
reviewVocabList.addAll(weeksGap.get(3));
|
||||||
|
|
||||||
|
checkList.addAll(weeksSync.get(2));
|
||||||
|
checkList.addAll(weeksSync.get(3));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<VocabularyBankDO> checkWords = vocabularyBankDOMapper
|
||||||
|
.selectVocabularyBankListSelfCheck(gradeUnitDO.getGradeId(), studentId, checkList.stream().map(VocabularyBankDO::getId).toList(), Math.max(checkTotal - checkList.size(), 0));
|
||||||
|
checkList.addAll(checkWords);
|
||||||
|
|
||||||
|
Map<String, Object> lessonPlanMap = null;
|
||||||
|
try {
|
||||||
|
lessonPlanMap = generateWeekdayPlans(syncVocabList, gapVocabList, reviewVocabList, checkList, i + 1, gradeDO, unitDO, studentId);
|
||||||
|
LessonPlansDO lessonPlansDO = LessonPlansDO.builder()
|
||||||
|
.title(lessonPlanMap.get("title").toString())
|
||||||
|
.gradeId(gradeDO.getId().toString())
|
||||||
|
.unitId(unitDO.getId())
|
||||||
|
.createdAt(new Date())
|
||||||
|
.contentDetails(JsonUtils.toJsonString(lessonPlanMap))
|
||||||
|
.build();
|
||||||
|
lessonPlansDOMapper.insert(lessonPlansDO);
|
||||||
|
|
||||||
|
StudentLessonPlansDO studentLessonPlansDO = StudentLessonPlansDO.builder()
|
||||||
|
.studentId(studentId)
|
||||||
|
.planId(lessonPlansDO.getId())
|
||||||
|
.build();
|
||||||
|
studentLessonPlansDOMapper.insert(studentLessonPlansDO);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
log.info("生成第{}天计划成功", i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
int syncWeekender = syncSize / 2;
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
List<VocabularyBankDO> checkList = vocabularyBankDOS.subList(i * syncWeekender, Math.min((i + 1) * syncWeekender, syncSize));
|
||||||
|
Map<String, Object> map = generateWeekendPlans(checkList, i + 6, gradeDO, unitDO, studentId);
|
||||||
|
|
||||||
|
LessonPlansDO lessonPlansDO = LessonPlansDO.builder()
|
||||||
|
.title(map.get("title").toString())
|
||||||
|
.gradeId(gradeDO.getId().toString())
|
||||||
|
.unitId(unitDO.getId())
|
||||||
|
.createdAt(new Date())
|
||||||
|
.contentDetails(JsonUtils.toJsonString(map))
|
||||||
|
.build();
|
||||||
|
lessonPlansDOMapper.insert(lessonPlansDO);
|
||||||
|
|
||||||
|
StudentLessonPlansDO studentLessonPlansDO = StudentLessonPlansDO.builder()
|
||||||
|
.studentId(studentId)
|
||||||
|
.planId(lessonPlansDO.getId())
|
||||||
|
.build();
|
||||||
|
studentLessonPlansDOMapper.insert(studentLessonPlansDO);
|
||||||
|
log.info("生成第{}天计划成功", i + 6);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<LessonPlansDO> findLessonPlans(List<Integer> ids) {
|
||||||
|
return lessonPlansDOMapper.findLessonPlansByStudentId(ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LessonPlansDO findLessonPlanById(Integer id) {
|
||||||
|
return lessonPlansDOMapper.selectByLessonId(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Map<String, Object> generateWeekendPlans(List<VocabularyBankDO> checkList,
|
||||||
|
int day,
|
||||||
|
GradeDO gradeDO, UnitDO unitDO, Integer studentId) throws IOException {
|
||||||
|
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("title", "第" + day + "天" + "复习" + gradeDO.getTitle() + unitDO.getTitle() + studentId);
|
||||||
|
data.put("checkList", checkList);
|
||||||
|
// LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();
|
||||||
|
// Configure config = Configure.builder()
|
||||||
|
// .bind("checkList", policy)
|
||||||
|
// .build();
|
||||||
|
//
|
||||||
|
// XWPFTemplate template = XWPFTemplate.compile(planWeekend, config);
|
||||||
|
// template.render(data);
|
||||||
|
// template.writeAndClose(new FileOutputStream("C:\\project\\java\\enlish_edu\\enlish\\enlish-service\\src\\main\\resources\\tmp\\word" + "复习" + day + ".docx"));
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> generateWeekdayPlans(List<VocabularyBankDO> syncVocabList,
|
||||||
|
List<VocabularyBankDO> gapVocabList,
|
||||||
|
List<VocabularyBankDO> reviewVocabList,
|
||||||
|
List<VocabularyBankDO> checkList,
|
||||||
|
int day,
|
||||||
|
GradeDO gradeDO, UnitDO unitDO, Integer studentId) throws Exception {
|
||||||
|
String title = gradeDO.getTitle() + " " + unitDO.getTitle() + " " + "第" + day + "天" + studentId;
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("title", title);
|
||||||
|
data.put("syncVocabList", syncVocabList);
|
||||||
|
data.put("gapVocabList", gapVocabList);
|
||||||
|
data.put("reviewVocabList", reviewVocabList);
|
||||||
|
data.put("checkList", checkList);
|
||||||
|
data.put("checkListAns", checkList);
|
||||||
|
|
||||||
|
// 中译英
|
||||||
|
List<VocabularyBankDO> drillRound1 = new ArrayList<>(syncVocabList);
|
||||||
|
Collections.shuffle(drillRound1);
|
||||||
|
data.put("drillRound1", drillRound1);
|
||||||
|
List<VocabularyBankDO> drillRound2 = new ArrayList<>(syncVocabList);
|
||||||
|
Collections.shuffle(drillRound2);
|
||||||
|
data.put("drillRound2", drillRound2);
|
||||||
|
List<VocabularyBankDO> drillRound3 = new ArrayList<>(syncVocabList);
|
||||||
|
Collections.shuffle(drillRound3);
|
||||||
|
data.put("drillRound3", drillRound3);
|
||||||
|
|
||||||
|
// 英译中
|
||||||
|
List<VocabularyBankDO> mixedDrill = new ArrayList<>();
|
||||||
|
mixedDrill.addAll(syncVocabList);
|
||||||
|
mixedDrill.addAll(gapVocabList);
|
||||||
|
mixedDrill.addAll(reviewVocabList);
|
||||||
|
Collections.shuffle(mixedDrill);
|
||||||
|
data.put("mixedDrill", mixedDrill);
|
||||||
|
|
||||||
|
// 文章 A
|
||||||
|
log.info("生成文章 A 中文开始");
|
||||||
|
Map<String, String> mapA = getArticleStringMap(gradeDO, unitDO, studentId, syncVocabList);
|
||||||
|
log.info("生成文章 A 成功 {}", mapA);
|
||||||
|
data.put("articleATitle", mapA.get(LessonPlanConstant.TITLE));
|
||||||
|
data.put("articleApassage", mapA.get(LessonPlanConstant.PASSAGE));
|
||||||
|
data.put("articleAquiz", mapA.get(LessonPlanConstant.QUIZ));
|
||||||
|
data.put("articleAans", mapA.get(LessonPlanConstant.ANSWER_KEY_EXPLANATION));
|
||||||
|
data.put("articleAtran", mapA.get(LessonPlanConstant.FULL_TRANSLATION));
|
||||||
|
|
||||||
|
// 文章 B
|
||||||
|
log.info("生成文章 B 中文开始");
|
||||||
|
Map<String, String> mapB;
|
||||||
|
List<VocabularyBankDO> wordsArticleB = new ArrayList<>();
|
||||||
|
wordsArticleB.addAll(syncVocabList);
|
||||||
|
wordsArticleB.addAll(gapVocabList);
|
||||||
|
wordsArticleB.addAll(reviewVocabList);
|
||||||
|
mapB = getArticleStringMap(gradeDO, unitDO, studentId, wordsArticleB);
|
||||||
|
log.info("生成文章 B 成功 {}", mapB);
|
||||||
|
data.put("articleBTitle", mapB.get(LessonPlanConstant.TITLE));
|
||||||
|
data.put("articleBpassage", mapB.get(LessonPlanConstant.PASSAGE));
|
||||||
|
data.put("articleBquiz", mapB.get(LessonPlanConstant.QUIZ));
|
||||||
|
data.put("articleBans", mapB.get(LessonPlanConstant.ANSWER_KEY_EXPLANATION));
|
||||||
|
data.put("articleBtran", mapB.get(LessonPlanConstant.FULL_TRANSLATION));
|
||||||
|
|
||||||
|
// LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();
|
||||||
|
// Configure config = Configure.builder()
|
||||||
|
// .bind("syncVocabList", policy)
|
||||||
|
// .bind("gapVocabList", policy)
|
||||||
|
// .bind("reviewVocabList", policy)
|
||||||
|
// .bind("drillRound1", policy)
|
||||||
|
// .bind("drillRound2", policy)
|
||||||
|
// .bind("drillRound3", policy)
|
||||||
|
// .bind("mixedDrill", policy)
|
||||||
|
// .bind("checkList", policy)
|
||||||
|
// .bind("checkListAns", policy)
|
||||||
|
// .build();
|
||||||
|
//
|
||||||
|
// XWPFTemplate template = XWPFTemplate.compile(planWeekday, config);
|
||||||
|
// template.render(data);
|
||||||
|
// template.writeAndClose(new FileOutputStream("C:\\project\\java\\enlish_edu\\enlish\\enlish-service\\src\\main\\resources\\tmp\\word" + title + ".docx"));
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private Map<String, String> getArticleStringMap(GradeDO gradeDO, UnitDO unitDO, Integer studentId, List<VocabularyBankDO> words) throws Exception {
|
||||||
|
Map<String, String> map;
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
words.forEach(word -> sb.append(word.getWord()).append(","));
|
||||||
|
sb.deleteCharAt(sb.length() - 1);
|
||||||
|
String string = sb.toString();
|
||||||
|
int i = 0;
|
||||||
|
do {
|
||||||
|
log.info("第{}次生成文章中文开始", ++i);
|
||||||
|
String answer = difyArticleClient.sendChat(string, String.valueOf(studentId) + UUID.randomUUID(), null).getAnswer();
|
||||||
|
map = StringToPlanMapUtil.parseTextToMap(answer);
|
||||||
|
} while (map.get(LessonPlanConstant.TITLE) == null
|
||||||
|
|| map.get(LessonPlanConstant.PASSAGE) == null
|
||||||
|
|| map.get(LessonPlanConstant.QUIZ) == null
|
||||||
|
|| map.get(LessonPlanConstant.ANSWER_KEY_EXPLANATION) == null
|
||||||
|
|| map.get(LessonPlanConstant.FULL_TRANSLATION) == null
|
||||||
|
);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public List<VocabularyBankDO> getVocabListRandom(List<VocabularyBankDO> list, int count) {
|
||||||
|
|
||||||
|
List<VocabularyBankDO> randomResultList;
|
||||||
|
|
||||||
|
int listSize = list.size();
|
||||||
|
if (listSize <= count) {
|
||||||
|
randomResultList = new ArrayList<>(list);
|
||||||
|
} else {
|
||||||
|
List<VocabularyBankDO> tempList = new ArrayList<>(list);
|
||||||
|
Collections.shuffle(tempList); // 随机打乱列表顺序
|
||||||
|
randomResultList = new ArrayList<>(tempList.subList(0, count));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return randomResultList;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.yinlihupo.enlish.service.service.plan;
|
||||||
|
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.StudentLessonPlansDO;
|
||||||
|
import com.yinlihupo.enlish.service.domain.mapper.StudentLessonPlansDOMapper;
|
||||||
|
import com.yinlihupo.enlish.service.domain.mapper.VocabularyBankDOMapper;
|
||||||
|
import com.yinlihupo.enlish.service.domain.mapper.WordMasteryLogDOMapper;
|
||||||
|
import com.yinlihupo.enlish.service.service.StudentLessonPlansService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class StudentLessonPlansServiceImpl implements StudentLessonPlansService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private StudentLessonPlansDOMapper studentLessonPlansDOMapper;
|
||||||
|
@Resource
|
||||||
|
private WordMasteryLogDOMapper wordMasteryLogDOMapper;
|
||||||
|
@Resource
|
||||||
|
private VocabularyBankDOMapper vocabularyBankDOMapper;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<StudentLessonPlansDO> findStudentLessonPlansDOList(Integer page, Integer size, String name) {
|
||||||
|
log.info("查询学生学案");
|
||||||
|
if (name.isEmpty()) {
|
||||||
|
return studentLessonPlansDOMapper.selectStudentLessonPlanList((page - 1) * size, size, null);
|
||||||
|
}
|
||||||
|
return studentLessonPlansDOMapper.selectStudentLessonPlanList((page - 1) * size, size, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer findStudentLessonPlanTotal() {
|
||||||
|
return studentLessonPlansDOMapper.selectStudentPlanTotal();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int finishStudentLessonPlan(Integer studentId, Integer planId) {
|
||||||
|
int wordStrengthCount = wordMasteryLogDOMapper.selectStudentStrengthCount(studentId);
|
||||||
|
Integer wordTotal = vocabularyBankDOMapper.selectWordTotal();
|
||||||
|
|
||||||
|
return studentLessonPlansDOMapper.finfishStudentPlan(studentId, planId, wordStrengthCount, (double) wordStrengthCount / wordTotal);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.yinlihupo.enlish.service.service.unit;
|
||||||
|
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.GradeUnitDO;
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.UnitDO;
|
||||||
|
import com.yinlihupo.enlish.service.domain.mapper.GradeUnitDOMapper;
|
||||||
|
import com.yinlihupo.enlish.service.domain.mapper.UnitDOMapper;
|
||||||
|
import com.yinlihupo.enlish.service.model.vo.unit.AddUnitReqVO;
|
||||||
|
import com.yinlihupo.enlish.service.service.UnitService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class UnitServiceImpl implements UnitService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private UnitDOMapper unitDOMapper;
|
||||||
|
@Resource
|
||||||
|
private GradeUnitDOMapper gradeUnitDOMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UnitDO> findUnitDOList(Integer page, Integer size) {
|
||||||
|
return unitDOMapper.selectUnitDOList((page - 1) * size, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer findUnitDOListCount() {
|
||||||
|
return unitDOMapper.selectUnitDOListCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(AddUnitReqVO addUnitReqVO) {
|
||||||
|
|
||||||
|
UnitDO unitDO = UnitDO.builder()
|
||||||
|
.title(addUnitReqVO.getTitle())
|
||||||
|
.createAt(LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
unitDOMapper.insertSelective(unitDO);
|
||||||
|
|
||||||
|
Integer gradeId = addUnitReqVO.getGradeId();
|
||||||
|
gradeUnitDOMapper.insert(GradeUnitDO.builder().gradeId(gradeId).unitId(unitDO.getId()).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delete(Integer id) {
|
||||||
|
unitDOMapper.deleteByPrimaryKey(id);
|
||||||
|
gradeUnitDOMapper.deleteByUnitId(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package com.yinlihupo.enlish.service.utils; // 修改为你的包名
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.ToString;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class DifyArticleClient {
|
||||||
|
|
||||||
|
@Value("${ai.key}")
|
||||||
|
private String apiKey;
|
||||||
|
@Value("${ai.url}")
|
||||||
|
private String baseUrl;
|
||||||
|
private final HttpClient httpClient;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
// 构造函数
|
||||||
|
public DifyArticleClient() {
|
||||||
|
|
||||||
|
this.httpClient = HttpClient.newBuilder()
|
||||||
|
.connectTimeout(Duration.ofSeconds(10)) // 连接超时
|
||||||
|
.build();
|
||||||
|
this.objectMapper = new ObjectMapper();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送对话请求 (阻塞模式)
|
||||||
|
*
|
||||||
|
* @param query 用户的问题
|
||||||
|
* @param userId 用户唯一标识
|
||||||
|
* @param conversationId 会话ID (首次传 null 或 空字符串)
|
||||||
|
* @return DifyResponse 包含回复内容和新的 conversationId
|
||||||
|
*/
|
||||||
|
public DifyResponse sendChat(String query, String userId, String conversationId) throws Exception {
|
||||||
|
String endpoint = this.baseUrl;
|
||||||
|
|
||||||
|
// 1. 构建请求体对象
|
||||||
|
ChatRequest payload = new ChatRequest();
|
||||||
|
payload.setQuery(query);
|
||||||
|
payload.setUser(userId);
|
||||||
|
payload.setResponseMode("blocking"); // 使用阻塞模式,一次性返回
|
||||||
|
// 如果有 conversationId,带上它以保持上下文
|
||||||
|
if (conversationId != null && !conversationId.isEmpty()) {
|
||||||
|
payload.setConversationId(conversationId);
|
||||||
|
}
|
||||||
|
// 如果你的 Dify 应用没有定义变量,inputs 传空 Map 即可,但字段必须存在
|
||||||
|
payload.setInputs(new HashMap<>());
|
||||||
|
|
||||||
|
// 2. 序列化为 JSON 字符串
|
||||||
|
String jsonBody = objectMapper.writeValueAsString(payload);
|
||||||
|
|
||||||
|
// 3. 构建 HTTP 请求
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(endpoint))
|
||||||
|
.header("Authorization", "Bearer " + this.apiKey)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
|
||||||
|
.timeout(Duration.ofSeconds(30)) // 读取超时
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 4. 发送请求
|
||||||
|
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
|
||||||
|
// 5. 检查状态码
|
||||||
|
if (response.statusCode() != 200) {
|
||||||
|
throw new RuntimeException("Dify 请求失败: HTTP " + response.statusCode() + " | Body: " + response.body());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 反序列化响应体
|
||||||
|
return objectMapper.readValue(response.body(), DifyResponse.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================= 内部类:DTO (数据传输对象) =================
|
||||||
|
|
||||||
|
// 请求体结构
|
||||||
|
@Data
|
||||||
|
@ToString
|
||||||
|
public static class ChatRequest {
|
||||||
|
private Map<String, Object> inputs;
|
||||||
|
private String query;
|
||||||
|
@JsonProperty("response_mode")
|
||||||
|
private String responseMode;
|
||||||
|
@JsonProperty("conversation_id")
|
||||||
|
private String conversationId;
|
||||||
|
private String user;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应体结构
|
||||||
|
// ignoreUnknown = true 非常重要:Dify 返回很多元数据,我们只映射需要的字段,防止报错
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
@Data
|
||||||
|
@ToString
|
||||||
|
public static class DifyResponse {
|
||||||
|
private String answer; // 核心回复内容
|
||||||
|
|
||||||
|
@JsonProperty("conversation_id")
|
||||||
|
private String conversationId; // 会话 ID
|
||||||
|
|
||||||
|
@JsonProperty("message_id")
|
||||||
|
private String messageId;
|
||||||
|
|
||||||
|
private long created_at;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package com.yinlihupo.enlish.service.utils;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Scanner;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class StringToPlanMapUtil {
|
||||||
|
|
||||||
|
public static Map<String, String> parseTextToMap(String text) {
|
||||||
|
// 使用 LinkedHashMap 保持插入顺序
|
||||||
|
Map<String, String> map = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
// 正则表达式:匹配行首的 **Key:** 格式
|
||||||
|
Pattern headerPattern = Pattern.compile("^\\s*\\*\\*(.+?):\\*\\*\\s*(.*)");
|
||||||
|
|
||||||
|
String currentKey = null;
|
||||||
|
StringBuilder currentValue = new StringBuilder();
|
||||||
|
|
||||||
|
try (Scanner scanner = new Scanner(text)) {
|
||||||
|
while (scanner.hasNextLine()) {
|
||||||
|
String line = scanner.nextLine();
|
||||||
|
Matcher matcher = headerPattern.matcher(line);
|
||||||
|
|
||||||
|
if (matcher.matches()) {
|
||||||
|
// 如果发现新的 Header
|
||||||
|
|
||||||
|
// 1. 保存上一个 Key-Value 对(如果存在)
|
||||||
|
if (currentKey != null) {
|
||||||
|
// 修改处:在存入 Map 前,调用 cleanMdBold 去除粗体语法
|
||||||
|
map.put(currentKey, cleanMdBold(currentValue.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 更新当前的 Key
|
||||||
|
currentKey = matcher.group(1).trim();
|
||||||
|
|
||||||
|
// 3. 重置 StringBuilder
|
||||||
|
currentValue = new StringBuilder();
|
||||||
|
String remainingText = matcher.group(2);
|
||||||
|
if (!remainingText.isEmpty()) {
|
||||||
|
currentValue.append(remainingText).append("\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果不是 Header 行,追加到当前 Value 中
|
||||||
|
if (currentKey != null) {
|
||||||
|
currentValue.append(line).append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 循环结束后,保存最后一个 Key-Value 对
|
||||||
|
if (currentKey != null) {
|
||||||
|
// 修改处:同样在最后存入时去除粗体语法
|
||||||
|
map.put(currentKey, cleanMdBold(currentValue.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 辅助方法:去除字符串两端的空白以及内部的 Markdown 粗体符号 (**)
|
||||||
|
*/
|
||||||
|
private static String cleanMdBold(String text) {
|
||||||
|
if (text == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
// 1. 替换掉所有的 ** 符号
|
||||||
|
// 2. 去除首尾空白
|
||||||
|
return text.replace("**", "").trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,8 @@ import java.util.zip.ZipOutputStream;
|
|||||||
public class WordExportUtil {
|
public class WordExportUtil {
|
||||||
|
|
||||||
private static final Configure config;
|
private static final Configure config;
|
||||||
|
private static final Configure configLessonPlanWeekday;
|
||||||
|
private static final Configure configLessonPlanWeekend;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();
|
LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();
|
||||||
@@ -23,6 +25,24 @@ public class WordExportUtil {
|
|||||||
.bind("words", policy)
|
.bind("words", policy)
|
||||||
.bind("answer", policy)
|
.bind("answer", policy)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
LoopRowTableRenderPolicy policyLessonPlanWeekday = new LoopRowTableRenderPolicy();
|
||||||
|
configLessonPlanWeekday = Configure.builder()
|
||||||
|
.bind("syncVocabList", policyLessonPlanWeekday)
|
||||||
|
.bind("gapVocabList", policyLessonPlanWeekday)
|
||||||
|
.bind("reviewVocabList", policyLessonPlanWeekday)
|
||||||
|
.bind("drillRound1", policyLessonPlanWeekday)
|
||||||
|
.bind("drillRound2", policyLessonPlanWeekday)
|
||||||
|
.bind("drillRound3", policyLessonPlanWeekday)
|
||||||
|
.bind("mixedDrill", policyLessonPlanWeekday)
|
||||||
|
.bind("checkList", policyLessonPlanWeekday)
|
||||||
|
.bind("checkListAns", policyLessonPlanWeekday)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
LoopRowTableRenderPolicy policyLessonPlan = new LoopRowTableRenderPolicy();
|
||||||
|
configLessonPlanWeekend = Configure.builder()
|
||||||
|
.bind("checkList", policyLessonPlan)
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,6 +67,28 @@ public class WordExportUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void generateLessonPlanDocx(Map<String, Object> map, String fileName, HttpServletResponse response, String templateWordPath, boolean isWeekday) throws IOException {
|
||||||
|
fileName = URLEncoder.encode(fileName + ".docx", StandardCharsets.UTF_8).replaceAll("\\+", "%20");
|
||||||
|
|
||||||
|
// 3. 设置响应头
|
||||||
|
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
|
||||||
|
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + fileName);
|
||||||
|
|
||||||
|
try (InputStream inputStream = new FileInputStream(templateWordPath)) {
|
||||||
|
XWPFTemplate template;
|
||||||
|
if (isWeekday) {
|
||||||
|
template = XWPFTemplate.compile(inputStream, configLessonPlanWeekday);
|
||||||
|
} else {
|
||||||
|
template = XWPFTemplate.compile(inputStream, configLessonPlanWeekend);
|
||||||
|
}
|
||||||
|
OutputStream out = response.getOutputStream();
|
||||||
|
template.render(map);
|
||||||
|
template.write(out);
|
||||||
|
template.close();
|
||||||
|
out.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 核心补充:批量渲染并打包为 ZIP
|
* 核心补充:批量渲染并打包为 ZIP
|
||||||
*/
|
*/
|
||||||
@@ -103,7 +145,7 @@ public class WordExportUtil {
|
|||||||
*/
|
*/
|
||||||
private static void generateExamWordsDocx(Map<String, Object> data, HttpServletResponse response, String templateWordPath) throws IOException {
|
private static void generateExamWordsDocx(Map<String, Object> data, HttpServletResponse response, String templateWordPath) throws IOException {
|
||||||
|
|
||||||
String fileName = URLEncoder.encode("摸底测试" + ".docx", StandardCharsets.UTF_8.toString()).replaceAll("\\+", "%20");
|
String fileName = URLEncoder.encode("摸底测试" + ".docx", StandardCharsets.UTF_8).replaceAll("\\+", "%20");
|
||||||
|
|
||||||
// 3. 设置响应头
|
// 3. 设置响应头
|
||||||
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
|
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
|
||||||
|
|||||||
@@ -25,5 +25,13 @@ templates:
|
|||||||
word: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\templates\assessment_v5.docx
|
word: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\templates\assessment_v5.docx
|
||||||
count: 100
|
count: 100
|
||||||
data: C:\project\tess
|
data: C:\project\tess
|
||||||
|
plan:
|
||||||
|
weekday: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\templates\tem_study_plan_v1.docx
|
||||||
|
weekend: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\templates\study_plan_review_v1.docx
|
||||||
|
plan_day: 7
|
||||||
tmp:
|
tmp:
|
||||||
png: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\tmp\png\
|
png: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\tmp\png\
|
||||||
|
|
||||||
|
ai:
|
||||||
|
key: app-loC6IrJpj4cS54MAYp73QtGl
|
||||||
|
url: https://chat.cosonggle.com/v1/chat-messages
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
targetProject="src/main/java"/>
|
targetProject="src/main/java"/>
|
||||||
|
|
||||||
<!-- 需要生成的表-实体类 -->
|
<!-- 需要生成的表-实体类 -->
|
||||||
<table tableName="grade_class" domainObjectName="GradeClassDO"
|
<table tableName="student_lesson_plans" domainObjectName="StudentLessonPlansDO"
|
||||||
enableCountByExample="false"
|
enableCountByExample="false"
|
||||||
enableUpdateByExample="false"
|
enableUpdateByExample="false"
|
||||||
enableDeleteByExample="false"
|
enableDeleteByExample="false"
|
||||||
|
|||||||
@@ -66,5 +66,12 @@
|
|||||||
from exam_words_judge_result
|
from exam_words_judge_result
|
||||||
where id = #{id}
|
where id = #{id}
|
||||||
</select>
|
</select>
|
||||||
|
<select id="selectByStudentId" resultMap="BaseResultMap">
|
||||||
|
select *
|
||||||
|
from exam_words_judge_result
|
||||||
|
where student_id = #{studentId}
|
||||||
|
order by start_date desc
|
||||||
|
limit 500;
|
||||||
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
@@ -13,5 +13,19 @@
|
|||||||
where grade_id = #{gradeId}
|
where grade_id = #{gradeId}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="selectByUnitId" resultMap="BaseResultMap">
|
||||||
|
select *
|
||||||
|
from grade_unit
|
||||||
|
where unit_id = #{unitId}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<insert id="insert">
|
||||||
|
insert into grade_unit (grade_id, unit_id)
|
||||||
|
values (#{gradeId}, #{unitId})
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<delete id="deleteByUnitId">
|
||||||
|
delete from grade_unit
|
||||||
|
where unit_id = #{unitId}
|
||||||
|
</delete>
|
||||||
</mapper>
|
</mapper>
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.yinlihupo.enlish.service.domain.mapper.LessonPlansDOMapper">
|
||||||
|
<resultMap id="BaseResultMap" type="com.yinlihupo.enlish.service.domain.dataobject.LessonPlansDO">
|
||||||
|
<id column="id" jdbcType="INTEGER" property="id" />
|
||||||
|
<result column="title" jdbcType="VARCHAR" property="title" />
|
||||||
|
<result column="grade_id" jdbcType="VARCHAR" property="gradeId" />
|
||||||
|
<result column="unit_id" jdbcType="INTEGER" property="unitId" />
|
||||||
|
<result column="created_at" jdbcType="TIMESTAMP" property="createdAt" />
|
||||||
|
</resultMap>
|
||||||
|
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.yinlihupo.enlish.service.domain.dataobject.LessonPlansDO">
|
||||||
|
<result column="content_details" jdbcType="LONGVARCHAR" property="contentDetails" />
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||||
|
insert into lesson_plans (title, grade_id, unit_id, content_details, created_at)
|
||||||
|
values (#{title}, #{gradeId}, #{unitId}, #{contentDetails}, now())
|
||||||
|
</insert>
|
||||||
|
<select id="selectById" resultMap="ResultMapWithBLOBs">
|
||||||
|
select *
|
||||||
|
from lesson_plans
|
||||||
|
where id = #{id}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findLessonPlansByStudentId" resultMap="BaseResultMap">
|
||||||
|
select *
|
||||||
|
from lesson_plans
|
||||||
|
<if test="ids != null">
|
||||||
|
where id in
|
||||||
|
<foreach item="id" collection="ids" separator="," open="(" close=")" index="">
|
||||||
|
#{id}
|
||||||
|
</foreach>
|
||||||
|
</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectByLessonId" resultMap="ResultMapWithBLOBs">
|
||||||
|
select *
|
||||||
|
from lesson_plans
|
||||||
|
where id = #{lessonId}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -61,4 +61,11 @@
|
|||||||
set is_deleted = 1
|
set is_deleted = 1
|
||||||
where id = #{id}
|
where id = #{id}
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
|
<select id="selectStudentCountByClassId" resultType="java.lang.Integer">
|
||||||
|
select count(1)
|
||||||
|
from student
|
||||||
|
where class_id = #{classId}
|
||||||
|
and is_deleted = 0
|
||||||
|
</select>
|
||||||
</mapper>
|
</mapper>
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.yinlihupo.enlish.service.domain.mapper.StudentLessonPlansDOMapper">
|
||||||
|
<resultMap id="BaseResultMap" type="com.yinlihupo.enlish.service.domain.dataobject.StudentLessonPlansDO">
|
||||||
|
<id column="id" jdbcType="INTEGER" property="id" />
|
||||||
|
<result column="student_id" jdbcType="INTEGER" property="studentId" />
|
||||||
|
<result column="plan_id" jdbcType="INTEGER" property="planId" />
|
||||||
|
<result column="start_time" jdbcType="TIMESTAMP" property="startTime" />
|
||||||
|
<result column="mastery_rat" jdbcType="DECIMAL" property="masteryRat" />
|
||||||
|
<result column="total_count" jdbcType="INTEGER" property="totalCount" />
|
||||||
|
<result column="is_finished" jdbcType="INTEGER" property="isFinished" />
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.yinlihupo.enlish.service.domain.dataobject.StudentLessonPlansDO">
|
||||||
|
<result column="memorized_words_json" jdbcType="LONGVARCHAR" property="memorizedWordsJson" />
|
||||||
|
<result column="unmemorized_words_json" jdbcType="LONGVARCHAR" property="unmemorizedWordsJson" />
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<insert id="insert">
|
||||||
|
insert into student_lesson_plans (student_id, plan_id, start_time, mastery_rat, total_count)
|
||||||
|
values (#{studentId,jdbcType=INTEGER}, #{planId,jdbcType=INTEGER},
|
||||||
|
now(), 0.0, 0
|
||||||
|
)
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<select id="selectStudentLessonPlanList" resultMap="BaseResultMap">
|
||||||
|
SELECT slp.*
|
||||||
|
FROM student_lesson_plans slp
|
||||||
|
LEFT JOIN student s ON slp.student_id = s.id
|
||||||
|
JOIN (
|
||||||
|
SELECT DISTINCT slp_in.student_id
|
||||||
|
FROM student_lesson_plans slp_in
|
||||||
|
LEFT JOIN student s_in ON slp_in.student_id = s_in.id
|
||||||
|
<if test="name != null and name != ''">
|
||||||
|
WHERE s_in.name LIKE CONCAT('%', #{name}, '%')
|
||||||
|
</if>
|
||||||
|
ORDER BY slp_in.student_id
|
||||||
|
LIMIT #{startIndex}, #{pageSize}
|
||||||
|
) AS temp ON slp.student_id = temp.student_id
|
||||||
|
where slp.is_finished = 0
|
||||||
|
ORDER BY slp.id
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectStudentPlanTotal">
|
||||||
|
SELECT count(DISTINCT student_id) AS total
|
||||||
|
FROM student_lesson_plans;
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<update id="finfishStudentPlan">
|
||||||
|
update student_lesson_plans
|
||||||
|
set is_finished = 1,
|
||||||
|
total_count = #{count},
|
||||||
|
mastery_rat = #{mastery}
|
||||||
|
where student_id = #{studentId}
|
||||||
|
and plan_id = #{planId}
|
||||||
|
</update>
|
||||||
|
</mapper>
|
||||||
@@ -91,4 +91,15 @@
|
|||||||
where title = #{title}
|
where title = #{title}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="selectUnitDOList" resultMap="BaseResultMap">
|
||||||
|
select *
|
||||||
|
from unit
|
||||||
|
limit #{startIndex}, #{pageSize}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectUnitDOListCount" resultType="java.lang.Integer">
|
||||||
|
select count(*)
|
||||||
|
from unit
|
||||||
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
<result column="definition" jdbcType="VARCHAR" property="definition" />
|
<result column="definition" jdbcType="VARCHAR" property="definition" />
|
||||||
<result column="pronunciation" jdbcType="VARCHAR" property="pronunciation" />
|
<result column="pronunciation" jdbcType="VARCHAR" property="pronunciation" />
|
||||||
<result column="unit_id" jdbcType="INTEGER" property="unitId" />
|
<result column="unit_id" jdbcType="INTEGER" property="unitId" />
|
||||||
|
<result column="pos" jdbcType="VARCHAR" property="pos" />
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
|
|
||||||
@@ -28,6 +29,9 @@
|
|||||||
<if test="unitId != null">
|
<if test="unitId != null">
|
||||||
unit_id,
|
unit_id,
|
||||||
</if>
|
</if>
|
||||||
|
<if test="pos != null">
|
||||||
|
pos,
|
||||||
|
</if>
|
||||||
</trim>
|
</trim>
|
||||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||||
<if test="id != null">
|
<if test="id != null">
|
||||||
@@ -45,6 +49,9 @@
|
|||||||
<if test="unitId != null">
|
<if test="unitId != null">
|
||||||
#{unitId,jdbcType=INTEGER},
|
#{unitId,jdbcType=INTEGER},
|
||||||
</if>
|
</if>
|
||||||
|
<if test="pos != null">
|
||||||
|
#{pos,jdbcType=VARCHAR},
|
||||||
|
</if>
|
||||||
</trim>
|
</trim>
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
@@ -69,4 +76,50 @@
|
|||||||
from vocabulary_bank
|
from vocabulary_bank
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="selectVocabularyBankDOAllByUnitId" resultMap="BaseResultMap">
|
||||||
|
select *
|
||||||
|
from vocabulary_bank
|
||||||
|
where unit_id = #{unitId}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectVocabularyBankListStudentNotMaster" resultMap="BaseResultMap">
|
||||||
|
<![CDATA[
|
||||||
|
select *
|
||||||
|
from vocabulary_bank
|
||||||
|
where unit_id in (
|
||||||
|
select unit_id
|
||||||
|
from grade_unit
|
||||||
|
where grade_id = #{gradeId}
|
||||||
|
)
|
||||||
|
and id in (
|
||||||
|
select word_id
|
||||||
|
from word_mastery_log
|
||||||
|
where memory_strength < 0
|
||||||
|
and student_id = #{studentId}
|
||||||
|
)
|
||||||
|
]]>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectVocabularyBankListSelfCheck" resultMap="BaseResultMap">
|
||||||
|
select *
|
||||||
|
from vocabulary_bank vb
|
||||||
|
inner join grade_unit gu on vb.unit_id = gu.unit_id
|
||||||
|
inner join word_mastery_log wml on vb.id = wml.word_id
|
||||||
|
where gu.grade_id = #{gradeId}
|
||||||
|
and wml.student_id = #{studentId}
|
||||||
|
<!-- 修复not in的语法错误 + 空集合防护 -->
|
||||||
|
<if test="ids != null and ids.size() > 0">
|
||||||
|
and vb.id not in (
|
||||||
|
<foreach item="id" collection="ids" separator=",">
|
||||||
|
#{id}
|
||||||
|
</foreach>
|
||||||
|
)
|
||||||
|
</if>
|
||||||
|
limit #{wordCount}
|
||||||
|
</select>
|
||||||
|
<select id="selectWordTotal" resultType="java.lang.Integer">
|
||||||
|
select count(*)
|
||||||
|
from vocabulary_bank
|
||||||
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
@@ -22,6 +22,12 @@
|
|||||||
order by memory_strength desc
|
order by memory_strength desc
|
||||||
limit #{limit}
|
limit #{limit}
|
||||||
</select>
|
</select>
|
||||||
|
<select id="selectStudentStrengthCount" resultType="java.lang.Integer">
|
||||||
|
select count(*)
|
||||||
|
from word_mastery_log
|
||||||
|
where student_id = #{studentId}
|
||||||
|
and memory_strength > 0
|
||||||
|
</select>
|
||||||
|
|
||||||
<insert id="batchInsertInitialization">
|
<insert id="batchInsertInitialization">
|
||||||
insert into word_mastery_log (student_id, word_id, update_time)
|
insert into word_mastery_log (student_id, word_id, update_time)
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,50 @@
|
|||||||
|
package com.yinlihupo.enlish.service.ai;
|
||||||
|
|
||||||
|
import com.yinlihupo.enlish.service.utils.DifyArticleClient;
|
||||||
|
import com.yinlihupo.enlish.service.utils.StringToPlanMapUtil;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
public class AITest {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private DifyArticleClient client;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test1() throws IOException {
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 2. 第一轮对话 (没有 conversation_id)
|
||||||
|
System.out.println("--- Round 1 ---");
|
||||||
|
String userId = "user-1001";
|
||||||
|
DifyArticleClient.DifyResponse response1 = client.sendChat("ruler, pencil, eraser, crayon, bag, pen, book, red, green, yellow, blue, face, ear, eye, nose, mouth, duck, pig, cat, bear, dog, elephant, monkey, bird, tiger, panda, bread, juice, egg, milk", userId, null);
|
||||||
|
|
||||||
|
//System.out.println("AI 回复: " + response1.getAnswer());
|
||||||
|
System.out.println("当前会话ID: " + response1.getConversationId());
|
||||||
|
|
||||||
|
// // 3. 第二轮对话 (传入上一轮的 conversation_id 以保持记忆)
|
||||||
|
// System.out.println("\n--- Round 2 ---");
|
||||||
|
// // 注意这里传入了 response1.getConversationId()
|
||||||
|
// DifyClient.DifyResponse response2 = client.sendChat("我刚才说了我叫什么?", userId, response1.getConversationId());
|
||||||
|
//
|
||||||
|
// System.out.println("AI 回复: " + response2.getAnswer());
|
||||||
|
|
||||||
|
System.out.println("\n--- Round 2 ---");
|
||||||
|
Map<String, String> stringStringMap = StringToPlanMapUtil.parseTextToMap(response1.getAnswer());
|
||||||
|
System.out.println(stringStringMap.get("Title"));
|
||||||
|
System.out.println(stringStringMap.get("The Passage"));
|
||||||
|
System.out.println(stringStringMap.get("Quiz"));
|
||||||
|
System.out.println(stringStringMap.get("Answer Key & Explanation"));
|
||||||
|
System.out.println(stringStringMap.get("Full Translation"));
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.yinlihupo.enlish.service.mapper;
|
||||||
|
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.LessonPlansDO;
|
||||||
|
import com.yinlihupo.enlish.service.domain.mapper.LessonPlansDOMapper;
|
||||||
|
import com.yinlihupo.framework.common.util.JsonUtils;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
public class PlanTest {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private LessonPlansDOMapper lessonPlansDOMapper;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() throws Exception {
|
||||||
|
LessonPlansDO lessonPlansDO = lessonPlansDOMapper.selectById(58);
|
||||||
|
Map<String, Object> stringObjectMap = JsonUtils.parseMap(lessonPlansDO.getContentDetails(), String.class, Object.class);
|
||||||
|
for (Map.Entry<String, Object> entry : stringObjectMap.entrySet()) {
|
||||||
|
System.out.println(entry.getKey());
|
||||||
|
System.out.println(entry.getValue());
|
||||||
|
System.out.println("------------------------");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.yinlihupo.enlish.service.service.plan;
|
||||||
|
|
||||||
|
import com.yinlihupo.enlish.service.service.LessonPlansService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
public class PlanTest {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private LessonPlansService lessonPlansService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
lessonPlansService.generateLessonPlans(2, 146);
|
||||||
|
}
|
||||||
|
}
|
||||||
26
enlish-vue/package-lock.json
generated
26
enlish-vue/package-lock.json
generated
@@ -9,6 +9,7 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
|
"echarts": "^6.0.0",
|
||||||
"element-plus": "^2.12.0",
|
"element-plus": "^2.12.0",
|
||||||
"flowbite": "^1.8.1",
|
"flowbite": "^1.8.1",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
@@ -1647,6 +1648,16 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/echarts": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/echarts/-/echarts-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "2.3.0",
|
||||||
|
"zrender": "6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.267",
|
"version": "1.5.267",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
|
||||||
@@ -3088,6 +3099,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/tslib": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
"node_modules/ufo": {
|
"node_modules/ufo": {
|
||||||
"version": "1.6.1",
|
"version": "1.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz",
|
||||||
@@ -3420,6 +3437,15 @@
|
|||||||
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
|
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/zrender": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/zrender/-/zrender-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "2.3.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
|
"echarts": "^6.0.0",
|
||||||
"element-plus": "^2.12.0",
|
"element-plus": "^2.12.0",
|
||||||
"flowbite": "^1.8.1",
|
"flowbite": "^1.8.1",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
|
|||||||
@@ -55,6 +55,12 @@ export function generateExamWords(data) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getStudentExamHistory(studentId) {
|
||||||
|
return axios.post('/exam/words/student/history', {
|
||||||
|
studentId: studentId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const resolveBlob = (res, fileName) => {
|
const resolveBlob = (res, fileName) => {
|
||||||
// 创建 Blob 对象,可以指定 type,也可以让浏览器自动推断
|
// 创建 Blob 对象,可以指定 type,也可以让浏览器自动推断
|
||||||
const blob = new Blob([res], { type: 'application/octet-stream' });
|
const blob = new Blob([res], { type: 'application/octet-stream' });
|
||||||
|
|||||||
69
enlish-vue/src/api/plan.js
Normal file
69
enlish-vue/src/api/plan.js
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import axios from "@/axios";
|
||||||
|
|
||||||
|
export function generateLessonPlan(studentId, unitId) {
|
||||||
|
return axios.post('/plan/generate', {
|
||||||
|
studentId: studentId,
|
||||||
|
unitId: unitId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function downloadLessonPlan(data) {
|
||||||
|
return axios.post('/plan/download', data, {
|
||||||
|
// 1. 重要:必须指定响应类型为 blob,否则下载的文件会损坏(乱码)
|
||||||
|
responseType: 'blob',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; application/octet-stream' // 根据需要调整
|
||||||
|
}
|
||||||
|
}).then(response => {
|
||||||
|
// 2. 提取文件名 (处理后端设置的 Content-Disposition)
|
||||||
|
// 后端示例: header("Content-Disposition", "attachment; filename*=UTF-8''" + fileName)
|
||||||
|
let fileName = 'download.zip'; // 默认兜底文件名
|
||||||
|
|
||||||
|
const contentDisposition = response.headers['content-disposition'];
|
||||||
|
if (contentDisposition) {
|
||||||
|
// 正则提取 filename*=utf-8''xxx.zip 或 filename="xxx.zip"
|
||||||
|
const fileNameMatch = contentDisposition.match(/filename\*?=['"]?(?:UTF-\d['"]*)?([^;\r\n"']*)['"]?;?/);
|
||||||
|
if (fileNameMatch && fileNameMatch[1]) {
|
||||||
|
// 后端如果用了 URLEncoder,这里需要 decode
|
||||||
|
fileName = decodeURIComponent(fileNameMatch[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 开始下载流程
|
||||||
|
resolveBlob(response.data, fileName);
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('下载失败', error);
|
||||||
|
showMessage('下载失败' + error, 'error');
|
||||||
|
// 注意:如果后端报错返回 JSON,因为 responseType 是 blob,
|
||||||
|
// 这里看到的 error.response.data 也是 blob,需要转回文本才能看到错误信息
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsText(error.response.data);
|
||||||
|
reader.onload = () => {
|
||||||
|
console.log(JSON.parse(reader.result)); // 打印后端实际报错
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveBlob = (res, fileName) => {
|
||||||
|
// 创建 Blob 对象,可以指定 type,也可以让浏览器自动推断
|
||||||
|
const blob = new Blob([res], { type: 'application/octet-stream' });
|
||||||
|
|
||||||
|
// 兼容 IE/Edge (虽然现在很少用了)
|
||||||
|
if (window.navigator.msSaveOrOpenBlob) {
|
||||||
|
navigator.msSaveBlob(blob, fileName);
|
||||||
|
} else {
|
||||||
|
// 创建一个临时的 URL 指向 Blob
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = window.URL.createObjectURL(blob);
|
||||||
|
link.download = fileName;
|
||||||
|
|
||||||
|
// 触发点击
|
||||||
|
link.style.display = 'none';
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
|
||||||
|
// 清理资源
|
||||||
|
document.body.removeChild(link);
|
||||||
|
window.URL.revokeObjectURL(link.href);
|
||||||
|
}
|
||||||
|
};
|
||||||
16
enlish-vue/src/api/studentLessonPlans.js
Normal file
16
enlish-vue/src/api/studentLessonPlans.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import axios from "@/axios";
|
||||||
|
|
||||||
|
export function findStudentLessonPlans(page, size, name) {
|
||||||
|
return axios.post('/studentLessonPlans/list', {
|
||||||
|
page: page,
|
||||||
|
size: size,
|
||||||
|
name: name ?? ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function finishLessonPlan(studentId, planId) {
|
||||||
|
return axios.post('/studentLessonPlans/finish', {
|
||||||
|
studentId: studentId,
|
||||||
|
planId: planId
|
||||||
|
})
|
||||||
|
}
|
||||||
21
enlish-vue/src/api/unit.js
Normal file
21
enlish-vue/src/api/unit.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import axios from "@/axios";
|
||||||
|
|
||||||
|
export function getUnitList(page, size) {
|
||||||
|
return axios.post('/unit/list', {
|
||||||
|
page: page,
|
||||||
|
size: size
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addUnit(name, gradeId) {
|
||||||
|
return axios.post('/unit/add', {
|
||||||
|
title: name,
|
||||||
|
gradeId: gradeId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteUnit(id) {
|
||||||
|
return axios.post('/unit/delete', {
|
||||||
|
id: id
|
||||||
|
})
|
||||||
|
}
|
||||||
86
enlish-vue/src/layouts/components/AddUnitDialog.vue
Normal file
86
enlish-vue/src/layouts/components/AddUnitDialog.vue
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog v-model="visible" title="新增单元" width="480px" :close-on-click-modal="false">
|
||||||
|
<div class="space-y-4" v-loading="loading">
|
||||||
|
<el-form label-width="80px">
|
||||||
|
<el-form-item label="单元名称">
|
||||||
|
<el-input v-model="name" placeholder="请输入单元名称,如:Unit 1" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="年级">
|
||||||
|
<el-select v-model="gradeId" placeholder="请选择年级" style="width: 260px">
|
||||||
|
<el-option v-for="g in gradeOptions" :key="g.id" :label="g.title" :value="g.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex justify-end gap-2">
|
||||||
|
<el-button @click="visible = false">取消</el-button>
|
||||||
|
<el-button type="primary" :disabled="!canSubmit" @click="handleSubmit">确定</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, watch } from 'vue'
|
||||||
|
import { getGradeList } from '@/api/grade'
|
||||||
|
import { addUnit } from '@/api/unit'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: { type: Boolean, default: false },
|
||||||
|
defaultGradeId: { type: [Number, String], default: null }
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['update:modelValue', 'success'])
|
||||||
|
|
||||||
|
const visible = computed({
|
||||||
|
get: () => props.modelValue,
|
||||||
|
set: (val) => emit('update:modelValue', val)
|
||||||
|
})
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const name = ref('')
|
||||||
|
const gradeId = ref(null)
|
||||||
|
const gradeOptions = ref([])
|
||||||
|
|
||||||
|
const canSubmit = computed(() => name.value.trim().length > 0 && !!gradeId.value)
|
||||||
|
|
||||||
|
async function fetchGrades() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await getGradeList(1, 100)
|
||||||
|
const d = res?.data
|
||||||
|
gradeOptions.value = Array.isArray(d?.data) ? d.data : []
|
||||||
|
if (props.defaultGradeId && !gradeId.value) {
|
||||||
|
gradeId.value = Number(props.defaultGradeId)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSubmit() {
|
||||||
|
if (!canSubmit.value) return
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
await addUnit(name.value.trim(), Number(gradeId.value))
|
||||||
|
ElMessage.success('新增单元成功')
|
||||||
|
emit('success')
|
||||||
|
visible.value = false
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(v) => {
|
||||||
|
if (v) {
|
||||||
|
name.value = ''
|
||||||
|
gradeId.value = props.defaultGradeId ? Number(props.defaultGradeId) : null
|
||||||
|
fetchGrades()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
@@ -45,22 +45,11 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="#"
|
<router-link
|
||||||
|
to="/learningplan"
|
||||||
class="block py-2 pr-4 pl-3 text-gray-700 border-b border-gray-100 hover:bg-gray-50 lg:hover:bg-transparent lg:border-0 lg:hover:text-primary-700 lg:p-0 dark:text-gray-400 lg:dark:hover:text-white dark:hover:bg-gray-700 dark:hover:text-white lg:dark:hover:bg-transparent dark:border-gray-700">
|
class="block py-2 pr-4 pl-3 text-gray-700 border-b border-gray-100 hover:bg-gray-50 lg:hover:bg-transparent lg:border-0 lg:hover:text-primary-700 lg:p-0 dark:text-gray-400 lg:dark:hover:text-white dark:hover:bg-gray-700 dark:hover:text-white lg:dark:hover:bg-transparent dark:border-gray-700">
|
||||||
Marketplace
|
学案
|
||||||
</a>
|
</router-link>
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#"
|
|
||||||
class="block py-2 pr-4 pl-3 text-gray-700 border-b border-gray-100 hover:bg-gray-50 lg:hover:bg-transparent lg:border-0 lg:hover:text-primary-700 lg:p-0 dark:text-gray-400 lg:dark:hover:text-white dark:hover:bg-gray-700 dark:hover:text-white lg:dark:hover:bg-transparent dark:border-gray-700">
|
|
||||||
Features
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#"
|
|
||||||
class="block py-2 pr-4 pl-3 text-gray-700 border-b border-gray-100 hover:bg-gray-50 lg:hover:bg-transparent lg:border-0 lg:hover:text-primary-700 lg:p-0 dark:text-gray-400 lg:dark:hover:text-white dark:hover:bg-gray-700 dark:hover:text-white lg:dark:hover:bg-transparent dark:border-gray-700">
|
|
||||||
Team
|
|
||||||
</a>
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<router-link
|
<router-link
|
||||||
|
|||||||
88
enlish-vue/src/layouts/components/LessonPlanDialog.vue
Normal file
88
enlish-vue/src/layouts/components/LessonPlanDialog.vue
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog v-model="visible" title="生成学案" width="520px" :close-on-click-modal="false">
|
||||||
|
<div class="space-y-4" v-loading="loading">
|
||||||
|
<el-form label-width="80px">
|
||||||
|
<el-form-item label="单元">
|
||||||
|
<el-select v-model="unitId" placeholder="请选择单元" style="width: 300px" filterable>
|
||||||
|
<el-option
|
||||||
|
v-for="u in unitOptions"
|
||||||
|
:key="u.id"
|
||||||
|
:label="u.title"
|
||||||
|
:value="u.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<div class="text-sm text-gray-500">
|
||||||
|
学生ID:{{ studentId }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex justify-end gap-2">
|
||||||
|
<el-button @click="visible = false">取消</el-button>
|
||||||
|
<el-button type="primary" :disabled="!unitId" @click="handleGenerate">生成</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, watch } from 'vue'
|
||||||
|
import { getUnitList } from '@/api/unit'
|
||||||
|
import { generateLessonPlan } from '@/api/plan'
|
||||||
|
import { showMessage } from '../../composables/util'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: { type: Boolean, default: false },
|
||||||
|
studentId: { type: [Number, String], required: true }
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
const visible = computed({
|
||||||
|
get: () => props.modelValue,
|
||||||
|
set: (val) => emit('update:modelValue', val)
|
||||||
|
})
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const unitId = ref(null)
|
||||||
|
const unitOptions = ref([])
|
||||||
|
|
||||||
|
async function fetchUnits() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await getUnitList(1, 100)
|
||||||
|
const d = res?.data
|
||||||
|
if (d.success) {
|
||||||
|
unitOptions.value = Array.isArray(d?.data) ? d.data : []
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleGenerate() {
|
||||||
|
if (!unitId.value || !props.studentId) return
|
||||||
|
const res = await generateLessonPlan(Number(props.studentId), Number(unitId.value))
|
||||||
|
const d = res?.data
|
||||||
|
if (d.success) {
|
||||||
|
ElMessage.success('生成学案任务已提交,请等待十分钟')
|
||||||
|
visible.value = false
|
||||||
|
} else {
|
||||||
|
showMessage(d.message || '生成学案失败,请联系管理员', 'error')
|
||||||
|
visible.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(v) => {
|
||||||
|
if (v) {
|
||||||
|
unitId.value = null
|
||||||
|
fetchUnits()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
155
enlish-vue/src/layouts/components/student/ExamHistoryChart.vue
Normal file
155
enlish-vue/src/layouts/components/student/ExamHistoryChart.vue
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
<template>
|
||||||
|
<div style="width: 100%; height: 260px;">
|
||||||
|
<div ref="elRef" style="width: 100%; height: 100%;"></div>
|
||||||
|
</div>
|
||||||
|
<ExamWordsDetailCard v-model="detailVisible" :id="detailId" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { defineProps, ref, onMounted, onUnmounted, watch } from 'vue'
|
||||||
|
import ExamWordsDetailCard from '@/layouts/components/ExamWordsDetailCard.vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const elRef = ref(null)
|
||||||
|
let chart = null
|
||||||
|
let echartsLib = null
|
||||||
|
const detailVisible = ref(false)
|
||||||
|
const detailId = ref(null)
|
||||||
|
|
||||||
|
function sortData(arr) {
|
||||||
|
return Array.isArray(arr)
|
||||||
|
? arr.slice().sort((a, b) => new Date(a.startDate).getTime() - new Date(b.startDate).getTime())
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
|
||||||
|
function toSource(arr) {
|
||||||
|
return sortData(arr).map(it => ({
|
||||||
|
startDate: it.startDate,
|
||||||
|
correctWordCount: Number(it.correctWordCount) || 0,
|
||||||
|
wrongWordCount: Number(it.wrongWordCount) || 0,
|
||||||
|
examWordsId: it.examWordsId ?? null,
|
||||||
|
id: it.id ?? null
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildOption(source) {
|
||||||
|
return {
|
||||||
|
dataset: [
|
||||||
|
{
|
||||||
|
id: 'dataset_history',
|
||||||
|
source: source
|
||||||
|
}
|
||||||
|
],
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis'
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
top: 8
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
nameLocation: 'middle'
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
name: 'Word Count',
|
||||||
|
min: 0
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'line',
|
||||||
|
datasetId: 'dataset_history',
|
||||||
|
showSymbol: false,
|
||||||
|
name: '正确词数',
|
||||||
|
encode: {
|
||||||
|
x: 'startDate',
|
||||||
|
y: 'correctWordCount',
|
||||||
|
itemName: 'startDate',
|
||||||
|
tooltip: ['correctWordCount']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'line',
|
||||||
|
datasetId: 'dataset_history',
|
||||||
|
showSymbol: false,
|
||||||
|
name: '错误词数',
|
||||||
|
encode: {
|
||||||
|
x: 'startDate',
|
||||||
|
y: 'wrongWordCount',
|
||||||
|
itemName: 'startDate',
|
||||||
|
tooltip: ['wrongWordCount']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resize() {
|
||||||
|
if (chart) chart.resize()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureEcharts() {
|
||||||
|
try {
|
||||||
|
const mod = await import('echarts')
|
||||||
|
echartsLib = mod
|
||||||
|
} catch (e) {
|
||||||
|
if (window.echarts) {
|
||||||
|
echartsLib = window.echarts
|
||||||
|
} else {
|
||||||
|
await new Promise(resolve => {
|
||||||
|
const s = document.createElement('script')
|
||||||
|
s.src = 'https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js'
|
||||||
|
s.onload = resolve
|
||||||
|
document.head.appendChild(s)
|
||||||
|
})
|
||||||
|
echartsLib = window.echarts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function render() {
|
||||||
|
if (!elRef.value) return
|
||||||
|
await ensureEcharts()
|
||||||
|
if (!echartsLib) return
|
||||||
|
if (!chart) {
|
||||||
|
chart = echartsLib.init(elRef.value)
|
||||||
|
chart.on('click', handlePointClick)
|
||||||
|
window.addEventListener('resize', resize)
|
||||||
|
}
|
||||||
|
const source = toSource(props.data)
|
||||||
|
const option = buildOption(source)
|
||||||
|
chart.setOption(option)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePointClick(params) {
|
||||||
|
const row = params?.data
|
||||||
|
console.log(row)
|
||||||
|
const id = row?.id ?? null
|
||||||
|
if (id !== null && id !== undefined) {
|
||||||
|
detailId.value = id
|
||||||
|
detailVisible.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
render()
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', resize)
|
||||||
|
if (chart) {
|
||||||
|
chart.off('click', handlePointClick)
|
||||||
|
chart.dispose()
|
||||||
|
chart = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.data, () => {
|
||||||
|
render()
|
||||||
|
}, { deep: true })
|
||||||
|
</script>
|
||||||
164
enlish-vue/src/pages/LearningPlan.vue
Normal file
164
enlish-vue/src/pages/LearningPlan.vue
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
<template>
|
||||||
|
<div class="common-layout">
|
||||||
|
<el-container>
|
||||||
|
<el-header>
|
||||||
|
<Header></Header>
|
||||||
|
</el-header>
|
||||||
|
|
||||||
|
<el-main class="p-4">
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||||
|
<div class="text-lg font-semibold mb-4">学案查询</div>
|
||||||
|
<div class="flex flex-wrap items-center gap-3 mb-4">
|
||||||
|
<el-input v-model="searchName" placeholder="按姓名查询" clearable style="max-width: 220px" />
|
||||||
|
<el-button type="primary" @click="onSearch">查询</el-button>
|
||||||
|
<el-button @click="onReset">重置</el-button>
|
||||||
|
</div>
|
||||||
|
<el-table ref="tableRef" :data="rows" border class="w-full" v-loading="loading" row-key="id">
|
||||||
|
<el-table-column type="expand">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="p-3">
|
||||||
|
<div class="text-sm font-semibold mb-2">学案</div>
|
||||||
|
<el-table :data="row.plans || []" size="small" border>
|
||||||
|
<el-table-column prop="id" label="计划ID" width="100" />
|
||||||
|
<el-table-column prop="title" label="标题" min-width="280" />
|
||||||
|
<el-table-column label="状态" width="120">
|
||||||
|
<template #default="{ row: plan }">
|
||||||
|
<el-tag :type="plan.isFinished === 1 ? 'success' : 'info'" effect="plain">
|
||||||
|
{{ plan.isFinished === 1 ? '已完成' : '未完成' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="200" fixed="right">
|
||||||
|
<template #default="{ row: plan }">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
:loading="downloadingIds.includes(plan.id)"
|
||||||
|
@click="onDownload(plan)"
|
||||||
|
>下载</el-button>
|
||||||
|
<el-button
|
||||||
|
class="ml-2"
|
||||||
|
type="success"
|
||||||
|
size="small"
|
||||||
|
:disabled="plan.isFinished === 1"
|
||||||
|
:loading="finishingIds.includes(plan.id)"
|
||||||
|
@click="onFinish(row.id, plan.id, plan)"
|
||||||
|
>完成</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="id" label="学生ID" width="100" />
|
||||||
|
<el-table-column prop="name" label="姓名" min-width="120" />
|
||||||
|
<el-table-column prop="className" label="班级" min-width="120" />
|
||||||
|
<el-table-column prop="gradeName" label="年级" min-width="120" />
|
||||||
|
</el-table>
|
||||||
|
<div class="mt-4 flex justify-end">
|
||||||
|
<el-pagination background layout="prev, pager, next, sizes, total" :total="totalCount"
|
||||||
|
:page-size="pageSize" :current-page="pageNo" @current-change="handlePageChange"
|
||||||
|
@size-change="handleSizeChange" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-main>
|
||||||
|
|
||||||
|
</el-container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import Header from '@/layouts/components/Header.vue'
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { findStudentLessonPlans, finishLessonPlan } from '@/api/studentLessonPlans'
|
||||||
|
import { downloadLessonPlan } from '@/api/plan'
|
||||||
|
import { showMessage } from '@/composables/util'
|
||||||
|
|
||||||
|
const rows = ref([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const pageNo = ref(1)
|
||||||
|
const pageSize = ref(10)
|
||||||
|
const totalCount = ref(0)
|
||||||
|
const searchName = ref('')
|
||||||
|
const tableRef = ref(null)
|
||||||
|
const downloadingIds = ref([])
|
||||||
|
const finishingIds = ref([])
|
||||||
|
|
||||||
|
async function fetchLessonPlans() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await findStudentLessonPlans(pageNo.value, pageSize.value, searchName.value || '')
|
||||||
|
const d = res.data
|
||||||
|
rows.value = Array.isArray(d.data) ? d.data : []
|
||||||
|
totalCount.value = d.totalCount || 0
|
||||||
|
pageNo.value = d.pageNo || pageNo.value
|
||||||
|
pageSize.value = d.pageSize || pageSize.value
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePageChange(p) {
|
||||||
|
pageNo.value = p
|
||||||
|
fetchLessonPlans()
|
||||||
|
}
|
||||||
|
function handleSizeChange(s) {
|
||||||
|
pageSize.value = s
|
||||||
|
pageNo.value = 1
|
||||||
|
fetchLessonPlans()
|
||||||
|
}
|
||||||
|
function onSearch() {
|
||||||
|
pageNo.value = 1
|
||||||
|
fetchLessonPlans()
|
||||||
|
}
|
||||||
|
function onReset() {
|
||||||
|
searchName.value = ''
|
||||||
|
pageNo.value = 1
|
||||||
|
fetchLessonPlans()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onDownload(plan) {
|
||||||
|
if (!plan?.id) {
|
||||||
|
showMessage('无效的计划ID', 'error')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!downloadingIds.value.includes(plan.id)) {
|
||||||
|
downloadingIds.value = [...downloadingIds.value, plan.id]
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await downloadLessonPlan({ id: plan.id })
|
||||||
|
showMessage('开始下载', 'success')
|
||||||
|
} finally {
|
||||||
|
downloadingIds.value = downloadingIds.value.filter(id => id !== plan.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onFinish(studentId, planId, plan) {
|
||||||
|
if (!studentId || !planId) {
|
||||||
|
showMessage('参数错误', 'error')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!finishingIds.value.includes(planId)) {
|
||||||
|
finishingIds.value = [...finishingIds.value, planId]
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await finishLessonPlan(studentId, planId)
|
||||||
|
const d = res.data
|
||||||
|
if (d?.success !== false) {
|
||||||
|
plan.isFinished = 1
|
||||||
|
showMessage('已标记完成', 'success')
|
||||||
|
} else {
|
||||||
|
showMessage(d?.message || '标记失败', 'error')
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
showMessage('标记失败', 'error')
|
||||||
|
} finally {
|
||||||
|
finishingIds.value = finishingIds.value.filter(id => id !== planId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchLessonPlans()
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 lg:col-span-1 lg:row-span-2">
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 lg:col-span-1 lg:row-span-1">
|
||||||
<div class="text-lg font-semibold mb-4">学生查询</div>
|
<div class="text-lg font-semibold mb-4">学生查询</div>
|
||||||
<div class="flex flex-wrap items-center gap-3 mb-4">
|
<div class="flex flex-wrap items-center gap-3 mb-4">
|
||||||
<el-input v-model="studentName" placeholder="按姓名查询" clearable style="max-width: 220px" />
|
<el-input v-model="studentName" placeholder="按姓名查询" clearable style="max-width: 220px" />
|
||||||
@@ -49,6 +49,10 @@
|
|||||||
@click="showGenerateDialog = true">
|
@click="showGenerateDialog = true">
|
||||||
生成试题
|
生成试题
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button type="warning" :disabled="selectedStudentIds.length !== 1"
|
||||||
|
@click="showLessonPlanDialog = true">
|
||||||
|
生成学案
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<el-table ref="studentTableRef" :data="students" border class="w-full"
|
<el-table ref="studentTableRef" :data="students" border class="w-full"
|
||||||
v-loading="studentLoading" @selection-change="onStudentSelectionChange">
|
v-loading="studentLoading" @selection-change="onStudentSelectionChange">
|
||||||
@@ -57,8 +61,9 @@
|
|||||||
<el-table-column prop="name" label="姓名" min-width="120" />
|
<el-table-column prop="name" label="姓名" min-width="120" />
|
||||||
<el-table-column prop="classId" label="班级ID" width="100" />
|
<el-table-column prop="classId" label="班级ID" width="100" />
|
||||||
<el-table-column prop="gradeId" label="年级ID" width="100" />
|
<el-table-column prop="gradeId" label="年级ID" width="100" />
|
||||||
<el-table-column label="操作" width="120" fixed="right">
|
<el-table-column label="操作" width="180" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
|
<el-button type="primary" size="small" @click.stop="onViewStudent(row)">详情</el-button>
|
||||||
<el-button type="danger" size="small" @click.stop="onDeleteStudent(row)">删除</el-button>
|
<el-button type="danger" size="small" @click.stop="onDeleteStudent(row)">删除</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -79,6 +84,10 @@
|
|||||||
:default-grade-id="selectedGradeId"
|
:default-grade-id="selectedGradeId"
|
||||||
@success="fetchStudents"
|
@success="fetchStudents"
|
||||||
/>
|
/>
|
||||||
|
<LessonPlanDialog
|
||||||
|
v-model="showLessonPlanDialog"
|
||||||
|
:student-id="selectedStudentIds[0]"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6" v-loading="gradeLoading">
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6" v-loading="gradeLoading">
|
||||||
@@ -105,6 +114,40 @@
|
|||||||
<AddGradeDialog v-model="showAddGradeDialog" @success="fetchGrades" />
|
<AddGradeDialog v-model="showAddGradeDialog" @success="fetchGrades" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6" v-loading="unitLoading">
|
||||||
|
<div class="text-lg font-semibold mb-4">单元列表</div>
|
||||||
|
<el-table ref="unitTableRef" :data="units" border class="w-full">
|
||||||
|
<el-table-column prop="id" label="ID" width="80" />
|
||||||
|
<el-table-column prop="title" label="单元名称" min-width="200" />
|
||||||
|
<el-table-column prop="version" label="版本" min-width="120" />
|
||||||
|
<el-table-column prop="createAt" label="创建时间" min-width="160" />
|
||||||
|
<el-table-column label="操作" width="120" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button type="danger" size="small" @click.stop="onDeleteUnit(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<div class="mt-4 flex justify-end">
|
||||||
|
<el-pagination
|
||||||
|
background
|
||||||
|
layout="prev, pager, next, sizes, total"
|
||||||
|
:total="unitTotalCount"
|
||||||
|
:page-size="unitPageSize"
|
||||||
|
:current-page="unitPageNo"
|
||||||
|
@current-change="handleUnitPageChange"
|
||||||
|
@size-change="handleUnitSizeChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 flex justify-end">
|
||||||
|
<el-button type="primary" :disabled="!selectedGradeId" @click="showAddUnitDialog = true">新增单元</el-button>
|
||||||
|
</div>
|
||||||
|
<AddUnitDialog
|
||||||
|
v-model="showAddUnitDialog"
|
||||||
|
:default-grade-id="selectedGradeId"
|
||||||
|
@success="fetchUnits"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</el-main>
|
</el-main>
|
||||||
|
|
||||||
@@ -116,12 +159,16 @@
|
|||||||
import Header from '@/layouts/components/Header.vue'
|
import Header from '@/layouts/components/Header.vue'
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { getClassList, deleteClass } from '@/api/class'
|
import { getClassList, deleteClass } from '@/api/class'
|
||||||
import { getGradeList } from '@/api/grade'
|
import { getGradeList, deleteGrade } from '@/api/grade'
|
||||||
import { getStudentList, deleteStudent } from '@/api/student'
|
import { getStudentList, deleteStudent } from '@/api/student'
|
||||||
import ExamGenerateDialog from '@/layouts/components/ExamGenerateDialog.vue'
|
import ExamGenerateDialog from '@/layouts/components/ExamGenerateDialog.vue'
|
||||||
import AddClassDialog from '@/layouts/components/AddClassDialog.vue'
|
import AddClassDialog from '@/layouts/components/AddClassDialog.vue'
|
||||||
import AddGradeDialog from '@/layouts/components/AddGradeDialog.vue'
|
import AddGradeDialog from '@/layouts/components/AddGradeDialog.vue'
|
||||||
import AddStudentDialog from '@/layouts/components/AddStudentDialog.vue'
|
import AddStudentDialog from '@/layouts/components/AddStudentDialog.vue'
|
||||||
|
import LessonPlanDialog from '@/layouts/components/LessonPlanDialog.vue'
|
||||||
|
import { getUnitList, deleteUnit } from '@/api/unit'
|
||||||
|
import AddUnitDialog from '@/layouts/components/AddUnitDialog.vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
const classes = ref([])
|
const classes = ref([])
|
||||||
const pageNo = ref(1)
|
const pageNo = ref(1)
|
||||||
@@ -153,6 +200,16 @@ const studentTableRef = ref(null)
|
|||||||
const selectedStudentIds = ref([])
|
const selectedStudentIds = ref([])
|
||||||
const showGenerateDialog = ref(false)
|
const showGenerateDialog = ref(false)
|
||||||
const showAddStudentDialog = ref(false)
|
const showAddStudentDialog = ref(false)
|
||||||
|
const showLessonPlanDialog = ref(false)
|
||||||
|
|
||||||
|
const units = ref([])
|
||||||
|
const unitPageNo = ref(1)
|
||||||
|
const unitPageSize = ref(10)
|
||||||
|
const unitTotalCount = ref(0)
|
||||||
|
const unitLoading = ref(false)
|
||||||
|
const unitTableRef = ref(null)
|
||||||
|
const showAddUnitDialog = ref(false)
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
async function fetchClasses() {
|
async function fetchClasses() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
@@ -235,6 +292,9 @@ function handleStudentSizeChange(s) {
|
|||||||
function onStudentSelectionChange(rows) {
|
function onStudentSelectionChange(rows) {
|
||||||
selectedStudentIds.value = rows.map(r => r.id)
|
selectedStudentIds.value = rows.map(r => r.id)
|
||||||
}
|
}
|
||||||
|
function onViewStudent(row) {
|
||||||
|
router.push(`/student/${row.id}`)
|
||||||
|
}
|
||||||
function onClassRowClick(row) {
|
function onClassRowClick(row) {
|
||||||
selectedClassId.value = row.id
|
selectedClassId.value = row.id
|
||||||
selectedClassTitle.value = row.title
|
selectedClassTitle.value = row.title
|
||||||
@@ -303,9 +363,48 @@ async function onDeleteGrade(row) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchUnits() {
|
||||||
|
unitLoading.value = true
|
||||||
|
try {
|
||||||
|
const res = await getUnitList(unitPageNo.value, unitPageSize.value)
|
||||||
|
const d = res.data
|
||||||
|
units.value = Array.isArray(d.data) ? d.data : []
|
||||||
|
unitTotalCount.value = d.totalCount || 0
|
||||||
|
unitPageNo.value = d.pageNo || unitPageNo.value
|
||||||
|
unitPageSize.value = d.pageSize || unitPageSize.value
|
||||||
|
} finally {
|
||||||
|
unitLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handleUnitPageChange(p) {
|
||||||
|
unitPageNo.value = p
|
||||||
|
fetchUnits()
|
||||||
|
}
|
||||||
|
function handleUnitSizeChange(s) {
|
||||||
|
unitPageSize.value = s
|
||||||
|
unitPageNo.value = 1
|
||||||
|
fetchUnits()
|
||||||
|
}
|
||||||
|
async function onDeleteUnit(row) {
|
||||||
|
try {
|
||||||
|
const res = await deleteUnit(row.id)
|
||||||
|
const data = res.data
|
||||||
|
console.log(data)
|
||||||
|
if (data.success) {
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
} else {
|
||||||
|
ElMessage.error(data.message || '删除失败')
|
||||||
|
}
|
||||||
|
await fetchUnits()
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error('删除失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchClasses()
|
fetchClasses()
|
||||||
fetchGrades()
|
fetchGrades()
|
||||||
fetchStudents()
|
fetchStudents()
|
||||||
|
fetchUnits()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
77
enlish-vue/src/pages/student.vue
Normal file
77
enlish-vue/src/pages/student.vue
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
<template>
|
||||||
|
<div class="common-layout">
|
||||||
|
<el-container>
|
||||||
|
<el-header>
|
||||||
|
<Header></Header>
|
||||||
|
</el-header>
|
||||||
|
|
||||||
|
<el-main class="p-4">
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-1 gap-6"
|
||||||
|
v-loading="loading">
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||||
|
<div class="text-lg font-semibold mb-4">学生详情</div>
|
||||||
|
<template v-if="detail">
|
||||||
|
<el-descriptions :column="1" border>
|
||||||
|
<el-descriptions-item label="ID">{{ detail.id }}</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.gradeName }}</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<el-empty description="请从班级页跳转" />
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||||
|
<div class="text-md font-semibold mb-3">学生考试记录</div>
|
||||||
|
<ExamHistoryChart :data="history" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-main>
|
||||||
|
|
||||||
|
</el-container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import Header from '@/layouts/components/Header.vue'
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import { getStudentDetail } from '@/api/student'
|
||||||
|
import { getStudentExamHistory } from '@/api/exam'
|
||||||
|
import ExamHistoryChart from '@/layouts/components/student/ExamHistoryChart.vue'
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const detail = ref(null)
|
||||||
|
const route = useRoute()
|
||||||
|
const history = ref([])
|
||||||
|
|
||||||
|
async function fetchDetail() {
|
||||||
|
const id = route.params.id
|
||||||
|
if (!id) return
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await getStudentDetail(id)
|
||||||
|
const d = res.data
|
||||||
|
detail.value = d?.data || null
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchExamHistory() {
|
||||||
|
const id = route.params.id
|
||||||
|
if (!id) return
|
||||||
|
const res = await getStudentExamHistory(id)
|
||||||
|
const d = res.data
|
||||||
|
history.value = Array.isArray(d?.data) ? d.data.slice().sort((a, b) => {
|
||||||
|
return new Date(a.startDate).getTime() - new Date(b.startDate).getTime()
|
||||||
|
}) : []
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchDetail()
|
||||||
|
fetchExamHistory()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import Index from '@/pages/index.vue'
|
import Index from '@/pages/index.vue'
|
||||||
import Uploadpng from '@/pages/uploadpng.vue'
|
import Uploadpng from '@/pages/uploadpng.vue'
|
||||||
|
import LearningPlan from '@/pages/LearningPlan.vue'
|
||||||
import Class from '@/pages/class.vue'
|
import Class from '@/pages/class.vue'
|
||||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||||
|
import Student from '@/pages/student.vue'
|
||||||
|
|
||||||
// 统一在这里声明所有路由
|
// 统一在这里声明所有路由
|
||||||
const routes = [
|
const routes = [
|
||||||
@@ -18,6 +20,20 @@ const routes = [
|
|||||||
meta: { // meta 信息
|
meta: { // meta 信息
|
||||||
title: '上传图片' // 页面标题
|
title: '上传图片' // 页面标题
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/learningplan', // 路由地址
|
||||||
|
component: LearningPlan, // 对应组件
|
||||||
|
meta: { // meta 信息
|
||||||
|
title: '学案' // 页面标题
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/student/:id', // 路由地址
|
||||||
|
component: Student, // 对应组件
|
||||||
|
meta: { // meta 信息
|
||||||
|
title: '学生详情' // 页面标题
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user