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 {
|
||||
ExamWordsDO examWordsDO = examWordsService.generateExamWords(gradeId, level, studentIds);
|
||||
if (examWordsDO == null || examWordsDO.getWordIds().isEmpty()) {
|
||||
throw new RuntimeException("没有单词");
|
||||
}
|
||||
List<VocabularyBankDO> vocabularyBankDOS = vocabularyService.findVocabularyBankDOListById(examWordsDO.getWordIds());
|
||||
List<Word> assessmentWords = vocabularyBankDOS.stream().map(vocabularyBankDO -> Word.builder()
|
||||
.id(vocabularyBankDO.getId())
|
||||
@@ -138,4 +141,22 @@ public class ExamWordsController {
|
||||
|
||||
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
|
||||
@Builder
|
||||
public class VocabularyBankDO {
|
||||
|
||||
private Integer id;
|
||||
|
||||
private String word;
|
||||
@@ -18,6 +19,8 @@ public class VocabularyBankDO {
|
||||
|
||||
private String pronunciation;
|
||||
|
||||
private String pos;
|
||||
|
||||
private Integer unitId;
|
||||
|
||||
}
|
||||
@@ -20,4 +20,6 @@ public interface ExamWordsJudgeResultDOMapper {
|
||||
Integer selectCount();
|
||||
|
||||
ExamWordsJudgeResultDO selectDetailById(@Param("id") Integer id);
|
||||
|
||||
List<ExamWordsJudgeResultDO> selectByStudentId(@Param("studentId") Integer studentId);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.yinlihupo.enlish.service.domain.mapper;
|
||||
|
||||
import com.yinlihupo.enlish.service.domain.dataobject.GradeUnitDO;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
@@ -7,4 +8,10 @@ import java.util.List;
|
||||
public interface GradeUnitDOMapper {
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
|
||||
import com.yinlihupo.enlish.service.domain.dataobject.UnitDO;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface UnitDOMapper {
|
||||
int deleteByPrimaryKey(Integer id);
|
||||
@@ -18,4 +21,8 @@ public interface UnitDOMapper {
|
||||
int updateByPrimaryKey(UnitDO record);
|
||||
|
||||
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<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 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 java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public interface ExamWordsJudgeService {
|
||||
@@ -13,4 +14,6 @@ public interface ExamWordsJudgeService {
|
||||
Integer getExamWordsJudgeResultCount();
|
||||
|
||||
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.GradeClassDOMapper;
|
||||
import com.yinlihupo.enlish.service.domain.mapper.GradeDOMapper;
|
||||
import com.yinlihupo.enlish.service.domain.mapper.StudentDOMapper;
|
||||
import com.yinlihupo.enlish.service.service.ClassService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -24,6 +25,8 @@ public class ClassServiceImpl implements ClassService {
|
||||
private GradeClassDOMapper gradeClassDOMapper;
|
||||
@Resource
|
||||
private GradeDOMapper gradeDOMapper;
|
||||
@Resource
|
||||
private StudentDOMapper studentDOMapper;
|
||||
|
||||
@Override
|
||||
public ClassDO findClassById(Integer id) {
|
||||
@@ -82,6 +85,10 @@ public class ClassServiceImpl implements ClassService {
|
||||
|
||||
@Override
|
||||
public void deleteClass(Integer classId) {
|
||||
int selectStudentCountByClassId = studentDOMapper.selectStudentCountByClassId(classId);
|
||||
if (selectStudentCountByClassId > 0) {
|
||||
throw new RuntimeException("该班级下有学生,请先删除该班级下的学生");
|
||||
}
|
||||
classDOMapper.delete(classId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,4 +144,9 @@ public class ExamWordsJudgeServiceImpl implements ExamWordsJudgeService {
|
||||
public ExamWordsJudgeResultDO getExamWordsJudgeResultDOById(Integer 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 {
|
||||
|
||||
private static final Configure config;
|
||||
private static final Configure configLessonPlanWeekday;
|
||||
private static final Configure configLessonPlanWeekend;
|
||||
|
||||
static {
|
||||
LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();
|
||||
@@ -23,6 +25,24 @@ public class WordExportUtil {
|
||||
.bind("words", policy)
|
||||
.bind("answer", policy)
|
||||
.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
|
||||
*/
|
||||
@@ -103,7 +145,7 @@ public class WordExportUtil {
|
||||
*/
|
||||
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. 设置响应头
|
||||
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
|
||||
count: 100
|
||||
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:
|
||||
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"/>
|
||||
|
||||
<!-- 需要生成的表-实体类 -->
|
||||
<table tableName="grade_class" domainObjectName="GradeClassDO"
|
||||
<table tableName="student_lesson_plans" domainObjectName="StudentLessonPlansDO"
|
||||
enableCountByExample="false"
|
||||
enableUpdateByExample="false"
|
||||
enableDeleteByExample="false"
|
||||
|
||||
@@ -66,5 +66,12 @@
|
||||
from exam_words_judge_result
|
||||
where id = #{id}
|
||||
</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>
|
||||
@@ -13,5 +13,19 @@
|
||||
where grade_id = #{gradeId}
|
||||
</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>
|
||||
@@ -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
|
||||
where id = #{id}
|
||||
</update>
|
||||
|
||||
<select id="selectStudentCountByClassId" resultType="java.lang.Integer">
|
||||
select count(1)
|
||||
from student
|
||||
where class_id = #{classId}
|
||||
and is_deleted = 0
|
||||
</select>
|
||||
</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}
|
||||
</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>
|
||||
@@ -7,6 +7,7 @@
|
||||
<result column="definition" jdbcType="VARCHAR" property="definition" />
|
||||
<result column="pronunciation" jdbcType="VARCHAR" property="pronunciation" />
|
||||
<result column="unit_id" jdbcType="INTEGER" property="unitId" />
|
||||
<result column="pos" jdbcType="VARCHAR" property="pos" />
|
||||
</resultMap>
|
||||
|
||||
|
||||
@@ -28,6 +29,9 @@
|
||||
<if test="unitId != null">
|
||||
unit_id,
|
||||
</if>
|
||||
<if test="pos != null">
|
||||
pos,
|
||||
</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="id != null">
|
||||
@@ -45,6 +49,9 @@
|
||||
<if test="unitId != null">
|
||||
#{unitId,jdbcType=INTEGER},
|
||||
</if>
|
||||
<if test="pos != null">
|
||||
#{pos,jdbcType=VARCHAR},
|
||||
</if>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
@@ -69,4 +76,50 @@
|
||||
from vocabulary_bank
|
||||
</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>
|
||||
@@ -22,6 +22,12 @@
|
||||
order by memory_strength desc
|
||||
limit #{limit}
|
||||
</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 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",
|
||||
"dependencies": {
|
||||
"axios": "^1.13.2",
|
||||
"echarts": "^6.0.0",
|
||||
"element-plus": "^2.12.0",
|
||||
"flowbite": "^1.8.1",
|
||||
"nprogress": "^0.2.0",
|
||||
@@ -1647,6 +1648,16 @@
|
||||
"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": {
|
||||
"version": "1.5.267",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
|
||||
@@ -3088,6 +3099,12 @@
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz",
|
||||
@@ -3420,6 +3437,15 @@
|
||||
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
|
||||
"dev": true,
|
||||
"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": {
|
||||
"axios": "^1.13.2",
|
||||
"echarts": "^6.0.0",
|
||||
"element-plus": "^2.12.0",
|
||||
"flowbite": "^1.8.1",
|
||||
"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) => {
|
||||
// 创建 Blob 对象,可以指定 type,也可以让浏览器自动推断
|
||||
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>
|
||||
</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">
|
||||
Marketplace
|
||||
</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">
|
||||
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>
|
||||
学案
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<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 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="flex flex-wrap items-center gap-3 mb-4">
|
||||
<el-input v-model="studentName" placeholder="按姓名查询" clearable style="max-width: 220px" />
|
||||
@@ -49,6 +49,10 @@
|
||||
@click="showGenerateDialog = true">
|
||||
生成试题
|
||||
</el-button>
|
||||
<el-button type="warning" :disabled="selectedStudentIds.length !== 1"
|
||||
@click="showLessonPlanDialog = true">
|
||||
生成学案
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table ref="studentTableRef" :data="students" border class="w-full"
|
||||
v-loading="studentLoading" @selection-change="onStudentSelectionChange">
|
||||
@@ -57,8 +61,9 @@
|
||||
<el-table-column prop="name" label="姓名" min-width="120" />
|
||||
<el-table-column prop="classId" 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 }">
|
||||
<el-button type="primary" size="small" @click.stop="onViewStudent(row)">详情</el-button>
|
||||
<el-button type="danger" size="small" @click.stop="onDeleteStudent(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -79,6 +84,10 @@
|
||||
:default-grade-id="selectedGradeId"
|
||||
@success="fetchStudents"
|
||||
/>
|
||||
<LessonPlanDialog
|
||||
v-model="showLessonPlanDialog"
|
||||
:student-id="selectedStudentIds[0]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<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" />
|
||||
</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>
|
||||
</el-main>
|
||||
|
||||
@@ -116,12 +159,16 @@
|
||||
import Header from '@/layouts/components/Header.vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { getClassList, deleteClass } from '@/api/class'
|
||||
import { getGradeList } from '@/api/grade'
|
||||
import { getGradeList, deleteGrade } from '@/api/grade'
|
||||
import { getStudentList, deleteStudent } from '@/api/student'
|
||||
import ExamGenerateDialog from '@/layouts/components/ExamGenerateDialog.vue'
|
||||
import AddClassDialog from '@/layouts/components/AddClassDialog.vue'
|
||||
import AddGradeDialog from '@/layouts/components/AddGradeDialog.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 pageNo = ref(1)
|
||||
@@ -153,6 +200,16 @@ const studentTableRef = ref(null)
|
||||
const selectedStudentIds = ref([])
|
||||
const showGenerateDialog = 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() {
|
||||
loading.value = true
|
||||
@@ -235,6 +292,9 @@ function handleStudentSizeChange(s) {
|
||||
function onStudentSelectionChange(rows) {
|
||||
selectedStudentIds.value = rows.map(r => r.id)
|
||||
}
|
||||
function onViewStudent(row) {
|
||||
router.push(`/student/${row.id}`)
|
||||
}
|
||||
function onClassRowClick(row) {
|
||||
selectedClassId.value = row.id
|
||||
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(() => {
|
||||
fetchClasses()
|
||||
fetchGrades()
|
||||
fetchStudents()
|
||||
fetchUnits()
|
||||
})
|
||||
</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 Uploadpng from '@/pages/uploadpng.vue'
|
||||
import LearningPlan from '@/pages/LearningPlan.vue'
|
||||
import Class from '@/pages/class.vue'
|
||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
import Student from '@/pages/student.vue'
|
||||
|
||||
// 统一在这里声明所有路由
|
||||
const routes = [
|
||||
@@ -18,6 +20,20 @@ const routes = [
|
||||
meta: { // meta 信息
|
||||
title: '上传图片' // 页面标题
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/learningplan', // 路由地址
|
||||
component: LearningPlan, // 对应组件
|
||||
meta: { // meta 信息
|
||||
title: '学案' // 页面标题
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/student/:id', // 路由地址
|
||||
component: Student, // 对应组件
|
||||
meta: { // meta 信息
|
||||
title: '学生详情' // 页面标题
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user