Files
WebWork/frontend/public/js/auth.js
祀梦 b1da021185 feat: 实现教师资料更新、操作日志和系统设置功能
新增教师资料更新功能,包括个人信息修改和密码更新
添加操作日志记录系统,记录用户关键操作
实现系统设置模块,支持动态配置系统参数
重构数据库模型,新增教师表和系统设置表
优化成绩录入逻辑,支持平时分、期中和期末成绩计算
添加数据导出功能,支持学生、教师和成绩数据导出
完善管理员后台,增加统计图表和操作日志查看
2025-12-22 23:30:01 +08:00

277 lines
9.8 KiB
JavaScript

/**
* 认证模块管理器
* 处理登录、注册、注销及权限检查
* 适配 Bootstrap 5 样式
*/
class AuthManager {
constructor() {
this.apiBase = '/api';
this.init();
}
init() {
this.createNotificationContainer();
this.initEventListeners();
this.checkAuthStatus();
}
/**
* 创建通知容器 (适配 Bootstrap Toast)
*/
createNotificationContainer() {
if (!document.getElementById('notification-container')) {
const container = document.createElement('div');
container.id = 'notification-container';
container.className = 'toast-container position-fixed top-0 end-0 p-3';
container.style.zIndex = '9999';
document.body.appendChild(container);
}
}
/**
* 检查用户认证状态
*/
async checkAuthStatus() {
const currentPath = window.location.pathname;
const isAuthPage = currentPath.includes('/login') || currentPath.includes('/register');
try {
const response = await fetch(`${this.apiBase}/auth/me`);
const data = await response.json();
if (data.success && data.data && data.data.user) {
const redirectUrl = this.getDashboardUrl(data.data.user.role);
if (isAuthPage || currentPath === '/') {
window.location.href = redirectUrl;
}
}
} catch (error) {
console.error('Auth check failed:', error);
}
}
getDashboardUrl(role) {
switch(role) {
case 'student': return '/student/dashboard';
case 'teacher': return '/teacher/dashboard';
case 'admin': return '/admin/dashboard';
default: return '/dashboard';
}
}
initEventListeners() {
// 登录表单
const loginForm = document.getElementById('loginForm');
if (loginForm) {
loginForm.addEventListener('submit', (e) => this.handleLogin(e));
}
// 注册表单
const registerForm = document.getElementById('registerForm');
if (registerForm) {
registerForm.addEventListener('submit', (e) => this.handleRegister(e));
// 角色选择联动
const roleSelect = document.getElementById('role');
if (roleSelect) {
roleSelect.addEventListener('change', (e) => this.handleRoleChange(e));
}
}
// 注销按钮
document.querySelectorAll('.btn-logout, #logoutBtn').forEach(btn => {
btn.addEventListener('click', (e) => this.handleLogout(e));
});
}
handleRoleChange(e) {
const role = e.target.value;
const classField = document.getElementById('classField');
const classInput = document.getElementById('class');
if (classField && classInput) {
if (role === 'student' || role === 'teacher') {
classField.style.display = 'flex'; // 配合 Bootstrap input-group
classInput.required = true;
} else {
classField.style.display = 'none';
classInput.required = false;
classInput.value = '';
}
}
}
async handleLogin(e) {
e.preventDefault();
const form = e.target;
const submitBtn = document.getElementById('loginBtn') || form.querySelector('button[type="submit"]');
if (this.setLoading(submitBtn, true)) {
try {
const formData = new FormData(form);
const data = Object.fromEntries(formData.entries());
const response = await fetch(`${this.apiBase}/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
this.showNotification('登录成功,正在跳转...', 'success');
// 获取用户信息,优先从 result.data.user 获取,兼容旧格式 result.data 或 result.user
const user = (result.data && result.data.user) || result.data || result.user;
const role = user ? user.role : null;
if (role) {
setTimeout(() => {
window.location.href = this.getDashboardUrl(role);
}, 1000);
} else {
console.error('Login success but role not found:', result);
this.showNotification('登录状态异常,请重试', 'error');
this.setLoading(submitBtn, false);
}
} else {
this.showNotification(result.message || '登录失败', 'error');
this.setLoading(submitBtn, false);
}
} catch (error) {
console.error('Login error:', error);
this.showNotification('服务器连接失败,请稍后重试', 'error');
this.setLoading(submitBtn, false);
}
}
}
async handleRegister(e) {
e.preventDefault();
const form = e.target;
const submitBtn = document.getElementById('registerBtn') || form.querySelector('button[type="submit"]');
const formData = new FormData(form);
const data = Object.fromEntries(formData.entries());
if (data.password !== data.confirmPassword) {
this.showNotification('两次输入的密码不一致', 'error');
return;
}
if (this.setLoading(submitBtn, true)) {
try {
const response = await fetch(`${this.apiBase}/auth/register`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
this.showNotification('注册成功,请登录', 'success');
setTimeout(() => {
window.location.href = '/login';
}, 1500);
} else {
this.showNotification(result.message || '注册失败', 'error');
this.setLoading(submitBtn, false);
}
} catch (error) {
console.error('Register error:', error);
this.showNotification('服务器连接失败,请稍后重试', 'error');
this.setLoading(submitBtn, false);
}
}
}
async handleLogout(e) {
e.preventDefault();
if (confirm('确定要退出登录吗?')) {
try {
const response = await fetch(`${this.apiBase}/auth/logout`, {
method: 'POST'
});
const result = await response.json();
if (result.success) {
alert('退出登录成功');
window.location.href = '/login';
} else {
alert(result.message || '退出登录失败');
}
} catch (error) {
console.error('Logout error:', error);
alert('退出登录出错');
}
}
}
/**
* 设置按钮加载状态
*/
setLoading(btn, isLoading) {
if (!btn) return false;
const spinner = btn.querySelector('.spinner-border');
if (isLoading) {
if (btn.disabled) return false;
btn.disabled = true;
if (spinner) spinner.classList.remove('d-none');
} else {
btn.disabled = false;
if (spinner) spinner.classList.add('d-none');
}
return true;
}
/**
* 显示通知 (Bootstrap 5 Toast)
*/
showNotification(message, type = 'info') {
const container = document.getElementById('notification-container');
if (!container) return;
const iconMap = {
success: 'fa-check-circle',
error: 'fa-exclamation-circle',
warning: 'fa-exclamation-triangle',
info: 'fa-info-circle'
};
const bgMap = {
success: 'bg-success',
error: 'bg-danger',
warning: 'bg-warning',
info: 'bg-info'
};
const toastId = 'toast-' + Date.now();
const toastHtml = `
<div id="${toastId}" class="toast align-items-center text-white ${bgMap[type]} border-0" role="alert" aria-live="assertive" aria-atomic="true">
<div class="d-flex">
<div class="toast-body">
<i class="fas ${iconMap[type]} me-2"></i>
${message}
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
</div>
`;
container.insertAdjacentHTML('beforeend', toastHtml);
const toastElement = document.getElementById(toastId);
const toast = new bootstrap.Toast(toastElement, { delay: 3000 });
toast.show();
toastElement.addEventListener('hidden.bs.toast', () => {
toastElement.remove();
});
}
}
// 初始化认证管理器
document.addEventListener('DOMContentLoaded', () => {
window.authManager = new AuthManager();
});