feat: 实现教师资料更新、操作日志和系统设置功能
新增教师资料更新功能,包括个人信息修改和密码更新 添加操作日志记录系统,记录用户关键操作 实现系统设置模块,支持动态配置系统参数 重构数据库模型,新增教师表和系统设置表 优化成绩录入逻辑,支持平时分、期中和期末成绩计算 添加数据导出功能,支持学生、教师和成绩数据导出 完善管理员后台,增加统计图表和操作日志查看
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -196,11 +196,14 @@ class AuthManager {
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
alert('退出登录成功');
|
||||
window.location.href = '/login';
|
||||
} else {
|
||||
alert(result.message || '退出登录失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Logout error:', error);
|
||||
window.location.href = '/login';
|
||||
alert('退出登录出错');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,24 @@ class TeacherManager {
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.initDashboard();
|
||||
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() {
|
||||
@@ -24,27 +38,100 @@ class TeacherManager {
|
||||
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() {
|
||||
// 检查是否在仪表板页面
|
||||
if (!document.getElementById('courseList')) return;
|
||||
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 => `
|
||||
<div class="col-md-6 col-xl-4 mb-3">
|
||||
<div class="card h-100 border-0 shadow-sm">
|
||||
<div class="card-body d-flex align-items-center">
|
||||
<div class="bg-info bg-opacity-10 text-info rounded p-3 me-3">
|
||||
<i class="fas fa-users fa-lg"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h6 class="mb-1 fw-bold">${c.class_name}</h6>
|
||||
<p class="text-muted small mb-0">${c.major || '专业未设置'} | ${c.grade}级</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).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.renderDashboard(result.data.courses);
|
||||
} else {
|
||||
if (window.authManager) {
|
||||
window.authManager.showNotification(result.message || '获取课程失败', 'error');
|
||||
}
|
||||
this.courses = result.data.courses;
|
||||
this.renderDashboard(this.courses);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fetch teacher data failed:', error);
|
||||
console.error('Fetch courses failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
renderDashboard(courses) {
|
||||
const courseList = document.getElementById('courseList');
|
||||
if (!courseList) return;
|
||||
@@ -57,16 +144,22 @@ class TeacherManager {
|
||||
courseList.innerHTML = courses.map(course => `
|
||||
<div class="col-md-6 col-xl-4 mb-4">
|
||||
<div class="card h-100 border-0 shadow-sm course-card">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<span class="badge bg-primary bg-opacity-10 text-primary px-3 py-2">
|
||||
<div class="card-body p-4">
|
||||
<div class="d-flex justify-content-between align-items-start mb-3">
|
||||
<span class="badge bg-primary bg-opacity-10 text-primary">
|
||||
<i class="fas fa-book me-1"></i> ${course.course_code || 'CODE'}
|
||||
</span>
|
||||
<span class="text-muted small">${course.credit} 学分</span>
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-link text-secondary p-0 btn-edit-course"
|
||||
data-id="${course.id}">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<span class="text-muted small">${course.credit} 学分</span>
|
||||
</div>
|
||||
</div>
|
||||
<h5 class="card-title fw-bold mb-2">${course.course_name}</h5>
|
||||
<p class="card-text text-secondary small mb-4">
|
||||
<i class="fas fa-users me-1"></i> 学生人数: ${course.student_count || 0}
|
||||
<i class="fas fa-users me-1"></i> ${course.class_name || '班级未指定'}
|
||||
</p>
|
||||
<div class="d-grid gap-2">
|
||||
<a href="/teacher/grade_entry?courseId=${course.id}" class="btn btn-outline-primary btn-sm">
|
||||
@@ -81,28 +174,439 @@ class TeacherManager {
|
||||
</div>
|
||||
`).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;
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
window.teacherManager = new TeacherManager();
|
||||
|
||||
// 从 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 teacherNameEl = document.getElementById('teacherName');
|
||||
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 = '<option value="">请选择班级...</option>' +
|
||||
result.data.classes.map(c => `<option value="${c.id}">${c.class_name}</option>`).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 (nameEl) nameEl.textContent = user.name;
|
||||
if (teacherNameEl) teacherNameEl.textContent = user.name;
|
||||
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 = '<option value="">请选择班级...</option>' +
|
||||
result.data.classes.map(c => `<option value="${c.id}" ${c.id == courseData.class_id ? 'selected' : ''}>${c.class_name}</option>`).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 = '<option value="">请选择课程...</option>' +
|
||||
result.data.courses.map(c => `<option value="${c.id}">${c.course_name} (${c.class_name || '未指定班级'})</option>`).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 = '<tr><td colspan="7" class="text-center py-5 text-muted">请选择课程以加载学生列表</td></tr>';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async loadStudentsForGradeEntry(courseId) {
|
||||
const tbody = document.getElementById('studentTableBody');
|
||||
tbody.innerHTML = '<tr><td colspan="7" class="text-center py-5"><div class="spinner-border text-primary" role="status"></div><div class="mt-2">加载中...</div></td></tr>';
|
||||
|
||||
// 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 = `<tr><td colspan="7" class="text-center text-danger">${result.message}</td></tr>`;
|
||||
}
|
||||
} catch (e) {
|
||||
tbody.innerHTML = `<tr><td colspan="7" class="text-center text-danger">加载失败: ${e.message}</td></tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
renderGradeEntryTable(grades) {
|
||||
const tbody = document.getElementById('studentTableBody');
|
||||
if (!grades || grades.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="7" class="text-center py-5 text-muted">该课程暂无学生</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = grades.map(g => `
|
||||
<tr>
|
||||
<td>${g.student_id}</td>
|
||||
<td>${g.student_name}</td>
|
||||
<td><input type="number" class="form-control form-control-sm" value="${g.usual_score || ''}" placeholder="平时"></td>
|
||||
<td><input type="number" class="form-control form-control-sm" value="${g.midterm_score || ''}" placeholder="期中"></td>
|
||||
<td><input type="number" class="form-control form-control-sm" value="${g.final_score || ''}" placeholder="期末"></td>
|
||||
<td>${g.total_score || '-'}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary save-grade-btn" data-student-id="${g.student_id}">
|
||||
<i class="fas fa-save"></i> 保存
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).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 = '<i class="fas fa-check"></i> 已保存';
|
||||
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 = '<option value="">全部课程</option>' +
|
||||
result.data.courses.map(c => `<option value="${c.id}">${c.course_name}</option>`).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 = '<tr><td colspan="7" class="text-center py-5"><div class="spinner-border text-primary"></div></td></tr>';
|
||||
|
||||
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 = '<tr><td colspan="7" class="text-center py-5 text-muted">未找到相关成绩记录</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = result.data.grades.map(g => `
|
||||
<tr>
|
||||
<td>${g.course_name || '-'}</td>
|
||||
<td>${g.student_id || '-'}</td>
|
||||
<td>${g.student_name || '-'}</td>
|
||||
<td>${g.total_score || '-'}</td>
|
||||
<td>${g.grade_point || '-'}</td>
|
||||
<td><span class="badge bg-${this.getBadgeColor(g.grade_level)}">${g.grade_level || '-'}</span></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-primary" onclick="window.location.href='/teacher/grade_entry?courseId=${g.course_id}'">
|
||||
<i class="fas fa-edit"></i> 修改
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
} catch (e) {
|
||||
tbody.innerHTML = `<tr><td colspan="7" class="text-center text-danger">查询失败</td></tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user