- 为多个页面容器添加最小高度类,保证页面满屏显示 - 调整部分主内容区高度样式,增强布局一致性和视觉整洁 - 替换头部组件Logo为自定义SVG图标,提升品牌识别度 - 优化头部用户菜单按钮交互和样式,统一暗黑模式视觉效果 - 调整TTS页面词汇列表布局,支持移动端和桌面端不同显示方式 - 修改学生详情页面样式,提升各模块容器的统一性和分隔感 - 修正历史数据日期格式,将“T”替换为空格以增强可读性
132 lines
3.1 KiB
Vue
132 lines
3.1 KiB
Vue
<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.replace('T', ' '),
|
|
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>
|
|
|