需测试[0.2.7.3][ci]

This commit is contained in:
DSQ
2026-03-15 17:11:31 +08:00
parent f38cb5ec76
commit b0c3707ccd
4 changed files with 148 additions and 9 deletions

View File

@@ -29,6 +29,13 @@
.image-item .info { padding: 10px; font-size: 12px; color: #888; text-align: center; }
.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; }
@@ -65,6 +72,28 @@
</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>
{% if achievements %}
<div class="image-grid">
@@ -128,6 +157,54 @@
const modal = document.getElementById('imageModal');
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>
</body>
</html>

View File

@@ -134,6 +134,13 @@
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;
@@ -262,8 +269,10 @@
<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">
@@ -272,6 +281,7 @@
<tr>
<th>用户ID</th>
<th>用户名</th>
<th>Key</th>
<th>权限</th>
<th>操作</th>
</tr>
@@ -364,11 +374,12 @@
}
// 获取所有用户
async function loadUsers(searchTerm = '') {
async function loadUsers(searchTerm = '', key = '') {
try {
const url = searchTerm ?
`/elastic/users/?search=${encodeURIComponent(searchTerm)}` :
'/elastic/users/';
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();
@@ -385,10 +396,13 @@
// 根据权限值显示权限名称
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 = `
<td>${user.user_id}</td>
<td>${user.username}</td>
<td>${keysText}</td>
<td>${permissionText}</td>
<td class="action-buttons">
<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() {
document.getElementById('modalTitle').textContent = '添加用户';
@@ -558,7 +595,8 @@
if (searchBtn) {
searchBtn.addEventListener('click', function() {
const searchTerm = document.getElementById('searchInput').value;
loadUsers(searchTerm);
const key = (document.getElementById('keyFilter') || {}).value || '';
loadUsers(searchTerm, key);
});
}
@@ -566,7 +604,19 @@
if (resetBtn) {
resetBtn.addEventListener('click', function() {
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() {
initKeyFilter();
const selfForm = document.getElementById('selfPwdForm');
if (selfForm) {
selfForm.addEventListener('submit', async (e) => {
@@ -635,7 +686,8 @@
}
const tbody = document.getElementById('usersTableBody');
if (tbody) {
loadUsers();
const select = document.getElementById('keyFilter');
loadUsers('', select ? select.value : '');
}
});
@@ -654,4 +706,4 @@
});
</script>
</body>
</html>
</html>

View File

@@ -444,6 +444,7 @@ def get_users(request):
is_admin = int(request.session.get("permission", 1)) == 0
requester = get_user_by_id(uid) or {}
mgr_keys = set(requester.get("manage_key") or [])
key_q = (request.GET.get("key") or "").strip()
q = (request.GET.get("search") or "").strip()
users = get_all_users()
if is_admin:
@@ -455,6 +456,15 @@ def get_users(request):
filtered = [u for u in users if match_manage(u)]
else:
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:
filtered = [u for u in filtered if q in str(u.get("username", ""))]
return JsonResponse({"status": "success", "data": filtered})

View File

@@ -44,7 +44,7 @@
{% if is_admin or has_manage_key %}
<a href="{% url 'elastic:manage_page' %}" onclick="return handleNavClick(this, '/elastic/manage/');">数据管理</a>
{% 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>
{% endif %}
<a href="/accounts/profile/">个人中心</a>