feat: 完成学情统计和学生成绩页面开发:新增LearningStatistics.vue学情统计页面,包含6个统计模块和ECharts图表,新增StudentGrades.vue学生成绩页面,支持成绩管理、权重设置和提醒功能

This commit is contained in:
QDKF 2025-09-04 17:23:05 +08:00
parent dd11e9aa2d
commit a30e23ab07
2 changed files with 1703 additions and 28 deletions

View File

@ -1,34 +1,676 @@
<template>
<div class="learning-statistics">
<div class="content-placeholder">
<h3>学情统计</h3>
<p>学情统计功能正在开发中...</p>
<!-- 班级选择 -->
<div class="class-selector">
<div class="selector-container">
<select class="class-select" v-model="selectedClass">
<option value="">班级名称</option>
<option value="class1">计算机科学1班</option>
<option value="class2">计算机科学2班</option>
<option value="class3">软件工程1班</option>
</select>
<div class="selector-arrow">
<img src="/images/teacher/箭头-黑.png" alt="箭头" />
</div>
</div>
</div>
</div>
</template>
<!-- 统计内容区域 -->
<div class="statistics-content">
<!-- 章节统计 -->
<div class="statistics-card">
<h3 class="card-title">
<span class="title-main">章节</span>
<span class="title-sub">(共4章5小节)</span>
</h3>
<div class="card-content">
<!-- 左侧文本统计 -->
<div class="text-stats">
<div class="stat-item">
<div class="stat-label">章节学习总人次</div>
<div class="stat-value">
<span class="stat-number">0</span>
<span class="stat-unit"></span>
</div>
</div>
<div class="stat-item">
<div class="stat-label">章节平均学习次数</div>
<div class="stat-value">
<span class="stat-number">0</span>
<span class="stat-unit"></span>
</div>
</div>
</div>
<!-- 右侧饼图 -->
<div class="chart-section">
<h4 class="chart-title">学习次数分布</h4>
<div class="chart-container">
<v-chart :option="learningDistributionOption" style="height: 100%;" />
</div>
</div>
</div>
</div>
<!-- 作业统计 -->
<div class="statistics-card">
<h3 class="card-title">
<span class="title-main">作业</span>
<span class="title-sub">(共5个)</span>
</h3>
<div class="card-content">
<div class="chart-section full-width">
<h4 class="chart-title">作业完成率</h4>
<div class="chart-container">
<v-chart :option="assignmentCompletionOption" style="height: 100%;" />
</div>
</div>
</div>
</div>
</div>
<!-- 考试和练习容器 -->
<div class="exam-practice-container">
<!-- 考试统计卡片 -->
<div class="statistics-card">
<h3 class="card-title">
<span class="title-main">考试</span>
<span class="title-sub">(共5场)</span>
</h3>
<div class="card-content">
<!-- 左侧文本统计 -->
<div class="text-stats">
<div class="stat-item">
<div class="stat-label">参与人数</div>
<div class="stat-value">
<span class="stat-number">70</span>
<span class="stat-unit"></span>
</div>
</div>
<div class="stat-item">
<div class="stat-label">平均成绩</div>
<div class="stat-value">
<span class="stat-number">60</span>
<span class="stat-unit"></span>
</div>
</div>
</div>
<!-- 右侧饼图 -->
<div class="chart-section">
<h4 class="chart-title">成绩占比</h4>
<div class="chart-container">
<v-chart :option="examScoreOption" style="height: 100%;" />
</div>
</div>
</div>
</div>
<!-- 练习统计卡片 -->
<div class="statistics-card">
<h3 class="card-title">
<span class="title-main">练习</span>
<span class="title-sub">(共5场)</span>
</h3>
<div class="card-content">
<!-- 左侧文本统计 -->
<div class="text-stats">
<div class="stat-item">
<div class="stat-label">参与人数</div>
<div class="stat-value">
<span class="stat-number">70</span>
<span class="stat-unit"></span>
</div>
</div>
<div class="stat-item">
<div class="stat-label">平均成绩</div>
<div class="stat-value">
<span class="stat-number">60</span>
<span class="stat-unit"></span>
</div>
</div>
</div>
<!-- 右侧饼图 -->
<div class="chart-section">
<h4 class="chart-title">成绩占比</h4>
<div class="chart-container">
<v-chart :option="practiceScoreOption" style="height: 100%;" />
</div>
</div>
</div>
</div>
</div>
<!-- 证书和讨论容器 -->
<div class="exam-practice-container">
<!-- 证书统计卡片 -->
<div class="statistics-card">
<h3 class="card-title">
<span class="title-main">证书</span>
<span class="title-sub">(共1个)</span>
</h3>
<div class="card-content">
<div class="chart-section full-width">
<h4 class="chart-title">证书获取率</h4>
<div class="chart-container">
<v-chart :option="certificateOption" style="height: 100%;" />
</div>
</div>
</div>
</div>
<!-- 讨论统计卡片 -->
<div class="statistics-card">
<h3 class="card-title">
<span class="title-main">讨论</span>
<span class="title-sub">(共5个)</span>
</h3>
<div class="card-content">
<!-- 左侧文本统计 -->
<div class="text-stats">
<div class="stat-item">
<div class="stat-label">讨论话题</div>
<div class="stat-value">
<span class="stat-number">7</span>
<span class="stat-unit"></span>
</div>
</div>
<div class="stat-item">
<div class="stat-label">回复数量</div>
<div class="stat-value">
<span class="stat-number">60</span>
<span class="stat-unit"></span>
</div>
</div>
</div>
<!-- 右侧饼图 -->
<div class="chart-section">
<h4 class="chart-title">讨论活跃度</h4>
<div class="chart-container">
<v-chart :option="discussionOption" style="height: 100%;" />
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
console.log('LearningStatistics component loaded')
import { ref, computed } from 'vue'
import type { EChartsOption } from 'echarts'
//
const selectedClass = ref('')
//
const learningDistributionOption = computed<EChartsOption>(() => ({
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
right: 8,
top: 'center',
itemWidth: 8,
itemHeight: 8,
itemGap: 25,
textStyle: {
fontSize: 11,
color: '#666'
},
icon: 'circle',
formatter: function(name: string) {
return name.replace(' ', ' ');
}
},
series: [
{
name: '学习次数分布',
type: 'pie',
radius: ['70%', '85%'],
center: ['28%', '50%'],
avoidLabelOverlap: false,
label: {
show: false
},
emphasis: {
label: {
show: false,
fontSize: '14',
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: [
{ value: 60, name: '学习一次 60次', itemStyle: { color: '#FF9F7F' } },
{ value: 20, name: '学习二次 20次', itemStyle: { color: '#3A8BFF' } },
{ value: 20, name: '学习三次 20次', itemStyle: { color: '#9FE6B8' } }
]
}
]
}))
//
const assignmentCompletionOption = computed<EChartsOption>(() => ({
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: '53%',
top: 'center',
itemWidth: 8,
itemHeight: 8,
itemGap: 35,
textStyle: {
fontSize: 11,
color: '#666'
},
icon: 'circle',
formatter: function(name: string) {
return name.replace(' ', ' ');
}
},
series: [
{
name: '作业完成率',
type: 'pie',
radius: ['70%', '85%'],
center: ['38%', '50%'],
avoidLabelOverlap: false,
label: {
show: false
},
emphasis: {
label: {
show: false,
fontSize: '14',
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: [
{ value: 60, name: '已完成 60%', itemStyle: { color: '#0C99DA' } },
{ value: 40, name: '未完成 40%', itemStyle: { color: '#9FE6B8' } }
]
}
]
}))
//
const examScoreOption = computed<EChartsOption>(() => ({
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
right: 8,
top: 'center',
itemWidth: 8,
itemHeight: 8,
itemGap: 25,
textStyle: {
fontSize: 11,
color: '#666'
},
icon: 'circle',
formatter: function(name: string) {
return name.replace(' ', ' ');
}
},
series: [
{
name: '成绩占比',
type: 'pie',
radius: ['70%', '85%'],
center: ['28%', '50%'],
avoidLabelOverlap: false,
label: {
show: false
},
emphasis: {
label: {
show: false,
fontSize: '14',
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: [
{ value: 60, name: '60分以下 60人', itemStyle: { color: '#3A8BFF' } },
{ value: 20, name: '60-79分 20人', itemStyle: { color: '#FF9F7F' } },
{ value: 20, name: '80分以上 20人', itemStyle: { color: '#9FE6B8' } }
]
}
]
}))
//
const practiceScoreOption = computed<EChartsOption>(() => ({
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
right: 8,
top: 'center',
itemWidth: 8,
itemHeight: 8,
itemGap: 25,
textStyle: {
fontSize: 11,
color: '#666'
},
icon: 'circle',
formatter: function(name: string) {
return name.replace(' ', ' ');
}
},
series: [
{
name: '成绩占比',
type: 'pie',
radius: ['70%', '85%'],
center: ['28%', '50%'],
avoidLabelOverlap: false,
label: {
show: false
},
emphasis: {
label: {
show: false,
fontSize: '14',
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: [
{ value: 60, name: '60分以下 60人', itemStyle: { color: '#3A8BFF' } },
{ value: 20, name: '60-79分 20人', itemStyle: { color: '#FF9F7F' } },
{ value: 20, name: '80分以上 20人', itemStyle: { color: '#9FE6B8' } }
]
}
]
}))
//
const certificateOption = computed<EChartsOption>(() => ({
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: '53%',
top: 'center',
itemWidth: 8,
itemHeight: 8,
itemGap: 35,
textStyle: {
fontSize: 11,
color: '#666'
},
icon: 'circle',
formatter: function(name: string) {
return name.replace(' ', ' ');
}
},
series: [
{
name: '证书获取率',
type: 'pie',
radius: ['70%', '85%'],
center: ['38%', '50%'],
avoidLabelOverlap: false,
label: {
show: false
},
emphasis: {
label: {
show: false,
fontSize: '14',
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: [
{ value: 60, name: '已获得 60%', itemStyle: { color: '#0C99DA' } },
{ value: 40, name: '未获得 40%', itemStyle: { color: '#9FE6B8' } }
]
}
]
}))
//
const discussionOption = computed<EChartsOption>(() => ({
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
right: 8,
top: 'center',
itemWidth: 8,
itemHeight: 8,
itemGap: 25,
textStyle: {
fontSize: 11,
color: '#666'
},
icon: 'circle',
formatter: function(name: string) {
return name.replace(' ', ' ');
}
},
series: [
{
name: '讨论活跃度',
type: 'pie',
radius: ['70%', '85%'],
center: ['28%', '50%'],
avoidLabelOverlap: false,
label: {
show: false
},
emphasis: {
label: {
show: false,
fontSize: '14',
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: [
{ value: 40, name: '活跃 40%', itemStyle: { color: '#3A8BFF' } },
{ value: 60, name: '一般 60%', itemStyle: { color: '#FF9F7F' } }
]
}
]
}))
</script>
<style scoped>
.learning-statistics {
padding: 0 24px 24px;
background: #fff;
min-height: 100vh;
}
/* 班级选择器 */
.class-selector {
margin-bottom: 24px;
}
.selector-container {
position: relative;
display: inline-block;
}
.class-select {
width: 200px;
height: 36px;
padding: 8px 12px;
border: 1px solid #F1F3F4;
border-radius: 4px;
background: #fff;
font-size: 14px;
color: #062333;
appearance: none;
cursor: pointer;
}
.selector-arrow {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
pointer-events: none;
}
.selector-arrow img {
width: 14px;
height: 8px;
}
/* 统计内容区域 */
.statistics-content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
margin-bottom: 20px;
}
/* 考试和练习卡片容器 */
.exam-practice-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
margin-bottom: 20px;
}
/* 统计卡片 */
.statistics-card {}
.card-title {
color: #333;
margin: 0 0 16px 0;
font-weight: normal;
}
.title-main {
font-size: 16px;
margin-right: 8px;
}
.title-sub {
font-size: 14px;
}
.card-content {
display: flex;
gap: 16px;
align-items: center;
border: 1.5px solid #E6E6E6;
border-radius: 2px;
padding: 20px;
}
.content-placeholder {
text-align: center;
padding: 40px 20px;
/* 文本统计 */
.text-stats {
width: 42%;
padding-right: 30px;
border-right: 1.5px solid #E6E6E6;
}
.content-placeholder h3 {
.stat-item {
margin-bottom: 8px;
background-color: #F5F8FB;
border-radius: 4px;
padding: 12px 16px;
}
.stat-item:last-child {
margin-bottom: 0;
}
.stat-label {
font-size: 14px;
color: #333;
margin-bottom: 4px;
}
.stat-value {
display: flex;
align-items: baseline;
gap: 2px;
}
.stat-number {
font-size: 18px;
font-weight: normal;
color: #333;
margin-bottom: 16px;
}
.content-placeholder p {
font-size: 14px;
color: #666;
.stat-unit {
font-size: 12px;
font-weight: normal;
color: #333;
}
</style>
/* 图表区域 */
.chart-section {
margin-left: 10px;
width: 50%;
}
.chart-section.full-width {
width: 100%;
}
.chart-title {
font-size: 14px;
color: #333;
margin-bottom: 5px;
font-weight: 500;
text-align: left;
}
.chart-container {
height: 140px;
width: 100%;
}
/* 响应式设计 */
@media (max-width: 768px) {
.statistics-content {
grid-template-columns: 1fr;
}
.card-content {
flex-direction: column;
align-items: stretch;
}
.chart-container {
height: 120px;
}
}
</style>

File diff suppressed because it is too large Load Diff