vue如何根据权限生成动态路由、导航栏
作者:秋刀鱼笛滋味
这篇文章主要介绍了vue如何根据权限生成动态路由、导航栏,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
基本思路
1、创建vueRouter,用公共路由实例化
2、创建需要根据权限筛选的路由对象(在路由对象,添加必要的权限判断字段)
3、登录完成,由后端配合返回当前用户的权限集合
4、筛选出有权限的路由对象,利用vueRouter的addRoutes方法,生成完整路由
5、处理刷新页面导致vueRouter重新实例化导致路由对象不完善 (利用router.beforeEach导航守卫,,利用addRoutes()完善 路由对象 )
6、侧边导航栏相关代码
相关代码
根据上面的顺序
1、如下
Vue.use(Router) // 公共路由 export const publicRoutes = [ { path: '/', name: 'login', component: login, meta: { title: '登录' } } ] // 需要根据权限筛选的路由 export const asyncRoutes = [ ...Home, ...Seting, ...CRM, { path: '*', component: login, meta: { hidden: true }, redirect: '/' } ] const vr = new Router({ mode: 'history', routes: publicRoutes })
2、以seting模块为例,role为判断是的权限字段,角色有这个字段对应的值就有当前页面的权限
import Layout from '@/views/layout/layout.vue' const account = r => require.ensure([], () => r(require('@/views/seting/account/account.vue')), 'seting'); const logs = r => require.ensure([], () => r(require('@/views/seting/logs/logs.vue')), 'seting'); const role = r => require.ensure([], () => r(require('@/views/seting/role/role.vue')), 'seting'); const Seting = [ { path: '/seting', component: Layout, redirect: '/seting/account', meta: { title: '系统设置', role: '123c6c6514d416472e640bc3f49297c550', icon: 'icon-xitong' }, children: [ { path: 'account', name: 'account', component: account, meta: { title: '账号管理', role: '1325cdeb897cc7f8e951d647de9b1d8e11', } }, { path: 'logs', name: 'logs', component: logs, meta: { title: '日志管理', role: '14bfbb0337ad3e7e2c9fc101294c3fe645', } }, { path: 'role', name: 'role', component: role, meta: { title: '角色管理', role: '1559d1c05d15a0dce5549b8bf5a58c0cf9', } } ] } ] export default Seting
如果有一些详情页不需要在导航列表展示还可以添加字段,在生成导航栏时去掉
eg:
{ path: 'addJoiner', name: 'addJoiner', component: addJoiner, meta: { hidden: true, // 隐藏字段 title: '***详情页', role: '14bfbb0337ad3e7e2c9fc101294c3fe645', } },
3、登录获取权限集合和基本信息
// 登录 login () { this.rememberPassword() this.$refs.form.validate((valid) => { if (valid) { this.loading = true let params = { login_name: this.form.user, password: this.form.password, code: this.form.yanzhenma, } this.$api.post('api/user/login', params).then(res => { if (res.err_code === 1) { // 把用户的基本信息储存在vuex,中 this.$store.dispatch('setBaseInfo', res.data).then(() => { // 获取有权限的路由表,添加到路由 router.addRoutes(this.$store.getters.addRouters) this.$router.push({ name: 'home' }) }) } this.loading = false }) } })
部分vuex代码
如果不太理解,点击下面链接:
actions.js
// import api from '@/api' const actions = { setBaseInfo ({ commit }, data) { return new Promise(resolve => { commit('set_userInfo', data.userInfo) commit('set_token', data.token) commit('set_roles', data.menus) // 把基本信息保存在本地防止刷新之后丢失 sessionStorage.setItem('baseInfo', JSON.stringify(data)) resolve() }) } } export default actions
mutations.js
const setStorage = (key, value) => { if (typeof (value) === 'object') { value = JSON.stringify(value) } sessionStorage.setItem(key, value) } /* * 避免刷新之后vuex被重置,在sessionStorage做一个备份 */ const mutations = { set_userInfo (state, payload) { state.userInfo = payload setStorage('userInfo', payload) }, set_token (state, payload) { state.token = payload setStorage('token', payload) }, set_roles (state, payload) { state.roles = payload setStorage('roles', payload) }, set_breadcrumb (state, payload) { state.breadcrumb = payload setStorage('breadcrumb', payload)/* */ }, changeCollapsed (state, payload) { state.isCollapsed = payload } } export default mutations
getters.js
import createdRoutes from '@/utils/createdRoutes.js' import { asyncRoutes } from '@/router/index.js' let getStoryage = (item) => { let str = sessionStorage.getItem(item) return JSON.parse(str) } const getters = { get_userInfo: (state) => { return state.userInfo ? state.userInfo : getStoryage('userInfo') }, get_token: (state) => { return state.token ? state.token : sessionStorage.getItem('token') }, get_roles: (state) => { return state.roles.length ? state.roles : getStoryage('roles') }, addRouters: (state, getters) => { let routes = createdRoutes(asyncRoutes, getters.get_roles) return routes }, get_breadcrumb: (state, getters) => { return state.breadcrumb.length ? state.breadcrumb : getStoryage('getStoryage') } } export default getters;
4、核心的筛选需要权限的路由方法:createdRoutes()
也就是3的getters,用到的方法,毫无保留奉上
/** * 判单当前的路由对象是否在登录人的权限之内 * @param {Array} roles 权限 * @param {Object} route 路由 */ function hasPermission (roles, route) { if (route.meta && route.meta.role) { // 路由需要权限就要在权限数组里面判断 return roles.includes(route.meta.role) } else { // 不需要权限就直接通过 return true } } /** * 根据接口获取的权限列表动态生成当前用户的侧边导航栏,返回通过权限验证的路由数组 * @param {Array} asyncRoutes 需要过滤的路由 * @param {Array} roles 权限 */ function createdRoutes (asyncRoutes, roles) { const accessedRouters = asyncRoutes.filter(route => { if (hasPermission(roles, route)) { // 当前路由通过权限验证直接通过 if (route.children && route.children.length) { // 当前路由有子路由,就递归验证 route.children = createdRoutes(route.children, roles) } return true } return false }) return accessedRouters } export default createdRoutes
5、处理刷新带来的问题
其实这里的代码是连接1的,注意注释
// 全局的导航守卫 vr.beforeEach((to, from, next) => { // 刷新页面之后导致vue-router和vuex重置,路由丢失,利用的就是刷新后vuex的state被重置判断 if (to.name !== 'login' && !store.state.token) { // 避免直接不登陆进页面 if (!sessionStorage.getItem('token')) { location.href = '/' return } let data = JSON.parse(sessionStorage.getItem('baseInfo')) store.dispatch('setBaseInfo', data).then(() => { vr.addRoutes(store.getters.addRouters) }) } // 设置面包屑导航 let breadcrumb = to.matched.filter(item => item.meta.title) if (breadcrumb.length) { breadcrumb = breadcrumb.map(item => item.meta.title) store.commit('set_breadcrumb', breadcrumb) } // 设置title document.title = to.meta.title next() })
6、侧边导航栏的完整代码
还是遍历的根据权限生成的路由表,干掉一些需要隐藏的详情页之类,
这里的代码和过滤l路由的核心函数,要根据自己的业务做相应的处理
element-ui的menu
<template> <el-menu class="el-menu-vertical-demo" :collapse="isCollapsed" background-color="#545c64" :default-active='activeIndex' text-color="#fff" active-text-color="#7EA8F5"> <section v-for="(item,index) in addRouters" :key="item.name" :class="isCollapsed ? 'collapsed':''"> <!-- 有子菜单 --> <el-submenu :index=" `${index+1}`" v-if="!item.meta.hidden && item.children && item.children.length"> <template slot="title"> <i :class="`icon iconfont ${item.meta.icon}`"></i> <span slot="title">{{item.meta.title}}</span> </template> <section v-for="(item2,index2) in item.children" :key="item2.name"> <!-- 二级菜单有子菜单 --> <el-submenu :index="`${index+1}-${index2+1}`" v-if="item2.children && item2.children.length" class="sub2"> <template slot="title"> <span slot="title">{{item2.meta.title}}</span> </template> <!-- 三级菜单 --> <el-menu-item v-for="(item3,index3) in item2.children" v-if="!item3.meta.hidden" :index="item3.name" :key="index3" @click.native="$router.push({name:item3.name})"> <span slot="title">{{item3.meta.title}}</span> </el-menu-item> </el-submenu> <!-- 二级菜单无子菜单 --> <!-- 不是隐藏的,详情页隐藏 --> <el-menu-item :index="item2.name" v-else-if="!item2.meta.hidden" @click.native="$router.push({name:item2.name})"> <span slot="title">{{item2.meta.title}}</span> </el-menu-item> </section> </el-submenu> <!-- 无子菜单 --> <el-menu-item v-else-if="item.meta.hidden && item.children && item.children.length" :index="item.children[0].name" @click.native="$router.push({name:item.children[0].name})" class="item"> <i :class="`iconfont ${item.children[0].meta.icon}`"></i> <span slot="title">{{item.children[0].meta.title}}</span> </el-menu-item> </section> </el-menu> </template>
<script> import { mapGetters } from 'vuex' export default { props: { isCollapsed: { type: Boolean, default: false } }, computed: { ...mapGetters(['addRouters']), activeIndex () { //集火的菜单 return this.$route.name } } } </script>
<style lang="scss" scoped> section { /deep/ .el-submenu__title { .icon { margin-right: 10px; } i { color: white; font-size: 14px; } } /deep/ .el-menu-item { padding-left: 50px !important; } /deep/ .el-menu-item.item { padding-left: 19px !important; i { color: white; font-size: 14px; margin-right: 12px; } } /deep/ .el-submenu .el-menu-item { min-width: 0; } /deep/ .el-submenu.sub2 .el-submenu__title { padding-left: 50px !important; i { margin-right: 0px; } } /* /deep/ .el-submenu.sub2 .el-menu-item { text-indent: 12px; } */ } .collapsed { width: 50px; /deep/ .el-submenu__title { .el-icon-arrow-right { display: none; } span[slot="title"] { display: none; } } } </style>
路由图
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。