962 lines
33 KiB
HTML
962 lines
33 KiB
HTML
<!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 select {
|
||
padding: 8px 12px;
|
||
border: 1px solid #d1d5db;
|
||
border-radius: 6px;
|
||
background: #fff;
|
||
}
|
||
|
||
.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: 6% 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;
|
||
}
|
||
|
||
.keys-box {
|
||
max-height: 140px;
|
||
overflow: auto;
|
||
border: 1px solid #d1d5db;
|
||
border-radius: 6px;
|
||
padding: 8px 10px;
|
||
background: #fff;
|
||
}
|
||
|
||
.key-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 4px 0;
|
||
font-size: 14px;
|
||
color: #111827;
|
||
user-select: none;
|
||
}
|
||
|
||
.key-item input[type="checkbox"] {
|
||
width: auto;
|
||
padding: 0;
|
||
margin: 0;
|
||
}
|
||
|
||
.key-edit-row {
|
||
display: flex;
|
||
gap: 10px;
|
||
align-items: center;
|
||
}
|
||
|
||
.selected-keys {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.key-tag {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 6px 10px;
|
||
border-radius: 999px;
|
||
background: #eef2ff;
|
||
color: #1f2937;
|
||
border: 1px solid #c7d2fe;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.key-tag button {
|
||
border: none;
|
||
background: transparent;
|
||
cursor: pointer;
|
||
color: #4b5563;
|
||
font-size: 14px;
|
||
line-height: 1;
|
||
}
|
||
|
||
.key-tag.locked {
|
||
background: #f3f4f6;
|
||
border: 1px solid #e5e7eb;
|
||
color: #374151;
|
||
}
|
||
</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">
|
||
<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="搜索用户名...">
|
||
<select id="keyFilter"></select>
|
||
<button id="searchBtn">搜索</button>
|
||
<button id="resetBtn">重置</button>
|
||
<button id="clearKeyBtn">清空Key</button>
|
||
</div>
|
||
|
||
<div class="table-container">
|
||
<table id="usersTable">
|
||
<thead>
|
||
<tr>
|
||
<th>用户ID</th>
|
||
<th>用户名</th>
|
||
<th>Key</th>
|
||
<th>Manage Key</th>
|
||
<th>权限</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="usersTableBody"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 添加/编辑用户模态框 -->
|
||
<div id="userModal" class="modal">
|
||
<div class="modal-content">
|
||
<span class="close">×</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" id="permissionGroup">
|
||
<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>Key(从已有 Key 中选择)</label>
|
||
<div class="key-edit-row">
|
||
<select id="userKeySelect"></select>
|
||
<button type="button" id="addUserKeyBtn" class="btn btn-primary">添加</button>
|
||
<button type="button" id="clearUserKeyBtn" class="btn">清空</button>
|
||
</div>
|
||
<div id="userKeysSelected" class="selected-keys"></div>
|
||
<div id="userKeysReadonlyGroup" style="display:none; margin-top: 10px;">
|
||
<div style="font-weight: 600; color: #374151; font-size: 13px; margin-bottom: 6px;">导师Key(不可修改)</div>
|
||
<div id="userKeysReadonly" class="selected-keys"></div>
|
||
</div>
|
||
</div>
|
||
<div class="form-group" id="manageKeyGroup">
|
||
<label>Manage Key(从已有 Key 中选择)</label>
|
||
<div class="key-edit-row">
|
||
<select id="userManageKeySelect"></select>
|
||
<button type="button" id="addUserManageKeyBtn" class="btn btn-primary">添加</button>
|
||
<button type="button" id="clearUserManageKeyBtn" class="btn">清空</button>
|
||
</div>
|
||
<div id="userManageKeysSelected" class="selected-keys"></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">×</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>
|
||
const IS_ADMIN = {{ is_admin|yesno:"true,false" }};
|
||
const IS_TUTOR = {{ is_tutor|yesno:"true,false" }};
|
||
const MY_MANAGE_KEYS_RAW = JSON.parse('{{ manage_keys_json|default:"[]"|escapejs }}');
|
||
const MY_KEYS_RAW = JSON.parse('{{ my_keys_json|default:"[]"|escapejs }}');
|
||
let KEY_OPTIONS_CACHE = null;
|
||
let MODAL_SELECTED_KEYS = [];
|
||
let MODAL_SELECTED_MANAGE_KEYS = [];
|
||
|
||
// 获取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 = '', key = '') {
|
||
try {
|
||
const params = new URLSearchParams();
|
||
if ((searchTerm || '').trim()) params.set('search', (searchTerm || '').trim());
|
||
if ((key || '').trim()) params.set('key', (key || '').trim());
|
||
const url = params.toString() ? `/elastic/users/?${params.toString()}` : '/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 ? '管理员' : '普通用户';
|
||
const keys = Array.isArray(user.key) ? user.key : (user.key ? [user.key] : []);
|
||
const keysText = keys.map(k => String(k || '').trim()).filter(Boolean).join('、') || '-';
|
||
const manageKeys = Array.isArray(user.manage_key) ? user.manage_key : (user.manage_key ? [user.manage_key] : []);
|
||
const manageKeysText = manageKeys.map(k => String(k || '').trim()).filter(Boolean).join('、') || '-';
|
||
|
||
row.innerHTML = `
|
||
<td>${user.user_id}</td>
|
||
<td>${user.username}</td>
|
||
<td>${keysText}</td>
|
||
<td>${manageKeysText}</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);
|
||
}
|
||
}
|
||
|
||
async function initKeyFilter() {
|
||
const select = document.getElementById('keyFilter');
|
||
if (!select) return;
|
||
select.innerHTML = '<option value="">全部Key</option>';
|
||
try {
|
||
const keys = await fetchKeyOptions();
|
||
keys.forEach(k => {
|
||
const opt = document.createElement('option');
|
||
opt.value = String(k || '').trim();
|
||
opt.textContent = String(k || '').trim();
|
||
if (opt.value) select.appendChild(opt);
|
||
});
|
||
} catch (e) {
|
||
}
|
||
select.addEventListener('change', () => {
|
||
const searchTerm = document.getElementById('searchInput').value;
|
||
loadUsers(searchTerm, select.value);
|
||
});
|
||
}
|
||
|
||
function normalizeStr(v) {
|
||
return String(v || '').trim();
|
||
}
|
||
|
||
const MY_MANAGE_KEYS = (Array.isArray(MY_MANAGE_KEYS_RAW) ? MY_MANAGE_KEYS_RAW : [])
|
||
.map(normalizeStr)
|
||
.filter(Boolean);
|
||
const MY_MANAGE_KEYS_SET = new Set(MY_MANAGE_KEYS);
|
||
const MY_KEYS = (Array.isArray(MY_KEYS_RAW) ? MY_KEYS_RAW : [])
|
||
.map(normalizeStr)
|
||
.filter(Boolean);
|
||
const MY_KEYS_SET = new Set(MY_KEYS);
|
||
|
||
async function fetchKeyOptions() {
|
||
if (Array.isArray(KEY_OPTIONS_CACHE)) return KEY_OPTIONS_CACHE;
|
||
try {
|
||
const resp = await fetch('/elastic/keys-for-filter/', { credentials: 'same-origin' });
|
||
const data = await resp.json();
|
||
if (data.status !== 'success') return [];
|
||
const keys = (data.data || []).map(normalizeStr).filter(Boolean);
|
||
KEY_OPTIONS_CACHE = keys;
|
||
return keys;
|
||
} catch (e) {
|
||
return [];
|
||
}
|
||
}
|
||
|
||
function setSelectOptions(selectId, options) {
|
||
const select = document.getElementById(selectId);
|
||
if (!select) return;
|
||
select.innerHTML = '<option value="">请选择Key</option>';
|
||
(options || []).forEach(k => {
|
||
const s = normalizeStr(k);
|
||
if (!s) return;
|
||
const opt = document.createElement('option');
|
||
opt.value = s;
|
||
opt.textContent = s;
|
||
select.appendChild(opt);
|
||
});
|
||
}
|
||
|
||
function setSelectOptionsMixed(selectId, enabledOptions, disabledOptions) {
|
||
const select = document.getElementById(selectId);
|
||
if (!select) return;
|
||
select.innerHTML = '<option value="">请选择Key</option>';
|
||
(enabledOptions || []).forEach(k => {
|
||
const s = normalizeStr(k);
|
||
if (!s) return;
|
||
const opt = document.createElement('option');
|
||
opt.value = s;
|
||
opt.textContent = s;
|
||
select.appendChild(opt);
|
||
});
|
||
(disabledOptions || []).forEach(k => {
|
||
const s = normalizeStr(k);
|
||
if (!s) return;
|
||
const opt = document.createElement('option');
|
||
opt.value = s;
|
||
opt.textContent = s;
|
||
opt.disabled = true;
|
||
select.appendChild(opt);
|
||
});
|
||
}
|
||
|
||
function renderSelectedTags(containerId, selectedArr) {
|
||
const container = document.getElementById(containerId);
|
||
if (!container) return;
|
||
container.innerHTML = '';
|
||
(selectedArr || []).forEach(k => {
|
||
const tag = document.createElement('span');
|
||
tag.className = 'key-tag';
|
||
const text = document.createElement('span');
|
||
text.textContent = k;
|
||
const btn = document.createElement('button');
|
||
btn.type = 'button';
|
||
btn.textContent = '×';
|
||
btn.addEventListener('click', () => {
|
||
const idx = selectedArr.indexOf(k);
|
||
if (idx >= 0) selectedArr.splice(idx, 1);
|
||
renderSelectedTags(containerId, selectedArr);
|
||
});
|
||
tag.appendChild(text);
|
||
tag.appendChild(btn);
|
||
container.appendChild(tag);
|
||
});
|
||
}
|
||
|
||
function renderReadonlyTags(containerId, keysArr) {
|
||
const container = document.getElementById(containerId);
|
||
if (!container) return;
|
||
container.innerHTML = '';
|
||
(keysArr || []).forEach(k => {
|
||
const tag = document.createElement('span');
|
||
tag.className = 'key-tag locked';
|
||
const text = document.createElement('span');
|
||
text.textContent = k;
|
||
tag.appendChild(text);
|
||
container.appendChild(tag);
|
||
});
|
||
}
|
||
|
||
function setReadonlyKeysVisible(visible) {
|
||
const group = document.getElementById('userKeysReadonlyGroup');
|
||
if (group) group.style.display = visible ? '' : 'none';
|
||
}
|
||
|
||
function setKeyEditorDisabled(prefix, disabled) {
|
||
const select = document.getElementById(prefix + 'Select');
|
||
const addBtn = document.getElementById('add' + prefix.charAt(0).toUpperCase() + prefix.slice(1) + 'Btn');
|
||
const clearBtn = document.getElementById('clear' + prefix.charAt(0).toUpperCase() + prefix.slice(1) + 'Btn');
|
||
if (select) select.disabled = !!disabled;
|
||
if (addBtn) addBtn.disabled = !!disabled;
|
||
if (clearBtn) clearBtn.disabled = !!disabled;
|
||
}
|
||
|
||
function addFromSelect(selectId, selectedArr, renderId) {
|
||
const select = document.getElementById(selectId);
|
||
if (!select) return;
|
||
const v = normalizeStr(select.value);
|
||
if (!v) return;
|
||
if (!selectedArr.includes(v)) selectedArr.push(v);
|
||
renderSelectedTags(renderId, selectedArr);
|
||
}
|
||
|
||
function clearSelected(selectedArr, renderId) {
|
||
selectedArr.length = 0;
|
||
renderSelectedTags(renderId, selectedArr);
|
||
}
|
||
|
||
// 打开添加用户模态框
|
||
async function openAddModal() {
|
||
document.getElementById('modalTitle').textContent = '添加用户';
|
||
document.getElementById('userForm').reset();
|
||
document.getElementById('userId').value = '';
|
||
document.getElementById('username').disabled = false;
|
||
document.getElementById('permission').disabled = false;
|
||
document.getElementById('permissionGroup').style.display = '';
|
||
document.getElementById('manageKeyGroup').style.display = '';
|
||
const options = await fetchKeyOptions();
|
||
if ((!IS_ADMIN) && IS_TUTOR) {
|
||
const enabled = (options || []).map(normalizeStr).filter(k => k && !MY_KEYS_SET.has(k));
|
||
setSelectOptionsMixed('userKeySelect', enabled, MY_KEYS);
|
||
} else {
|
||
setSelectOptions('userKeySelect', options);
|
||
}
|
||
setSelectOptions('userManageKeySelect', options);
|
||
MODAL_SELECTED_KEYS = [];
|
||
MODAL_SELECTED_MANAGE_KEYS = [];
|
||
renderSelectedTags('userKeysSelected', MODAL_SELECTED_KEYS);
|
||
renderSelectedTags('userManageKeysSelected', MODAL_SELECTED_MANAGE_KEYS);
|
||
setReadonlyKeysVisible(false);
|
||
renderReadonlyTags('userKeysReadonly', []);
|
||
setKeyEditorDisabled('userKey', false);
|
||
setKeyEditorDisabled('userManageKey', false);
|
||
document.getElementById('password').required = true;
|
||
document.getElementById('confirmPassword').required = true;
|
||
document.getElementById('userModal').style.display = 'block';
|
||
}
|
||
|
||
// 打开编辑用户模态框
|
||
async 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;
|
||
const options = await fetchKeyOptions();
|
||
setSelectOptions('userManageKeySelect', options);
|
||
const allUserKeys = (Array.isArray(user.key) ? user.key : (user.key ? [user.key] : [])).map(normalizeStr).filter(Boolean);
|
||
const lockedKeys = allUserKeys.filter(k => MY_KEYS_SET.has(k));
|
||
if ((!IS_ADMIN) && IS_TUTOR) {
|
||
const enabled = (options || []).map(normalizeStr).filter(k => k && !MY_KEYS_SET.has(k));
|
||
setSelectOptionsMixed('userKeySelect', enabled, MY_KEYS);
|
||
} else {
|
||
setSelectOptions('userKeySelect', options);
|
||
}
|
||
MODAL_SELECTED_KEYS = IS_ADMIN ? allUserKeys : allUserKeys.filter(k => !MY_KEYS_SET.has(k));
|
||
MODAL_SELECTED_MANAGE_KEYS = (Array.isArray(user.manage_key) ? user.manage_key : (user.manage_key ? [user.manage_key] : [])).map(normalizeStr).filter(Boolean);
|
||
MODAL_SELECTED_KEYS = Array.from(new Set(MODAL_SELECTED_KEYS));
|
||
MODAL_SELECTED_MANAGE_KEYS = Array.from(new Set(MODAL_SELECTED_MANAGE_KEYS));
|
||
renderSelectedTags('userKeysSelected', MODAL_SELECTED_KEYS);
|
||
renderSelectedTags('userManageKeysSelected', MODAL_SELECTED_MANAGE_KEYS);
|
||
setReadonlyKeysVisible((!IS_ADMIN) && IS_TUTOR && lockedKeys.length > 0);
|
||
renderReadonlyTags('userKeysReadonly', ((!IS_ADMIN) && IS_TUTOR) ? Array.from(new Set(lockedKeys)) : []);
|
||
|
||
if (IS_ADMIN) {
|
||
document.getElementById('username').disabled = false;
|
||
document.getElementById('permission').disabled = false;
|
||
document.getElementById('permissionGroup').style.display = '';
|
||
document.getElementById('manageKeyGroup').style.display = '';
|
||
setKeyEditorDisabled('userKey', false);
|
||
setKeyEditorDisabled('userManageKey', false);
|
||
} else {
|
||
document.getElementById('username').disabled = true;
|
||
document.getElementById('permission').disabled = true;
|
||
document.getElementById('permissionGroup').style.display = 'none';
|
||
document.getElementById('manageKeyGroup').style.display = 'none';
|
||
setKeyEditorDisabled('userKey', !IS_TUTOR);
|
||
setKeyEditorDisabled('userManageKey', true);
|
||
}
|
||
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 = {};
|
||
if (IS_ADMIN) {
|
||
data.username = username;
|
||
data.permission = parseInt(permission);
|
||
data.key = MODAL_SELECTED_KEYS;
|
||
data.manage_key = MODAL_SELECTED_MANAGE_KEYS;
|
||
} else {
|
||
data.key = MODAL_SELECTED_KEYS;
|
||
}
|
||
|
||
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';
|
||
const searchTerm = (document.getElementById('searchInput') || {}).value || '';
|
||
const key = (document.getElementById('keyFilter') || {}).value || '';
|
||
loadUsers(searchTerm, key);
|
||
} 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;
|
||
const key = (document.getElementById('keyFilter') || {}).value || '';
|
||
loadUsers(searchTerm, key);
|
||
});
|
||
}
|
||
|
||
const resetBtn = document.getElementById('resetBtn');
|
||
if (resetBtn) {
|
||
resetBtn.addEventListener('click', function() {
|
||
document.getElementById('searchInput').value = '';
|
||
const select = document.getElementById('keyFilter');
|
||
if (select) select.value = '';
|
||
loadUsers('', '');
|
||
});
|
||
}
|
||
|
||
const clearKeyBtn = document.getElementById('clearKeyBtn');
|
||
if (clearKeyBtn) {
|
||
clearKeyBtn.addEventListener('click', function() {
|
||
const select = document.getElementById('keyFilter');
|
||
if (select) select.value = '';
|
||
const searchTerm = document.getElementById('searchInput').value;
|
||
loadUsers(searchTerm, '');
|
||
});
|
||
}
|
||
|
||
const addUserKeyBtn = document.getElementById('addUserKeyBtn');
|
||
if (addUserKeyBtn) {
|
||
addUserKeyBtn.addEventListener('click', function() {
|
||
addFromSelect('userKeySelect', MODAL_SELECTED_KEYS, 'userKeysSelected');
|
||
});
|
||
}
|
||
const clearUserKeyBtn = document.getElementById('clearUserKeyBtn');
|
||
if (clearUserKeyBtn) {
|
||
clearUserKeyBtn.addEventListener('click', function() {
|
||
clearSelected(MODAL_SELECTED_KEYS, 'userKeysSelected');
|
||
});
|
||
}
|
||
const addUserManageKeyBtn = document.getElementById('addUserManageKeyBtn');
|
||
if (addUserManageKeyBtn) {
|
||
addUserManageKeyBtn.addEventListener('click', function() {
|
||
addFromSelect('userManageKeySelect', MODAL_SELECTED_MANAGE_KEYS, 'userManageKeysSelected');
|
||
});
|
||
}
|
||
const clearUserManageKeyBtn = document.getElementById('clearUserManageKeyBtn');
|
||
if (clearUserManageKeyBtn) {
|
||
clearUserManageKeyBtn.addEventListener('click', function() {
|
||
clearSelected(MODAL_SELECTED_MANAGE_KEYS, 'userManageKeysSelected');
|
||
});
|
||
}
|
||
|
||
// 点击模态框外部关闭模态框
|
||
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() {
|
||
initKeyFilter();
|
||
const tbody = document.getElementById('usersTableBody');
|
||
if (tbody) {
|
||
const select = document.getElementById('keyFilter');
|
||
loadUsers('', select ? select.value : '');
|
||
}
|
||
});
|
||
|
||
// 为表格中的编辑和删除按钮添加事件监听器
|
||
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>
|