vue3 学习笔记17 – 基于el-menu封装菜单
前提条件:组件创建完成
配置路由
// src/router/index.ts
import { createRouter, createWebHashHistory } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router'
export const Layout = () => import('@/layout/index.vue')
// 静态路由
export const routes: RouteRecordRaw[] = [
{
path: '/login',
component: () => import('@/views/login/index.vue'),
hidden: true
},
{
path: '/401',
component: () => import('@/views/error-page/401.vue'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/error-page/404.vue'),
hidden: true
},
{
path: '/',
component: Layout,
redirect: '/home',
children: [
{
path: 'home',
component: () => import('@/views/home/index.vue'),
name: 'Home',
meta: { title: '首页', icon: 'home' }
}
]
},
{
path: '/permission',
component: Layout,
meta: {
title: '权限管理',
icon: 'permission'
},
children: [
{
path: 'user',
component: () => import('@/views/permission/user/index.vue'),
name: 'User',
meta: { title: '用户管理'}
},
{
path: 'role',
component: () => import('@/views/permission/role/index.vue'),
name: 'Role',
meta: { title: '角色管理' }
}
]
}
]
const router = createRouter({
history: createWebHashHistory(),
routes: routes as RouteRecordRaw[],
scrollBehavior: () => ({ left: 0, top: 0 })
})
/**
* 重置路由
*/
export function resetRouter() {
router.replace({ path: '/login' })
}
export default router
封装el-menu
-
目录结构
-
sidebar/index.vue
<script lang="ts" setup>
// sidebarItem 项组件
import SideBarItem from './sidebarItem.vue'
import { useRouter } from 'vue-router'
import { usePermissionStoreHook } from '@/stores/permission'
import { toRaw } from 'vue'
// 拿到路由列表,过滤我们不想要的
const router = useRouter()
const permissionStore = usePermissionStoreHook()
const routerList = toRaw(permissionStore.permission.routes)
console.log(routerList, '获取到的路由list')
const defaultOpenList = []
const activeMenu = ref()
function handleSelect(index, indexPath) {
console.log(index, indexPath)
}
watch(
() => router.currentRoute.value.path,
(toPath) => {
activeMenu.value = toPath
//要执行的方法
console.log(toPath)
},
{ immediate: true, deep: true }
)
</script>
<template>
<div class="sidebar">
<!-- 导航菜单 -->
<el-menu
ref="elMenu"
:default-active="activeMenu"
@select="handleSelect"
:default-openeds="defaultOpenList"
router
>
<!-- 引入子组件 -->
<SideBarItem :routerList="routerList" />
</el-menu>
</div>
</template>
<style lang="scss" scoped>
.sidebar {
height: 100%;
padding-top: 30px;
:deep(.el-menu) {
width: 100%;
height: 100%;
overflow-y: auto;
background: transparent;
border: none;
.el-menu-item {
font-size: 16px;
height: 25px;
padding: 0 30px;
margin-bottom: 40px;
background: transparent;
display: flex;
align-items: center;
outline: none;
box-sizing: border-box;
&:last-child {
margin-bottom: 0;
}
img {
width: 24px;
height: 24px;
margin-right: 10px;
}
span {
color: var(--vt-main-color);
}
&.is-active {
position: relative;
span {
color: var(--vt-main-color);
font-weight: 600;
font-family: 'PingFangSC-Semibold';
}
&::after {
content: '';
position: absolute;
width: 4px;
height: 30px;
background: #2b5ae8;
left: 0;
top: 0;
bottom: 0;
margin: auto;
z-index: 999;
}
}
}
.el-sub-menu {
font-size: 14px;
margin-bottom: 40px;
.el-menu-item {
min-width: 179px;
padding: 0 64px !important;
outline: none;
overflow: hidden;
font-size: 14px;
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
&:first-child {
margin-top: 20px;
}
span {
color: #797979;
}
&.is-active {
span {
font-family: 'PingFangSC-Semibold';
font-weight: 600;
color: var(--vt-main-color);
}
}
}
&.is-opened {
.el-submenu__title {
> span {
color: var(--vt-main-color);
}
}
&.is-active {
.el-submenu__title {
font-weight: 600;
}
}
}
.el-sub-menu__title {
font-size: 16px;
height: 25px;
display: flex;
align-items: center;
padding: 0 30px !important;
background: transparent;
img {
width: 24px;
height: 24px;
margin-right: 10px;
}
&:hover {
background: transparent;
}
+ .el-menu {
.el-submenu__title {
background: transparent;
padding: 0 64px !important;
}
.el-submenu {
.el-menu {
.el-menu-item {
padding: 0 100px !important;
&.is-active {
padding: 0 100px !important;
}
}
}
}
}
// span {
// position: relative;
// &::after {
// content: '';
// @include imageURL('closeMenu.png');
// width: vw(5 * 2);
// height: vw(5 * 2);
// position: absolute;
// right: vw(-15 * 2);
// top: 0;
// bottom: 0;
// margin: auto;
// }
// }
}
.el-sub-menu__icon-arrow {
display: none;
}
&.is-opened {
> .el-sub-menu__title {
&:hover {
background: transparent;
}
}
.el-menu-item {
padding: 0 64px !important;
&.is-active {
padding-left: 64px !important;
font-weight: 600;
color: var(--vt-main-color);
}
}
}
.el-submenu {
margin-bottom: 20px;
&:first-child {
margin-top: 20px;
}
}
}
}
}
</style>
- sidebar/siderbarItem.vue
<script lang="ts" setup>
defineProps({
routerList: {
type: Array,
default: () => {
return []
}
}
})
const getImageUrl = (iconName) => {
return new URL(`../../../assets/menu/${iconName}.png`, import.meta.url).href
}
</script>
<template>
<template v-for="menu in routerList">
<el-sub-menu
:key="menu.url"
:index="menu.url"
v-if="menu.children && menu.children.length > 0 && !menu.hidden"
>
<template #title>
<img :src="getImageUrl(menu.meta.icon)" alt="" />
<span class="menu-title">{{ menu.meta.title }}</span>
</template>
<sidebarItem :router-list="menu.children"></sidebarItem>
</el-sub-menu>
<el-menu-item :key="menu.meta.url + 'u'" :index="menu.meta.url" v-else-if="!menu.hidden">
<img :src="getImageUrl(menu.meta.icon)" alt="" v-if="menu.meta.icon" />
<span class="menu-title">{{ menu.meta.title }}</span>
</el-menu-item>
</template>
</template>
- layout/index.vue – 引入菜单
<template>
<div class="page-box">
<div class="page-left">
<div class="logo-box">
<img src="@/assets/menu/logo-word.png" alt="" />
</div>
<sidebar class="side-menu"></sidebar>
</div>
<div class="page-right">
<div class="header-box">
<div class="title">让电看得见摸得着.</div>
</div>
<Layout></Layout>
</div>
</div>
</template>
<script setup lang="ts">
import Layout from './layout.vue'
import sidebar from './components/sidebar/index.vue'
</script>
<style lang="scss" scoped>
.page-box {
width: 100%;
height: 100%;
display: flex;
overflow: hidden;
position: relative;
background: #f0f0f0;
.page-left {
width: 258px;
position: fixed;
left: 6px;
top: 0;
bottom: 0;
margin: auto;
display: flex;
flex-direction: column;
background: #f0f0f0;
.logo-box {
height: 73px;
display: flex;
align-items: center;
padding-left: 34px;
width: 100%;
img {
width: 73px;
height: 30px;
}
}
.side-menu {
width: 100%;
flex: 1;
background: url('@/assets/menu/bg.png') no-repeat;
background-size: 100% 100%;
overflow: hidden;
transition: width 0.28s;
margin-bottom: 60px;
box-sizing: content-box;
}
}
.page-right {
flex: 1;
height: 100%;
transition: margin-left 0.28s;
margin-left: 264px;
display: flex;
flex-direction: column;
.header-box {
position: fixed;
width: 87.0625%;
//210-230
left: 260px;
top: 0;
padding: 0 50px;
display: flex;
align-items: center;
height: 73px;
justify-content: space-between;
box-sizing: border-box;
z-index: 9;
}
}
}
</style>
- layout/lauout.vue
<template>
<div id="app-main">
<router-view v-slot="{ Component, route }">
<transition name="router-fade" mode="out-in">
<div key="route.fullPath">
<component :is="Component" />
</div>
</transition>
</router-view>
</div>
</template>
<script setup lang="ts"></script>
<style lang="scss">
#app-main {
flex: 1;
height: 100%;
padding-top: 73px;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
padding-left: 40px;
}
</style>
- 运行项目查看页面