最近在写一个基于vue的动态权限,由于没有在前端做过动态权限,看了一下vue-element-admin是前端写的动态路由,就试着仿写了一下。
首先是路由表
// 这是在 router 的index.js中
export const constantRouterMap = [ // 静态路由,所有用户都能访问的路由
{
hidden: true,
path: "/login",
component: Login
},
{
hidden: true,
path:"/404",
component: NotFind
}
]
export const asyncRouterMap = [ // 动态路由,只有指定meta.role里面的角色能访问
{
path: "/",
icon:"iconfont icon-yibiaopan",
redirect: "/dashboard",
component: Home,
children: [{
path: "/dashboard",
name: "首页",
icon:"iconfont icon-yibiaopan",
component: Dashboard,
meta: {
activePath: "/dashboard"
}
}]
},
{
path: "/system_manage",
redirect: "/system_manage/manage_list",
name: "系统管理",
icon: "iconfont icon-xitongguanli-",
meta: {
role: ['Root'],
breadCrumbRole: ["Root"]
},
component: Home,
children: [
{
path: "/system_manage/manage_list",
name: "系统管理员列表",
icon: "iconfont icon-xitongguanliyuan",
meta: {
role: ['Root'],
activePath: "/system_manage/manage_list",
breadCrumbRole: ["Root"]
},
component: AdminList
},
{
path: "/system_manage/role_list",
name: "角色列表",
icon: "iconfont icon-role-list",
meta: {
role: ['Root'],
activePath: "/system_manage/role_list",
breadCrumbRole: ["Root"]
},
component: RoleList
},
{
path: "/system_manage/power_list",
name: "权限列表",
icon: "iconfont icon-quanxianliebiao",
meta: {
role: ['Root'],
activePath: "/system_manage/power_list",
breadCrumbRole: ["Root"]
},
component: PowerList
}
]
},
{
hidden: true,
path: "*",
redirect: '/404'
}
]
对于最终的路由表处理我和vue-element-admin一样,创建了一个vuex模块来管理,其中做了一些更改
import { asyncRouterMap, constantRouterMap } from "router/index"
/**
*
* @param [用户拥有的角色] roles
* @param [路由需要的角色] route
*/
function hasPermission(roles, route) {
// 如果当前路由含有 meta.role 属性,则判断用户的角色有没有至少一个在所需角色里面
if (route.meta && route.meta.role) {
return roles.some(role => route.meta.role.indexOf(role) >= 0)
} else {
return true
}
}
const state = {
routers: constantRouterMap, // 静态路由
addRouters: [], // 要添加的动态路由,之后会被拼接到上面的routers中
isAddRoutes: false // 设置是否已加载动态路由的状态,对于刷新之后空白的处理我是在路由的beforeEach中处理的,先判断有没有动态添加。
}
const getters = {
routers() {
return state.routers
},
isAddRoutes() {
return state.isAddRoutes
}
}
const mutations = {
SET_ROUTERS(state, routers) {
state.addRouters = routers
state.routers = constantRouterMap.concat(routers) // 拼接路由
},
SET_ADD_STATUS(state) {
state.isAddRoutes = true
}
}
const actions = {
// 生成过滤后的路由
GenerateRoutes({ commit }, roles = []) {
return new Promise(resolve => {
// 过滤动态路由列表
const accessedRouters = asyncRouterMap.filter(v => {
// 如果传过来的roles中含有 Root 管理员角色,则直接返回true,不进行过滤
if (roles.indexOf('Root') >= 0) return true;
// 不是则进行权限判断
if (hasPermission(roles, v)) {
// 拥有权限返回true
if (v.children && v.children.length > 0) {
v.children = v.children.filter(child => {
if (hasPermission(roles, child)) {
return child
}
return false;
});
return v
} else {
return v
}
}
return false;
});
console.log(accessedRouters);
commit('SET_ROUTERS', accessedRouters);
commit("SET_ADD_STATUS")
resolve(accessedRouters);
})
}
}
export default {
namespaced: true,
state,
mutations,
actions,
getters
}
这样一来动态路由就好了,然后nav菜单栏。
nav菜单栏也是跟vue-element-admin一样,做了两层,将每一个菜单都用一个组件来渲染。数据则用的是动态路由的数据,前面已经加了hidden属性来控制
要显示的菜单栏,对于高亮当前选中的菜单问题,我在meta另外加了个activePath用来专门显示活跃的菜单(基于elementui)。
// 首先是父组件
<template>
<div class="siderbar_container" :style="{width:sideHidden?'64px':'200px'}">
<el-menu
:default-active="$route.meta.activePath"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b"
:collapse="sideHidden"
:collapse-transition="false"
style="width:100%;"
router
unique-opened
>
<!-- 循环一级菜单 -->
<SideBarItem v-for="route in menuList" :item="route" :key="route.path" />
</el-menu>
</div>
</template>
// 子组件
<template>
<div>
<!-- 判断该路由的显示状态 -->
<template v-if="!item.hidden">
<!-- 如果没有子路由则用 el-menu-item -->
<el-menu-item :index="item.path" v-if="!item.children">
<i :class="[item.icon,'side_iconfont']"></i>
<span slot="title">{{item.name}}</span>
</el-menu-item>
<!-- 如果有子路由则用 el-submenu -->
<template v-else>
<!-- 如果子路由只有一个,则只显示这个子路由 -->
<template v-if="item.children.length === 1">
<SideBarItem :item="item.children[0]"></SideBarItem>
</template>
<el-submenu :index="item.path" v-else :key="item.path">
<template slot="title">
<i :class="[item.icon,'side_iconfont']"></i>
<span>{{item.name}}</span>
</template>
<!-- 调用组件自己,形成递归 -->
<SideBarItem v-for="child in item.children" :key="child.path" :item="child"></SideBarItem>
</el-submenu>
</template>
</template>
</div>
</template>
这样一来菜单栏跟动态路由就都好了。
对于退出登录后切换账号,权限重复的处理,我是在登录页面的create作判断,来重新载入页面
created() {
let isAddRoutes = this.$store.getters["permission/isAddRoutes"]
if(isAddRoutes) {
console.log("已经动态加载了route,刷新页面");
return window.location.reload()
}
}