效果图:
点:
首先我们的项目中需要安装unplugin-vue-components/vite和unplugin-auto-import/vite,可以自动引用组件(我看另外一个开源项目中直接在页面中引用、使用,在组件中就不需要引用了。。。。回头可以试一下子);或者将所有的组件的引用封装在一起,可以查看 vue ---- 全局组件统一管理(这篇是用vue2写的,用于vue3得稍作改动);
组件思路是:el-menu,里面是一会儿将要嵌套的小组件~小组件循环每个路由单项;
组件单项思路(将menu-item抽出来):看el-menu中从有没有子项就分为el-sub-menu和el-menu-item两种:el-sub-menu是右侧有小箭头,代表该项存在子菜单,可展开;el-menu-item没有小箭头,代表没有子菜单;
在menu-item中进行判断,以有无子项为准,选择采用以上哪种子项;
渲染el-menu时需要设置defaultActive默认项,一般为首页唯一值dashboard;但如果因为页面刷新,需要实时监听当前路由,再将唯一值赋值给defaultActive;
图中菜单右侧属于el-menu的部分明显有一道透明的边边。因为el-menu有一个1px透明的边边。。。。我们在组件中设置以下代码即可;
-
动态路由的过滤方法全部放在store的permission模块中;在每个页面渲染之前需要先用守卫拦一下子,一般在src下的permission.ts中完成:
- 需要给出一个完整的侧边路由和固定路由,方便获取权限后进行过滤(pinia);
- 路由守卫beforeEach中需要做出的操作内容:
- 先判断是否为登录态,不是直接去LoginPage,是就接下来判断是否有角色分配;
- 有角色分配直接next放行,无角色分配就先获取角色;
- 根据角色(代码中的modulePermission)对完整路由进行过滤(分别对路由进行添加、给侧边菜单赋值);
思路就是这,下面是代码
sidebar代码:
<script lang="ts" setup>
import Router from "@/router";
import store from '@/store/index'
const {app, permission} = store();
const list = computed(() => {
return permission.setSideBarRoutes(permission.state.filterRoutes);
})
const defaultActive = computed(() => {
return app.state.activeRoute;
})
// 页面方法
const toHome = () => {
Router.push({
path: '/'
});
}
</script>
<template>
<div class="sidebar-view">
<div class="logo-view" @click="toHome">
<img src="https://img1.baidu.com/it/u=3940612183,1877024013&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1682010000&t=426150bc8f1786722713152075ee49f3"
alt="" class="logo">
<div v-if="!app.state.isCollapse">嘻嘻嘻嘻嘻嘻嘻</div>
</div>
<div class="menu-view">
<Menu :list="list" :defaultActive="defaultActive" :collapse="app.state.isCollapse" />
</div>
</div>
</template>
<style lang="scss" scoped>
.sidebar-view {
height: 100%;
width: 100%;
background: $primary;
color: #fff;
}
.logo-view {
height: 50px;
display: flex;
justify-content: space-evenly;
align-items: center;
cursor: pointer;
.logo {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
}
}
.menu-view {
width: 100%;
height: calc(100% - 50px);
overflow-y: auto;
overflow-x: hidden;
}
</style>
menu组件代码:
<template>
<el-menu class="el-menu-vertical-demo" :default-active="props.defaultActive" router :collapse="collapse"
:background-color="commonStyle.primary" text-color="#fff" active-text-color="#304156">
<menu-item v-for="item in list" :key="item.name" :menu="item" />
</el-menu>
</template>
<script setup lang="ts">
import type {RouteType} from '@/store/types'
import commonStyle from '@/assets/css/variables.module.scss'
interface PropsType {
list: RouteType[]
defaultActive?: string
collapse?: boolean
}
const props = withDefaults(defineProps<PropsType>(), {
defaultActive: '/dashboard'
})
</script>
<style lang="scss" scoped>
.el-menu {
border: none!important;
}
</style>
menuItem组件代码:
<script setup lang="ts">
const props = defineProps(['menu'])
const menu = computed(() => {
return props.menu;
})
</script>
<template>
<el-sub-menu v-if="menu.children" :index="menu.fullPath">
<template #title>
<el-icon>
<component v-if="menu.icon" :is="menu.icon"></component>
</el-icon>
<span>{{ menu.meta.title }}</span>
</template>
<menu-item v-for="item in menu.children" :key="item.name" :menu="item" />
</el-sub-menu>
<el-menu-item v-else :index="menu.fullPath">
<el-icon>
<component v-if="menu.icon" :is="menu.icon" class="menu-icon"></component>
</el-icon>
<template #title>{{ menu.meta.title }}</template>
</el-menu-item>
</template>
<style lang="scss">
.el-menu-item.is-active {
font-weight: 700;
}
</style>
src下的permission.ts代码:
import Router,{addRouter} from '@/router'
import store from '@/store/index'
Router.beforeEach(async (to, from, next) => {
const { permission, app } = store();
const whiteList = ['/login', '/dashboard'];
const toPath = to.path;
const isLogin = localStorage.getItem("NJX_TOKEN");
if (isLogin) {
// 权限判断
const hasPermission = permission.state.modulePermission.length > 0;
if (hasPermission) {
next();
} else {
permission.clearPermission();
try {
// 获取角色
await permission.getPermission();
app.setActiveRoute(to);
addRouter(permission.state.filterRoutes);
next({
...to,
replace: true
})
} catch {
Router.push('/login')
}
}
}
else {
const isWhite = whiteList.findIndex(item => toPath === item) !== -1;
if (!isWhite) {
Router.push('/login')
} else {
next()
}
}
})
Router.afterEach((to) => {
document.title = to.meta.title as string;
})
tada~~一个可以配合动态权限的菜单就完成啦~
明天加一个login页的redirect哈