Files
mce/templates/admin/static/js/users.js
Victor_Jay 983f539268 fix: 修复管理后台 JS api 变量冲突导致页面加载失败
- common.js 中 const api 改为 var api,避免与 utils.js 的 function api() 重复声明冲突,导致 roleLabel/statusLabel 等未定义
- common.js 新增 api.del() 方法
- comments.js 升级为对象式 api.get/api.del,修复 api('GET') 函数调用失败
- comments.js 修复 csrfToken 未定义导致删除请求 403
- users.js/audit.js catch 块改为输出真实错误信息
2026-06-02 21:13:58 +08:00

158 lines
6.5 KiB
JavaScript
Raw Permalink 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.

/* =====================================================
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) {
console.error('loadUsers error:', e);
showToast('加载失败: ' + (e.message || '网络错误'), '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.id}</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.id === 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.id}, '${escapeHtml(u.username)}')">封禁</button>`;
} else if (u.status === 'banned') {
btns += `<button class="btn btn-outline btn-sm" onclick="unbanUser(${u.id}, '${escapeHtml(u.username)}')">解封</button>`;
}
// 强制下线按钮
if (u.status === 'active' || u.status === 'banned') {
btns += `<button class="btn btn-outline btn-sm" onclick="resetToken(${u.id}, '${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.id}, '${escapeHtml(u.username)}')">删除</button>`;
}
// 解锁按钮(仅 owner 可见,状态为 locked
if (isOwner && u.status === 'locked') {
btns += `<button class="btn btn-outline btn-sm" onclick="unlockUser(${u.id}, '${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 });
}