Files
WebWork/frontend/public/js/auth.js
祀梦 bcf2c71fad refactor(frontend): 重构前端目录结构并优化认证流程
将前端文件从html目录迁移到views目录,按功能模块组织
重构认证中间件和路由处理,简化页面权限控制
更新静态资源引用路径,统一使用/public前缀
添加学生仪表板页面,优化移动端显示
移除旧版html和js文件,更新样式和脚本
2025-12-21 22:07:23 +08:00

287 lines
9.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 认证模块管理器
* 处理登录、注册、注销及权限检查
*/
class AuthManager {
constructor() {
this.apiBase = '/api';
this.init();
}
init() {
this.createNotificationContainer();
this.initEventListeners();
this.checkAuthStatus();
}
/**
* 创建通知容器
*/
createNotificationContainer() {
if (!document.getElementById('notification-container')) {
const container = document.createElement('div');
container.id = 'notification-container';
container.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
z-index: 9999;
display: flex;
flex-direction: column;
gap: 10px;
`;
document.body.appendChild(container);
}
}
/**
* 检查用户认证状态
*/
async checkAuthStatus() {
// 如果当前是公共页面可以选择不检查或者检查后更新UI
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.user) {
// 用户已登录
const redirectUrl = this.getDashboardUrl(data.user.role);
// 如果在登录/注册页,跳转到仪表板
if (isAuthPage || currentPath === '/') {
window.location.href = redirectUrl;
}
} else {
// 用户未登录,如果在受保护页面,跳转到登录页
// 注意:后端通常已经处理了重定向,这里是前端的额外保障
if (!isAuthPage && currentPath !== '/') {
// 可以在这里添加逻辑,但通常交给后端控制
}
}
} 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 = 'block';
classInput.required = true;
} else {
classField.style.display = 'none';
classInput.required = false;
classInput.value = ''; // 清空值
}
}
}
async handleLogin(e) {
e.preventDefault();
const form = e.target;
const submitBtn = 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');
setTimeout(() => {
window.location.href = this.getDashboardUrl(result.user.role);
}, 1000);
} 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 = 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) {
this.showNotification('已退出登录', 'success');
setTimeout(() => {
window.location.href = '/login';
}, 1000);
}
} catch (error) {
console.error('Logout error:', error);
// 即使出错也强制跳转到登录页
window.location.href = '/login';
}
}
}
/**
* 设置按钮加载状态
* @param {HTMLElement} btn 按钮元素
* @param {boolean} isLoading 是否正在加载
* @param {string} text 加载时的文本
* @returns {boolean} true表示状态设置成功
*/
setLoading(btn, isLoading, text = '') {
if (!btn) return false;
if (isLoading) {
if (btn.dataset.loading) return false; // 防止重复提交
btn.dataset.loading = 'true';
btn.dataset.originalText = btn.innerHTML;
btn.innerHTML = `<i class="fas fa-spinner fa-spin"></i> ${text}`;
btn.disabled = true;
} else {
btn.innerHTML = btn.dataset.originalText || btn.innerHTML;
delete btn.dataset.loading;
btn.disabled = false;
}
return true;
}
/**
* 显示通知
* @param {string} message 消息内容
* @param {string} type 消息类型 'success' | 'error' | 'info'
*/
showNotification(message, type = 'info') {
const container = document.getElementById('notification-container');
if (!container) return;
const notification = document.createElement('div');
notification.className = `notification ${type}`;
let icon = 'info-circle';
if (type === 'success') icon = 'check-circle';
if (type === 'error') icon = 'exclamation-circle';
notification.innerHTML = `
<i class="fas fa-${icon}"></i>
<span class="notification-content">${message}</span>
`;
container.appendChild(notification);
// 动画显示
requestAnimationFrame(() => {
notification.classList.add('show');
});
// 自动消失
setTimeout(() => {
notification.classList.remove('show');
notification.addEventListener('transitionend', () => {
notification.remove();
});
}, 3000);
}
}
// 初始化
document.addEventListener('DOMContentLoaded', () => {
window.authManager = new AuthManager();
});