在我们开发项目的过程中,或许只要涉及有用户和角色的概念的话,就永远避不开权限的验证。权限的验证,体现在项目上比较直观的就是一些页面是否显示或者一些按钮是否显示。而在vue中,这些菜单所对应的页面,往往是由vue-router(也就是路由)来控制的。
   所以问题转换为如何将 router 处理成是正确的,这一步主要是在前端上做,由于可能会涉及到与后端返回的数据进行匹配,一旦涉及查询就要尽可能的快得到结果,故在此,查询匹配这些都是用的迭代查找,抛弃一般的递归查找。
   而本人在开发自己项目过程中,涉及到采用vue-element-admin这个框架,所以根据结合它的规则及后端返回的数据,大概做成如下的样子:

# main.js

//忽略其他无关的内容
import Vue from 'vue'

import 'normalize.css/normalize.css' // A modern alternative to CSS resets

import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import App from './App'
import store from './store'
import router from './router'

...
import '@/permission' // 权限控制

...

new Vue({
  el: '#app',
  router,
  store,
  render: h => h(App)
})


# src/permission.js

import router from './router';
import store from './store';
import NProgress from 'nprogress'; // progress bar
import 'nprogress/nprogress.css'; // progress bar style
import {getToken, getRole} from 'xxx';

NProgress.configure({
  showSpinner: false,
}); // NProgress Configuration

const whiteList = ['/login']; // no redirect whitelist

router.beforeEach(async (to, from, next) => {
  // start progress bar
  NProgress.start();

  // set page title
  document.title = '测试项目';

  // determine whether the user has logged in
  const hasToken = getToken();

  if (hasToken) {
    //如果有token
    if (to.path === '/login') {
      // if is logged in, redirect to the home page
      next({
        path: '/',
      });
      NProgress.done();
    } else {
      //如果不是去登录页
      if (store.getters.addRouters.length === 0) {
        //store.getters.xxx主要是在store的getter.js上自己配置一下映射就好
        const role = getSystemRole(); //获取角色
        if (role) {
          //操作比较复杂,单独列一个Module添加到store中
          store
            .dispatch('permission/GenerateRoutes', role)
            .then(res => {
              router.addRoutes(store.getters.addRouters); //添加动态路由
              next({...to, replace: true}); //必须这么写,不写刷新路由就没了,这一块具体还不是很清楚
              router.options.routes = store.getters.routers;
            })
            .catch(err => {
              console.log(err);
            });
        } else {
          next('/login'); //没有角色直接去登录页重新登录获取
        }
      } else {
        next();
        NProgress.done();
      }
    }
  } else {
    if (whiteList.indexOf(to.path) !== -1) {
      // in the free login whitelist, go directly
      next(); //有白名单直接去
    } else {
      // other pages that do not have permission to access are redirected to the login page.
      // next(`/login?redirect=${to.path}`)
      next('/login'); //没有跳回登录页
      // NProgress.done()
    }
  }
});

router.afterEach(() => {
  // finish progress bar
  NProgress.done();
});

# src/store/permission.js

import {getRoleID} from 'xxx';
import {getTree} from 'xxx';
import {asyncRoutes, constantRoutes} from '@/router';
import {Message} from 'element-ui';

const state = {
  routers: constantRoutes,
  addRouters: [],
};

const mutations = {
  SET_ROUTERS: (state, routers) => {
    state.addRouters = routers;
    state.routers = constantRoutes.concat(routers);
  },
};

const actions = {
  GenerateRoutes({commit}, role) {
    return new Promise((resolve, reject) => {
      filterAsyncRouter(asyncRoutes, role).then(data => {
        commit('SET_ROUTERS', data);
        resolve();
      });
    });
  },
};

const getters = {};

//获取后台树
function getBackenTree(systemRole) {
  const param = {
    roleId: getRoleID(),
    systemRole,
  };

  return new Promise(function (resolve, reject) {
    getTree(param)
      .then(function (res) {
        if (res.success) {
          //成功弹数据
          return resolve(res.data);
        } else {
          //失败弹消息
          return reject(res.message);
        }
      })
      .catch(err => {
        return reject(err.message || err);
      });
  });
}

//过滤
function filterAsyncRouter(routes, role) {
  const res = [],
    roletype = getUserRoleType();
  return new Promise(function (resolve, reject) {
    //admin的权限
    if (role.trim() === 'SYS_ADMIN') {
      routes.push({
        path: '*',
        redirect: '/404',
        hidden: true,
      });
      return resolve(routes);
    } else {
      //非admin权限
      getBackenTree(role)
        .then(tree => {
          if (!tree || tree.length === 0) {
            res.push({
              path: '*',
              redirect: '/404',
              hidden: true,
            });
            return resolve(res);
          }
          routes.forEach(route => {
            let tmp = {...route};
            if (tmp.children && tmp.children.length > 0) {
              //区分用户下哪个checked === true
              tmp.children = tmp.children.filter(v => {
                if (v.meta && v.meta.key) return hasPermission(tree, v);
                else return true;
              });

              if (tmp.children[0]) {
                tmp.redirect = tmp.children[0].path;
              }

              if (roletype != 'null') {
                //有角色的处理逻辑
              } else {
                //没有角色另外处理的逻辑
              }
            }
          });
          //404页面必加在最后
          res.push({
            path: '*',
            redirect: '/404',
            hidden: true,
          });
          return resolve(res);
        })
        .catch(err => {
          Message.error(err);
          reject();
        });
    }
  });
}

//走与后台匹配
function hasPermission(backenTree, route) {
  if (route.meta) {
    if (route.meta.key) {
      let [checked, children] = haveChecked(backenTree, route.meta.key);
      //按钮级别
      if (children && children.length > 0) {
        //这里处理按钮的权限
      }
      return checked;
    }
  }
}

//广度优先遍历查找后台树
function haveChecked(backenTree, key) {
  for (let i = 0; i < backenTree.length; i++) {
    let node = backenTree[i];
    if (!node) continue;
    let queue = [node];
    while (queue.length != 0) {
      let n = queue.length;
      for (let j = 0; j < n; j++) {
        let Node = queue.shift();
        if (Node.children)
          for (let k of Node.children) {
            queue.push(k);
          }
        if (Node.key === key) {
          return [Node.checked, Node.children];
        }
      }
    }
  }
  return [false, undefined];
}

export default {
  namespaced: true,
  state,
  mutations,
  actions,
  getters,
};

  写完之后,把它引入到store的index.js中的Module里即可


# router.js

import Vue from 'vue';
import Router from 'vue-router';

Vue.use(Router);

import Layout from '@/layout';

export const asyncRoutes = [
  // 测试页面1
  {
    path: '/test1',
    component: Layout,
    redirect: '/test1',
    meta: {
      role: ['user'],
      key: '1',
    },
    children: [
      {
        path: '/test1',
        name: '测试页面1',
        component: () => import('xxx'),
        meta: {
          title: '测试页面1',
          icon: 'test',
        },
      },
    ],
  },

  // 测试页面2
  {
    path: '/test2',
    component: Layout,
    redirect: '/test2',
    meta: {
      role: ['user'],
      key: '2',
    },
    children: [
      {
        path: '/test2',
        name: '测试页面2',
        component: () => import('xxx'),
        meta: {
          title: '测试页面2',
          icon: 'test',
          key: '3',
        },
        children: [
          {
            //结构相似
          },
        ],
      },
      {
        //结构相似
        path: '/test3',
        name: '测试页面3',
        component: () => import('xxx'),
        meta: {
          title: '测试页面3',
          icon: 'test',
          key: '4',
        },
      },
    ],
  },

  //权限管理
  {
    path: '/permissions',
    component: Layout,
    name: '权限管理',
    redirect: '/permissions/userList',
    meta: {
      title: '权限管理',
      icon: 'auth',
      role: ['admin'],
    },
    children: [
      {
        path: '/permissions/userList',
        name: '用户列表',
        component: () => import('xxx'),
        meta: {
          title: '用户列表',
          icon: 'user',
        },
      },
      {
        path: '/permissions/roleList',
        name: '角色列表',
        component: () => import('xxx'),
        meta: {
          title: '角色列表',
          icon: 'role',
        },
      },
    ],
  },
];

export const constantRoutes = [
  //登录页
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true,
  },

  //404错误页
  {
    path: '/404',
    component: () => import('@/views/404'),
    hidden: true,
  },

  //首页
  {
    path: '/',
    component: Layout,
    redirect: '/home',
    children: [
      {
        path: '/home',
        name: '首页',
        component: () => import('@/views/home/index'),
        meta: {
          title: '首页',
          icon: 'dashboard',
        },
      },
    ],
  },
];

const createRouter = () =>
  new Router({
    // mode: 'history', // require service support
    scrollBehavior: () => ({
      y: 0,
    }),
    routes: constantRoutes,
  });

const router = createRouter();

// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
  const newRouter = createRouter();
  router.matcher = newRouter.matcher; // reset router
}

export default router;

  以上就是涉及的比较关键的代码,其他像获取角色之类的,都事比较简单的,故在此不展开。权限验证的问题可容易可难,看你业务的需求以及和后端人员沟通好返回个什么结构给你,有一个可以让你匹配的变量和可以让你判断是否有权限的字段即可。
   网上有些方法可能说是把路由整个交给后台,就包括那些组件路径,路由路径等。这么做也可以,但是处理路由会变得很麻烦,上述做法仅是把必要的路由放在 constantRoutes 下,把需要权限的路由放到另外一个数组,根据后台返回的数据来过滤即可。为了速度快,这里不建议使用递归。