/** * 学生端功能管理 */ class StudentManager { constructor() { this.apiBase = '/api/student'; this.init(); } init() { this.initDashboard(); this.initMyCourses(); this.initGradeAnalysis(); this.updateCurrentTime(); setInterval(() => this.updateCurrentTime(), 1000); // 绑定刷新按钮 const refreshBtn = document.getElementById('refreshCourses'); if (refreshBtn) { refreshBtn.addEventListener('click', () => this.initMyCourses()); } const refreshTrendBtn = document.getElementById('refreshTrend'); if (refreshTrendBtn) { refreshTrendBtn.addEventListener('click', () => this.initGradeAnalysis()); } } updateCurrentTime() { const timeElement = document.getElementById('currentTime'); if (timeElement) { const now = new Date(); const options = { year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' }; timeElement.textContent = now.toLocaleString('zh-CN', options); } } async initDashboard() { // 检查是否在仪表板页面 if (!document.getElementById('gradesTableBody')) return; try { const response = await fetch(`${this.apiBase}/grades`); const result = await response.json(); if (result.success) { this.renderDashboard(result.data); } else { if (window.authManager) { window.authManager.showNotification(result.message || '获取数据失败', 'error'); } } } catch (error) { console.error('Fetch dashboard data failed:', error); } } async initMyCourses() { // 检查是否在我的课程页面 const tbody = document.getElementById('coursesTableBody'); if (!tbody) return; // 显示加载状态 tbody.innerHTML = `
数据加载中... `; try { const response = await fetch(`${this.apiBase}/courses`); const result = await response.json(); if (result.success) { this.renderCourses(result.data); } else { tbody.innerHTML = ` ${result.message || '获取课程失败'} `; if (window.authManager) { window.authManager.showNotification(result.message || '获取课程失败', 'error'); } } } catch (error) { console.error('Fetch courses data failed:', error); tbody.innerHTML = ` 网络错误,请稍后重试 `; } } renderCourses(courses) { const tbody = document.getElementById('coursesTableBody'); if (!tbody) return; if (!courses || courses.length === 0) { tbody.innerHTML = '暂无课程记录'; return; } tbody.innerHTML = courses.map(course => ` ${course.course_code} ${course.course_name} ${course.credit} ${course.teacher_name} ${course.semester || '2023-2024 下学期'} `).join(''); } async viewCourseDetails(courseId) { try { const response = await fetch(`${this.apiBase}/courses/${courseId}`); const result = await response.json(); if (result.success) { const course = result.data; this.updateElement('modalCourseName', course.course_name); this.updateElement('modalCourseCode', course.course_code); this.updateElement('modalCourseCredit', course.credit); this.updateElement('modalTeacherName', course.teacher_name); this.updateElement('modalClassName', course.class_name); this.updateElement('modalSemester', course.semester || '2023-2024 下学期'); this.updateElement('modalTeacherEmail', course.teacher_email || '暂无'); const modalEl = document.getElementById('courseDetailsModal'); if (modalEl) { const modal = bootstrap.Modal.getOrCreateInstance(modalEl); modal.show(); } else { console.error('Course details modal element not found'); if (window.authManager) { window.authManager.showNotification('无法打开课程详情:模态框组件缺失', 'error'); } } } else { if (window.authManager) { window.authManager.showNotification(result.message || '获取课程详情失败', 'error'); } } } catch (error) { console.error('Fetch course details failed:', error); if (window.authManager) { window.authManager.showNotification('获取课程详情失败:网络错误', 'error'); } } } renderDashboard(data) { const { grades, statistics } = data; // 渲染统计数据 if (statistics) { this.updateElement('gpaValue', statistics.gpa); this.updateElement('courseCount', statistics.totalCourses); this.updateElement('creditTotal', statistics.totalCredits); // 班级排名如果后端没提供,可以显示为 '-' 或固定值 this.updateElement('classRank', statistics.classRank || '-'); } // 渲染成绩表格 const tbody = document.getElementById('gradesTableBody'); if (tbody) { if (!grades || grades.length === 0) { tbody.innerHTML = '暂无成绩记录'; return; } tbody.innerHTML = grades.map(grade => ` ${grade.course_name} ${grade.course_code || '-'} ${grade.credit} ${grade.score} ${grade.grade_level || '-'} ${grade.grade_point || '-'} ${this.getScoreText(grade.score)} `).join(''); } } updateElement(id, value) { const el = document.getElementById(id); if (el) el.textContent = value; } getScoreBadgeClass(score) { const s = parseFloat(score); if (s >= 90) return 'bg-success'; if (s >= 80) return 'bg-info'; if (s >= 60) return 'bg-warning'; return 'bg-danger'; } getScoreText(score) { const s = parseFloat(score); if (s >= 60) return '及格'; return '不及格'; } async viewDetails(id) { try { const response = await fetch(`${this.apiBase}/grades/${id}`); const result = await response.json(); if (result.success) { const grade = result.data.grade; this.updateElement('modalGradeCourseName', grade.course_name); this.updateElement('modalGradeCourseCode', grade.course_code); this.updateElement('modalTotalScore', grade.score); this.updateElement('modalUsualScore', grade.usual_score || '-'); this.updateElement('modalMidtermScore', grade.midterm_score || '-'); this.updateElement('modalFinalScore', grade.final_score || '-'); this.updateElement('modalGradeCredit', grade.credit); this.updateElement('modalGradeLevel', grade.grade_level || '-'); this.updateElement('modalGradePoint', grade.grade_point || '-'); this.updateElement('modalGradeTeacher', grade.teacher_name); this.updateElement('modalGradeTime', new Date(grade.created_at).toLocaleString()); const remarkContainer = document.getElementById('modalRemarkContainer'); if (grade.remark) { remarkContainer.style.display = 'block'; this.updateElement('modalRemark', grade.remark); } else { remarkContainer.style.display = 'none'; } const modalEl = document.getElementById('gradeDetailsModal'); if (modalEl) { const modal = bootstrap.Modal.getOrCreateInstance(modalEl); modal.show(); } else { console.error('Grade details modal element not found'); if (window.authManager) { window.authManager.showNotification('无法打开成绩详情:模态框组件缺失', 'error'); } } } else { if (window.authManager) { window.authManager.showNotification(result.message || '获取成绩详情失败', 'error'); } } } catch (error) { console.error('Fetch grade details failed:', error); if (window.authManager) { window.authManager.showNotification('获取成绩详情失败:网络错误', 'error'); } } } async initGradeAnalysis() { if (!document.getElementById('gpaTrendChart')) return; try { const response = await fetch(`${this.apiBase}/statistics`); const result = await response.json(); if (result.success) { this.renderAnalysisCharts(result.data); this.renderCreditsInfo(result.data.credits, result.data.semesterCredits); } else { if (window.authManager) { window.authManager.showNotification(result.message || '获取统计数据失败', 'error'); } } } catch (error) { console.error('Fetch statistics failed:', error); } } renderAnalysisCharts(data) { try { // 1. GPA 趋势图 const ctxGPA = document.getElementById('gpaTrendChart'); if (ctxGPA) { const ctx = ctxGPA.getContext('2d'); if (this.gpaChart) this.gpaChart.destroy(); this.gpaChart = new Chart(ctx, { type: 'line', data: { labels: (data.trend || []).map(t => t.semester), datasets: [{ label: '学期 GPA', data: (data.trend || []).map(t => t.gpa), borderColor: '#4e73df', backgroundColor: 'rgba(78, 115, 223, 0.05)', tension: 0.3, fill: true }] }, options: { maintainAspectRatio: false, scales: { y: { beginAtZero: false, min: 0, max: 4.0 } } } }); } // 2. 成绩分布饼图 const ctxDist = document.getElementById('gradeDistributionChart'); if (ctxDist) { const ctx = ctxDist.getContext('2d'); if (this.distChart) this.distChart.destroy(); this.distChart = new Chart(ctx, { type: 'doughnut', data: { labels: ['A (优秀)', 'B (良好)', 'C (中等)', 'D (及格)', 'F (不及格)'], datasets: [{ data: [ data.distribution.A || 0, data.distribution.B || 0, data.distribution.C || 0, data.distribution.D || 0, data.distribution.F || 0 ], backgroundColor: ['#1cc88a', '#36b9cc', '#f6c23e', '#fd7e14', '#e74a3b'] }] }, options: { maintainAspectRatio: false, plugins: { legend: { position: 'bottom' } } } }); } } catch (error) { console.error('Render charts failed:', error); } // 3. 课程类别表现 (使用进度条列表展示) this.renderCategoryPerformance(data.categories); } renderCategoryPerformance(categories) { const container = document.getElementById('categoryPerformanceList'); if (!container || !categories) return; container.innerHTML = categories.map(cat => { const gpaValue = parseFloat(cat.gpa); const percentage = (gpaValue / 4.0) * 100; const totalCredits = parseFloat(cat.totalCredits || 0); // 根据绩点选择颜色 let bgColor = 'bg-primary'; if (gpaValue >= 3.5) bgColor = 'bg-success'; else if (gpaValue < 2.0) bgColor = 'bg-danger'; else if (gpaValue < 3.0) bgColor = 'bg-warning'; return `
${cat.category} (${totalCredits.toFixed(1)} 学分)
${cat.gpa} GPA
`; }).join(''); } renderCreditsInfo(credits, semesterCredits) { // 总进度 const percent = Math.min(100, Math.round((credits.earned / credits.target) * 100)); const progressBar = document.getElementById('creditProgressBar'); const percentText = document.getElementById('creditPercent'); if (progressBar && percentText) { progressBar.style.width = `${percent}%`; percentText.textContent = `${percent}%`; // 更新数值 this.updateElement('earnedCredits', credits.earned); this.updateElement('targetCredits', credits.target); } // 学期学分列表 const listContainer = document.getElementById('semesterCreditsList'); if (listContainer) { listContainer.innerHTML = semesterCredits.map(item => `
${item.semester} ${item.credits} 学分
`).join(''); } } } // 初始化 document.addEventListener('DOMContentLoaded', () => { window.studentManager = new StudentManager(); // 从 Session 获取用户信息并更新 UI fetch('/api/auth/me') .then(res => res.json()) .then(result => { if (result.success && result.data.user) { const user = result.data.user; const nameEl = document.getElementById('userName'); const studentNameEl = document.getElementById('studentName'); const studentClassEl = document.getElementById('studentClass'); if (nameEl) nameEl.textContent = user.name; if (studentNameEl) studentNameEl.textContent = user.name; if (studentClassEl) studentClassEl.textContent = user.class || '未分配'; } }); });