1.新建global.d.ts文件
declare namespace Menu {
interface MenuOptions {
path: string;
name: string;
component?: string | (() => Promise<any>);
redirect?: string;
meta: MetaProps;
children?: MenuOptions[];
}
interface MetaProps {
icon: string;
title: string;
activeMenu?: string;
isLink?: string;
isHide: boolean;
isFull: boolean;
isAffix: boolean;
isKeepAlive: boolean;
}
}
declare type Recordable<T = any> = Record<string, T>;
declare interface ViteEnv {
VITE_API_URL: string;
VITE_PORT: number;
VITE_OPEN: boolean;
VITE_GLOB_APP_TITLE: string;
VITE_DROP_CONSOLE: boolean;
VITE_PROXY_URL: string;
VITE_BUILD_GZIP: boolean;
VITE_REPORT: boolean;
}
2.新建SubMenu.vue文件
<template>
<template v-for="subItem in menuList" :key="subItem.path">
<el-sub-menu v-if="subItem.children && subItem.children.length > 0" :index="subItem.path">
<template #title>
<el-icon>
<component :is="subItem.meta.icon"></component>
</el-icon>
<span>{{ subItem.meta.title }}</span>
</template>
<SubMenu :menuList="subItem.children" />
</el-sub-menu>
<el-menu-item v-else :index="subItem.path" @click="handleClickMenu(subItem)">
<el-icon>
// 渲染图标组件,需要提前注册好
<component :is="subItem.meta.icon"></component>
</el-icon>
<template #title>
<span>{{ subItem.meta.title }}</span>
</template>
</el-menu-item>
</template>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router'
defineProps<{ menuList: Menu.MenuOptions[] }>()
const router = useRouter()
const handleClickMenu = (subItem: Menu.MenuOptions) => {
if (subItem.meta.isLink) return window.open(subItem.meta.isLink, '_blank')
router.push(subItem.path)
}
</script>
3.新建一个sideBar.vue页面,引入SubMenu.vue
<template>
<el-container class="layout">
<el-header>
<div class="header-lf">
<div class="logo flx-center">
<img src="@/assets/images/logo.svg" alt="logo" />
<span>HB Admin</span>
</div>
<ToolBarLeft />
</div>
<ToolBarRight />
</el-header>
<el-container class="classic-content">
<el-aside>
<div class="menu" :style="{ width: isCollapse ? '65px' : '210px' }">
<el-scrollbar>
<el-menu
:default-active="activeMenu"
:router="false"
:collapse="isCollapse"
:collapse-transition="false"
:unique-opened="true"
background-color="#ffffff"
text-color="#303133"
>
<SubMenu :menuList="menuList" />
</el-menu>
</el-scrollbar>
</div>
</el-aside>
<el-container class="classic-main">
<Main />
</el-container>
</el-container>
</el-container>
</template>
<script setup lang="ts" name="layoutClassic">
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import { GlobalStore } from '@/stores'
import { AuthStore } from '@/stores/modules/auth'
import Main from '@/layouts/components/Main/index.vue'
import SubMenu from '@/layouts/components/Menu/SubMenu.vue'
import ToolBarLeft from '@/layouts/components/Header/ToolBarLeft.vue'
import ToolBarRight from '@/layouts/components/Header/ToolBarRight.vue'
const route = useRoute()
const authStore = AuthStore()
const globalStore = GlobalStore()
const activeMenu = computed(() => (route.meta.activeMenu ? route.meta.activeMenu : route.path))
const menuList = computed(() => authStore.showMenuListGet)
const isCollapse = computed(() => globalStore.themeConfig.isCollapse)
</script>
<style scoped lang="scss">
@import './index.scss';
</style>
<style lang="scss">
.classic {
.classic-content {
height: calc(100% - 55px); // 减去头部高度
.classic-main {
display: flex;
flex-direction: column;
}
}
.el-menu,
.el-menu--popup {
.el-menu-item {
&.is-active {
background: var(--el-color-primary-light-9);
&::before {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 4px;
content: '';
// background: var(--el-color-primary);
}
}
}
}
// guide
#driver-highlighted-element-stage {
background-color: #606266 !important;
}
}
</style>

4.menuList的数据格式
{
"data": [
{
"path": "/home",
"name": "home",
"redirect": "/personalManage",
"meta": {
"title": "首页",
"isLink": "",
"isHide": true,
"isFull": false,
"isAffix": false,
"isKeepAlive": true
}
},
{
"path": "/dataScreen",
"name": "dataScreen",
"component": "/dataScreen/index",
"meta": {
"title": "汇博资源库",
"isLink": "",
"isHide": true,
"isFull": true,
"isAffix": false,
"isKeepAlive": true
}
},
{
"path": "/personalManage",
"name": "personalManage",
"component": "/personalManage/index",
"meta": {
"title": "用户管理",
"isLink": "",
"isHide": false,
"isFull": false,
"isAffix": false,
"isKeepAlive": true
}
},
{
"path": "/watermark",
"name": "watermark",
"component": "/watermark/index",
"meta": {
"title": "水印管理",
"isLink": "",
"isHide": false,
"isFull": false,
"isAffix": false,
"isKeepAlive": true
}
},
{
"path": "/fileTrace",
"name": "fileTrace",
"component": "/fileTrace/index",
"meta": {
"title": "文件溯源",
"isLink": "",
"isHide": false,
"isFull": false,
"isAffix": false,
"isKeepAlive": true
}
},
{
"path": "/logHistory",
"name": "logHistory",
"component": "/logHistory/index",
"meta": {
"title": "操作日志",
"isLink": "",
"isHide": false,
"isFull": false,
"isAffix": false,
"isKeepAlive": true
}
}
]
}
5.elementPlus 图标组件注册,在main.ts中
import * as Icons from '@element-plus/icons-vue'
const app = createApp(App)
Object.keys(Icons).forEach(key => {
app.component(key, Icons[key as keyof typeof Icons])
})
app.use(xxx).use(xxx).mount('#app')