feat: 添加统计首页, 基础数据,课堂报告页面

This commit is contained in:
QDKF 2025-09-03 19:19:32 +08:00
parent 39020bd14c
commit a0cb27afe4
11 changed files with 1354 additions and 37 deletions

View File

@ -20,11 +20,13 @@
"axios": "^1.11.0",
"ckplayer": "^3.1.2",
"dplayer": "^1.27.1",
"echarts": "5.6.0",
"naive-ui": "^2.42.0",
"naive-ui-editor": "^1.0.6",
"pinia": "^3.0.3",
"quill": "^2.0.3",
"vue": "^3.5.17",
"vue-echarts": "7.0.3",
"vue-i18n": "^9.14.5",
"vue-quill-editor": "^3.0.6",
"vue-router": "^4.5.1"

61
pnpm-lock.yaml generated
View File

@ -29,6 +29,9 @@ importers:
dplayer:
specifier: ^1.27.1
version: 1.27.1
echarts:
specifier: 5.6.0
version: 5.6.0
naive-ui:
specifier: ^2.42.0
version: 2.42.0(vue@3.5.18(typescript@5.9.2))
@ -44,6 +47,9 @@ importers:
vue:
specifier: ^3.5.17
version: 3.5.18(typescript@5.9.2)
vue-echarts:
specifier: 7.0.3
version: 7.0.3(@vue/runtime-core@3.5.18)(echarts@5.6.0)(vue@3.5.18(typescript@5.9.2))
vue-i18n:
specifier: ^9.14.5
version: 9.14.5(vue@3.5.18(typescript@5.9.2))
@ -930,6 +936,9 @@ packages:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
echarts@5.6.0:
resolution: {integrity: sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==}
electron-to-chromium@1.5.199:
resolution: {integrity: sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ==}
@ -1478,6 +1487,9 @@ packages:
treemate@0.3.11:
resolution: {integrity: sha512-M8RGFoKtZ8dF+iwJfAJTOH/SM4KluKOKRJpjCMhI8bG3qB74zrFoArKZ62ll0Fr3mqkMJiQOmWYkdYgDeITYQg==}
tslib@2.3.0:
resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
type@2.7.3:
resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==}
@ -1582,6 +1594,27 @@ packages:
vscode-uri@3.1.0:
resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
vue-demi@0.13.11:
resolution: {integrity: sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==}
engines: {node: '>=12'}
hasBin: true
peerDependencies:
'@vue/composition-api': ^1.0.0-rc.1
vue: ^3.0.0-0 || ^2.6.0
peerDependenciesMeta:
'@vue/composition-api':
optional: true
vue-echarts@7.0.3:
resolution: {integrity: sha512-/jSxNwOsw5+dYAUcwSfkLwKPuzTQ0Cepz1LxCOpj2QcHrrmUa/Ql0eQqMmc1rTPQVrh2JQ29n2dhq75ZcHvRDw==}
peerDependencies:
'@vue/runtime-core': ^3.0.0
echarts: ^5.5.1
vue: ^2.7.0 || ^3.1.1
peerDependenciesMeta:
'@vue/runtime-core':
optional: true
vue-i18n@9.14.5:
resolution: {integrity: sha512-0jQ9Em3ymWngyiIkj0+c/k7WgaPO+TNzjKSNq9BvBQaKJECqn9cd9fL4tkDhB5G1QBskGl9YxxbDAhgbFtpe2g==}
engines: {node: '>= 16'}
@ -1635,6 +1668,9 @@ packages:
resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==}
engines: {node: '>=18'}
zrender@5.6.1:
resolution: {integrity: sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==}
snapshots:
'@ampproject/remapping@2.3.0':
@ -2504,6 +2540,11 @@ snapshots:
es-errors: 1.3.0
gopd: 1.2.0
echarts@5.6.0:
dependencies:
tslib: 2.3.0
zrender: 5.6.1
electron-to-chromium@1.5.199: {}
entities@4.5.0: {}
@ -3061,6 +3102,8 @@ snapshots:
treemate@0.3.11: {}
tslib@2.3.0: {}
type@2.7.3: {}
typescript@5.9.2: {}
@ -3152,6 +3195,20 @@ snapshots:
vscode-uri@3.1.0: {}
vue-demi@0.13.11(vue@3.5.18(typescript@5.9.2)):
dependencies:
vue: 3.5.18(typescript@5.9.2)
vue-echarts@7.0.3(@vue/runtime-core@3.5.18)(echarts@5.6.0)(vue@3.5.18(typescript@5.9.2)):
dependencies:
echarts: 5.6.0
vue: 3.5.18(typescript@5.9.2)
vue-demi: 0.13.11(vue@3.5.18(typescript@5.9.2))
optionalDependencies:
'@vue/runtime-core': 3.5.18
transitivePeerDependencies:
- '@vue/composition-api'
vue-i18n@9.14.5(vue@3.5.18(typescript@5.9.2)):
dependencies:
'@intlify/core-base': 9.14.5
@ -3209,3 +3266,7 @@ snapshots:
yallist@3.1.1: {}
yoctocolors@2.1.1: {}
zrender@5.6.1:
dependencies:
tslib: 2.3.0

View File

@ -14,6 +14,35 @@ import '@/assets/fonts/庞门正道标题体3.0.ttf'
import '@/assets/fonts/DouyinSansBold.otf'
import '@/assets/fonts/Alibaba_PuHuiTi_2.0_55_Regular_85_Bold.ttf'
// ECharts 全局注册
import VChart from 'vue-echarts'
import { use } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
import { LineChart, BarChart, PieChart, ScatterChart } from 'echarts/charts'
import {
TitleComponent,
TooltipComponent,
LegendComponent,
GridComponent,
DataZoomComponent,
GraphicComponent
} from 'echarts/components'
// 注册 ECharts 组件
use([
CanvasRenderer,
LineChart,
BarChart,
PieChart,
ScatterChart,
TitleComponent,
TooltipComponent,
LegendComponent,
GridComponent,
DataZoomComponent,
GraphicComponent
])
// Naive UI
import {
create,
@ -173,6 +202,9 @@ app.use(router)
app.use(i18n)
app.use(naive)
// 全局注册 VChart 组件
app.component('v-chart', VChart)
// 初始化用户认证状态
const userStore = useUserStore()

View File

@ -52,7 +52,7 @@ import FileViewer from '@/views/teacher/course/FileViewer.vue'
import FolderBrowser from '@/views/teacher/course/FolderBrowser.vue'
import CertificateManagement from '@/views/teacher/certificate/CertificateManagement.vue'
import DiscussionManagement from '@/views/teacher/course/DiscussionManagement.vue'
import StatisticsManagement from '@/views/teacher/course/StatisticsManagement.vue'
import StatisticsManagement from '@/views/teacher/statistics/StatisticsManagement.vue'
import NotificationManagement from '@/views/teacher/course/NotificationManagement.vue'
import GeneralManagement from '@/views/teacher/course/GeneralManagement.vue'

View File

@ -1,36 +0,0 @@
<template>
<div class="statistics-management">
<div class="content-placeholder">
<h2>统计管理</h2>
<p>统计管理功能正在开发中...</p>
</div>
</div>
</template>
<script setup lang="ts">
//
</script>
<style scoped>
.statistics-management {
padding: 20px;
background: #fff;
height: 100%;
}
.content-placeholder {
text-align: center;
padding: 60px 20px;
}
.content-placeholder h2 {
font-size: 24px;
color: #333;
margin-bottom: 20px;
}
.content-placeholder p {
font-size: 16px;
color: #666;
}
</style>

View File

@ -0,0 +1,432 @@
<template>
<div class="statistics-management">
<!-- 顶部区域课程信息和图表 -->
<div class="top-section">
<div class="statistics-container">
<!-- 左侧课程信息面板 -->
<div class="course-info-panel">
<div class="course-thumbnail">
<img src="/images/teacher/fj.png" alt="课程封面" class="thumbnail-image" />
</div>
<div class="course-details">
<h3 class="course-name">课程名称课程名称课</h3>
<div class="course-meta">
<p class="instructor">课程讲师: 王建国</p>
<p class="activity">学生活跃度: 74</p>
</div>
<button class="course-details-btn" @click="goToCourseDetails">课程详情</button>
</div>
</div>
<!-- 右侧活动图表面板 -->
<div class="activity-chart-panel">
<h3 class="chart-title">近7日课程活跃数: 20</h3>
<div class="chart-container">
<v-chart :option="chartOption" autoresize style="height: 100%;" />
</div>
</div>
</div>
</div>
<!-- 中间区域标签页 -->
<div class="middle-section">
<div class="tab-container">
<div class="tab-bar">
<div
v-for="(tab, index) in tabs"
:key="index"
class="tab-item"
:class="{ active: activeTab === index }"
@click="switchTab(index)"
>
<span>{{ tab.name }}</span>
</div>
</div>
</div>
</div>
<!-- Tab内容区域 -->
<div class="tab-content">
<transition name="tab-fade" mode="out-in">
<component :is="currentTabComponent" :key="activeTab" />
</transition>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import type { EChartsOption } from 'echarts'
// tab
import BasicData from './tab/BasicData.vue'
import ClassroomReport from './tab/ClassroomReport.vue'
import LearningStatistics from './tab/LearningStatistics.vue'
import StudentGrades from './tab/StudentGrades.vue'
import LearningMonitor from './tab/LearningMonitor.vue'
//
const router = useRouter()
const route = useRoute()
// Tab
const activeTab = ref(0)
// Tab
const tabs = [
{ name: '基础数据', component: BasicData },
{ name: '课堂报告', component: ClassroomReport },
{ name: '学情统计', component: LearningStatistics },
{ name: '学生成绩', component: StudentGrades },
{ name: '学习监控', component: LearningMonitor }
]
// tab
const currentTabComponent = computed(() => {
return tabs[activeTab.value].component
})
// tab
const switchTab = (index: number) => {
activeTab.value = index
}
//
const goToCourseDetails = () => {
// ID
const courseId = route.params.id || '1' // ID使
router.push(`/teacher/course-detail/${courseId}`)
}
// ECharts
const chartOption = ref<EChartsOption>({
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'line',
lineStyle: {
type: 'dashed',
color: '#ccc'
}
},
formatter: function(params: any) {
return `<div style="padding: 8px;">
<div style="font-size: 12px; color: #999; margin-bottom: 4px;">活跃数</div>
<div style="font-size: 16px; font-weight: bold; color: #333;">${params[0].value}</div>
</div>`
}
},
grid: {
left: '2%',
right: '6%',
bottom: '15%',
top: '15%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['2022-04-02', '2022-04-05', '2022-04-08', '2022-04-11', '2022-04-14', '2022-04-17'],
axisLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
color: '#666',
fontSize: 9,
margin: 8
},
splitLine: {
show: true,
lineStyle: {
type: 'dashed',
color: '#f0f0f0'
}
}
},
yAxis: {
type: 'value',
min: 0,
max: 14000,
interval: 2000,
axisLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
color: '#666',
fontSize: 9,
margin: 8
},
splitLine: {
show: true,
lineStyle: {
type: 'dashed',
color: '#f0f0f0'
}
}
},
series: [
{
name: '活跃数',
type: 'line',
smooth: false,
symbol: 'none',
lineStyle: {
color: '#2196F3',
width: 1.5
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: 'rgba(33, 150, 243, 0.25)'
},
{
offset: 1,
color: 'rgba(33, 150, 243, 0.02)'
}
]
}
},
data: [2000, 10500, 12000, 9800, 11000, 7500]
}
]
})
console.log('CourseStatistics component loaded with ECharts and Tab functionality')
</script>
<style scoped>
.statistics-management {
background: #fff;
min-height: 100vh;
}
/* 顶部区域 */
.top-section {
margin-bottom: 24px;
}
.statistics-container {
padding: 20px;
display: flex;
justify-content: space-between;
gap: 20px;
margin: 0 auto;
border-bottom: 1.5px solid #F1F3F4;
}
/* 调整左右面板比例左侧75%右侧25% */
.course-info-panel {
flex: 0 0 60%;
background: white;
display: flex;
gap: 12px;
align-items: center;
}
.activity-chart-panel {
flex: 0 0 30%;
background: white;
border-radius: 8px;
padding: 8px;
/* 从12px减少到8px */
}
/* 左侧课程信息面板样式 */
.course-thumbnail {
flex-shrink: 0;
}
.thumbnail-image {
width: 194px;
height: 136px;
object-fit: cover;
background: linear-gradient(135deg, #4CAF50, #8BC34A, #FFEB3B);
}
.course-details {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
.course-name {
font-size: 18px;
font-weight: 500;
color: #333;
margin: 0;
line-height: 1.3;
margin-bottom: 15px;
}
.course-meta {
display: flex;
flex-direction: column;
gap: 4px;
}
.instructor,
.activity {
font-size: 14px;
color: #333;
line-height: 1.3;
margin: 0;
}
.course-details-btn {
background-color: #fff;
align-self: flex-start;
border: 1.5px solid #0C99DA;
color: #0C99DA;
padding: 6px 12px;
border-radius: 2px;
font-size: 12px;
cursor: pointer;
transition: all 0.3s ease;
margin-top: 4px;
}
.course-details-btn:hover {
background: #BBDEFB;
border-color: #1976D2;
color: #1976D2;
}
/* 右侧活动图表面板样式 */
.chart-title {
padding-left: 40px;
font-size: 14px;
font-weight: 500;
color: #666;
margin: 0 0 16px 0;
}
.chart-container {
position: relative;
height: 120px;
/* 从160px减少到120px */
}
/* ECharts容器样式 */
.chart-container :deep(.echarts) {
width: 100% !important;
height: 100% !important;
}
/* 中间区域:标签页 */
.middle-section {
margin-bottom: 24px;
}
.tab-container {
display: flex;
border-bottom: 1.5px solid #F1F3F4;
padding-bottom: 24px;
}
.tab-bar {
display: flex;
background: white;
border: 1.5px solid #0C99DA;
border-radius: 4px;
overflow: hidden;
margin-left: 30px;
}
.tab-item {
min-width: 151px;
flex: 1;
padding: 5px 10px;
cursor: pointer;
transition: all 0.3s ease;
text-align: center;
background: white;
color: #2196F3;
font-size: 16px;
font-weight: 500;
border-right: 1.5px solid #0C99DA;
position: relative;
}
.tab-item:last-child {
border-right: none;
}
.tab-item.active {
background: #0C99DA;
color: white;
font-weight: 600;
}
.tab-item:hover:not(.active) {
background: #F5F9FF;
color: #1976D2;
}
/* Tab内容区域 */
.tab-content {
background: white;
min-height: 200px;
}
/* Tab切换过渡动画 */
.tab-fade-enter-active,
.tab-fade-leave-active {
transition: all 0.3s ease;
}
.tab-fade-enter-from {
opacity: 0;
transform: translateX(20px);
}
.tab-fade-leave-to {
opacity: 0;
transform: translateX(-20px);
}
/* 响应式设计 */
@media (max-width: 768px) {
.statistics-container {
flex-direction: column;
}
.course-info-panel {
flex-direction: column;
text-align: center;
}
.course-details-btn {
align-self: center;
}
.tab-bar {
flex-wrap: wrap;
}
.tab-item {
flex: none;
min-width: 120px;
}
}
</style>

View File

@ -0,0 +1,295 @@
<template>
<div class="basic-data">
<!-- 顶部统计卡片区域 -->
<div class="stats-cards">
<div class="stats-row">
<div class="stat-card">
<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-card">
<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-card">
<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-card">
<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-card">
<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-card">
<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-card">
<div class="stat-label">证书</div>
<div class="stat-value">
<span class="stat-number">0</span>
<span class="stat-unit"></span>
</div>
</div>
</div>
</div>
<!-- 教学建设区域 -->
<div class="teaching-construction">
<h3 class="section-title">教学建设</h3>
<div class="construction-grid">
<!-- 课件/视频 -->
<div class="construction-card blue" style="background-image: url('/images/teacher/teaching-construction1.png');">
<div class="card-icon">
<img src="/images/teacher/teaching-construction1.png" alt="课件/视频" />
</div>
<div class="card-content">
<div class="card-number">20</div>
<div class="card-label">课件/视频</div>
</div>
</div>
<!-- 资料/文档 -->
<div class="construction-card orange" style="background-image: url('/images/teacher/teaching-construction2.png');">
<div class="card-icon">
<img src="/images/teacher/teaching-construction2.png" alt="资料/文档" />
</div>
<div class="card-content">
<div class="card-number">139</div>
<div class="card-label">资料/文档</div>
</div>
</div>
<!-- 题库总数 -->
<div class="construction-card blue-gray" style="background-image: url('/images/teacher/teaching-construction3.png');">
<div class="card-icon">
<img src="/images/teacher/teaching-construction3.png" alt="题库总数" />
</div>
<div class="card-content">
<div class="card-number">862</div>
<div class="card-label">题库总数</div>
</div>
</div>
<!-- 试卷总数 -->
<div class="construction-card yellow" style="background-image: url('/images/teacher/teaching-construction4.png');">
<div class="card-icon">
<img src="/images/teacher/teaching-construction4.png" alt="试卷总数" />
</div>
<div class="card-content">
<div class="card-number">10</div>
<div class="card-label">试卷总数</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
console.log('BasicData component loaded')
</script>
<style scoped>
.basic-data {
padding: 0 30px;
}
/* 顶部统计卡片区域 */
.stats-cards {
margin-bottom: 0;
}
.stats-row {
display: flex;
gap: 16px;
justify-content: space-between;
border: 1px solid #F1F3F4;
background-color: #FCFCFC;
border-radius: 2px;
padding: 20px;
}
.stat-card {
flex: 1;
text-align: center;
min-height: 80px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.stat-label {
font-size: 12px;
color: #999999;
margin-bottom: 8px;
line-height: 1.2;
white-space: nowrap;
width: 50px;
text-align: left;
}
.stat-value {
display: flex;
align-items: baseline;
gap: 2px;
line-height: 1.2;
white-space: nowrap;
width: 50px;
text-align: left;
}
.stat-number {
font-size: 18px;
font-weight: 500;
color: #333;
}
.stat-unit {
font-size: 10px;
color: #666;
}
/* 教学建设区域 */
.teaching-construction {
margin-top: 26px;
}
.section-title {
font-size: 16px;
font-weight: 500;
color: #333;
margin: 0 0 12px 0;
text-align: left;
}
.construction-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 30px;
}
.construction-card {
background: white;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
border-radius: 2px;
display: flex;
align-items: center;
gap: 24px;
transition: transform 0.3s ease;
position: relative;
overflow: hidden;
min-height: 95px;
padding: 40px;
}
.construction-card:hover {
transform: translateY(-2px);
}
.card-icon {
width: 48px;
height: 48px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
position: relative;
z-index: 2;
opacity: 0;
}
.card-icon img {
width: 20px;
height: 20px;
filter: brightness(0) invert(1); /* 使图标变为白色 */
}
.card-content {
text-align: left;
position: relative;
z-index: 2;
}
.card-number {
font-size: 22px;
font-weight: 500;
color: #646464;
margin-bottom: 10px;
line-height: 1;
}
.card-label {
font-size: 14px;
color: #646464;
line-height: 1.2;
}
/* 卡片颜色主题 */
.construction-card.blue .card-icon {
background: #2196F3;
}
.construction-card.orange .card-icon {
background: #FF9800;
}
.construction-card.blue-gray .card-icon {
background: #607D8B;
}
.construction-card.yellow .card-icon {
background: #FFC107;
}
/* 响应式设计 */
@media (max-width: 768px) {
.stats-row {
flex-wrap: wrap;
gap: 12px;
}
.stat-card {
flex: 1 1 calc(50% - 6px);
min-width: 120px;
}
.construction-grid {
grid-template-columns: repeat(2, 1fr);
max-width: 100%;
}
}
@media (max-width: 480px) {
.stat-card {
flex: 1 1 100%;
}
}
</style>

View File

@ -0,0 +1,429 @@
<template>
<div class="classroom-report">
<!-- 课程访问量图表 -->
<div class="chart-section">
<div class="chart-header">
<h3 class="chart-title">课程访问量</h3>
<div class="time-selector">
<span
class="time-option"
:class="{ active: visitVolumeTimeRange === 'week' }"
@click="switchVisitVolumeTimeRange('week')"
>
本周
</span>
<span
class="time-option"
:class="{ active: visitVolumeTimeRange === 'month' }"
@click="switchVisitVolumeTimeRange('month')"
>
本月
</span>
</div>
</div>
<div class="chart-container">
<v-chart :option="visitVolumeOption" style="height: 100%;" />
</div>
</div>
<!-- 课程互动图表 -->
<div class="chart-section">
<div class="chart-header chart-header2">
<h3 class="chart-title">课程互动</h3>
<div class="time-selector">
<span
class="time-option"
:class="{ active: interactionTimeRange === 'week' }"
@click="switchInteractionTimeRange('week')"
>
本周
</span>
<span
class="time-option"
:class="{ active: interactionTimeRange === 'month' }"
@click="switchInteractionTimeRange('month')"
>
本月
</span>
</div>
</div>
<div class="chart-wrapper">
<div class="chart-legend">
<div class="legend-item">
<div class="legend-color" style="background-color: #81A2FF;"></div>
<span>评论</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #3DC8F4;"></div>
<span>弹幕</span>
</div>
</div>
<div class="chart-container">
<v-chart :option="interactionOption" style="height: 100%;" />
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import type { EChartsOption } from 'echarts'
//
const visitVolumeTimeRange = ref<'week' | 'month'>('week')
const interactionTimeRange = ref<'week' | 'month'>('week')
const switchVisitVolumeTimeRange = (range: 'week' | 'month') => {
visitVolumeTimeRange.value = range
}
const switchInteractionTimeRange = (range: 'week' | 'month') => {
interactionTimeRange.value = range
}
//
const weekData = {
dates: ['7.11', '7.12', '7.13', '7.14', '7.15', '7.16', '7.18'],
visitVolume: [280, 390, 180, 300, 130, 380, 250],
comments: [240, 420, 260, 390, 240, 370, 220],
bulletComments: [160, 300, 120, 200, 180, 280, 180]
}
const monthData = {
dates: ['7.1', '7.5', '7.10', '7.15', '7.20', '7.25', '7.30'],
visitVolume: [200, 350, 280, 400, 320, 380, 300],
comments: [180, 320, 250, 380, 300, 350, 280],
bulletComments: [120, 250, 180, 280, 220, 300, 200]
}
//
const visitVolumeData = computed(() => {
return visitVolumeTimeRange.value === 'week' ? weekData : monthData
})
const interactionData = computed(() => {
return interactionTimeRange.value === 'week' ? weekData : monthData
})
// 访
const visitVolumeOption = computed<EChartsOption>(() => ({
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
grid: {
left: '2%',
right: '1%',
top: '14%',
bottom: '6%',
containLabel: true
},
xAxis: {
type: 'category',
data: visitVolumeData.value.dates,
axisLine: {
lineStyle: {
color: '#C0CEE7'
}
},
axisTick: {
show: false
},
axisLabel: {
color: '#BDC6D8',
fontSize: 12
}
},
yAxis: {
type: 'value',
max: 400,
interval: 100,
axisLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
color: '#BDC6D8',
fontSize: 12
},
splitLine: {
lineStyle: {
color: '#F5F5F5',
type: 'dashed'
}
}
},
series: [
{
name: '背景',
type: 'bar',
data: [400, 400, 400, 400, 400, 400, 400],
itemStyle: {
color: '#F8F8FC'
},
barWidth: '8px',
barGap: '-100%',
silent: true
},
{
name: '访问量',
type: 'bar',
data: visitVolumeData.value.visitVolume,
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: '#39E7E8' },
{ offset: 1, color: '#0288D1' }
]
}
},
barWidth: '8px',
emphasis: {
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: '#4FE8E9' },
{ offset: 1, color: '#039BE5' }
]
}
}
}
}
]
}))
//
const interactionOption = computed<EChartsOption>(() => ({
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'line'
}
},
grid: {
left: '2%',
right: '1%',
bottom: '8%',
top: '12%',
containLabel: true
},
xAxis: {
type: 'category',
data: interactionData.value.dates,
axisLine: {
lineStyle: {
color: '#C0CEE7'
}
},
axisTick: {
show: false
},
axisLabel: {
color: '#BDC6D8',
fontSize: 12
}
},
yAxis: {
type: 'value',
max: 500,
interval: 100,
axisLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
color: '#BDC6D8',
fontSize: 12
},
splitLine: {
lineStyle: {
color: '#F5F5F5',
type: 'dashed'
}
}
},
series: [
{
name: '评论',
type: 'line',
smooth: true,
data: interactionData.value.comments,
lineStyle: {
color: '#81A2FF',
width: 2
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(129, 162, 255, 0.3)' },
{ offset: 1, color: 'rgba(129, 162, 255, 0.05)' }
]
}
},
itemStyle: {
color: '#81A2FF'
},
symbol: 'none'
},
{
name: '弹幕',
type: 'line',
smooth: true,
data: interactionData.value.bulletComments,
lineStyle: {
color: '#3DC8F4',
width: 2
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(61, 200, 244, 0.2)' },
{ offset: 1, color: 'rgba(61, 200, 244, 0.02)' }
]
}
},
itemStyle: {
color: '#3DC8F4'
},
symbol: 'none'
}
]
}))
</script>
<style scoped>
.classroom-report {
padding: 0 30px 20px;
}
.chart-section {
margin-bottom: 24px;
}
.chart-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.chart-header2 {
margin-bottom: 35px;
}
.chart-title {
font-size: 16px;
font-weight: 500;
color: #333;
margin: 0;
}
.time-selector {
display: flex;
padding: 4px;
}
.time-option {
padding: 6px 16px;
font-size: 16px;
color: #333333;
cursor: pointer;
transition: all 0.3s ease;
}
.time-option.active {
color: #0C99DA;
}
.time-option:hover:not(.active) {
background: #E3F2FD;
color: #1976D2;
}
.chart-wrapper {
position: relative;
}
.chart-legend {
position: absolute;
top: -30px;
left: 0;
display: flex;
gap: 16px;
z-index: 10;
}
.legend-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: #666;
}
.legend-color {
width: 12px;
height: 12px;
border-radius: 50%;
}
.chart-container {
width: 100%;
height: 210px;
border: 1px solid #E6E6E6;
border-radius: 4px;
overflow: hidden;
}
/* 响应式设计 */
@media (max-width: 768px) {
.classroom-report {
padding: 0 16px;
}
.chart-section {
padding: 16px;
margin-bottom: 16px;
}
.chart-header {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
.time-selector {
align-self: flex-end;
}
}
</style>

View File

@ -0,0 +1,34 @@
<template>
<div class="learning-monitor">
<div class="content-placeholder">
<h3>学习监控</h3>
<p>学习监控功能正在开发中...</p>
</div>
</div>
</template>
<script setup lang="ts">
console.log('LearningMonitor component loaded')
</script>
<style scoped>
.learning-monitor {
padding: 20px;
}
.content-placeholder {
text-align: center;
padding: 40px 20px;
}
.content-placeholder h3 {
font-size: 18px;
color: #333;
margin-bottom: 16px;
}
.content-placeholder p {
font-size: 14px;
color: #666;
}
</style>

View File

@ -0,0 +1,34 @@
<template>
<div class="learning-statistics">
<div class="content-placeholder">
<h3>学情统计</h3>
<p>学情统计功能正在开发中...</p>
</div>
</div>
</template>
<script setup lang="ts">
console.log('LearningStatistics component loaded')
</script>
<style scoped>
.learning-statistics {
padding: 20px;
}
.content-placeholder {
text-align: center;
padding: 40px 20px;
}
.content-placeholder h3 {
font-size: 18px;
color: #333;
margin-bottom: 16px;
}
.content-placeholder p {
font-size: 14px;
color: #666;
}
</style>

View File

@ -0,0 +1,34 @@
<template>
<div class="student-grades">
<div class="content-placeholder">
<h3>学生成绩</h3>
<p>学生成绩功能正在开发中...</p>
</div>
</div>
</template>
<script setup lang="ts">
console.log('StudentGrades component loaded')
</script>
<style scoped>
.student-grades {
padding: 20px;
}
.content-placeholder {
text-align: center;
padding: 40px 20px;
}
.content-placeholder h3 {
font-size: 18px;
color: #333;
margin-bottom: 16px;
}
.content-placeholder p {
font-size: 14px;
color: #666;
}
</style>