一般我们“菜单“保存在数据库时,不会仅仅保存路由相关参数,还会增加一下自定义参数,类似下面的数据结构:
const menu = {
id: "1",
pid: "-1", // 父id
nameCn: "平台管理", //中文名
type: "menu", //类别:menu 菜单,button 按钮
icon: "platform", //图标
routeName: "platform", //路由名
routePath: "/platform", //路由地址
routeLevel: 1, //路由级别: 一级路由(左侧显示) 二级路由(左侧不显示)
componentPath: "", //组件路径
sort: 1, //排序
children: [],
};
所以需要手动构造路由,渲染侧栏菜单
需要注意的地方:
效果图:
components/SvgIcon.vue
<template>
<svg
aria-hidden="true"
:class="svgClass"
:style="{ width: size, height: size }"
>
<use :xlink:href="symbolId" :fill="color" />
</svg>
</template>
<script setup lang="ts">
const props = defineProps({
prefix: {
type: String,
default: "icon",
},
name: {
type: String,
required: false,
default: "",
},
color: {
type: String,
default: "",
},
size: {
type: String,
default: "1em",
},
className: {
type: String,
default: "",
},
});
const symbolId = computed(() => `#${props.prefix}-${props.name}`);
const svgClass = computed(() => {
if (props.className) {
return `svg-icon ${props.className}`;
}
return "svg-icon";
});
</script>
<style scoped>
.svg-icon {
display: inline-block;
width: 1em;
height: 1em;
overflow: hidden;
vertical-align: -0.15em;
outline: none;
fill: currentcolor;
}
</style>
layout/index.vue
<template>
<div class="container">
<div class="aside">
<el-scrollbar height="100%">
<el-menu
:default-active="activeIndex"
:ellipsis="false"
:mode="'vertical'"
:collapse="false"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b"
router
>
<template v-for="item in menus" :key="item.id">
<MenuItem :menu="item"></MenuItem>
</template>
</el-menu>
</el-scrollbar>
</div>
<div class="content">
<RouterView></RouterView>
</div>
</div>
</template>
<script setup lang="ts">
import MenuItem from "./components/MenuItem.vue";
import { menus } from "../router/index";
const route = useRoute();
const activeIndex = ref<string>(route.path);
</script>
<style lang="scss">
.container {
width: 100%;
height: 100%;
display: flex;
.aside {
width: 300px;
height: 100%;
background-color: #545c64;
}
.content {
flex: 1;
}
}
</style>
layout/components/MenuItem.vue
<template>
<!-- 不存在children -->
<template v-if="!menu.children?.length">
<el-menu-item
v-if="menu.type === 'menu' && menu.routeLevel === 1"
:index="processRoutePath(menu)"
>
<template #title>
<SvgIcon :name="menu.icon" class="icon"></SvgIcon>
<span>{{ menu.nameCn }}</span>
</template>
</el-menu-item>
</template>
<!-- 存在children -->
<template v-else>
<el-sub-menu
v-if="menu.children.some((c: any) => c.type === 'menu' && c.routeLevel === 1)"
:index="menu.routePath"
>
<template #title>
<SvgIcon :name="menu.icon" class="icon"></SvgIcon>
<span>{{ menu.nameCn }}</span>
</template>
<template v-for="item in menu.children" :key="item.id">
<MenuItem :menu="item"></MenuItem>
</template>
</el-sub-menu>
<el-menu-item
v-else-if="menu.type === 'menu' && menu.routeLevel === 1"
:index="processRoutePath(menu)"
>
<template #title>
<SvgIcon :name="menu.icon" class="icon"></SvgIcon>
<span>{{ menu.nameCn }}</span>
</template>
</el-menu-item>
</template>
</template>
<script lang="ts" setup>
import MenuItem from "./MenuItem.vue";
import SvgIcon from "../../components/SvgIcon.vue";
const props = defineProps({
menu: {
type: Object,
required: true,
},
});
const router = useRouter();
const routes = router.getRoutes();
<!-- 用于处理子路由path没有以/开头的情况 -->
const processRoutePath = (item) => {
for (let route of routes) {
if (route.name === item.routeName) {
return route.path;
}
}
return item.routePath;
};
</script>
<style lang="scss">
.icon {
margin-right: 10px;
}
</style>
router/index.ts
import { createRouter, createWebHistory } from "vue-router";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/",
name: "index",
component: () => import("../layout/index.vue"),
meta: {
title: "index",
sort: 1,
},
},
],
// 刷新时,滚动条位置还原
scrollBehavior: () => ({ left: 0, top: 0 }),
});
export const menus = [
{
id: "1",
pid: "-1", // 父id
nameCn: "平台管理", //中文名
type: "menu", //类别:menu 菜单,button 按钮
icon: "platform", //图标
routeName: "platform", //路由名
routePath: "/platform", //路由地址
routeLevel: 1, //路由级别: 一级路由(左侧显示) 二级路由(左侧不显示)
componentPath: "", //组件路径
sort: 1, //排序
children: [
{
id: "1-1",
nameCn: "角色管理",
permission: "",
type: "menu",
pid: "1",
icon: "role",
iconColor: "",
routeName: "role",
routePath: "role",
routeLevel: 1,
componentPath: "/platform/role/index.vue",
sort: 1,
children: [
{
id: "1-1-1",
nameCn: "新增",
permission: "account:add",
type: "button",
pid: "1-1",
icon: "user",
iconColor: "",
routeName: "",
routePath: "",
routeLevel: null,
componentPath: "",
sort: 1,
children: [],
},
],
},
{
id: "1-2",
nameCn: "账户管理",
permission: "",
type: "menu",
pid: "1",
icon: "user",
iconColor: "",
routeName: "account",
routePath: "account",
routeLevel: 1,
componentPath: "/platform/account/index.vue",
sort: 2,
children: [],
},
],
},
{
id: "2",
pid: "-1",
nameCn: "资产管理",
type: "menu",
icon: "property",
routeName: "",
routePath: "/property",
routeLevel: 1,
componentPath: "",
sort: 2,
children: [
{
id: "2-1",
pid: "2",
nameCn: "文档管理",
permission: "",
type: "menu",
icon: "document",
iconColor: "",
routeName: "document",
routePath: "document",
routeLevel: 1,
componentPath: "/property/document/index.vue",
sort: 1,
children: [],
},
{
id: "2-2",
pid: "2",
nameCn: "硬件管理",
permission: null,
type: "menu",
icon: "hardware",
iconColor: "",
routeName: "",
routePath: "hardware",
routeLevel: 1,
componentPath: "/property/layout/Empty.vue",
sort: 2,
children: [
{
id: "2-2-1",
pid: "2-2",
nameCn: "硬件管理",
permission: null,
type: "menu",
icon: "",
routeName: "hardware",
routePath: "",
routeLevel: 2,
componentPath: "/property/hardware/index.vue",
sort: 1,
children: [],
},
{
id: "2-2-2",
pid: "2-2",
nameCn: "硬件配置",
permission: null,
type: "menu",
icon: "",
routeName: "config",
routePath: "config",
routeLevel: 2,
componentPath: "/property/hardware/Config.vue",
sort: 2,
children: [],
},
],
},
],
},
];
const initRouter = (routerTree: any) => {
const routerArr: any = [];
routerTree.forEach((item: any) => {
if (item.type === "menu") {
routerArr.push({
meta: { title: item.nameCn },
name: item.routeName || "",
path: item.routePath || "",
component: item.componentPath
? () => import(/* @vite-ignore */ "../view" + item.componentPath)
: "",
children: item.children ? initRouter(item.children) : [],
});
}
});
return routerArr;
};
const routers = initRouter(menus);
routers.forEach((item: any) => {
router.addRoute("index", item);
});
console.log(router.getRoutes());
export default router;
view/platform/account/index.vue
<template>账户管理</template>
view/platform/role/index.vue
<template>角色管理</template>
view/property/layout/Empty.vue
<template>
<router-view />
</template>
view/property/hardware/index.vue
<template>
<div>硬件管理</div>
<el-button type="primary" @click="handleCilck">硬件配置</el-button>
</template>
<script setup lang="ts">
const router = useRouter();
const handleCilck = () => {
router.push({
name: "config",
});
};
</script>
view/property/hardware/Config.vue
<template>硬件配置</template>
view/property/document/index.vue
<template>文档管理</template>