效果预览
首先,先解释一下什么是面包屑导航栏和路由标签栏。
如下图所示,面包屑导航栏就是展示当前所处路由信息和父辈路由信息的导航栏,它的作用是提示用户当前页面所在位置;路由标签栏就类似于浏览器的标签栏,每个标签对应一个路由页面,点击该标签可以进入该路由页面。
在这里解释一下,我所说的父辈路由是父路由的超集,对于一个子路由,它的父路由、它的父路由的父路由、它的父路由的父路由的父路由…都属于它的父辈路由。我也不知道这种叫法是否规范,反正大家理解这个意思就行。
然后展示一下效果:
这是我自己做的一个基于vue3+typescript的网站,后端是用golang写的,网站有查询微博、百度、凤凰历史热搜的功能,以及上传图片、创建相册、分享相册等云图床相关功能。这是网站预览地址和gitee仓库地址。(ps:此网站并不是一个demo,而是具有完整前后端交互和一定功能的网站,需要注册并登陆才能使用,注册很简单,只需要设置用户名、账号、密码即可。也可登陆测试账号: admin,密码: 123456。但如果有两人同时登陆这个测试账号,那么后者会把前者挤下去,当然这种概率很小罢了。)
对于侧边导航栏如何实现的,请移步这篇文章:vue-router + element-plus实现多层级路由的动态展示
在本文章中,我主要讲解如何实现面包屑导航栏和路由标签栏。
面包屑导航栏的实现
先讲一下大体思路:
- 首先弄清楚面包屑导航栏要展示哪些数据
- 然后想办法获取这些数据
- 最后把数据应用到组件中
首先,面包屑展示哪些数据?
路由的结构其实就是一棵树,没有子路由的路由对应树中的一个叶子节点,面包屑导航栏中要展示的就是这个当前页面对应的子路由、以及该子路由所有的父辈路由。
为了方便说明,我举个例子,现在有一个网页的路由结构如下图中所示,假如当前我们处于localhost:8080/earth/asia/china页面,那么当前页面的子路由就是中国,它有两个父辈路由分别是亚洲和地球,所以我们要在面包屑中展示的信息就是 “地球 / 亚洲 / 中国”
然后,如何获取这些数据?
在一棵树中,要获得一个叶子节点的所有父辈节点信息,可以从根节点开始进行dfs深度优先搜索,在搜索的过程中存一下在当前分支上搜过的路径,如果遇到叶子节点就把路径保存起来作为结果。
这种方法是一种可行方案,但其实还有更简单的方法。我们可以把这棵树线索化,在每个节点中存一下父节点的信息,那么就可以根据该节点直接获得父节点,进而获得父节点的父节点,父节点的父节点的父节点…,递归操作,就可以该节点获得所有父辈节点信息。
下面是从我的网站源码/src/router/index.ts中截取出来的代码,可以看到我在meta里自定义了一个parentRouteName属性,这个属性就是线索属性,我们可以借助这个属性获取该路由的父路由。
{
path:'weibo',
name:'Weibo',component:()=>import('../views/hot/Weibo.vue'),
meta:{
description:'微博热搜查询',
parentRouteName:"Hot",
showInAside:true,
showInHeader:true}},
下面这个函数的作用是获取某个路由的所有父辈路由信息,参数name是子路由的名称,参数routes是路由数组,返回值是一个string数组,它的值是 […, 爷爷路由名称, 父亲路由名称, 子路由名称]。
依然沿用上面的例子,假如我们传入的name属性等于’china’,那么函数的返回值是[‘earth’, ‘asia’, ‘china’]
// 获取面包屑的路由名称数组exportfunctiongetNames(name:string, routes:RouteRecordNormalized[]):string[]{let names:string[]=[]// 使用while循环进行递归操作,直至遍历到最顶级的父辈路由,然后退出循环while(true){
names.push(name)let route =getRouteByName(name, routes)as RouteRecordNormalized
let parentRoute =getRouteByName(route.meta?.parentRouteName asstring, routes)if(parentRoute){
name = parentRoute.name asstringcontinue}else{break}}return names.reverse()}
最后,将数据写入element-plus提供的面包屑组件
获取了数据之后,接下来要做的就很简单了,只需要把数据写到面包屑组件里做展示即可,如下面代码所示。
还需要添加一个路由守卫,在每次路由跳转后都要更新面包屑里的数据。其实可以写成计算属性的,毕竟route对象和router对象都是响应式的。
下面的代码是从源码/src/components/layout/Header.vue里截出来的,想看完整的代码请访问gitee仓库地址。
<template><el-breadcrumbseparator="/"><el-breadcrumb-itemv-for="description,index in descriptions":key="description":to="{ name: routeNames[index] }"class="breadcrumb-item">
{{description}}
</el-breadcrumb-item></el-breadcrumb></template><scriptsetuplang="ts">import{ getNames, getDescriptions, getRouteByName }from'../../util/handleRoutes'import{useRouter, useRoute, RouteRecordNormalized}from'vue-router'const router =useRouter()const route =useRoute()let descriptions = ref<string[]>([])let routeNames = ref<string[]>([])//获取面包屑的数据
routeNames.value =getNames(route.name as string, router.getRoutes())
descriptions.value =getDescriptions(routeNames.value, router.getRoutes())//在每次路由跳转后,更新面包屑的数据
router.afterEach((to,from)=>{//不知道为什么,进入页面时,会有两次路由跳转,route.name为空if(!route.name){
console.log(route.name, route.meta)return}
routeNames.value =getNames(route.name as string, router.getRoutes())
descriptions.value =getDescriptions(routeNames.value, router.getRoutes())})</script>
路由标签栏的实现
和上面的思路类似,要实现路由标签栏,我们也是分三个步骤:
- 首先,考虑路由标签栏需要哪些数据
- 然后,如何获取这些数据
- 最后,如何把这些应用到组件并展示出来
首先,路由标签栏需要哪些数据?
类比一下,浏览器标签栏存放的是曾经访问过的网页,那么很显然路由标签栏需要的数据就是曾经访问过的路由页面。
然后,如何获取访问过的路由页面?
我是这样做的,在pinia里定义一个routerStore,里面有个visitedRoutes属性,用来存放所有访问过的路由,它的初始值只包含首页对应的路由。
import{ defineStore }from"pinia"import{ RouteRecordNormalized}from"vue-router"import{ router }from"../router"import{ getRouteByName }from"../util/handleRoutes"let defaultRoute =getRouteByName('Home', router.getRoutes())as RouteRecordNormalized
let visitedRoutes =[defaultRoute]exportconst useRouteStore =defineStore('route',{// 推荐使用 完整类型推断的箭头函数state:()=>{return{
visitedRoutes
}},})
添加一个路由守卫,在每次路由跳转后,如果进的路由不存在于visitedRoutes,那么把这个路由添加到visitedRoutes里面。
router.afterEach((to, from)=>{if(!to.meta.showInHeader){return}let result = routeStore.visitedRoutes.find(val => val.name == to.name)if(!result){
routeStore.visitedRoutes.push(getRouteByName(to.name asstring, router.getRoutes())as RouteRecordNormalized)}})
最后,把历史访问数据应用到组件里
这一步比面包屑的最后一步要复杂一些,因为路由标签的交互比较多,当路由标签被点击时,需要跳转到相应路由。当路由标签被关闭时,如果当前页面并不是要关闭的路由标签,那么直接关闭即可;如果当前页面时要关闭的路由标签,那么需要进行路由跳转,跳转到首页。
下面的代码是从源码/src/components/layout/Header.vue里截出来的,想看完整的代码请访问gitee仓库地址。
<template><divclass="header-part-two"><el-tagv-for="visitedRoute in routeStore.visitedRoutes":key="visitedRoute.name":closable="visitedRoute.name != 'Home'":disable-transitions="false"@close="handleClose(visitedRoute.name as string)"@click="goToRoute(visitedRoute.name as string)"@click.middle="handleClose(visitedRoute.name as string)":class="{'active-tag': isActive(visitedRoute.name as string)}">
{{ visitedRoute.meta?.description }}
</el-tag></div></template><script>import{useRouter, useRoute, RouteRecordNormalized}from'vue-router'import{ useRouteStore }from'../../store/route'const routeStore =useRouteStore()const router =useRouter()const route =useRoute()// 跳转functiongoToRoute(name:string){
router.push({name})}functionisActive(name:string){return name == route.name
}// 关闭functionhandleClose(routeName: string){if(routeName ==='Home'){return}
routeStore.visitedRoutes = routeStore.visitedRoutes.filter(val=> val.name != routeName)if(route.name == routeName){
router.push('/')}}</script>
版权归原作者 开longlong了吗? 所有, 如有侵权,请联系我们删除。