做什么:封装通用左侧菜单栏组件
怎么做:使用Element-Plus
组件库中的el-menu
组件进行二次封装
技术栈:Vue3 + Ts + Vite,且采用 setup 语法糖写法
准备工作:请各位自行引入Element-plus组件库,本文中有用到 svg组件,svg组件封装教程请看第五点
查看
Element-plus
组件库中的el-menu
组件,不难发现,菜单栏大致可以分为两类,一
类是有子菜单的,一类是无子菜单的
。
所以我们将对这两类进行分情况设计,再结合递归
,即可完成根据路由列表,动态渲染
菜单栏
<script lang="ts" setup>
// sidebarItem 项组件
import SideBarItem from './sidebarItem.vue';
import { useRouter } from 'vue-router';
// 拿到路由列表,过滤我们不想要的
const router = useRouter();
const routerList = router.getRoutes().filter((v) => v.meta && v.meta.isShow);
</script>
<template>
<div class="sidebar">
<!-- 项目名称及logo -->
<div class="sidebar-logo flex-center">
<svg-icon icon-class="logo" />
<span>VitalityAdmin</span>
</div>
<!-- 导航菜单 -->
<el-menu
active-text-color="#fff"
background-color="#001529"
:default-active="$route.path"
text-color="#999"
:unique-opened="true"
router>
<!-- 引入子组件 -->
<SideBarItem :routerList="routerList" />
</el-menu>
<!-- active-text-color:当前菜单项被选中时,字体的颜色 -->
<!-- background-color:这个menu菜单的背景色 -->
<!-- default-active: 当前激活菜单的 index -->
<!-- text-color:菜单项字体颜色 -->
<!-- unique-opened:unique-opened 是否只保持一个子菜单的展开 -->
<!-- router:是否使用 vue-router 的模式,启用该模式会在激活导航时以 index 作为 path 进行路由跳转 -->
</div>
</template>
<style lang="scss" scoped>
.sidebar {
height: 100%;
.sidebar-logo {
height: 48px;
background-color: #002140;
color: #fff;
font-weight: 700;
line-height: 48px;
text-align: center;
font-size: 20px;
}
.el-menu {
height: calc(100% - 48px);
border-right: 0;
overflow: auto;
}
}
</style>
<script setup lang="ts">
import { RouteRecordRaw } from 'vue-router';
// 做类型限制,解决ts类型报错
type CustomRouteRecordRaw = RouteRecordRaw & {
meta: {
isShow?: boolean;
};
};
const props = defineProps({
// 拿到父组件传递过来的路由列表进行渲染
routerList: {
type: Array as () => CustomRouteRecordRaw[],
required: true
}
});
</script>
<template>
<template v-for="item in props.routerList" :key="item.path">
<!-- 当该菜单项有子菜单时 -->
<el-sub-menu :index="item.path" v-if="item.children && item.children.length > 0">
<template #title v-if="item.meta.icon">
<!-- 菜单项图标,我此处用的是全局封装的 svg组件 -->
<el-icon><svg-icon :icon-class="item.meta.icon" /></el-icon>
<!-- 菜单项名称,在路由中定义好 -->
<span>{{ item.meta.title }}</span>
</template>
<!-- 若路由中未定义菜单项icon,则仅展示名称--(我的仅一级菜单有图标) -->
<template #title v-else>{{ item.meta.title }}
<!-- 递归遍历-自己调用自己(核心代码) -->
<sidebarItem :routerList="( item.children as CustomRouteRecordRaw[])" />
</el-sub-menu>
<!-- 当前菜单项无子菜单 -->
<el-menu-item :index="item.path" v-else>
<!-- 与上面注释大致相同,不多做额外注释 -->
<template v-if="item.meta.icon">
<el-icon><svg-icon :icon-class="item.meta.icon" /></el-icon>
<span>{{ item.meta.title }}</span>
</template>
<template v-else>
{{ item.meta.title }}
</template>
</el-menu-item>
</template>
</template>
<style scoped lang="scss">
.is-active {
background: #409eff;
font-weight: 700;
}
.el-menu-item {
&:hover {
color: #fff;
font-weight: 700;
}
}
.el-menu--collapse {
.el-menu-item {
justify-content: center;
}
}
// 下列代码是用于兼容horizontal所写,酌情删或留
.el-menu--horizontal {
.el-menu-item.is-active {
background-color: transparent !important;
border-bottom: 2px solid #409eff !important;
.el-icon,
span {
color: #409eff !important;
}
}
.el-sub-menu.is-active {
.el-sub-menu__title {
border: 0 !important;
}
.el-icon,
span {
color: #409eff !important;
}
}
}
</style>
isShow: true, // 控制当前项是否在菜单栏中渲染出来,比如你写了 login 页面的路由,但是并不希望 login在menu菜单中渲染出来,即可设为false
title: ‘首页’, // menu菜单项的名称,没啥好说的
icon: ‘menu-home’ // menu菜单项的图标,我此处是与封装好的 svg 组件结合使用的
export default [
{
path: '/layout',
name: 'layoutIndex',
component: () => import('@/layout/index.vue'),
children: [
{
path: '/home',
name: 'homeIndex',
component: () => import('@/views/home/index.vue'),
meta: {
isShow: true, // 控制当前项是否在菜单栏中渲染出来,比如你写了 login 页面的路由,但是并不希望 login在menu菜单中渲染出来,即可设为false
title: '首页', // menu菜单项的名称,没啥好说的
icon: 'menu-home' // menu菜单项的图标,我此处是与封装好的 svg 组件结合使用的
}
},
{
path: '/echarts',
name: 'echartIndex',
// component: () => import('@/views/echarts/index.vue'),
meta: {
isShow: true,
title: 'Echarts页',
icon: 'menu-echarts'
},
children: [
{
path: '/echarts/barCharts',
name: 'barCharts',
component: () => import('@/views/echarts/barCharts.vue'),
meta: {
title: '柱状图'
}
},
{
path: '/echarts/pieCharts',
name: 'pieCharts',
component: () => import('@/views/echarts/pieCharts.vue'),
meta: {
title: '饼图'
}
}
]
},
{
path: '/package',
name: 'packageIndex',
component: () => import('@/views/package/index.vue'),
meta: {
isShow: true,
title: '组件',
icon: 'menu-package'
}
},
{
path: '/menu',
name: 'menuIndex',
redirect: '/menu/menu-1',
meta: {
isShow: true,
title: '一级菜单',
icon: 'menu-package'
},
children: [
{
path: '/menu/menu-1',
name: 'menu-1',
component: () => import('@/views/menu/menu1.vue'),
meta: {
title: '二级菜单-1'
}
},
{
path: '/menu/menu-2',
name: 'menu-2',
component: () => import('@/views/menu/menu2.vue'),
meta: {
title: '二级菜单-2'
},
children: [
{
path: '/menu/menu-2/children',
name: 'menu3',
component: () => import('@/views/menu/menu3.vue'),
meta: {
title: '三级菜单'
}
}
]
}
]
}
]
}
];
本文不展开讲解
svg组件
的封装与使用,有需要的朋友欢迎参考下面的svg组件
封装教程
svg组件封装教程:http://t.csdn.cn/uYsSJ
本系列文章记录了
从零到一
搭建Vue3+Ts+Vite
项目的全过程
包括但不限于项目配置、组件封装、过渡动画等
系列文章持续更新中~~,有任何问题欢迎评论区留言
最后,希望本文都能对你有一点帮助,点赞收藏不迷路