157 lines
6.4 KiB
JavaScript
157 lines
6.4 KiB
JavaScript
/* =====================================================
|
||
MetaLab 管理面板 - 用户管理 JS
|
||
单一职责:用户列表渲染、搜索筛选、封禁/解封、强制下线
|
||
===================================================== */
|
||
|
||
let currentPage = 1;
|
||
const pageSize = 20;
|
||
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
loadUsers();
|
||
|
||
document.getElementById('searchBtn').addEventListener('click', () => {
|
||
currentPage = 1;
|
||
loadUsers();
|
||
});
|
||
document.getElementById('searchInput').addEventListener('keydown', (e) => {
|
||
if (e.key === 'Enter') { currentPage = 1; loadUsers(); }
|
||
});
|
||
document.getElementById('roleFilter').addEventListener('change', () => {
|
||
currentPage = 1; loadUsers();
|
||
});
|
||
document.getElementById('statusFilter').addEventListener('change', () => {
|
||
currentPage = 1; loadUsers();
|
||
});
|
||
});
|
||
|
||
async function loadUsers() {
|
||
const keyword = document.getElementById('searchInput').value.trim();
|
||
const role = document.getElementById('roleFilter').value;
|
||
const status = document.getElementById('statusFilter').value;
|
||
|
||
const params = new URLSearchParams({
|
||
page: currentPage,
|
||
page_size: pageSize,
|
||
keyword, role, status
|
||
});
|
||
|
||
try {
|
||
const res = await api.get('/api/admin/users?' + params);
|
||
if (res.success) {
|
||
renderTable(res.data.users);
|
||
renderPagination(res.data.total, res.data.page);
|
||
} else {
|
||
showToast(res.message || '加载失败', 'error');
|
||
}
|
||
} catch (e) {
|
||
showToast('网络错误', 'error');
|
||
}
|
||
}
|
||
|
||
function renderTable(users) {
|
||
const tbody = document.getElementById('userTableBody');
|
||
if (!users || users.length === 0) {
|
||
tbody.innerHTML = '<tr><td colspan="7" style="text-align:center;padding:40px;color:#636e72;">没有找到用户</td></tr>';
|
||
return;
|
||
}
|
||
tbody.innerHTML = users.map(u => `
|
||
<tr>
|
||
<td class="col-uid">${u.uid}</td>
|
||
<td class="col-username">${escapeHtml(u.username)}</td>
|
||
<td class="col-email">${escapeHtml(u.email)}</td>
|
||
<td class="col-role">${roleLabel(u.role)}</td>
|
||
<td class="col-status">${statusLabel(u.status)}</td>
|
||
<td class="col-time">${formatTime(u.created_at)}</td>
|
||
<td class="col-actions">
|
||
<div class="action-group">
|
||
${renderActions(u)}
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
function renderActions(u) {
|
||
// 不可操作自己
|
||
if (u.uid === window.__currentUid) return '—';
|
||
|
||
let btns = '';
|
||
const isOwner = window.__isOwner || false;
|
||
// 封禁/解封按钮
|
||
if (u.status === 'active') {
|
||
btns += `<button class="btn btn-danger btn-sm" onclick="banUser(${u.uid}, '${escapeHtml(u.username)}')">封禁</button>`;
|
||
} else if (u.status === 'banned') {
|
||
btns += `<button class="btn btn-outline btn-sm" onclick="unbanUser(${u.uid}, '${escapeHtml(u.username)}')">解封</button>`;
|
||
}
|
||
// 强制下线按钮
|
||
if (u.status === 'active' || u.status === 'banned') {
|
||
btns += `<button class="btn btn-outline btn-sm" onclick="resetToken(${u.uid}, '${escapeHtml(u.username)}')">踢下线</button>`;
|
||
}
|
||
// 删除按钮(仅 owner 可见,且目标不是 locked/deleted)
|
||
if (isOwner && u.status !== 'locked' && u.status !== 'deleted') {
|
||
btns += `<button class="btn btn-danger btn-sm" onclick="deleteUser(${u.uid}, '${escapeHtml(u.username)}')">删除</button>`;
|
||
}
|
||
// 解锁按钮(仅 owner 可见,状态为 locked)
|
||
if (isOwner && u.status === 'locked') {
|
||
btns += `<button class="btn btn-outline btn-sm" onclick="unlockUser(${u.uid}, '${escapeHtml(u.username)}')">解锁</button>`;
|
||
}
|
||
return btns || '—';
|
||
}
|
||
|
||
async function banUser(uid, username) {
|
||
const ok = await showConfirm('封禁用户', `确定要封禁用户「${username}」吗?封禁后该用户将无法登录。`);
|
||
if (!ok) return;
|
||
const res = await api.put(`/api/admin/users/${uid}/status`, { status: 'banned' });
|
||
if (res.success) { showToast(res.message, 'success'); loadUsers(); }
|
||
else { showToast(res.message || '操作失败', 'error'); }
|
||
}
|
||
|
||
async function unbanUser(uid, username) {
|
||
const ok = await showConfirm('解封用户', `确定要解封用户「${username}」吗?`);
|
||
if (!ok) return;
|
||
const res = await api.put(`/api/admin/users/${uid}/status`, { status: 'active' });
|
||
if (res.success) { showToast(res.message, 'success'); loadUsers(); }
|
||
else { showToast(res.message || '操作失败', 'error'); }
|
||
}
|
||
|
||
async function deleteUser(uid, username) {
|
||
const ok = await showConfirm('删除用户', `确定要永久删除用户「${username}」吗?此操作不可撤销,该账号将被立即锁定。`);
|
||
if (!ok) return;
|
||
const res = await api.put(`/api/admin/users/${uid}/status`, { status: 'locked' });
|
||
if (res.success) { showToast(res.message, 'success'); loadUsers(); }
|
||
else { showToast(res.message || '操作失败', 'error'); }
|
||
}
|
||
|
||
async function unlockUser(uid, username) {
|
||
const ok = await showConfirm('解锁用户', `确定要解锁用户「${username}」吗?该账号将恢复为正常状态。`);
|
||
if (!ok) return;
|
||
const res = await api.put(`/api/admin/users/${uid}/status`, { status: 'active' });
|
||
if (res.success) { showToast(res.message, 'success'); loadUsers(); }
|
||
else { showToast(res.message || '操作失败', 'error'); }
|
||
}
|
||
|
||
async function resetToken(uid, username) {
|
||
const ok = await showConfirm('强制下线', `确定要强制「${username}」下线吗?该用户需要重新登录。`);
|
||
if (!ok) return;
|
||
const res = await api.post(`/api/admin/users/${uid}/reset-token`);
|
||
if (res.success) { showToast(res.message, 'success'); }
|
||
else { showToast(res.message || '操作失败', 'error'); }
|
||
}
|
||
|
||
function renderPagination(total, page) {
|
||
const totalPages = Math.ceil(total / pageSize);
|
||
const bar = document.getElementById('paginationBar');
|
||
if (totalPages <= 1) { bar.innerHTML = ''; return; }
|
||
|
||
let html = `<button onclick="goPage(${page - 1})" ${page <= 1 ? 'disabled' : ''}>上一页</button>`;
|
||
html += `<span class="page-current">第 ${page} / ${totalPages} 页(共 ${total} 条)</span>`;
|
||
html += `<button onclick="goPage(${page + 1})" ${page >= totalPages ? 'disabled' : ''}>下一页</button>`;
|
||
bar.innerHTML = html;
|
||
}
|
||
|
||
function goPage(p) {
|
||
currentPage = p;
|
||
loadUsers();
|
||
window.scrollTo({ top: 0 });
|
||
}
|