Files
WebWork/frontend/js/admin.js
2025-12-21 21:50:37 +08:00

592 lines
19 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 AdminDashboard {
constructor() {
// 动态设置API基础URL支持file:///协议和localhost:3000访问
this.apiBase = window.location.protocol === 'file:' ? 'http://localhost:3000/api' : '/api';
this.currentUser = null;
this.stats = {};
this.users = [];
this.students = [];
this.teachers = [];
this.init();
}
async init() {
// 检查登录状态
if (!await this.checkAuth()) {
window.location.href = '/frontend/html/login.html';
return;
}
// 加载用户信息
await this.loadUserInfo();
// 加载统计数据
await this.loadStats();
// 加载用户数据
await this.loadUsers();
// 绑定事件
this.bindEvents();
// 更新界面
this.updateUI();
// 初始化图表
this.initCharts();
}
async checkAuth() {
try {
const response = await fetch(`${this.apiBase}/auth/me`, {
credentials: 'include'
});
if (!response.ok) {
return false;
}
const data = await response.json();
return data.success && data.user.role === 'admin';
} catch (error) {
console.error('认证检查失败:', error);
return false;
}
}
async loadUserInfo() {
try {
const response = await fetch(`${this.apiBase}/auth/me`, {
credentials: 'include'
});
if (response.ok) {
const data = await response.json();
if (data.success) {
this.currentUser = data.user;
}
}
} catch (error) {
console.error('加载用户信息失败:', error);
}
}
async loadStats() {
try {
const response = await fetch(`${this.apiBase}/admin/stats`, {
credentials: 'include'
});
if (response.ok) {
const data = await response.json();
if (data.success) {
this.stats = data.stats;
this.updateStatsUI();
}
}
} catch (error) {
console.error('加载统计数据失败:', error);
this.showNotification('加载统计数据失败', 'error');
}
}
async loadUsers() {
try {
const response = await fetch(`${this.apiBase}/admin/users`, {
credentials: 'include'
});
if (response.ok) {
const data = await response.json();
if (data.success) {
this.users = data.users;
this.renderUsersTable();
}
}
} catch (error) {
console.error('加载用户数据失败:', error);
this.showNotification('加载用户数据失败', 'error');
}
}
updateStatsUI() {
// 更新统计卡片
const statElements = {
'totalUsers': 'totalUsers',
'totalStudents': 'totalStudents',
'totalTeachers': 'totalTeachers',
'totalCourses': 'totalCourses',
'totalGrades': 'totalGrades',
'avgScore': 'avgScore'
};
Object.entries(statElements).forEach(([key, elementId]) => {
const element = document.getElementById(elementId);
if (element && this.stats[key] !== undefined) {
element.textContent = this.stats[key];
}
});
// 更新时间
const timeElement = document.getElementById('currentTime');
if (timeElement) {
timeElement.textContent = new Date().toLocaleString();
}
}
renderUsersTable() {
const tableBody = document.getElementById('usersTableBody');
if (!tableBody) return;
if (this.users.length === 0) {
tableBody.innerHTML = `
<tr>
<td colspan="7" class="text-center">
<div class="no-data">
<i class="fas fa-info-circle"></i>
<p>暂无用户数据</p>
</div>
</td>
</tr>
`;
return;
}
tableBody.innerHTML = this.users.map(user => {
const roleClass = this.getRoleClass(user.role);
return `
<tr>
<td><input type="checkbox" class="user-checkbox" data-id="${user.id}"></td>
<td>${user.user_id}</td>
<td>${user.full_name}</td>
<td><span class="role-badge ${roleClass}">${user.role}</span></td>
<td>${user.class_name || 'N/A'}</td>
<td>${user.email || 'N/A'}</td>
<td>
<div class="action-buttons">
<button class="btn-edit" data-id="${user.id}">
<i class="fas fa-edit"></i> 编辑
</button>
<button class="btn-delete" data-id="${user.id}">
<i class="fas fa-trash"></i> 删除
</button>
</div>
</td>
</tr>
`;
}).join('');
}
getRoleClass(role) {
switch (role) {
case 'admin': return 'role-admin';
case 'teacher': return 'role-teacher';
case 'student': return 'role-student';
default: return 'role-default';
}
}
bindEvents() {
// 导航菜单点击
document.querySelectorAll('.nav-link').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const page = link.dataset.page;
this.loadPage(page);
});
});
// 搜索按钮
document.getElementById('searchBtn')?.addEventListener('click', () => {
this.handleSearch();
});
// 重置按钮
document.getElementById('resetBtn')?.addEventListener('click', () => {
this.resetFilters();
});
// 添加用户按钮
document.getElementById('addUserBtn')?.addEventListener('click', () => {
this.addUser();
});
// 导出按钮
document.getElementById('exportBtn')?.addEventListener('click', () => {
this.exportUsers();
});
// 批量删除按钮
document.getElementById('batchDeleteBtn')?.addEventListener('click', () => {
this.batchDeleteUsers();
});
// 表格操作按钮事件委托
document.addEventListener('click', (e) => {
if (e.target.closest('.btn-edit')) {
const userId = e.target.closest('.btn-edit').dataset.id;
this.editUser(userId);
}
if (e.target.closest('.btn-delete')) {
const userId = e.target.closest('.btn-delete').dataset.id;
this.deleteUser(userId);
}
});
// 退出登录
document.getElementById('logoutBtn')?.addEventListener('click', () => {
this.handleLogout();
});
// 刷新按钮
document.getElementById('refreshBtn')?.addEventListener('click', () => {
this.refreshData();
});
}
async loadPage(page) {
// 这里可以实现页面切换逻辑
// 暂时使用简单跳转
switch (page) {
case 'users':
window.location.href = '/frontend/html/user_management.html';
break;
case 'students':
window.location.href = '/frontend/html/student_management.html';
break;
case 'teachers':
// 可以跳转到教师管理页面
break;
case 'grades':
window.location.href = '/frontend/html/grade_management.html';
break;
case 'settings':
// 可以跳转到系统设置页面
break;
}
}
handleSearch() {
const userId = document.getElementById('userIdFilter')?.value || '';
const name = document.getElementById('nameFilter')?.value || '';
const role = document.getElementById('roleFilter')?.value || '';
const className = document.getElementById('classFilter')?.value || '';
// 这里可以实现搜索逻辑
this.showNotification('搜索功能待实现', 'info');
}
resetFilters() {
document.getElementById('userIdFilter').value = '';
document.getElementById('nameFilter').value = '';
document.getElementById('roleFilter').value = '';
document.getElementById('classFilter').value = '';
// 重新加载数据
this.loadUsers();
}
async addUser() {
// 这里可以打开添加用户模态框
const userData = {
user_id: prompt('请输入用户ID:'),
full_name: prompt('请输入姓名:'),
role: prompt('请输入角色 (admin/teacher/student):'),
email: prompt('请输入邮箱:'),
class_name: prompt('请输入班级 (学生/教师可选):')
};
if (!userData.user_id || !userData.full_name || !userData.role) {
this.showNotification('用户ID、姓名和角色为必填项', 'error');
return;
}
try {
const response = await fetch(`${this.apiBase}/admin/users`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify(userData)
});
const data = await response.json();
if (data.success) {
this.showNotification('用户添加成功', 'success');
await this.loadUsers();
} else {
this.showNotification(data.message || '添加失败', 'error');
}
} catch (error) {
console.error('添加用户失败:', error);
this.showNotification('添加用户失败', 'error');
}
}
async exportUsers() {
try {
const response = await fetch(`${this.apiBase}/admin/users/export`, {
credentials: 'include'
});
if (response.ok) {
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `用户列表_${new Date().toISOString().split('T')[0]}.xlsx`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}
} catch (error) {
console.error('导出失败:', error);
this.showNotification('导出失败', 'error');
}
}
async batchDeleteUsers() {
const checkboxes = document.querySelectorAll('.user-checkbox:checked');
if (checkboxes.length === 0) {
this.showNotification('请选择要删除的用户', 'warning');
return;
}
if (!confirm(`确定要删除选中的 ${checkboxes.length} 个用户吗?`)) {
return;
}
const userIds = Array.from(checkboxes).map(cb => cb.dataset.id);
try {
const response = await fetch(`${this.apiBase}/admin/users/batch`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ userIds })
});
const data = await response.json();
if (data.success) {
this.showNotification(`成功删除 ${userIds.length} 个用户`, 'success');
await this.loadUsers();
} else {
this.showNotification(data.message || '删除失败', 'error');
}
} catch (error) {
console.error('批量删除失败:', error);
this.showNotification('批量删除失败', 'error');
}
}
async editUser(userId) {
const user = this.users.find(u => u.id == userId);
if (!user) return;
// 这里可以打开编辑模态框
const newName = prompt('请输入新的姓名:', user.full_name);
if (newName === null) return;
const newRole = prompt('请输入新的角色:', user.role);
if (newRole === null) return;
try {
const response = await fetch(`${this.apiBase}/admin/users/${userId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({
full_name: newName,
role: newRole,
email: user.email,
class_name: user.class_name
})
});
const data = await response.json();
if (data.success) {
this.showNotification('用户更新成功', 'success');
await this.loadUsers();
} else {
this.showNotification(data.message || '更新失败', 'error');
}
} catch (error) {
console.error('更新用户失败:', error);
this.showNotification('更新用户失败', 'error');
}
}
async deleteUser(userId) {
if (!confirm('确定要删除这个用户吗?')) {
return;
}
try {
const response = await fetch(`${this.apiBase}/admin/users/${userId}`, {
method: 'DELETE',
credentials: 'include'
});
const data = await response.json();
if (data.success) {
this.showNotification('用户删除成功', 'success');
await this.loadUsers();
} else {
this.showNotification(data.message || '删除失败', 'error');
}
} catch (error) {
console.error('删除用户失败:', error);
this.showNotification('删除用户失败', 'error');
}
}
async handleLogout() {
try {
const response = await fetch(`${this.apiBase}/auth/logout`, {
method: 'POST',
credentials: 'include'
});
if (response.ok) {
window.location.href = '/html/login.html';
}
} catch (error) {
console.error('退出登录失败:', error);
}
}
async refreshData() {
await this.loadStats();
await this.loadUsers();
this.showNotification('数据已刷新', 'success');
}
updateUI() {
// 更新用户信息
if (this.currentUser) {
const userInfoElements = document.querySelectorAll('.user-info');
userInfoElements.forEach(el => {
el.textContent = `${this.currentUser.full_name} (${this.currentUser.role})`;
});
}
}
async initCharts() {
// 加载Chart.js库
await this.loadChartLibrary();
// 初始化用户分布饼图
this.initUserDistributionChart();
// 初始化成绩分布柱状图
this.initGradeDistributionChart();
}
showNotification(message, type = 'info') {
// 创建通知元素
const notification = document.createElement('div');
notification.className = `notification notification-${type}`;
notification.innerHTML = `
<i class="fas fa-${type === 'success' ? 'check-circle' : type === 'error' ? 'exclamation-circle' : type === 'warning' ? 'exclamation-triangle' : 'info-circle'}"></i>
<span>${message}</span>
<button class="notification-close">&times;</button>
`;
// 添加到页面
document.body.appendChild(notification);
// 添加关闭事件
notification.querySelector('.notification-close').addEventListener('click', () => {
notification.remove();
});
// 自动移除
setTimeout(() => {
if (notification.parentNode) {
notification.remove();
}
}, 5000);
}
async loadChartLibrary() {
if (typeof Chart !== 'undefined') return;
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/chart.js@3.9.1/dist/chart.min.js';
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
initUserDistributionChart() {
const ctx = document.getElementById('userDistributionChart');
if (!ctx) return;
// 模拟数据
const data = {
labels: ['学生', '教师', '管理员'],
datasets: [{
data: [this.stats.totalStudents || 100, this.stats.totalTeachers || 20, 1],
backgroundColor: [
'rgba(54, 162, 235, 0.8)',
'rgba(255, 206, 86, 0.8)',
'rgba(255, 99, 132, 0.8)'
]
}]
};
new Chart(ctx, {
type: 'pie',
data: data,
options: {
responsive: true,
plugins: {
legend: {
position: 'bottom'
}
}
}
});
}
initGradeDistributionChart() {
const ctx = document.getElementById('gradeDistributionChart');
if (!ctx) return;
// 模拟数据
const data = {
labels: ['A', 'B', 'C', 'D', 'F'],
datasets: [{
label: '成绩分布',
data: [25, 35, 20, 15, 5],
backgroundColor: [
'rgba(75, 192, 192, 0.8)',
'rgba(54, 162, 235, 0.8)',
'rgba(255, 206, 86, 0.8)',
'rgba(255, 159, 64, 0.8)',
'rgba(255, 99, 132, 0.8)'
]
}]
};
new Chart(ctx, {
type: 'bar',
data: data,
options: {
responsive: true,
scales: {
y: {
beginAtZero: true,
ticks: {
stepSize: 10
}
}
}
}
});
}
}