/** * ============================================= * 权限管理模块 - Layui版本 * Auth + RBAC 权限控制完整实现 * ============================================= * * 使用说明: * 1. 放置在layui的modules目录下 * 2. 使用:layui.use('permission', function(mod){...}) * 3. 或者直接:var permission = layui.permission; * * @version 2.0.0 - Layui Module * @author Permission Module * @date 2024-01-01 */ layui.define(function (exports) { 'use strict'; /** * ============================================= * 权限配置管理器 * ============================================= */ const PermissionConfig = (function () { // 私有变量 const _defaultToolbar = { create: { title: '创建资源', layEvent: 'dataCreate', icon: 'layui-icon-add-1', permission: 'create', color: 'layui-btn-normal', size: '', show: true }, recycle: { title: '回收站', layEvent: 'dataRecycle', icon: 'layui-icon-refresh', permission: 'recycle', color: 'layui-btn-warm', size: '', show: true }, filter: { title: '筛选', layEvent: 'filter', icon: 'layui-icon-search', permission: 'filter', color: 'layui-btn-primary', size: '', show: true }, export: { title: '导出', layEvent: 'export', icon: 'layui-icon-download-circle', permission: 'export', color: 'layui-btn-primary', size: '', show: true }, print: { title: '打印', layEvent: 'print', icon: 'layui-icon-print', permission: 'print', color: 'layui-btn-primary', size: '', show: true }, refresh: { title: '刷新', layEvent: 'refresh', icon: 'layui-icon-refresh', permission: 'refresh', color: 'layui-btn-primary', size: '', show: true }, batchDelete: { title: '批量删除', layEvent: 'batchDelete', icon: 'layui-icon-delete', permission: 'batch_delete', color: 'layui-btn-danger', size: '', show: true }, import: { title: '导入', layEvent: 'import', icon: 'layui-icon-upload-circle', permission: 'import', color: 'layui-btn-primary', size: '', show: true } }; const _rowToolbar = { view: { title: '查看', layEvent: 'dataView', icon: 'layui-icon-eye', permission: 'view', color: 'layui-btn-primary layui-btn-xs', size: 'layui-btn-xs', show: true }, edit: { title: '编辑', layEvent: 'dataEdit', icon: 'layui-icon-edit', permission: 'edit', color: 'layui-btn-normal layui-btn-xs', size: 'layui-btn-xs', show: true }, delete: { title: '删除', layEvent: 'dataDelete', icon: 'layui-icon-delete', permission: 'delete', color: 'layui-btn-danger layui-btn-xs', size: 'layui-btn-xs', show: true }, copy: { title: '复制', layEvent: 'dataCopy', icon: 'layui-icon-file-b', permission: 'copy', color: 'layui-btn-warm layui-btn-xs', size: 'layui-btn-xs', show: true }, enable: { title: '启用', layEvent: 'dataEnable', icon: 'layui-icon-play', permission: 'enable', color: 'layui-btn-normal layui-btn-xs', size: 'layui-btn-xs', show: true }, disable: { title: '禁用', layEvent: 'dataDisable', icon: 'layui-icon-pause', permission: 'disable', color: 'layui-btn-warm layui-btn-xs', size: 'layui-btn-xs', show: true }, audit: { title: '审核', layEvent: 'dataAudit', icon: 'layui-icon-ok-circle', permission: 'audit', color: 'layui-btn-normal layui-btn-xs', size: 'layui-btn-xs', show: true }, unaudit: { title: '取消审核', layEvent: 'dataUnaudit', icon: 'layui-icon-close', permission: 'unaudit', color: 'layui-btn-warm layui-btn-xs', size: 'layui-btn-xs', show: true } }; const _customButtons = { toolbar: {}, row: {} }; // 工具函数 const _isArray = function (obj) { return Object.prototype.toString.call(obj) === '[object Array]'; }; // 公共方法 return { /** * 获取默认工具条配置 */ getDefaultToolbar: function () { return Object.assign({}, _defaultToolbar); }, /** * 获取行工具条配置 */ getRowToolbar: function () { return Object.assign({}, _rowToolbar); }, /** * 注册自定义按钮 * @param {String} type - 按钮类型 (toolbar/row) * @param {String} key - 按钮唯一标识 * @param {Object} config - 按钮配置 */ registerButton: function (type, key, config) { if (!['toolbar', 'row'].includes(type)) { console.error('按钮类型必须是 toolbar 或 row'); return false; } if (!_customButtons[type]) { _customButtons[type] = {}; } _customButtons[type][key] = Object.assign({ title: key, layEvent: key, icon: 'layui-icon-more', permission: key, color: 'layui-btn-primary', size: '', show: true }, config); return true; }, /** * 获取工具条配置 * @param {String} type - toolbar/row * @param {Array} keys - 需要的按钮key列表 * @param {Object} customConfig - 自定义配置(覆盖默认配置) * @returns {Array} 配置数组 */ getToolbarConfig: function (type, keys = [], customConfig = {}) { const baseConfig = type === 'toolbar' ? _defaultToolbar : _rowToolbar; const customBtns = _customButtons[type] || {}; // 合并基础配置和自定义配置 const allConfig = Object.assign({}, baseConfig, customBtns); // 应用自定义配置覆盖 Object.keys(customConfig).forEach(key => { if (allConfig[key]) { allConfig[key] = Object.assign(allConfig[key], customConfig[key]); } }); // 如果指定了keys,只返回指定的按钮 if (keys.length > 0) { return keys.map(key => allConfig[key]).filter(Boolean); } // 返回所有配置 return Object.values(allConfig).filter(btn => btn.show !== false); }, /** * 获取单个按钮配置 * @param {String} type - toolbar/row * @param {String} key - 按钮key * @returns {Object} 按钮配置 */ getButtonConfig: function (type, key) { const config = this.getToolbarConfig(type); return config.find(btn => btn.layEvent === key || (btn.permission && btn.permission.includes(key))); }, /** * 批量注册按钮 * @param {String} type - 按钮类型 * @param {Object} buttons - 按钮配置对象 */ registerButtons: function (type, buttons) { Object.keys(buttons).forEach(key => { this.registerButton(type, key, buttons[key]); }); }, /** * 清除自定义按钮 * @param {String} type - 按钮类型 * @param {String} key - 按钮key,不传则清除所有 */ clearCustomButtons: function (type, key) { if (key) { delete _customButtons[type]?.[key]; } else { _customButtons[type] = {}; } } }; })(); /** * ============================================= * 权限验证工具类 * ============================================= */ const Auth = (function () { // 私有变量 let _permissions = []; let _permissionCache = {}; let _user = null; let _controller = null; // 当前页面对应的控制器(用于裸动作 token 解析) // 工具函数 const _isArray = function (obj) { return Object.prototype.toString.call(obj) === '[object Array]'; }; const _isString = function (obj) { return typeof obj === 'string'; }; // 前端短词 → 后端动作 同义词(按钮配置常用 create,权限 key 用 add) const _synonyms = { create: 'add', recycle: 'recyclebin', batch_delete: 'delete', batchDelete: 'delete' }; const _splitPermissions = function (perm) { if (_isArray(perm)) return perm; if (_isString(perm)) return perm.split(',').map(p => p.trim()); return []; }; // 公共方法 return { /** * 初始化权限 * @param {Array} permissions - 用户权限列表 * @param {Object} user - 用户信息 */ init: function (permissions = [], user = null) { _permissions = permissions || []; _user = user; _controller = null; this.clearCache(); }, /** * 获取当前用户 * @returns {Object} 用户信息 */ getUser: function () { return _user; }, /** * 设置当前用户 * @param {Object} user - 用户信息 */ setUser: function (user) { _user = user; }, /** * 获取权限列表 * @returns {Array} 权限列表 */ getPermissions: function () { return _permissions; }, /** * 设置当前页面对应的控制器(用于裸动作 token 解析,如 has('view') → admin:view) */ setController: function (controller) { _controller = controller || null; this.clearCache(); return this; }, /** * 检查单个权限 * 支持: * - 精确 key:admin:edit * - 控制器通配:admin:*(覆盖该控制器下所有动作) * - 超级管理员:* / *:* * - 裸动作 token(结合 setController):view → admin:view,create → admin:add(同义词) * @param {String} permission - 权限标识 * @returns {Boolean} */ has: function (permission) { if (!permission) return true; // 使用缓存 if (_permissionCache[permission] !== undefined) { return _permissionCache[permission]; } const perms = _splitPermissions(permission); const result = perms.some(p => this._match(p)); _permissionCache[permission] = result; return result; }, /** * 单个权限标识匹配(内部) */ _match: function (p) { if (!p) return true; // 精确匹配 if (_permissions.indexOf(p) > -1) return true; // 超级管理员放行 if (_permissions.indexOf('*') > -1 || _permissions.indexOf('*:*') > -1) return true; // 完整 key:controller:action / controller:* if (p.indexOf(':') > -1) { const parts = p.split(':'); const ctrl = parts[0]; const act = parts[1]; if (act === '*') { return _permissions.indexOf(ctrl + ':*') > -1; } return _permissions.indexOf(ctrl + ':' + act) > -1 || _permissions.indexOf(ctrl + ':*') > -1; } // 裸动作 token:结合当前控制器解析(含同义词) const action = _synonyms[p] || p; if (_controller) { if (_permissions.indexOf(_controller + ':*') > -1) return true; if (_permissions.indexOf(_controller + ':' + action) > -1) return true; } return false; }, /** * 检查多个权限(满足一个即可) * @param {Array} permissions - 权限列表 * @returns {Boolean} */ hasAny: function (permissions) { if (!_isArray(permissions) || permissions.length === 0) return false; return permissions.some(p => this.has(p)); }, /** * 检查所有权限 * @param {Array} permissions - 权限列表 * @returns {Boolean} */ hasAll: function (permissions) { if (!_isArray(permissions) || permissions.length === 0) return false; return permissions.every(p => this.has(p)); }, /** * 检查权限并返回权限标识 * @param {String|Array} permissions - 权限标识 * @returns {String|Boolean} 有权限返回权限标识,无权限返回false */ check: function (permissions) { if (_isArray(permissions)) { for (let p of permissions) { if (this.has(p)) { return p; } } return false; } return this.has(permissions) ? permissions : false; }, /** * 过滤有权限的按钮 * @param {Array} buttons - 按钮配置数组 * @returns {Array} 有权限的按钮数组 */ filterButtons: function (buttons) { if (!_isArray(buttons)) return []; return buttons.filter(btn => { // 如果没有设置permission,默认显示 if (!btn.permission) return true; // 支持多个权限,用逗号分隔 const perms = _splitPermissions(btn.permission); return this.hasAny(perms); }); }, /** * 生成工具条HTML * @param {Array} buttons - 按钮配置数组 * @param {String} containerClass - 容器样式类 * @param {Object} options - 额外选项 * @returns {String} HTML字符串 */ renderToolbar: function (buttons, containerClass = 'layui-btn-container', options = {}) { const filteredButtons = this.filterButtons(buttons); if (filteredButtons.length === 0) return ''; console.log('🔧 渲染工具条 - 需要在项目中实现',filteredButtons); // const html = filteredButtons.map(btn => { // const color = btn.color || 'layui-btn-primary'; // const size = btn.size || ''; // const icon = btn.icon ? `` : ''; // const disabled = btn.disabled ? 'disabled' : ''; // const style = btn.style ? `style="${btn.style}"` : ''; // return ``; // }).join(''); // const containerStyle = options.containerStyle ? `style="${options.containerStyle}"` : ''; // return `
${html}
`; }, /** * 生成行工具条HTML * @param {Array} buttons - 按钮配置数组 * @param {Object} options - 额外选项 * @returns {String} HTML字符串 */ renderRowToolbar: function (buttons, options = {}) { const filteredButtons = this.filterButtons(buttons); if (filteredButtons.length === 0) return ''; const html = filteredButtons.map(btn => { const color = btn.color || 'layui-btn-primary layui-btn-xs'; const icon = btn.icon ? `` : ''; const disabled = btn.disabled ? 'disabled' : ''; const style = btn.style ? `style="${btn.style}"` : ''; return ` ${icon} `; }).join(''); return `
${html}
`; }, /** * 清除缓存 */ clearCache: function () { _permissionCache = {}; }, /** * 批量检查权限 * @param {Array} permissions - 权限列表 * @returns {Object} 权限检查结果对象 */ batchCheck: function (permissions) { if (!_isArray(permissions)) return {}; return permissions.reduce((result, perm) => { result[perm] = this.has(perm); return result; }, {}); }, /** * 预加载常用权限 * @param {Array} permissions - 常用权限列表 */ preload: function (permissions = ['create', 'edit', 'delete', 'view']) { permissions.forEach(perm => this.has(perm)); } }; })(); /** * ============================================= * 动态权限更新管理器 * ============================================= */ const PermissionManager = (function () { // 私有变量 const _listeners = []; let _refreshTimeout = null; // 公共方法 return { /** * 添加权限变更监听器 * @param {Function} callback - 回调函数 * @param {String} id - 监听器ID(用于移除) */ onPermissionChange: function (callback, id = null) { _listeners.push({ callback, id }); }, /** * 移除权限变更监听器 * @param {String} id - 监听器ID */ offPermissionChange: function (id) { const index = _listeners.findIndex(l => l.id === id); if (index !== -1) { _listeners.splice(index, 1); } }, /** * 触发权限变更 * @param {Array} newPermissions - 新的权限列表 * @param {Object} user - 用户信息 */ triggerChange: function (newPermissions, user = null) { // 更新权限 Auth.init(newPermissions, user); // 保存到localStorage if (typeof localStorage !== 'undefined') { localStorage.setItem('permissions', JSON.stringify(newPermissions)); if (user) { localStorage.setItem('user', JSON.stringify(user)); } } // 通知所有监听器 _listeners.forEach(({ callback }) => { try { callback(newPermissions, user); } catch (e) { console.error('权限变更监听器执行出错:', e); } }); // 防抖刷新表格 clearTimeout(_refreshTimeout); _refreshTimeout = setTimeout(() => { this.refreshTables(); }, 100); }, /** * 刷新所有表格 */ refreshTables: function () { console.log('🔄 刷新表格 - 需要在项目中实现'); // 触发自定义事件 const event = new CustomEvent('permissionChanged', { detail: { permissions: Auth.getPermissions() } }); if (typeof window !== 'undefined') { window.dispatchEvent(event); } }, /** * 从服务器同步最新权限 * @param {String} url - 权限接口地址 * @returns {Promise} 同步结果 */ syncPermissions: function (url = '/api/user/permissions') { // return new Promise((resolve, reject) => { // if (typeof $ !== 'undefined' && $.ajax) { // $.get(url, function (res) { // if (res.code === 200 || res.success) { // this.triggerChange(res.data.permissions || res.data, res.data.user); // resolve(true); // } else { // reject(new Error(res.msg || '同步权限失败')); // } // }.bind(this)).fail(function (xhr) { // reject(new Error('网络请求失败: ' + xhr.statusText)); // }); // } else if (typeof fetch !== 'undefined') { // fetch(url) // .then(res => res.json()) // .then(res => { // if (res.code === 200 || res.success) { // this.triggerChange(res.data.permissions || res.data, res.data.user); // resolve(true); // } else { // reject(new Error(res.msg || '同步权限失败')); // } // }) // .catch(err => reject(err)); // } else { // reject(new Error('不支持的HTTP请求方式')); // } // }.bind(this)); }, /** * 清除所有监听器 */ clearListeners: function () { _listeners.length = 0; } }; })(); /** * ============================================= * 权限按钮组件 * ============================================= */ class AuthButton extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); this._clickHandler = this._handleClick.bind(this); } static get observedAttributes() { return ['permission', 'text', 'icon', 'type', 'size', 'disabled']; } connectedCallback() { this.render(); } disconnectedCallback() { this.shadowRoot.removeEventListener('click', this._clickHandler); } attributeChangedCallback(name, oldValue, newValue) { if (oldValue !== newValue) { this.render(); } } _handleClick(e) { if (!this.hasAttribute('disabled')) { this.dispatchEvent(new CustomEvent('auth-click', { bubbles: true, composed: true, detail: { button: this } })); } } render() { const permission = this.getAttribute('permission'); const text = this.getAttribute('text') || '按钮'; const icon = this.getAttribute('icon') || ''; const type = this.getAttribute('type') || 'primary'; const size = this.getAttribute('size') || ''; const disabled = this.hasAttribute('disabled'); const hasPermission = !permission || Auth.has(permission); // 无权限时隐藏 if (!hasPermission) { this.shadowRoot.innerHTML = ''; this.style.display = 'none'; return; } this.style.display = ''; const iconHtml = icon ? `` : ''; const btnClass = `layui-btn layui-btn-${type} ${size}`; const disabledAttr = disabled ? 'disabled' : ''; this.shadowRoot.innerHTML = ` `; // 添加点击事件 const btn = this.shadowRoot.querySelector('button'); btn.removeEventListener('click', this._clickHandler); btn.addEventListener('click', this._clickHandler); } } // 注册自定义元素 if (typeof customElements !== 'undefined' && !customElements.get('auth-button')) { customElements.define('auth-button', AuthButton); } /** * ============================================= * 导出模块 * ============================================= */ exports('auth', { // 核心对象auth Auth: Auth, PermissionConfig: PermissionConfig, PermissionManager: PermissionManager, // 便捷方法(直接暴露常用方法) init: Auth.init.bind(Auth), setController: Auth.setController.bind(Auth), has: Auth.has.bind(Auth), hasAny: Auth.hasAny.bind(Auth), hasAll: Auth.hasAll.bind(Auth), getPermissions: Auth.getPermissions.bind(Auth), renderToolbar: Auth.renderToolbar.bind(Auth), renderRowToolbar: Auth.renderRowToolbar.bind(Auth), filterButtons: Auth.filterButtons.bind(Auth), // 配置方法 getToolbarConfig: PermissionConfig.getToolbarConfig.bind(PermissionConfig), registerButton: PermissionConfig.registerButton.bind(PermissionConfig), registerButtons: PermissionConfig.registerButtons.bind(PermissionConfig), // 管理器方法 onPermissionChange: PermissionManager.onPermissionChange.bind(PermissionManager), triggerChange: PermissionManager.triggerChange.bind(PermissionManager), syncPermissions: PermissionManager.syncPermissions.bind(PermissionManager), // 按钮组件 AuthButton: AuthButton }); });