前言:
此篇文章是按照自己的理解做的一个demo,仅供参考。学这块知识的时候在网上也看过不少的文章,每篇文章都有自己的风格,直接使用他人的逻辑总感觉懵懵的,所以决定自己尝试写一遍。
正文:
此种方式路由权限业务逻辑是:
1、前端在页面上定义好全部的路由
2、通过用户不同向后台请求不同的用户权限数据
3、请求回来的数据和全部的路由进行比对,取出来作为路由配置
# 全部路由:
import Right from "../pages/pages/user/rights.vue";
import Role from "../pages/pages/user/roles.vue";
import User from "../pages/pages/user/user.vue";
import Operation from "../pages/pages/system/operation.vue";
import Department from "../pages/pages/cards/department.vue";
import Event from "../pages/pages/cards/Event.vue";
import Leader from "../pages/pages/cards/Leader.vue";
import SpecialColumn from "../pages/pages/cards/specialColumn.vue";
export const DynamicRoutes = [
{
name: "用户管理",
path: "/home/user",
component: User,
},
{
name: "权限管理",
path: "/home/right",
component: Right,
},
{
name: "角色管理",
path: "/home/role",
component: Role,
},
{
name: "操作日志",
path: "/home/operation",
component: Operation,
},
{
name: "部门管理",
path: "/home/department",
component: Department,
},
{
name: "重大事件",
path: "/home/event",
component: Event,
},
{
name: "领导人卡片",
path: "/home/leader",
component: Leader,
},
{
name: "重点专题",
path: "/home/specialcolumn",
component: SpecialColumn,
},
];
# 本地模拟接口响应路由数据 (admin和user)
//模拟走接口请求回来的数据
//管理员角色
export const admin = [
{
name: "用户管理",
children: [
{
name: "用户管理",
},
{
name: "角色管理",
},
{
name: "权限管理",
},
],
},
{
name: "卡片内容管理",
children: [
{ name: "部门管理" },
{ name: "重大事件" },
{ name: "领导人卡片" },
{ name: "重点专题" },
],
},
{
name: "系统管理",
children: [
{
name: "操作日志",
},
],
},
];
//用户角色
export const user = [
{
name: "卡片内容管理",
children: [
{ name: "部门管理" },
{ name: "重大事件" },
{ name: "领导人卡片" },
{ name: "重点专题" },
],
},
];
# 路由配置
import { createWebHashHistory, createRouter } from "vue-router";
import Login from "../pages/login/login.vue";
import Home from "../pages/home/home.vue";
import { matchRouters } from "../utils/matchRoutes";
import useUser from "../store/user";
//初始化路由
const routes = [
{
path: "/",
redirect: "/login",
},
{
path: "/login",
name: "Login",
component: Login,
},
{
path: "/home",
component: Home,
name: "Home",
children: [],
},
];
const router = createRouter({
history: createWebHashHistory(),
routes,
});
router.beforeEach((to, from, next) => {
let userStore = useUser();
let token = userStore.GET_TOKEN;
let menu = userStore.GET_MENU;
// console.log(token, "token", menu);
if (!token && to.name == "Login") {
next();
} else {
if (to.name == "Login") {
userStore.logOutFn();
next()
} else {
if (menu.length) {
if (userStore.GET_ISLOADROUTE) {
next();
} else {
userStore.SET_ISLOADROUTE(true);
matchRouters(menu);
userStore.SET_MENULIST(menu);
if(to.path == '/home'){
//重定向
next(menu[0].children[0].path)
}
next({ ...to, replace: true });
}
}else{
next('/login')
}
}
}
});
export default router;
# 路由匹配函数
根据name进行对比,给请求回来的数据添加上path
// 匹配路由
import { DynamicRoutes } from "../router/dynamic-routes";
import router from "../router";
export function matchRouters(routes: any) {
routes.forEach((v: any) => {
if (v.children && v.children.length > 0) {
matchRouters(v.children);
} else {
DynamicRoutes.forEach((item) => {
if (v.name === item.name) {
//通过router.addRouter追加路由
router.addRoute("Home", item);
v.path = item.path;
}
});
}
});
}
# store
import { defineStore } from "pinia";
const useUser = defineStore("user", {
state: () => {
return {
token: sessionStorage.getItem('token')||'',
menu: JSON.parse(sessionStorage.getItem("menu")) || [], //接口响应数据
memuList: [],
isLoadRoute:false, //路由是否已经进行过匹配
};
},
getters: {
GET_MENU(): any {
return this.menu;
},
GET_TOKEN(): string {
return this.token;
},
GET_ISLOADROUTE():boolean{
return this.isLoadRoute;
}
},
actions: {
SET_TOKEN(value: string) {
sessionStorage.setItem("token", value);
this.token = value;
},
SET_MENU(value: string) {
sessionStorage.setItem("menu", JSON.stringify(value));
this.menu = value;
},
SET_MENULIST(value:[]) {
sessionStorage.setItem("menulist", JSON.stringify(value));
this.memuList = value;
},
SET_ISLOADROUTE(value:boolean){
this.isLoadRoute = value
},
//退出登录
logOutFn(){
sessionStorage.clear()
this.SET_ISLOADROUTE(false)
}
},
});
export default useUser;
# 登录页面
用户名如果是 admin 就是管理员,其他的都视为 普通用户 user
<template>
<div class="login">
<a-form
:model="formState"
name="basic"
:label-col="{ span: 8 }"
:wrapper-col="{ span: 16 }"
autocomplete="off"
@finish="onFinish"
@finishFailed="onFinishFailed"
>
<a-form-item
label="Username"
name="username"
:rules="[{ required: true, message: 'Please input your username!' }]"
>
<a-input v-model:value="formState.username" />
</a-form-item>
<a-form-item
label="Password"
name="password"
:rules="[{ required: true, message: 'Please input your password!' }]"
>
<a-input-password v-model:value="formState.password" />
</a-form-item>
<a-form-item :wrapper-col="{ offset: 8, span: 16 }">
<a-button type="primary" html-type="submit">Submit</a-button>
</a-form-item>
</a-form>
</div>
</template>
<script setup lang="ts">
import { user, admin } from "../../utils/path";
import { reactive } from "vue";
import { useRouter } from "vue-router";
import useUser from "../../store/user";
const router = useRouter();
const useStore = useUser();
const formState = reactive({
username: "",
password: "",
});
function onFinish() {
useStore.SET_TOKEN(formState.username);
if (formState.username == "admin") {
useStore.SET_MENU(admin);
} else {
useStore.SET_MENU(user);
}
router.replace("/home");
}
function onFinishFailed() {}
</script>
<style scoped>
.login {
width: 30%;
margin: 200px auto;
}
</style>
# 页面布局 (home.vue)
<!-- home -->
<template>
<div class="home">
<!-- 左侧数据 -->
<div class="left">
<Slider />
</div>
<!-- 右侧数据 -->
<div class="right">
<!-- 头部 -->
<Home />
<!-- main -->
<Main />
</div>
</div>
</template>
<script setup lang="ts">
import Slider from "@/pages/layout/slide.vue";
import Home from "@/pages/layout/header.vue";
import Main from "@/pages/layout/main.vue";
</script>
<style lang="scss" scoped>
.home {
display: flex;
justify-content: space-between;
width: 100vw;
height: 100vh;
overflow: hidden;
.left {
width: 260px;
height: 100%;
background-color: skyblue;
}
.right {
display: flex;
flex-direction: column;
width: calc(100% - 280px);
height: 100%;
}
}
</style>
# 侧边栏 (slide.vue)
<!-- 侧边栏 -->
<template>
<div class="slide">
<div class="title">LYF后台管理系统</div>
<a-menu
mode="inline"
v-model:openKeys="openKeys"
v-model:selectedKeys="selectedKeys"
theme="dark"
>
<a-sub-menu v-for="m in memu" :key="m.name">
<template #title>
<span>{{ m.name }}</span>
</template>
<a-menu-item v-for="item in m.children" :key="item.name">
<router-link :to="item.path">{{ item.name }}</router-link>
</a-menu-item>
</a-sub-menu>
</a-menu>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import useUser from "../../store/user";
import { useRoute } from "vue-router";
const route = useRoute();
const userStore = useUser();
let memu = userStore.memuList as any;
//数组扁平化
function flatMenus() {
let new_menu = memu
?.map((item: any) => {
return item.children;
})
.flat();
return new_menu;
}
//根据当前路由的name属性找selectedKey
const getSelectedKeys = () => {
const currentSelectedKey = flatMenus()?.find(
(item: any) => item.path === route.path
);
return currentSelectedKey ? [currentSelectedKey.name] : [];
};
//根据currentSelectedKey找父级name
const getOpenKeys = () => {
const selectedKey = getSelectedKeys();
if (selectedKey.length === 0) {
return [];
}
const [key] = selectedKey;
let currrentOpenKey = memu.find((item: any) => {
return item.children?.some((v: any) => {
return v.name == key;
});
});
return currrentOpenKey ? [currrentOpenKey.name] : [];
};
let selectedKeys = ref(getSelectedKeys());
let openKeys = ref(getOpenKeys());
</script>
<style scoped lang="scss">
.slide {
width: 100%;
height: 100%;
color: #fff;
overflow: auto;
background-color: #001529;
.title {
font-size: 18px;
text-align: center;
margin: 100px 0 20px 0;
}
}
</style>
# header.vue
<template>
<div class="header">
<div></div>
<div class="log_out" @click.stop="logOut">退出</div>
</div>
</template>
<script lang="ts" setup>
import { useRouter } from "vue-router";
import useUser from "../../store/user";
const router = useRouter();
const userStore = useUser();
function logOut() {
userStore.logOutFn();
router.replace("/login");
}
</script>
<style lang="scss" scoped>
.header {
display: flex;
align-items: center;
justify-content: space-between;
height: 60px;
width: 100%;
padding: 0 20px;
margin-bottom: 20px;
background-color: orange;
box-sizing: border-box;
.log_out {
cursor: pointer;
}
}
</style>
# main.vue
<template>
<div class="main">
<router-view></router-view>
</div>
</template>
<style lang="scss" scoped>
.main {
width: 100%;
height: calc(100% - 80px);
background-color: bisque;
overflow: hidden;
}
</style>