需测试[0.2.7.3][ci]
This commit is contained in:
@@ -29,6 +29,13 @@
|
|||||||
.image-item .info { padding: 10px; font-size: 12px; color: #888; text-align: center; }
|
.image-item .info { padding: 10px; font-size: 12px; color: #888; text-align: center; }
|
||||||
|
|
||||||
.no-data { text-align: center; color: #999; padding: 40px; }
|
.no-data { text-align: center; color: #999; padding: 40px; }
|
||||||
|
.form-group { margin-bottom: 14px; }
|
||||||
|
.form-group label { display:block; margin-bottom: 6px; font-weight: 600; color: #333; }
|
||||||
|
.form-group input { width: 100%; padding: 10px 12px; border: 1px solid #d1d5db; border-radius: 8px; box-sizing: border-box; }
|
||||||
|
.btn { padding: 10px 14px; border: none; border-radius: 10px; cursor: pointer; background: #4f46e5; color: #fff; }
|
||||||
|
.msg { margin-top: 10px; font-size: 13px; }
|
||||||
|
.msg.error { color: #b91c1c; }
|
||||||
|
.msg.success { color: #166534; }
|
||||||
|
|
||||||
/* 图片放大模态框 */
|
/* 图片放大模态框 */
|
||||||
.image-modal { position: fixed; inset: 0; background: rgba(0,0,0,0.8); display: none; align-items: center; justify-content: center; z-index: 2000; }
|
.image-modal { position: fixed; inset: 0; background: rgba(0,0,0,0.8); display: none; align-items: center; justify-content: center; z-index: 2000; }
|
||||||
@@ -65,6 +72,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if permission_name != "管理员" and not profile_user.manage_key %}
|
||||||
|
<div class="profile-card">
|
||||||
|
<div class="profile-header">
|
||||||
|
<div class="profile-info">
|
||||||
|
<h2>修改密码</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form id="pwdForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="newPassword">新密码</label>
|
||||||
|
<input type="password" id="newPassword" autocomplete="new-password" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="confirmPassword">确认密码</label>
|
||||||
|
<input type="password" id="confirmPassword" autocomplete="new-password" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn">保存</button>
|
||||||
|
<div id="pwdMsg" class="msg"></div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="section-title">我的提交</div>
|
<div class="section-title">我的提交</div>
|
||||||
{% if achievements %}
|
{% if achievements %}
|
||||||
<div class="image-grid">
|
<div class="image-grid">
|
||||||
@@ -128,6 +157,54 @@
|
|||||||
const modal = document.getElementById('imageModal');
|
const modal = document.getElementById('imageModal');
|
||||||
if (event.target == modal) closeModal();
|
if (event.target == modal) closeModal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pwdForm = document.getElementById('pwdForm');
|
||||||
|
if (pwdForm) {
|
||||||
|
pwdForm.addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const msg = document.getElementById('pwdMsg');
|
||||||
|
msg.textContent = '';
|
||||||
|
msg.className = 'msg';
|
||||||
|
|
||||||
|
const pwd = (document.getElementById('newPassword').value || '').trim();
|
||||||
|
const cpwd = (document.getElementById('confirmPassword').value || '').trim();
|
||||||
|
if (pwd !== cpwd) {
|
||||||
|
msg.textContent = '密码和确认密码不匹配';
|
||||||
|
msg.className = 'msg error';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pwd.length < 6) {
|
||||||
|
msg.textContent = '密码长度至少为6位';
|
||||||
|
msg.className = 'msg error';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const csrftoken = getCookie('csrftoken');
|
||||||
|
const resp = await fetch(`/elastic/users/{{ profile_user.user_id }}/update/`, {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': csrftoken || ''
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ password: pwd })
|
||||||
|
});
|
||||||
|
const data = await resp.json();
|
||||||
|
if (resp.ok && data.status === 'success') {
|
||||||
|
msg.textContent = '修改成功';
|
||||||
|
msg.className = 'msg success';
|
||||||
|
document.getElementById('newPassword').value = '';
|
||||||
|
document.getElementById('confirmPassword').value = '';
|
||||||
|
} else {
|
||||||
|
msg.textContent = data.message || '操作失败';
|
||||||
|
msg.className = 'msg error';
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
msg.textContent = '操作失败';
|
||||||
|
msg.className = 'msg error';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -134,6 +134,13 @@
|
|||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search-container select {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
.search-container button {
|
.search-container button {
|
||||||
padding: 8px 15px;
|
padding: 8px 15px;
|
||||||
background: #4f46e5;
|
background: #4f46e5;
|
||||||
@@ -262,8 +269,10 @@
|
|||||||
|
|
||||||
<div class="search-container">
|
<div class="search-container">
|
||||||
<input type="text" id="searchInput" placeholder="搜索用户名...">
|
<input type="text" id="searchInput" placeholder="搜索用户名...">
|
||||||
|
<select id="keyFilter"></select>
|
||||||
<button id="searchBtn">搜索</button>
|
<button id="searchBtn">搜索</button>
|
||||||
<button id="resetBtn">重置</button>
|
<button id="resetBtn">重置</button>
|
||||||
|
<button id="clearKeyBtn">清空Key</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
@@ -272,6 +281,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>用户ID</th>
|
<th>用户ID</th>
|
||||||
<th>用户名</th>
|
<th>用户名</th>
|
||||||
|
<th>Key</th>
|
||||||
<th>权限</th>
|
<th>权限</th>
|
||||||
<th>操作</th>
|
<th>操作</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -364,11 +374,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取所有用户
|
// 获取所有用户
|
||||||
async function loadUsers(searchTerm = '') {
|
async function loadUsers(searchTerm = '', key = '') {
|
||||||
try {
|
try {
|
||||||
const url = searchTerm ?
|
const params = new URLSearchParams();
|
||||||
`/elastic/users/?search=${encodeURIComponent(searchTerm)}` :
|
if ((searchTerm || '').trim()) params.set('search', (searchTerm || '').trim());
|
||||||
'/elastic/users/';
|
if ((key || '').trim()) params.set('key', (key || '').trim());
|
||||||
|
const url = params.toString() ? `/elastic/users/?${params.toString()}` : '/elastic/users/';
|
||||||
|
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
@@ -385,10 +396,13 @@
|
|||||||
|
|
||||||
// 根据权限值显示权限名称
|
// 根据权限值显示权限名称
|
||||||
const permissionText = Number(user.permission) === 0 ? '管理员' : '普通用户';
|
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('、') || '-';
|
||||||
|
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
<td>${user.user_id}</td>
|
<td>${user.user_id}</td>
|
||||||
<td>${user.username}</td>
|
<td>${user.username}</td>
|
||||||
|
<td>${keysText}</td>
|
||||||
<td>${permissionText}</td>
|
<td>${permissionText}</td>
|
||||||
<td class="action-buttons">
|
<td class="action-buttons">
|
||||||
<button class="btn btn-success edit-btn" data-user='${JSON.stringify(user)}'>编辑</button>
|
<button class="btn btn-success edit-btn" data-user='${JSON.stringify(user)}'>编辑</button>
|
||||||
@@ -407,6 +421,29 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function initKeyFilter() {
|
||||||
|
const select = document.getElementById('keyFilter');
|
||||||
|
if (!select) return;
|
||||||
|
select.innerHTML = '<option value="">全部Key</option>';
|
||||||
|
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 || [];
|
||||||
|
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 openAddModal() {
|
function openAddModal() {
|
||||||
document.getElementById('modalTitle').textContent = '添加用户';
|
document.getElementById('modalTitle').textContent = '添加用户';
|
||||||
@@ -558,7 +595,8 @@
|
|||||||
if (searchBtn) {
|
if (searchBtn) {
|
||||||
searchBtn.addEventListener('click', function() {
|
searchBtn.addEventListener('click', function() {
|
||||||
const searchTerm = document.getElementById('searchInput').value;
|
const searchTerm = document.getElementById('searchInput').value;
|
||||||
loadUsers(searchTerm);
|
const key = (document.getElementById('keyFilter') || {}).value || '';
|
||||||
|
loadUsers(searchTerm, key);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -566,7 +604,19 @@
|
|||||||
if (resetBtn) {
|
if (resetBtn) {
|
||||||
resetBtn.addEventListener('click', function() {
|
resetBtn.addEventListener('click', function() {
|
||||||
document.getElementById('searchInput').value = '';
|
document.getElementById('searchInput').value = '';
|
||||||
loadUsers();
|
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, '');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -609,6 +659,7 @@
|
|||||||
|
|
||||||
// 页面加载时获取用户列表
|
// 页面加载时获取用户列表
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
initKeyFilter();
|
||||||
const selfForm = document.getElementById('selfPwdForm');
|
const selfForm = document.getElementById('selfPwdForm');
|
||||||
if (selfForm) {
|
if (selfForm) {
|
||||||
selfForm.addEventListener('submit', async (e) => {
|
selfForm.addEventListener('submit', async (e) => {
|
||||||
@@ -635,7 +686,8 @@
|
|||||||
}
|
}
|
||||||
const tbody = document.getElementById('usersTableBody');
|
const tbody = document.getElementById('usersTableBody');
|
||||||
if (tbody) {
|
if (tbody) {
|
||||||
loadUsers();
|
const select = document.getElementById('keyFilter');
|
||||||
|
loadUsers('', select ? select.value : '');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -654,4 +706,4 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -444,6 +444,7 @@ def get_users(request):
|
|||||||
is_admin = int(request.session.get("permission", 1)) == 0
|
is_admin = int(request.session.get("permission", 1)) == 0
|
||||||
requester = get_user_by_id(uid) or {}
|
requester = get_user_by_id(uid) or {}
|
||||||
mgr_keys = set(requester.get("manage_key") or [])
|
mgr_keys = set(requester.get("manage_key") or [])
|
||||||
|
key_q = (request.GET.get("key") or "").strip()
|
||||||
q = (request.GET.get("search") or "").strip()
|
q = (request.GET.get("search") or "").strip()
|
||||||
users = get_all_users()
|
users = get_all_users()
|
||||||
if is_admin:
|
if is_admin:
|
||||||
@@ -455,6 +456,15 @@ def get_users(request):
|
|||||||
filtered = [u for u in users if match_manage(u)]
|
filtered = [u for u in users if match_manage(u)]
|
||||||
else:
|
else:
|
||||||
filtered = [u for u in users if str(u.get("user_id")) == str(uid)]
|
filtered = [u for u in users if str(u.get("user_id")) == str(uid)]
|
||||||
|
if key_q:
|
||||||
|
k = str(key_q).strip()
|
||||||
|
def match_key(user):
|
||||||
|
try:
|
||||||
|
ukeys = {str(x).strip() for x in (user.get("key") or []) if str(x).strip()}
|
||||||
|
except Exception:
|
||||||
|
ukeys = set()
|
||||||
|
return k in ukeys
|
||||||
|
filtered = [u for u in filtered if match_key(u)]
|
||||||
if q:
|
if q:
|
||||||
filtered = [u for u in filtered if q in str(u.get("username", ""))]
|
filtered = [u for u in filtered if q in str(u.get("username", ""))]
|
||||||
return JsonResponse({"status": "success", "data": filtered})
|
return JsonResponse({"status": "success", "data": filtered})
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
{% if is_admin or has_manage_key %}
|
{% if is_admin or has_manage_key %}
|
||||||
<a href="{% url 'elastic:manage_page' %}" onclick="return handleNavClick(this, '/elastic/manage/');">数据管理</a>
|
<a href="{% url 'elastic:manage_page' %}" onclick="return handleNavClick(this, '/elastic/manage/');">数据管理</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if is_admin %}
|
{% if is_admin or has_manage_key %}
|
||||||
<a href="{% url 'elastic:user_manage' %}" onclick="return handleNavClick(this, '/elastic/user_manage/');">用户管理</a>
|
<a href="{% url 'elastic:user_manage' %}" onclick="return handleNavClick(this, '/elastic/user_manage/');">用户管理</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="/accounts/profile/">个人中心</a>
|
<a href="/accounts/profile/">个人中心</a>
|
||||||
|
|||||||
Reference in New Issue
Block a user