Files
squad-rain-ops-mini/wwwroot/user-manager.html

726 lines
26 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>子账户管理</title>
<link rel="stylesheet" href="/lib/css/bootstrap.min.css">
<link rel="stylesheet" href="/lib/css/font-awesome.min.css">
<style>
:root {
--primary-color: #0056b3;
--bg-color: #f0f2f5;
--card-bg: #ffffff;
--text-color: #2c3e50;
--border-color: #d1d5db;
--danger-color: #d93025;
--success-color: #107c10;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
margin: 0;
padding: 20px;
display: flex;
flex-direction: column;
min-height: 100vh;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.title {
font-size: 1.5rem;
font-weight: bold;
display: flex;
align-items: center;
gap: 10px;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
display: inline-flex;
align-items: center;
gap: 6px;
transition: background-color 0.2s;
text-decoration: none;
}
.btn-primary {
background-color: var(--primary-color);
color: white;
}
.btn-primary:hover {
background-color: #005a9e;
}
.btn-danger {
background-color: white;
color: var(--danger-color);
border: 1px solid var(--danger-color);
}
.btn-danger:hover {
background-color: #fff0f0;
}
.btn-sm {
padding: 4px 8px;
font-size: 0.85rem;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn-secondary:hover {
background-color: #5a6268;
}
.container {
background: var(--card-bg);
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
padding: 20px;
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #eee;
}
th {
background-color: #f9f9f9;
font-weight: 600;
color: #666;
}
.status-badge {
padding: 4px 8px;
border-radius: 12px;
font-size: 0.85em;
font-weight: 500;
}
.status-active {
background-color: #e6ffed;
color: var(--success-color);
}
.status-disabled {
background-color: #fff0f0;
color: var(--danger-color);
}
/* Modal */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal.active {
display: flex;
}
.modal-content {
background-color: white;
padding: 25px;
border-radius: 8px;
width: 500px;
max-width: 90%;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.modal-header {
font-size: 1.2rem;
font-weight: bold;
margin-bottom: 20px;
display: flex;
justify-content: space-between;
}
.close-btn {
cursor: pointer;
font-size: 1.5rem;
line-height: 1;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: 500;
}
.form-control {
width: 100%;
padding: 8px;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 4px;
}
.permissions-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 10px;
margin-top: 5px;
background: #f9f9f9;
padding: 10px;
border-radius: 4px;
}
.permissions-section {
margin-bottom: 15px;
border: 1px solid #eee;
border-radius: 6px;
padding: 10px;
}
.permissions-section h4 {
margin: 0 0 10px 0;
font-size: 1rem;
color: #555;
border-bottom: 1px solid #eee;
padding-bottom: 5px;
}
.checkbox-label {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.9rem;
cursor: pointer;
}
.actions-cell {
display: flex;
gap: 8px;
}
</style>
</head>
<body>
<div class="header">
<div class="title">
<a href="/" class="btn btn-secondary btn-sm" style="margin-right: 10px;"><i class="fas fa-arrow-left"></i> 返回</a>
<i class="fas fa-users-cog"></i> 子账户管理
</div>
<div>
<button class="btn btn-secondary" onclick="window.location.href='/role-manager.html'" style="margin-right: 10px;"><i class="fas fa-user-tag"></i> 角色管理</button>
<button class="btn btn-primary" onclick="openAddModal()"><i class="fas fa-plus"></i> 新增账户</button>
</div>
</div>
<div class="container">
<table id="userTable">
<thead>
<tr>
<th>用户名</th>
<th>角色</th>
<th>状态</th>
<th>权限</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- Users will be loaded here -->
</tbody>
</table>
</div>
<!-- Edit/Add Modal -->
<div id="userModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<span id="modalTitle">新增账户</span>
<span class="close-btn" onclick="closeModal('userModal')">&times;</span>
</div>
<div class="form-group">
<label>用户名</label>
<input type="text" id="username" class="form-control">
</div>
<div class="form-group" id="passwordGroup">
<label>密码</label>
<input type="password" id="password" class="form-control" placeholder="输入密码">
</div>
<div class="form-group">
<label>状态</label>
<select id="isEnabled" class="form-control">
<option value="true">启用</option>
<option value="false">停用</option>
</select>
</div>
<div class="form-group">
<label>角色 (可选)</label>
<select id="roleSelect" class="form-control" onchange="onRoleChange()">
<option value="">-- 自定义权限 --</option>
</select>
<small style="color: #666; display: block; margin-top: 5px;">选择角色将自动填充权限</small>
</div>
<div class="form-group">
<label>权限配置</label>
<div class="permissions-section">
<h4 style="display:flex;justify-content:space-between;align-items:center;">
菜单访问权限
<label class="checkbox-label" style="font-size:0.9rem;font-weight:normal;">
<input type="checkbox" onchange="toggleSelectAll(this)"> 全选
</label>
</h4>
<div id="permissionContainer" style="max-height: 400px; overflow-y: auto;">
<!-- Permissions generated by JS -->
</div>
</div>
</div>
<div style="text-align: right; margin-top: 20px;">
<button class="btn btn-secondary" onclick="closeModal('userModal')">取消</button>
<button class="btn btn-primary" onclick="saveUser()">保存</button>
</div>
</div>
</div>
<script>
// Permission Check
window.checkPermission = function(key) {
const isSuper = localStorage.getItem('isSuperAdmin') === 'true';
if (isSuper) return true;
try {
const perms = JSON.parse(localStorage.getItem('permissions') || '[]');
return perms.includes('all') || perms.includes(key);
} catch { return false; }
};
(function() {
const required = 'users.view';
if (!checkPermission(required)) {
document.body.innerHTML = '<div style="display:flex;justify-content:center;align-items:center;height:100vh;color:red;font-size:1.5rem;">Access Denied: Permission "users.view" required.</div>';
throw new Error("Access Denied");
}
})();
let currentUserId = null;
let allUsers = [];
let allRoles = [];
const PERMISSION_CONFIG = [
{
category: "核心功能",
items: [
{ label: "仪表盘", value: "dashboard", children: [
{ label: "聊天面板", value: "dashboard.chat" },
{ label: "战斗事件", value: "dashboard.battle" },
{ label: "在线玩家", value: "dashboard.online" },
{ label: "刷新列表", value: "dashboard.refresh" },
{ label: "随机阵营", value: "dashboard.shuffle" },
{ label: "清理挂机", value: "dashboard.clearafk" },
{ label: "地图管理", value: "dashboard.maps" },
{ label: "设置票数", value: "dashboard.settickets" }
]},
{ label: "RCON控制台", value: "rcon", children: [{ label: "执行命令", value: "rcon.execute" }] },
{ label: "数据分析", value: "analytics" },
{ label: "历史对局", value: "history" },
{ label: "日志查询", value: "logs" }
]
},
{
category: "玩家管理",
items: [
{ label: "玩家库", value: "players", children: [
{ label: "踢出玩家", value: "players.kick" },
{ label: "封禁玩家", value: "players.ban" },
{ label: "警告玩家", value: "players.warn" },
{ label: "更换阵营", value: "players.changeteam" },
{ label: "解散小队", value: "players.disbandsquad" },
{ label: "移出小队", value: "players.removesquad" },
{ label: "游戏时长", value: "players.gametime" }
]},
{ label: "积分管理", value: "points.view", children: [
{ label: "修改积分", value: "points.edit" }
]},
{ label: "聊天消息", value: "chat" }
]
},
{
category: "系统管理",
items: [
{ label: "权限管理", value: "admins.view", children: [
{ label: "新增管理员", value: "admins.add" },
{ label: "编辑管理员", value: "admins.edit" },
{ label: "删除管理员", value: "admins.delete" }
]},
{ label: "会员管理", value: "vips.view", children: [
{ label: "新增会员", value: "vips.add" },
{ label: "编辑会员", value: "vips.edit" },
{ label: "删除会员", value: "vips.delete" }
]},
{ label: "CDK管理", value: "cdks.view", children: [
{ label: "生成CDK", value: "cdks.add" },
{ label: "删除CDK", value: "cdks.delete" }
]},
{ label: "子账户管理", value: "users.view", children: [
{ label: "新增账户", value: "users.add" },
{ label: "编辑账户", value: "users.edit" },
{ label: "删除账户", value: "users.delete" }
]},
{ label: "角色管理", value: "roles.view", children: [
{ label: "新增角色", value: "roles.add" },
{ label: "编辑角色", value: "roles.edit" },
{ label: "删除角色", value: "roles.delete" }
]}
]
}
];
document.addEventListener('DOMContentLoaded', () => {
renderPermissions();
loadRoles().then(loadUsers);
// Button permissions
if (!checkPermission('users.add')) {
const btn = document.querySelector('button[onclick="openAddModal()"]');
if (btn) btn.style.display = 'none';
}
if (!checkPermission('roles.view')) {
const btn = document.querySelector('button[onclick*="role-manager.html"]');
if (btn) btn.style.display = 'none';
}
});
function renderPermissions() {
const container = document.getElementById('permissionContainer');
container.innerHTML = '';
PERMISSION_CONFIG.forEach(group => {
const groupDiv = document.createElement('div');
groupDiv.style.marginBottom = '15px';
const title = document.createElement('div');
title.style.borderBottom = '1px solid #eee';
title.style.paddingBottom = '5px';
title.style.marginBottom = '10px';
title.style.fontWeight = 'bold';
title.style.color = '#555';
title.innerText = group.category;
groupDiv.appendChild(title);
const grid = document.createElement('div');
grid.className = 'permissions-grid';
group.items.forEach(item => {
const itemDiv = document.createElement('div');
const label = document.createElement('label');
label.className = 'checkbox-label';
label.style.fontWeight = 'bold';
label.innerHTML = `<input type="checkbox" value="${item.value}" onchange="toggleChildren(this)"> ${item.label}`;
itemDiv.appendChild(label);
if (item.children) {
const childrenDiv = document.createElement('div');
childrenDiv.style.marginLeft = '20px';
childrenDiv.style.marginTop = '5px';
childrenDiv.style.display = 'flex';
childrenDiv.style.flexDirection = 'column';
childrenDiv.style.gap = '5px';
item.children.forEach(child => {
const childLabel = document.createElement('label');
childLabel.className = 'checkbox-label';
childLabel.style.fontSize = '0.85rem';
childLabel.innerHTML = `<input type="checkbox" value="${child.value}"> ${child.label}`;
childrenDiv.appendChild(childLabel);
});
itemDiv.appendChild(childrenDiv);
}
grid.appendChild(itemDiv);
});
groupDiv.appendChild(grid);
container.appendChild(groupDiv);
});
}
function toggleChildren(source) {
const itemDiv = source.parentNode.parentNode;
const childrenDiv = itemDiv.querySelector('div');
if (childrenDiv) {
childrenDiv.querySelectorAll('input[type="checkbox"]').forEach(cb => cb.checked = source.checked);
}
}
async function loadRoles() {
try {
const res = await fetch('/api/roles');
if (res.ok) {
allRoles = await res.json();
const select = document.getElementById('roleSelect');
select.innerHTML = '<option value="">-- 自定义权限 --</option>';
allRoles.forEach(role => {
const opt = document.createElement('option');
opt.value = role.id;
opt.innerText = role.name;
select.appendChild(opt);
});
}
} catch (err) {
console.error('Failed to load roles', err);
}
}
function loadUsers() {
fetch('/api/users')
.then(res => {
if (res.status === 401) {
alert('无权访问');
window.location.href = '/login.html';
return [];
}
return res.json();
})
.then(users => {
allUsers = users || [];
renderTable(allUsers);
})
.catch(err => console.error(err));
}
function renderTable(users) {
const tbody = document.querySelector('#userTable tbody');
tbody.innerHTML = '';
const canEdit = checkPermission('users.edit');
const canDelete = checkPermission('users.delete');
users.forEach(user => {
const tr = document.createElement('tr');
const roleName = allRoles.find(r => r.id === user.roleId)?.name || '<span style="color:#999">自定义</span>';
let actions = '';
if (canEdit) {
actions += `<button class="btn btn-primary btn-sm" onclick="openEditModal('${user.id}')"><i class="fas fa-edit"></i> 编辑</button>`;
actions += `<button class="btn btn-secondary btn-sm" onclick="resetPassword('${user.id}')"><i class="fas fa-key"></i> 重置密码</button>`;
}
if (canDelete) {
actions += `<button class="btn btn-danger btn-sm" onclick="deleteUser('${user.id}')"><i class="fas fa-trash"></i> 删除</button>`;
}
tr.innerHTML = `
<td>${user.username}</td>
<td>${roleName}</td>
<td><span class="status-badge ${user.isEnabled ? 'status-active' : 'status-disabled'}">${user.isEnabled ? '启用' : '停用'}</span></td>
<td>${user.permissions ? user.permissions.length : 0} 项权限</td>
<td>${new Date(user.createdAt).toLocaleString()}</td>
<td class="actions-cell">
${actions}
</td>
`;
tbody.appendChild(tr);
});
}
function openAddModal() {
currentUserId = null;
document.getElementById('modalTitle').innerText = '新增账户';
document.getElementById('username').value = '';
document.getElementById('username').disabled = false;
document.getElementById('password').value = '';
document.getElementById('passwordGroup').style.display = 'block';
document.getElementById('isEnabled').value = 'true';
document.getElementById('roleSelect').value = '';
// Clear checkboxes
document.querySelectorAll('#permissionContainer input[type="checkbox"]').forEach(cb => cb.checked = false);
document.getElementById('userModal').classList.add('active');
}
function openEditModal(id) {
const user = allUsers.find(u => u.id === id);
if (!user) return;
currentUserId = id;
document.getElementById('modalTitle').innerText = '编辑账户';
document.getElementById('username').value = user.username;
document.getElementById('username').disabled = true; // Cannot change username
document.getElementById('password').value = '';
document.getElementById('passwordGroup').style.display = 'none'; // Use reset password for existing users
document.getElementById('isEnabled').value = user.isEnabled.toString();
document.getElementById('roleSelect').value = user.roleId || '';
// Set checkboxes
document.querySelectorAll('#permissionContainer input[type="checkbox"]').forEach(cb => {
cb.checked = user.permissions && user.permissions.includes(cb.value);
});
document.getElementById('userModal').classList.add('active');
}
function closeModal(modalId) {
document.getElementById(modalId).classList.remove('active');
}
function toggleSelectAll(source) {
const checkboxes = document.querySelectorAll('#permissionContainer input[type="checkbox"]');
checkboxes.forEach(cb => cb.checked = source.checked);
}
function onRoleChange() {
const roleId = document.getElementById('roleSelect').value;
if (!roleId) return;
const role = allRoles.find(r => r.id === roleId);
if (role) {
document.querySelectorAll('#permissionContainer input[type="checkbox"]').forEach(cb => {
cb.checked = role.permissions && role.permissions.includes(cb.value);
});
}
}
function saveUser() {
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
const isEnabled = document.getElementById('isEnabled').value === 'true';
const roleId = document.getElementById('roleSelect').value;
if (!username) {
alert('请输入用户名');
return;
}
if (!currentUserId && !password) {
alert('请输入密码');
return;
}
const permissions = [];
document.querySelectorAll('#permissionContainer input[type="checkbox"]:checked').forEach(cb => {
permissions.push(cb.value);
});
const userData = {
username: username,
isEnabled: isEnabled,
permissions: permissions,
roleId: roleId
};
if (!currentUserId) {
userData.password = password;
}
const url = currentUserId ? `/api/users/${currentUserId}` : '/api/users';
const method = currentUserId ? 'PUT' : 'POST';
fetch(url, {
method: method,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(userData)
})
.then(async res => {
if (res.ok) {
closeModal('userModal');
loadUsers();
} else {
const err = await res.json();
alert('保存失败: ' + (err.error || '未知错误'));
}
})
.catch(err => {
console.error(err);
alert('请求失败');
});
}
function deleteUser(id) {
if (!confirm('确定要删除该账户吗?')) return;
fetch(`/api/users/${id}`, {
method: 'DELETE'
})
.then(async res => {
if (res.ok) {
loadUsers();
} else {
const err = await res.json();
alert('删除失败: ' + (err.error || '未知错误'));
}
})
.catch(err => {
console.error(err);
alert('请求失败');
});
}
function resetPassword(id) {
const newPassword = prompt('请输入新密码:');
if (newPassword === null) return;
if (!newPassword.trim()) {
alert('密码不能为空');
return;
}
fetch(`/api/users/${id}/reset-password`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ newPassword: newPassword })
})
.then(async res => {
if (res.ok) {
alert('密码重置成功');
} else {
const err = await res.json();
alert('重置失败: ' + (err.error || '未知错误'));
}
})
.catch(err => {
console.error(err);
alert('请求失败');
});
}
// Close modal when clicking outside
window.onclick = function(event) {
const modal = document.getElementById('userModal');
if (event.target === modal) {
closeModal('userModal');
}
}
</script>
</body>
</html>