时间:2021-05-26
演示
在年初开发一个中后台管理系统,功能涉及到了各个部门(产品、客服、市场等等),在开始的版本中,我和后端配合使用了花裤衩手摸手系列的权限方案,前期非常nice,但是慢慢的随着功能增多、业务越来越复杂,就变得有些吃力了,因为我们的权限动态性太大了
后端的配合:
routerName
有一些注意点:
实现
操作列表示例
以Restful风格接口为例
const operations = [ { url: '/xxx', type: 'get', name: '查询xxx', routeName: 'route1', // 接口对应的路由 opcode: 'XXX_GET' // 操作码,不变的 }, { url: '/xxx', type: 'post', name: '新增xxx', routeName: 'route1', opcode: 'XXX_POST' }, // ......]路由的变化
在路由的meta 中增加一个配置字段如requireOps ,值可能为String 或者Array ,这表示当前路由页面要显示的必要的操作码,Array 类型是为了处理一个路由页面需要满足同时存在多个操作权限时才显示的情况。若值不为这2种则视为无权限控制,任何用户都能访问
由于最终需要根据过滤后的权限路由动态生成菜单,所以还需要在路由选项中增加几个字段处理显示问题,其中hidden 优先级大于visible
hiddenvisibleconst permissionRoutes = [ { // visible: false, // hidden: true, path: '/xxx', name: 'route1', meta: { title: '路由1', requireOps: 'XXX_GET' }, // ... }]由于路由在前端维护,所以以上配置只能写死,如果后端能同意维护这一份路由表,那就可以有很多的发挥空间了,体验也能做的更好。
权限路由过滤
先将权限路由规范一下,同时保留一个副本,可能在可视化时需要用到
const routeMap = (routes, cb) => routes.map(route => { if (route.children && route.children.length > 0) { route.children = routeMap(route.children, cb) } return cb(route)})const hasRequireOps = ops => Array.isArray(ops) || typeof ops === 'string'const normalizeRequireOps = ops => hasRequireOps(ops) ? [].concat(...[ops]) : nullconst normalizeRouteMeta = route => { const meta = route.meta = { ...(route.meta || {}) } meta.requireOps = normalizeRequireOps(meta.requireOps) return route}permissionRoutes = routeMap(permissionRoutes, normalizeRouteMeta)const permissionRoutesCopy = JSON.parse(JSON.stringify(permissionRoutes))获取到操作列表后,只需要遍历权限路由,然后查询requireOps 代表的操作有没有在操作列表中。这里需要处理一下requireOps 未设置的情况,如果子路由中都是权限路由,需要为父级路由自动加上requireOps 值,不然当所有子路由都没有权限时,父级路由就被认为是无权限控制且可访问的;而如果子路由中只要有一个路由无权限控制,那就不需要处理父路由。所以这里可以用递归来解决,先处理子路由再处理父路由
const filterPermissionRoutes = (routes, cb) => { // 可能父路由没有设置requireOps 需要根据子路由确定父路由的requireOps routes.forEach(route => { if (route.children) { route.children = filterPermissionRoutes(route.children, cb) if (!route.meta.requireOps) { const hasNoPermission = route.children.some(child => child.meta.requireOps === null) // 如果子路由中存在不需要权限控制的路由,则跳过 if (!hasNoPermission) { route.meta.requireOps = [].concat(...route.children.map(child => child.meta.requireOps)) } } } }) return cb(routes)}然后根据操作列表对权限路由进行过滤
let operations = null // 从后端获取后更新它const hasOp = opcode => operations ? operations.some(op => op.opcode === opcode) : falseconst proutes = filterPermissionRoutes(permissionRoutes, routes => routes.filter(route => { const requireOps = route.meta.requireOps if (requireOps) { return requireOps.some(hasOp) } return true}))// 动态添加路由router.addRoutes(proutes)函数式组件控制局部权限
这个组件实现很简单,根据传入的操作码进行权限判断,若通过则返回插槽内容,否则返回null。另外,为了统一风格,支持一下root 属性,表示组件的根节点
const AccessControl = { functional: true, render (h, { data, children }) { const attrs = data.attrs || {} // 如果是root,直接透传 if (attrs.root !== undefined) { return h(attrs.root || 'div', data, children) } if (!attrs.opcode) { return h('span', { style: { color: 'red', fontSize: '30px' } }, '请配置操作码') } const opcodes = attrs.opcode.split(',') if (opcodes.some(hasOp)) { return children } return null }}动态生成权限菜单
以ElementUI为例,由于动态渲染需要进行递归,如果以文件组件的形式会多一层根组件,所以这里直接用render function简单写一个示例,可以根据自己的需求改造
// 权限菜单组件export const PermissionMenuTree = { name: 'MenuTree', props: { routes: { type: Array, required: true }, collapse: Boolean }, render (h) { const createMenuTree = (routes, parentPath = '') => routes.map(route => { // hidden: 为true时当前菜单和子菜单都不显示 if (route.hidden === true) { return null } // 子路径处理 const fullPath = route.path.charAt(0) === '/' ? route.path : `${parentPath}/${route.path}` // visible: 为false时不显示当前菜单,但显示子菜单 if (route.visible === false) { return createMenuTree(route.children, fullPath) } const title = route.meta.title const props = { index: fullPath, key: route.path } if (!route.children || route.children.length === 0) { return h( 'el-menu-item', { props }, [h('span', title)] ) } return h( 'el-submenu', { props }, [ h('span', { slot: 'title' }, title), ...createMenuTree(route.children, fullPath) ] ) }) return h( 'el-menu', { props: { collapse: this.collapse, router: true, defaultActive: this.$route.path } }, createMenuTree(this.routes) ) }}接口的权限控制
我们一般用axios,这里只需要在axios封装的基础上加几行代码就可以了,axios封装花样多多,这里简单示例
const ajax = axios.create()export default { post (url, data, opcode, config = {}) { if (opcode && !hasOp(opcode)) { return Promise.reject(new Error('没有操作权限')) } return ajax.post(url, data, { ...config }).then(({ data }) => data) }, // ...}到这里,这个方案差不多就完成了,权限配置的可视化可以根据操作列表中的routeName 来做,将操作与权限路由一一对应,在demo 中有一个简单实现
总结
以上所述是小编给大家介绍的Vue实现按钮级权限方案,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!
声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。
使用vue阻止子级元素的click事件冒泡,很简单,用stop按钮1按钮2这样点击div里面的按钮1,就不会触发div绑定时间test1()方法。以上这篇vue
之前在基于Vue实现后台系统权限控制一文中提到路由权限的实现思路,因为不喜欢在每次路由跳转的before钩子里做判断,所以在初始化Vue实例前对路由做了筛选,再
目标:layui实现点击按钮添加一行解决方案:方案1、table是用转换静态表格的方式创建的,写一个button,每次点击按钮,就添加一个标签;方案2、tabl
本文实例为大家分享了vue实现二级导航栏效果展示的具体代码,供大家参考,具体内容如下实现如下功能:在.vue文件中,template中的内容如下:{{item.
注意:vue-router是无法完全控制前端路由权限。1、实现思路使用vue-router实例函数addRoutes动态添加路由规则,不多废话直接上思维导图:2