feat(plan): 添加学生学案历史查询及展示功能

- 新增FindStudentPlanHistoryReqVO和FindStudentPlanHistoryListRspVO数据对象
- 将LessonPlansDO及StudentLessonPlansDO中日期类型由Date改为LocalDateTime
- LessonPlansServiceImpl中创建时间使用LocalDateTime.now()
- StudentLessonPlansService及实现类添加按学生ID查询学案历史接口
- StudentLessonPlansDOMapper及XML添加按学生ID查询学案历史SQL映射
- StudentLessonPlansController新增/history接口返回学生学案历史列表
- 前端student.vue新增学案历史图表PlanHistoryChart组件展示学案历史数据
- 新增PlanHistoryChart.vue组件,实现基于echarts的学案历史折线图
- studentLessonPlans.js新增findStudentPlanHistory接口调用后端学案历史数据接口
This commit is contained in:
lbw
2025-12-18 12:43:48 +08:00
parent a50c9a2b16
commit 7a66548aed
13 changed files with 228 additions and 9 deletions

View File

@@ -3,10 +3,7 @@ package com.yinlihupo.enlish.service.controller;
import com.yinlihupo.enlish.service.domain.dataobject.LessonPlansDO; import com.yinlihupo.enlish.service.domain.dataobject.LessonPlansDO;
import com.yinlihupo.enlish.service.domain.dataobject.StudentLessonPlansDO; import com.yinlihupo.enlish.service.domain.dataobject.StudentLessonPlansDO;
import com.yinlihupo.enlish.service.model.bo.StudentDetail; 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.*;
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.LessonPlansService;
import com.yinlihupo.enlish.service.service.StudentLessonPlansService; import com.yinlihupo.enlish.service.service.StudentLessonPlansService;
import com.yinlihupo.enlish.service.service.StudentService; import com.yinlihupo.enlish.service.service.StudentService;
@@ -77,7 +74,7 @@ public class StudentLessonPlansController {
return PageResponse.success(findStudentPlansRspVOList, page, studentLessonPlanTotal, size); return PageResponse.success(findStudentPlansRspVOList, page, studentLessonPlanTotal, size);
} }
@PostMapping("/finish") @PostMapping("finish")
@ApiOperationLog(description = "完成学案") @ApiOperationLog(description = "完成学案")
public Response<String> finishStudentPlan(@RequestBody FinishStudentPlanReqVO finishStudentPlanReqVO) { public Response<String> finishStudentPlan(@RequestBody FinishStudentPlanReqVO finishStudentPlanReqVO) {
Integer studentId = finishStudentPlanReqVO.getStudentId(); Integer studentId = finishStudentPlanReqVO.getStudentId();
@@ -89,4 +86,22 @@ public class StudentLessonPlansController {
} }
return Response.fail("完成学案失败"); return Response.fail("完成学案失败");
} }
@PostMapping("history")
@ApiOperationLog(description = "查询学案历史")
public Response<List<FindStudentPlanHistoryListRspVO>> findStudentPlanHistory(@RequestBody FindStudentPlanHistoryReqVO findStudentPlanHistoryReqVO) {
Integer studentId = findStudentPlanHistoryReqVO.getStudentId();
List<FindStudentPlanHistoryListRspVO> list = studentLessonPlansService.findStudentLessonPlansByStudentId(studentId)
.stream().map(studentLessonPlansDO -> FindStudentPlanHistoryListRspVO.builder()
.id(studentLessonPlansDO.getId())
.studentId(studentLessonPlansDO.getStudentId())
.planId(studentLessonPlansDO.getPlanId())
.startTime(studentLessonPlansDO.getStartTime())
.masteryRat(studentLessonPlansDO.getMasteryRat())
.totalCount(studentLessonPlansDO.getTotalCount())
.build()
).toList();
return Response.success(list);
}
} }

View File

@@ -5,6 +5,7 @@ import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.Date; import java.util.Date;
@AllArgsConstructor @AllArgsConstructor
@@ -20,7 +21,7 @@ public class LessonPlansDO {
private Integer unitId; private Integer unitId;
private Date createdAt; private LocalDateTime createdAt;
private String contentDetails; private String contentDetails;

View File

@@ -6,6 +6,7 @@ import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date; import java.util.Date;
@AllArgsConstructor @AllArgsConstructor
@@ -19,7 +20,7 @@ public class StudentLessonPlansDO {
private Integer planId; private Integer planId;
private Date startTime; private LocalDateTime startTime;
private BigDecimal masteryRat; private BigDecimal masteryRat;

View File

@@ -14,4 +14,6 @@ public interface StudentLessonPlansDOMapper {
Integer selectStudentPlanTotal(); Integer selectStudentPlanTotal();
Integer finfishStudentPlan(@Param("studentId") Integer studentId, @Param("planId") Integer planId, @Param("count") Integer count, @Param("mastery") double mastery); Integer finfishStudentPlan(@Param("studentId") Integer studentId, @Param("planId") Integer planId, @Param("count") Integer count, @Param("mastery") double mastery);
List<StudentLessonPlansDO> selectStudentLessonPlanListByStudentId(@Param("studentId") Integer studentId);
} }

View File

@@ -0,0 +1,29 @@
package com.yinlihupo.enlish.service.model.vo.plan;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;
@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
public class FindStudentPlanHistoryListRspVO {
private Integer id;
private Integer studentId;
private Integer planId;
private LocalDateTime startTime;
private BigDecimal masteryRat;
private Integer totalCount;
}

View File

@@ -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 FindStudentPlanHistoryReqVO {
private Integer studentId;
}

View File

@@ -11,4 +11,6 @@ public interface StudentLessonPlansService {
Integer findStudentLessonPlanTotal(); Integer findStudentLessonPlanTotal();
int finishStudentLessonPlan(Integer studentId, Integer planId); int finishStudentLessonPlan(Integer studentId, Integer planId);
List<StudentLessonPlansDO> findStudentLessonPlansByStudentId(Integer studentId);
} }

View File

@@ -15,6 +15,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.io.IOException; import java.io.IOException;
import java.time.LocalDateTime;
import java.util.*; import java.util.*;
@Service @Service
@@ -104,7 +105,7 @@ public class LessonPlansServiceImpl implements LessonPlansService {
.title(lessonPlanMap.get("title").toString()) .title(lessonPlanMap.get("title").toString())
.gradeId(gradeDO.getId().toString()) .gradeId(gradeDO.getId().toString())
.unitId(unitDO.getId()) .unitId(unitDO.getId())
.createdAt(new Date()) .createdAt(LocalDateTime.now())
.contentDetails(JsonUtils.toJsonString(lessonPlanMap)) .contentDetails(JsonUtils.toJsonString(lessonPlanMap))
.build(); .build();
lessonPlansDOMapper.insert(lessonPlansDO); lessonPlansDOMapper.insert(lessonPlansDO);
@@ -132,7 +133,7 @@ public class LessonPlansServiceImpl implements LessonPlansService {
.title(map.get("title").toString()) .title(map.get("title").toString())
.gradeId(gradeDO.getId().toString()) .gradeId(gradeDO.getId().toString())
.unitId(unitDO.getId()) .unitId(unitDO.getId())
.createdAt(new Date()) .createdAt(LocalDateTime.now())
.contentDetails(JsonUtils.toJsonString(map)) .contentDetails(JsonUtils.toJsonString(map))
.build(); .build();
lessonPlansDOMapper.insert(lessonPlansDO); lessonPlansDOMapper.insert(lessonPlansDO);

View File

@@ -44,4 +44,9 @@ public class StudentLessonPlansServiceImpl implements StudentLessonPlansService
return studentLessonPlansDOMapper.finfishStudentPlan(studentId, planId, wordStrengthCount, (double) wordStrengthCount / wordTotal); return studentLessonPlansDOMapper.finfishStudentPlan(studentId, planId, wordStrengthCount, (double) wordStrengthCount / wordTotal);
} }
@Override
public List<StudentLessonPlansDO> findStudentLessonPlansByStudentId(Integer studentId) {
return studentLessonPlansDOMapper.selectStudentLessonPlanListByStudentId(studentId);
}
} }

View File

@@ -45,6 +45,12 @@
SELECT count(DISTINCT student_id) AS total SELECT count(DISTINCT student_id) AS total
FROM student_lesson_plans; FROM student_lesson_plans;
</select> </select>
<select id="selectStudentLessonPlanListByStudentId" resultMap="BaseResultMap">
SELECT *
FROM student_lesson_plans
WHERE student_id = #{studentId}
limit 500
</select>
<update id="finfishStudentPlan"> <update id="finfishStudentPlan">
update student_lesson_plans update student_lesson_plans

View File

@@ -14,3 +14,9 @@ export function finishLessonPlan(studentId, planId) {
planId: planId planId: planId
}) })
} }
export function findStudentPlanHistory(studentId) {
return axios.post('/studentLessonPlans/history', {
studentId: studentId
})
}

View File

@@ -0,0 +1,131 @@
<template>
<div style="width: 100%; height: 260px;">
<div ref="elRef" style="width: 100%; height: 100%;"></div>
</div>
</template>
<script setup>
import { defineProps, ref, onMounted, onUnmounted, watch } from 'vue'
import { findStudentPlanHistory } from '@/api/studentLessonPlans'
const props = defineProps({
studentId: { type: [Number, String], required: true }
})
const elRef = ref(null)
let chart = null
let echartsLib = null
const rows = ref([])
function sortData(arr) {
return Array.isArray(arr)
? arr.slice().sort((a, b) => new Date(a.startTime).getTime() - new Date(b.startTime).getTime())
: []
}
function toSource(arr) {
return sortData(arr).map(it => ({
startTime: it.startTime,
totalCount: Number(it.totalCount) || 0,
planId: it.planId ?? null,
id: it.id ?? null
}))
}
function buildOption(source) {
return {
dataset: [
{
id: 'dataset_history',
source: source
}
],
tooltip: {
trigger: 'axis'
},
xAxis: {
type: 'category',
nameLocation: 'middle'
},
yAxis: {
name: 'Total Count',
min: 0
},
series: [
{
type: 'line',
datasetId: 'dataset_history',
showSymbol: false,
encode: {
x: 'startTime',
y: 'totalCount',
itemName: 'startTime',
tooltip: ['totalCount']
}
}
]
}
}
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)
window.addEventListener('resize', resize)
}
const source = toSource(rows.value)
const option = buildOption(source)
chart.setOption(option)
}
async function fetch() {
if (props.studentId === undefined || props.studentId === null) return
const res = await findStudentPlanHistory(Number(props.studentId))
const d = res?.data?.data ?? []
rows.value = Array.isArray(d) ? d : []
}
onMounted(async () => {
await fetch()
await render()
})
onUnmounted(() => {
window.removeEventListener('resize', resize)
if (chart) {
chart.dispose()
chart = null
}
})
watch(() => props.studentId, async () => {
await fetch()
await render()
})
</script>

View File

@@ -27,6 +27,10 @@
<div class="text-md font-semibold mb-3">学生考试记录</div> <div class="text-md font-semibold mb-3">学生考试记录</div>
<ExamHistoryChart :data="history" /> <ExamHistoryChart :data="history" />
</div> </div>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div class="text-md font-semibold mb-3">学生学案记录</div>
<PlanHistoryChart :student-id="route.params.id" />
</div>
</div> </div>
</el-main> </el-main>
@@ -41,6 +45,7 @@ import { useRoute } from 'vue-router'
import { getStudentDetail } from '@/api/student' import { getStudentDetail } from '@/api/student'
import { getStudentExamHistory } from '@/api/exam' import { getStudentExamHistory } from '@/api/exam'
import ExamHistoryChart from '@/layouts/components/student/ExamHistoryChart.vue' import ExamHistoryChart from '@/layouts/components/student/ExamHistoryChart.vue'
import PlanHistoryChart from '@/layouts/components/student/PlanHistoryChart.vue'
const loading = ref(false) const loading = ref(false)
const detail = ref(null) const detail = ref(null)