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

news2025/1/21 22:04:23

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>

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

nmap端口扫描工具安装和使用方法

nmap&#xff08;Network Mapper&#xff09;是一款开源免费的针对大型网络的端口扫描工具&#xff0c;nmap可以检测目标主机是否在线、主机端口开放情况、检测主机运行的服务类型及版本信息、检测操作系统与设备类型等信息。本文主要介绍nmap工具安装和基本使用方法。 nmap主…

循环神经⽹络中的梯度算法GRU

1. 什么是GRU 在循环神经⽹络中的梯度计算⽅法中&#xff0c;我们发现&#xff0c;当时间步数较⼤或者时间步较小时&#xff0c;**循环神经⽹络的梯度较容易出现衰减或爆炸。虽然裁剪梯度可以应对梯度爆炸&#xff0c;但⽆法解决梯度衰减的问题。**通常由于这个原因&#xff0…

Android模拟器的安装和adb连接

一、前置说明 APP 自动化可以使用真机进行测试&#xff0c;也可以使用模拟器来模拟安卓设备。我们可以根据个人喜好安装模拟器&#xff0c;个人推荐安装两款模拟器&#xff1a;网易 MuMu 模拟器、夜神模拟器。 MuMu模拟器可以支持 Android 12 版本&#xff0c;优点是&#xf…

服务器经常死机怎么办?如何处理

关于服务器死机这一话题相信大家是不会陌生的&#xff0c;平时在使用服务器的过程中&#xff0c;或多或少都是会有遇到过。轻则耽误业务开展&#xff0c;重则造成数据丢失&#xff0c;相信每个人都不想碰到服务器死机的情况。下文我也简单的介绍下服务器死机的原因以及对应的预…

进程间通信---无名管道

无名管道和有名管道的区别&#xff1a; 无名管道只能用于父进程和子进程之间通信&#xff0c;而有名管道可以用于任意两个进程间通信 管道工作的原理&#xff1a; 切记&#xff1a;无名管道一旦创建完成后&#xff0c;操作无名管道等同于操作文件&#xff0c;无名管道的读端/写…

全网超细,Pytest自动化测试YAML数据驱动实战(详全)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、YAML简介 yam…

acwing Linux 租云服务器环境配置

今天给大家讲解acwing Linux 租云服务器&环境配置&#xff0c;这里以阿里云为例子给大家讲解一下如何租用这个云服务器&#xff0c;现在有阿里云、华为云、腾讯云、京东云这么几个大的服务系统&#xff0c;我个人是喜欢华为云的嘻嘻&#xff0c;因为个人比较喜欢华为公司&a…

React 中的 ref 和 refs:解锁更多可能性(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

【ITK库学习】使用itk库进行图像配准:内插器(插值)

目录 1、itkNearestNeighborInterpolateImageFunction 最近点插值2、itkLinearInterpolateImageFunction 线性插值3、itkBSplineInterpolateImageFunction B样条插值4、itkWindowedSincInterpolateImageFunction 窗口化Sinc插值5、itkRayCastInterpolateImageFunction 投射插值…

阿里云ECS配置IPv6后,如果无法访问该服务器上的网站,可检查如下配置

1、域名解析到这个IPv6地址,同一个子域名可以同时解析到IPv4和IPv6两个地址&#xff0c;这样就可以给网站配置ip4和ipv6双栈&#xff1b; 2、在安全组规则开通端口可访问&#xff0c;设定端口后注意授权对象要特殊设置“源:::/0” 3、到服务器nginx配置处&#xff0c;增加端口…

虾皮跨境电商的收款方式及选择指南

虾皮&#xff08;Shopee&#xff09;作为一家知名的跨境电商平台&#xff0c;为卖家提供了多种收款方式&#xff0c;以满足不同卖家的需求。本文将介绍虾皮跨境电商平台的主要收款方式&#xff0c;并提供选择指南&#xff0c;帮助卖家根据自身需求和目标市场选择最合适的收款方…

AutoEncoder个人记录

原理 最常见的降维算法有主成分分析法PCA&#xff0c;通过对协方差矩阵进行特征分解而得到数据的主要成分&#xff0c;但是 PCA 本质上是一种线性变换&#xff0c;提取特征的能力极为有限。 AutoEncoder把长度为d_in输入特征向量变换到长度为d_out的输出向量&#xff0c;借助于…

深圳鼎信|输电线路防山火视频监控预警装置:森林火灾来袭,安全不留白!

受线路走廊制约和环保要求影响&#xff0c;输电线路大多建立在高山上&#xff0c;不仅可以减少地面障碍物和人类活动的干扰&#xff0c;还能提高线路的抗灾能力和可靠性。但同时也会面临其它的难题&#xff0c;例如森林火灾预防。今天&#xff0c;深圳鼎信智慧将从不同角度分析…

福FLUKE禄克8808A数字多用表

福禄克8808A&#xff0c;用于制造、研发、维修等应用的多功能数字表&#xff0c;FLUKE 8808A 5.5位数字多用表可以完成当今众多常用的测量工作。无论是功能测 展开 福禄克8808A&#xff0c;用于制造、研发、维修等应用的多功能数字表&#xff0c;FLUKE 8808A 5.5位数字多用表可…

NiNNet

目录 一、网络介绍 1、全连接层存在的问题 2、NiN的解决方案(NiN块) 3、NiN架构 4、总结 二、代码实现 1、定义NiN卷积块 2、NiN模型 3、训练模型 一、网络介绍 NiN&#xff08;Network in Network&#xff09;是一种用于图像识别任务的卷积神经网络模型。它由谷歌研究…

node-red:使用node-red-contrib-amqp节点,实现与RabbitMQ服务器(AMQP)的消息传递

node-red-contrib-amqp节点使用 一、简介1.1 什么是AMQP协议?1.2 什么是RabbitMQ? -> 开源的AMQP协议实现1.3 RabbitMQ的WEB管理界面介绍1.3 如何实现RabbitMQ的数据采集? -> node-red 二、node-red-contrib-amqp节点安装与使用教程2.1 节点安装2.2 节点使用2.2.1 amq…

tsconfig.app.json文件报红:Option ‘importsNotUsedAsValues‘ is deprecated...

在创建vue3 vite ts项目时的 tsconfig.json&#xff08;或者tsconfig.app.json&#xff09; 配置文件经常会报一个这样的错误&#xff1a; 爆红&#xff1a; Option ‘importsNotUsedAsValues’ is deprecated and will stop functioning in TypeScript 5.5. Specify compi…

干货:教你如何在JMeter中调用Python代码N种方法!

在性能测试领域&#xff0c;Jmeter已经成为测试专业人士的首选工具&#xff0c;用于模拟用户行为、测量响应时间、评估系统性能。而现在大部分接口都会涉及到验签、签名、加密等操作&#xff0c;为了满足特定需求&#xff0c;我们需要更多的灵活性&#xff0c;比如引入Python来…

推荐算法架构7:特征工程(吊打面试官,史上最全!)

系列文章&#xff0c;请多关注 推荐算法架构1&#xff1a;召回 推荐算法架构2&#xff1a;粗排 推荐算法架构3&#xff1a;精排 推荐算法架构4&#xff1a;重排 推荐算法架构5&#xff1a;全链路专项优化 推荐算法架构6&#xff1a;数据样本 推荐算法架构7&#xff1a;特…

QTNet:Query-based Temporal Fusion with Explicit Motion for 3D Object Detection

参考代码&#xff1a;QTNet 动机和出发点 自动驾驶中时序信息对感知性能具有较大影响&#xff0c;如在感知稳定性维度上。对于常见的时序融合多是在feature的维度上做&#xff0c;这个维度的融合主要分为如下两个方案&#xff1a; 1&#xff09;BEV-based方案&#xff1a;将之…