Skip to content
On this page

面包屑导航组件

主要是利用vue 路由 route.matched属性 matched文档说明

本节目标 image.png

2-1 route.matched

与给定路由地址匹配的标准化的路由记录数组。

当路由路径是 /system/menu 时 会匹配到两条路由记录 image.png 每条记录里 有我们所需要的 meta 字段 image.png route.meta.title 会作为路由面包屑导航名称,如果没有 title 就不显示在面包屑导航

2-2 导入 element 的面包屑组件 ElBreadcrumb 和 ElBreadcrumbItem

image.png

2-3 安装 path-to-regexp(这里有坑 不明确)

path-to-regexp可以将动态路由路径/user/:id 转换成正则表达式。Path-to-RegExp

bash
# 安装固定版本
npm install path-to-regexp@6.2.0 --save

# npm默认安装使用时
# import { compile } from 'path-to-regexp'
# 老报错compile函数是undefined 时好时坏

1
2
3
4
5
6
7

这里我们要用它解决 面包屑导航是动态路由的情况下,点击面包屑导航 router.push 跳转时 怎么把动态参数填充进去 拼接成正常路由 /user/:class/role/:id + {id: 2, class: 3} => /user/3/role/2 然后正确跳转

typescript
例如:动态路径是 /user/:class/role/:id  我们要将{id: 2, class: 3}填充为完整路径 然后才能正确跳转

const { compile } = require('path-to-regexp')

const toPath = compile('/user/:class/role/:id')

const path = toPath({ id: 2, class: 3 })
console.log('path:', path) // path: /user/3/role/2

1
2
3
4
5
6
7
8
9

2-4 创建 Breadcrumb 组件

src/components/Breadcrumb/index.vue

vue
<template>
  <el-breadcrumb class="app-breadcrumb breadcrumb-container" separator="/">
    <el-breadcrumb-item
      v-for="(item, index) in levelList"
      :key="item.path"
    >
      <!-- 面包屑导航最后一个是不可点击的 因为最后一个正是当前所显示的路由 -->
      <span
          v-if="index == levelList.length - 1"
          class="no-redirect"
        >
          {{ item.meta.title }}
      </span>
      <a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
    </el-breadcrumb-item>
  </el-breadcrumb>
</template>

<script lang="ts">
import { defineComponent, ref, watch, onBeforeMount } from 'vue'
import { useRoute, useRouter, RouteLocationMatched, RouteLocationRaw } from 'vue-router'
import { compile } from 'path-to-regexp'

type PartialRouteLocationMatched = Partial<RouteLocationMatched>

export default defineComponent({
  name: 'Breadcrumb',
  setup() {
    const route = useRoute() // 相当于this.$route对象
    const router = useRouter() // 相当于this.$router对象
    const levelList = ref<Array<PartialRouteLocationMatched>>([]) // 导航列表 存放matched里筛选的路由记录

    // 判断是不是Dashboard路由
    const isDashboard = (route?: PartialRouteLocationMatched) => {
      const name = route && route.name
      if (!name) {
        return false
      }
      return (name as string).trim().toLocaleLowerCase() === 'Dashboard'.toLocaleLowerCase()
    }

    // 获取面包屑导航
    const getBreadcrumb = () => {
      // 对匹配的路由进行过滤 过滤掉没有title属性的路由,没有title就无法作为面包屑导航
      let matched = route.matched.filter(item => item.meta && item.meta.title) as PartialRouteLocationMatched[]
      // 获取第一个匹配路由记录
      const first = matched[0]
      // 我们要把dashboard作为首页 始终固定在面包屑导航第一个 Dashboard/System/Menu Management
      // 如果第一个匹配到的路由记录不是dashboard 我们自己就把它放在记录数组的第一项
      if (!isDashboard(first)) {
        matched = ([{
          path: '/dashboard',
          meta: {
            title: 'Dashboard'
          }
        }] as PartialRouteLocationMatched[]).concat(matched)
      }
      // route.meta.breadcrumb自定义属性 如果为false 匹配到时 将不会再面包屑导航显示该路由
      // {
      //  path: 'menu',
      //  meta: {
      //    title: 'Menu Management',
      //    breadcrumb: false // 不显示在面包屑导航 默认true
      //  }
      // }
      levelList.value = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
    }

    onBeforeMount(() => {
      getBreadcrumb()
    })

    watch(() => route.path, () => {
      getBreadcrumb()
    })

    // 主要是针对 动态路由 /user/:id 进行动态参数填充
    // path-to-regexp 文档说明 https://www.npmjs.com/package/path-to-regexp
    const pathCompile = (path: string) => {
      // 根据路径变编译成正则函数 并接收具体参数 比如根据正则/user/:id 帮你将:id替换成具体路径
      const toPath = compile(path)  // 比如 path /user/:id
      const params = route.params // { id: 10 }
      return toPath(params) // toPath({ id: 10 }) => /user/10 返回填充后的路径
    }

    // 点击面包屑导航可跳转
    const handleLink = (route: RouteLocationMatched) => {
      const { path, redirect } = route
      // 如果是重定向路由 就走重定向路径
      if (redirect) {
        router.push(redirect as RouteLocationRaw)
        return
      }
      router.push(pathCompile(path))
    }
    return {
      levelList,
      handleLink
    }
  }
})
</script>

<style lang="scss" scoped>
.app-breadcrumb.el-breadcrumb {
  display: inline-block;
  /* float: left; */
  line-height: 50px;
  font-size: 14px;
  margin-left: 8px;
}

.no-redirect {
  color: #97a8be;
  cursor: text;
}
</style>

<style lang="scss">

.breadcrumb-enter-active,
.breadcrumb-leave-active {
  transition: all .5s;
}

.breadcrumb-enter,
.breadcrumb-leave-active {
  opacity: 0;
  transform: translateX(20px);
}

.breadcrumb-leave-active {
  position: absolute;
}

.breadcrumb-move {
  transition: all .5s;
}
</style>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140

本节参考源码

https://gitee.com/brolly/vue3-element-admin/commit/45c652324fed0f105d02ae40d61614d158a2c2b8

沪ICP备20006251号-1