在年初开发一个中后台管理系统,功能涉及到了各个部门(产品、客服、市场等等),在开始的版本中,我和后端配合使用了花裤衩手摸手系列的权限方案,前期非常nice,但是慢慢的随着功能增多、业务越来越复杂,就变得有些吃力了,因为我们的权限动态性太大了

  1. 手摸手系列权限方案是有比较清晰的权限划分的,而我们公司部门的岗位职责有时比较模糊。
  2. 后端采用RBAC权限方案,为了达到第1点要求,将角色划分的很细,并且角色有时频繁变动,导致每一次前端都需要手动维护

为了解决上面2个痛点,我将原方案进行了一丢丢改造。

  1. 前端不再以角色来控制权限,而是以更小粒度的操作(接口)来控制,也就是前端不关心角色
  2. 路由还是由前端维护(我们的后端很排斥维护和他们不相干的东西),但改为通过操作列表对权限路由进行过滤
  3. 使用单一的方式(方便维护)控制页面的局部权限,不再使用自定义指令方式,而是通过函数式组件,原因是使用自定义指令有多余的开销(插入再移除)

我自己是一名从事了多年开发的web前端老程序员,目前辞职在做自己的web前端私人定制课程,今年年初我花了一个月整理了一份最适合2019年学习的web前端学习干货,各种框架都有整理,送给每一位前端小伙伴,想要获取的可以关注我并在后台私信我:前端,即可免费获取。

后端的配合:

  1. 提供一个获取当前用户操作列表的接口
  2. 操作列表需要增加一个唯一标识(操作码)供前端使用,不变的
  3. 操作列表需要增加一个routerName字段,用于可视化权限编辑

有一些注意点:

  1. 比如一个有权限的列表页面A,同时这个列表接口被权限页面B使用,现在你配置权限让某一个用户没有A页面权限,但可以使用B页面,如果你的本意是可以使用B页面的所有功能,这时就会有问题,所以尽量不要将权限接口跨页面使用,需要分清哪些数据需要通过字典接口获取还是通过权限接口获取
  2. 有些人可能会纠结,前端维护权限安全吗?肯定是不安全的,安全性主要还在后端这边把控,后端做好数据和接口方面的权限控制,前端做权限控制我认为主要还是为了交互体验等。没有权限你为什么要让我看到那一坨?
  3. 在使用这种方式之前,要明确当前场景是否确实需要这么做,毕竟在项目比较大且接口很多的情况下,你跟操作码之间有一场持久战

实现

操作列表示例

以Restful风格接口为例

路由的变化

在路由的meta中增加一个配置字段如requireOps,值可能为String或者Array,这表示当前路由页面要显示的必要的操作码,Array类型是为了处理一个路由页面需要满足同时存在多个操作权限时才显示的情况。若值不为这2种则视为无权限控制,任何用户都能访问

由于最终需要根据过滤后的权限路由动态生成菜单,所以还需要在路由选项中增加几个字段处理显示问题,其中hidden优先级大于visible

  1. hidden,值为true时,路由包括子路由都不会出现在菜单中
  2. visible,值为false时,路由不显示,但显示子路由

由于路由在前端维护,所以以上配置只能写死,如果后端能同意维护这一份路由表,那就可以有很多的发挥空间了,体验也能做的更好。

权限路由过滤

先将权限路由规范一下,同时保留一个副本,可能在可视化时需要用到

获取到操作列表后,只需要遍历权限路由,然后查询requireOps代表的操作有没有在操作列表中。这里需要处理一下requireOps未设置的情况,如果子路由中都是权限路由,需要为父级路由自动加上requireOps值,不然当所有子路由都没有权限时,父级路由就被认为是无权限控制且可访问的;而如果子路由中只要有一个路由无权限控制,那就不需要处理父路由。所以这里可以用递归来解决,先处理子路由再处理父路由

const filterPermissionRoutes = (routes, cb) => {// 可能父路由没有设置requireOps 需要根据子路由确定父路由的requireOpsroutes.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.requireOpsif (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.titleconst 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(/* config */)export default {post (url, data, opcode, config = {}) {if (opcode && !hasOp(opcode)) {return Promise.reject(new Error('没有操作权限'))}return ajax.post(url, data, { /* config */ ...config }).then(({ data }) => data)},// ...
}

到这里,这个方案差不多就完成了,权限配置的可视化可以根据操作列表中的routeName来做,将操作与权限路由一一对应,在demo中有一个简单实现

原文链接:https://juejin.im/post/5dd2a8d1518825499543c772
作者:前端小智

路由到另外一个页面_一个简单的Vue按钮级权限方案相关推荐

  1. 为什么函数lamda显示权限不足_一个简单的Vue按钮级权限方案

    在年初开发一个中后台管理系统,功能涉及到了各个部门(产品.客服.市场等等),在开始的版本中,我和后端配合使用了花裤衩手摸手系列的权限方案,前期非常nice,但是慢慢的随着功能增多.业务越来越复杂,就变 ...

  2. mpvue返回上一个页面_小程序返回上一页 - 芊芊一隅

    这个有点像子-->父传值 第一步,在子页面点击上一步或者保存数据请求成功以后添加如下代码. 第二部,在父组件里的onshow生命周期里获取参数,对了,前提是需要你在data里建一个mydata对 ...

  3. java按钮触发另一个页面_前端跨页面通信,你知道哪些方法?

    戳蓝字「前端技术优选」关注我们哦! 引言 在浏览器中,我们可以同时打开多个Tab页,每个Tab页可以粗略理解为一个"独立"的运行环境,即使是全局对象也不会在多个Tab间共享.然而有 ...

  4. java 返回上一个页面_页面返回上一页浏览位置

    1.如果上一页是静态页面,可以用 history.go(-1)方法: go() 方法可加载历史列表中的某个具体的页面. 该参数可以是数字,使用的是要访问的 URL 在 History 的 URL 列表 ...

  5. JSP页面如何从一个页面传递一个参数到另外一个页面

    (1).一种是重定向跳转,超连<a>就是一种重定向跳转,这样的跳转request对象是传不到下一个页面的,下一个页面得到的request对象是一个新的对象,而不是上一个页面传过来的就得不到 ...

  6. vue滑杆_非常简单的Vue滑杆组件

    vue滑杆 Vue滑杆 (Vue Slide Bar) Very Simple Vue Slider Bar Component. 非常简单的Vue滑动条组件. 安装 (Install) npm in ...

  7. java用爬虫爬一个页面_使用Java写一个简单爬虫爬取单页面

    使用Java爬虫爬取人民日报公众号页面图片 使用Java框架Jsoup和HttpClient实现,先看代码 爬取目标页面 1.使用Maven构建一个普通Java工程 加入依赖: org.jsoup j ...

  8. 路由到另外一个页面_如何在多个页面中,引入一个公共组件

    应用场景 在前端开发的过程中,时常有这样的一个需求,需要将某个组件,展示在不同的页面中.常见的有,头部菜单栏.底部版权,如下图中的菜单,就需要在不同页面中进行显示. 解决方法 假设有这样一个需求,希望 ...

  9. java后台管理页面_一个很不错的,代码简单的后台管理界面

    无标题文档 body{font-size:12px;} ul,li,h2{margin:0;padding:0;} ul{list-style:none;} #top{width:900px;heig ...

最新文章

  1. jstack可以定位到线程堆栈
  2. 【ok】李宏毅机器学习12: 对称矩阵
  3. silverlight 打开html_在Silverlight中嵌入HTML或aspx页面-阿里云开发者社区
  4. 京东 你访问的页面需要验证证书_SSL证书安全认证有什么原理?
  5. 关于java.net.URLEncoder.encode编码问题
  6. 如何对用户进行细分-邮件营销中用户细分的方法
  7. Codeforces Global Round 10
  8. apollo持久化sentinel_Spring Cloud Alibaba基础教程:Sentinel使用Apollo存储规则
  9. 第三十八期:如何在Windows 10上使用Windows Update目录驱动程序安装打印机
  10. cent os mysql图形界面_cent os 6.4安装使用mysql
  11. 方法 -------JavaScript
  12. 进程、地址空间、文件、I/O、保护、虚拟内存
  13. 天梯赛练习集--L1-009 (分式求和)
  14. WeakHashMap回收时机结合JVM 虚拟机GC的一些理解
  15. touch事件的分发和消费机制
  16. 锋利的jQuery系列一
  17. TCP/IP协议栈详解
  18. 天地图API搜索。定位等
  19. Python爬虫:ZzzFun动漫视频网
  20. 抽奖程序(python)

热门文章

  1. x-requested-with 请求头 区分ajax请求还是普通请求
  2. 如何让asp.net应用程序定时自动执行代码
  3. jupyter notebook的链接密码 token查询 以及 pycharm 如何使用 jupyter notebook
  4. mongodb系列-访问控制
  5. 解决 502、504 Gateway Time-out(nginx)
  6. ELK学习4_Elasticsearch+Logstash+Kibana安装_简易版
  7. Linux配置apache虚拟主机:静态文件
  8. 计算机网络一种开源,第一公里以太网
  9. 计算机无法检测电池损耗怎么办,笔记本电脑无法充电怎么办?笔记本电池损耗如何修复?...
  10. datagrip mysql乱码_DataGrip和IDEA无法连接上Mysql问题解决方法详解