文章目录
Vue3 根据角色权限动态加载路由
效果
普通用户:

管理员:

超级管理员:

目录结构
├─assets
├─components
├─menus
│ ├─admin
│ ├─manager
│ └─user
├─router
│ ├─admin
│ ├─common
│ ├─manager
│ └─user
├─stores
├─utils
└─views
├─main
│ ├─admin
│ ├─manager
│ └─user
└─not-found
代码实现
路由代码
普通用户:
js
export const userRoutes = [
{
path: "/main/user/OneUser",
component: () => import("@/views/main/user/OneUser.vue")
},
{
path: "/main/user/TwoUser",
component: () => import("@/views/main/user/TwoUser.vue")
},
{
path: "/main/user/ThreeUser",
component: () => import("@/views/main/user/ThreeUser.vue")
},
{
path: "/main/user/FourUser",
component: () => import("@/views/main/user/FourUser.vue")
},
];
管理员:
js
export const managerRoutes = [
{
path: "/main/manager/OneManager",
component: () => import("@/views/main/manager/OneManager.vue"),
},
{
path: "/main/manager/TwoManager",
component: () => import("@/views/main/manager/TwoManager.vue"),
},
];
超级管理员:
js
export const adminRoutes = [
{
path: "/main/admin/OneAdmin",
component: () => import("@/views/main/admin/OneAdmin.vue"),
},
{
path: "/main/admin/TwoAdmin",
component: () => import("@/views/main/admin/TwoAdmin.vue"),
}
];
动态加载路由代码
javascript
import {adminMenus} from "@/menus/admin/adminMenus.js";
import {managerMenus} from "@/menus/manager/managerMenus.js";
import {userMenus} from "@/menus/user/userMenus.js";
import {adminRoutes} from "@/router/admin/adminRoutes.js";
import router from "@/router/index.js";
import {managerRoutes} from "@/router/manager/managerRoutes.js";
import {userRoutes} from "@/router/user/userRoutes.js";
import {localCache} from "@/utils/cache.js";
import {defineStore} from "pinia";
import {ref} from "vue";
const useLoginStore = defineStore("loginStore", () => {
const role = ref("");
const menus = ref([]);
const firstRoute = ref(null);
const routesLoaded = ref(false);
const addedRouteNames = ref([]);
function login(roleParam) {
role.value = roleParam;
routesLoaded.value = false;
loadRoutesByRole(role.value);
loadMenusByRole(role.value);
localCache.setCache("role", role.value);
router.push("/main");
}
function logout() {
removeDynamicRoutes();
role.value = "";
menus.value = [];
firstRoute.value = null;
routesLoaded.value = false;
localCache.removeCache("role");
router.push("/login");
}
function loadLocalData() {
role.value = localCache.getCache("role");
if (role.value && !routesLoaded.value) {
addedRouteNames.value = [];
loadRoutesByRole(role.value);
loadMenusByRole(role.value);
}
}
function loadRoutesByRole(role) {
if (routesLoaded.value) return;
let accessRoutes = [];
if (role === "admin") {
accessRoutes = [...adminRoutes, ...managerRoutes, ...userRoutes];
} else if (role === "manager") {
accessRoutes = [...managerRoutes, ...userRoutes];
} else if (role === "user") {
accessRoutes = [...userRoutes];
}
addedRouteNames.value = [];
accessRoutes.forEach((route, index) => {
if (index === 0 && !firstRoute.value) {
firstRoute.value = route;
}
router.addRoute("main", route);
if (route.name) {
addedRouteNames.value.push(route.name);
}
});
routesLoaded.value = true;
}
function removeDynamicRoutes() {
addedRouteNames.value.forEach(name => {
if (name && router.hasRoute(name)) {
router.removeRoute(name);
}
});
addedRouteNames.value = [];
routesLoaded.value = false;
}
function loadMenusByRole(role) {
if (role === "admin") {
menus.value = [...adminMenus, ...managerMenus, ...userMenus];
} else if (role === "manager") {
menus.value = [...managerMenus, ...userMenus];
} else if (role === "user") {
menus.value = [...userMenus];
}
}
return {role, menus, firstRoute, login, logout, loadLocalData};
});
export default useLoginStore;
侧边菜单栏代码
vue
<script setup>
import useLoginStore from "@/stores/loginStore.js";
import {pathToMenu} from "@/utils/map.js";
import {Location} from "@element-plus/icons-vue";
import {computed} from "vue";
import {useRoute, useRouter} from "vue-router";
defineProps({
isCollapse: {
type: Boolean,
default: true
}
});
const router = useRouter();
const route = useRoute();
const loginStore = useLoginStore();
const menus = computed(() => loginStore.menus || []);
const handleItem = (item) => {
router.push(item.path);
};
const defaultActive = computed(() => {
if (loginStore.menus && loginStore.menus.length > 0) {
const menuItem = pathToMenu(route.path, loginStore.menus);
return menuItem?.id || "";
}
return "";
});
</script>
<template>
<div class="left-menu">
<el-menu
active-text-color="#ffd04b"
text-color="#fff"
background-color="#001529"
:default-active="defaultActive"
:collapse="isCollapse"
:collapse-transition="true"
>
<template v-for="menu in menus" :key="menu.id">
<el-sub-menu :index="menu.id">
<template #title>
<el-icon>
<location/>
</el-icon>
<span>{{ menu.name }}</span>
</template>
<template v-for="menuItem in menu.menuItems" :key="menuItem.id">
<el-menu-item :index="menuItem.id" @click="handleItem(menuItem)">{{ menuItem.name }}</el-menu-item>
</template>
</el-sub-menu>
</template>
</el-menu>
</div>
</template>
<style scoped>
.left-menu {
height: 100%;
background-color: #001529;
}
.el-menu {
border-right: none;
}
</style>
主页代码
vue
<script setup>
import LeftMenu from "@/components/LeftMenu.vue";
import useLoginStore from "@/stores/loginStore.js";
import {computed, ref} from "vue";
const loginStore = useLoginStore();
const isCollapse = ref(false);
const role = computed(() => loginStore.role);
const doLogout = () => {
loginStore.logout();
};
</script>
<template>
<main>
<el-container>
<el-aside :width="isCollapse ? '64px' : '200px'">
<LeftMenu :isCollapse="isCollapse"/>
</el-aside>
<el-container>
<el-header>
<div class="content">
<div>
<el-radio-group v-model="isCollapse">
<el-radio-button :value="true">收起</el-radio-button>
<el-radio-button :value="false">展开</el-radio-button>
</el-radio-group>
</div>
<div class="info">
<el-dropdown>
<span class="user-info">
<el-avatar :size="30" src="https://www.baidu.com/img/flexible/logo/pc/result.png"/>
<span class="name">角色:{{ role }}</span>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item divided @click="doLogout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</el-header>
<el-main>
<router-view></router-view>
</el-main>
</el-container>
</el-container>
</main>
</template>
<style scoped lang="css">
main {
height: 100%;
.el-container {
height: 100%;
}
}
.content {
height: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 0 18px;
.info {
.user-info {
display: flex;
flex-direction: row;
align-items: center;
cursor: pointer;
.name {
margin-left: 5px;
}
}
}
}
.el-main {
background-color: #f0f2f5;
}
</style>