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 相关文章推荐
删除重复数据的算法
Nov 23 Javascript
Javascript 判断客户端浏览器类型代码
Mar 01 Javascript
jquery 操作表格实现代码(多种操作打包)
Mar 20 Javascript
基于jquery的图片轮播 tab切换组件
Jul 19 Javascript
js简易namespace管理器 实例代码
Jun 21 Javascript
jQuery中filter()和find()的区别深入了解
Sep 25 Javascript
js中indexof的用法详细解析
Dec 24 Javascript
JavaScript计时器用法分析【setTimeout和clearTimeout】
Jan 18 Javascript
微信小程序实现点击文字页面跳转功能【附源码下载】
Dec 12 Javascript
详解Vue.js iview实现树形权限表(可扩展表)
Sep 30 Javascript
vue百度地图 + 定位的详解
May 13 Javascript
通过实例解析chrome如何在mac环境中安装vue-devtools插件
Jul 10 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
IIS php环境配置PHP5 MySQL5 ZendOptimizer phpmyadmin安装与配置
2008/11/18 PHP
php array_pop()数组函数将数组最后一个单元弹出(出栈)
2011/07/12 PHP
PHP切割汉字的常用方法实例总结
2019/04/27 PHP
javascript 网页跳转的方法
2008/12/24 Javascript
基于jquery创建的一个图片、视频缓冲的效果样式插件
2012/08/28 Javascript
jQuery简易图片放大特效示例代码
2014/06/09 Javascript
jquery+ajax验证不通过也提交表单问题处理
2014/12/12 Javascript
javascript中的正则表达式使用指南
2015/03/01 Javascript
深入理解JS DOM事件机制
2016/08/06 Javascript
Angular2 路由问题修复详解
2017/03/01 Javascript
js canvas实现二维码和图片合成的海报
2020/11/19 Javascript
使用puppeteer爬取网站并抓出404无效链接
2018/12/20 Javascript
jQuery实现带3D切割效果的轮播图功能示例【附源码下载】
2019/04/04 jQuery
layui 数据表格 根据值(1=业务,2=机构)显示中文名称示例
2019/10/26 Javascript
Python编程中的for循环语句学习教程
2015/10/14 Python
Python出现segfault错误解决方法
2016/04/16 Python
Python操作SQLite数据库的方法详解【导入,创建,游标,增删改查等】
2017/07/11 Python
PyQt5每天必学之布局管理
2018/04/19 Python
使用python实现语音文件的特征提取方法
2019/01/09 Python
Elasticsearch py客户端库安装及使用方法解析
2020/09/14 Python
HTML5实现分享到微信好友朋友圈QQ好友QQ空间微博二维码功能
2018/01/03 HTML / CSS
HUGO BOSS美国官方网上商店:世界知名奢侈品牌
2017/08/04 全球购物
英国第一的滑雪服装和装备零售商:Snow+Rock
2020/02/01 全球购物
static全局变量与普通的全局变量有什么区别
2014/05/27 面试题
关于毕业的中学校园广播稿
2014/01/26 职场文书
北京大学自荐信范文
2014/01/28 职场文书
社会学专业学生职业规划书
2014/02/07 职场文书
新闻编辑求职信
2014/07/13 职场文书
数学考试作弊检讨书300字
2015/02/16 职场文书
清明节网上祭英烈寄语2015
2015/03/04 职场文书
入党介绍人考察意见
2015/06/01 职场文书
幼儿园奖惩制度范本
2015/08/05 职场文书
小学三年级语文教学反思
2016/03/03 职场文书
2019银行员工个人工作自我鉴定
2019/06/27 职场文书
MySQL中order by的使用详情
2021/11/17 MySQL
MySQL中B树索引和B+树索引的区别详解
2022/03/03 MySQL