| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017 |
- <template>
- <div class="learning-behavior-analysis">
- <!-- 页面头部 -->
- <div class="header">
- <h1>📊 学习行为分析</h1>
- <p>全面分析学院教学情况和学员学习行为</p>
- </div>
- <!-- 导航标签 -->
- <div class="nav-tabs">
- <div class="nav-tab" :class="{ active: activeTab === 'college' }" @click="switchTab('college')">
- 🏫 学院维度分析
- </div>
- <div class="nav-tab" :class="{ active: activeTab === 'student' }" @click="switchTab('student')">
- 👤 学员维度分析
- </div>
- </div>
- <!-- 学院维度分析 -->
- <div v-show="activeTab === 'college'" class="tab-content">
- <!-- 筛选条件 -->
- <div class="filter-section">
- <h3>🔍 数据筛选</h3>
- <div class="filter-controls">
- <div class="filter-group">
- <label>选择学院</label>
- <a-select v-model:value="collegeFilters.collegeId" placeholder="全部学院" @change="updateCollegeData">
- <a-select-option v-for="college in collegeList" :key="college.id" :value="college.id">
- {{ college.name }}
- </a-select-option>
- </a-select>
- </div>
- <div class="filter-group">
- <label>时间范围</label>
- <a-select v-model:value="collegeFilters.timeRange" @change="updateCollegeData">
- <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="updateCollegeData">查询</a-button>
- </div>
- </div>
- </div>
- <!-- 学院整体统计 -->
- <div class="stats-grid">
- <div class="stat-card">
- <h3>📚 课程访问统计</h3>
- <div class="stat-number">{{ collegeStats.totalCourses }}</div>
- <div class="stat-label">总课程数</div>
- <div class="stat-number">{{ collegeStats.activeCourses }}</div>
- <div class="stat-label">活跃课程数</div>
- <div class="stat-number">{{ collegeStats.courseAccessRate }}%</div>
- <div class="stat-label">课程访问率</div>
- </div>
- <div class="stat-card">
- <h3>👥 用户登录统计</h3>
- <div class="stat-number">{{ collegeStats.totalLogins.toLocaleString() }}</div>
- <div class="stat-label">总登录人次</div>
- <div class="stat-number">{{ collegeStats.uniqueUsers.toLocaleString() }}</div>
- <div class="stat-label">独立用户数</div>
- <div class="stat-number">{{ collegeStats.avgLoginPerUser }}</div>
- <div class="stat-label">人均登录次数</div>
- </div>
- <div class="stat-card">
- <h3>⏰ 观看时长统计</h3>
- <div class="stat-number">{{ collegeStats.totalOnlineTime }}h</div>
- <div class="stat-label">总观看时长</div>
- <div class="stat-number">{{ collegeStats.avgSessionTime }}min</div>
- <div class="stat-label">平均时长</div>
- <div class="stat-number">{{ collegeStats.peakOnlineUsers }}</div>
- <div class="stat-label">峰值观看人数</div>
- </div>
- </div>
- <!-- 登录时段分析图表 -->
- <div class="chart-container">
- <h3>⏰ 用户登录时段分布</h3>
- <div ref="loginTimeChart" class="chart"></div>
- </div>
- <!-- 课程访问热度图表 -->
- <div class="chart-container">
- <h3>🔥 课程访问热度排行</h3>
- <div ref="courseHeatChart" class="chart"></div>
- </div>
- <!-- 学院课程详细统计 -->
- <div class="data-table">
- <h3>📊 学院课程详细统计</h3>
- <a-table
- :columns="collegeColumns"
- :data-source="collegeTableData"
- :pagination="{ pageSize: 10 }"
- row-key="id"
- />
- </div>
- </div>
- <!-- 学员维度分析 -->
- <div v-show="activeTab === 'student'" class="tab-content">
- <!-- 学员搜索 -->
- <div class="filter-section">
- <h3>🔍 学员搜索</h3>
- <div class="filter-controls">
- <div class="filter-group">
- <label>学员姓名/学号</label>
- <a-input v-model:value="studentSearch" placeholder="输入学员姓名或学号" @pressEnter="searchStudent" />
- </div>
- <div class="filter-group">
- <a-button type="primary" @click="searchStudent">搜索</a-button>
- </div>
- <div class="filter-group">
- <a-button @click="clearSearch">清除</a-button>
- </div>
- </div>
- </div>
- <!-- 学员学习行为卡片 -->
- <div class="student-cards">
- <div
- v-for="student in filteredStudents"
- :key="student.id"
- class="student-card"
- :class="{ highlighted: student.highlighted }"
- >
- <div class="student-header">
- <div class="student-info">
- <h4>{{ student.name }} ({{ student.studentId }})</h4>
- <div class="student-meta">{{ student.college }} | {{ student.major }} | {{ student.grade }}</div>
- </div>
- <div class="student-stats">
- <div class="stat-item">
- <div class="stat-value">{{ student.overallProgress }}%</div>
- <div class="stat-label">总体进度</div>
- </div>
- </div>
- </div>
- <div class="course-progress">
- <div v-for="course in student.courses" :key="course.id" class="course-item">
- <div class="course-name">{{ course.name }}</div>
- <div class="course-stats">
- <div class="stat-item">
- <div class="stat-value">{{ course.progress }}%</div>
- <div class="stat-label">学习进度</div>
- </div>
- <div class="stat-item">
- <div class="stat-value">{{ course.completedAssignments }}/{{ course.totalAssignments }}</div>
- <div class="stat-label">作业完成</div>
- </div>
- <div class="stat-item">
- <div class="stat-value">{{ course.discussionParticipation }}</div>
- <div class="stat-label">讨论参与</div>
- </div>
- <div class="stat-item">
- <div class="stat-value">{{ course.questionCount }}</div>
- <div class="stat-label">提问次数</div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </template>
- <script setup>
- import { ref, reactive, computed, onMounted, nextTick, onUnmounted } from 'vue'
- import * as echarts from 'echarts'
- import {
- getCollegeList,
- getCollegeStats,
- getLoginTimeDistribution,
- getCourseHeat,
- getCollegeCourseDetails,
- searchStudents
- } from '@/api/statisticalAnalysis/analysisLearningBehaviors'
- // 当前活动标签
- const activeTab = ref('college')
- // 学院维度筛选条件
- const collegeFilters = reactive({
- collegeId: '',
- timeRange: 30
- })
- // 学院列表
- const collegeList = ref([])
- // 学院统计数据
- const collegeStats = reactive({
- totalCourses: 156,
- activeCourses: 142,
- courseAccessRate: 91.0,
- totalLogins: 12456,
- uniqueUsers: 3234,
- avgLoginPerUser: 3.85,
- totalOnlineTime: 2456,
- avgSessionTime: 45.2,
- peakOnlineUsers: 234
- })
- // 图表引用
- const loginTimeChart = ref(null)
- const courseHeatChart = ref(null)
- let loginTimeChartInstance = null
- let courseHeatChartInstance = null
- // 学院表格列定义
- const collegeColumns = [
- {
- title: '学院名称',
- dataIndex: 'name',
- key: 'name'
- },
- {
- title: '课程数量',
- dataIndex: 'courseCount',
- key: 'courseCount'
- },
- {
- title: '总访问量',
- dataIndex: 'totalVisits',
- key: 'totalVisits'
- },
- {
- title: '平均完成率',
- dataIndex: 'avgCompletionRate',
- key: 'avgCompletionRate',
- customRender: ({ text }) => `${text}%`
- },
- {
- title: '作业提交率',
- dataIndex: 'assignmentSubmissionRate',
- key: 'assignmentSubmissionRate',
- customRender: ({ text }) => `${text}%`
- },
- {
- title: '退课率',
- dataIndex: 'dropoutRate',
- key: 'dropoutRate',
- customRender: ({ text }) => `${text}%`
- }
- ]
- // 学院表格数据
- const collegeTableData = ref([
- {
- id: 1,
- name: '计算机学院',
- courseCount: 45,
- totalVisits: 5234,
- avgCompletionRate: 78.5,
- assignmentSubmissionRate: 85.2,
- dropoutRate: 10.2
- },
- {
- id: 2,
- name: '商学院',
- courseCount: 38,
- totalVisits: 4567,
- avgCompletionRate: 72.3,
- assignmentSubmissionRate: 79.8,
- dropoutRate: 12.5
- },
- {
- id: 3,
- name: '艺术学院',
- courseCount: 28,
- totalVisits: 3123,
- avgCompletionRate: 68.9,
- assignmentSubmissionRate: 76.4,
- dropoutRate: 15.2
- },
- {
- id: 4,
- name: '理学院',
- courseCount: 35,
- totalVisits: 3890,
- avgCompletionRate: 75.2,
- assignmentSubmissionRate: 82.1,
- dropoutRate: 13.8
- }
- ])
- // 学员搜索
- const studentSearch = ref('')
- // 学员数据
- const studentsData = ref([
- {
- id: 1,
- name: '张三',
- studentId: '2021001',
- college: '计算机学院',
- major: '计算机科学与技术',
- grade: '大三',
- overallProgress: 85.6,
- highlighted: false,
- courses: [
- {
- id: 1,
- name: 'JavaScript程序设计',
- progress: 92,
- completedAssignments: 8,
- totalAssignments: 10,
- discussionParticipation: 15,
- questionCount: 5
- },
- {
- id: 2,
- name: '数据结构与算法',
- progress: 78,
- completedAssignments: 6,
- totalAssignments: 8,
- discussionParticipation: 12,
- questionCount: 3
- }
- ]
- },
- {
- id: 2,
- name: '李四',
- studentId: '2021002',
- college: '商学院',
- major: '工商管理',
- grade: '大二',
- overallProgress: 72.3,
- highlighted: false,
- courses: [
- {
- id: 3,
- name: '管理学原理',
- progress: 85,
- completedAssignments: 7,
- totalAssignments: 9,
- discussionParticipation: 8,
- questionCount: 2
- },
- {
- id: 4,
- name: '市场营销学',
- progress: 65,
- completedAssignments: 4,
- totalAssignments: 7,
- discussionParticipation: 6,
- questionCount: 1
- }
- ]
- },
- {
- id: 3,
- name: '王五',
- studentId: '2021003',
- college: '艺术学院',
- major: '视觉传达设计',
- grade: '大一',
- overallProgress: 91.2,
- highlighted: false,
- courses: [
- {
- id: 5,
- name: '设计基础',
- progress: 95,
- completedAssignments: 10,
- totalAssignments: 10,
- discussionParticipation: 20,
- questionCount: 8
- },
- {
- id: 6,
- name: '色彩构成',
- progress: 88,
- completedAssignments: 8,
- totalAssignments: 9,
- discussionParticipation: 15,
- questionCount: 6
- }
- ]
- }
- ])
- // 过滤后的学员数据
- const filteredStudents = computed(() => {
- if (!studentSearch.value.trim()) {
- return studentsData.value.map((student) => ({ ...student, highlighted: false }))
- }
- const searchTerm = studentSearch.value.trim().toLowerCase()
- return studentsData.value
- .filter((student) => {
- const matchName = student.name.toLowerCase().includes(searchTerm)
- const matchId = student.studentId.includes(searchTerm)
- return matchName || matchId
- })
- .map((student) => ({ ...student, highlighted: true }))
- })
- // 切换标签
- const switchTab = (tabName) => {
- activeTab.value = tabName
- if (tabName === 'college') {
- nextTick(async () => {
- await initCollegeCharts()
- })
- }
- }
- // 更新学院数据
- const updateCollegeData = async () => {
- try {
- // 获取学院统计数据
- const statsResponse = await getCollegeStats({
- collegeId: collegeFilters.collegeId,
- timeRange: collegeFilters.timeRange
- })
- Object.assign(collegeStats, statsResponse)
- // 获取课程详细统计
- const detailsResponse = await getCollegeCourseDetails({
- collegeId: collegeFilters.collegeId,
- timeRange: collegeFilters.timeRange
- })
- collegeTableData.value = detailsResponse
- // 重新初始化图表
- await initCollegeCharts()
- } catch (error) {
- console.error('获取学院数据失败:', error)
- }
- }
- // 搜索学员
- const searchStudent = async () => {
- try {
- const response = await searchStudents({
- keyword: studentSearch.value.trim()
- })
- studentsData.value = response.map((student) => ({
- ...student,
- highlighted: !!studentSearch.value.trim()
- }))
- if (response.length === 0 && studentSearch.value.trim()) {
- alert(`未找到包含"${studentSearch.value}"的学员`)
- }
- } catch (error) {
- console.error('搜索学员失败:', error)
- }
- }
- // 清除搜索
- const clearSearch = async () => {
- studentSearch.value = ''
- await searchStudent()
- }
- // 初始化学院维度图表
- const initCollegeCharts = async () => {
- await initLoginTimeChart()
- await initCourseHeatChart()
- }
- // 登录时段分布图表
- const initLoginTimeChart = async () => {
- if (!loginTimeChart.value) return
- if (loginTimeChartInstance) {
- loginTimeChartInstance.dispose()
- }
- loginTimeChartInstance = echarts.init(loginTimeChart.value)
- try {
- const response = await getLoginTimeDistribution({
- collegeId: collegeFilters.collegeId,
- timeRange: collegeFilters.timeRange
- })
- const { hours, loginCounts } = response
- const option = {
- title: {
- text: '24小时登录时段分布',
- left: 'center',
- textStyle: {
- color: '#2c3e50',
- fontSize: 16,
- fontWeight: 'bold'
- }
- },
- tooltip: {
- trigger: 'axis',
- backgroundColor: 'rgba(255, 255, 255, 0.95)',
- borderColor: '#3498db',
- borderWidth: 1,
- textStyle: {
- color: '#333'
- }
- },
- xAxis: {
- type: 'category',
- data: hours,
- axisLine: {
- lineStyle: {
- color: '#bdc3c7'
- }
- },
- axisLabel: {
- color: '#2c3e50'
- }
- },
- yAxis: {
- type: 'value',
- name: '登录人次',
- nameTextStyle: {
- color: '#2c3e50'
- },
- axisLine: {
- lineStyle: {
- color: '#bdc3c7'
- }
- },
- axisLabel: {
- color: '#2c3e50'
- },
- splitLine: {
- lineStyle: {
- color: '#ecf0f1'
- }
- }
- },
- series: [
- {
- name: '登录人次',
- type: 'bar',
- data: loginCounts,
- itemStyle: {
- color: {
- type: 'linear',
- x: 0,
- y: 0,
- x2: 0,
- y2: 1,
- colorStops: [
- {
- offset: 0,
- color: '#3498db'
- },
- {
- offset: 1,
- color: '#2980b9'
- }
- ]
- }
- },
- emphasis: {
- itemStyle: {
- color: '#e74c3c'
- }
- }
- }
- ]
- }
- loginTimeChartInstance.setOption(option)
- } catch (error) {
- console.error('获取登录时段数据失败:', error)
- }
- }
- // 课程访问热度图表
- const initCourseHeatChart = async () => {
- if (!courseHeatChart.value) return
- if (courseHeatChartInstance) {
- courseHeatChartInstance.dispose()
- }
- courseHeatChartInstance = echarts.init(courseHeatChart.value)
- try {
- const response = await getCourseHeat({
- collegeId: collegeFilters.collegeId,
- timeRange: collegeFilters.timeRange
- })
- const { courses, visits } = response
- const option = {
- title: {
- text: '课程访问热度TOP8',
- left: 'center',
- textStyle: {
- color: '#2c3e50',
- fontSize: 16,
- fontWeight: 'bold'
- }
- },
- tooltip: {
- trigger: 'axis',
- backgroundColor: 'rgba(255, 255, 255, 0.95)',
- borderColor: '#3498db',
- borderWidth: 1,
- textStyle: {
- color: '#333'
- }
- },
- xAxis: {
- type: 'value',
- name: '访问量',
- nameTextStyle: {
- color: '#2c3e50'
- },
- axisLine: {
- lineStyle: {
- color: '#bdc3c7'
- }
- },
- axisLabel: {
- color: '#2c3e50'
- },
- splitLine: {
- lineStyle: {
- color: '#ecf0f1'
- }
- }
- },
- yAxis: {
- type: 'category',
- data: courses,
- axisLine: {
- lineStyle: {
- color: '#bdc3c7'
- }
- },
- axisLabel: {
- color: '#2c3e50'
- }
- },
- series: [
- {
- name: '访问量',
- type: 'bar',
- data: visits,
- itemStyle: {
- color: {
- type: 'linear',
- x: 0,
- y: 0,
- x2: 1,
- y2: 0,
- colorStops: [
- {
- offset: 0,
- color: '#e74c3c'
- },
- {
- offset: 1,
- color: '#f39c12'
- }
- ]
- }
- },
- emphasis: {
- itemStyle: {
- color: '#3498db'
- }
- }
- }
- ]
- }
- courseHeatChartInstance.setOption(option)
- } catch (error) {
- console.error('获取课程热度数据失败:', error)
- }
- }
- // 响应式处理
- const handleResize = () => {
- loginTimeChartInstance?.resize()
- courseHeatChartInstance?.resize()
- }
- // 初始化数据
- const initData = async () => {
- try {
- // 获取学院列表
- const colleges = await getCollegeList()
- collegeList.value = colleges
- // 获取初始学员数据
- await searchStudent()
- // 获取学院数据
- await updateCollegeData()
- } catch (error) {
- console.error('初始化数据失败:', error)
- }
- }
- // 组件挂载后初始化
- onMounted(() => {
- nextTick(async () => {
- await initData()
- window.addEventListener('resize', handleResize)
- })
- })
- // 组件卸载时清理
- onUnmounted(() => {
- window.removeEventListener('resize', handleResize)
- loginTimeChartInstance?.dispose()
- courseHeatChartInstance?.dispose()
- })
- </script>
- <style scoped>
- .learning-behavior-analysis {
- padding: 20px;
- }
- .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;
- }
- .nav-tabs {
- display: flex;
- background: rgba(255, 255, 255, 0.95);
- backdrop-filter: blur(10px);
- border-radius: 15px;
- padding: 5px;
- margin-bottom: 30px;
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
- }
- .nav-tab {
- flex: 1;
- padding: 15px 20px;
- text-align: center;
- cursor: pointer;
- border-radius: 10px;
- transition: all 0.3s ease;
- font-weight: 600;
- color: #7f8c8d;
- }
- .nav-tab.active {
- background: linear-gradient(135deg, #3498db, #2980b9);
- color: white;
- box-shadow: 0 4px 15px rgba(52, 152, 219, 0.4);
- }
- .nav-tab:hover:not(.active) {
- background: rgba(52, 152, 219, 0.1);
- color: #3498db;
- }
- .tab-content {
- display: block;
- }
- .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(250px, 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.2em;
- border-bottom: 2px solid #3498db;
- padding-bottom: 10px;
- }
- .stat-number {
- font-size: 2.2em;
- font-weight: bold;
- color: #3498db;
- margin-bottom: 8px;
- }
- .stat-label {
- color: #7f8c8d;
- font-size: 0.9em;
- }
- .chart-container {
- 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);
- }
- .chart-container h3 {
- color: #2c3e50;
- margin-bottom: 20px;
- font-size: 1.5em;
- }
- .chart {
- height: 400px;
- border-radius: 10px;
- }
- .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;
- }
- .student-cards {
- display: flex;
- flex-direction: column;
- gap: 20px;
- }
- .student-card {
- background: rgba(255, 255, 255, 0.95);
- backdrop-filter: blur(10px);
- border-radius: 15px;
- padding: 20px;
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
- transition: transform 0.3s ease;
- }
- .student-card:hover {
- transform: translateY(-3px);
- }
- .student-card.highlighted {
- border: 2px solid #3498db;
- box-shadow: 0 8px 32px rgba(52, 152, 219, 0.2);
- }
- .student-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 15px;
- padding-bottom: 10px;
- border-bottom: 2px solid #ecf0f1;
- }
- .student-info h4 {
- color: #2c3e50;
- font-size: 1.3em;
- margin-bottom: 5px;
- }
- .student-meta {
- color: #7f8c8d;
- font-size: 0.9em;
- }
- .course-progress {
- margin-bottom: 15px;
- }
- .course-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 10px 0;
- border-bottom: 1px solid #ecf0f1;
- }
- .course-name {
- font-weight: 600;
- color: #2c3e50;
- }
- .course-stats {
- display: flex;
- gap: 20px;
- font-size: 0.9em;
- }
- .stat-item {
- text-align: center;
- }
- .stat-value {
- font-weight: bold;
- color: #3498db;
- }
- .stat-label {
- color: #7f8c8d;
- font-size: 0.8em;
- }
- :deep(.completion-rate) {
- color: #27ae60;
- font-weight: bold;
- }
- :deep(.low-engagement) {
- color: #e74c3c;
- font-weight: bold;
- }
- @media (max-width: 768px) {
- .stats-grid {
- grid-template-columns: 1fr;
- }
- .filter-controls {
- grid-template-columns: 1fr;
- }
- .header h1 {
- font-size: 2em;
- }
- .nav-tabs {
- flex-direction: column;
- }
- .student-header {
- flex-direction: column;
- align-items: flex-start;
- }
- .course-stats {
- flex-direction: column;
- gap: 10px;
- }
- }
- </style>
|