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:
@@ -14,3 +14,9 @@ export function finishLessonPlan(studentId, planId) {
|
||||
planId: planId
|
||||
})
|
||||
}
|
||||
|
||||
export function findStudentPlanHistory(studentId) {
|
||||
return axios.post('/studentLessonPlans/history', {
|
||||
studentId: studentId
|
||||
})
|
||||
}
|
||||
|
||||
131
enlish-vue/src/layouts/components/student/PlanHistoryChart.vue
Normal file
131
enlish-vue/src/layouts/components/student/PlanHistoryChart.vue
Normal 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>
|
||||
|
||||
@@ -27,6 +27,10 @@
|
||||
<div class="text-md font-semibold mb-3">学生考试记录</div>
|
||||
<ExamHistoryChart :data="history" />
|
||||
</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>
|
||||
</el-main>
|
||||
|
||||
@@ -41,6 +45,7 @@ import { useRoute } from 'vue-router'
|
||||
import { getStudentDetail } from '@/api/student'
|
||||
import { getStudentExamHistory } from '@/api/exam'
|
||||
import ExamHistoryChart from '@/layouts/components/student/ExamHistoryChart.vue'
|
||||
import PlanHistoryChart from '@/layouts/components/student/PlanHistoryChart.vue'
|
||||
|
||||
const loading = ref(false)
|
||||
const detail = ref(null)
|
||||
|
||||
Reference in New Issue
Block a user