| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781 |
- <template>
- <div class="video-analysis-container">
- <!-- 页面头部 -->
- <div class="header">
- <h1>📊 视频分析</h1>
- <p>为教学过程及课程内容改进提供数据支撑</p>
- </div>
- <!-- 筛选条件 -->
- <div class="filter-section">
- <h3>🔍 数据筛选</h3>
- <div class="filter-controls">
- <div class="filter-group">
- <label>选择课程</label>
- <a-select v-model:value="filters.courseId" placeholder="全部课程" style="width: 100%">
- <a-select-option value="">全部课程</a-select-option>
- <a-select-option value="course1">JavaScript基础教程</a-select-option>
- <a-select-option value="course2">Python数据分析</a-select-option>
- <a-select-option value="course3">React前端开发</a-select-option>
- <a-select-option value="course4">机器学习入门</a-select-option>
- </a-select>
- </div>
- <div class="filter-group">
- <label>时间范围</label>
- <a-select v-model:value="filters.timeRange" style="width: 100%">
- <a-select-option value="7">最近7天</a-select-option>
- <a-select-option value="30">最近30天</a-select-option>
- <a-select-option value="90">最近90天</a-select-option>
- <a-select-option value="365">最近一年</a-select-option>
- </a-select>
- </div>
- <div class="filter-group">
- <a-button type="primary" @click="updateStats">查询</a-button>
- </div>
- </div>
- </div>
- <!-- 核心统计数据 -->
- <div class="stats-grid">
- <div class="stat-card">
- <h3>👥 观看人数统计</h3>
- <div class="stat-number">{{ stats.totalViewers.toLocaleString() }}</div>
- <div class="stat-label">总观看人数</div>
- <div class="stat-number">{{ stats.completedViewers.toLocaleString() }}</div>
- <div class="stat-label">完成观看人数</div>
- <div class="completion-rate">{{ stats.completionRate }}%</div>
- <div class="stat-label">完成率</div>
- </div>
- <div class="stat-card">
- <h3>📥 讲义下载统计</h3>
- <div class="stat-number">{{ stats.totalDownloads.toLocaleString() }}</div>
- <div class="stat-label">总下载次数</div>
- <div class="stat-number">{{ stats.downloadRate }}%</div>
- <div class="stat-label">下载率</div>
- <div class="stat-number">{{ stats.avgDownloads }}</div>
- <div class="stat-label">人均下载次数</div>
- </div>
- <div class="stat-card">
- <h3>⏱️ 跳出时间分析</h3>
- <div class="stat-number">{{ stats.totalExits.toLocaleString() }}</div>
- <div class="stat-label">总跳出次数</div>
- <div class="stat-number">{{ stats.exitRate }}%</div>
- <div class="stat-label">跳出率</div>
- <div class="stat-number">{{ stats.avgExitTime }}</div>
- <div class="stat-label">平均跳出时间</div>
- </div>
- <div class="stat-card">
- <h3>📝 互动数据统计</h3>
- <div class="stat-number">{{ stats.totalNotes.toLocaleString() }}</div>
- <div class="stat-label">笔记总数</div>
- <div class="stat-number">{{ stats.totalDiscussions.toLocaleString() }}</div>
- <div class="stat-label">讨论总数</div>
- <div class="stat-number">{{ stats.totalReplies.toLocaleString() }}</div>
- <div class="stat-label">回帖总数</div>
- </div>
- </div>
- <!-- ECharts 图表区域 -->
- <div class="charts-section">
- <!-- 学习进度分布图 -->
- <div class="chart-container">
- <h3>📈 学习进度分布</h3>
- <div ref="progressChart" class="chart"></div>
- </div>
- <!-- 观看时长趋势图 -->
- <div class="chart-container">
- <h3>📊 观看时长趋势</h3>
- <div ref="timeChart" class="chart"></div>
- </div>
- <!-- 章节完成率对比图 -->
- <div class="chart-container">
- <h3>📚 章节完成率对比</h3>
- <div ref="chapterChart" class="chart"></div>
- </div>
- <!-- 互动数据统计图 -->
- <div class="chart-container">
- <h3>💬 互动数据统计</h3>
- <div ref="interactionChart" class="chart"></div>
- </div>
- </div>
- <!-- 学员详细数据表格 -->
- <div class="data-table">
- <h3>👤 学员学习行为详细数据</h3>
- <a-table
- :columns="studentColumns"
- :data-source="studentData"
- :pagination="{ pageSize: 10 }"
- :scroll="{ x: 1200 }"
- >
- <template #bodyCell="{ column, record }">
- <template v-if="column.key === 'progress'">
- <div class="progress-container">
- <a-progress :percent="record.progress" size="small" />
- <span :class="{ 'low-engagement': record.progress < 50 }"> {{ record.progress }}% </span>
- </div>
- </template>
- <template v-else-if="column.key === 'exitPoints'">
- <span v-for="point in record.exitPoints" :key="point" class="time-point">
- {{ point }}
- </span>
- </template>
- </template>
- </a-table>
- </div>
- <!-- 视频章节详细统计 -->
- <div class="data-table">
- <h3>📚 视频章节详细统计</h3>
- <a-table :columns="chapterColumns" :data-source="chapterData" :pagination="false">
- <template #bodyCell="{ column, record }">
- <template v-if="column.key === 'completionRate'">
- <span
- :class="{ 'completion-rate': record.completionRate >= 70, 'low-engagement': record.completionRate < 70 }"
- >
- {{ record.completionRate }}%
- </span>
- </template>
- <template v-else-if="column.key === 'exitRate'">
- <span :class="{ 'low-engagement': record.exitRate > 25 }"> {{ record.exitRate }}% </span>
- </template>
- </template>
- </a-table>
- </div>
- </div>
- </template>
- <script setup>
- import { ref, reactive, onMounted, nextTick } from 'vue'
- import { message } from 'ant-design-vue'
- import * as echarts from 'echarts'
- // 筛选条件
- const filters = reactive({
- courseId: '',
- timeRange: '30'
- })
- // 统计数据
- const stats = reactive({
- totalViewers: 1247,
- completedViewers: 892,
- completionRate: 71.6,
- totalDownloads: 456,
- downloadRate: 36.6,
- avgDownloads: 0.37,
- totalExits: 234,
- exitRate: 18.8,
- avgExitTime: '12:34',
- totalNotes: 1089,
- totalDiscussions: 567,
- totalReplies: 2341
- })
- // 图表引用
- const progressChart = ref(null)
- const timeChart = ref(null)
- const chapterChart = ref(null)
- const interactionChart = ref(null)
- // 学员数据表格列定义
- const studentColumns = [
- { title: '学员ID', dataIndex: 'id', key: 'id', width: 80 },
- { title: '姓名', dataIndex: 'name', key: 'name', width: 100 },
- { title: '访问总时长', dataIndex: 'totalTime', key: 'totalTime', width: 120 },
- { title: '学习进度', dataIndex: 'progress', key: 'progress', width: 150 },
- { title: '观看次数', dataIndex: 'viewCount', key: 'viewCount', width: 100 },
- { title: '跳出时间点', dataIndex: 'exitPoints', key: 'exitPoints', width: 200 },
- { title: '快进快退次数', dataIndex: 'seekCount', key: 'seekCount', width: 120 },
- { title: '笔记数', dataIndex: 'noteCount', key: 'noteCount', width: 80 },
- { title: '讨论数', dataIndex: 'discussionCount', key: 'discussionCount', width: 80 },
- { title: '回帖数', dataIndex: 'replyCount', key: 'replyCount', width: 80 },
- { title: '最后访问', dataIndex: 'lastAccess', key: 'lastAccess', width: 150 }
- ]
- // 学员数据
- const studentData = ref([
- {
- key: '1',
- id: '001',
- name: '张三',
- totalTime: '2小时35分钟',
- progress: 85,
- viewCount: '3次',
- exitPoints: ['05:23', '18:45'],
- seekCount: '12次',
- noteCount: 5,
- discussionCount: 3,
- replyCount: 8,
- lastAccess: '2024-01-15 14:30'
- },
- {
- key: '2',
- id: '002',
- name: '李四',
- totalTime: '1小时48分钟',
- progress: 60,
- viewCount: '2次',
- exitPoints: ['08:12', '25:30', '42:15'],
- seekCount: '8次',
- noteCount: 2,
- discussionCount: 1,
- replyCount: 3,
- lastAccess: '2024-01-15 16:45'
- },
- {
- key: '3',
- id: '003',
- name: '王五',
- totalTime: '3小时12分钟',
- progress: 95,
- viewCount: '5次',
- exitPoints: ['03:45'],
- seekCount: '15次',
- noteCount: 8,
- discussionCount: 5,
- replyCount: 12,
- lastAccess: '2024-01-15 20:15'
- },
- {
- key: '4',
- id: '004',
- name: '赵六',
- totalTime: '45分钟',
- progress: 25,
- viewCount: '1次',
- exitPoints: ['12:30', '18:20', '28:45'],
- seekCount: '3次',
- noteCount: 0,
- discussionCount: 0,
- replyCount: 0,
- lastAccess: '2024-01-15 10:20'
- },
- {
- key: '5',
- id: '005',
- name: '钱七',
- totalTime: '2小时08分钟',
- progress: 78,
- viewCount: '4次',
- exitPoints: ['07:15', '22:40'],
- seekCount: '10次',
- noteCount: 4,
- discussionCount: 2,
- replyCount: 6,
- lastAccess: '2024-01-15 19:30'
- }
- ])
- // 章节数据表格列定义
- const chapterColumns = [
- { title: '章节', dataIndex: 'chapter', key: 'chapter', width: 200 },
- { title: '视频时长', dataIndex: 'duration', key: 'duration', width: 100 },
- { title: '观看人数', dataIndex: 'viewers', key: 'viewers', width: 100 },
- { title: '完成人数', dataIndex: 'completed', key: 'completed', width: 100 },
- { title: '完成率', dataIndex: 'completionRate', key: 'completionRate', width: 100 },
- { title: '平均观看时长', dataIndex: 'avgWatchTime', key: 'avgWatchTime', width: 120 },
- { title: '跳出率', dataIndex: 'exitRate', key: 'exitRate', width: 100 },
- { title: '下载次数', dataIndex: 'downloads', key: 'downloads', width: 100 },
- { title: '笔记数', dataIndex: 'notes', key: 'notes', width: 80 },
- { title: '讨论数', dataIndex: 'discussions', key: 'discussions', width: 80 }
- ]
- // 章节数据
- const chapterData = ref([
- {
- key: '1',
- chapter: '第1章:课程介绍',
- duration: '15:30',
- viewers: 1247,
- completed: 1156,
- completionRate: 92.7,
- avgWatchTime: '14:25',
- exitRate: 7.3,
- downloads: 89,
- notes: 45,
- discussions: 12
- },
- {
- key: '2',
- chapter: '第2章:基础知识',
- duration: '28:15',
- viewers: 1156,
- completed: 987,
- completionRate: 85.4,
- avgWatchTime: '24:30',
- exitRate: 14.6,
- downloads: 156,
- notes: 78,
- discussions: 23
- },
- {
- key: '3',
- chapter: '第3章:核心概念',
- duration: '35:20',
- viewers: 987,
- completed: 756,
- completionRate: 76.6,
- avgWatchTime: '28:45',
- exitRate: 23.4,
- downloads: 123,
- notes: 89,
- discussions: 34
- },
- {
- key: '4',
- chapter: '第4章:实战应用',
- duration: '42:10',
- viewers: 756,
- completed: 523,
- completionRate: 69.2,
- avgWatchTime: '32:15',
- exitRate: 30.8,
- downloads: 88,
- notes: 67,
- discussions: 28
- },
- {
- key: '5',
- chapter: '第5章:高级技巧',
- duration: '38:45',
- viewers: 523,
- completed: 345,
- completionRate: 66.0,
- avgWatchTime: '29:20',
- exitRate: 34.0,
- downloads: 67,
- notes: 45,
- discussions: 19
- }
- ])
- // 初始化图表
- const initCharts = () => {
- initProgressChart()
- initTimeChart()
- initChapterChart()
- initInteractionChart()
- }
- // 学习进度分布图
- const initProgressChart = () => {
- const chart = echarts.init(progressChart.value)
- const option = {
- title: {
- text: '学习进度分布',
- left: 'center'
- },
- tooltip: {
- trigger: 'item',
- formatter: '{a} <br/>{b}: {c} ({d}%)'
- },
- legend: {
- orient: 'vertical',
- left: 'left'
- },
- series: [
- {
- name: '学习进度',
- type: 'pie',
- radius: '50%',
- data: [
- { value: 234, name: '0-25%' },
- { value: 189, name: '26-50%' },
- { value: 345, name: '51-75%' },
- { value: 479, name: '76-100%' }
- ],
- emphasis: {
- itemStyle: {
- shadowBlur: 10,
- shadowOffsetX: 0,
- shadowColor: 'rgba(0, 0, 0, 0.5)'
- }
- }
- }
- ]
- }
- chart.setOption(option)
- }
- // 观看时长趋势图
- const initTimeChart = () => {
- const chart = echarts.init(timeChart.value)
- const option = {
- title: {
- // text: '观看时长趋势',
- left: 'center'
- },
- tooltip: {
- trigger: 'axis'
- },
- legend: {
- data: ['观看时长', '完成人数']
- },
- xAxis: {
- type: 'category',
- data: ['第1章', '第2章', '第3章', '第4章', '第5章']
- },
- yAxis: [
- {
- type: 'value',
- name: '时长(分钟)',
- position: 'left'
- },
- {
- type: 'value',
- name: '人数',
- position: 'right'
- }
- ],
- series: [
- {
- name: '观看时长',
- type: 'bar',
- data: [14.4, 24.5, 28.8, 32.3, 29.3],
- itemStyle: {
- color: '#3498db'
- }
- },
- {
- name: '完成人数',
- type: 'line',
- yAxisIndex: 1,
- data: [1156, 987, 756, 523, 345],
- itemStyle: {
- color: '#e74c3c'
- }
- }
- ]
- }
- chart.setOption(option)
- }
- // 章节完成率对比图
- const initChapterChart = () => {
- const chart = echarts.init(chapterChart.value)
- const option = {
- title: {
- text: '章节完成率对比',
- left: 'center'
- },
- tooltip: {
- trigger: 'axis',
- axisPointer: {
- type: 'shadow'
- }
- },
- grid: {
- left: '3%',
- right: '4%',
- bottom: '3%',
- containLabel: true
- },
- xAxis: {
- type: 'value',
- max: 100
- },
- yAxis: {
- type: 'category',
- data: ['第5章', '第4章', '第3章', '第2章', '第1章']
- },
- series: [
- {
- name: '完成率',
- type: 'bar',
- data: [66.0, 69.2, 76.6, 85.4, 92.7],
- itemStyle: {
- color: function (params) {
- const value = params.value
- if (value >= 80) return '#27ae60'
- if (value >= 60) return '#f39c12'
- return '#e74c3c'
- }
- }
- }
- ]
- }
- chart.setOption(option)
- }
- // 互动数据统计图
- const initInteractionChart = () => {
- const chart = echarts.init(interactionChart.value)
- const option = {
- title: {
- // text: '互动数据统计',
- left: 'center'
- },
- tooltip: {
- trigger: 'axis'
- },
- legend: {
- data: ['笔记数', '讨论数', '回帖数']
- },
- xAxis: {
- type: 'category',
- data: ['第1章', '第2章', '第3章', '第4章', '第5章']
- },
- yAxis: {
- type: 'value'
- },
- series: [
- {
- name: '笔记数',
- type: 'bar',
- data: [45, 78, 89, 67, 45],
- itemStyle: {
- color: '#3498db'
- }
- },
- {
- name: '讨论数',
- type: 'bar',
- data: [12, 23, 34, 28, 19],
- itemStyle: {
- color: '#2ecc71'
- }
- },
- {
- name: '回帖数',
- type: 'bar',
- data: [25, 45, 67, 56, 38],
- itemStyle: {
- color: '#f39c12'
- }
- }
- ]
- }
- chart.setOption(option)
- }
- // 更新统计数据
- const updateStats = () => {
- // 模拟数据更新
- stats.totalViewers = Math.floor(Math.random() * 500) + 1000
- stats.completedViewers = Math.floor(stats.totalViewers * 0.7)
- stats.completionRate = Number(((stats.completedViewers / stats.totalViewers) * 100).toFixed(1))
- // 重新初始化图表
- nextTick(() => {
- initCharts()
- })
- // 显示更新提示
- message.success('数据已更新!')
- }
- // 组件挂载后初始化图表
- onMounted(() => {
- nextTick(() => {
- initCharts()
- })
- // 监听窗口大小变化,重新调整图表
- window.addEventListener('resize', () => {
- const charts = [progressChart, timeChart, chapterChart, interactionChart]
- charts.forEach((chartRef) => {
- if (chartRef.value) {
- const chart = echarts.getInstanceByDom(chartRef.value)
- if (chart) {
- chart.resize()
- }
- }
- })
- })
- })
- </script>
- <style scoped>
- .video-analysis-container {
- padding: 20px;
- /* background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); */
- }
- .header {
- background: rgba(255, 255, 255, 0.95);
- backdrop-filter: blur(10px);
- border-radius: 15px;
- padding: 30px;
- margin-bottom: 30px;
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
- text-align: center;
- }
- .header h1 {
- color: #2c3e50;
- font-size: 2.5em;
- margin-bottom: 10px;
- }
- .header p {
- color: #7f8c8d;
- font-size: 1.1em;
- }
- .filter-section {
- background: rgba(255, 255, 255, 0.95);
- backdrop-filter: blur(10px);
- border-radius: 15px;
- padding: 25px;
- margin-bottom: 30px;
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
- }
- .filter-section h3 {
- color: #2c3e50;
- margin-bottom: 20px;
- font-size: 1.5em;
- }
- .filter-controls {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
- gap: 15px;
- align-items: end;
- }
- .filter-group {
- display: flex;
- flex-direction: column;
- }
- .filter-group label {
- margin-bottom: 8px;
- color: #2c3e50;
- font-weight: 600;
- }
- .stats-grid {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
- gap: 25px;
- margin-bottom: 30px;
- }
- .stat-card {
- background: rgba(255, 255, 255, 0.95);
- backdrop-filter: blur(10px);
- border-radius: 15px;
- padding: 25px;
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
- transition: transform 0.3s ease, box-shadow 0.3s ease;
- }
- .stat-card:hover {
- transform: translateY(-5px);
- box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
- }
- .stat-card h3 {
- color: #2c3e50;
- margin-bottom: 15px;
- font-size: 1.3em;
- border-bottom: 2px solid #3498db;
- padding-bottom: 10px;
- }
- .stat-number {
- font-size: 2.5em;
- font-weight: bold;
- color: #3498db;
- margin-bottom: 10px;
- }
- .stat-label {
- color: #7f8c8d;
- font-size: 0.9em;
- margin-bottom: 15px;
- }
- .charts-section {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
- gap: 25px;
- margin-bottom: 30px;
- }
- .chart-container {
- background: rgba(255, 255, 255, 0.95);
- backdrop-filter: blur(10px);
- border-radius: 15px;
- padding: 25px;
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
- }
- .chart-container h3 {
- color: #2c3e50;
- margin-bottom: 20px;
- font-size: 1.5em;
- }
- .chart {
- height: 400px;
- width: 100%;
- }
- .data-table {
- background: rgba(255, 255, 255, 0.95);
- backdrop-filter: blur(10px);
- border-radius: 15px;
- padding: 25px;
- margin-bottom: 30px;
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
- }
- .data-table h3 {
- color: #2c3e50;
- margin-bottom: 20px;
- font-size: 1.5em;
- }
- .progress-container {
- display: flex;
- align-items: center;
- gap: 10px;
- }
- .time-point {
- display: inline-block;
- background: #e74c3c;
- color: white;
- padding: 2px 8px;
- border-radius: 12px;
- font-size: 0.8em;
- margin: 2px;
- }
- .completion-rate {
- color: #27ae60;
- font-weight: bold;
- }
- .low-engagement {
- color: #e74c3c;
- font-weight: bold;
- }
- @media (max-width: 768px) {
- .stats-grid {
- grid-template-columns: 1fr;
- }
- .charts-section {
- grid-template-columns: 1fr;
- }
- .filter-controls {
- grid-template-columns: 1fr;
- }
- .header h1 {
- font-size: 2em;
- }
- }
- </style>
|