vue:菜单栏联动内容页面tab

一、需求

需要实现效果:左侧菜单栏与右侧内容部分联动,当点击左侧的菜单,右侧会展示对应的tab,没有点击时,不展示(如刚进入页面没有点击菜单,则没有tab);点击后没有关闭tab再打开其他菜单(如测试项目2),则测试项目2的tab高亮为选中状态。

实现效果:

二、整体实现思路

1.环境:vue、element-ui

2.首先,在el-tab-pane中,是展示的tab(如上图的测试项目1、测试项目2)。因此我们创建了一个数组activeTabs来储存tab的信息。

  • :label="getTabTitle(route.path)" 对应的则是tab展示的标题内容,我用了一个方法getTabTitle获取路由对应的标题。
  • :name="route.meta.title" 则是与el-tabs下的 v-model="activeName"相对应,如果name的值与v-model的值一样,则表示当前选中的tab。
  • 代码中,v-model="activeName" 用于控制 el-tabs 组件的当前活动标签,而 :name="route.meta.title" 用于为每个标签指定一个唯一的名称,这样就可以通过这个名称来确定当前选中的标签。通过这两个指令影响到同一个数据(例如 activeNameroute.meta.title),以达到实现标签切换的效果。
  • 当前选中的状态由 @tab-click="selectTab" 事件处理器决定。当点击一个标签时(tab-click 事件),会触发 selectTab 方法。在 selectTab 方法中,this.activeName 属性根据点击的标签的 meta.title 进行更新。v-model="activeName" 会自动反映这个变化,使得 el-tabs 组件根据 activeName 的值来确定哪个标签是当前选中的,从而产生高亮效果。
  •  @edit="handleTabsEdit"是为了对tab做一些操作,点击 tabs 的新增按钮或 tab 被关闭后触发。

值得注意的是v-model="activeName"、selectTab、 :name="route.meta.title"几个的对应关系,以及其数据结构。

   <el-tabs
      v-model="activeName"
      editable
      @edit="handleTabsEdit"
      @tab-click="selectTab"
    >
      <el-tab-pane
        v-for="(route, index) in activeTabs"
        :key="index"
        :label="getTabTitle(route.path)"
        :name="route.meta.title"
      >
      </el-tab-pane>
    </el-tabs>

三、我遇到的问题

1.数据结构问题,一开始拿到的只是name或label、path,这样是不行的,最好还是拿到当前对应菜单栏的完整router,方便我们操作。

2.点击菜单栏对应的tab没有高亮但内容显示了

3.点击关闭tab,tab关闭了但下面的内容没有关闭,没有跳转到下一个剩余tab的内容。

4.点击tab之间相互切换,功能是正常的页面内容切换,但存在问题tab没有高亮,有时候要点击两次才会高亮,判段问题是出在没有更新调用selectTab。

四、具体代码

<template>
  <section class="app-main">
    <el-tabs
      v-model="activeName"
      editable
      @edit="handleTabsEdit"
      @tab-click="selectTab"
    >
      <el-tab-pane
        v-for="(route, index) in activeTabs"
        :key="index"
        :label="getTabTitle(route.path)"
        :name="route.meta.title"
      >
      </el-tab-pane>
    </el-tabs>
    <transition name="fade-transform" mode="out-in">  //展示具体页面内容
      <content   //自定义组件
      >
        <div class="router-inner-container">
          <router-view v-show="activeName" :key="key" /> //v-show="activeName"非常重要,注意不要用v-if,用来与当前tab对应,即关闭tab也关闭当前内容。
        </div>
      </content>
    </transition>
  </section>
</template>

<script>
import { mapGetters } from 'vuex'; //引入vuex,来拿到我的所有菜单路由

export default {
  name: 'AppMain',
  props: {
    noTag: {
      type: Boolean,
      default: false
    },
    noMap: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      activeName: null, // 默认选中第一个 tab
      activeTabs: [] // 存储当前显示的 tab
    };
  },
  computed: {
    ...mapGetters(['sidebar', 'sidebarRouters']), //菜单所有路由sidebarRouters
    key() {
      return this.$route.path;
    },
    isStatisticsView() {
      if (this.$route.path == '/home/index') {
        return true;
      } else {
        return false;
      }
    }
  },

  methods: {
    selectTab(tab, event) {

      if (Array.isArray(tab) && tab.length > 0) {  //由于数据结构问题做的判段,拿到activeName 
        this.activeName = tab[0].meta.title;
      } else if (typeof tab === 'object' && tab.meta) {
        this.activeName = tab.meta.title;
      } else if (Array.isArray(tab) && !tab.length > 0) {
        this.activeName = ''; //当所有tab都关闭时,关闭所有内容,否则页面会tab都关闭了,但还有最后一个关闭的tab的页面内容。
      }
    },
    handleTabsEdit(targetName, action) {
      // 处理标签页编辑事件
      if (action === 'remove') {
        // 删除标签页
        this.removeTab(targetName);
      }
    },
    removeTab(targetName) {
      const tabs = this.activeTabs;
      const index = tabs.findIndex((tab) => tab.meta.title === targetName);
      if (index !== -1) {
        tabs.splice(index, 1);
        this.selectTab(tabs, event); //很重要,更新activeName,否则删除后不会切换下一个对应tab也不会切换页面内容
      }
    },
    updateActiveTabs() {
      const currentPath = this.$route;
      // 判断对象是否在 activeTabs 中存在
      const existsInTabs = this.activeTabs.some(
        (tab) => tab.hasOwnProperty('path') && tab.path === currentPath.path
      );
      if (!existsInTabs) {
        // 如果当前路由不在 activeTabs 中,将其添加进去
        this.activeTabs.push(currentPath);
      }
      this.selectTab(currentPath, event); //很重要,更新activeName,否则切换菜单栏时对应的tab不会高亮不会切换
    },
    findMatchingRoute(routes, targetPath) {   //为了处理数组的
      for (const route of routes) {
        if (route.path === targetPath) {
          return route;
        }
        if (route.children && route.children.length > 0) {
          const matchingChild = this.findMatchingRoute(
            route.children,
            targetPath
          );
          if (matchingChild) {
            return matchingChild;
          }
        }
      }
      return null;
    },
    getTabTitle(route) {
      // 根据路由信息获取对应的标题
      const matchingRoute = this.findMatchingRoute(this.sidebarRouters, route);
      return matchingRoute ? matchingRoute.meta.title : '';
    },
    findMatchingMenu(routes, targetTitle) {
      // 递归查找匹配的菜单项
      for (const route of routes) {
        if (route.meta) {
          if (route.meta.title === targetTitle) {
            return route;
          }

          if (route.children && route.children.length > 0) {
            const matchingChild = this.findMatchingMenu(
              route.children,
              targetTitle
            );
            if (matchingChild) {
              return matchingChild;
            }
          }
        }
      }

      return null;
    }
  },
  mounted() {},
  watch: {
    $route(to, from) {
      if (to && to.meta && !to.meta.noMap) {
        this.$nextTick(() => {
          this.$refs.jmap.resizeMap();
        });
      }
      // 更新 activeTabs
      this.updateActiveTabs();
    },
    activeName: {
      immediate: true,
      deep: true,
      handler(val, oldVal) {
        if (val && val !== oldVal) {
          const matchingMenu = this.findMatchingMenu(this.sidebarRouters, val);
          if (matchingMenu) {
            this.$router.push({ path: matchingMenu.path });  //切换tab时,切换路由来对应想要页面内容
          }
        }
      }
    }
  }
};
</script>

<style lang="scss">
.app-main {
  /*50 = navbar  */
  height: calc(100vh - 50px);
  width: 100%;
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  background: #f3f3f3;
  &.no-header {
    height: 100vh;
  }
}
.fixed-header + .app-main {
  padding-top: 60px;
}

.single-layout {
  .app-main {
    padding: 0px;
  }
}

.map-style + .router-inner-container {
  position: absolute;
}
</style>

<style lang="scss">
// fix css style bug in open el-dialog
.el-popup-parent--hidden {
  .fixed-header {
    padding-right: 15px;
  }
}
.no-header {
  .float-panel {
    height: 100vh;
  }
}
</style>

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

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

相关文章

深度学习中Numpy的一些注意点(多维数组;数据类型转换、数组扁平化、np.where()、np.argmax()、图像拼接、生成同shape的图片)

文章目录 1多维数组压缩维度扩充维度 2numpy类型转换深度学习常见的float32类型。 3数组扁平化4np.where()的用法5np.argmax()6图像拼接7生成同shape的图片&#xff0c;指定数据类型 1多维数组 a.shape(3,2);既数组h3&#xff0c;w2 a.shape(2,3,2);这里第一个2表示axis0维度上…

C#用Convert.ToString(Int32, Int32)和Convert.Tolnt64(String, Int32)进行数值转换

目录 一、Convert.ToString(Int32, Int32) 方法 1.定义 2. 示例 二、Convert.ToInt64(String, Int32) 1.定义 2.实例 三、用Convert.ToString(Int32, Int32)和Convert.Tolnt64(String, Int32)进行数值转换 1.Main() 2.类库 3.生成效果 使用Convert.ToString(Int32…

QuestDB时序数据库快速入门

简介 QuestDB是一个开源的高性能时序数据库&#xff0c;专门用于处理时间序列相关的数据存储与查询&#xff1b; QuestDB使用列式存储模型。数据存储在表中&#xff0c;每列存储在其自己的文件和其自己的本机格式中。新数据被附加到每列的底部&#xff0c;以便能够按照与摄取…

storm统计服务开启zookeeper、kafka 、Storm(sasl认证)

部署storm统计服务开启zookeeper、kafka 、Storm&#xff08;sasl认证&#xff09; 当前测试验证结果&#xff1a; 单独配置zookeeper 支持acl 设置用户和密码&#xff0c;在storm不修改代码情况下和kafka支持当kafka 开启ACL时&#xff0c;storm 和ccod模块不清楚配置用户和密…

【分享】MathWorks中国汽车年会:“软件定义汽车”

从软件赋能到软件定义&#xff0c;汽车行业不仅需要解决诸如错误发现滞后带来的高昂代价、功能融合所需的跨学科知识、功能安全与实施成本之间的权衡等老问题&#xff0c;也面临着新的挑战&#xff1a;软件复杂度的不断提升、利用数据驱动创造价值、人工智能的引入和实现、数字…

PyCharm 快捷键(Ctrl + R)正则表达式批量替换

目录 一、使用快捷键CtrlR&#xff0c;打开替换界面 二、输入替换格式 三、点击全部替换 一、使用快捷键CtrlR&#xff0c;打开替换界面 二、输入替换格式 在第一个框输入 (.*): (.*) 第二个框输入 $1:$2, 三、点击全部替换

【JVM】并发的可达性分析详细解释

​ &#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;JVM ⛳️ 功不唐捐&#xff0c;玉汝于成 ​ 目录 前言 正文 可达性分析的基本原理&#xff1a; 根集合&#xff08;Root Set&#xff09;&#xff1a; 对象引用关系&#xff1a; 标记…

Java导出Excel并合并单元格

需求&#xff1a;需要在导出excel时合并指定的单元格 ruoyi excel 项目基于若伊框架二次开发&#xff0c;本着能用现成的就不自己写的原则&#xff0c;先是尝试了Excel注解中needMerge属性 /*** 是否需要纵向合并单元格,应对需求:含有list集合单元格)*/public boolean needMer…

记一次 .NET某道闸收费系统 内存溢出分析

一&#xff1a;背景 1. 讲故事 前些天有位朋友找到我&#xff0c;说他的程序几天内存就要爆一次&#xff0c;不知道咋回事&#xff0c;找不出原因&#xff0c;让我帮忙看一下&#xff0c;这种问题分析dump是最简单粗暴了&#xff0c;拿到dump后接下来就是一顿分析。 二&…

11.1 pcl_ros的点云学习

本文是看了两个博主的内容&#xff0c;整理在这里是为了以后用时方便查找&#xff0c;更容易理解。引用的博文路径如下&#xff08;本人也是刚开始看PCL的运用&#xff0c;本文是完全抄下面博主的内容&#xff0c;觉得这位博主写的很详细很清楚&#xff0c;并且自己运行了一遍有…

Java17新特性详解含示例代码(值得珍藏)

1. 概述 Java 17 是 Java 开发工具包&#xff08;JDK&#xff09;的一个重要版本&#xff0c;它带来了一系列的新特性和改进&#xff0c;以进一步增强 Java 语言的功能和性能。以下是 Java 17 中的一些主要新特性及其详细说明。 2. 新特性详解 JEP 356: Enhanced Pseudo-Ran…

uniapp uni.chooseLocation调用走失败那里,错误码:112

问题&#xff1a;我配置了百度上所有能配置的&#xff0c;一直调用不成功&#xff0c;如下图配置的 1:第一个 配置 代码&#xff1a; "permission": {"scope.userLocation": {"desc": "你的位置信息将用于小程序位置接口的效果展示"}…

openpose之使用摄像头检测并输出到json文件

编程如画&#xff0c;我是panda&#xff01; 前言 之前给大家分享了如何搭建openpose环境&#xff0c;并进行了测试案例&#xff0c;但是如果要使用摄像头的话&#xff0c;还需要修改一下运行文件&#xff0c;并且这次会教大家如何输出到json文件 。 如果环境还没有搭建好&am…

Unix时间戳

时间戳&#xff0c;相信很多相关专业的人&#xff0c;计算机软件电子等等都会听过。由于最早是由Unix系统使用所以又叫Unix时间戳。 Unix 时间戳&#xff08;Unix Timestamp&#xff09;定义为从UTC&#xff08;世界协调时&#xff09;/GMT&#xff08;格林尼治时&#xff09;…

iPhone解锁工具---AnyMP4 iPhone Unlocker 中文

AnyMP4 iPhone Unlocker是一款功能强大的iPhone解锁软件&#xff0c;旨在帮助用户轻松解锁iPhone&#xff0c;从而在电脑上进行数据备份、传输和编辑。该软件支持多种iPhone型号&#xff0c;包括最新的iPhone 14系列&#xff0c;并支持多种解锁模式&#xff0c;如屏幕密码解锁、…

【Docker】安装 Nacos容器并根据Nginx实现负载均衡

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《Docker实战》。&#x1f3af;&#x1f3af; &…

Go后端开发 -- 反射reflect 结构体标签

Go后端开发 – 反射reflect && 结构体标签 文章目录 Go后端开发 -- 反射reflect && 结构体标签一、反射reflect1.编程语言中反射的概念2.interface 和反射3.变量内置的pair结构4.reflect的基本功能TypeOf和ValueOf5.从relfect.Value中获取接口interface的信息6…

2018年认证杯SPSSPRO杯数学建模D题(第二阶段)投篮的最佳出手点全过程文档及程序

2018年认证杯SPSSPRO杯数学建模 D题 投篮的最佳出手点 原题再现&#xff1a; 影响投篮命中率的因素不仅仅有出手角度、球感、出手速度&#xff0c;还有出手点的选择。规范的投篮动作包含两膝微屈、重心落在两脚掌上、下肢蹬地发力、身体随之向前上方伸展、同时抬肘向投篮方向…

SpringBoot:前端提交数据,服务端无法获取数据

http://www.xxx.com?phone111111111111&code1332 上述访问传值方式为键值对方式&#xff0c;服务端springmvc获取 >> // 在HttpServlet实现类的doGet、doPost方法中获取前端传来的值 doGet(ServerHttpRequest request){String phone request.getParameter("…

Codeforces Round 767 (Div. 1) D2. Game on Sum (Hard Version)(博弈 期望 dp 贡献)

题目 t(t<1e5)组样例&#xff0c;每次给定n,m,k(m<n<1e6&#xff0c;0<k<1e97) 有一个游戏&#xff0c;持续n轮&#xff0c;每轮Alice先选一个[0,k]的实数&#xff0c; Bob决定从总分里加上这个值还是减去这个值 特别地&#xff0c;n轮里&#xff0c;Bob选择…