916 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			HTML
		
	
	
	
			
		
		
	
	
			916 lines
		
	
	
		
			28 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>
 | ||
|     <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()">×</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> |