mirror of
https://gitee.com/xiarenalofs/squad-rain-ops-mini.git
synced 2026-08-06 21:36:25 +08:00
607 lines
21 KiB
HTML
607 lines
21 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;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
textarea.form-control {
|
|
resize: vertical;
|
|
min-height: 80px;
|
|
}
|
|
|
|
.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="/user-manager.html" class="btn btn-secondary btn-sm" style="margin-right: 10px;"><i class="fas fa-arrow-left"></i> 返回子账户</a>
|
|
<i class="fas fa-user-tag"></i> 角色管理
|
|
</div>
|
|
<button class="btn btn-primary" onclick="openAddModal()"><i class="fas fa-plus"></i> 新增角色</button>
|
|
</div>
|
|
|
|
<div class="container">
|
|
<table id="roleTable">
|
|
<thead>
|
|
<tr>
|
|
<th>角色名称</th>
|
|
<th>描述</th>
|
|
<th>权限数量</th>
|
|
<th>创建时间</th>
|
|
<th>操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<!-- Roles will be loaded here -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Edit/Add Modal -->
|
|
<div id="roleModal" class="modal">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<span id="modalTitle">新增角色</span>
|
|
<span class="close-btn" onclick="closeModal('roleModal')">×</span>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>角色名称</label>
|
|
<input type="text" id="roleName" class="form-control" placeholder="例如:管理员">
|
|
</div>
|
|
<div class="form-group">
|
|
<label>描述</label>
|
|
<textarea id="roleDescription" class="form-control" placeholder="角色职责描述..."></textarea>
|
|
</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('roleModal')">取消</button>
|
|
<button class="btn btn-primary" onclick="saveRole()">保存</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 = 'roles.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 "roles.view" required.</div>';
|
|
throw new Error("Access Denied");
|
|
}
|
|
})();
|
|
|
|
let currentRoleId = null;
|
|
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();
|
|
|
|
// Button permissions
|
|
if (!checkPermission('roles.add')) {
|
|
const btn = document.querySelector('button[onclick="openAddModal()"]');
|
|
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);
|
|
}
|
|
}
|
|
|
|
function loadRoles() {
|
|
fetch('/api/roles')
|
|
.then(res => {
|
|
if (res.status === 401) {
|
|
alert('无权访问');
|
|
window.location.href = '/login.html';
|
|
return [];
|
|
}
|
|
return res.json();
|
|
})
|
|
.then(roles => {
|
|
allRoles = roles || [];
|
|
renderTable(allRoles);
|
|
})
|
|
.catch(err => console.error(err));
|
|
}
|
|
|
|
function renderTable(roles) {
|
|
const tbody = document.querySelector('#roleTable tbody');
|
|
tbody.innerHTML = '';
|
|
|
|
const canEdit = checkPermission('roles.edit');
|
|
const canDelete = checkPermission('roles.delete');
|
|
|
|
roles.forEach(role => {
|
|
const tr = document.createElement('tr');
|
|
let actions = '';
|
|
if (canEdit) {
|
|
actions += `<button class="btn btn-primary btn-sm" onclick="openEditModal('${role.id}')"><i class="fas fa-edit"></i> 编辑</button>`;
|
|
}
|
|
if (canDelete) {
|
|
actions += `<button class="btn btn-danger btn-sm" onclick="deleteRole('${role.id}')"><i class="fas fa-trash"></i> 删除</button>`;
|
|
}
|
|
|
|
tr.innerHTML = `
|
|
<td>${role.name}</td>
|
|
<td>${role.description || '-'}</td>
|
|
<td><span class="status-badge" style="background:#eef;color:#448;">${role.permissions ? role.permissions.length : 0} 项权限</span></td>
|
|
<td>${new Date(role.createdAt).toLocaleString()}</td>
|
|
<td class="actions-cell">
|
|
${actions}
|
|
</td>
|
|
`;
|
|
tbody.appendChild(tr);
|
|
});
|
|
}
|
|
|
|
function openAddModal() {
|
|
currentRoleId = null;
|
|
document.getElementById('modalTitle').innerText = '新增角色';
|
|
document.getElementById('roleName').value = '';
|
|
document.getElementById('roleDescription').value = '';
|
|
|
|
// Clear checkboxes
|
|
document.querySelectorAll('.permissions-grid input[type="checkbox"]').forEach(cb => cb.checked = false);
|
|
|
|
document.getElementById('roleModal').classList.add('active');
|
|
}
|
|
|
|
function openEditModal(id) {
|
|
const role = allRoles.find(r => r.id === id);
|
|
if (!role) return;
|
|
|
|
currentRoleId = id;
|
|
document.getElementById('modalTitle').innerText = '编辑角色';
|
|
document.getElementById('roleName').value = role.name;
|
|
document.getElementById('roleDescription').value = role.description;
|
|
|
|
// Set checkboxes
|
|
document.querySelectorAll('.permissions-grid input[type="checkbox"]').forEach(cb => {
|
|
cb.checked = role.permissions && role.permissions.includes(cb.value);
|
|
});
|
|
|
|
document.getElementById('roleModal').classList.add('active');
|
|
}
|
|
|
|
function closeModal(modalId) {
|
|
document.getElementById(modalId).classList.remove('active');
|
|
}
|
|
|
|
function toggleSelectAll(source) {
|
|
const checkboxes = document.querySelectorAll('.permissions-grid input[type="checkbox"]');
|
|
checkboxes.forEach(cb => cb.checked = source.checked);
|
|
}
|
|
|
|
function saveRole() {
|
|
const name = document.getElementById('roleName').value;
|
|
const description = document.getElementById('roleDescription').value;
|
|
|
|
if (!name) {
|
|
alert('请输入角色名称');
|
|
return;
|
|
}
|
|
|
|
const permissions = [];
|
|
document.querySelectorAll('.permissions-grid input[type="checkbox"]:checked').forEach(cb => {
|
|
permissions.push(cb.value);
|
|
});
|
|
|
|
const roleData = {
|
|
name: name,
|
|
description: description,
|
|
permissions: permissions
|
|
};
|
|
|
|
const url = currentRoleId ? `/api/roles/${currentRoleId}` : '/api/roles';
|
|
const method = currentRoleId ? 'PUT' : 'POST';
|
|
|
|
fetch(url, {
|
|
method: method,
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(roleData)
|
|
})
|
|
.then(async res => {
|
|
if (res.ok) {
|
|
closeModal('roleModal');
|
|
loadRoles();
|
|
} else {
|
|
const err = await res.json();
|
|
alert('保存失败: ' + (err.error || '未知错误'));
|
|
}
|
|
})
|
|
.catch(err => {
|
|
console.error(err);
|
|
alert('请求失败');
|
|
});
|
|
}
|
|
|
|
function deleteRole(id) {
|
|
if (!confirm('确定要删除该角色吗?')) return;
|
|
|
|
fetch(`/api/roles/${id}`, {
|
|
method: 'DELETE'
|
|
})
|
|
.then(async res => {
|
|
if (res.ok) {
|
|
loadRoles();
|
|
} 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('roleModal');
|
|
if (event.target === modal) {
|
|
closeModal('roleModal');
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html> |