vue 如何做一个动态的 BreadCrumb 组件,el-breadcrumb ElementUI

elementUI 中的 Breadcrumb 组件是这样定义的

html 复制代码
<template>
  <el-breadcrumb separator="/">
    <el-breadcrumb-item :to="{ path: '/' }">主页</el-breadcrumb-item>
    <el-breadcrumb-item>系统配置</el-breadcrumb-item>
    <el-breadcrumb-item>基础配置</el-breadcrumb-item>
    <el-breadcrumb-item>自动登录</el-breadcrumb-item>
  </el-breadcrumb>
</template>

效果如图:

二、实现原理

我们需要实现的是,让它自己通过路由去填写这部分内容

原理是根据当前路由值,拆分成多个段,然后通过路由 path 去匹配对应的路由名称,再填入到上面的内容中即可。

比如:

1. 当前的路由值是 /system/normal-setup/auto-login

2. 通过拆分 / 生成一个数组

3. 依次匹配对应的路由名称

得到这个数组之后,依次去路由列表中匹配对应的路由名称

  • /system 系统配置
  • /system/normal-setup 基础配置
  • /system/normal-setup/auto-login 自动登录

4. 结果

这样就得到了一个 breadCrumb 数组,直接遍历这个数组,显示 BreadCrumb 即可

三、具体实现过程

知道了上面的实现原理,才会有具体的实现过程,这个过程还是有点麻烦的。

1. 处理路由数据

项目中用到的路由数据是这样的树形结构,路由数据的定义是这样的,里面的 children 可以嵌套任意层:

ts 复制代码
interface MenuEntity {
    id?: number | null,
    parent_id: number,
    name: string,
    icon?: string,
    type: EnumMenuType, // 1->目录 2->菜单 3->按钮 4->外链
    path: string,
    component?: string,
    visible: EnumMenuVisible, // 1->可见 2->隐藏 默认为1
    redirect: string,
    sort: number, // 默认为 20
    perm: string, // permission
    created_at?: string,
    updated_at?: string,
    children?: MenuEntity[]
}

实际的数据是这样的:

json 复制代码
{
    "name": "系统配置",
    "id": 10,
    "parent_id": -1,
    "type": 1,
    "path": "/system",
    "component": "",
    "visible": 1,
    "redirect": "",
    "perm": "",
    "sort": 100,
    "icon": "Setting",
    "created_at": "2024-02-26T14:55:12+08:00",
    "updated_at": "2024-02-26T16:12:34+08:00",
    "children": [
        {
            "name": "基础配置",
            "id": 12,
            "parent_id": -1,
            "type": 1,
            "path": "/system/normal-setup",
            "component": "",
            "visible": 1,
            "redirect": "",
            "perm": "",
            "sort": 10,
            "icon": "CreditCard",
            "created_at": "2024-02-26T15:20:15+08:00",
            "updated_at": "2024-02-26T16:11:56+08:00",
            "children": [
                {
                    "name": "自动登录",
                    "id": 13,
                    "parent_id": 12,
                    "type": 2,
                    "path": "/system/normal-setup/auto-login",
                    "component": "System/NormalSetup/AutoLoginSetup.vue",
                    "visible": 1,
                    "redirect": "",
                    "perm": "",
                    "sort": 30,
                    "icon": "User",
                    "created_at": "2024-02-26T15:24:18+08:00",
                    "updated_at": "2024-05-17T14:11:52+08:00",
                    "children": []
                },
                {
                    "name": "系统更新",
                    "id": 28,
                    "parent_id": 12,
                    "type": 2,
                    "path": "/system/normal-setup/system-update",
                    "component": "System/SystemUpdate.vue",
                    "visible": 1,
                    "redirect": "",
                    "perm": "",
                    "sort": 50,
                    "icon": "UploadFilled",
                    "created_at": "2024-02-26T16:19:49+08:00",
                    "updated_at": "2024-05-17T14:11:39+08:00",
                    "children": []
                },
                {
                    "name": "申请厂家技术支持",
                    "id": 29,
                    "parent_id": 12,
                    "type": 2,
                    "path": "/system/normal-setup/factory-help",
                    "component": "User/Space.vue",
                    "visible": 1,
                    "redirect": "",
                    "perm": "",
                    "sort": 40,
                    "icon": "SuitcaseLine",
                    "created_at": "2024-02-26T16:20:11+08:00",
                    "updated_at": "2024-03-27T09:04:20+08:00",
                    "children": []
                }
            ]
        }
    ]
}
        

为了好后续匹配 path 到路由名,需要将这个数据平化成一个数组,并构建一个 Map<path, RouteItem> 这样的一个 Map 数据,目的是当执行下面操作时,取到对应的路由数据

js 复制代码
flatMenuPathNameMap.get('/system')

// 最终取到这样的数据
{
    "name": "系统配置",
    "id": 10,
    "parent_id": -1,
    "type": 1,
    "path": "/system",
    "component": "",
    "visible": 1,
    "redirect": "",
    "perm": "",
    "sort": 100,
    "icon": "Setting",
    "created_at": "2024-02-26T14:55:12+08:00",
    "updated_at": "2024-02-26T16:12:34+08:00",
}

平化树形数据、生成对应的 Map 数据结构:

ts 复制代码
/**
 * 菜单相关
 * 这里是单独放到了 pinia 中
 */
export const useMenuStore = defineStore('menuStore', {
    state: () => ({
        menus: [] as Array<RouteRecordRaw>,
        flatMenuArray: [] as Array<MenuEntity>,
        flatMenuPathNameMap: new Map<string, string>()
    }),
    actions: {
        generateMenuArrayAndMap(){
            let menuString = localStorage.getItem('dataMenu')
            let menusCache = menuString ? JSON.parse(menuString) as Array<MenuEntity> : [] as Array<MenuEntity>
            let flatMenuArray = recursionMenuData(menusCache)
            this.flatMenuArray = flatMenuArray
            this.flatMenuPathNameMap = new Map(flatMenuArray.map(item => [item.path, item.name]))

            // 递归方法,平化菜单数据
            function recursionMenuData(menuArray: Array<MenuEntity>){
                let tempArray: Array<MenuEntity> = []
                menuArray.forEach(item => {
                    if (item.children && item.children.length > 0){
                        tempArray = tempArray.concat(recursionMenuData(item.children))
                        // 添加本身,并去除 children 属性
                        delete item.children
                        tempArray.push(item)
                    } else {
                        tempArray.push(item)
                    }
                })
                return tempArray
            }
        },
     }
})

使用的时候

ts 复制代码
import {useMenuStore, useProjectStore} from "./pinia";
const storeMenu = useMenuStore()
// 当执行下面的操作时就会补全  storeMenu.flatMenuArray 和  storeMenu.flatMenuPathNameMap
storeMenu.generateMenuArrayAndMap()

路由树的基础数据是这样的:

平化后的路由数组是这样的:

最终生成的 Map 数据是这样的:

2. 拆分当前路由 path,并匹配

比如当前路由是 /system/normal-setup/auto-login,把它通过 / 拆分之后就是这样的结果

js 复制代码
import {useRoute} from "vue-router";
const route = useRoute()
let routeSectionArray = route.path.split('/').filter(item => item !== '')
// 这样拆分之后,前面会多出一个空白的 "" ,所以这里剔除了它

接下来要做的就是通过上面的 routerSectionArray 生成下面的几个路由组合,再去之前生成的 Map 中匹配对应的路由名即可

  • /system
  • /system/normal-setup
  • /system/normal-setup/auto-login

匹配之后就是这样的结果

  • /system 系统配置
  • /system/normal-setup 基础配置
  • /system/normal-setup/auto-login 自动登录

代码是这样的:

ts 复制代码
import {useRoute} from "vue-router";
import {onMounted, ref} from "vue";
import {useMenuStore} from "@/pinia";

const storeMenu = useMenuStore()
const route = useRoute()

const breadCrumbArray = ref<Array<{name: string, path: string}>>([])

onMounted(()=>{
    let routeSectionArray = route.path.split('/').filter(item => item !== '')
    console.log(routeSectionArray)
    routeSectionArray.forEach((_, index) => {
        let path = `/${routeSectionArray.slice(0,index + 1).join('/')}`
        let pathName = storeMenu.flatMenuPathNameMap.get(path)
        console.log('---',pathName, path)
        if (pathName){
            breadCrumbArray.value.push({name: pathName, path: path})
        }
    })
})

四、搭配其它组件构建页面

弄好上面的 BreadCrumb 组件之后,就可以不用再管它内部的内容了,它会自动根据当前路由值生成对应的内容。

这样我们就可以放心的把它放到页面结构中了。

比如我的页面主要结构是这样的:

Toolerbar.vue

html 复制代码
<template>
    <div class="tool-bar">
        <div class="left">
            <Breadcrumb/>
            <slot name="left"/>
        </div>
        <div class="center">
            <slot name="center"/>
        </div>
        <div class="right">
            <slot name="right"/>
        </div>
    </div>
</template>

<script setup lang="ts">

import Breadcrumb from "@/layout/Breadcrumb.vue";
</script>


<style scoped lang="scss">
.tool-bar{
    padding: 0 20px;
    align-items: center;
    min-height: 50px;
    display: flex;
    flex-flow: row wrap;
    justify-content: space-between;
    .left{
        display: flex;
        flex-flow: row nowrap;
        justify-content: flex-start;
        align-items: center;
        flex-shrink: 0;
    }
    .center{
        display: flex;
        flex-flow: row nowrap;
        justify-content: flex-start;
        align-items: center;
        flex-grow: 1;
        flex-shrink: 0;
    }
    .right{
        display: flex;
        flex-flow: row nowrap;
        justify-content: flex-start;
        align-items: center;
        flex-shrink: 0;
    }
}
</style>

Breadcrumb.vue

html 复制代码
<template>
    <el-breadcrumb separator="/">
        <el-breadcrumb-item :to="{ path: '/' }">主页</el-breadcrumb-item>
        <el-breadcrumb-item
            v-for="item in breadCrumbArray"
            :key="item">{{ item.name }}</el-breadcrumb-item>
    </el-breadcrumb>
</template>

<script setup lang="ts">
import {useRoute} from "vue-router";
import {onMounted, ref} from "vue";
import {useMenuStore} from "@/pinia";

const storeMenu = useMenuStore()
const route = useRoute()

defineProps( {
    height: { // 高度
        type: Number,
        default: 100
    }
})

const breadCrumbArray = ref<Array<{name: string, path: string}>>([])

onMounted(()=>{
    let routeSectionArray = route.path.split('/').filter(item => item !== '')
    routeSectionArray.forEach((_, index) => {
        let path = `/${routeSectionArray.slice(0,index + 1).join('/')}`
        let pathName = storeMenu.flatMenuPathNameMap.get(path)
        console.log('---',pathName, path)
        if (pathName){
            breadCrumbArray.value.push({name: pathName, path: path})
        }
    })
})



</script>

<style lang="scss" scoped>
@import "../assets/scss/plugin";


</style>

实际页面中使用时这样:

html 复制代码
<template>
    <Container>
    
        <Toolbar>
            <template #left>
            </template>
            <template #center>
            </template>
            <template #right>
            </template>
        </Toolbar>
        
        <Content>
        </Content>
    </Container>
<template>

五、结果

相关推荐
拉不动的猪32 分钟前
前端常见数组分析
前端·javascript·面试
小吕学编程1 小时前
ES练习册
java·前端·elasticsearch
Asthenia04121 小时前
Netty编解码器详解与实战
前端
袁煦丞1 小时前
每天省2小时!这个网盘神器让我告别云存储混乱(附内网穿透神操作)
前端·程序员·远程工作
Mr.app2 小时前
vue mixin混入与hook
vue.js
一个专注写代码的程序媛2 小时前
vue组件间通信
前端·javascript·vue.js
一笑code2 小时前
美团社招一面
前端·javascript·vue.js
懒懒是个程序员3 小时前
layui时间范围
前端·javascript·layui
NoneCoder3 小时前
HTML响应式网页设计与跨平台适配
前端·html
凯哥19703 小时前
在 Uni-app 做的后台中使用 Howler.js 实现强大的音频播放功能
前端