vue element plus 管理系统路由菜单简要设计(后端获取菜单)

1 需求

  • 管理系统“菜单”由后端接口返回,前端需要根据后端返回的“菜单”数组,构造路由,渲染侧栏菜单
  • 有些菜单是子菜单,有对应的路由,但是不在侧栏显示(比如一些详情页面) 注:这里的“菜单”,不是字面意思的菜单,即可以是菜单也可以是按钮,如果是菜单则对应路由

2 分析

一般我们“菜单“保存在数据库时,不会仅仅保存路由相关参数,还会增加一下自定义参数,类似下面的数据结构:

const menu = {id: "1",pid: "-1", // 父idnameCn: "平台管理", //中文名type: "menu", //类别:menu 菜单,button 按钮icon: "platform", //图标routeName: "platform", //路由名routePath: "/platform", //路由地址routeLevel: 1, //路由级别: 一级路由(左侧显示)  二级路由(左侧不显示)componentPath: "", //组件路径sort: 1, //排序children: [],
};

所以需要手动构造路由,渲染侧栏菜单

需要注意的地方

  • 通过设置 routeLevel 字段来判断当前“菜单“是否在左侧菜单显示
  • 添加 el-menu 的 router 属性,这样可以点击 menu 自动跳转到 index 绑定的路径,否则需要绑定 click 事件,进行手动跳转(适用于需要跳转外链的情况,需要再递归菜单时进行判断,不给index赋值path)
  • 如果当前“菜单“存在 children,但是 children 所有 child 是二级路由并且 path 没有以“/”开头(如下面路由中的硬件管理),那么需要将一个 Empty.vue 组件指向当前“菜单“,并且创建一个 path 为空的 child 来匹配当前“菜单“,否则二级路由无法跳转(无法跳转指的是是当前“菜单“组件路径直接配置目的组件路径)
  • 如果当前“菜单“存在 children,但是 children 中有 child 的 path 没有以“/”开头,在构造左侧菜单时,需要拼接祖先 path 后再赋值给 index,否则无法跳转 这里使用 router.getRoutes()返回的所有路由,并且使用路由 name 匹配,这样比自己递归拼接方便,如下面 MenuItem.vue 组件中使用的 processRoutePath 函数

3 实现

效果图:

3.1 目录结构

3.2 代码:

components/SvgIcon.vue

<template><svgaria-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-itemv-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-menuv-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-itemv-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", // 父idnameCn: "平台管理", //中文名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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/291502.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++结合OpenCV:掌握图像基础与处理

本文详细介绍了使用 OpenCV4 进行图像处理的基础知识和操作。内容包括图像的基础概念、色彩空间理解、以及如何在 C 中进行图像读取、显示和基础操作。 1.图像的基本概念与术语 图像表示 在计算机视觉中&#xff0c;图像通常表示为一个二维或三维的数组。二维数组表示灰度图像&…

基于多反应堆的高并发服务器【C/C++/Reactor】(上)

&#xff08;一&#xff09;初始化服务器端用于监听的套接字 Server.h #pragma once // 初始化监听的套接字 int initListenFd(unsigned short port); Server.c int initListenFd(unsigned short port) {// 1.创建监听的fdint lfd socket(AF_INET, SOCK_STREAM, 0);if(lf…

opencv静态链接error LNK2019

opencv 3.1.0 静态库&#xff0c;包括以下文件 只链接opencv_world310d.lib&#xff0c;报错 opencv_world310d.lib(matrix.obj) : error LNK2019: 无法解析的外部符号 _ippicvsFlip_16u_I8&#xff0c;该符号在函数 "enum IppStatus (__stdcall*__cdecl cv::getFlipFu…

【项目问题解决】% sql注入问题

目录 【项目问题解决】% sql注入问题 1.问题描述2.问题原因3.解决思路4.解决方案1.前端限制传入特殊字符2.后端拦截特殊字符-正则表达式3.后端拦截特殊字符-拦截器 5.总结6.参考 文章所属专区 项目问题解决 1.问题描述 在处理接口入参的一些sql注入问题&#xff0c;虽然通过M…

一套rk3588 rtsp服务器推流的 github 方案及记录 -03(完结)

opencv 解码记录 解码库使用的时候发现瑞芯微以前做过解码库对ffmpeg和gstreamer的支持 然后最近实在不想再调试Rtsp浪费时间了&#xff0c;就从这中间找了一个比较快的方案 ffmpeg 带硬解码库编译 编译流程参考文献 https://blog.csdn.net/T__zxt/article/details/12342435…

Hbase的安装配置

注&#xff1a;本文默认已经完成hadoop的下载以及环境配置 1.上传zookeeper和hbase压缩包到指令路径并且解压 (理论上讲&#xff0c;hbase其实内置了zookeeper&#xff0c;我们也可以不另外下载&#xff0c;另外下载的目的在于减少组件间依赖性) cd /home mkir hbase cd /hom…

03-JVM对象创建与内存分配机制深度剖析

文章目录 对象的创建对象创建的主要流程一、类加载检查二、分配内存划分内存的方法解决并发问题的方法 三、初始化零值四、设置对象头五、执行<init>方法 对象半初始化对象大小与指针压缩什么是java对象的指针压缩&#xff1f;为什么要进行指针压缩&#xff1f; 对象内存…

大IP时代文旅品牌如何用数字人玩转数字营销?

在大IP时代&#xff0c;有IP意味着话题、人气、流量以及变现能力&#xff0c;文旅品牌如何打造一个成功的、受欢迎的IP&#xff0c;拓宽文旅资源价值&#xff0c;成为文旅品牌营销的一大痛点。随着元宇宙概念兴起&#xff0c;数字人IP可以满足文旅品牌多元化需求&#xff0c;文…

字节跳动 Spark Shuffle 大规模云原生化演进实践

Spark 是字节跳动内部使用广泛的计算引擎&#xff0c;已广泛应用于各种大规模数据处理、机器学习和大数据场景。目前中国区域内每天的任务数已经超过 150 万&#xff0c;每天的 Shuffle 读写数据量超过 500 PB。同时某些单个任务的 Shuffle 数据能够达到数百 TB 级别。 与此同…

开关电源厚膜集成电路引脚功能

开关电源厚膜集成电路引脚功能 一、 STR51213、STR50213、STR50103 引脚号 引脚功能 1 接地&#xff0c;内接稳压基准电路 2 开关管基极 3 开关管集电极 4 开关管发射极 5 误差比较电压信号输入&#xff0c;兼待机控制 二、 STR3302、STR3202 引脚号 引脚功能 1内部半…

HTML5的完整学习笔记

HTML 什么是HTML&#xff1a; 作为前端三件套之一&#xff0c;HTML的全称是超文本标记语言&#xff08;Hypertext Markup Language&#xff09;。HTML是一种标记语言&#xff0c;用于创建网页。它由一系列标签组成&#xff0c;这些标签用于定义网页的结构和内容。HTML标签告诉…

【产品经理】Axure原型工具教程

笔记为项目总结笔记&#xff0c;若有错误欢迎指出哟~ Axure原型工具教程 Axure简介原型图分类常用操作常用原件常用交互母版常用设备分辨率 Axure简介 Axure是一款专业的原型设计与交互设计软件&#xff0c;可以帮助用户快速创建高保真的原型和交互设计。Axure支持多种常见的交…