Files
YwxAppThink/public/assets/ywxapp/modules/auth.js
T
yangwx 18101cf11a feat: 上线前安全加固与发布工程化;appmall 由插件迁移为独立应用
安全(P0)
- 发布包签名:新增 scripts/package_keygen.php、scripts/package_verify.php、
  ywxapp\service\PackageVerifier;FrameworkService::apply() 解压前强制验签
  (ywxapp.framework_package_verify,默认开启)
- JWT 收紧:config/jwt.php 移除公开默认密钥,JwtService::assertStrongSecret()
  校验非空 / 长度≥32 / 已知弱值;.env.production.example 键名由 JWT_SECRET_KEY 修正为 JWT_SECRET
- /api/v1/Article:免登录白名单收窄为只读接口,写操作字段白名单 + assertOwner() 越权校验
- 开发者令牌:查找接口不再回显明文、统一失败文案 + 限流
- TemplateInstaller:文件名白名单 + realpath 归一化 + 目录内断言,修复 Zip Slip;补 use ZipArchive
- 后台登录:接入验证码、失败锁定与登录限流(config/ratelimit.php)
- 缓存 / Session 驱动可由 .env 覆盖(CACHE_DRIVER / SESSION_TYPE),集群部署改 redis
- paycenter 支付日志开关改为 PAY_LOG,不再跟随 APP_DEBUG

工程化 / 交付
- 新增 CHANGELOG.md 与 .env.production.example;composer.lock 入库锁定依赖版本
- 插件离线安装 CLI 增加 --uid,后台上传安装透传 uid,付费插件授权校验可通过
- 授权拒绝文案区分「未携带 uid」与「未找到有效授权」,便于排障
- .gitignore 放行 .env.production.example / .env.addon.example / .gitattributes / public/.htaccess

结构
- appmall 由 addon/appmall 迁移为独立应用 app/appmall(市场 / 开发者中心 / 审核 / 分润)
2026-10-22 00:53:39 +08:00

830 lines
33 KiB
JavaScript
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.
/**
* =============================================
* 权限管理模块 - 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 [];
};
/**
* 后端注入的权限集(app/backend/view/common/layout.html → layui.app.powers)
* 由 BackendBase::afterAuth() 赋值,是当前登录者权限的唯一可信来源。
*/
const _injectedPowers = function () {
return (typeof layui !== 'undefined' && layui.app && Array.isArray(layui.app.powers))
? layui.app.powers
: [];
};
// 公共方法
return {
/**
* 初始化权限
* @param {Array} permissions - 用户权限列表
* @param {Object} user - 用户信息
*/
init: function (permissions = [], user = null) {
// 权限源优先级:后端每页注入的 layui.app.powers > 调用方传入值。
//
// layui.data(...).permission 是登录那一刻写入的快照,管理员改动角色权限后
// 它不会更新,若以它为准就会出现「权限改了但按钮没变、要重新登录才生效」。
// 后端注入值每次页面加载都重新计算(后端不缓存权限),因此恒定优先。
// 仅在后端未注入(如脱离后台外壳的独立页面)时才使用调用方传入值。
const injected = _injectedPowers();
if (injected.length) {
permissions = injected;
}
_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);
});
},
/**
* 按权限过滤 DOM:移除容器内无权限的 [data-perm] 元素。
*
* 此前各后台页面都在表格 done 回调里重复书写同一段过滤逻辑(20+ 处),
* 统一收口到这里,行为与原来一致:无权限即 remove()。
*
* @param {Object|String|Element} container - jQuery 对象 / 选择器 / 原生元素;
* 不传则作用于整个文档
* @returns {Number} 被移除的元素个数
*/
filterDom: function (container) {
const self = this;
let roots = [];
if (container === undefined || container === null) {
roots = [document];
} else if (typeof container === 'string') {
roots = Array.prototype.slice.call(document.querySelectorAll(container));
} else if (container.jquery || (typeof container.length === 'number' && container.nodeType === undefined)) {
// jQuery 对象 / 类数组
roots = Array.prototype.slice.call(container);
} else {
roots = [container];
}
let removed = 0;
roots.forEach(function (root) {
if (!root) return;
// 容器自身也可能带 data-perm(如 $('.layui-btn[data-perm]'))
if (root.nodeType === 1) check(root);
Array.prototype.forEach.call(root.querySelectorAll('[data-perm]'), check);
});
return removed;
function check(el) {
const perm = el.getAttribute('data-perm');
if (!perm || self.has(perm)) return; // 无标注或有权限 → 保留
if (el.parentNode) {
el.parentNode.removeChild(el);
removed++;
}
}
},
/**
* 生成工具条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 ? `<i class="${btn.icon}"></i>` : '';
// const disabled = btn.disabled ? 'disabled' : '';
// const style = btn.style ? `style="${btn.style}"` : '';
// return `<button class="layui-btn ${color} ${size}" lay-event="${btn.layEvent}" title="${btn.title}" ${disabled} ${style}>
// ${icon}${btn.title}
// </button>`;
// }).join('');
// const containerStyle = options.containerStyle ? `style="${options.containerStyle}"` : '';
// return `<div class="${containerClass}" ${containerStyle}>${html}</div>`;
},
/**
* 生成行工具条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 ? `<i class="${btn.icon}"></i>` : '';
const disabled = btn.disabled ? 'disabled' : '';
const style = btn.style ? `style="${btn.style}"` : '';
return `<a class="layui-btn ${color}" lay-event="${btn.layEvent}" title="${btn.title}" ${disabled} ${style}>
${icon}
</a>`;
}).join('');
return `<div class="layui-btn-group">${html}</div>`;
},
/**
* 清除缓存
*/
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 ? `<i class="${icon}"></i>` : '';
const btnClass = `layui-btn layui-btn-${type} ${size}`;
const disabledAttr = disabled ? 'disabled' : '';
this.shadowRoot.innerHTML = `
<style>
:host {
display: inline-block;
}
.layui-btn {
display: inline-block;
height: 38px;
line-height: 38px;
padding: 0 18px;
background-color: #009688;
color: #fff;
white-space: nowrap;
text-align: center;
font-size: 14px;
border: none;
border-radius: 2px;
cursor: pointer;
box-sizing: border-box;
text-decoration: none;
}
.layui-btn:hover {
opacity: 0.9;
}
.layui-btn[disabled] {
opacity: 0.5;
cursor: not-allowed;
}
.layui-btn-xs {
height: 26px;
line-height: 26px;
padding: 0 10px;
font-size: 12px;
}
.layui-btn-sm {
height: 30px;
line-height: 30px;
padding: 0 10px;
font-size: 12px;
}
.layui-btn-lg {
height: 44px;
line-height: 44px;
padding: 0 25px;
font-size: 16px;
}
.layui-btn-primary {
background-color: #fff;
color: #333;
border: 1px solid #d2d2d2;
}
.layui-btn-normal {
background-color: #1E9FFF;
}
.layui-btn-warm {
background-color: #FFB800;
}
.layui-btn-danger {
background-color: #FF5722;
}
i {
margin-right: 5px;
}
</style>
<button class="${btnClass}" ${disabledAttr}>
${iconHtml}${text}
</button>
`;
// 添加点击事件
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);
}
/**
* 权限集自愈接管
* -------------------------------------------------------------------------
* 后端已把当前登录者的权限集注入 layui.app.powers
* (见 app/backend/view/common/layout.html,由 BackendBase::afterAuth() 赋值)。
*
* 此前没有任何地方调用 init(),_permissions 恒为空,auth.has() 永远返回 false,
* 于是所有带 [data-perm] 的按钮都被 filterButtons 无条件移除。
* 这里在模块加载时直接接管,不依赖调用方显式初始化,也与模块加载顺序无关。
*/
if (typeof layui !== 'undefined' && layui.app && Array.isArray(layui.app.powers)) {
Auth.init(layui.app.powers);
}
/**
* =============================================
* 导出模块
* =============================================
*/
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),
filterDom: Auth.filterDom.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
});
});