871 lines
30 KiB
JavaScript
871 lines
30 KiB
JavaScript
document.addEventListener('DOMContentLoaded', () => {
|
|
const themeToggle = document.getElementById('themeToggle');
|
|
if (themeToggle) {
|
|
themeToggle.addEventListener('click', () => {
|
|
document.body.classList.toggle('dark-mode');
|
|
themeToggle.innerHTML = document.body.classList.contains('dark-mode')
|
|
? '<i class="fas fa-sun"></i>'
|
|
: '<i class="fas fa-moon"></i>';
|
|
});
|
|
}
|
|
|
|
// 初始化用户管理相关的功能
|
|
initUserManagement();
|
|
|
|
// 添加用户表单提交
|
|
document.getElementById('addUserForm').addEventListener('submit', async function(e) {
|
|
e.preventDefault();
|
|
const userData = {
|
|
username: document.getElementById('name').value.trim(),
|
|
email: document.getElementById('email').value.trim(),
|
|
password: document.getElementById('password').value,
|
|
phone_number: document.getElementById('phoneNumber').value.trim() || null,
|
|
role: document.getElementById('role').value,
|
|
is_active: false,
|
|
email_verified: false,
|
|
phone_verified: false
|
|
};
|
|
|
|
try {
|
|
// 前端验证
|
|
validateUserData(userData);
|
|
|
|
const token = localStorage.getItem('access_token');
|
|
|
|
const response = await fetch(`${API_URL}/register`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${token}`
|
|
},
|
|
body: JSON.stringify(userData)
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
console.log('服务器返回的完整错误信息:', JSON.stringify(errorData, null, 2));
|
|
|
|
if (Array.isArray(errorData.detail)) {
|
|
const firstError = errorData.detail[0];
|
|
throw new Error(firstError?.msg || lang[selectedLang].addUserFailed);
|
|
}
|
|
|
|
throw new Error(errorData.detail || lang[selectedLang].addUserFailed);
|
|
}
|
|
|
|
fetchUsers();
|
|
closeAddUserModal();
|
|
this.reset(); // 重置表单
|
|
showToast(lang[selectedLang].addUserSuccess, 'success');
|
|
} catch (error) {
|
|
console.error(lang[selectedLang].addUserError, error);
|
|
showToast(error.message || lang[selectedLang].addUserFailed, 'error');
|
|
}
|
|
});
|
|
|
|
|
|
// 初始化用户管理相关的功能
|
|
initUserManagement();
|
|
});
|
|
|
|
function initUserManagement() {
|
|
// 初始化用户管理相关的功能
|
|
checkLoginStatus();
|
|
|
|
showFeature();
|
|
}
|
|
|
|
const API_URL = 'https://user.obscura.work/user';
|
|
let currentUser = null;
|
|
|
|
// 获取用户信息的函数
|
|
async function fetchUserInfo() {
|
|
try {
|
|
const token = localStorage.getItem('access_token');
|
|
if (!token) {
|
|
throw new Error('No access token found');
|
|
}
|
|
|
|
const response = await makeAuthenticatedRequest(`${API_URL}/me`);
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch user info');
|
|
}
|
|
|
|
const userInfo = await response.json();
|
|
return userInfo; // 直接返回用户信息
|
|
|
|
} catch (error) {
|
|
console.error('Error fetching user info:', error);
|
|
logout();
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// 更新用户显示的函数
|
|
function updateUserDisplay() {
|
|
const userName = document.getElementById('userName');
|
|
const userAvatar = document.getElementById('userAvatar');
|
|
const dashboardContent = document.getElementById('dashboardContent');
|
|
const usersContent = document.getElementById('usersContent');
|
|
|
|
if (currentUser) {
|
|
userName.textContent = currentUser.username;
|
|
userAvatar.textContent = currentUser.username.charAt(0).toUpperCase();
|
|
if (dashboardContent) dashboardContent.style.display = 'block';
|
|
if (usersContent) usersContent.style.display = 'none';
|
|
if (userCenterContent) userCenterContent.style.display = 'none';
|
|
|
|
} else {
|
|
userName.textContent = 'Guest';
|
|
userAvatar.textContent = 'G';
|
|
if (dashboardContent) dashboardContent.style.display = 'none';
|
|
if (usersContent) usersContent.style.display = 'none';
|
|
if (userCenterContent) userCenterContent.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
// 登出函数
|
|
async function logout() {
|
|
const token = localStorage.getItem('access_token');
|
|
if (token) {
|
|
try {
|
|
await fetch(`${API_URL}/logout`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
console.error(lang[selectedLang].logoutError, error);
|
|
}
|
|
}
|
|
localStorage.removeItem('access_token');
|
|
currentUser = null;
|
|
updateUserDisplay();
|
|
window.location.href = 'login.html'; // 重定向到登录页面
|
|
}
|
|
|
|
// 显示仪表板
|
|
async function showDashboard() {
|
|
try {
|
|
const userInfo = await fetchUserInfo();
|
|
if (!userInfo) {
|
|
alert(lang[selectedLang].pleaseLoginToViewDashboard);
|
|
window.location.href = 'login.html'; // 重定向到登录页面
|
|
return;
|
|
}
|
|
|
|
// 检查邮箱验证状态
|
|
const emailVerified = userInfo.email_verified === 'True' || userInfo.email_verified === true;
|
|
console.log('Email Verified:', emailVerified); // 打印邮箱验证状态
|
|
if (!emailVerified) {
|
|
alert(lang[selectedLang].pleaseVerifyEmail);
|
|
showUserCenterSection('profile'); // 跳转到个人资料页面
|
|
return;
|
|
}
|
|
|
|
// 如果用户已登录且邮箱已验证,继续显示仪表盘
|
|
const pageTitle = document.getElementById('pageTitle');
|
|
const dashboardContent = document.getElementById('dashboardContent');
|
|
const usersContent = document.getElementById('usersContent');
|
|
const chatContent = document.getElementById('chatContent');
|
|
const featureContent = document.getElementById('featureContent');
|
|
const userCenterContent = document.getElementById('userCenterContent');
|
|
const showApiDocs = document.getElementById('showApiDocs');
|
|
const apiDocsContent = document.getElementById('apiDocsContent');
|
|
;
|
|
if (!userInfo) {
|
|
console.error('No user info found');
|
|
return;
|
|
}
|
|
|
|
if (apiDocsContent) apiDocsContent.style.display = 'none'
|
|
if (showApiDocs) showApiDocs.style.display = 'none';
|
|
if (userCenterContent) userCenterContent.style.display = 'none';
|
|
if (chatContent) chatContent.style.display = 'none';
|
|
if (featureContent) featureContent.style.display = 'none';
|
|
if (usersContent) usersContent.style.display = 'none';
|
|
if (pageTitle) pageTitle.textContent = lang[selectedLang].dashboard;
|
|
if (dashboardContent) {
|
|
dashboardContent.style.display = 'block';
|
|
dashboardContent.innerHTML = `
|
|
<h2>Hello, ${userInfo.username}!</h2>
|
|
<section class="dashboard-cards">
|
|
<div class="card">
|
|
<i class="fas fa-users card-icon"></i>
|
|
<h3 class="card-title" data-i18n="totalUsers">Total Users</h3>
|
|
<p class="card-value" id="totalUsers">Loading...</p>
|
|
</div>
|
|
<div class="card">
|
|
<i class="fas fa-user-plus card-icon"></i>
|
|
<h3 class="card-title" data-i18n="newUsersToday">New Users Today</h3>
|
|
<p class="card-value" id="newUsers">Loading...</p>
|
|
</div>
|
|
<div class="card">
|
|
<i class="fas fa-user-check card-icon"></i>
|
|
<h3 class="card-title" data-i18n="activeUsers">Active Users</h3>
|
|
<p class="card-value" id="activeUsers">Loading...</p>
|
|
</div>
|
|
</section>
|
|
<section class="recent-activities">
|
|
<h3 data-i18n="recentNewUsers">Recent New Users</h3>
|
|
<ul class="activity-list" id="activityList">
|
|
<li data-i18n="loading">Loading recent users...</li>
|
|
</ul>
|
|
</section>
|
|
<div class="chart-container">
|
|
<canvas id="userChart"></canvas>
|
|
</div>
|
|
`;
|
|
fetchDashboardData();
|
|
}
|
|
} catch (error) {
|
|
console.error('Error in showDashboard:', error);
|
|
alert(lang[selectedLang].errorGettingUserInfo);
|
|
}
|
|
}
|
|
|
|
// 获取仪表板数据
|
|
async function fetchDashboardData() {
|
|
try {
|
|
const response = await makeAuthenticatedRequest(`${API_URL}/dashboard`);
|
|
if (!response.ok) {
|
|
throw new Error(lang[selectedLang].fetchDashboardDataFailed);
|
|
}
|
|
const data = await response.json();
|
|
updateDashboardUI(data);
|
|
fetchUserStats();
|
|
} catch (error) {
|
|
console.error('Error fetching dashboard data:', error);
|
|
alert(lang[selectedLang].fetchDashboardDataFailed);
|
|
}
|
|
}
|
|
|
|
async function fetchUserStats() {
|
|
try {
|
|
const response = await makeAuthenticatedRequest(`${API_URL}/user-stats`);
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch user stats');
|
|
}
|
|
const data = await response.json();
|
|
createUserChart(data);
|
|
} catch (error) {
|
|
console.error('Error fetching user stats:', error);
|
|
}
|
|
}
|
|
|
|
// 更新仪表板UI
|
|
function updateDashboardUI(data) {
|
|
try {
|
|
// 更新用户统计数据
|
|
const totalUsersEl = document.getElementById('totalUsers');
|
|
const newUsersEl = document.getElementById('newUsers');
|
|
const activeUsersEl = document.getElementById('activeUsers');
|
|
|
|
if (totalUsersEl) totalUsersEl.textContent = data.total_users;
|
|
if (newUsersEl) newUsersEl.textContent = data.new_users_today;
|
|
if (activeUsersEl) activeUsersEl.textContent = data.active_users;
|
|
|
|
// 更新最近用户列表
|
|
const activityList = document.getElementById('activityList');
|
|
if (activityList) {
|
|
activityList.innerHTML = '';
|
|
if (data.recent_users && Array.isArray(data.recent_users) && data.recent_users.length > 0) {
|
|
data.recent_users.forEach(user => {
|
|
const li = document.createElement('li');
|
|
li.className = 'activity-item';
|
|
li.innerHTML = `
|
|
<i class="fas fa-user-plus activity-icon"></i>
|
|
<div class="activity-details">
|
|
<p class="activity-description">${lang[selectedLang].newUser}: ${user.username}</p>
|
|
<p class="activity-time">${formatActivityTime(user.created_at)}</p>
|
|
</div>
|
|
`;
|
|
activityList.appendChild(li);
|
|
});
|
|
} else {
|
|
console.warn(lang[selectedLang].noRecentUsersData);
|
|
activityList.innerHTML = `<li>${lang[selectedLang].noRecentUserActivities}</li>`;
|
|
}
|
|
} else {
|
|
console.error(lang[selectedLang].activityListNotFound);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error updating dashboard UI:', error);
|
|
}
|
|
}
|
|
|
|
// 格式化活动时间
|
|
function formatActivityTime(timestamp) {
|
|
const date = new Date(timestamp);
|
|
const now = new Date();
|
|
const diffTime = Math.abs(now - date);
|
|
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
|
|
const diffHours = Math.floor(diffTime / (1000 * 60 * 60));
|
|
const diffMinutes = Math.floor(diffTime / (1000 * 60));
|
|
|
|
if (diffDays > 0) {
|
|
return `${diffDays} ${lang[selectedLang].daysAgo}`;
|
|
} else if (diffHours > 0) {
|
|
return `${diffHours} ${lang[selectedLang].hoursAgo}`;
|
|
} else if (diffMinutes > 0) {
|
|
return `${diffMinutes} ${lang[selectedLang].minutesAgo}`;
|
|
} else {
|
|
return lang[selectedLang].justNow;
|
|
}
|
|
}
|
|
|
|
// 创建用户统计图表
|
|
function createUserChart(userStats) {
|
|
const ctx = document.getElementById('userChart').getContext('2d');
|
|
new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: userStats.dates,
|
|
datasets: [{
|
|
label: lang[selectedLang].newUsersToday,
|
|
data: userStats.daily_new_users,
|
|
backgroundColor: 'rgba(102, 126, 234, 0.2)',
|
|
borderColor: 'rgba(102, 126, 234, 1)',
|
|
borderWidth: 2,
|
|
pointBackgroundColor: 'rgba(102, 126, 234, 1)',
|
|
pointBorderColor: '#fff',
|
|
pointHoverBackgroundColor: '#fff',
|
|
pointHoverBorderColor: 'rgba(102, 126, 234, 1)'
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
title: {
|
|
display: true,
|
|
text: lang[selectedLang].numberOfNewUsers
|
|
}
|
|
},
|
|
x: {
|
|
title: {
|
|
display: true,
|
|
text: lang[selectedLang].date
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
// 显示用户列表
|
|
let currentPage = 1;
|
|
const usersPerPage = 10; // 每页显示的用户数量
|
|
let allUsers = []; // 存储所有用户数据
|
|
|
|
// 在文件开头添加变量
|
|
let searchUserTimeout = null;
|
|
|
|
// 修改 showUsers 函数,添加搜索框初始化
|
|
async function showUsers() {
|
|
try {
|
|
const userInfo = await fetchUserInfo();
|
|
if (!userInfo) {
|
|
alert(lang[selectedLang].pleaseLoginToViewUserList);
|
|
window.location.href = 'login.html';
|
|
return;
|
|
}
|
|
|
|
// 检查邮箱验证状态
|
|
const emailVerified = userInfo.email_verified === 'True' || userInfo.email_verified === true;
|
|
if (!emailVerified) {
|
|
alert(lang[selectedLang].pleaseCompleteEmailVerification);
|
|
showUserCenterSection('profile');
|
|
return;
|
|
}
|
|
|
|
// 更新页面内容
|
|
const pageTitle = document.getElementById('pageTitle');
|
|
const dashboardContent = document.getElementById('dashboardContent');
|
|
const usersContent = document.getElementById('usersContent');
|
|
const chatContent = document.getElementById('chatContent');
|
|
const featureContent = document.getElementById('featureContent');
|
|
const userCenterContent = document.getElementById('userCenterContent');
|
|
const apiDocsContent = document.getElementById('apiDocsContent');
|
|
|
|
if (apiDocsContent) apiDocsContent.style.display = 'none';
|
|
if (dashboardContent) dashboardContent.style.display = 'none';
|
|
if (featureContent) featureContent.style.display = 'none';
|
|
if (usersContent) usersContent.style.display = 'block';
|
|
if (chatContent) chatContent.style.display = 'none';
|
|
if (userCenterContent) userCenterContent.style.display = 'none';
|
|
if (pageTitle) pageTitle.innerText = lang[selectedLang].userManagement;
|
|
|
|
// 初始化搜索功能
|
|
initializeUserSearch();
|
|
|
|
fetchUsers();
|
|
} catch (error) {
|
|
console.error('Error in showUsers:', error);
|
|
alert(lang[selectedLang].errorGettingUserInfo);
|
|
}
|
|
}
|
|
|
|
// 修改搜索初始化函数
|
|
function initializeUserSearch() {
|
|
const searchInput = document.getElementById('userSearch');
|
|
const searchIcon = document.querySelector('.user-search-icon');
|
|
|
|
if (searchInput) {
|
|
// 添加占位符提示
|
|
searchInput.placeholder = '搜索用户名、邮箱或角色(admin/user)...';
|
|
|
|
// 输入事件处理(实时搜索)
|
|
searchInput.addEventListener('input', function(e) {
|
|
if (searchUserTimeout) {
|
|
clearTimeout(searchUserTimeout);
|
|
}
|
|
searchUserTimeout = setTimeout(() => {
|
|
searchUsers(e.target.value);
|
|
}, 300);
|
|
});
|
|
|
|
// 回车键事件处理
|
|
searchInput.addEventListener('keypress', function(e) {
|
|
if (e.key === 'Enter') {
|
|
e.preventDefault();
|
|
searchUsers(this.value);
|
|
}
|
|
});
|
|
|
|
// 点击搜索图标事件处理
|
|
if (searchIcon) {
|
|
searchIcon.addEventListener('click', function() {
|
|
searchUsers(searchInput.value);
|
|
});
|
|
searchIcon.style.cursor = 'pointer';
|
|
}
|
|
}
|
|
}
|
|
|
|
// 修改搜索用户函数
|
|
async function searchUsers(query) {
|
|
try {
|
|
const token = localStorage.getItem('access_token');
|
|
const response = await fetch(`${API_URL}/api/users-search?query=${encodeURIComponent(query)}`, {
|
|
method: 'GET',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('搜索失败');
|
|
}
|
|
|
|
const searchResults = await response.json();
|
|
allUsers = searchResults; // 更新全局用户列表
|
|
currentPage = 1; // 重置到第一页
|
|
updatePagination();
|
|
displayUsers();
|
|
|
|
// 如果没有搜索结果,显示提示
|
|
if (searchResults.length === 0) {
|
|
showToast('未找到匹配的用户', 'info');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('搜索用户时出错:', error);
|
|
showToast(error.message || lang[selectedLang].searchFailed, 'error');
|
|
}
|
|
}
|
|
|
|
// 获取用户列表
|
|
async function fetchUsers() {
|
|
try {
|
|
const response = await makeAuthenticatedRequest(`${API_URL}/api/users`);
|
|
if (!response.ok) {
|
|
throw new Error('Network response was not ok');
|
|
}
|
|
allUsers = await response.json();
|
|
updatePagination();
|
|
displayUsers();
|
|
return allUsers;
|
|
} catch (error) {
|
|
console.error('Error fetching users:', error);
|
|
alert('Failed to fetch users. Please try again.');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// 更新分页信息
|
|
function updatePagination() {
|
|
const totalPages = Math.ceil(allUsers.length / usersPerPage);
|
|
const paginationDiv = document.querySelector('.pagination');
|
|
let paginationHTML = `
|
|
<a href="#" class="page-nav" onclick="changePage(-1)"><</a>
|
|
`;
|
|
|
|
const showPages = (start, end) => {
|
|
for (let i = start; i <= end; i++) {
|
|
paginationHTML += `
|
|
<a href="#" class="page-number ${i === currentPage ? 'active' : ''}" onclick="goToPage(${i})">${i}</a>
|
|
`;
|
|
}
|
|
};
|
|
|
|
if (totalPages <= 7) {
|
|
showPages(1, totalPages);
|
|
} else {
|
|
if (currentPage <= 3) {
|
|
showPages(1, 5);
|
|
paginationHTML += `<span class="page-ellipsis">...</span>`;
|
|
paginationHTML += `<a href="#" class="page-number" onclick="goToPage(${totalPages})">${totalPages}</a>`;
|
|
} else if (currentPage >= totalPages - 2) {
|
|
paginationHTML += `<a href="#" class="page-number" onclick="goToPage(1)">1</a>`;
|
|
paginationHTML += `<span class="page-ellipsis">...</span>`;
|
|
showPages(totalPages - 4, totalPages);
|
|
} else {
|
|
paginationHTML += `<a href="#" class="page-number" onclick="goToPage(1)">1</a>`;
|
|
paginationHTML += `<span class="page-ellipsis">...</span>`;
|
|
showPages(currentPage - 1, currentPage + 1);
|
|
paginationHTML += `<span class="page-ellipsis">...</span>`;
|
|
paginationHTML += `<a href="#" class="page-number" onclick="goToPage(${totalPages})">${totalPages}</a>`;
|
|
}
|
|
}
|
|
|
|
paginationHTML += `
|
|
<a href="#" class="page-nav" onclick="changePage(1)">></a>
|
|
`;
|
|
|
|
paginationDiv.innerHTML = paginationHTML;
|
|
}
|
|
|
|
// 切换到指定页面
|
|
function goToPage(page) {
|
|
currentPage = page;
|
|
displayUsers();
|
|
updatePagination();
|
|
}
|
|
|
|
// 修改changePage函数
|
|
function changePage(direction) {
|
|
const totalPages = Math.ceil(allUsers.length / usersPerPage);
|
|
currentPage += direction;
|
|
if (currentPage < 1) currentPage = 1;
|
|
if (currentPage > totalPages) currentPage = totalPages;
|
|
displayUsers();
|
|
updatePagination();
|
|
}
|
|
|
|
|
|
// 修改显示用户函数
|
|
function displayUsers() {
|
|
const start = (currentPage - 1) * usersPerPage;
|
|
const end = start + usersPerPage;
|
|
const pageUsers = allUsers.slice(start, end);
|
|
|
|
const userTableBody = document.getElementById('userTableBody');
|
|
userTableBody.innerHTML = '';
|
|
|
|
if (pageUsers.length === 0) {
|
|
// 果没有用户数据,显示一个提示行
|
|
const emptyRow = document.createElement('tr');
|
|
emptyRow.innerHTML = `
|
|
<td colspan="5" class="text-center">${lang[selectedLang].noUsersFound}</td>
|
|
`;
|
|
userTableBody.appendChild(emptyRow);
|
|
return;
|
|
}
|
|
|
|
pageUsers.forEach((user, index) => {
|
|
const row = document.createElement('tr');
|
|
row.innerHTML = `
|
|
<td>${(currentPage - 1) * usersPerPage + index + 1}</td>
|
|
<td>${user.username}</td>
|
|
<td>${user.email}</td>
|
|
<td>${user.role}</td>
|
|
<td>
|
|
<button onclick="editUser('${user.id}')"><i class="fas fa-edit"></i></button>
|
|
<button onclick="deleteUser('${user.id}')"><i class="fas fa-trash"></i></button>
|
|
</td>
|
|
`;
|
|
userTableBody.appendChild(row);
|
|
});
|
|
}
|
|
|
|
// 修改编辑用户函数
|
|
async function editUser(id) {
|
|
try {
|
|
// 获取当前用户信息
|
|
const userResponse = await makeAuthenticatedRequest(`${API_URL}/api/users/${id}`);
|
|
if (!userResponse.ok) {
|
|
throw new Error(lang[selectedLang].getUserInfoFailed);
|
|
}
|
|
const userData = await userResponse.json();
|
|
|
|
// 创建编辑表单
|
|
const formContent = `
|
|
<div class="edit-user-form">
|
|
<div class="form-group">
|
|
<label for="edit-username">${lang[selectedLang].username}</label>
|
|
<input type="text" id="edit-username" value="${userData.username}" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="edit-role">${lang[selectedLang].role}</label>
|
|
<select id="edit-role">
|
|
<option value="admin" ${userData.role === 'admin' ? 'selected' : ''}>Admin</option>
|
|
<option value="user" ${userData.role === 'user' ? 'selected' : ''}>User</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// 使用自定义对话框显示表单
|
|
const result = await showCustomDialog(
|
|
lang[selectedLang].editUser,
|
|
formContent,
|
|
lang[selectedLang].save,
|
|
lang[selectedLang].cancel
|
|
);
|
|
|
|
if (result && result.confirmed) {
|
|
const { username, role } = result.formData;
|
|
|
|
if (!username) {
|
|
throw new Error(lang[selectedLang].usernameRequired);
|
|
}
|
|
|
|
const response = await makeAuthenticatedRequest(`${API_URL}/edit/${id}`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
username,
|
|
role
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
throw new Error(errorData.detail || lang[selectedLang].updateUserFailed);
|
|
}
|
|
|
|
await fetchUsers(); // 重新获取用户列表
|
|
showToast(lang[selectedLang].userInfoUpdateSuccess, 'success');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error editing user:', error);
|
|
showToast(error.message || lang[selectedLang].updateUserFailed, 'error');
|
|
}
|
|
}
|
|
|
|
// 修改自定义对话框函数
|
|
function showCustomDialog(title, content, confirmText, cancelText) {
|
|
return new Promise((resolve) => {
|
|
const dialog = document.createElement('div');
|
|
dialog.className = 'custom-dialog';
|
|
dialog.innerHTML = `
|
|
<div class="dialog-content">
|
|
<h2>${title}</h2>
|
|
<div>${content}</div>
|
|
<div class="dialog-buttons">
|
|
<button class="btn-confirm">${confirmText}</button>
|
|
<button class="btn-cancel">${cancelText}</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
document.body.appendChild(dialog);
|
|
|
|
const confirmBtn = dialog.querySelector('.btn-confirm');
|
|
const cancelBtn = dialog.querySelector('.btn-cancel');
|
|
|
|
confirmBtn.addEventListener('click', () => {
|
|
const username = dialog.querySelector('#edit-username')?.value;
|
|
const role = dialog.querySelector('#edit-role')?.value;
|
|
document.body.removeChild(dialog);
|
|
resolve({
|
|
confirmed: true,
|
|
formData: { username, role }
|
|
});
|
|
});
|
|
|
|
cancelBtn.addEventListener('click', () => {
|
|
document.body.removeChild(dialog);
|
|
resolve({ confirmed: false });
|
|
});
|
|
});
|
|
}
|
|
|
|
// 修改显示提示消息函数
|
|
function showToast(message, type = 'info') {
|
|
try {
|
|
// 移除已存在的提示框
|
|
const existingToast = document.querySelector('.toast-container');
|
|
if (existingToast && existingToast.parentNode) {
|
|
existingToast.parentNode.removeChild(existingToast);
|
|
}
|
|
|
|
// 创建提示框容器
|
|
const toastContainer = document.createElement('div');
|
|
toastContainer.className = 'toast-container';
|
|
|
|
// 创建提示框
|
|
const toast = document.createElement('div');
|
|
toast.className = `toast toast-${type}`;
|
|
|
|
// 添加图标
|
|
const icon = document.createElement('i');
|
|
switch (type) {
|
|
case 'success':
|
|
icon.className = 'fas fa-check-circle';
|
|
break;
|
|
case 'error':
|
|
icon.className = 'fas fa-exclamation-circle';
|
|
break;
|
|
case 'info':
|
|
icon.className = 'fas fa-info-circle';
|
|
break;
|
|
case 'warning':
|
|
icon.className = 'fas fa-exclamation-triangle';
|
|
break;
|
|
}
|
|
|
|
// 创建消息文本元素
|
|
const messageText = document.createElement('span');
|
|
messageText.textContent = message;
|
|
|
|
// 组装提示框
|
|
toast.appendChild(icon);
|
|
toast.appendChild(messageText);
|
|
toastContainer.appendChild(toast);
|
|
document.body.appendChild(toastContainer);
|
|
|
|
// 添加显示动画
|
|
setTimeout(() => {
|
|
toast.classList.add('show');
|
|
setTimeout(() => {
|
|
toast.classList.remove('show');
|
|
setTimeout(() => {
|
|
if (toastContainer && toastContainer.parentNode) {
|
|
toastContainer.parentNode.removeChild(toastContainer);
|
|
}
|
|
}, 300);
|
|
}, 3000);
|
|
}, 100);
|
|
} catch (error) {
|
|
console.error('Error showing toast:', error);
|
|
}
|
|
}
|
|
|
|
// 修改删除用户函数
|
|
async function deleteUser(id) {
|
|
try {
|
|
const confirmed = await showCustomDialog(
|
|
lang[selectedLang].confirmDelete,
|
|
`<p>${lang[selectedLang].confirmDeleteUserMessage}</p>`,
|
|
lang[selectedLang].delete,
|
|
lang[selectedLang].cancel
|
|
);
|
|
|
|
if (confirmed) {
|
|
const response = await makeAuthenticatedRequest(`${API_URL}/api/users/${id}`, {
|
|
method: 'DELETE'
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
throw new Error(errorData.detail || lang[selectedLang].deleteUserFailed);
|
|
}
|
|
|
|
await fetchUsers(); // 重新获取用户列表
|
|
showToast(lang[selectedLang].userDeleteSuccess, 'success');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error deleting user:', error);
|
|
showToast(error.message || lang[selectedLang].deleteUserFailed, 'error');
|
|
}
|
|
}
|
|
|
|
// 打开添加用户模态框
|
|
function openAddUserModal() {
|
|
const modal = document.getElementById('addUserModal');
|
|
const form = document.getElementById('addUserForm');
|
|
form.reset(); // 确保表单是空的
|
|
modal.style.display = 'block';
|
|
}
|
|
|
|
// 关闭添加用户模态框
|
|
function closeAddUserModal() {
|
|
const modal = document.getElementById('addUserModal');
|
|
const form = document.getElementById('addUserForm');
|
|
modal.style.display = 'none';
|
|
form.reset(); // 重置表单
|
|
}
|
|
|
|
|
|
async function makeAuthenticatedRequest(url, options = {}) {
|
|
const token = localStorage.getItem('access_token');
|
|
if (!token) {
|
|
throw new Error('未找到token');
|
|
}
|
|
|
|
const headers = new Headers(options.headers || {});
|
|
headers.append('Authorization', `Bearer ${token}`);
|
|
|
|
const response = await fetch(url, { ...options, headers });
|
|
|
|
if (response.status === 401) {
|
|
logout();
|
|
throw new Error('未授权');
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
// 导出用户数据为CSV
|
|
function exportUserData() {
|
|
if (!allUsers || allUsers.length === 0) {
|
|
alert(lang[selectedLang].noUserDataToExport);
|
|
return;
|
|
}
|
|
|
|
const csvContent = "data:text/csv;charset=utf-8,"
|
|
+ "用户名,邮箱,角色\n"
|
|
+ allUsers.map(user => `${user.username},${user.email},${user.role}`).join("\n");
|
|
|
|
const encodedUri = encodeURI(csvContent);
|
|
const link = document.createElement("a");
|
|
link.setAttribute("href", encodedUri);
|
|
link.setAttribute("download", "user_data.csv");
|
|
document.body.appendChild(link);
|
|
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
}
|
|
|
|
// 添加数据验证函数
|
|
function validateUserData(userData) {
|
|
// 用户名验证:只验证必填
|
|
if (!userData.username || userData.username.trim() === '') {
|
|
throw new Error('用户名不能为空');
|
|
}
|
|
|
|
// 邮箱验证:必填且唯一,格式正确
|
|
if (!userData.email || !userData.email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
|
|
throw new Error('请输入有效的邮箱地址');
|
|
}
|
|
|
|
// 密码验证
|
|
if (!userData.password || userData.password.length < 6) {
|
|
throw new Error('密码至少需要6个字符');
|
|
}
|
|
|
|
// 手机号验证:如果提供则必须格式正确
|
|
if (userData.phone_number && userData.phone_number !== '') {
|
|
if (!userData.phone_number.match(/^\d{11}$/)) {
|
|
throw new Error('请输入有效的手机号码');
|
|
}
|
|
}
|
|
}
|