首页 > 编程语言 >(系列十)Vue3中菜单和路由的结合使用,实现菜单的动态切换(附源码)

(系列十)Vue3中菜单和路由的结合使用,实现菜单的动态切换(附源码)

时间:2024-10-30 16:34:35浏览次数:3  
标签:el 菜单 menu value 源码 Vue3 routes path

说明

    该文章是属于OverallAuth2.0系列文章,每周更新一篇该系列文章(从0到1完成系统开发)。

    该系统文章,我会尽量说的非常详细,做到不管新手、老手都能看懂。

    说明:OverallAuth2.0 是一个简单、易懂、功能强大的权限+可视化流程管理系统。

友情提醒:本篇文章是属于系列文章,看该文章前,建议先看之前文章,可以更好理解项目结构。

qq群:801913255

有兴趣的朋友,请关注我吧(*^▽^*)。

关注我,学不会你来打我

上篇回顾

在上一篇:(系列九)使用Vue3+Element Plus创建前端框架(附源码) 博客中,我们说道,使用vue3+element plus 创建项目,成功实现了布局组件container+菜单组件Menu搭建框架。

布局样式如下:

然而我们只是实现了界面的搭建,并没有实现任何交互。

也因此有很多人在询问,如何做动态切换菜单。

我想说,不要慌,一切需求都会安排到位。

接下来我们就要实现菜单和路由的结合使用,做到动态切换菜单。

安装路由

命令:npm install vue-router

安装成功后,手动创建以下目录及文件

base-routes.ts 内容

import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
import Panel from '../../views/panel/index.vue';

export const routes: RouteRecordRaw[] = [];

routes.push(
  {
    path: '/panel',
    component: Panel,
    name: "面板",
  },
  {
    path: '/menu',
    redirect: '/menu/index',
    meta: { title: '菜单管理' },
    name: "菜单管理",
    children: [
      {
        path: '/menu',
        name: '菜单',
        component: () => import('../../views/menu/index.vue'),
        meta: { title: '菜单', requireAuth: true, affix: true, closable: false },
      }
    ]
  },
  {
    path: '/user',
    meta: { title: '用户管理' },
    name: "用户管理",
    children: [
      {
        path: '/user',
        name: '用户',
        component: () => import('../../views/user/index.vue'),
        meta: { title: '用户' },
      }]
  },
)

//创建路由,并且暴露出去
const router = createRouter({
  history: createWebHashHistory(), //开发环境
  //history:createWebHistory(), //正式环境
  routes
})
export default router

该文件主要是配置菜单的json文件,及暴露路由。里面的属性应该不必多说,很容易看懂。

至于views文件夹中的vue文件内容,大家随便填写什么都可以,只要三个页面的内容不一样即可。

然后在main.ts中配置路由,全局变量。

如下图:

使用路由

做完以上步骤,接下来的工作就很简单了,我们只需要,在HelloWorld.vue(接上一篇文章代码),中修改代码如下

el-main中的内容替换为 <router-view></router-view> el-menu中添加 router属性 然后导入base-routes.ts 文件,并添加如下代码
  const menu = routes;
    return {
      menu,
    };

完整的HelloWorld.vue代码如下

<template>
  <div style="height: calc(100vh); overflow: hidden">
    <el-container style="height: 100%; overflow: hidden">
      <el-aside width="auto">
        <el-menu
          class="el-menu-vertical-demo"
          background-color="#545c64"
          text-color="#fff"
          active-text-color="#ffd04b"
          style="height: 100%"
          router
        >
          <div class="el-menu-box">
            <div
              class="logo-image"
              style="width: 18px; height: 18px; background-size: 18px 18px"
            ></div>
            <div style="padding-left: 5px; padding-top: 7px">
              OverallAuth2.0
            </div>
          </div>
          <div v-for="menuItem in menu" :key="menuItem.path">
            <el-sub-menu
              v-if="menuItem.children && menuItem.children.length"
              :index="menuItem.path"
              :key="menuItem.name"
            >
              <template #title>
                <el-icon><location /></el-icon>{{ menuItem.name }}</template
              >
              <el-menu-item
                v-for="subMenuItem in menuItem.children"
                :index="subMenuItem.path"
                :route="{ name: subMenuItem.name }"
                :key="subMenuItem.name"
                style="cursor: pointer"
              >
                {{ subMenuItem.name }}
              </el-menu-item>
            </el-sub-menu>

            <el-menu-item
              v-else
              :index="menuItem.path"
              :key="menuItem.path"
              :route="{ name: menuItem.name }"
              style="cursor: pointer"
            >
              {{ menuItem.name }}
            </el-menu-item>
          </div>
        </el-menu>
      </el-aside>

      <el-container>
        <el-header class="headerCss">
          <div style="display: flex; height: 100%; align-items: center">
            <div
              style="
                text-align: left;
                width: 50%;
                font-size: 18px;
                display: flex;
              "
            >
              <div class="logo-image" style="width: 32px; height: 32px"></div>
              <div style="padding-left: 10px; padding-top: 7px">
                OverallAuth2.0 权限管理系统
              </div>
            </div>
            <div
              style="
                text-align: right;
                width: 50%;
                display: flex;
                justify-content: right;
                cursor: pointer;
              "
            >
              <div
                class="user-image"
                style="width: 22px; height: 22px; background-size: 22px 22px"
              ></div>
              <div style="padding-left: 5px; padding-top: 3px">
                微信公众号:不只是码农
              </div>
            </div>
          </div>
        </el-header>

        <el-main>
          <router-view></router-view>
        </el-main>
      </el-container>
    </el-container>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from "vue";
import { routes } from "../router/module/base-routes";

export default defineComponent({
  setup() {
    const menu = routes;
    return {
      menu,
    };
  },
  components: {},
});
</script>

<style scoped>
.el-menu-vertical-demo:not(.el-menu--collapse) {
  width: 200px;
  min-height: 400px;
}
.el-menu-box {
  display: flex;
  padding-left: 25px;
  align-items: center;
  height: 57px;
  box-shadow: 0 1px 4px #00152914;
  border: 1px solid #00152914;
  color: white;
}
.el-main {
  padding-top: 0px;
  padding-left: 1px;
  padding-right: 1px;
  margin: 0;
}
.headerCss {
  font-size: 12px;
  border: 1px solid #00152914;
  box-shadow: 0 1px 4px #00152914;
  justify-content: right;
  align-items: center;
  /* display: flex; */
}
.logo-image {
  background-image: url("../components/权限分配.png");
}
.user-image {
  background-image: url("../components/用户.png");
}
.demo-tabs /deep/ .el-tabs__header {
  color: #333; /* 标签页头部字体颜色 */
  margin: 0 0 5px !important;
}
.demo-tabs /deep/ .el-tabs__nav-wrap {
  padding-left: 10px;
}
</style>

做好这些后,我们就能够动态切换菜单。

效果如下图

是不是很完美。

不,我想说,还没有完,还差的远。

接着看往下看

加入tab标签

可能大家也发现了,在我们点击左侧菜单时,访问过的菜单,在系统中没有历史访问标签。

现在我们做以下操作,把访问过的菜单记录到tab标签中,以防止系统重新对接口进行请求。

同样修改HelloWorld.vue文件。

把el-main标签中的内容换成

 <el-tabs
            v-if="tabsList.length > 0"
            v-model="defaultActive"
            class="demo-tabs"
            @click="tabsClick(defaultActive)"
            @tab-remove="tabRemoveClick"
          >
            <el-tab-pane
              v-for="item in tabsList"
              :label="item.name"
              :name="item.path"
              :key="item.path"
              :closable="item.path == '/panel' ? false : true"
              style="font-size: 16px;"
            >
              <router-view></router-view>
            </el-tab-pane>
          </el-tabs>
在el-menu-item标签中,加入菜单切换事件menuItemClick() setup()方法中的内容替换成
 setup() {
    const defaultActive = ref("/panel");
    const menu = routes;
    const tabsList = ref<RouteRecordRaw[]>([]);
      onMounted(() => {
      tabsList.value.push(routes[0]);
      router.push(routes[0]);
    });
    //菜单项点击事件
    function menuItemClick(subMenuItem: RouteRecordRaw) {
      // tabList中不存在则追加
      if (!tabsList.value.some((sub) => sub.path == subMenuItem.path)) {
        tabsList.value.push(subMenuItem);
      }
      defaultActive.value = subMenuItem.path;
    }

    //菜单标签点击事件
    const tabsClick = (item: string) => {
      defaultActive.value = item;
      router.push({ path: item });
    };

    //菜单标签移除事件
    const tabRemoveClick = (path: any) => {
      tabsList.value.map((item: { path: string }, index: any) => {
        if (item.path == path) tabsList.value.splice(index, 1); //index 当前元素索引;1:需要删除的元素个数
      });
      defaultActive.value = "/panel";
      router.push({ path: "/panel" });
    };
    return {
      menu,
      tabsList,
      defaultActive,
      tabsClick,
      tabRemoveClick,
      menuItemClick,
    };
  },

以上是HelloWorld.vue文件中变动的代码。

我们来看下完整的HelloWorld.vue文件代码

<template>
  <div style="height: calc(100vh); overflow: hidden">
    <el-container style="height: 100%; overflow: hidden">
      <el-aside width="auto">
        <el-menu
          :default-active="defaultActive"
          class="el-menu-vertical-demo"
          background-color="#545c64"
          text-color="#fff"
          active-text-color="#ffd04b"
          style="height: 100%"
          router
        >
          <div class="el-menu-box">
            <div
              class="logo-image"
              style="width: 18px; height: 18px; background-size: 18px 18px"
            ></div>
            <div style="padding-left: 5px; padding-top: 7px">
              OverallAuth2.0
            </div>
          </div>
          <div v-for="menuItem in menu" :key="menuItem.path">
            <el-sub-menu
              v-if="menuItem.children && menuItem.children.length"
              :index="menuItem.path"
              :key="menuItem.name"
            >
              <template #title>
                <el-icon><location /></el-icon>{{ menuItem.name }}</template
              >
              <el-menu-item
                v-for="subMenuItem in menuItem.children"
                :index="subMenuItem.path"
                :route="{ name: subMenuItem.name }"
                :key="subMenuItem.name"
                @click="menuItemClick(subMenuItem)"
                style="cursor: pointer"
              >
                {{ subMenuItem.name }}
              </el-menu-item>
            </el-sub-menu>

            <el-menu-item
              v-else
              :index="menuItem.path"
              :key="menuItem.path"
              :route="{ name: menuItem.name }"
              @click="menuItemClick(menuItem)"
              style="cursor: pointer"
            >
              {{ menuItem.name }}
            </el-menu-item>
          </div>
        </el-menu>
      </el-aside>

      <el-container>
        <el-header class="headerCss">
          <div style="display: flex; height: 100%; align-items: center">
            <div
              style="
                text-align: left;
                width: 50%;
                font-size: 18px;
                display: flex;
              "
            >
              <div class="logo-image" style="width: 32px; height: 32px"></div>
              <div style="padding-left: 10px; padding-top: 7px">
                OverallAuth2.0 权限管理系统
              </div>
            </div>
            <div
              style="
                text-align: right;
                width: 50%;
                display: flex;
                justify-content: right;
                cursor: pointer;
              "
            >
              <div
                class="user-image"
                style="width: 22px; height: 22px; background-size: 22px 22px"
              ></div>
              <div style="padding-left: 5px; padding-top: 3px">
                微信公众号:不只是码农
              </div>
            </div>
          </div>
        </el-header>

        <el-main>
          <el-tabs
            v-if="tabsList.length > 0"
            v-model="defaultActive"
            class="demo-tabs"
            @click="tabsClick(defaultActive)"
            @tab-remove="tabRemoveClick"
          >
            <el-tab-pane
              v-for="item in tabsList"
              :label="item.name"
              :name="item.path"
              :key="item.path"
              :closable="item.path == '/panel' ? false : true"
              style="font-size: 16px"
            >
              <router-view></router-view>
            </el-tab-pane>
          </el-tabs>
        </el-main>
      </el-container>
    </el-container>
  </div>
</template>

<script lang="ts">
import { defineComponent, onMounted, ref } from "vue";
import router, { routes } from "../router/module/base-routes";
import { RouteRecordRaw } from "vue-router";

export default defineComponent({
  setup() {
    const defaultActive = ref("/panel");
    const menu = routes;
    const tabsList = ref<RouteRecordRaw[]>([]);
    
    //初始加载dom
    onMounted(() => {
      tabsList.value.push(routes[0]); //默认打开第一个标签
      router.push(routes[0]); 
    });
    //菜单项点击事件
    function menuItemClick(subMenuItem: RouteRecordRaw) {
      // tabList中不存在则追加
      if (!tabsList.value.some((sub) => sub.path == subMenuItem.path)) {
        tabsList.value.push(subMenuItem);
      }
      defaultActive.value = subMenuItem.path;
    }

    //菜单标签点击事件
    const tabsClick = (item: string) => {
      defaultActive.value = item;
      router.push({ path: item });
    };

    //菜单标签移除事件
    const tabRemoveClick = (path: any) => {
      tabsList.value.map((item: { path: string }, index: any) => {
        if (item.path == path) tabsList.value.splice(index, 1); //index 当前元素索引;1:需要删除的元素个数
      });
      defaultActive.value = "/panel";
      router.push({ path: "/panel" });
    };
    return {
      menu,
      tabsList,
      defaultActive,
      tabsClick,
      tabRemoveClick,
      menuItemClick,
    };
  },
  components: {},
});
</script>

<style scoped>
.el-menu-vertical-demo:not(.el-menu--collapse) {
  width: 200px;
  min-height: 400px;
}
.el-menu-box {
  display: flex;
  padding-left: 25px;
  align-items: center;
  height: 57px;
  box-shadow: 0 1px 4px #00152914;
  border: 1px solid #00152914;
  color: white;
}
.el-main {
  padding-top: 0px;
  padding-left: 1px;
  padding-right: 1px;
  margin: 0;
}
.headerCss {
  font-size: 12px;
  border: 1px solid #00152914;
  box-shadow: 0 1px 4px #00152914;
  justify-content: right;
  align-items: center;
  /* display: flex; */
}
.logo-image {
  background-image: url("../components/权限分配.png");
}
.user-image {
  background-image: url("../components/用户.png");
}
.demo-tabs /deep/ .el-tabs__header {
  color: #333; /* 标签页头部字体颜色 */
  margin: 0 0 5px !important;
}
.demo-tabs /deep/ .el-tabs__nav-wrap {
  padding-left: 10px;
}
</style>

我们看下效果

后端WebApi 预览地址:http://139.155.137.144:8880/swagger/index.html

前端vue 预览地址:http://139.155.137.144:8881

关注公众号:发送【权限】,获取前后端代码

有兴趣的朋友,请关注我微信公众号吧(*^▽^*)。

关注我:一个全栈多端的宝藏博主,定时分享技术文章,不定时分享开源项目。关注我,带你认识不一样的程序世界

标签:el,菜单,menu,value,源码,Vue3,routes,path
From: https://www.cnblogs.com/cyzf/p/18502316

相关文章

  • SpringBoot安卓开发的水果商城app (案例分析)-附源码
    摘  要在移动互联网的快速发展背景下,手机应用已成为人们生活中不可或缺的一部分。水果商城App作为电商领域的重要应用之一,为用户提供便捷的购物体验和丰富的商品选择。本研究旨在基于Android平台开发水果商城App,结合SpringBoot框架和Mysql数据库,以实现功能强大、操作简......
  • vue2使用vue3语法
    CompositionAPICompositionAPI将是Vue3的核心功能,它具有许多更改和性能改进。我们也可以在Vue2中通过npm插件@vue/composition-api使用它。安装yarnadd@vue/composition-api之后,在入口文件main.js中使用它。importVuefrom'vue'importVueCompositionAPI......
  • (开题报告)django+vue线上自习管理系统论文+源码
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容一、选题背景关于线上自习管理系统的研究,现有研究主要以传统线下自习室管理或单纯的线上学习平台为主,专门针对django+vue技术构建线上自习管理系......
  • (开题报告)django+vue同城搬家管理系统论文+源码
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容选题背景关于同城搬家管理系统的研究,现有研究主要以传统的搬家服务运营模式为主,专门针对借助django+vue技术构建同城搬家管理系统的研究较少。在......
  • 推荐一款免费开源的PDF文件处理神器!上手简单,一步到位(带私活源码)
     背景介绍PDFGuru致力于打造一款PDF文件处理神器,具有PDF合并、拆分、旋转、水印、加密、等20多项常用功能,本项目完全开源,个人用户使用免费,上手简单,超级好用。功能特点本地化:本地安全,离线运行,不必担心隐私泄露功能丰富:支持包括PDF批量合并、拆分、添加水印、加密/解密、......
  • 虚拟小玩具!推荐一个基于网页技术的3D魔方,摸鱼党快玩(带私活源码)
     楔子魔方,这个词汇对于大家来说应该并不陌生。在儿时的记忆中,我们曾经可以一整天都在玩魔方。然而,随着时间的流逝,我们步入了程序员的行业,每天与电脑的鼠标和键盘为伍。在这个过程中,魔方也与时俱进,从实体玩具转变为装载在电脑中的虚拟小玩具。对制作网页魔方原理感兴趣的......
  • (开题报告)django+vue小红书App论文+源码
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容选题背景关于django和vue在社交类App开发中的应用研究,现有研究主要集中在电商、在线教育等领域。专门针对构建类似小红书App这种以用户分享、社区......
  • 基于Spring的高校实习信息发布网站的设计与实现,LW+源码+讲解
    摘   要传统信息的管理大部分依赖于管理人员的手工登记与管理,然而,随着近些年信息技术的迅猛发展,让许多比较老套的信息管理模式进行了更新迭代,职位实习信息因为其管理内容繁杂,管理数量繁多导致手工进行处理不能满足广大用户的需求,因此就应运而生出相应的高校实习信息发布网......
  • 基于协同推荐的黔醉酒业白酒销售系统设计与实现,LW+源码+讲解
    摘 要基于协同推荐的黔醉酒业白酒销售系统主要针对黔醉酒业的具体业务需求所设计,现阶段阶段我国大型企业都会有自己的电商平台以及销售管理系统,其功能对于中小型过于冗长复杂,成本也不是中小型企业能够承受的,而用电子报表来解决销售统计等问题呢,又过于浪费人力资源,且效率不高......
  • 《vue3第五章》新的组件,包含:Fragment、Teleport、Suspense
    @目录五、新的组件1.Fragment2.Teleport案例完整代码3.Suspense案例完整代码本人其他相关文章链接五、新的组件1.Fragment在Vue2中:组件必须有一个根标签在Vue3中:组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中好处:减少标签层级,减小内存占用2......