前言
之前的几篇文章都是基于comtainer布局的aside边栏部分来写的,像logo、menu以及Icon图标组件,后面也写了关于路由动态加载和菜单渲染的文章。
今天就撤离aside,来讲一下header部分的实现。
NavBar导航栏
在BuidAdmin中,header部分实现了NavBar。可以看到NavBar由两部分构成,一个是左侧的可变的tab页,一个是右边固定的菜单栏。
通过源码,来看看BuildAdmin的header是如何实现的。
可以看到header的内容是由 <component> 动态组件实现的,使用is属性绑定不同的导航栏组件。通过修改pinia定义的状态变量 config.layout.layoutMode,然后拼接NavBar,来映射defineComponent中的导航组件。
pinia中定义了四种布局模式,Default|Classic|Streamline|Double,默认是Default。不同的布局也定义了不同的NavBar。这里看一下效果。
一开始BuildAdmin使用的就是默认布局。我在这里不需要使用动态组件,也不需要实现其他三个布局组件,我只实现一个默认布局的navBar。我们在navBar目录下查看默认布局中navBar是如何定义的。
从default.vue也可以看到,NavBar是由NavTabs和NavMenus两个组件组成的 这里就先看tabs的实现。
NavTab
用开发者工具查看源码,分析tab的实现。
从源码可以看到: 整个导航栏就是一个div,里面有多个tab。一个tab是由一个div和一个Icon元素组成。
最简单的代码实现:
这时候还没有定义css,展示出来的效果如下:
接着定义css样式。
主要使用的是flex弹性布局,然后水平分布居中。BuildAdmin中是在navBar中通过deep透传样式给子元素tab的。我这里直接抽离这部分代码直接到tab.vue中,这样便于直观阅读,我们看一下添加css后的样式。
同时,选中tab时字体颜色加深;选中关闭按钮时候,按钮会有动画。
当然,上面的tab是写死的,点击菜单栏并不能增加,点击关闭按钮也无法关闭。所以接下来就是实现两个部分:添加按钮和关闭按钮。
动态添加tab
我们点击menu菜单,如果没有这个tab就新建一个,如果有这个页面tab,就跳转,可以看到,tab的创建、跳转是和路由同步的,所以tab的实现离不开router。
在BuildAdmin中的tabs.vue中实现了动态添加tab的功能。
使用了路由守卫onBeforeRouteUpdate来监测路由是否更新,如果更新则触发 addTabs() 添加tab到tabsNav的tabsView中去。
实现动态添加tab
但是onBeforeRouteUpdate使用起来有一定的难度,搞了好久,查阅了好多文章都没有达到想要的效果,所以这一块我就换了一种思路,就用了watch来监控activeRoute。
使用watch的话就需要自己去另外实现一些功能,比如activeRoute是如何设置的。那么,activeRoute是什么呢,接着往下看。
1. 定义tabs状态
使用pinia定义了一个userNavTabs的路由信息状态,方便各个组件修改路由的状态。
之前在路由动态加载中就提到过,在router.ts工具类中,通过 setTabsViewRoutes()将处理好的菜单路由放到了tabsViewRoutes中,然后渲染menu。
activeRoute、activeIndex、tabsView 是实现tabs的三个变量。activeRoute表示当前激活的路由是哪个,这样才能利用route.push 跳转到目标页面。
tabsView是存放tabs的地方,一共两个作用:1是这样在NavTabs组件中渲染一个tab,2是用于排除重复tab的作用。
最后就是activeIndex,表示当前激活的路由在tabsView中的下标。
2. 获取activeRoute
使用onBeforeRouteUpdate的好处就是,这个导航守卫函数,有to和from的路由参数。使用watch只能监控某一个路由的变化,没法获取to和from两个路由,只能从其他导航守卫来处理这两个路由。
activeRoute为激活的路由,什么是激活的路由?那肯定是即将要跳转到目标路由,在router导航守卫afterEach中,可以获取到to路由
router.afterEach((to, from) => {
// 用于添加tab
const navTabs = useNavTabs()
// 将即将跳转的路由设置为activeRoute
navTabs.setActiveRoute(to)
}
我们在这里调用navTabs.setActiveRoute(to) 将目标路由设置为activeRoute。稍等会儿我们看看setActiveRoute方法的定义。
3. watch回调函数
watch是监控一个变量的变化,然后执行一个回调函数,在tab的新增、关闭、跳转中,变化的还是路由,activeRoute作为一个一直在变化的目标路由,且是一个共享状态变量,所以用来作为watch的变量再也合适不过了。
watch(() => navTabs.state.activeRoute,
() => {
navTabs.addTabs()
nextTick(() => {
const div = tabsRefs.value[navTabs.state.activeIndex]
selectNavTab(div)
})
})
当点击菜单,activeRoute会发生变化,触发watch调用回调函数,回调函数就会调用状态变量的addTabs() 函数。我们来看看addTabs的定义:
主要核心代码逻辑就是:遍历tabsView,根据路由的path判断activeRoute是否在tabsView中,如果在,则结束方法;如果不在,将activeRoute放到tabsView中。在这里,就保证了在tabsView中的tab不会重复。
4. setActiveRoute
这里也一起把setActiveRoute 看了,当在路由导航守卫afterEach调用此方法时,就会将afterEach传过来的to路由赋值给activeRoute,然后还是遍历tabsView,匹配判断to在tabsView的下标位置,然后赋值给activeIndex,activeIndex有什么用,后面在在tab关闭会讲。
看完setActiveRoute有两个疑惑:
为什么要判断path为admin?因为在整个项目中会有很多路由,例如上面讲到的404、loading都是,所以这里的路由必须保证为menu中的路由,所以要以admin开头。
为什么要调用addTabs方法?因为只有addTabs方法中才会向tabsView添加路由,不执行在tabsView中就找不到activeRoute的index,activeIndex也无法赋值。
总结:只要路由跳转,activeRoute和activeIndex就会改变。
4. 创建tab
最后就是实现tabs.vue,遍历tabsView渲染导航栏的tabs。
结语
这样就实现了tab的基本功能,后面会接着写tab的关闭和切换。