最近在寫一個基於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()
}
}
