应用场景

后端管理系统有多个角色,每个角色的权限不同可访问的页面也不同;
这时前端就需要判断用户角色权限,动态加载左侧菜单栏;

实现思路

第一种: 通过vue提供的addRoutes,动态添加路由 (三级菜单时建议使用)

1 ) 创建vue实例的时候将vue-router挂载,但这个时候vue-router挂载一些登录或者不用权限的公用的页面

2 ) 当用户登录后,获取用role,将role和路由表每个页面的需要的权限作比较,生成最终用户可访问的路由表

3 ) 调用router.addRoutes(store.getters.addRouters)添加用户可访问的路由

4 ) 使用vuex管理路由表,根据vuex中可访问的路由渲染侧边栏组件

第二种: 通过vuex操作数据,判断路由表,通过v-if 判断是否展示 (本文采用此方式)

1 ) 创建vue实例的时候将vue-router挂载,但这个时候vue-router挂载登录页面

2 ) 用户登录,获取role,将role和路由表每个页面的需要的权限作比较,存在添加show=true,反之show=false,返回新路由表

3 ) 使用vuex管理路由表,左侧菜单栏,动态渲染返回的路由表,通过v-if指令判断是否展示

实现过程

首先确定项目中是否安装vuex,没有先 npm insatll vuex --save
在src下创建store文件夹,然后创建一个index.js ,配置如下:

import Vue from "vue";
import Vuex from "vuex";
import { apiUserAuthorize } from "../api/login";
import bus from "../components/common/bus";
import { dealGetDirs, checkPermission } from "../utils/Recursive";  // 核心文件,遍历路由表方法
Vue.use(Vuex);

export default new Vuex.Store({
  // 根据环境 配置 生产环境不使用严格模式
  strict: process.env.NODE_ENV !== "development",
  state: {
    token: null,  // 标识
    userInfo: null,  // 用户信息
    userDirs: null,  // 用户权限
  },
  getters: {
    // 返回新的路由表
    userDirsItems: state => {
      // 遍历账户权限 数组 => 对象
      const arr = state.userDirs;
      const result = dealGetDirs(arr);
      // 本地路由配置格式
      let items = [
        {
          icon: "el-icon-lx-goods",
          index: "goods",
          title: "商品管理",
          subs: [
            {
              index: "supplierList",
              title: "供应商"
            },
            {
              index: "categoryList",
              title: "商品名录"
            },
          ]
        }
      ];
      // 账户权限与本地路由对比,路由 ? show = true :show = false
      items = checkPermission(items, result);
      return items;
    },
  },
  mutations: {
    // 添加token
    setToken(state, token) {
      state.token = token;
    },
    // 移除token
    setTokenNull(state) {
      state.token = null;
    },
    // 添加用户信息
    setUserInfo(state, obj) {
      state.userInfo = obj;
    },
    // 移除用户信息
    setUserInfoNull(state) {
      state.userInfo = null;
    },
    // 添加权限
    setDirs(state, obj) {
      state.userDirs = obj;
    },
    // 移除权限
    setDirsNull(state) {
      state.userDirs = null;
    },
  },
  actions: {
    // 设置token
    actionSetToken({ commit }, { token }) {
      return new Promise((ro, rj) => {
        try { commit("setToken", token); ro(); }
        catch (error) {  rj(error); }
      });
    },
    // 设置userinfo
    actionSetUserinfo({ commit }, { users }) {
      return new Promise((ro, rj) => {
        try { commit("setUserInfo", users); ro(); } 
        catch (error) {  rj(error); }
      });
    },
    // 退出登录
    actionLogout({ commit }) {
      commit("setTokenNull");
      commit("setUserInfoNull");
      commit("setDirsNull")
      sessionStorage.clear();
    },
    // 获取权限
    actionGetDirs({ commit }) {
      new Promise((ro, rj) => {
        // 调用获取权限接口
        apiUserAuthorize().then(res => {
          commit("setDirs", res);
          bus.$emit("collapse", false);  //  菜单栏折叠
          ro(res);
        }).catch(err => {
            rj(err);
        });
      });
    },
  }
});

配置好store => index.js之后,在main.js中,引用挂载stroe,
然后利用路由守卫,跳转拦截 传送门:VUE-导航守卫

//引入store
import store from './store'

// 挂载
new Vue({
  router,
  i18n,
  store,
  render: h => h(App)
})

/使用钩子函数对路由进行权限跳转
router.beforeEach((to, from, next) => {
  let token = store.state.token;
  let users = null;
  const isLogin = !!store.state.userInfo && !!store.state.userDirs;
  if (!token) {
    token = sessionStorage.getItem("ttoken");
    users = sessionStorage.getItem("userInfo");
    // 异步操作store
    store.dispatch('actionSetToken', { token: token });
    store.dispatch('actionSetUserinfo', { users: JSON.stringify(users) })
  }

  if (!token && to.path !== '/login') {
    next('/login');
  } else if (!isLogin && to.path !== "/login") {
    // 获取当前登录用户权限
    store.dispatch("actionGetDirs").then(
      () => { next(); }
    ).catch(
      err => { next("/login"); }
    );
  } else {
    next();
  }
})

此时是无法登陆的,我们需要在登陆页面写点东西

apiSignin({
    username: ...,
    password: ...,
    imgcodes: ...
}).then(res = >{
    sessionStorage.setItem("ttoken", res.access_token);
    sessionStorage.setItem("userInfo", JSON.stringify(res.userInfo));
    this.$router.push("/");
}).
catch(err = >{
    this.$message({
        message: err.message,
        type: "warning"
    });
});

到此基本配置,逻辑工作就完成了,接下来就是对比路由表了, 接下来是 关键步骤 !!!

// 1.递归处理导航信息 数组 => 对象
const result = {};
export const dealGetDirs = arr => {
  if (arr instanceof Array) {
    arr.map(item => {
      result[item.name] = item.name;
      // itemMenu是获取到的二级权限
      if (item.hasOwnProperty("itemMenu") && item.itemMenu) {
        dealGetDirs(item.itemMenu);
      }
    });
  }
  return result;
};

// 2. 对比导航信息
export const checkPermission = (items, DirsResult) => {
  items.map(item => {
    item.show = DirsResult[item.title] ? true : false;
    // subs是store中的items(slidebar的子菜单配置)
    if (item.hasOwnProperty("subs") && item.subs) {
      checkPermission(item.subs, DirsResult);
    }
  });
  return items;
};

此时已经拿到了新的路由表,接下来就是去slidebar菜单栏,渲染出来就可以了

<template>
  <div class="sidebar">
    <el-menu class="sidebar-el-menu" :default-active="onRoutes" :collapse="collapse" unique-opened router>
      <template v-for="item in items">
        <template v-if="item.subs">
          <el-submenu :index="item.index" :key="item.index" v-if="item.show">
            <template slot="title">
              <i :class="item.icon"></i>
              <span slot="title">{{ item.title }}</span>
            </template>
            <template v-for="subItem in item.subs">
              <el-menu-item v-if="subItem.show" :index="subItem.index" :key="subItem.index">
                <i class="icon"></i>
                {{ subItem.title }}
              </el-menu-item>
            </template>
          </el-submenu>
        </template>
        <template v-else>
          <el-menu-item :index="item.index" :key="item.index" v-if="item.show">
            <i :class="item.icon"></i>
            <span slot="title">{{ item.title }}</span>
          </el-menu-item>
        </template>
      </template>
    </el-menu>
  </div>
</template>

<script>
import bus from "../common/bus";
export default {
  data() {
    return {
      collapse: false,
      roleArr: {} // 对于权限的处理后
    };
  },
  computed: {
    onRoutes() {
      return this.$route.path.replace("/", "");
    },
    // 返回的新的路由表
    items: function() {
      return this.$store.getters.userDirsItems;
    }
  },
  created() {
    // 通过 Event Bus 进行组件间通信,来折叠侧边栏
    bus.$on("collapse", msg => {
      this.collapse = msg;
    });
    // 侧边栏更新
    bus.$on("sidebarup", msg => {
      this.getPermission();
    });
  },
  methods: {
    getPermission() {
      const userDirs = this.$store.state.userDirs;
      this.dealPermission(userDirs, this.roleArr);
      this.checkPermission(this.items);
    },
    checkPermission(items) {
      items.map(item => {
        item.show = this.roleArr[item.title];
        if (item.hasOwnProperty("itemMenu")) {
          this.checkPermission(item.itemMenu);
        }
      });
    },
    dealPermission(arr, result) {
      if (arr instanceof Array) {
        arr.map(item => {
          result[item.title] = item.show;
          if (item.hasOwnProperty("subs")) {
            this.dealPermission(item.subs, result);
          }
        });
      }
    },
    // 侧边栏折叠
    collapseChage() {
      this.collapse = !this.collapse;
      bus.$emit("collapse", this.collapse);
    }
  }
};
</script>

教程指南

手把手教你用VUE撸后台-花裤衩