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 相关文章推荐
在textarea中显示html页面的javascript代码
Apr 20 Javascript
javascript 当前日期加(天、周、月、年)
Aug 09 Javascript
超级24小时弹窗代码 24小时退出弹窗代码 100%弹窗代码(IE only)
Jun 11 Javascript
基于jquery的checkbox下拉框插件代码
Jun 25 Javascript
用js的document.write输出的广告无阻塞加载的方法
Jun 05 Javascript
Bootstrap学习笔记之js组件(4)
Jun 12 Javascript
新手vue构建单页面应用实例代码
Sep 18 Javascript
老生常谈JS中的继承及实现代码
Jul 06 Javascript
vue如何进行动画的封装
Sep 26 Javascript
django使用channels2.x实现实时通讯
Nov 28 Javascript
原生JS forEach()和map()遍历的区别、兼容写法及jQuery $.each、$.map遍历操作
Feb 27 jQuery
VUE实现密码验证与提示功能
Oct 18 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
全国FM电台频率大全 - 2 天津市
2020/03/11 无线电
php中将地址生成迅雷快车旋风链接的代码[测试通过]
2011/04/20 PHP
关于php连接mssql:pdo odbc sql server
2011/07/20 PHP
PHP5常用函数列表(分享)
2013/06/07 PHP
删除html标签得到纯文本可处理嵌套的标签
2014/04/28 PHP
一个js控制的导航菜单实例代码
2013/12/03 Javascript
jquery无刷新验证邮箱地址实现实例
2014/02/19 Javascript
node.js中的fs.symlinkSync方法使用说明
2014/12/15 Javascript
浅谈Javascript Base64 加密解密
2014/12/28 Javascript
详解参数传递四种形式
2015/07/21 Javascript
jquery通过扩展select控件实现支持enter或focus选择的方法
2015/11/19 Javascript
Javascript中的几种继承方式对比分析
2016/03/22 Javascript
JS实现支持Ajax验证的表单插件
2016/03/24 Javascript
etmvc+jQuery EasyUI+combobox多值操作实现角色授权实例
2016/11/09 Javascript
JavaScript实现倒计时跳转页面功能【实用】
2016/12/13 Javascript
浅析JS中的 map, filter, some, every, forEach, for in, for of 用法总结
2017/03/29 Javascript
详解Vuejs2.0之异步跨域请求
2017/04/20 Javascript
详解webpack和webpack-simple中如何引入css文件
2017/06/28 Javascript
AngularJS基于http请求实现下载php生成的excel文件功能示例
2018/01/23 Javascript
react以create-react-app为基础创建项目
2018/03/14 Javascript
微信小程序图表插件wx-charts用法实例详解
2019/05/20 Javascript
python 读写txt文件 json文件的实现方法
2016/10/22 Python
基于MTCNN/TensorFlow实现人脸检测
2018/05/24 Python
python操作redis方法总结
2018/06/06 Python
python 读写文件,按行修改文件的方法
2018/07/12 Python
Python流行ORM框架sqlalchemy安装与使用教程
2019/06/04 Python
django 中使用DateTime常用的时间查询方式
2019/12/03 Python
python实现计算图形面积
2021/02/22 Python
基于tensorflow __init__、build 和call的使用小结
2021/02/26 Python
曼联官方网上商店:Manchester United Direct
2017/07/28 全球购物
PHP如何删除一个Cookie值
2012/11/15 面试题
幼师自我鉴定范文
2013/10/01 职场文书
大学生护理专业自荐信
2013/10/03 职场文书
小学老师寄语大全
2014/04/04 职场文书
优秀毕业生找工作自荐信
2014/06/23 职场文书
学校教代会开幕词
2016/03/04 职场文书