dm-design/平台web/permission-manager.html

916 lines
28 KiB
HTML
Raw 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.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>权限管理系统</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f5f7fa;
color: #333;
height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #2c3e50, #3498db);
color: #fff;
padding: 20px 30px;
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 {
font-size: 1.8rem;
font-weight: 600;
}
.header-actions {
display: flex;
gap: 10px;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 5px;
}
.btn-primary {
background-color: #3498db;
color: #fff;
}
.btn-primary:hover {
background-color: #2980b9;
transform: translateY(-2px);
}
.btn-success {
background-color: #27ae60;
color: #fff;
}
.btn-success:hover {
background-color: #229954;
}
.btn-danger {
background-color: #e74c3c;
color: #fff;
}
.btn-danger:hover {
background-color: #c0392b;
}
.btn-warning {
background-color: #f39c12;
color: #fff;
}
.btn-warning:hover {
background-color: #e67e22;
}
/* 搜索栏 */
.search-bar {
padding: 20px 30px;
background-color: #f8f9fa;
border-bottom: 1px solid #dee2e6;
}
.search-form {
display: flex;
gap: 15px;
align-items: center;
}
.search-input {
flex: 1;
padding: 10px 15px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 14px;
}
.search-input:focus {
outline: none;
border-color: #3498db;
box-shadow: 0 0 5px rgba(52, 152, 219, 0.3);
}
/* 表格样式 */
.table-container {
padding: 0 30px 30px 30px;
overflow-x: auto;
}
.permissions-table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
background-color: #fff;
}
.permissions-table th,
.permissions-table td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #eee;
}
.permissions-table th {
background-color: #f8f9fa;
font-weight: 600;
color: #2c3e50;
position: sticky;
top: 0;
z-index: 10;
}
.permissions-table tr:hover {
background-color: #f8f9fa;
}
.permission-row {
cursor: pointer;
transition: background-color 0.2s ease;
}
.permission-row.parent-row {
font-weight: 500;
}
.permission-row.child-row {
background-color: #fafbfc;
}
.permission-row.child-row td:first-child {
padding-left: 45px;
position: relative;
}
.permission-row.child-row td:first-child::before {
content: "└─";
position: absolute;
left: 25px;
color: #bdc3c7;
}
.permission-code {
font-family: monospace;
background-color: #ecf0f1;
padding: 2px 6px;
border-radius: 3px;
font-size: 13px;
}
.permission-type {
padding: 4px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
}
.type-menu {
background-color: #3498db;
color: #fff;
}
.type-button {
background-color: #27ae60;
color: #fff;
}
.expand-btn {
background: none;
border: none;
cursor: pointer;
padding: 2px 5px;
margin-right: 8px;
color: #3498db;
font-size: 12px;
transition: transform 0.3s ease;
}
.expand-btn.collapsed {
transform: rotate(-90deg);
}
.actions {
display: flex;
gap: 5px;
}
.btn-sm {
padding: 5px 10px;
font-size: 12px;
}
/* 弹窗样式 */
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
}
.modal.show {
display: flex;
align-items: center;
justify-content: center;
}
.modal-content {
background-color: #fff;
border-radius: 8px;
width: 500px;
max-width: 90vw;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
transform: scale(0.8);
transition: transform 0.3s ease;
}
.modal.show .modal-content {
transform: scale(1);
}
.modal-header {
background: linear-gradient(135deg, #2c3e50, #3498db);
color: #fff;
padding: 20px 25px;
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-header h3 {
font-size: 1.2rem;
font-weight: 600;
}
.close {
background: none;
border: none;
color: #fff;
font-size: 24px;
cursor: pointer;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: background-color 0.3s ease;
}
.close:hover {
background-color: rgba(255, 255, 255, 0.2);
}
.modal-body {
padding: 25px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #2c3e50;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 10px 15px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 14px;
transition: border-color 0.3s ease;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: #3498db;
box-shadow: 0 0 5px rgba(52, 152, 219, 0.3);
}
.form-group textarea {
resize: vertical;
min-height: 80px;
}
.modal-footer {
background-color: #f8f9fa;
padding: 15px 25px;
display: flex;
justify-content: flex-end;
gap: 10px;
border-top: 1px solid #dee2e6;
}
/* 右键菜单样式 */
.context-menu {
position: fixed;
background-color: #fff;
border: 1px solid #ddd;
border-radius: 5px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
z-index: 2000;
padding: 5px 0;
min-width: 120px;
display: none;
}
.context-menu-item {
padding: 8px 15px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.2s ease;
display: flex;
align-items: center;
gap: 8px;
}
.context-menu-item:hover {
background-color: #f8f9fa;
}
/* 空状态样式 */
.empty-state {
text-align: center;
padding: 60px 20px;
color: #7f8c8d;
}
.empty-state h3 {
margin-bottom: 10px;
color: #95a5a6;
}
/* 响应式设计 */
@media (max-width: 768px) {
.container {
margin: 10px;
border-radius: 0;
}
.header {
padding: 15px 20px;
flex-direction: column;
gap: 15px;
}
.search-bar {
padding: 15px 20px;
}
.table-container {
padding: 0 20px 20px 20px;
}
.permissions-table {
font-size: 14px;
}
.permissions-table th,
.permissions-table td {
padding: 8px 10px;
}
.modal-content {
width: 95vw;
margin: 10px;
}
}
</style>
</head>
<body>
<div class="container">
<!-- 头部 -->
<div class="header">
<h1>权限管理系统</h1>
<div class="header-actions">
<button class="btn btn-success" onclick="openAddModal()">
新增权限
</button>
</div>
</div>
<!-- 搜索栏 -->
<div class="search-bar">
<div class="search-form">
<input type="text" class="search-input" id="searchInput" placeholder="搜索权限编码、名称或描述...">
<button class="btn btn-primary" onclick="searchPermissions()">🔍 搜索</button>
<button class="btn btn-warning" onclick="resetSearch()">🔄 重置</button>
</div>
</div>
<!-- 表格容器 -->
<div class="table-container">
<table class="permissions-table">
<thead>
<tr>
<th>权限编码</th>
<th>权限名称</th>
<th>权限描述</th>
<th>权限类型</th>
<th>父级权限</th>
<th>操作</th>
</tr>
</thead>
<tbody id="permissionsTableBody">
<!-- 数据将通过JavaScript动态加载 -->
</tbody>
</table>
<div class="empty-state" id="emptyState" style="display: none;">
<h3>暂无权限数据</h3>
<p>点击"新增权限"按钮开始添加权限信息</p>
</div>
</div>
</div>
<!-- 权限弹窗 -->
<div id="permissionModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3 id="modalTitle">新增权限</h3>
<button type="button" class="close" onclick="closeModal()">&times;</button>
</div>
<div class="modal-body">
<form id="permissionForm">
<div class="form-group">
<label for="permissionCode">权限编码 *</label>
<input type="text" id="permissionCode" name="code" required placeholder="请输入全局唯一的权限编码">
</div>
<div class="form-group">
<label for="permissionName">权限名称 *</label>
<input type="text" id="permissionName" name="name" required placeholder="请输入权限名称">
</div>
<div class="form-group">
<label for="permissionDesc">权限描述</label>
<textarea id="permissionDesc" name="description" placeholder="请输入权限描述(可选)"></textarea>
</div>
<div class="form-group">
<label for="parentCode">父级权限编码</label>
<select id="parentCode" name="parentCode">
<option value="">无父级权限</option>
<!-- 选项将通过JavaScript动态加载 -->
</select>
</div>
<div class="form-group">
<label for="permissionType">权限类型 *</label>
<select id="permissionType" name="type" required>
<option value="">请选择权限类型</option>
<option value="menu">菜单</option>
<option value="button">按钮</option>
</select>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-warning" onclick="closeModal()">取消</button>
<button type="button" class="btn btn-success" onclick="savePermission()">保存</button>
</div>
</div>
</div>
<!-- 右键菜单 -->
<div id="contextMenu" class="context-menu">
<div class="context-menu-item" onclick="addChildPermission()">
添加子权限
</div>
<div class="context-menu-item" onclick="editPermission()">
✏️ 编辑权限
</div>
<div class="context-menu-item" onclick="deletePermission()">
🗑️ 删除权限
</div>
</div>
<script>
// 权限数据存储
let permissions = [];
let currentEditingId = null;
let contextMenuTargetRow = null;
// 初始化示例数据
function initSampleData() {
permissions = [
{
id: 1,
code: 'SYSTEM',
name: '系统管理',
description: '系统管理模块',
parentCode: '',
type: 'menu'
},
{
id: 2,
code: 'SYSTEM_USER',
name: '用户管理',
description: '用户管理功能',
parentCode: 'SYSTEM',
type: 'menu'
},
{
id: 3,
code: 'SYSTEM_USER_ADD',
name: '添加用户',
description: '添加新用户按钮',
parentCode: 'SYSTEM_USER',
type: 'button'
},
{
id: 4,
code: 'SYSTEM_USER_EDIT',
name: '编辑用户',
description: '编辑用户信息按钮',
parentCode: 'SYSTEM_USER',
type: 'button'
},
{
id: 5,
code: 'SYSTEM_ROLE',
name: '角色管理',
description: '角色管理功能',
parentCode: 'SYSTEM',
type: 'menu'
},
{
id: 6,
code: 'CONTENT',
name: '内容管理',
description: '内容管理模块',
parentCode: '',
type: 'menu'
}
];
renderPermissionsTable();
}
// 渲染权限表格
function renderPermissionsTable(data = permissions) {
const tableBody = document.getElementById('permissionsTableBody');
const emptyState = document.getElementById('emptyState');
if (data.length === 0) {
tableBody.innerHTML = '';
emptyState.style.display = 'block';
return;
}
emptyState.style.display = 'none';
// 构建树状结构
const tree = buildPermissionTree(data);
let html = '';
tree.forEach(item => {
html += renderPermissionRow(item, 0);
});
tableBody.innerHTML = html;
}
// 构建权限树
function buildPermissionTree(data) {
const map = {};
const roots = [];
// 创建映射
data.forEach(item => {
map[item.code] = { ...item, children: [] };
});
// 构建树结构
data.forEach(item => {
if (item.parentCode && map[item.parentCode]) {
map[item.parentCode].children.push(map[item.code]);
} else {
roots.push(map[item.code]);
}
});
return roots;
}
// 渲染权限行
function renderPermissionRow(item, level) {
const hasChildren = item.children && item.children.length > 0;
const isParent = level === 0;
const parentClass = isParent ? 'parent-row' : 'child-row';
let html = `
<tr class="permission-row ${parentClass}"
data-id="${item.id}"
data-code="${item.code}"
oncontextmenu="showContextMenu(event, this)">
<td>
${hasChildren ? `<button class="expand-btn" onclick="toggleChildren(this)">▼</button>` : ''}
<span class="permission-code">${item.code}</span>
</td>
<td>${item.name}</td>
<td>${item.description || '-'}</td>
<td><span class="permission-type type-${item.type}">${item.type === 'menu' ? '菜单' : '按钮'}</span></td>
<td>${item.parentCode || '-'}</td>
<td class="actions">
<button class="btn btn-primary btn-sm" onclick="editPermissionById(${item.id})">编辑</button>
<button class="btn btn-danger btn-sm" onclick="deletePermissionById(${item.id})">删除</button>
</td>
</tr>
`;
// 添加子权限行
if (hasChildren) {
item.children.forEach(child => {
html += renderPermissionRow(child, level + 1);
});
}
return html;
}
// 展开/收起子权限
function toggleChildren(btn) {
const row = btn.closest('tr');
const code = row.dataset.code;
let nextRow = row.nextElementSibling;
const isExpanded = btn.textContent === '▼';
btn.textContent = isExpanded ? '▶' : '▼';
btn.classList.toggle('collapsed', isExpanded);
// 切换所有子行的显示状态
while (nextRow && nextRow.classList.contains('child-row')) {
nextRow.style.display = isExpanded ? 'none' : 'table-row';
nextRow = nextRow.nextElementSibling;
}
}
// 显示右键菜单
function showContextMenu(event, row) {
event.preventDefault();
contextMenuTargetRow = row;
const menu = document.getElementById('contextMenu');
menu.style.display = 'block';
menu.style.left = event.pageX + 'px';
menu.style.top = event.pageY + 'px';
}
// 隐藏右键菜单
function hideContextMenu() {
document.getElementById('contextMenu').style.display = 'none';
contextMenuTargetRow = null;
}
// 点击其他地方隐藏右键菜单
document.addEventListener('click', hideContextMenu);
// 打开新增弹窗
function openAddModal() {
currentEditingId = null;
document.getElementById('modalTitle').textContent = '新增权限';
document.getElementById('permissionForm').reset();
updateParentCodeOptions();
document.getElementById('permissionModal').classList.add('show');
}
// 添加子权限
function addChildPermission() {
if (!contextMenuTargetRow) return;
const parentCode = contextMenuTargetRow.dataset.code;
openAddModal();
document.getElementById('parentCode').value = parentCode;
hideContextMenu();
}
// 编辑权限
function editPermission() {
if (!contextMenuTargetRow) return;
const id = parseInt(contextMenuTargetRow.dataset.id);
editPermissionById(id);
hideContextMenu();
}
// 根据ID编辑权限
function editPermissionById(id) {
const permission = permissions.find(p => p.id === id);
if (!permission) return;
currentEditingId = id;
document.getElementById('modalTitle').textContent = '编辑权限';
document.getElementById('permissionCode').value = permission.code;
document.getElementById('permissionName').value = permission.name;
document.getElementById('permissionDesc').value = permission.description || '';
document.getElementById('permissionType').value = permission.type;
updateParentCodeOptions(permission.code);
document.getElementById('parentCode').value = permission.parentCode || '';
document.getElementById('permissionModal').classList.add('show');
}
// 删除权限
function deletePermission() {
if (!contextMenuTargetRow) return;
const id = parseInt(contextMenuTargetRow.dataset.id);
deletePermissionById(id);
hideContextMenu();
}
// 根据ID删除权限
function deletePermissionById(id) {
const permission = permissions.find(p => p.id === id);
if (!permission) return;
// 检查是否有子权限
const hasChildren = permissions.some(p => p.parentCode === permission.code);
if (hasChildren) {
alert('该权限下有子权限,请先删除子权限!');
return;
}
if (confirm(`确定要删除权限"${permission.name}"吗?`)) {
permissions = permissions.filter(p => p.id !== id);
renderPermissionsTable();
}
}
// 更新父级权限选项
function updateParentCodeOptions(excludeCode = '') {
const select = document.getElementById('parentCode');
select.innerHTML = '<option value="">无父级权限</option>';
permissions.forEach(permission => {
if (permission.code !== excludeCode && permission.type === 'menu') {
const option = document.createElement('option');
option.value = permission.code;
option.textContent = `${permission.code} (${permission.name})`;
select.appendChild(option);
}
});
}
// 保存权限
function savePermission() {
const form = document.getElementById('permissionForm');
const formData = new FormData(form);
const code = formData.get('code').trim();
const name = formData.get('name').trim();
const description = formData.get('description').trim();
const parentCode = formData.get('parentCode');
const type = formData.get('type');
// 验证
if (!code || !name || !type) {
alert('请填写必填项!');
return;
}
// 检查权限编码是否重复(编辑时排除自身)
const existingPermission = permissions.find(p => p.code === code && p.id !== currentEditingId);
if (existingPermission) {
alert('权限编码已存在!');
return;
}
// 验证父级权限
if (parentCode) {
const parentPermission = permissions.find(p => p.code === parentCode);
if (!parentPermission) {
alert('父级权限不存在!');
return;
}
if (parentPermission.type !== 'menu') {
alert('只能选择菜单类型的权限作为父级权限!');
return;
}
}
const permissionData = {
code,
name,
description,
parentCode: parentCode || '',
type
};
if (currentEditingId) {
// 编辑权限
const index = permissions.findIndex(p => p.id === currentEditingId);
if (index !== -1) {
permissions[index] = { ...permissions[index], ...permissionData };
}
} else {
// 新增权限
const newId = Math.max(...permissions.map(p => p.id), 0) + 1;
permissions.push({ id: newId, ...permissionData });
}
closeModal();
renderPermissionsTable();
}
// 关闭弹窗
function closeModal() {
document.getElementById('permissionModal').classList.remove('show');
currentEditingId = null;
}
// 搜索权限
function searchPermissions() {
const keyword = document.getElementById('searchInput').value.trim().toLowerCase();
if (!keyword) {
renderPermissionsTable();
return;
}
const filteredPermissions = permissions.filter(permission =>
permission.code.toLowerCase().includes(keyword) ||
permission.name.toLowerCase().includes(keyword) ||
(permission.description && permission.description.toLowerCase().includes(keyword))
);
renderPermissionsTable(filteredPermissions);
}
// 重置搜索
function resetSearch() {
document.getElementById('searchInput').value = '';
renderPermissionsTable();
}
// 监听回车键搜索
document.getElementById('searchInput').addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
searchPermissions();
}
});
// 监听ESC键关闭弹窗
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeModal();
hideContextMenu();
}
});
// 阻止弹窗背景点击关闭
document.getElementById('permissionModal').addEventListener('click', function(e) {
if (e.target === this) {
closeModal();
}
});
// 初始化页面
document.addEventListener('DOMContentLoaded', function() {
initSampleData();
});
</script>
</body>
</html>