/** * 教师端功能管理 */ class TeacherManager { constructor() { this.apiBase = '/api/teacher'; this.init(); } async init() { this.updateCurrentTime(); setInterval(() => this.updateCurrentTime(), 1000); await this.loadUserInfo(); // 页面路由逻辑 if (document.getElementById('courseList')) { this.initDashboard(); } if (document.getElementById('studentTableBody') && document.getElementById('courseSelect')) { this.initGradeEntry(); } if (document.getElementById('gradeTableBody')) { this.initGradeManagement(); } if (document.getElementById('profileForm')) { this.initProfilePage(); } } 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 loadUserInfo() { try { const res = await fetch('/api/auth/me'); const result = await res.json(); if (result.success && result.data.user) { const user = result.data.user; this.user = user; // Update Sidebar const nameEls = document.querySelectorAll('#teacherName, #userName, #profileName'); const idEls = document.querySelectorAll('#teacherId, #profileId, #inputTeacherId'); nameEls.forEach(el => el.textContent = user.name); idEls.forEach(el => { if (el.tagName === 'INPUT') el.value = user.id; else el.textContent = user.id; }); // Profile Page Specific const inputName = document.getElementById('inputName'); const inputClass = document.getElementById('inputClass'); if (inputName) inputName.value = user.name; if (inputClass) inputClass.value = user.class || ''; } } catch (error) { console.error('Load user info failed:', error); } } // ================= Dashboard ================= async initDashboard() { await Promise.all([ this.loadCourses(), this.loadManagedClasses() ]); this.initAddCourse(); this.initEditCourse(); } async loadManagedClasses() { const managedClassesSection = document.getElementById('managedClassesSection'); const managedClassList = document.getElementById('managedClassList'); const classCountEl = document.getElementById('classCount'); if (!managedClassList) return; try { const res = await fetch(`${this.apiBase}/my-classes`); const result = await res.json(); if (result.success && result.data.classes.length > 0) { const classes = result.data.classes; classCountEl.textContent = classes.length; managedClassesSection.style.display = 'block'; managedClassList.innerHTML = classes.map(c => `
${c.class_name}

${c.major || '专业未设置'} | ${c.grade}级

`).join(''); } else { classCountEl.textContent = '0'; managedClassesSection.style.display = 'none'; } } catch (e) { console.error('Load managed classes failed', e); } } async loadCourses() { try { const response = await fetch(`${this.apiBase}/courses`); const result = await response.json(); if (result.success) { this.courses = result.data.courses; this.renderDashboard(this.courses); } } catch (error) { console.error('Fetch courses failed:', error); } } renderDashboard(courses) { const courseList = document.getElementById('courseList'); if (!courseList) return; if (!courses || courses.length === 0) { courseList.innerHTML = '
暂无负责课程
'; return; } courseList.innerHTML = courses.map(course => `
${course.course_code || 'CODE'}
${course.credit} 学分
${course.course_name}

${course.class_name || '班级未指定'}

`).join(''); document.getElementById('courseCount').textContent = courses.length; // Calculate total students const totalStudents = courses.reduce((sum, c) => sum + (c.student_count || 0), 0); document.getElementById('totalStudents').textContent = totalStudents; } initAddCourse() { const btn = document.getElementById('addCourseBtn'); const modalEl = document.getElementById('addCourseModal'); const saveBtn = document.getElementById('saveCourseBtn'); if (!btn || !modalEl) return; const modal = new bootstrap.Modal(modalEl); btn.addEventListener('click', async () => { // Load classes try { const res = await fetch(`${this.apiBase}/classes`); const result = await res.json(); if (result.success) { const select = document.getElementById('classSelect'); select.innerHTML = '' + result.data.classes.map(c => ``).join(''); } } catch (e) { console.error('Load classes failed', e); } modal.show(); }); saveBtn.addEventListener('click', async () => { const form = document.getElementById('addCourseForm'); const formData = new FormData(form); const data = Object.fromEntries(formData.entries()); try { const res = await fetch(`${this.apiBase}/courses`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); const result = await res.json(); if (result.success) { alert('课程创建成功'); modal.hide(); this.loadCourses(); form.reset(); } else { alert(result.message || '创建失败'); } } catch (e) { console.error('Create course failed', e); alert('系统错误'); } }); } initEditCourse() { const modalEl = document.getElementById('editCourseModal'); if (!modalEl) return; const modal = new bootstrap.Modal(modalEl); const updateBtn = document.getElementById('updateCourseBtn'); // Use event delegation for dynamically created edit buttons document.addEventListener('click', async (e) => { const btn = e.target.closest('.btn-edit-course'); if (!btn) return; const courseId = btn.dataset.id; if (!this.courses) return; const courseData = this.courses.find(c => c.id == courseId); if (!courseData) return; // Fill form document.getElementById('editCourseId').value = courseData.id; document.getElementById('editCourseName').value = courseData.course_name; document.getElementById('editCourseCode').value = courseData.course_code; document.getElementById('editCourseCredit').value = courseData.credit; document.getElementById('editSemester').value = courseData.semester; // Load classes and select current one try { const res = await fetch(`${this.apiBase}/classes`); const result = await res.json(); if (result.success) { const select = document.getElementById('editClassSelect'); select.innerHTML = '' + result.data.classes.map(c => ``).join(''); } } catch (e) { console.error('Load classes failed', e); } modal.show(); }); updateBtn.addEventListener('click', async () => { const form = document.getElementById('editCourseForm'); const formData = new FormData(form); const data = Object.fromEntries(formData.entries()); const courseId = data.id; try { const res = await fetch(`${this.apiBase}/courses/${courseId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); const result = await res.json(); if (result.success) { alert('课程更新成功'); modal.hide(); this.loadCourses(); } else { alert(result.message || '更新失败'); } } catch (e) { console.error('Update course failed', e); alert('系统错误'); } }); } // ================= Grade Entry ================= async initGradeEntry() { const courseSelect = document.getElementById('courseSelect'); // Load Courses for Select try { const response = await fetch(`${this.apiBase}/courses`); const result = await response.json(); if (result.success) { courseSelect.innerHTML = '' + result.data.courses.map(c => ``).join(''); // Check URL params const params = new URLSearchParams(window.location.search); const courseId = params.get('courseId'); if (courseId) { courseSelect.value = courseId; this.loadStudentsForGradeEntry(courseId); } } } catch (e) { console.error(e); } courseSelect.addEventListener('change', (e) => { if (e.target.value) { this.loadStudentsForGradeEntry(e.target.value); } else { document.getElementById('studentTableBody').innerHTML = '请选择课程以加载学生列表'; } }); } async loadStudentsForGradeEntry(courseId) { const tbody = document.getElementById('studentTableBody'); tbody.innerHTML = '
加载中...
'; // Since we don't have a direct "get students by course" API that returns grades yet, // we might need to rely on what we have. // Actually, we need to fetch students enrolled in the class of this course. // AND fetch their existing grades for this course. // For simplicity, let's assume we have an endpoint or we modify `getGradeStatistics` or similar? // No, we should probably add `GET /api/teacher/course/:id/students`? // Or just use `GET /api/teacher/grades?courseId=X`. // Let's assume `GET /api/teacher/grades?courseId=X` returns the list of students with their grades (or null if no grade). // I need to implement this backend logic if it's missing. // For now, I'll simulate or try to use what I have. // I'll add `getCourseGrades` to TeacherController. // Wait, I can't modify backend endlessly. // Let's check `TeacherController.js` again. // It has `addScore`. // It does NOT have `getCourseGrades`. // I need to add it to support this view. try { // Placeholder: I will add this endpoint in next step. const res = await fetch(`${this.apiBase}/grades?courseId=${courseId}`); const result = await res.json(); if (result.success) { this.renderGradeEntryTable(result.data.grades); } else { tbody.innerHTML = `${result.message}`; } } catch (e) { tbody.innerHTML = `加载失败: ${e.message}`; } } renderGradeEntryTable(grades) { const tbody = document.getElementById('studentTableBody'); if (!grades || grades.length === 0) { tbody.innerHTML = '该课程暂无学生'; return; } tbody.innerHTML = grades.map(g => ` ${g.student_id} ${g.student_name} ${g.total_score || '-'} `).join(''); // Bind save events document.querySelectorAll('.save-grade-btn').forEach(btn => { btn.addEventListener('click', (e) => this.saveGrade(e.target.closest('tr'), btn.dataset.studentId)); }); } async saveGrade(row, studentId) { const inputs = row.querySelectorAll('input'); const usual = inputs[0].value; const midterm = inputs[1].value; const final = inputs[2].value; const courseId = document.getElementById('courseSelect').value; // Simple calculation for total score preview (Backend should do the real one) const total = (usual * 0.3 + midterm * 0.3 + final * 0.4).toFixed(1); // Example weights try { const res = await fetch(`${this.apiBase}/grades`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ studentId, courseId, usual_score: usual, midterm_score: midterm, final_score: final, score: total // Passing total score as 'score' for compatibility with existing API }) }); const result = await res.json(); if (result.success) { // Update total cell row.cells[5].textContent = total; // Show success feedback const btn = row.querySelector('.save-grade-btn'); const originalText = btn.innerHTML; btn.innerHTML = ' 已保存'; btn.classList.remove('btn-primary'); btn.classList.add('btn-success'); setTimeout(() => { btn.innerHTML = originalText; btn.classList.add('btn-primary'); btn.classList.remove('btn-success'); }, 2000); } else { alert(result.message || '保存失败'); } } catch (e) { console.error(e); alert('保存失败'); } } // ================= Grade Management ================= async initGradeManagement() { // Similar logic to Grade Entry but Read-Only or Filter focused // Fetch courses for filter try { const response = await fetch(`${this.apiBase}/courses`); const result = await response.json(); if (result.success) { const select = document.getElementById('courseSelectFilter'); select.innerHTML = '' + result.data.courses.map(c => ``).join(''); } } catch (e) { console.error(e); } document.getElementById('searchBtn').addEventListener('click', () => this.searchGrades()); // Check URL params const params = new URLSearchParams(window.location.search); if (params.get('courseId')) { document.getElementById('courseSelectFilter').value = params.get('courseId'); this.searchGrades(); } } async searchGrades() { const courseId = document.getElementById('courseSelectFilter').value; const studentName = document.getElementById('studentNameFilter').value; const tbody = document.getElementById('gradeTableBody'); tbody.innerHTML = '
'; try { let url = `${this.apiBase}/grades?`; if (courseId) url += `courseId=${courseId}&`; if (studentName) url += `studentName=${studentName}`; const res = await fetch(url); const result = await res.json(); if (result.success && result.data.grades) { if (result.data.grades.length === 0) { tbody.innerHTML = '未找到相关成绩记录'; return; } tbody.innerHTML = result.data.grades.map(g => ` ${g.course_name || '-'} ${g.student_id || '-'} ${g.student_name || '-'} ${g.total_score || '-'} ${g.grade_point || '-'} ${g.grade_level || '-'} `).join(''); } } catch (e) { tbody.innerHTML = `查询失败`; } } getBadgeColor(level) { if (!level) return 'secondary'; if (level.startsWith('A')) return 'success'; if (level.startsWith('B')) return 'info'; if (level.startsWith('C')) return 'warning'; if (level.startsWith('F')) return 'danger'; return 'primary'; } // ================= Profile ================= initProfilePage() { const saveProfileBtn = document.getElementById('saveProfileBtn'); if (saveProfileBtn) { saveProfileBtn.addEventListener('click', async () => { const name = document.getElementById('inputName').value; const className = document.getElementById('inputClass').value; try { const res = await fetch('/api/auth/update-profile', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, class: className }) }); const result = await res.json(); if (result.success) { alert('资料更新成功'); // 更新侧边栏和顶栏 const nameEls = document.querySelectorAll('#teacherName, #userName, #profileName'); nameEls.forEach(el => el.textContent = name); } else { alert(result.message || '更新失败'); } } catch (e) { console.error('Update profile failed', e); alert('系统错误'); } }); } const passwordForm = document.getElementById('passwordForm'); if (passwordForm) { passwordForm.addEventListener('submit', async (e) => { e.preventDefault(); const oldPassword = document.getElementById('oldPassword').value; const newPassword = document.getElementById('newPassword').value; const confirmPassword = document.getElementById('confirmPassword').value; const errorEl = document.getElementById('passwordError'); // Hide previous error errorEl.style.display = 'none'; // Basic validation if (newPassword !== confirmPassword) { errorEl.textContent = '两次输入的新密码不一致'; errorEl.style.display = 'block'; return; } if (newPassword.length < 6) { errorEl.textContent = '新密码长度至少为 6 位'; errorEl.style.display = 'block'; return; } try { const res = await fetch('/api/auth/update-password', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ oldPassword, newPassword }) }); const result = await res.json(); if (result.success) { alert('密码修改成功,请重新登录'); // Logout and redirect await fetch('/api/auth/logout', { method: 'POST' }); window.location.href = '/login'; } else { errorEl.textContent = result.message || '修改失败'; errorEl.style.display = 'block'; } } catch (err) { console.error(err); errorEl.textContent = '服务器错误,请稍后再试'; errorEl.style.display = 'block'; } }); } } } document.addEventListener('DOMContentLoaded', () => { window.teacherManager = new TeacherManager(); });