vue自定义指令和动态路由实现权限控制


Posted in Javascript onAugust 28, 2020

功能概述:

  • 根据后端返回接口,实现路由动态显示
  • 实现按钮(HTML元素)级别权限控制

涉及知识点:

  • 路由守卫
  • Vuex使用
  • Vue自定义指令

导航守卫

前端工程采用Github开源项目Vue-element-admin作为模板,该项目地址:Github | Vue-element-admin 。

在Vue-element-admin模板项目的src/permission.js文件中,给出了路由守卫、加载动态路由的实现方案,在实现了基于不同角色加载动态路由的功能。我们只需要稍作改动,就能将基于角色加载路由改造为基于权限加载路由。

导航守卫:可以应用于在路由跳转时,对用户的登录状态或权限进行判断。项目中使用全局前置守卫。参考Vue官方文档:https://router.vuejs.org/zh/guide/advanced/navigation-guards.html

后台返回接口

vue自定义指令和动态路由实现权限控制

权限系统后台采用基于角色的权限控制方案(role-based access control),如上图所示,
该用户信息接口将查询用户所具有的所有角色,再将这些角色的权限并集按照路由 - 操作整合在一起返回。在用户登录入系统后,我们从后台请求获得用户信息(个人信息 + 权限信息),作为全局属性储存在前端。不同权限的用户看到的页面不同,依赖于这些属性,它们决定了路由如何加载、页面如何渲染。

这种多个组件依赖一组属性的场景,Vue提供了VueX作为全局状态管理方案。

使用VueX存储权限信息

src/store/moudules目录下定义permission.js

1.定义异步方法,方法内部包含HTTP请求从后台拉取数据

import http from '../../axios';
async function getUserInfo() {
 const res = await http.getUserInfo();
 return res;
}

使用await关键字,保证执行顺序正确。这里是为了保证能拿到接口返回的内容,以便于下一步处理。

const actions = {
 getPermissions({ commit }) {
 return new Promise(resolve => {
  getUserInfo().then(res => {
  if (res) {
   let permissionList = res.permissionList;
   commit('SET_PERMISSIONS', permissionList);
   // 根据后台返回的路由,生成实际可以访问的路由
   let accessRoutes = filterAsyncRoutesByPermissions(asyncRoutes, permissionList);
   commit('SET_ROUTES', accessRoutes);
   commit('SET_USER_INFO', { name: res.name, accountName: res.accountName })
   resolve(accessRoutes);
  } else {
   resolve([]);
  }
  }).catch(() => resolve([]));
 })
 }
}

VueX中action定义异步方法。

2. 定义静态、动态路由

src/router/index.js

静态路由:

export const constantRoutes = [
 {
  path: '/redirect',
  component: Layout,
  hidden: true,
  children: [
   {
    path: '/redirect/:path(.*)',
    component: () => import('@/views/redirect/index'),
   },
  ],
 ,
 ...
 {
  path: '/404',
  component: () => import('@/views/error-page/404'),
  hidden: true,
 }
];

动态路由:

export const asyncRoutes = [
 {
  path: '/system',
  component: Layout,
  name: '系统管理',
  meta: { title: '系统管理', icon: 'el-icon-user', affix: true },
  children: [
   {
    path: '/system',
    component: () => import('@/views/management/system/Index'),
    meta: { title: '系统管理', icon: 'el-icon-setting', affix: true },
   },
  ],
 }
...
]

静态路由中定义了所有用户均可访问的路由,动态路由中定义了动态加载的路由。

3.根据权限过滤并排序路由

export function filterAsyncRoutesByPermissions(routes, menus) {
 const res = []
 routes.forEach(route => {
 const tmp = { ...route }
 let index = menus.map(menu => menu.url).indexOf(tmp.path);
 if (index != -1) {
  // 后端返回路由信息覆盖前端定义路由信息
  tmp.name = menus[index].name;
  // debugger;
  tmp.meta.title = menus[index].name;
  tmp.children.forEach(child => {
  if (child.path == tmp.path) {
   child.meta.title = tmp.meta.title;
  }
  })
  res.push(tmp)
 }
 });
 // 根据返回菜单顺序,确定路由顺序
 /**
 * TODO 子菜单排序
 */
 res.sort((routeA, routeB) => menus.map(menu => menu.url).indexOf(routeA.path) - menus.map(menu => menu.url).indexOf(routeB.path))
 return res
}

根据url匹配,匹配到url的路由则加入数组。最终用户可以访问的路由 = 允许访问的动态路由 + 不需要权限的静态路由。

4.src/permission.js中的处理逻辑

// 引入store
import store from './store';
const whiteList = ['/login', '/auth-redirect']; // no redirect whitelist

// 路由守卫
router.beforeEach(async (to, from, next) => {
 //start progress bar
 NProgress.start()
 if (hasToken) {
  if (to.path === '/login') {
   // ... 省略登出逻辑
   NProgress.done();
  } else { 
   // 查看是否已缓存过动态路由
   const hasRoutes = store.getters.permission_routes && store.getters.permission_routes.length > 0;
   if (hasRoutes) {
    next();
   } else {
    try {
     const accessRoutes = await store.dispatch('permission/getPermissions');
     router.addRoutes(accessRoutes);
     const toRoute = accessRoutes.filter((route) => route.path == to.path);
     next({ path: toRoute.length > 0 ? toRoute[0].path : accessRoutes[0].path, replace: true });
    } catch (error) {
     next(`/login?redirect=${to.path}`);
     NProgress.done();
    }
   }
  }
 } else {
  if (whiteList.indexOf(to.path) !== -1) {
   // in the free login whitelist, go directly
   next();
  } else {
   next(`/login?redirect=${to.path}`);
   NProgress.done();
  }
 }
});

router.afterEach(() => {
 // finish progress bar
 NProgress.done();
});

以上是动态路由实现方案。

Vue支持自定义指令,用法类似于Vue原生指令如v-modelv-on等,网上查阅到的大部分细粒度权限控制方案都使用这种方法。下面将给出我的实现。

自定义指令

自定义指令 v-permission

src/directive/permission/index.js

import store from '@/store'
 export default {
 inserted(el, binding, vnode) {
 const { value } = binding
 const permissions = store.getters && store.getters.permissions;
 if (value) {
  // 获取当前所挂载的vue所在的上下文节点url
  let url = vnode.context.$route.path;
  let permissionActions = permissions[url];
  // console.log(permissionActions)
  const hasPermission = permissionActions.some(action => {
  if (value.constructor === Array) {
   // 或判断: 只要存在任1,判定为有权限
   return value.includes(action);
  } else {
   return action === value;
  }
  })
  if (!hasPermission) {
  el.parentNode && el.parentNode.removeChild(el)
  }
 } else {
  throw new Error(`need further permissions!`)
 }
 }
}

后端给出的权限数据是路由(url)与操作的对应Map,url可以通过将要挂载到的vnode属性拿到。这个方法有点类似于AOP,在虚拟元素挂载之后做判断,如果没有权限则从父元素上移除掉。
使用方法:

  • 举例一:单个按钮 (注意双引号套单引号的写法)
    <el-button @click.native.prevent="editUser(scope.row)" type="text" size="small" v-permission="'op_edit'">
            编辑
     </el-button>
  • 举例二:或判断(传入数组),只要拥有数组中一个权限,则保留元素,所有权限都没有,则移除。

在上一篇博客https://3water.com/article/194361.htm
下拉菜单上增加控制:

vue自定义指令和动态路由实现权限控制

vue自定义指令和动态路由实现权限控制

相应数据定义中增加action属性。
该方法无法覆盖所有场景,所以依然给出相应工具类:

/**
 * 
 * @param {*当前页面路由} url 
 * @param {*操作code e.g op_add } value 
 * @return true/false 是否有该项权限
 */
function checkPermission(url, value) {

 const permissions = store.getters && store.getters.permissions;
 let permissionActions = permissions[url];

 if (!permissionActions) {
  return false;
 }

 let hasPermission = permissionActions.some(action => {
  if (value.constructor === Array) {
   // 或判断: 只要存在任1,判定为有权限
   return value.includes(action);
  } else {
   return action === value;
  }
 });
 return hasPermission;

}

以上完成按钮粒度权限控制。

以上就是vue自定义指令和动态路由实现权限控制的详细内容,更多关于vue自定义指令和动态路由的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
js实现收缩菜单效果实例代码
Oct 30 Javascript
jQuery固定浮动侧边栏实现思路及代码
Sep 28 Javascript
JavaScript中数据结构与算法(三):链表
Jun 19 Javascript
jquery背景跟随鼠标滑动导航
Nov 20 Javascript
javascript运动框架用法实例分析(实现放大与缩小效果)
Jan 08 Javascript
详解javascript new的运行机制
Jan 26 Javascript
js点击文本框弹出可选择的checkbox复选框
Feb 03 Javascript
js中async函数结合promise的小案例浅析
Apr 14 Javascript
Vue2.0 实现页面缓存和不缓存的方式
Nov 12 Javascript
JavaScript常用进制转换及位运算实例解析
Oct 14 Javascript
vue使用Sass时报错问题的解决方法
Oct 14 Javascript
React Native项目框架搭建的一些心得体会
May 28 Javascript
vue 动态给每个页面添加title、关键词和描述的方法
Aug 28 #Javascript
vue-cli+webpack项目打包到服务器后,ttf字体找不到的解决操作
Aug 28 #Javascript
vue select 获取value和lable操作
Aug 28 #Javascript
VSCode 添加自定义注释的方法(附带红色警戒经典注释风格)
Aug 27 #Javascript
js实现弹幕飞机效果
Aug 27 #Javascript
jQuery编写QQ简易聊天框
Aug 27 #jQuery
jQuery实现简单QQ聊天框
Aug 27 #jQuery
You might like
咖啡豆分级制度 咖啡豆等级分类 咖啡豆是按口感分类的吗?
2021/03/05 新手入门
php FLEA中二叉树数组的遍历输出
2012/09/26 PHP
php 强制下载文件实现代码
2013/10/28 PHP
PHP imagegrabscreen和imagegrabwindow(截取网站缩略图)的实例代码
2013/11/07 PHP
ThinkPHP3.1新特性之Action参数绑定
2014/06/19 PHP
PHP提示Deprecated: mysql_connect(): The mysql extension is deprecated的解决方法
2014/08/28 PHP
php获取ajax的headers方法与内容实例
2017/12/27 PHP
基于Jquery的实现回车键Enter切换焦点
2010/09/14 Javascript
最短的IE判断var ie=!-[1,]分析
2014/05/28 Javascript
原生的html元素选择器类似jquery选择器
2014/10/15 Javascript
js函数与php函数的区别实例浅析
2015/01/12 Javascript
jquery表格datatables实例解析 直接加载和延迟加载
2016/08/12 Javascript
web 前端常用组件之Layer弹出层组件
2016/09/22 Javascript
基于vue的下拉刷新指令和滚动刷新指令
2016/12/23 Javascript
jQuery实现 上升、下降、删除、添加一行代码
2017/03/06 Javascript
基于HTML5+JS实现本地图片裁剪并上传功能
2017/03/24 Javascript
基于JavaScript实现新增内容滚动播放效果附完整代码
2017/08/24 Javascript
js中getBoundingClientRect的作用及兼容方案详解
2018/02/01 Javascript
详解如何实现一个简单的 vuex
2018/02/10 Javascript
vue采用EventBus实现跨组件通信及注意事项小结
2018/06/14 Javascript
vue-router beforeEach跳转路由验证用户登录状态
2018/12/26 Javascript
js常见遍历操作小结
2019/06/06 Javascript
关于better-scroll插件的无法滑动bug(2021通过插件解决)
2021/03/01 Javascript
基于Python中numpy数组的合并实例讲解
2018/04/04 Python
pyqt5之将textBrowser的内容写入txt文档的方法
2019/06/21 Python
python中seaborn包常用图形使用详解
2019/11/25 Python
python实现视频读取和转化图片
2019/12/10 Python
Pytorch 实现focal_loss 多类别和二分类示例
2020/01/14 Python
keras自动编码器实现系列之卷积自动编码器操作
2020/07/03 Python
Flask中sqlalchemy模块的实例用法
2020/08/02 Python
PyCharm上安装Package的实现(以pandas为例)
2020/09/18 Python
比较基础的php面试题及答案-填空题
2014/04/26 面试题
Weblogic和WebSphere不同特点
2012/05/09 面试题
军校本科大学生自我评价
2014/01/14 职场文书
反邪教标语
2014/06/23 职场文书
2016年五一劳动节专题校园广播稿
2015/12/17 职场文书