Vue实现面包屑导航的正确方式

 更新时间:2023年06月20日 16:45:51   作者:小沈阳  
面包屑导航(BreadcrumbNavigation)这个概念来自童话故事“汉赛尔和格莱特”,它的作用是告诉访问者他们在网站中的位置以及如何返回,本文为大家介绍了实现面包屑导航的正确方式,需要的可以参考一下

前言

面包屑导航(BreadcrumbNavigation)这个概念来自童话故事“汉赛尔和格莱特”,当汉赛尔和格莱特穿过森林时,不小心迷路了,但是他们发现沿途走过的地方都撒下了面包屑,让这些面包屑来帮助他们找到回家的路。所以,面包屑导航的作用是告诉访问者他们在网站中的位置以及如何返回。

正如童话故事里的面包屑的作用,网站中的面包屑组件是为了告诉我们在网站中的位置。

然而,在看了一些 admin 项目后,发现面包屑的使用并不符合逻辑。

以 82k+ star 的 vue-element-admin 为例:

我们发现点击某篇文章的编辑后,导航路径从 首页/综合实例/文章列表 变为 首页/综合实例/编辑文章,而用户正确的路径是 首页/综合实例/文章列表/编辑文章。

分析 vue-element-admin 这种行为的原因

这里贴上其官网自动生成面包屑的代码:

function getBreadcrumb() {
  // only show routes with meta.title
  let matched = this.$route.matched.filter(item => item.meta && item.meta.title)
  const first = matched[0]
  if (!this.isDashboard(first)) {
    matched = [{ path: '/dashboard', meta: { title: 'Dashboard' }}].concat(matched)
  }
  this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
}

我们可以发现作者用到了 $route.matched,它的作用是:一个路由匹配到的所有路由记录会暴露为 $route 对象(还有在导航守卫中的路由对象)的 $route.matched 数组。

那么我看来看看其路由是如何注册的:

可以看到列表和编辑被注册到了同一级,matched 最后自然不会包含列表这一级。而且我们可以看到完全使用 matched 的缺点:

图中有一级综合实例,这个路径是因为在路由注册时,它与列表或者编辑页面形成了上下级关系。

所以,如果你的系统可以接收这个效果,可以参考 vue-element-admin 的行为。

怎么生成正确的路径

至于怎么生成正确的路径,如:首页/综合实例/文章列表/编辑文章。

我的方案是:每个页面都写自己的面包屑路径!

这是最灵活,适应性最强的一种方式了,当然缺点也非常明显,如果你的页面很多,那改动的成本将会非常高。

所以,我又研究了第二种方式,它依旧是依赖于路由数据生成的。

当然前面我们已经知道了 router 注册的数组,只能表示组件在页面结构上的一些父子关系,并不能正确表示用户访问的路径。

所以我们需要额外维护一份,表示页面访问关系的树形结构(可以是环形,但是代码需要额外处理,且不一定能表示正确的用户访问路径,我们此次不讨论)。

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import ListView from '@/views/ListView.vue'
import EditView from '@/views/EditView.vue'
import {useBreadcrumbStore} from "@/store/useBreadcrumbStore";
import DetailView from "@/views/DetailView.vue";
import _ from "lodash";
import ForthView from "@/views/ForthView.vue";
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      meta: {
        title: '首页'
      },
      component: HomeView
    },
    {
      path: '/list',
      name: 'list',
      meta: {
        title: '列表'
      },
      component: ListView
    },
    {
      path: '/detail',
      name: 'detail',
      meta: {
        title: '详情',
      },
      component: DetailView
    },
    {
      path: '/forth',
      name: 'forth',
      meta: {
        title: '第四级页面',
      },
      component: ForthView,
    },
    {
      path: '/edit',
      name: 'edit',
      meta: {
        title: '编辑'
      },
      component: EditView
    },
    {
      path: '/about',
      name: 'about',
      meta: {
        title: '关于'
      },
      component: () => import('../views/AboutView.vue')
    }
  ]
})
const breadcrumbsStruct = [
  {
    name: 'home',
    children: [
      {
        name: 'list',
        children: [
          {
            name: 'edit'
          },
          {
            name: 'detail',
            children: [
              {
                name: 'forth',
              },
            ],
          }
        ]
      }
    ]
  },
  {
    name: 'about'
  }
]
router.afterEach((to) => {
  const target = to.name;
  const data: string[] = []
  let finalData = data;
  function dfs(arr: any[]) { // 在 breadcrumbsStruct 查找 target,且记录下树上的路径
    if (!arr || !arr.length) {
      return;
    }
    for (let i=0; i<arr.length; i++) {
      const item = arr[i];
      data.push(item.name);
      if (item.name === target) {
        finalData = _.cloneDeep(data);
        break;
      }
      // 向深一级查找,查不到应该回溯
      dfs(item.children);
      data.pop();
    }
  }
  dfs(breadcrumbsStruct);
  const routeList = router.getRoutes();
  const routeInfoList = finalData.map(item => {
      const info = routeList.find(r => r.name === item);
      if (info) {
        return info;
      } else {
        console.error('构建路径失败');
      }
  });
  const store = useBreadcrumbStore();
  store.setBreadcrumbs(routeInfoList);
})
export default router

出了 router 的注册外,额外维护一份 breadcrumbsStruct 数据,其中 name 用于 router 中注册的页面匹配,为了方便匹配,规定它是不能重复的。children 表示从这个页面可以打开哪些页面。

afterEach 路由钩子中,我们利用回溯算法生成面包屑数据。

完整的工程代码在此处

效果演示

页面做的比较简陋,但是我需要的效果达到了。

总结

最后的方案,需要额外维护一份数据,这是需要成本的,但是这个成本可控。

面包屑导航属于项目中的小细节,一般不会引起重视,但是仔细想想,诸如 vue-element-admin 的实现确实生成的不是真正的用户访问路径。

到此这篇关于Vue实现面包屑导航的正确方式的文章就介绍到这了,更多相关Vue面包屑内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 使用Vue3和Plotly.js绘制动态3D图表的示例代码

    使用Vue3和Plotly.js绘制动态3D图表的示例代码

    在数据可视化应用中,需要将数据动态加载到图表中并进行实时更新,本文将展示如何使用Plotly.js和Vue.js实现这一功能,从加载外部数据到创建交互式图表,文中有相关的代码示例供大家参考,需要的朋友可以参考下
    2024-06-06
  • vue3.0 加载json的方法(非ajax)

    vue3.0 加载json的方法(非ajax)

    这篇文章主要介绍了vue3.0 加载json的方法(非ajax),帮助大家更好的理解和学习vue,感兴趣的朋友可以了解下
    2020-10-10
  • 解决vant-UI库修改样式无效的问题

    解决vant-UI库修改样式无效的问题

    这篇文章主要介绍了解决vant-UI库修改样式无效的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-11-11
  • vue项目同时兼容pc和移动端的解决方式

    vue项目同时兼容pc和移动端的解决方式

    我们经常在项目中会有支持pc与手机端需求,下面这篇文章主要给大家介绍了关于vue项目同时兼容pc和移动端的解决方式,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2023-10-10
  • vue移动端自适应适配问题详解

    vue移动端自适应适配问题详解

    这篇文章主要介绍了vue移动端自适应适配问题,本文通过实例代码详解给大家介绍的非常详细,需要的朋友可以参考下
    2021-04-04
  • 如何使用vue-pdf-embed实现PDF在线预览

    如何使用vue-pdf-embed实现PDF在线预览

    vue-pdf-embed是一个基于Vue.js的插件,专门用于在Vue应用中嵌入和展示PDF文件,本文将使用vue-pdf-embed实现PDF在线预览功能,有需要的小伙伴可以参考一下
    2025-03-03
  • Vue.js基础知识汇总

    Vue.js基础知识汇总

    Vue.js 专注于 MVVM 模型的 ViewModel 层。它通过双向数据绑定把 View 层和 Model 层连接了起来。Vue.js和其他库相比是一个小而美的库,作者的主要目的是通过一个尽量简单的 API 产生可反映的数据绑定和可组合的视图组件,感觉作者的思路非常清晰。
    2016-04-04
  • vue3+ts+echarts实现按需引入和类型界定方式

    vue3+ts+echarts实现按需引入和类型界定方式

    这篇文章主要介绍了vue3+ts+echarts实现按需引入和类型界定方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-10-10
  • vue3限制table表格选项个数的解决方法

    vue3限制table表格选项个数的解决方法

    这篇文章主要为大家详细介绍了vue3限制table表格选项个数的解决方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-04-04
  • 详解.vue文件中style标签的几个标识符

    详解.vue文件中style标签的几个标识符

    这篇文章主要介绍了详解.vue文件中style标签的几个标识符,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-07-07

最新评论