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

1 需求

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

2 分析

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

const menu = {
  id: "1",
  pid: "-1", // 父id
  nameCn: "平台管理", //中文名
  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>
  <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>

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HTML美化网页

使用CSS3美化的原因 用css美化页面文本,使页面漂亮、美观、吸引用户 可以更好的突出页面的主题内容,使用户第一眼可以看到页面主要内容 具有良好的用户体验 <span>标签 作用 能让某几个文字或者某个词语凸显出来 有效的传递页面信息用css美化页面文本&#xff0c;使页面漂…

四、Spring IoC实践和应用(基于注解方式管理 Bean)

本章概要 基于注解方式管理 Bean 实验一&#xff1a; Bean注解标记和扫描 (IoC)实验二&#xff1a; 组件&#xff08;Bean&#xff09;作用域和周期方法注解实验三&#xff1a; Bean属性赋值&#xff1a;引用类型自动装配 (DI)实验四&#xff1a; Bean属性赋值&#xff1a;基本…

如何用docke启动redis?(解决双击docker服务闪退问题)

要使用Docker启动Redis服务&#xff0c;您可以按照以下步骤进行操作&#xff1a; 安装Docker&#xff1a; 如果您还没有安装Docker&#xff0c;请先在您的系统上安装Docker。您可以从Docker官方网站获取安装说明。 https://www.docker.com/get-started/ 2.在Docker Hub上查找R…

论文中公式怎么降重 papergpt

大家好&#xff0c;今天来聊聊论文中公式怎么降重&#xff0c;希望能给大家提供一点参考。 以下是针对论文重复率高的情况&#xff0c;提供一些修改建议和技巧&#xff0c;可以借助此类工具&#xff1a; 论文中公式怎么降重 一、引言 在论文撰写过程中&#xff0c;公式是表达学…

声音克隆:让你的声音变得无所不能

什么是声音克隆&#xff1f; 声音克隆是一种利用人工智能技术&#xff0c;根据一段声音样本&#xff0c;生成与之相似或完全相同的声音的过程。声音克隆可以用于多种场景。 声音克隆的原理是利用深度学习模型&#xff0c;从声音样本中提取声音特征&#xff0c;然后根据目标文…

华为OD机试 - 发广播 - 并查集(Java 2023 B卷 200分)

目录 专栏导读一、题目描述二、输入描述三、输出描述1、输入2、输出3、说明 四、并查集Java 实现并查集 五、Java算法源码六、效果展示1、输入2、输出3、说明 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&…

机器学习算法(12) — 集成技术(Boosting — Xgboost 分类)

一、说明 时间这是集成技术下的第 4 篇文章&#xff0c;如果您想了解有关集成技术的更多信息&#xff0c;您可以参考我的第 1 篇集成技术文章。 机器学习算法&#xff08;9&#xff09; - 集成技术&#xff08;装袋 - 随机森林分类器和...... 在这篇文章中&#xff0c;我将解释…

​创新驱动,边缘计算领袖:亚马逊云科技海外服务器服务再进化

2022年亚马逊云科技re:Invent盛会于近日在拉斯维加斯成功召开&#xff0c;吸引了众多业界精英和创新者。亚马逊云科技边缘服务副总裁Jan Hofmeyr在演讲中分享了关于亚马逊云科技海外服务器边缘计算的最新发展和创新成果&#xff0c;引发与会者热烈关注。 re:Invent的核心主题是…

057:vue组件方法中加载匿名函数

第057个 查看专栏目录: VUE ------ element UI 专栏目标 在vue和element UI联合技术栈的操控下&#xff0c;本专栏提供行之有效的源代码示例和信息点介绍&#xff0c;做到灵活运用。 &#xff08;1&#xff09;提供vue2的一些基本操作&#xff1a;安装、引用&#xff0c;模板使…

激光打标机:快速、精确、耐用的标记解决方案

随着科技的不断进步&#xff0c;激光打标机已经成为现代工业生产中不可或缺的一部分。作为一种高效、精确、耐用的标记解决方案&#xff0c;激光打标机在各个领域都发挥着重要的作用。 一、快速、精确的标记技术 激光打标机采用激光束作为标记工具&#xff0c;通过精确控制激光…

华为鸿蒙操作系统简介及系统架构分析(2)

接前一篇文章&#xff1a;华为鸿蒙操作系统简介及系统架构分析&#xff08;1&#xff09; 本文部分内容参考&#xff1a; 鸿蒙系统学习笔记(一) 鸿蒙系统介绍 特此致谢&#xff01; 上一回对于华为的鸿蒙操作系统&#xff08;HarmonyOS&#xff09;进行了介绍并说明了其层次化…

医保购药小程序:智能合约引领医疗数字革新

在医疗领域&#xff0c;医保购药小程序通过引入智能合约技术&#xff0c;为用户提供更为高效、安全的购药体验。本文将通过简单的智能合约代码示例&#xff0c;深入探讨医保购药小程序如何利用区块链技术中的智能合约&#xff0c;实现医保结算、购药监控等功能&#xff0c;为医…

Linux 宿主机搭建jenkins

目录 前言错误信息 前言 最近项目需要使用jenkins进行CICD&#xff0c;搭建后始终找不到git 错误信息 Source Code Management None出现这种情况主要是插件没有了&#xff0c;需要我们安装插件&#xff1a;

深入理解网络 I/O:mmap、sendfile、Direct I/O

&#x1f52d; 嗨&#xff0c;您好 &#x1f44b; 我是 vnjohn&#xff0c;在互联网企业担任 Java 开发&#xff0c;CSDN 优质创作者 &#x1f4d6; 推荐专栏&#xff1a;Spring、MySQL、Nacos、Java&#xff0c;后续其他专栏会持续优化更新迭代 &#x1f332;文章所在专栏&…

scrapy_redis概念作用和流程

scrapy_redis概念作用和流程 学习目标 了解 分布式的概念及特点了解 scarpy_redis的概念了解 scrapy_redis的作用了解 scrapy_redis的工作流程 在前面scrapy框架中我们已经能够使用框架实现爬虫爬取网站数据,如果当前网站的数据比较庞大, 我们就需要使用分布式来更快的爬取数…

PDF文件如何设置限制打印?

想要限制PDF文件的打印功能&#xff0c;想要限制PDF文件打印清晰度&#xff0c;都可以通过设置限制编辑来达到目的。 打开PDF编辑器&#xff0c;找到设置限制编辑的界面&#xff0c;切换到加密状态&#xff0c;然后我们就看到 有印刷许可。勾选【权限密码】输入一个PDF密码&am…

FPGA——XILINX原语(1)

FPGA——XILINX原语&#xff08;1&#xff09; 1.时钟组件&#xff08;1&#xff09;BUFG&#xff08;2&#xff09;BUFH&#xff08;3&#xff09;BUFR&#xff08;4&#xff09;BUFIO&#xff08;5&#xff09;使用场景 2.IO端口组件&#xff08;1&#xff09;IDDR&#xff0…

3. 行为模式 - 迭代器模式

亦称&#xff1a; Iterator 意图 迭代器模式是一种行为设计模式&#xff0c; 让你能在不暴露集合底层表现形式 &#xff08;列表、 栈和树等&#xff09; 的情况下遍历集合中所有的元素。 问题 集合是编程中最常使用的数据类型之一。 尽管如此&#xff0c; 集合只是一组对象的…

flink watermark 实例分析

WATERMARK 定义了表的事件时间属性&#xff0c;其形式为: WATERMARK FOR rowtime_column_name AS watermark_strategy_expression rowtime_column_name 把一个现有的列定义为一个为表标记事件时间的属性。该列的类型必须为 TIMESTAMP(3)/TIMESTAMP_LTZ(3)&#xff0c;且是 sche…

【让云服务器更灵活】iptables转发tcp/udp端口请求

iptables转发tcp/udp端口请求 文章目录 前言一、路由转发涉及点二、转发如何配置本机端口转发到本机其它端口本机端口转发到其它机器 三、固化iptables总结 前言 路由转发是计算机网络中的一种重要概念&#xff0c;特别是在网络设备和系统之间。它涉及到如何处理和传递数据包&…