Files
Achievement_Inputing/elastic/templates/elastic/users.html

657 lines
21 KiB
HTML
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.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>用户管理</title>
<style>
body {margin: 0;font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;background: #fafafa;}
/* 导航栏样式 */
.sidebar {position: fixed;top: 0;left: 0;width: 180px;height: 100vh;background: #1e1e2e;color: white;padding: 20px;box-shadow: 2px 0 5px rgba(0,0,0,0.1);z-index: 1000;display: flex;
flex-direction: column;align-items: center;}
.user-id {text-align: center;margin-bottom: 0px;}
.sidebar h3 {margin-top: 0;font-size: 18px;color: #add8e6;text-align: center; margin-bottom: 20px;}
.navigation-links {width: 100%;margin-top: 60px;}
.sidebar a,
.sidebar button {display: block;color: #8be9fd;text-decoration: none;margin: 10px 0;font-size: 16px;padding: 15px;border-radius: 4px;background: transparent;
border: none;cursor: pointer; width: calc(100% - 40px);text-align: left;transition: all 0.2s ease;}
.sidebar a:hover,
.sidebar button:hover {color: #ff79c6;background-color: rgba(139, 233, 253, 0.2);}
/* 主内容区 */
.main-content {
margin-left: 200px;
padding: 20px;
color: #333;
}
.card {
background: #fff;
border-radius: 14px;
box-shadow: 0 10px 24px rgba(31,35,40,0.08);
padding: 20px;
margin-bottom: 20px;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.btn {
padding: 8px 12px;
border: none;
border-radius: 8px;
cursor: pointer;
margin: 0 4px;
}
.btn-primary {
background: #4f46e5;
color: #fff;
}
.btn-danger {
background: #ef4444;
color: #fff;
}
.btn-success {
background: #22c55e;
color: #fff;
}
.table-container {
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}
th, td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #e5e7eb;
}
th {
background-color: #f9fafb;
font-weight: bold;
color: #374151;
}
tr:hover {
background-color: #f3f4f6;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.form-group input, .form-group select {
width: 100%;
padding: 8px 12px;
border: 1px solid #d1d5db;
border-radius: 6px;
box-sizing: border-box;
}
.form-row {
display: flex;
gap: 15px;
margin-bottom: 15px;
}
.form-row .form-group {
flex: 1;
margin-bottom: 0;
}
.action-buttons {
display: flex;
gap: 8px;
}
.search-container {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.search-container input {
flex: 1;
padding: 8px 12px;
border: 1px solid #d1d5db;
border-radius: 6px;
}
.search-container button {
padding: 8px 15px;
background: #4f46e5;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
}
.modal {
display: none;
position: fixed;
z-index: 2000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
}
.modal-content {
background-color: white;
margin: 10% auto;
padding: 20px;
border-radius: 8px;
width: 80%;
max-width: 500px;
}
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
.close:hover {
color: black;
}
.notification {
padding: 10px;
margin-bottom: 15px;
border-radius: 4px;
display: none;
}
.notification.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.notification.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.nav-error {
color: #ef4444;
font-size: 12px;
margin-top: 5px;
text-align: center;
}
</style>
</head>
<body>
<!-- 左侧固定栏目 -->
<div class="sidebar">
<div class="user-id">
<h3>你好,{{ username|default:"访客" }}</h3>
</div>
<div class="navigation-links">
<a href="{% url 'main:home' %}" onclick="return handleNavClick(this, '/');">返回主页</a>
<a id="logoutBtn">退出登录</a>
<div id="logoutMsg"></div>
{% csrf_token %}
</div>
</div>
<div class="main-content">
{% if is_student %}
<div class="card">
<div class="header"><h2>修改密码</h2></div>
<form id="selfPwdForm">
<input type="hidden" id="selfUserId" name="user_id" value="{{ user_id }}">
<div class="form-group">
<label for="password">新密码</label>
<input type="password" id="password" name="password" required>
</div>
<div class="form-group">
<label for="confirmPassword">确认密码</label>
<input type="password" id="confirmPassword" name="confirmPassword" required>
</div>
<button type="submit" class="btn btn-primary">保存</button>
</form>
</div>
{% else %}
{% if is_tutor %}
<div class="card">
<div class="header"><h2>修改本人密码</h2></div>
<form id="selfPwdForm">
<input type="hidden" id="selfUserId" name="user_id" value="{{ user_id }}">
<div class="form-group">
<label for="password">新密码</label>
<input type="password" id="password" name="password" required>
</div>
<div class="form-group">
<label for="confirmPassword">确认密码</label>
<input type="password" id="confirmPassword" name="confirmPassword" required>
</div>
<button type="submit" class="btn btn-primary">保存</button>
</form>
</div>
{% endif %}
<div class="card">
<div class="header">
<h2>用户管理</h2>
{% if is_admin %}<button id="addUserBtn" class="btn btn-primary">添加用户</button>{% endif %}
</div>
<div class="notification success" id="successNotification">操作成功!</div>
<div class="notification error" id="errorNotification">操作失败!</div>
<div class="search-container">
<input type="text" id="searchInput" placeholder="搜索用户名...">
<button id="searchBtn">搜索</button>
<button id="resetBtn">重置</button>
</div>
<div class="table-container">
<table id="usersTable">
<thead>
<tr>
<th>用户ID</th>
<th>用户名</th>
<th>权限</th>
<th>操作</th>
</tr>
</thead>
<tbody id="usersTableBody"></tbody>
</table>
</div>
</div>
{% endif %}
</div>
<!-- 添加/编辑用户模态框 -->
<div id="userModal" class="modal">
<div class="modal-content">
<span class="close">&times;</span>
<h2 id="modalTitle">添加用户</h2>
<form id="userForm">
<input type="hidden" id="userId" name="user_id">
<div class="form-row">
<div class="form-group">
<label for="username">用户名</label>
<input type="text" id="username" name="username" required>
</div>
<div class="form-group">
<label for="permission">权限</label>
<select id="permission" name="permission" required>
<option value="0">管理员</option>
<option value="1">普通用户</option>
</select>
</div>
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" id="password" name="password" required>
</div>
<div class="form-group">
<label for="confirmPassword">确认密码</label>
<input type="password" id="confirmPassword" name="confirmPassword" required>
</div>
<button type="submit" class="btn btn-primary">保存</button>
</form>
</div>
</div>
<!-- 确认删除模态框 -->
<div id="deleteModal" class="modal">
<div class="modal-content">
<span class="close">&times;</span>
<h2>确认删除</h2>
<p>确定要删除用户 <strong id="deleteUserName"></strong> 吗?此操作不可撤销。</p>
<input type="hidden" id="deleteUserId">
<button id="confirmDeleteBtn" class="btn btn-danger">确认删除</button>
<button class="btn">取消</button>
</div>
</div>
<script>
// 获取CSRF令牌的函数
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
// 导航点击处理函数提供备用URL
function handleNavClick(element, fallbackUrl) {
// 尝试使用Django模板生成的URL如果失败则使用备用URL
try {
// 如果模板渲染正常直接返回true让默认行为处理
return true;
} catch (e) {
// 如果模板渲染有问题使用备用URL
window.location.href = fallbackUrl;
return false;
}
}
// 显示通知
function showNotification(message, isSuccess = true) {
const notification = isSuccess ?
document.getElementById('successNotification') :
document.getElementById('errorNotification');
notification.textContent = message;
notification.style.display = 'block';
setTimeout(() => {
notification.style.display = 'none';
}, 3000);
}
// 获取所有用户
async function loadUsers(searchTerm = '') {
try {
const url = searchTerm ?
`/elastic/users/?search=${encodeURIComponent(searchTerm)}` :
'/elastic/users/';
const response = await fetch(url);
const result = await response.json();
if (result.status === 'success') {
const tbody = document.getElementById('usersTableBody');
tbody.innerHTML = '';
// 处理不同格式的API响应
const users = result.data || result.users || [];
users.forEach(user => {
const row = document.createElement('tr');
// 根据权限值显示权限名称
const permissionText = Number(user.permission) === 0 ? '管理员' : '普通用户';
row.innerHTML = `
<td>${user.user_id}</td>
<td>${user.username}</td>
<td>${permissionText}</td>
<td class="action-buttons">
<button class="btn btn-success edit-btn" data-user='${JSON.stringify(user)}'>编辑</button>
<button class="btn btn-danger delete-btn" data-username="${user.username}" data-userid="${user.user_id}">删除</button>
</td>
`;
tbody.appendChild(row);
});
} else {
showNotification('获取用户列表失败', false);
}
} catch (error) {
console.error('加载用户列表失败:', error);
showNotification('获取用户列表失败', false);
}
}
// 打开添加用户模态框
function openAddModal() {
document.getElementById('modalTitle').textContent = '添加用户';
document.getElementById('userForm').reset();
document.getElementById('userId').value = '';
document.getElementById('password').required = true;
document.getElementById('confirmPassword').required = true;
document.getElementById('userModal').style.display = 'block';
}
// 打开编辑用户模态框
function openEditModal(user) {
document.getElementById('modalTitle').textContent = '编辑用户';
document.getElementById('username').value = user.username;
document.getElementById('userId').value = user.user_id;
document.getElementById('permission').value = user.permission;
document.getElementById('password').required = false;
document.getElementById('confirmPassword').required = false;
document.getElementById('userModal').style.display = 'block';
}
// 打开删除确认模态框
function openDeleteModal(username, userId) {
document.getElementById('deleteUserName').textContent = username;
document.getElementById('deleteUserId').value = userId;
document.getElementById('deleteModal').style.display = 'block';
}
// 保存用户(添加或编辑)
async function saveUser(event) {
event.preventDefault();
const formData = new FormData(event.target);
const userId = formData.get('user_id');
const username = formData.get('username');
const permission = formData.get('permission');
const password = formData.get('password');
const confirmPassword = formData.get('confirmPassword');
// 验证密码
if (password !== confirmPassword) {
showNotification('密码和确认密码不匹配', false);
return;
}
// 验证密码长度(如果提供了密码)
if (password && password.length < 6) {
showNotification('密码长度至少为6位', false);
return;
}
const data = {
username: username,
permission: parseInt(permission)
};
if (password) {
data.password = password;
}
try {
const csrftoken = getCookie('csrftoken');
let response;
if (userId) {
response = await fetch(`/elastic/users/${userId}/update/`, {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken
},
body: JSON.stringify(data)
});
} else {
response = await fetch('/elastic/users/add/', {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken
},
body: JSON.stringify(data)
});
}
const result = await response.json();
if (result.status === 'success') {
showNotification(userId ? '用户更新成功' : '用户添加成功');
document.getElementById('userModal').style.display = 'none';
loadUsers();
} else {
showNotification(result.message || '操作失败', false);
}
} catch (error) {
console.error('保存用户失败:', error);
showNotification('保存用户失败', false);
}
}
// 删除用户
async function deleteUser() {
const userId = document.getElementById('deleteUserId').value;
try {
const csrftoken = getCookie('csrftoken');
const response = await fetch(`/elastic/users/${userId}/delete/`, {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken
}
});
const result = await response.json();
if (result.status === 'success') {
showNotification('用户删除成功');
document.getElementById('deleteModal').style.display = 'none';
loadUsers();
} else {
showNotification(result.message || '删除失败', false);
}
} catch (error) {
console.error('删除用户失败:', error);
showNotification('删除用户失败', false);
}
}
// 事件监听器
const addBtn = document.getElementById('addUserBtn');
if (addBtn) {
addBtn.addEventListener('click', openAddModal);
}
document.getElementById('userForm').addEventListener('submit', saveUser);
document.getElementById('confirmDeleteBtn').addEventListener('click', deleteUser);
document.querySelectorAll('.close').forEach(closeBtn => {
closeBtn.addEventListener('click', function() {
this.parentElement.parentElement.style.display = 'none';
});
});
const searchBtn = document.getElementById('searchBtn');
if (searchBtn) {
searchBtn.addEventListener('click', function() {
const searchTerm = document.getElementById('searchInput').value;
loadUsers(searchTerm);
});
}
const resetBtn = document.getElementById('resetBtn');
if (resetBtn) {
resetBtn.addEventListener('click', function() {
document.getElementById('searchInput').value = '';
loadUsers();
});
}
// 点击模态框外部关闭模态框
window.addEventListener('click', function(event) {
const modals = document.querySelectorAll('.modal');
modals.forEach(modal => {
if (event.target === modal) {
modal.style.display = 'none';
}
});
});
// 登出功能
document.getElementById('logoutBtn').addEventListener('click', async () => {
const msg = document.getElementById('logoutMsg');
msg.textContent = '';
const csrftoken = getCookie('csrftoken');
try {
const resp = await fetch('/accounts/logout/', {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken || ''
},
body: JSON.stringify({})
});
const data = await resp.json();
if (!resp.ok || !data.ok) {
throw new Error('登出失败');
}
document.cookie = 'sessionid=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';
document.cookie = 'csrftoken=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';
window.location.href = data.redirect_url;
} catch (e) {
msg.textContent = e.message || '发生错误';
}
});
// 页面加载时获取用户列表
document.addEventListener('DOMContentLoaded', function() {
const selfForm = document.getElementById('selfPwdForm');
if (selfForm) {
selfForm.addEventListener('submit', async (e) => {
e.preventDefault();
const uid = document.getElementById('selfUserId').value;
const pwd = document.getElementById('password').value;
const cpwd = document.getElementById('confirmPassword').value;
if (pwd !== cpwd) { showNotification('密码和确认密码不匹配', false); return; }
if ((pwd || '').length < 6) { showNotification('密码长度至少为6位', false); return; }
try {
const csrftoken = getCookie('csrftoken');
const resp = await fetch(`/elastic/users/${uid}/update/`, {
method: 'POST', credentials: 'same-origin',
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrftoken || '' },
body: JSON.stringify({ password: pwd })
});
const result = await resp.json();
if (resp.ok && result.status === 'success') { showNotification('修改成功'); }
else { showNotification(result.message || '操作失败', false); }
} catch (error) {
showNotification('保存失败', false);
}
});
}
const tbody = document.getElementById('usersTableBody');
if (tbody) {
loadUsers();
}
});
// 为表格中的编辑和删除按钮添加事件监听器
document.addEventListener('click', function(e) {
if (e.target.classList.contains('edit-btn')) {
const user = JSON.parse(e.target.getAttribute('data-user'));
openEditModal(user);
}
if (e.target.classList.contains('delete-btn')) {
const username = e.target.getAttribute('data-username');
const userId = e.target.getAttribute('data-userid');
openDeleteModal(username, userId);
}
});
</script>
</body>
</html>