如何使用Vue实现按钮级权限方案

发布时间:2021-04-02 09:58:27 作者:小新
来源:亿速云 阅读:206

这篇文章将为大家详细讲解有关如何使用Vue实现按钮级权限方案,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。

演示

在年初开发一个中后台管理系统,功能涉及到了各个部门(产品、客服、市场等等),在开始的版本中,我和后端配合使用了花裤衩手摸手系列的权限方案,前期非常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

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)
 },
 // ...
}

关于“如何使用Vue实现按钮级权限方案”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。

推荐阅读:
  1. Vue权限管理解决方案
  2. Vue 动态路由的实现及 Springsecurity 按钮级别的权限控制

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

vue

上一篇:使用vue实现在线预览pdf文件和下载的方法

下一篇:javascript声明变量的方法有哪些

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》