vue-router 控制路由权限的实现


Posted in Javascript onSeptember 24, 2020

注意:vue-router是无法完全控制前端路由权限。

1、实现思路

使用vue-router实例函数addRoutes动态添加路由规则,不多废话直接上思维导图:

vue-router 控制路由权限的实现

2、实现步骤

2.1、路由匹配判断

// src/router.js

import Vue from 'vue';
import Store from '@/store';
import Router from 'vue-router';
import Cookie from 'js-cookie';
const routers = new Router({
  base : "/test",
  // 定义默认路由比如登录、404、401等
  routes : [{
    path : "/404",
    // ...
  },{
    path : "/401",
    // ...
  }]
})
// ...省略部分代码
routes.beforeEach((to, from, next) => {
  const { meta, matched, path } = to;
  let isMatched = matched && matched.length > 0; // 是否匹配路由
  if(isMatched){
  
  }else{
  
  }
})

通过vue-router前置守卫beforeEach中参数to来简单的实现匹配结果

2.2、登录访问控制

在实际开发中路由常常存在是否登录访问和是否需要登录访问的情况,于是可以通过token和路由配置meta信息中定义isAuth字段来区分。

// ...省略部分重复代码

const openRouters = [];
const authRouters = [{
  path : "order/list",
  // ...
  meta : {
    // 是否身份验证(至于默认定义false还是true由开发者自定义)
    isAuth : true
  }
}];

routes.beforeEach((to, from, next) => {
  const { meta, matched, path } = to;
  let isMatched = matched && matched.length > 0; // 是否匹配路由
  let isLogin = Cookie.get("token") || null;
  let { isAuth } = (meta || {});
  if(isMatched){
    // 匹配到路由
    if(isAuth){
      // 需要登录访问
      if(isLogin){
        // 已登录访问
        next(); // 调用钩子函数
      }else{
        // 未登录访问
        next("/login"); // 跳转登录
      }
    }else{
      // 不需要登录访问
      next(); // 调用钩子函数
    }
  }else{
    // 未匹配到路由
    if(isLogin){
      // 已登录访问
      
    }else{
      // 未登录访问
      next("/login"); // 跳转登录
    }
  }
})

2.3、动态添加路由规则

实现动态添加路由规则只需要使用vue-router实例方法router.addRoutes(routes: Array) 。
那么问题来了,我们怎么才能获取到需要动态添加的路由规则呢?

2.4、构建路由规则匹配函数

假如后台获取到的路由权限列表是这样的:

[{
 resourceUrl : "/order/list",
 childMenu : ...
}]

为了对比用户权限和路由是否匹配我们需要提取出权限路由数组

// 简单的通过递归获取到了所有权限url
export function getAuthRouters(authMenu) {
  let authRouters = [];
  (authMenu || []).forEach((item) => {
    const { resourceUrl, childMenu } = item;
    resourceUrl && authRouters.push(resourceUrl);
    if (childMenu && childMenu.length > 0) {
      // 合并子级菜单
      authRouters = [...authRouters, ...getAuthRouters(childMenu)];
    }
  });
  return authRouters;
}

通过getAuthRouters函数获取到了所有用户路由权限,接下来是要怎么和vue-router路由匹配呢?

这要和(我这里使用的是RBAC模型)系统配置权限关联上。vue-router路由规则要和权限配置保持一致。所以通过递归动态拼接vue-router路由规则和用户拥有的路由权限做对比。如果匹配就保留该路由;然后得到一份过滤后的vue-router路由规则配置。最后通过实例方法addRoutes添加路由规则。具体实现代码如下:

// src/utils/index.js
const { pathToRegexp } = require('path-to-regexp');

export function createAuthRouters(authRouters) {
  const isAuthUrl = (url) => {
    return (authRouters || []).some((cUrl) => {
      return pathToRegexp(url).toString() === pathToRegexp(cUrl).toString();
    });
  };
  return function createRouters(routers, upperPath) {
    let nRouters = [];
    (routers || []).forEach((item) => {
      const { children, path, name } = item;
      let isMatched = false,
        nItem = { ...item },
        fullPath = `${upperPath || ''}/${path}`.replace(/\/{2,}/, '/'),
        nChildren = null;
      children && (nChildren = createRouters(children, fullPath));
      // 1.当前路由匹配
      if (isAuthUrl(fullPath)) {
        isMatched = true;
      }
      // 2.存在子路由匹配
      if (nChildren && nChildren.length > 0) {
        nItem.children = nChildren;
        isMatched = true;
      }
      // 特殊处理(不需要可以删除)
      if(name === "home"){
        isMatched = true;
      }
      // nItem
      isMatched && nRouters.push(nItem);
    });
    return nRouters;
  };
}

值得注意的是createAuthRouters方法通过变量isMatched控制是否保留,之所以通过变量来决定是因为嵌套路由中父路由可能无法匹配,但是子路由能匹配所以父路由规则也需要子路参与是否保留。比如:

// 路由规则
const routers = new Router({
  base : "/test",
  // 定义默认路由比如登录、404、401等
  routes : [{
    path : "/",
    ...
    children : [{
      path : "login",
      ...
    },{
      path : "about",
      ...
    },{
      path : "order",
      ...
      children : [{
        path : "id"
      }]
    }]
  }]
})

// 用户权限
["/order/id"]; // 在匹配的过程中 "/" 不等于 "/order/id" 、"/" 不等于 "/order" 但是子路由 "/order/id" == "/order/id" 所以不但要保留 path : "/",还得保留 path : "order" 嵌套层。

2.5、动态注册

// ...省略部分重复代码

const openRouters = [];
const authRouters = [{
  path : "order/list",
  // ...
  meta : {
    // 是否身份验证(至于默认定义false还是true由开发者自定义)
    isAuth : true
  }
}];

/* 动态注册路由 */
async function AddRoutes() {
  // 获取用户路由权限
  let res = await POST(API.AUTH_RESOURCE_LISTSIDEMENU);
  try {
    const { code, data } = res || {};
    if (code === '000') {
      let newAuthRoutes = createAuthRouters(getAuthRouters(data))(authRouters, routes.options.base);
      // 注册路由
      routes.addRoutes([].concat(newAuthRoutes, openRouters));
      // 设置已注册
      Store.commit('UPDATE_IS_ADD_ROUTERS', true);
      // 保存菜单信息
      Store.commit('UPDATE_MENU_INFO', data);
    }
  } catch (error) {
    console.error('>>> AddRoutes() - error:', error);
  }
}

routes.beforeEach((to, from, next) => {
  const { meta, matched, path } = to;
  let isMatched = matched && matched.length > 0; // 是否匹配路由
  let isLogin = Cookie.get("token") || null;
  let { isAuth } = (meta || {});
  if(isMatched){
    // 匹配到路由
    if(isAuth){
      // 需要登录访问
      if(isLogin){
        // 已登录访问
        next(); // 调用钩子函数
      }else{
        // 未登录访问
        next("/login"); // 跳转登录
      }
    }else{
      // 不需要登录访问
      next(); // 调用钩子函数
    }
  }else{
    // 未匹配到路由
    if(isLogin){
      // 已登录访问
      AddRoutes();
      next();
    }else{
      // 未登录访问
      next("/login"); // 跳转登录
    }
  }
})

2.6、归类整理

/* 路由前置 */
let { origin } = window.location || {};
routes.beforeEach((to, from, next) => {
  const { meta, matched, path } = to;
  let isMatched = matched && matched.length > 0; // 是否匹配
  let isAuth = (meta || {}).isAuth; // 是否授权访问
  let { isAddRoutes } = Store.state; // 注册路由
  let isLogin = Cookie.get('token') || null; // 是否登录
  if ((isMatched && !isAuth) || (isMatched && isAuth && isLogin)) {
    // next()
    // 1.匹配路由 && 未登录访问
    // 2.匹配路由 && 登录访问 && 登录
    next();
  } else if ((isMatched && isAuth && !isLogin) || (!isMatched && !isLogin)) {
    // 登录
    // 1.匹配路由 && 登录访问 && 未登录
    // 2.未匹配路由 && 未登录
    next(`/login?r=${origin}/e-lottery${path}`);
  } else if (!isMatched && isLogin && isAddRoutes) {
    // 404
    // 1.未匹配路由 && 登录 && 动态注册路由
    next('/404');
  } else if (!isMatched && isLogin && !isAddRoutes) {
    // 注册路由
    // 1.未匹配路由 && 登录 && 未动态注册路由
    AddRoutes();
    next();
  }
});

嗯! 这下看起来舒服多了。

3、完整实现代码

// src/utils/index.js
const { pathToRegexp } = require('path-to-regexp');

export function getAuthRouters(authMenu) {
  let authRouters = [];
  (authMenu || []).forEach((item) => {
    const { resourceUrl, childMenu } = item;
    resourceUrl && authRouters.push(resourceUrl);
    if (childMenu && childMenu.length > 0) {
      // 合并子级菜单
      authRouters = [...authRouters, ...getAuthRouters(childMenu)];
    }
  });
  return authRouters;
}
/**
 *
 * @param { Array } authRouters
 */
export function createAuthRouters(authRouters) {
  const isAuthUrl = (url) => {
    return (authRouters || []).some((cUrl) => {
      return pathToRegexp(url).toString() === pathToRegexp(cUrl).toString();
    });
  };
  return function createRouters(routers, upperPath) {
    let nRouters = [];
    (routers || []).forEach((item) => {
      const { children, path, name } = item;
      let isMatched = false,
        nItem = { ...item },
        fullPath = `${upperPath || ''}/${path}`.replace(/\/{2,}/, '/'),
        nChildren = null;
      children && (nChildren = createRouters(children, fullPath));
      // 1.当前路由匹配
      if (isAuthUrl(fullPath)) {
        isMatched = true;
      }
      // 2.存在子路由匹配
      if (nChildren && nChildren.length > 0) {
        nItem.children = nChildren;
        isMatched = true;
      }
      // 特殊处理
      if(name === "home"){
        isMatched = true;
      }
      // nItem
      isMatched && nRouters.push(nItem);
    });
    return nRouters;
  };
}

 
// src/router.js

import Vue from 'vue';
import Store from '@/store';
import Router from 'vue-router';
import Cookie from 'js-cookie';

const openRouters = [];
const authRouters = [{
  path : "order/list",
  // ...
  meta : {
    // 是否身份验证(至于默认定义false还是true由开发者自定义)
    isAuth : true
  }
}];

/* 动态注册路由 */
async function AddRoutes() {
  // 获取用户路由权限
  let res = await POST(API.AUTH_RESOURCE_LISTSIDEMENU);
  try {
    const { code, data } = res || {};
    if (code === '000') {
      let newAuthRoutes = createAuthRouters(getAuthRouters(data))(authRouters, routes.options.base);
      // 注册路由
      routes.addRoutes([].concat(newAuthRoutes, openRouters));
      // 设置已注册
      Store.commit('UPDATE_IS_ADD_ROUTERS', true);
      // 保存菜单信息
      Store.commit('UPDATE_MENU_INFO', data);
    }
  } catch (error) {
    console.error('>>> AddRoutes() - error:', error);
  }
}


/* 路由前置 */
let { origin } = window.location || {};
routes.beforeEach((to, from, next) => {
  const { meta, matched, path } = to;
  let isMatched = matched && matched.length > 0; // 是否匹配
  let isAuth = (meta || {}).isAuth; // 是否授权访问
  let { isAddRoutes } = Store.state; // 注册路由
  let isLogin = Cookie.get('token') || null; // 是否登录
  if ((isMatched && !isAuth) || (isMatched && isAuth && isLogin)) {
    // next()
    // 1.匹配路由 && 未登录访问
    // 2.匹配路由 && 登录访问 && 登录
    next();
  } else if ((isMatched && isAuth && !isLogin) || (!isMatched && !isLogin)) {
    // 登录
    // 1.匹配路由 && 登录访问 && 未登录
    // 2.未匹配路由 && 未登录
    next(`/login?r=${origin}/e-lottery${path}`);
  } else if (!isMatched && isLogin && isAddRoutes) {
    // 404
    // 1.未匹配路由 && 登录 && 动态注册路由
    next('/404');
  } else if (!isMatched && isLogin && !isAddRoutes) {
    // 注册路由
    // 1.未匹配路由 && 登录 && 未动态注册路由
    AddRoutes();
    next();
  }
});

虽然前端能够通过vue-router实现对路由权限的控制,但是实际是伪权限控制,无法达到完全控制;强烈建议对于需要控制路由权限的系统采用后端控制。

到此这篇关于vue-router 控制路由权限的实现的文章就介绍到这了,更多相关vue-router 控制路由权限内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木! 

Javascript 相关文章推荐
学习YUI.Ext 第六天--关于树TreePanel(Part 2异步获取节点)
Mar 10 Javascript
JavaScript语法着色引擎(demo及打包文件下载)
Jun 13 Javascript
javascript工具库代码
Mar 29 Javascript
jQuery固定浮动侧边栏实现思路及代码
Sep 28 Javascript
利用jquery操作Radio方法小结
Oct 20 Javascript
jquery操作angularjs对象
Jun 26 Javascript
JS时间特效最常用的三款
Aug 19 Javascript
AngularJS基础 ng-paste 指令简单示例
Aug 02 Javascript
详解javascript中对数据格式化的思考
Jan 23 Javascript
vue.js全局API之nextTick全面解析
Jul 07 Javascript
jquery获取file表单选择文件的路径、名字、大小、类型
Jan 18 jQuery
微信小程序后端无法保持session的原因及解决办法问题
Mar 20 Javascript
vue+elementUI实现简单日历功能
Sep 24 #Javascript
JavaScript获取时区实现过程解析
Sep 24 #Javascript
小程序点餐界面添加购物车左右摆动动画
Sep 23 #Javascript
原生js实现购物车功能
Sep 23 #Javascript
详解微信小程序动画Animation执行过程
Sep 23 #Javascript
原生js实现分页效果
Sep 23 #Javascript
原生js实现购物车
Sep 23 #Javascript
You might like
php中数据的批量导入(csv文件)
2006/10/09 PHP
PHP实现MySQL更新记录的代码
2008/06/07 PHP
PHP 登录记住密码实现思路
2013/05/07 PHP
PHP中$this和$that指针使用实例
2015/01/06 PHP
php通过array_merge()函数合并两个数组的方法
2015/03/18 PHP
基于php实现七牛抓取远程图片
2015/12/01 PHP
Smarty保留变量用法分析
2016/05/23 PHP
懒就要懒到底——鼠标自动点击(含时间判断)
2007/02/20 Javascript
JS+CSS实现的拖动分页效果实例
2015/05/11 Javascript
用svg制作富有动态的tooltip
2015/07/17 Javascript
基于javascript bootstrap实现生日日期联动选择
2016/04/07 Javascript
JQuery中attr属性和jQuery.data()学习笔记【必看】
2016/05/18 Javascript
浅谈js中的this问题
2017/08/31 Javascript
原生JavaScript实现拖动校验功能
2020/09/29 Javascript
详解vue-router的导航钩子(导航守卫)
2020/11/02 Javascript
element-plus一个vue3.xUI框架(element-ui的3.x 版初体验)
2020/12/02 Vue.js
Python中asyncore异步模块的用法及实现httpclient的实例
2016/06/28 Python
python获取网页中所有图片并筛选指定分辨率的方法
2018/03/31 Python
用Python实现最速下降法求极值的方法
2019/07/10 Python
Python 进程之间共享数据(全局变量)的方法
2019/07/16 Python
Python 使用matplotlib模块模拟掷骰子
2019/08/08 Python
python3中替换python2中cmp函数的实现
2019/08/20 Python
解决Pycharm 包已经下载,但是运行代码提示找不到模块的问题
2019/08/31 Python
Python实现aes加密解密多种方法解析
2020/05/15 Python
CSS3打造百度贴吧的3D翻牌效果示例
2017/01/04 HTML / CSS
HTML5基于flash实现播放RTMP协议视频的示例代码
2020/12/04 HTML / CSS
Vans(范斯)德国官网:美国南加州的原创极限运动潮牌
2017/05/02 全球购物
NFL加拿大官方网上商店:NHLShop.ca
2019/03/12 全球购物
VisionPros美国站:加拿大在线隐形眼镜和眼镜零售商
2020/02/11 全球购物
材料物理专业个人求职信
2013/12/15 职场文书
简历里的自我评价范文
2014/02/24 职场文书
员工安全生产责任书
2014/07/22 职场文书
广告设计专业毕业生自我鉴定
2014/09/27 职场文书
教师思想作风整顿个人剖析材料
2014/10/10 职场文书
人才市场接收函
2015/01/30 职场文书
redis中lua脚本使用教程
2021/11/01 Redis