Vue权限动态路由菜单

一. 什么是权限动态路由?

权限动态路由:是用户登录的时候,根据用户信息,判断用户角色,动态的加载当前角色的路由。一般管理后台用到的比较多。

二. 预先了解。

  1. Vue-Router ====> beforeEach 是什么?
    Router.berforeEach是什么:路由守卫之全局前置守卫,每次切换页面的时候都会经过此页面,他接受三个参数,分别是
    to: 即将要进入的目标 路由对象{path:’/‘,omponent:’’}
    from: 当前导航正要离开的路由
    next: 回调钩子,逻辑的最后一定要调用,不然页面会卡住,
    next()方法还可以传递路由参数进行路由重定向
  2. Vue-Router ====> addRouter 是什么?
    addRouter:动态添加更多的路由规则。参数必须是一个符合 router 选项要求的数组。
    一般前端的路由表写死了,就无法添加了 ,通过addRouter方法可以动态的添加新的路由,当然你得确保有页面 不然会报错的。
  3. Vuex
    使用Vuex,存储后台根据权限返回来的路由表,并持久化存储。以保证页面刷新数据不会丢失,不频繁的请求权限路由表
  4. element-ui
    这个对照着官方文档撸传送门

三. 搭建布局页面(layout),模块页面(vivew)

  1. 布局页面:在src/components目录下新建layout目录并添加以下组件,最后页面显示成右侧(文末附上git地址)
    layout1
  2. 模块页面: 创建3个模块,添加7个页面
    2个模块登录后加载,一个模块本地合并登录后的一起加载 (project=>本地路由,setting与video登陆后返回)
    layout2

四. 添加路由

添加路由模板文件(router.js) 和 环境路由模板路径处理文件
模板文件用于本地化测试,本地化测试成功再使用接口请求
layout3
layout4

五. 使用Vuex

在Vuex中设置路由变量,登录验证等;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import Vue from "vue";
import Vuex from "vuex";
import { GET_SESSION_STORAGE, SET_SESSION_STORAGE } from "@/utils/sessionStorage.js";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
router: GET_SESSION_STORAGE("router") || [], // 用户权限路由列表
navRouter: GET_SESSION_STORAGE("navRouter") || [], // 从后台获取的导航栏路由
isLogin: GET_SESSION_STORAGE("login") || false // 判断用户是否登录
},
getters: {
// 将本地路由与后台返回的路由结合, 渲染侧边栏组件
comRouter: state => state.router,
// 后台返回的路由列表
comNavRouter: state => state.navRouter,
comLogin: state => state.isLogin

},
mutations: {
SET_COM_LOGIN: (state, params) => {
SET_SESSION_STORAGE("login", params);
state.isLogin = params;
},
// 修改路由列表
SET_ROUTER_CHANGE: (state, params) => {
SET_SESSION_STORAGE("router", params);
state.router = params;
},
// 将本地路由与后台返回的路由结合, 渲染侧边栏组件
SET_NAV_ROUTER: (state, params) => {
SET_SESSION_STORAGE("navRouter", params);
state.navRouter = params;
}
}
});

六. 登录保存,路由信息。

这时候我们创建登录页面
当我们点击登录的时候 模拟从后台获取到的路由数据 navRouter 并保存在Vuex中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// src/views/login
<!--登录页面 -->
<template>
<div class="wrap">
<div class="login ">
<el-input v-model="input" placeholder="请输入账号"></el-input>
<el-input v-model="pwad" placeholder="请输入密码"></el-input>
<el-button @click="handleSubmit">登录</el-button>
</div>
</div>
</template>

<script>
import navRouter from "@/router/router.js";
import { mapMutations } from "vuex";
export default {
data () {
return {
input: "",
pwad: ""
};
},
methods: {
// 用户登录
handleSubmit () {
// 登录的时候将路由写入navRouter
this.SET_NAV_ROUTER(navRouter);
this.SET_COM_LOGIN(true);
this.$router.push("/");
},
...mapMutations({
SET_NAV_ROUTER: "SET_NAV_ROUTER",
SET_COM_LOGIN: "SET_COM_LOGIN"
})
}
};
</script>

七. 登录成功 处理路由数据,并将最终的路由保存在Vuex中

在src目录下添加路由动态加载js(promission.js => 权限加载js)生成最后的侧边栏路由导航
这里是重点,这里写了大量的注释就是方便大家可以看懂,这里看懂,就成功了一半了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import router from "./router/index";
// Layout 是布局组件,不在后台返回,在文件里单独引入
import Layout from "@/components/Layout/layout.vue";

import Store from "./store.js";// 获取Vuex实例

// 默认加载的路由,不需要后台返回的路由 可以在router文件夹中添加默认路由
import defaultRouter from "@/router/defaultRouter.js";

// process.env.NODE_ENV 根据不同的环境 加载不同的路由模板处理文件
const _import = require("./router/config/_import_" + process.env.NODE_ENV);

var getRouter; // 用来保存后台拿到的路由

router.beforeEach((to, from, next) => {
// 判断 数据仓库中有没有 用户登录返回的路由列表 有
if (Store.getters.comNavRouter && Store.getters.comNavRouter.length > 0) {
if (!getRouter) { // 判断有没有路由权限 没有 路由权限 重新请求|从Vuex中获取
getRouter = Store.getters.comNavRouter;// 拿到路由
routerGo(to, next);// 调用动态添加路由的方法
} else { // 判断有没有路由列表 有 允许进入下个这里是第二个beforeEach
next();
}
} else { // 判断 数据仓库中有没有 用户登录返回的路由列表 没有 ===>去登陆页
next();
}
});
function routerGo (to, next) {
// 过滤路由 调用filterAsyncRouter 数据模板处理方法 返回Vue-router可以识别数据
getRouter = filterAsyncRouter(getRouter);
// 动态添加所有的的路由
router.addRoutes(Store.getters.comRouter);
// 调用next()放行
next({ ...to, replace: true });
}

// 遍历后台传来的路由字符串,转换为组件对象 递归方法
function filterAsyncRouter (asyncRouterMap) {
const accessedRouters = asyncRouterMap.filter(route => {
if (route.component) { // 路由有component
if (route.component === "Layout") { // 判断路由是Layout 布局组件,将上方引用的Layout布局组件放进去
route.component = Layout;
} else { // 路由不是Layout组件 二级也main
route.component = _import(route.component);
}
}
// 判断当前的路由对象中是否含有children 有再次调用本方法 递归调用 直到没有
if (route.children && route.children.length) {
route.children = filterAsyncRouter(route.children);
}
return true;
});

Store.commit("SET_ROUTER_CHANGE", defaultRouter.concat(Store.getters.comNavRouter));// 合并整个路由表(默认路由+后台返回的路由) 保存在Vuex中
return accessedRouters;
}

八. 最后在Nva组件中渲染路由列表

有想法的同学可以将侧边栏组件设置为递归组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!-- Layout侧边栏组件页面 -->
<template>
<el-menu default-active="" class="el-menu-vertical-demo" router @open="handleOpen" @close="handleClose" background-color="#545c64" text-color="#fff" active-text-color="#ffd04b">
<!-- 一个模块 -->
<el-submenu :index="value.path" v-for="(value,index) in comRouter" :key="'comRouter'+index">
<template slot="title">
<i class="el-icon-location"></i>
<span>{{value.meta.title}}</span>
</template>
<!-- 一个模块的每个组成页 -->
<el-menu-item-group v-for="(item,i) in value.children" :key="'children'+i">
<el-menu-item :index="value.path+'/'+item.path">{{item.meta.text}}</el-menu-item>
</el-menu-item-group>
</el-submenu>
</el-menu>
</template>

<script>
import { mapGetters } from "vuex";
export default {
methods: {
handleOpen (key, keyPath) {
console.log(key, keyPath);
},
handleClose (key, keyPath) {
console.log(key, keyPath);
}
},
computed: {
...mapGetters({
comRouter: "comRouter"
})
}
};
</script>

<style scoped lang="scss">
.el-menu-vertical-demo{
width: 100%;
}
</style>

九. 最后的演示效果+Git地址

layout
GitHub地址
GitHub:https://github.com/kemaosen/vue-admin

十. 常见错误

  1. Uncaught (in promise) undefined
    1
    运行 npm install npm i vue-router@3.0 -S

layout

  1. 报错:Cannot find module ‘xxx/xxx/xx.vue’
    多半是后台返回的数据与项目的文件名不一致,本地找到到’xxx/xx/xx/xx.vue’这个文件,和后台协调好数据不要写错注意大小写
    layout
  2. asyncRouterMap.filter is not a function
    这种xxx.filter不是一个方法 filter是数组的API ,换句话说只允许数组调用Array.filter.
    全局搜索asyncRouterMap这个参数(好吧 他就在promission.js中) 在filterAsyncRouter方法中把参数asyncRouterMap打印出来看看是不是数组 不是逐步向上推演代码找到问题,并解决他。
    layout
  3. 为什么刷新后,页面没有重置?
    登录后数据存在session中,不关闭当前对话框数据就会一直存在浏览器中,可以清除浏览器缓存,或点击头部退出按钮.清空登录数据。

    十一. 参考

    Vue动态菜单(路由)的实现方案(beforeEach+addRoutes+elementUI)
    Vue 动态路由的实现(后台传递路由,前端拿到并生成侧边栏)