记一次Vue跨导航栏问题解决方案

简述
这篇文章是我项目中,遇到的一个issue,我将解决过程和方法记录下来。
本篇文章基于Vue.js进行的前端页面构建,由于仅涉及前端,将不做数据来源及其他部分的叙述。使用的CSS框架是 BootstrapVue 和 Element-UI 。数据使用 json 文件进行模拟,数据可在文章末的链接源码中查看

需求描述

项目需求是实现双导航栏:顶部导航栏和侧边导航栏。顶部导航栏用于展示一级菜单,根据点击的不同一级菜单,在屏幕左侧展示不同的二级三级导航栏。要求当前页面导航栏菜单需要高亮。

需求分析

数据,json 文件使用axios进行获取,然后赋值给Home.vue下的menus变量,再分发到Nav.vue中,进行一级导航的展示。默认情况下,高亮第一个一级菜单。如果该一级菜单下没有二级菜单和三级菜单,则不显示左侧导航栏,如果有,则展示。当点击了一级菜单时,默认页面展示该菜单下的第一个菜单的页面。

例如:点击了第一个菜单,这个菜单有二级,三级菜单,则展示该菜单下的三级菜单的页面,如果没有三级菜单,则展示二级菜单的页面,如果没有二级菜单,则展示一级菜单的页面。

默认情况下,渲染完成侧边导航栏后高亮导航栏的第一个最下级菜单

主要代码结构

遇到的问题

1、父组件与子组件相互传值

父组件传值给子组件(Home.vue,父):

父组件传值个子组件(myNav.vue,子)

子组件通过props进行数据的接收,该属性是一个数组的形式,父组件通过绑定的形式直接给组件的属性赋值便可实现父组件向子组件传值。

子组件传值给父组件(myNav.vue 子):


子组件传值给父组件(Home.vue 父):


父组件传值给子组件与子组件传值给父组件不同,父组件需要对自定义事件进行监听,而子组件需要触发该事件才能够通过$emit进行传值,在父组件中,传值的接收变量名固定为$event,不能使用其他名字进行获取数据。

2、顶部导航栏的高亮问题

因为顶部导航栏使用的是Bootstrap-Vue框架,所以高亮菜单时只需加上class:active便可以使其高亮,问题是根据需求,高亮的控制权交由父组件Home.vue,而不在子组件myNav.vue中完成,这也就涉及到了刚刚说的组件传值的问题,上述已经叙述过组件传值的问题,此处仅简述其逻辑。
子组件中绑定点击事件后,会将点击的一级菜单的ID值传给父组件。由于原需求中,可能会涉及到URL跳转到任意页面,所以,根据this.$route.path来判断是哪一个页面,再将这个页面的ID值传回给子组件。
传回后使用三元运算符,来比对是否与渲染时id匹配,若匹配,则高亮

3、左侧导航的高亮问题

需求重述:
点击一级菜单,若该菜单数据中存在二级菜单,则在左侧导航栏中显示,如不存在,则隐藏左侧导航栏。默认情况下,若该一级菜单有下属三级子菜单,则跳转第一个下属二级子菜单下的第一个下属三级菜单;若没有三级菜单,则跳转第一个下属二级子菜单的URL;若没有下属二级子菜单,则跳转至一级的URL。在此基础上,高亮当前的菜单。

与顶部导航栏不同,顶部导航栏的高亮是依靠菜单的点击和路由来控制,而侧边导航栏使用的是element-UI的导航栏,根据default-active进行高亮。
default-active的原理是该属性的值与菜单中的index进行比较,若相等则高亮。原理很简单,但是实现起来却没有那么容易。
第一次解决方案:在点击一级菜单时,上述已经说过,将id值传给父组件Home.vue中。父组件监听到该事件触发后,遍历之前通过axios获取到的数据,最终找到匹配的一级菜单,进而判断该菜单下是否有二级乃至三级菜单,若有,则显示第一个,并且将该属性的url值赋值给上述的default-active。
从原理的角度来说,该思想没有问题。可是该方法会导致一个问题,在进入页面后,点击任何一级菜单都能够正常生效,但是互相点击时,左侧导航不显示高亮,页面能够正常跳转。

例如:进入到该页面后,第一次点击任何一级菜单都没有问题,正常显示。在第二次点击任何菜单时均没有高亮显示。

在模拟数据中,有一个一级菜单没有下属菜单,若第一次点击除此菜单以外的菜单时,均可正常显示。第二次若点击除此菜单以外的菜单时,并不会高亮。
我想说的是,若第二次点击的是这个没有下属菜单的一级菜单,第三次再去点任意菜单,又可以正常显示了。

对于这个问题,至少在我看来觉得是很神奇的。于是我将这个操作模拟到每一次的页面点击中,也就是任何一次点击都做一次向那个没有数据的菜单来一次跳转,再跳转回来,发现问题解决了。

那么问题出现在哪里,应该怎样解决,总不至于,每次跳转都需要经过那个没有下属菜单的页面吧,数据是动态的,并不清楚哪个一级菜单没有下属页面菜单。

分析
跳转至无下属菜单的一级菜单时,做了两件事,1:将侧边导航栏数据清空,2:隐藏侧边导航栏。
跳转至其他菜单时,也做了两件事,1:重新赋值侧边导航栏,2:显示侧边导航栏。
首先排除隐藏/显示导航栏,样式的修改,应该不至于会出现这种问题。(其实我也偷偷试过。。)
接下来就只剩下赋值和清空数据了
该代码如下

click1thMenu (id) {
	this.menus2th = []
	this.active1thMenu = id
	for (let i = 0; i < this.menus.length; i++) {
		if (this.menus[i].id === id) {
			if (this.menus[i].child !== undefined) {
				this.asideWidth = '200px'
				this.menus2th = this.menus[i].child
			} else {
				this.asideWidth = '0'
			}
		}
	}
}

我的代码逻辑中,不管是赋值还是情况操作都会将数据清空。到这里,问题就扑朔迷离了。

好了,不卖关子了,直接说结果
我思考,会不会因为在有下属菜单的情况下,清空语句执行后再执行赋值语句,会导致情况语句成为无用代码,被浏览器直接优化掉了,于是我在赋值语句上加了一个延时函数,给他一定的延时。

methods: {
    click1thMenu (id) {
      this.menus2th = []
      this.active1thMenu = id
      for (let i = 0; i < this.menus.length; i++) {
        if (this.menus[i].id === id) {
          if (this.menus[i].child !== undefined) {
            this.asideWidth = '200px'
            setTimeout(() => {
              this.menus2th = this.menus[i].child
            }, 10)
          } else {
            this.asideWidth = '0'
          }
        }
      }
    }

然后发现问题就解决了,如果有明白这个原理的大神,也欢迎在下面留言讨论

源码地址://gitee.com/handsky/vue-nav