430 lines
8.6 KiB
Vue

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