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解密入门 最终变量劫持
Jun 25 Javascript
Json对象与Json字符串互转(4种转换方式)
Mar 27 Javascript
用表格输出1-1000之间的数字实现代码(附特效)
Apr 21 Javascript
jquery、js调用iframe父窗口与子窗口元素的方法整理
Jul 31 Javascript
AngularJS 执行流程详细介绍
Aug 18 Javascript
原生JS实现幻灯片
Feb 22 Javascript
关于jQuery中fade(),show()起始位置的一点小发现
Apr 25 jQuery
Axios学习笔记之使用方法教程
Jul 21 Javascript
Vue引入jquery实现平滑滚动到指定位置
May 09 jQuery
Bootstrap 模态框自定义点击和关闭事件详解
Aug 10 Javascript
原生JS实现记忆翻牌游戏
Jul 31 Javascript
js canvas实现俄罗斯方块
Oct 11 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
JAVA/JSP学习系列之二
2006/10/09 PHP
jQuery.prototype.init选择器构造函数源码思路分析
2013/02/05 Javascript
jquery利用ajax调用后台方法实例
2013/08/23 Javascript
基于iframe实现类似于ajax的页面无刷新
2014/05/31 Javascript
超级给力的JavaScript的React框架入门教程
2015/07/02 Javascript
js获取时间精确到秒(年月日)
2016/03/16 Javascript
JS遍历页面所有对象属性及实现方法
2016/08/01 Javascript
js实现select选择框效果及美化
2016/08/19 Javascript
用headjs来管理和加载js 提高网站加载速度
2016/11/29 Javascript
详解Jquery EasyUI tree 的异步加载(遍历指定文件夹,根据文件夹内的文件生成tree)
2017/02/11 Javascript
老生常谈jquery中detach()和remove()的区别
2017/03/02 Javascript
用React实现一个完整的TodoList的示例代码
2017/10/30 Javascript
详解vue-cli之webpack3构建全面提速优化
2017/12/25 Javascript
浅谈实现vue2.0响应式的基本思路
2018/02/13 Javascript
浅谈在vue中用webpack打包之后运行文件的问题以及相关配置方法
2018/02/21 Javascript
详解vue-cli中模拟数据的两种方法
2018/07/03 Javascript
webpack3里使用uglifyjs压缩js时打包报错的解决
2018/12/13 Javascript
JavaScript工具库MyTools详解
2020/01/01 Javascript
在vue项目实现一个ctrl+f的搜索功能
2020/02/28 Javascript
详解JavaScript中new操作符的解析和实现
2020/09/04 Javascript
Python中字典(dict)和列表(list)的排序方法实例
2014/06/16 Python
详解Python中的Numpy、SciPy、MatPlotLib安装与配置
2017/11/17 Python
python 从csv读数据到mysql的实例
2018/06/21 Python
解决Pycharm的项目目录突然消失的问题
2020/01/20 Python
Python用类实现扑克牌发牌的示例代码
2020/06/01 Python
如何在 Matplotlib 中更改绘图背景的实现
2020/11/26 Python
群胜软件Java笔试题
2012/09/29 面试题
园林资料员岗位职责
2013/12/30 职场文书
公司总经理工作职责管理办法
2014/02/28 职场文书
仓库主管岗位职责
2014/03/02 职场文书
高中语文课后反思
2014/04/27 职场文书
冬季安全检查方案
2014/05/23 职场文书
签约仪式策划方案
2014/06/02 职场文书
劳动纠纷调解协议书格式
2014/11/30 职场文书
Python中Selenium对Cookie的操作方法
2021/07/09 Python
MySQL 原理与优化之原数据锁的应用
2022/08/14 MySQL