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 相关文章推荐
写了10年的Javascript也未必全了解的连续赋值运算
Mar 25 Javascript
js 函数调用模式小结
Dec 26 Javascript
jquery操作复选框checkbox的方法汇总
Feb 05 Javascript
jQuery实现定时读取分析xml文件的方法
Jul 16 Javascript
jQuery复合事件结合toggle()方法的用法示例
Jun 10 jQuery
node基于puppeteer模拟登录抓取页面的实现
May 09 Javascript
基于axios 解决跨域cookie丢失的问题
Sep 26 Javascript
小程序实现列表点赞功能
Nov 02 Javascript
vue实现分页组件
Jun 16 Javascript
vue axios封装及API统一管理的方法
Apr 18 Javascript
elementUI select组件使用及注意事项详解
May 29 Javascript
微信小程序自定义模态弹窗组件详解
Dec 24 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
域名和cookie问题(域名后缀)
2012/10/10 PHP
PHP中mb_convert_encoding与iconv函数的深入解析
2013/06/21 PHP
PHP中的事务使用实例
2015/05/26 PHP
PHP转换文本框内容为HTML格式的方法
2016/07/20 PHP
php注册和登录界面的实现案例(推荐)
2016/10/24 PHP
自适应图片大小的弹出窗口
2006/07/27 Javascript
JS 时间显示效果代码
2009/08/23 Javascript
JavaScript打开word文档的实现代码(c#)
2012/04/16 Javascript
javascript上传图片前预览图片兼容大多数浏览器
2013/10/25 Javascript
js获取客户端外网ip的简单实例
2013/11/21 Javascript
JS实现两个大数(整数)相乘
2014/04/28 Javascript
使用Meteor配合Node.js编写实时聊天应用的范例
2015/06/23 Javascript
Css3制作变形与动画效果
2015/07/24 Javascript
javascript实现抽奖程序的简单实例
2016/06/07 Javascript
JS中split()用法(将字符串按指定符号分割成数组)
2016/10/24 Javascript
使用webpack编译es6代码的方法步骤
2019/04/28 Javascript
js获取浏览器地址(获取第1个斜杠后的内容)
2019/09/03 Javascript
微信小程序纯文本实现@功能
2020/04/08 Javascript
[49:21]完美世界DOTA2联赛循环赛 Ink Ice vs LBZS BO2第二场 11.05
2020/11/06 DOTA
使用Python下载Bing图片(代码)
2013/11/07 Python
Python中的包和模块实例
2014/11/22 Python
介绍Python中的文档测试模块
2015/04/28 Python
Python中关键字nonlocal和global的声明与解析
2017/03/12 Python
Python进阶_关于命名空间与作用域(详解)
2017/05/29 Python
Python:Scrapy框架中Item Pipeline组件使用详解
2017/12/27 Python
django url到views参数传递的实例
2019/07/19 Python
wxPython:python首选的GUI库实例分享
2019/10/05 Python
python 中的9个实用技巧,助你提高开发效率
2020/08/30 Python
详解HTML5表单新增属性
2016/12/21 HTML / CSS
详解HTML5新增标签
2017/11/27 HTML / CSS
斯凯奇新西兰官网:SKECHERS新西兰
2018/02/22 全球购物
澳洲网红粉泥面膜:Sand & Sky
2019/08/13 全球购物
俄罗斯购买剧院和演唱会门票网站:Parter.ru
2019/11/09 全球购物
应届毕业生求职信范文
2013/12/18 职场文书
个人对照检查剖析材料
2014/10/13 职场文书
redis击穿 雪崩 穿透超详细解决方案梳理
2022/03/17 Redis