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

@@ -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>