DDR爱好者之家 Design By 杰米

注意: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"color: #ff0000">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实现对路由权限的控制,但是实际是伪权限控制,无法达到完全控制;强烈建议对于需要控制路由权限的系统采用后端控制。

DDR爱好者之家 Design By 杰米
广告合作:本站广告合作请联系QQ:858582 申请时备注:广告合作(否则不回)
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
DDR爱好者之家 Design By 杰米

《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线

暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。

艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。

《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。