Vue实现按钮级权限方案

演示

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

  • 手摸手系列权限方案是有比较清晰的权限划分的,而我们公司部门的岗位职责有时比较模糊。
  • 后端采用RBAC权限方案,为了达到第1点要求,将角色划分的很细,并且角色有时频繁变动,导致每一次前端都需要手动维护
  • 为了解决上面2个痛点,我将原方案进行了一丢丢改造。
  • 前端不再以角色来控制权限,而是以更小粒度的操作(接口)来控制,也就是前端不关心角色
  • 路由还是由前端维护(我们的后端很排斥维护和他们不相干的东西:joy:),但改为通过操作列表对权限路由进行过滤
  • 使用单一的方式(方便维护)控制页面的局部权限,不再使用自定义指令方式,而是通过函数式组件,原因是使用自定义指令有多余的开销(插入再移除)

后端的配合:

routerName

有一些注意点:

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

实现

操作列表示例

以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

hidden
visible
const 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])
 : null
const 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)
 : false

const 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(/* 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 中有一个简单实现

总结

以上所述是小编给大家介绍的Vue实现按钮级权限方案,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

(0)

相关推荐

  • 详解Vue.js iview实现树形权限表(可扩展表)

    问题: 需要一个可折叠的权限管理系统,用表格展示. 主要用的iView组件库,有Table(表格),Tree(树形控件),Collapse(折叠面板)看起来比较符合意思 深入查看Table相关范例,发现有个 通过给 columns 数据设置一项,指定 type: 'expand',即可开启扩展功能  可以考虑 但也只是展开详细内容,如需树形结构,则需要render自定义去渲染,挺麻烦的.再去看看element UI,也没有什么比较好的选择. 过程: 第一步,先构建根节点的数据表,后台返回JSON

  • Vue 指令实现按钮级别权限管理功能

    在项目中经常有需求要根据用户的权限对界面上的元素进行控制,这里介绍了一直简单的实现,仅供参考. 当前用户的权限列表储存在 store 里,也可以是其他地方. 指令 // src/directives/permission.js import Vue from 'vue'; import store from '@/store'; import {get} from '@/utils'; // 是否有权限 const hasPermission = userPermission => { let

  • vue addRoutes实现动态权限路由菜单的示例

    需求 最近接手一个后台管理系统,需要实现导航菜单从后台拉取的效果:根据登录用户的权限不同分别拉出来的导航菜单也不一样,另外可操作的界面也存在区别. 问题 因为后台管理系统是准备使用vue+vue-router+element-ui+vuex的搭配来做的,可是单页应用在进入页面之前就已经将vue-router实例化并且注入vue实例中了,所以在进入登录页面的时候旧没办法在重新定制路由了.接下来各种百之谷之,发现vue-router在2.0版本中提供了addRoutes方法添加路由,希望的曙光出现.

  • 基于Vue自定义指令实现按钮级权限控制思路详解

    思路: 登录:当用户填写完账号和密码后向服务端验证是否正确,验证通过之后,服务端会返回一个token,拿到token之后(我会将这个token存贮到sessionStorage中,保证刷新页面后能记住用户登录状态),前端会根据token再去拉取一个 user_info 的接口来获取用户的详细信息(如用户权限,用户名等等信息). 权限验证:通过token获取用户对应的 role,自定义指令,获取路由meta属性里btnPermissions( 注: meta.btnPermissions是存放按钮

  • 详解基于vue-router的动态权限控制实现方案

    使用vue开发带权限管理系统,尤其是采用了vue-router做路由,很多人都遇到的一个问题就是如何动态加载路由path对应的component. 典型的应用场景就是:前端菜单不静态的写在vue程序里,而是要从后台程序和数据库返回的菜单来动态加载到vue应用中. 网上很多问权限的问题,但几乎找不到很好的解决答案,在很长一段时间里,非常打击使用vue技术栈开发的信心.最有质量的一篇文章是:http://www.jb51.net/article/124801.htm 但作者并没有完全解决这个问题,还

  • vue iview实现动态路由和权限验证功能

    github上关于vue动态添加路由的例子很多,本项目参考了部分项目后,在iview框架基础上完成了动态路由的动态添加和菜单刷新.为了帮助其他需要的朋友,现分享出实现逻辑,欢迎一起交流学习. Github地址 iview-dynamicRouter 实现目标 客户端从服务端拿到路由和权限数据后,刷新项目的路由和菜单列表,并进行权限控制. 项目基础 基础框架: iview组件库官方模板项目 iview-admin 的template分支项目,此项目为 iview-admin 的基础框架代码.项目地

  • 基于Vue实现后台系统权限控制的示例代码

    用Vue/React这类双向绑定框架做后台系统再适合不过,后台系统相比普通前端项目除了数据交互更频繁以外,还有一个特别的需求就是对用户的权限控制,那么如何在一个Vue应用中实现权限控制呢?下面是我的一点经验. 权限控制是什么 在权限的世界里服务端提供的一切都是资源,资源可以由请求方法+请求地址来描述,权限是对特定资源的访问许可,所谓权限控制,也就是确保用户只能访问到被分配的资源.具体的说,前端对资源的访问通常是由界面上的按钮发起,比如删除某条数据:或由用户进入某一个页面发起,比如获取某个列表数据

  • Vue 动态路由的实现及 Springsecurity 按钮级别的权限控制

    思路 : 动态路由实现:在导航守卫中判断用户是否有用户信息, 通过调用接口,拿到后台根据用户角色生成的菜单树, 格式化菜单树结构信息并递归生成层级路由表并 使用Vuex保存,通过  router.addRoutes  动态挂载到  router  上,按钮级别的权限控制,则需使用自定义指令去实现. 实现: 导航守卫代码: router.beforeEach((to, from, next) => { NProgress.start() // start progress bar to.meta

  • Vue实现按钮级权限方案

    演示 在年初开发一个中后台管理系统,功能涉及到了各个部门(产品.客服.市场等等),在开始的版本中,我和后端配合使用了花裤衩手摸手系列的权限方案,前期非常nice,但是慢慢的随着功能增多.业务越来越复杂,就变得有些吃力了,因为我们的权限动态性太大了 手摸手系列权限方案是有比较清晰的权限划分的,而我们公司部门的岗位职责有时比较模糊. 后端采用RBAC权限方案,为了达到第1点要求,将角色划分的很细,并且角色有时频繁变动,导致每一次前端都需要手动维护 为了解决上面2个痛点,我将原方案进行了一丢丢改造.

  • vue-element-admin按钮级权限管控的实现

    目录 思路 表结构与数据 实现 按钮调用 随着软件的发展,网站从最初的满足用户业务需求到提升用户.就比如一个按钮只要求权限方面的管控我们可以通过 shiro,注解等方式来实现,但是页面上用户点击后没反应或者点击后弹框没权限,这显然不是一个好的用户体验,因此通过前端来实现权限管控也是很有必要的. 思路 1.获取按钮权限2.按钮权限保存在前端全局中 vuex3.页面加载按钮时通过判断权限的存在与否,控制按钮的显示隐藏或者样式选择 PS:事实证明,思路清晰,实现起来就很容易,没几行代码 表结构与数据

  • vue实现前端按钮组件权限管理

    目录 方案1:数组+自定义指令 关于路由权限 方案2: 二进制 表达权限: 权限判断 判断和使用 小结 方案1:数组+自定义指令 把权限放到数组中,通过vue的自定义指令来判断是否拥有该权限,有则显示,反之则不显示 我们可以把这个按钮需要的权限放到组件上 <el-button v-hasPermi="['home:advertising:update']" >新建</el-button> 自定义指令: 逻辑就是我们在登陆后会获取该用户的权限,并存储到localS

  • 详解VUE前端按钮权限控制

    在用户登陆后,根据用户id读取用户的所有权限数据,放入本地的storage进行存储,之后,定义指令,如果用户含有此按钮权限,则在页面显示出来: /**权限指令**/ Vue.directive('has', { bind: function(el, binding) { if (!Vue.prototype.$_has(binding.value)) { el.parentNode.removeChild(el); } } }); //权限检查方法 Vue.prototype.$_has = f

  • vue实现后台管理权限系统及顶栏三级菜单显示功能

    •效果演示地址 项目demo展示 重要功能总结 权限功能的实现 权限路由思路: 根据用户登录的roles信息与路由中配置的roles信息进行比较过滤,生成可以访问的路由表,并通过router.addRoutes(store.getters.addRouters)动态添加可访问权限路由表,从而实现左侧和顶栏菜单的展示. 实现步骤: 1.在router/index.js中,给相应的菜单设置默认的roles信息: 如下:给"权限设置"菜单设置的权限为:meta:{roles: ['admin

  • Vue常见组件间通信方案及典型应用场景详解

    目录 什么是组件通信 1.父子组件通信场景 2.兄弟组件通信场景 3.根组件和后代组件通信场景 4.插槽通信场景 5 无直接关系的组件通信场景 6 大型项目中的复杂组件通信场景 - Vuex状态管理 7 其他的一些组件通信方案 总结 什么是组件通信 所谓组件通信,就是组件之间的数据交互,也就是把一个组件A里面的数据传递到另一个组件B,并能够让组件B根据这个数据更新界面. 在 Vue中,可用的通信方案有很多,下面给大家描述几个常用的组件通信方案及其典型的应用场景. 1.父子组件通信场景 父子组件通

  • Vue波纹按钮组件制作

    先说一下用法: <zk-button class="btn btn-default">默认按钮</zk-button> <zk-button class="btn btn-default btn-round">默认按钮</zk-button> <zk-button class="btn btn-default btn-round" :speed="4" :opacity=&

随机推荐