提交 c8379c83 authored 作者: 王鹏飞's avatar 王鹏飞

feat: 新增数据可视化

上级 bd9a443c
...@@ -210,6 +210,12 @@ export const menus: IMenuItem[] = [ ...@@ -210,6 +210,12 @@ export const menus: IMenuItem[] = [
// import.meta.env.VITE_BI_URL + // import.meta.env.VITE_BI_URL +
// '/bi/?proc=1&action=viewer&hback=true&isInPreview=true&db=!7d2b!!8346!!6559!!80b2!e-SaaS!2f!!5b66!!4e60!!884c!!4e3a!!753b!!50cf!.db&platform=PC&browserType=chrome', // '/bi/?proc=1&action=viewer&hback=true&isInPreview=true&db=!7d2b!!8346!!6559!!80b2!e-SaaS!2f!!5b66!!4e60!!884c!!4e3a!!753b!!50cf!.db&platform=PC&browserType=chrome',
}, },
{
tag: '',
icon: DataAnalysis,
name: '数据可视化',
path: '/teach/chart/visualization',
},
// { // {
// tag: '', // tag: '',
// icon: DataAnalysis, // icon: DataAnalysis,
......
...@@ -8,6 +8,7 @@ export const routes: Array<RouteRecordRaw> = [ ...@@ -8,6 +8,7 @@ export const routes: Array<RouteRecordRaw> = [
children: [ children: [
{ path: 'resource', component: () => import('./views/Resource.vue') }, { path: 'resource', component: () => import('./views/Resource.vue') },
{ path: 'learning', component: () => import('./views/Learning.vue') }, { path: 'learning', component: () => import('./views/Learning.vue') },
{ path: 'visualization', component: () => import('./views/Visualization.vue') },
], ],
}, },
] ]
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import * as echarts from 'echarts'
import { User, School, Trophy, TrendCharts, Monitor, Document } from '@element-plus/icons-vue'
// 教学数据统计
const teachingStats = ref({
totalTeachers: 0,
totalStudents: 0,
totalCourses: 0,
totalClasses: 0,
avgScore: 0,
completionRate: 0,
})
// 学习进度数据
const learningProgressData = ref({
students: [],
progress: [],
})
// 成绩分析数据
const scoreAnalysisData = ref({
distribution: [],
subjects: [],
trends: [],
})
// 图表实例
let teachingOverviewChart = null
let learningProgressChart = null
let scoreDistributionChart = null
let subjectScoreChart = null
let scoreTrendChart = null
// 加载模拟数据
const loadData = () => {
// 教学数据统计
teachingStats.value = {
totalTeachers: 128,
totalStudents: 2850,
totalCourses: 76,
totalClasses: 38,
avgScore: 87.2,
completionRate: 94.5,
}
// 学习进度数据
learningProgressData.value = {
students: ['计算机1班', '数学1班', '物理1班', '化学1班', '英语1班', '语文1班'],
progress: [92, 88, 85, 90, 87, 83],
}
// 成绩分析数据
scoreAnalysisData.value = {
distribution: [
{ name: '优秀(90-100)', value: 35, count: 998 },
{ name: '良好(80-89)', value: 40, count: 1140 },
{ name: '中等(70-79)', value: 20, count: 570 },
{ name: '及格(60-69)', value: 4, count: 114 },
{ name: '不及格(<60)', value: 1, count: 28 },
],
subjects: [
{ name: '数学', score: 89.5 },
{ name: '英语', score: 85.2 },
{ name: '计算机', score: 92.8 },
{ name: '物理', score: 87.6 },
{ name: '化学', score: 88.9 },
{ name: '语文', score: 84.3 },
],
trends: {
months: ['1月', '2月', '3月', '4月', '5月', '6月'],
scores: [85.2, 86.8, 87.5, 88.1, 87.9, 87.2],
},
}
}
// 初始化图表
const initCharts = () => {
// 教学数据总览
const teachingEl = document.getElementById('teachingOverviewChart')
if (teachingEl) {
if (teachingOverviewChart) {
teachingOverviewChart.dispose()
}
teachingOverviewChart = echarts.init(teachingEl)
const option = {
title: {
text: '教学数据总览',
left: 'center',
top: '5%',
textStyle: { fontSize: 16 },
},
tooltip: {},
radar: {
indicator: [
{ name: '教师数量', max: 150 },
{ name: '学生数量', max: 3000 },
{ name: '课程数量', max: 100 },
{ name: '班级数量', max: 50 },
{ name: '平均成绩', max: 100 },
{ name: '完成率', max: 100 },
],
center: ['50%', '60%'],
radius: '70%',
},
series: [
{
name: '教学数据',
type: 'radar',
data: [
{
value: [
teachingStats.value.totalTeachers,
teachingStats.value.totalStudents,
teachingStats.value.totalCourses,
teachingStats.value.totalClasses,
teachingStats.value.avgScore,
teachingStats.value.completionRate,
],
name: '当前数据',
areaStyle: { color: 'rgba(64, 158, 255, 0.3)' },
},
],
},
],
}
teachingOverviewChart.setOption(option)
}
// 学习进度柱状图
const progressEl = document.getElementById('learningProgressChart')
if (progressEl) {
if (learningProgressChart) {
learningProgressChart.dispose()
}
learningProgressChart = echarts.init(progressEl)
const option = {
title: {
text: '各班级学习进度',
left: 'center',
top: '5%',
textStyle: { fontSize: 16 },
},
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
},
grid: {
top: '20%',
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: {
type: 'category',
data: learningProgressData.value.students,
axisLabel: { rotate: 45 },
},
yAxis: {
type: 'value',
name: '进度(%)',
max: 100,
},
series: [
{
name: '学习进度',
type: 'bar',
data: learningProgressData.value.progress,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#83bff6' },
{ offset: 0.5, color: '#188df0' },
{ offset: 1, color: '#188df0' },
]),
},
},
],
}
learningProgressChart.setOption(option)
}
// 成绩分布饼图
const distributionEl = document.getElementById('scoreDistributionChart')
if (distributionEl) {
if (scoreDistributionChart) {
scoreDistributionChart.dispose()
}
scoreDistributionChart = echarts.init(distributionEl)
const option = {
title: {
text: '成绩分布情况',
left: 'center',
top: '5%',
textStyle: { fontSize: 16 },
},
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c}% ({d}人)',
},
legend: {
orient: 'vertical',
right: '10px',
top: 'center',
itemWidth: 15,
itemHeight: 12,
textStyle: {
fontSize: 11,
},
formatter: function (name) {
return name.length > 6 ? name.substring(0, 6) + '...' : name
},
},
series: [
{
name: '成绩分布',
type: 'pie',
radius: ['30%', '60%'],
center: ['35%', '50%'],
data: scoreAnalysisData.value.distribution,
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
},
],
}
scoreDistributionChart.setOption(option)
}
// 学科成绩柱状图
const subjectEl = document.getElementById('subjectScoreChart')
if (subjectEl) {
if (subjectScoreChart) {
subjectScoreChart.dispose()
}
subjectScoreChart = echarts.init(subjectEl)
const option = {
title: {
text: '各学科平均成绩',
left: 'center',
top: '5%',
textStyle: { fontSize: 16 },
},
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
},
grid: {
top: '20%',
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: {
type: 'category',
data: scoreAnalysisData.value.subjects.map((item) => item.name),
},
yAxis: {
type: 'value',
name: '平均成绩',
max: 100,
},
series: [
{
name: '平均成绩',
type: 'bar',
data: scoreAnalysisData.value.subjects.map((item) => item.score),
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#f7ba2c' },
{ offset: 0.5, color: '#ff9f7f' },
{ offset: 1, color: '#ff9f7f' },
]),
},
},
],
}
subjectScoreChart.setOption(option)
}
// 成绩趋势折线图
const trendEl = document.getElementById('scoreTrendChart')
if (trendEl) {
if (scoreTrendChart) {
scoreTrendChart.dispose()
}
scoreTrendChart = echarts.init(trendEl)
const option = {
title: {
text: '成绩变化趋势',
left: 'center',
top: '5%',
textStyle: { fontSize: 16 },
},
tooltip: {
trigger: 'axis',
},
grid: {
top: '20%',
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: {
type: 'category',
data: scoreAnalysisData.value.trends.months,
},
yAxis: {
type: 'value',
name: '平均成绩',
},
series: [
{
name: '平均成绩',
type: 'line',
data: scoreAnalysisData.value.trends.scores,
smooth: true,
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(255, 99, 132, 0.3)' },
{ offset: 1, color: 'rgba(255, 99, 132, 0.1)' },
]),
},
},
],
}
scoreTrendChart.setOption(option)
}
}
// 窗口大小改变时重新调整图表
const handleResize = () => {
teachingOverviewChart?.resize()
learningProgressChart?.resize()
scoreDistributionChart?.resize()
subjectScoreChart?.resize()
scoreTrendChart?.resize()
}
// 清理所有图表实例
const disposeAllCharts = () => {
teachingOverviewChart?.dispose()
learningProgressChart?.dispose()
scoreDistributionChart?.dispose()
subjectScoreChart?.dispose()
scoreTrendChart?.dispose()
teachingOverviewChart = null
learningProgressChart = null
scoreDistributionChart = null
subjectScoreChart = null
scoreTrendChart = null
}
onMounted(() => {
loadData()
setTimeout(() => {
initCharts()
}, 500)
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
disposeAllCharts()
window.removeEventListener('resize', handleResize)
})
</script>
<template>
<div class="visualization-page">
<div class="page-header">
<h1>数据可视化分析</h1>
<p>教学数据统计、学习进度可视化、成绩分析图表</p>
</div>
<!-- 教学数据统计 -->
<div class="stats-section">
<h2>教学数据统计</h2>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon teachers">
<User />
</div>
<div class="stat-content">
<div class="stat-number">{{ teachingStats.totalTeachers }}</div>
<div class="stat-label">教师总数</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon students">
<School />
</div>
<div class="stat-content">
<div class="stat-number">{{ teachingStats.totalStudents.toLocaleString() }}</div>
<div class="stat-label">学生总数</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon courses">
<Document />
</div>
<div class="stat-content">
<div class="stat-number">{{ teachingStats.totalCourses }}</div>
<div class="stat-label">课程总数</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon classes">
<Monitor />
</div>
<div class="stat-content">
<div class="stat-number">{{ teachingStats.totalClasses }}</div>
<div class="stat-label">班级总数</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon score">
<Trophy />
</div>
<div class="stat-content">
<div class="stat-number">{{ teachingStats.avgScore }}</div>
<div class="stat-label">平均成绩</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon completion">
<TrendCharts />
</div>
<div class="stat-content">
<div class="stat-number">{{ teachingStats.completionRate }}%</div>
<div class="stat-label">完成率</div>
</div>
</div>
</div>
</div>
<!-- 图表区域 -->
<div class="charts-section">
<div class="charts-row">
<div class="chart-container">
<div id="teachingOverviewChart" class="chart"></div>
</div>
<div class="chart-container">
<div id="learningProgressChart" class="chart"></div>
</div>
</div>
<div class="charts-row">
<div class="chart-container">
<div id="scoreDistributionChart" class="chart"></div>
</div>
<div class="chart-container">
<div id="subjectScoreChart" class="chart"></div>
</div>
</div>
<div class="charts-row">
<div class="chart-container large">
<div id="scoreTrendChart" class="chart"></div>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.visualization-page {
padding: 20px;
background-color: #f8f8f8;
min-height: 100vh;
.page-header {
text-align: center;
margin-bottom: 30px;
h1 {
font-size: 28px;
color: #303133;
margin-bottom: 10px;
}
p {
font-size: 16px;
color: #606266;
}
}
.stats-section,
.charts-section {
margin-bottom: 40px;
h2 {
font-size: 20px;
color: #303133;
margin-bottom: 20px;
padding-left: 10px;
border-left: 4px solid #409eff;
}
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
}
.stat-card {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
transition: transform 0.3s ease;
&:hover {
transform: translateY(-2px);
}
.stat-icon {
width: 60px;
height: 60px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 15px;
svg {
width: 24px;
height: 24px;
color: #ba8b45;
}
&.teachers,
&.students,
&.courses,
&.classes,
&.score,
&.completion {
background: #f5ebda;
}
}
.stat-content {
flex: 1;
.stat-number {
font-size: 32px;
font-weight: bold;
color: #303133;
line-height: 1;
margin-bottom: 5px;
}
.stat-label {
font-size: 14px;
color: #909399;
}
}
}
.charts-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
}
.chart-container {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
&.large {
grid-column: span 2;
}
.chart {
width: 100%;
height: 300px;
}
}
}
@media (max-width: 768px) {
.visualization-page {
padding: 10px;
.stats-grid {
grid-template-columns: 1fr;
}
.charts-row {
grid-template-columns: 1fr;
.chart-container.large {
grid-column: span 1;
}
}
}
}
</style>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论