Files
zydi-user/manage.js
T
2025-01-12 05:13:22 +00:00

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)">&lt;</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)">&gt;</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('请输入有效的手机号码');
}
}
}