动态路由 + 路由鉴权 路由鉴权介绍 一般 Vue 的权限控制有两种方案:
路由元信息(meta)
动态加载菜单和路由(addRoutes)
如果一个网站有不同的角色,比如管理员 和普通用户 ,要求不同的角色能访问的页面是不一样的
这个时候我们就可以把所有的页面都放在路由表里 ,只要在访问的时候判断一下角色权限 。如果有权限就让访问,没有权限的话就拒绝访问,跳转到 404 页面。
vue-router
在构建路由时提供了元信息 meta
配置接口,我们可以在元信息中添加路由对应的权限,然后在路由守卫中检查相关权限,控制其路由跳转。
可以在每一个路由的 meta
属性里,将能访问该路由的角色添加到 roles
里。用户每次登陆后,将用户的角色返回。然后在访问页面时,把路由的 meta
属性和用户的角色进行对比,如果用户的角色在路由的 roles
里,那就是能访问,如果不在就拒绝访问。
代码示例 1:
路由信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 routes: [ { path: '/login' , name: 'login' , meta: { roles: ['admin' , 'user' ] }, component: () => import ('../components/Login.vue' ) }, { path: 'home' , name: 'home' , meta: { roles: ['admin' ] }, component: () => import ('../views/Home.vue' ) } ];
页面控制:
1 2 3 4 5 6 7 8 9 10 11 const role = 'user' router.beforeEach((to,from ,next )=> { if (to.meta.roles.includes(role)){ next() }esle{ next({path :"/404" }) } })
代码示例 2
当然也可以用下面的一种方法:
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 [ { path: '' , redirect: '/home' }, { path: '/home' , meta: { title: 'Home' , icon: 'home' } }, { path: '/userCenter' , meta: { title: '个人中心' , requireAuth: true } } ]; function gaurd (to, from , next ) { }
可以在多个路由下面添加这个权限标识,达到控制的目的
只要一切换页面,就需要看有没有这个权限,所以可以在最大的路由下 main.js
中配置
存储信息
一般的,用户登录后会在本地存储用户的认证信息,可以用 token
、cookie
等,这里我们用 token
。
将用户的token
保存到localStorage
里,而用户信息则存在内存store
中。这样可以在vuex
中存储一个标记用户登录状态的属性 auth,方便权限控制。
代码示例
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 { state: { token: window .localStorage.getItem('token' ), auth: false , userInfo: {} }, mutations: { setToken (state, token) { state.token = token window .localStorage.setItem('token' , token) }, clearToken (state) { state.token = '' window .localStorage.setItem('token' , '' ) }, setUserInfo (state, userInfo) { state.userInfo = userInfo state.auth = true } }, actions: { async getUserInfo (ctx, token) { return fetchUserInfo(token).then(response => { if (response.code === 200 ) { ctx.commit('setUserInfo' , response.data) } return response }) }, async login (ctx, account) { return login(account).then(response => { if (response.code === 200 ) { ctx.commit('setUserInfo' , response.data.userInfo) ctx.commit('setToken' , response.data.token) } }) } } }
写好路由表和 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 router.beforeEach(async (to, from , next) => { if (to.matched.some(record => record.meta.requireAuth)) { if (!store.state.auth) { if (store.state.token) { try { const data = await store.dispatch('getUserInfo' , store.state.token); if (data.code === 200 ) { next(); } else { window .alert('请登录' ); store.commit('clearToken' ); next({ name : 'Login' }); } } catch (err) { window .alert('请登录' ); store.commit('clearToken' ); next({ name : 'Login' }); } } else { window .alert('请登录' ); next({ name : 'Login' }); } } else { next(); } } else { next(); } });
上述的方法是基于jwt
认证方式,本地不持久化用户信息,只保存token
,当用户刷新或者重新打开网页时,进入需要登录的页面都会尝试去请求用户信息,该操作在整个访问过程中只进行一次,直到刷新或者重新打开,对于应用后期的开发维护和扩展支持都很好。
动态加载菜单和路由(addRoutes) 有时候为了安全,我们需要根据用户权限或者是用户属性去动态的添加菜单和路由表,可以实现对用户的功能进行定制。vue-router
提供了addRoutes()
方法,可以动态注册路由,需要注意的是,动态添加路由是在路由表中 push 路由,由于路由是按顺序匹配的,因此需要将诸如 404 页面这样的路由放在动态添加的最后。
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const dynamicRoutes = [ { path: '/manage' , name: 'Manage' , meta: { requireAuth: true }, component: () => import ('./views/Manage' ) }, { path: '/userCenter' , name: 'UserCenter' , meta: { requireAuth: true }, component: () => import ('./views/UserCenter' ) } ];
在vuex
中添加userRoutes
数组用于存储用户的定制菜单。在 setUserInfo 中根据后端返回的菜单生成用户的路由表。
1 2 3 4 5 6 7 8 9 10 setUserInfo (state, userInfo) { state.userInfo = userInfo state.auth = true state.userRoutes = dynamicRoutes.filter(route => { return userInfo.menus.some(menu => menu.name === route.name) }) router.addRoutes(state.userRoutes) }
修改菜单渲染
1 2 3 4 5 6 7 8 9 10 11 12 // App.vue <div id ="nav" > <router-link to ="/" > 主页</router-link > | <router-link to ="/login" > 登录</router-link > <template v-for ="(menu, index) of $store.state.userInfo.menus" > | <router-link :to ="{ name: menu.name }" :key ="index" > {{menu.title}} </router-link > </template > </div >
前面铺垫了这么多,终于到本篇博客的主题了,下面我们要自己实现一套「动态生成无限菜单+路由」的方案,也就是第二种路由鉴权。
最终实现的菜单列表 1 2 3 4 5 6 个人中心 Profile.vue 商店 Shop.vue 购物车 Cart.vue └── 购物车列表 CartList.vue ├── 商品 Product.vue └── 彩票 Lottery.vue
基本思路
后端返回路由权限列表(带权限标识,假定为 auth)
前端进行全局的路由拦截,判断自身有没有获取过后端返回的路由列表
如果没有则获取数据并保存到 vuex 中,动态的生成菜单和路由,然后进行 next 跳转
如果有则不再获取,直接 next 跳转
其中第三步是核心点,它还可以细分为下列步骤:
菜单栏往往有多级嵌套,所以得根据后端返回的数据进行转换,最终生成无限级菜单;并在转换过程中找到所有可以有权访问的路由名称清单(根据上文提到的 auth 标识寻找)
前端分别保存两种路由列表,第一种是不需要权限就能访问的,例如首页;第二种是需要等级权限才能访问的路由列表,例如新增 admin 管理员
动态生成路由列表(在第二种路由列表中,通过路由名称清单过滤)
后台返回的 admin 路由权限 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 [ { pid : -1 , path : '/cart' , name : '购物车' , id : 1 , auth : 'cart' }, { pid: 1 , path: '/cart/cart-list' , name: '购物车列表' , id: 4 , auth: 'cart-list' }, { pid: 4 , path: '/cart/cart-list/lottery' , auth: 'lottery' , id: 5 , name: '彩票' }, { pid: 4 , path: '/cart/cart-list/product' , auth: 'product' , id: 6 , name: '商品' }, { pid : -1 , path : '/shop' , name : '商店' , id : 2 , auth : 'shop' }, { pid : -1 , path : '/profile' , name : '个人中心' , id : 3 , auth : 'store' } ];
项目生成 1 2 3 4 5 6 7 8 vue create menu-auth ? Check the features needed for your project: ◉ Babel ◉ Router ◉ Vuex history mode In dedicated config files Save this as a preset for future projects? (y/N) n
移除不必要的文件及其代码
components/HelloWorld.vue
views/About.vue
views/Home.vue 中 .home 标签下所有内容,有关 HelloWorld.vue 相关代码
App.vue 中 #app 下除 router-view
所有内容
router.js 下有关 About.vue 相关代码
后端生成 根目录下创建 server.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 57 58 59 60 61 62 63 64 65 66 let express = require ('express' );let app = express();app.use('*' , function (req, res, next ) { res.header('Access-Control-Allow-Origin' , '*' ); res.header('Access-Control-Allow-Headers' , 'Content-Type' ); res.header('Access-Control-Allow-Methods' , '*' ); res.header('Content-Type' , 'application/json;charset=utf-8' ); next(); }); app.get('/role' , (req, res ) => { res.json({ menuList: [ { pid: -1 , path: '/cart' , name: '购物车' , id: 1 , auth: 'cart' }, { pid: 1 , path: '/cart/cart-list' , name: '购物车列表' , id: 4 , auth: 'cart-list' }, { pid: 4 , path: '/cart/cart-list/lottery' , auth: 'lottery' , id: 5 , name: '彩票' }, { pid: 4 , path: '/cart/cart-list/product' , auth: 'product' , id: 6 , name: '商品' }, { pid: -1 , path: '/shop' , name: '商店' , id: 2 , auth: 'shop' }, { pid: -1 , path: '/profile' , name: '个人中心' , id: 3 , auth: 'profile' } ], buttonAuth: { edit: true } }); }); app.listen(3000 );
终端 node server.js
启动,或者使用 nodemon server.js
热启动。
新建路由所需相关文件 components/menu/ 文件下新建:
cart-list.vue
1 2 3 4 5 6 7 8 9 10 11 12 <template > <div > cart-list <router-view > </router-view > </div > </template > <script > export default {}; </script > <style > </style >
cart.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <template > <div > cart <router-view > </router-view > </div > </template > <script > export default { name: 'cart' }; </script > <style > </style >
lottery.vue
1 2 3 4 5 6 7 8 9 <template > <div > lottery</div > </template > <script > export default {}; </script > <style > </style >
product.vue
1 2 3 4 5 6 7 8 9 <template > <div > product</div > </template > <script > export default {}; </script > <style > </style >
profile.vue
1 2 3 4 5 6 7 8 9 <template > <div > profile</div > </template > <script > export default {}; </script > <style > </style >
shop.vue
1 2 3 4 5 6 7 8 9 <template > <div > shop</div > </template > <script > export default {}; </script > <style > </style >
最后来一个 404 页面,放在 views/
文件夹下:
1 2 3 4 5 6 7 8 9 <template > <div > 404</div > </template > <script > export default {}; </script > <style > </style >
配置路由 来到 router.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 57 58 59 60 61 62 63 64 65 import Vue from 'vue' ;import Router from 'vue-router' ;import Home from './views/Home.vue' ;Vue.use(Router); export default new Router({ mode: 'history' , base: process.env.BASE_URL, routes: [ { path: '/' , redirect: '/home' }, { path: '/home' , name: 'home' , component: Home }, { path: '/cart' , name: 'cart' , component: () => import ('@/components/menu/cart.vue' ), children: [ { path: 'cart-list' , name: 'cart-list' , component: () => import ('@/components/menu/cart-list.vue' ), children: [ { path: 'lottery' , name: 'lottery' , component: () => import ('@/components/menu/lottery.vue' ) }, { path: 'product' , name: 'product' , component: () => import ('@/components/menu/product.vue' ) } ] } ] }, { path: '/profile' , name: 'profile' , component: () => import ('@/components/menu/profile.vue' ) }, { path: '/shop' , name: 'shop' , component: () => import ('@/components/menu/shop.vue' ) }, { path: '*' , component: () => import ('@/views/404.vue' ) } ] });
配置完成后 npm run serve
跑一下项目,分别进入几个路由路径,看是否能正确打开相应页面。
获取后端 admin 权限数据 为了拿到后端返回的数据,我们需要利用 axios 发起异步请求,并把响应回来的数据存放到 vuex 中去,并且还需要对后端数据做一个转换,将其转换为下面格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [ {id : 1 , name : 'a' , pid : -1 }, {id : 2 , name : 'b' , pid : -1 }, {id : 3 , name : 'c' , pid : 1 }, {id : 4 , name : 'd' , pid : 1 } ] ↓↓↓ [ {id : 1 , name : 'a' , pid : -1 , children : [ {id : 3 , name : 'c' , pid : 1 , children : null }, {id : 4 , name : 'd' , pid : 1 , children : null } ]}, {id : 2 , name : 'b' , pid : -1 , children : null } ]
安装 axios
在全局路由守卫中监听是否已获取过路由权限数据 路由权限的数据,为了安全我们需要页面切换时就再次获取,所以可以用全局路由守卫帮助我们实现,逻辑如下:
1 2 3 4 5 6 7 8 9 10 11 12 router.beforeEach(async (to, from , next) => { if (!store.state.hasRules) { await store.dispatch('getMenuList' ); next(); } else { next(); } });
编写 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 36 37 38 39 40 41 import Vue from 'vue' ;import Vuex from 'vuex' ;import axios from 'axios' ;Vue.use(Vuex); let formatMenuList = menuList => { function r (pid ) { return menuList.filter(menu => { if (menu.pid === pid) { let children = r(menu.id); menu.children = children.length ? children : null ; return true ; } }); } return r(-1 ); }; export default new Vuex.Store({ state: { menuList: [], hasRules: false }, mutations: {}, actions: { async getMenuList ( ) { let { data } = await axios.get('http://localhost:3000/role' ); let menuList = data.menuList; menuList = formatMenuList(menuList); console .log(menuList); } } });
但目前有个小问题,即我们在 router.js 中配置了所有路由,然后根据后端返回的权限来动态展示相应菜单,从而实现跳转。但如果用户猜出了不允许他访问的路由,直接修改 url 跳转怎么办?那他不是也能访问了吗?
所以我们需要让后端在返回的数据中,给每个路由对象添加一个 最开始提到的 auth 标识:
1 { pid : -1 , path : '/cart' , name : '购物车' , id : 1 , auth : 'cart' }
如果有 auth 标识,代表当前用户能够访问,如果没有,则表示当前用户不能访问,这样我们前端就能据此动态的删除掉不能访问的路由。
回到 src/store.js
,我们需要把后端这个 auth
属性都保存起来。首先在 state
中加上 authList
属性,用来存储哪些路由能有权限访问:
1 2 3 state: { authList: []; }
然后在 formatMenuList
方法中,提取权限:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 let formatMenuList = menuList => { let arr = []; function r (pid ) { return menuList.filter(menu => { if (menu.pid === pid) { arr.push(menu.auth); let children = r(menu.id); menu.children = children.length ? children : null ; return true ; } }); } return { menuL : r(-1 ), authL : arr }; };
这样改造后,formatMenuList
方法就返回一个对象了,该对象中包含路由菜单和路由权限两个数组。
相应的,我们还得改造使用了 formatMenuList
方法的 action ——> getMenuList
:
1 2 3 4 5 6 7 async getMenuList ({ commit } ) { let { data } =await axios.get('http://localhost:3000/role' ) let { menuL, authL } = formatMenuList(data.menuList) console .log(menuL, authL) commit('set_menuList' , menuL) commit('set_authList' , authL) }
对应的,编写两个 mutation :
1 2 3 4 5 6 7 set_menuList (state, m ) { state.menuList = m }, set_authList (state, a ) { state.authList = a state.hasRules = true }
保存后 console.log 一下,menuList 和 authList 就出来了。
渲染无限菜单 配置 element-ui 安装:
main.js
中进行配置:
1 2 3 import ElementUI from 'element-ui' ;import 'element-ui/lib/theme-chalk/index.css' ;Vue.use(ElementUI);
在 Home.vue 页面中引入静态菜单 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <template > <div class ="home" > <el-menu default-active ="2" class ="el-menu-vertical-demo" > <el-submenu index ="1" > <template slot ="title" > 导航一</template > <el-submenu index ="1-1" > <template slot ="title" > 选项1-1</template > <el-menu-item index ="1-1-1" > 选项1-1-1</el-menu-item > <el-menu-item index ="1-1-2" > 选项1-1-2</el-menu-item > </el-submenu > <el-menu-item index ="1-2" > 选项1-2</el-menu-item > </el-submenu > <el-menu-item index ="2" > 导航二</el-menu-item > <el-menu-item index ="3" > 导航三</el-menu-item > <el-menu-item index ="4" > 导航四</el-menu-item > </el-menu > </div > </template >
刷新页面后就能显示静态的 Element 菜单了。但点击某个菜单选项后浏览器无法跳转,所以我们还得给 <el-menu></el-menu>
标签设置个 router
属性为 true ,这样就能根据菜单的 index
属性进行路由跳转了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <template > <div class ="home" > <el-menu default-active ="2" class ="el-menu-vertical-demo" :router ="true" > <el-submenu index ="1" > <template slot ="title" > 导航一</template > <el-submenu index ="1-1" > <template slot ="title" > 选项1-1</template > <el-menu-item index ="1-1-1" > 选项1-1-1</el-menu-item > <el-menu-item index ="1-1-2" > 选项1-1-2</el-menu-item > </el-submenu > <el-menu-item index ="1-2" > 选项1-2</el-menu-item > </el-submenu > <el-menu-item index ="2" > 导航二</el-menu-item > <el-menu-item index ="3" > 导航三</el-menu-item > <el-menu-item index ="4" > 导航四</el-menu-item > </el-menu > </div > </template >
静态转动态菜单 最后利用后端获取的 admin 权限数据将静态菜单改为动态菜单:
src/views/Home.vue
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 <template > <div class ="home" > <el-menu default-active ="2" class ="el-menu-vertical-demo" :router ="true" > <template v-for ="m in menuList" > <el-submenu index ="1-1" :key ="m.auth" v-if ="m.children" > <template slot ="title" > {{m.name}}</template > <el-menu-item index ="1-1-1" > 选项1-1-1</el-menu-item > <el-menu-item index ="1-1-2" > 选项1-1-2</el-menu-item > </el-submenu > <el-menu-item index ="1-2" :key ="m.auth" v-else > {{m.name}}</el-menu-item > </template > </el-menu > </div > </template > <script > import { mapState } from 'vuex' ; export default { name: 'home' , computed: { ...mapState(['menuList' ]) } }; </script >
无限菜单 仔细观察上方的菜单格式,会发现下面这段代码会重复出现:
1 2 3 4 5 6 7 8 <el-submenu index ="1-1" :key ="m.auth" v-if ="m.children" > <template slot ="title" > {{m.name}}</template > <el-menu-item index ="1-1-1" > 选项1-1-1</el-menu-item > <el-menu-item index ="1-1-2" > 选项1-1-2</el-menu-item > </el-submenu > <el-menu-item index ="1-2" :key ="m.auth" v-else > {{m.name}}</el-menu-item >
所以我们把它提取出去单独渲染,这样就能实现无限菜单了:
新建 src/views/ReSubMenu.vue
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 <template > <el-submenu :index ="data.path" > <template slot ="title" > <router-link :to ="data.path" > {{data.name}}</router-link > </template > <template v-for ="c in data.children" > <ReSubMenu :key ="c.auth" v-if ="data.children" :data ="c" > </ReSubMenu > <el-menu-item :key ="c.auth" v-else :index ="c.path" > {{c.name}} </el-menu-item > </template > </el-submenu > </template > <script > export default { name: 'ReSubMenu' , props: { data: { type: Object , default : () => ({}) } } }; </script >
然后在父组件 Home.vue
中使用:
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 <template > <div > <el-menu default-active ="2" class ="el-menu-vertical-demo" :router ="true" > <template v-for ="m in menuList" > <ReSubMenu :data ="m" :key ="m.auth" v-if ="m.children" > </ReSubMenu > <el-menu-item v-else :key ="m.auth" :index ="m.path" > {{m.name}} </el-menu-item > </template > </el-menu > <router-view > </router-view > </div > </template > <script > import { mapState } from 'vuex' ; import ReSubMenu from './ReSubMenu' ; export default { name: 'home' , computed: { ...mapState(['menuList' ]) }, components: { ReSubMenu } }; </script >
根据权限动态添加路由 实现了无限菜单后,我们接下来就要根据之前保存的 authList
实现动态路由了。
第一步就是把 router.js
中的路由列表分成两部分,一部分是不需要权限就能访问的,另一部分是需要根据权限列表动态生成的,我们首先来抽离之前在 routes 中的路由列表,拷贝一份后存到两个变量中,其中 authRoutes
需要 export 导出,等会 store.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 57 58 59 let defaultRoutes = [ { path: '/' , redirect: '/home' }, { path: '/home' , name: 'home' , component: Home }, { path: '*' , component: () => import ('@/views/404.vue' ) } ]; export let authRoutes = [ { path: '/cart' , name: 'cart' , component: () => import ('@/components/menu/cart.vue' ), children: [ { path: 'cart-list' , name: 'cart-list' , component: () => import ('@/components/menu/cart-list.vue' ), children: [ { path: 'lottery' , name: 'lottery' , component: () => import ('@/components/menu/lottery.vue' ) }, { path: 'product' , name: 'product' , component: () => import ('@/components/menu/product.vue' ) } ] } ] }, { path: '/profile' , name: 'profile' , component: () => import ('@/components/menu/profile.vue' ) }, { path: '/shop' , name: 'shop' , component: () => import ('@/components/menu/shop.vue' ) } ];
然后把原本 routes
列表中的数组删除,改为 defaultRoutes
:
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 import Vue from 'vue' ;import Router from 'vue-router' ;import Home from './views/Home.vue' ;Vue.use(Router); let defaultRoutes = [ { path: '/' , redirect: '/home' }, { path: '/home' , name: 'home' , component: Home }, { path: '*' , component: () => import ('@/views/404.vue' ) } ]; export let authRoutes = [ { path: '/cart' , name: 'cart' , component: () => import ('@/components/menu/cart.vue' ), children: [ { path: 'cart-list' , name: 'cart-list' , component: () => import ('@/components/menu/cart-list.vue' ), children: [ { path: 'lottery' , name: 'lottery' , component: () => import ('@/components/menu/lottery.vue' ) }, { path: 'product' , name: 'product' , component: () => import ('@/components/menu/product.vue' ) } ] } ] }, { path: '/profile' , name: 'profile' , component: () => import ('@/components/menu/profile.vue' ) }, { path: '/shop' , name: 'shop' , component: () => import ('@/components/menu/shop.vue' ) } ]; export default new Router({ mode: 'history' , base: process.env.BASE_URL, routes: defaultRoutes });
再回到 main.js
中的全局钩子函数,我们需要创建一个 action
来动态生成需要权限才能访问的路由:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 router.beforeEach(async (to, from , next) => { if (!store.state.hasRules) { await store.dispatch('getMenuList' ); let r = await store.dispatch('getAuthRoute' ); router.addRoutes(r); next(); } else { next(); } });
最后在 store.js
中实现 getAuthRoute
这个 action:
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 { authRoutes } from './router' Vue.use(Vuex) ... let getNeedRoutes = (auth ) => { function r (authRoutes ) { return authRoutes.filter(route => { if (auth.includes(route.name)) { if (route.children) { route.children = r(route.children) } return true ; } }) } return r(authRoutes); } export default new Vuex.Store({ ... actions: { .. async getAuthRoute ({ commit, state } ) { let r = getNeedRoutes(state.authList) return r } } })
到此为止就实现了一套 动态路由 + 路由鉴权
功能。
配套代码 动态路由+路由鉴权