Files
zlqy/templates/admin_users.html
2026-03-05 12:57:55 +08:00

130 lines
5.9 KiB
HTML

{% extends "admin_base.html" %}
{% block title %}用户管理 - 智联青云管理后台{% endblock %}
{% block admin_content %}
<div class="space-y-6">
<h1 class="text-3xl font-extrabold bg-gradient-to-r from-blue-400 via-purple-400 to-pink-400 bg-clip-text text-transparent">用户管理</h1>
<div class="flex gap-4">
<input id="search-input" type="text" placeholder="搜索用户名/邮箱..." class="input-futuristic flex-1">
<select id="role-filter" class="input-futuristic">
<option value="">全部角色</option>
<option value="admin">管理员</option>
<option value="teacher">教师</option>
<option value="student">学生</option>
</select>
<button onclick="loadUsers()" class="btn-futuristic px-6">搜索</button>
</div>
<div class="futuristic-card-dark overflow-hidden">
<div class="table-responsive">
<table class="table-futuristic" style="min-width: 900px;">
<thead>
<tr>
<th style="min-width: 60px;">ID</th>
<th style="min-width: 120px;">用户名</th>
<th style="min-width: 180px;">邮箱</th>
<th style="min-width: 100px;">角色</th>
<th style="min-width: 100px;">状态</th>
<th style="min-width: 150px;">注册时间</th>
<th style="min-width: 180px;">操作</th>
</tr>
</thead>
<tbody id="users-tbody"></tbody>
</table>
</div>
<div id="loading-spinner" class="text-center py-8 text-slate-500 hidden">
<svg class="animate-spin h-6 w-6 mx-auto text-cyan-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span class="text-sm">加载中...</span>
</div>
<div id="empty-msg" class="text-center py-12 text-slate-500 hidden">暂无用户</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
const roleMap = {
'admin': ['管理员', 'bg-red-500/20 text-red-400 border-red-500/30'],
'teacher': ['教师', 'bg-blue-500/20 text-blue-400 border-blue-500/30'],
'student': ['学生', 'bg-green-500/20 text-green-400 border-green-500/30']
};
async function loadUsers() {
const q = document.getElementById('search-input').value;
const role = document.getElementById('role-filter').value;
const tbody = document.getElementById('users-tbody');
const spinner = document.getElementById('loading-spinner');
const empty = document.getElementById('empty-msg');
tbody.innerHTML = '';
spinner.classList.remove('hidden');
empty.classList.add('hidden');
try {
const params = new URLSearchParams();
if (q) params.set('q', q);
if (role) params.set('role', role);
const res = await fetch('/api/admin/users?' + params);
const data = await res.json();
spinner.classList.add('hidden');
if (!data.success || !data.users.length) {
empty.classList.remove('hidden');
return;
}
let html = '';
data.users.forEach(u => {
const [roleText, roleClass] = roleMap[u.role] || ['未知', 'bg-slate-500/20 text-slate-400 border-slate-500/30'];
const banned = u.is_banned;
html += `<tr>
<td>${u.id}</td>
<td class="font-medium text-slate-200">${u.name}</td>
<td class="text-slate-400">${u.email || '-'}</td>
<td>
<span class="badge-futuristic ${roleClass}">${roleText}</span>
</td>
<td>
<span class="badge-futuristic ${banned ? 'bg-red-500/20 text-red-400 border-red-500/30' : 'bg-green-500/20 text-green-400 border-green-500/30'}">${banned ? '已封禁' : '正常'}</span>
</td>
<td class="text-slate-400">${u.created_at}</td>
<td class="space-x-2">
<button onclick="toggleBan(${u.id}, ${banned})" class="${banned ? 'btn-futuristic' : 'btn-outline-futuristic'} px-3 py-1 text-xs">${banned ? '解封' : '封禁'}</button>
<button onclick="deleteUser(${u.id}, '${u.name.replace(/'/g, "\\'")}')" class="btn-outline-futuristic px-3 py-1 text-xs border-red-500/30 text-red-400 hover:bg-red-500/20 hover:border-red-500/50">删除</button>
</td>
</tr>`;
});
tbody.innerHTML = html;
} catch(e) {
spinner.classList.add('hidden');
empty.textContent = '加载失败,请稍后重试';
empty.classList.remove('hidden');
}
}
async function toggleBan(id, currentlyBanned) {
if (!confirm(currentlyBanned ? '确定解封该用户?' : '确定封禁该用户?')) return;
try {
const res = await fetch(`/api/admin/users/${id}/ban`, {method: 'PUT'});
const data = await res.json();
if (data.success) { loadUsers(); } else { alert(data.message || '操作失败'); }
} catch(e) { alert('网络错误'); }
}
async function deleteUser(id, name) {
if (!confirm(`确定删除用户「${name}」?此操作不可恢复!`)) return;
try {
const res = await fetch(`/api/admin/users/${id}`, {method: 'DELETE'});
const data = await res.json();
if (data.success) { loadUsers(); } else { alert(data.message || '操作失败'); }
} catch(e) { alert('网络错误'); }
}
document.getElementById('search-input').addEventListener('keydown', e => { if (e.key === 'Enter') loadUsers(); });
document.addEventListener('DOMContentLoaded', loadUsers);
</script>
{% endblock %}