vue 如何做一個動態的 BreadCrumb 組件 el-breadcrumb ElementUI
一、ElementUI 中的 BreadCrumb 定義
elementUI 中的 Breadcrumb 組件是這樣定義的
<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 可以嵌套任意層:
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->隱藏 默認為1redirect: string,sort: number, // 默認為 20perm: string, // permissioncreated_at?: string,updated_at?: string,children?: MenuEntity[]
}
實際的數據是這樣的:
{"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 數據,目的是當執行下面操作時,取到對應的路由數據
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 數據結構:
/*** 菜單相關* 這里是單獨放到了 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 = flatMenuArraythis.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.childrentempArray.push(item)} else {tempArray.push(item)}})return tempArray}},}
})
使用的時候
import {useMenuStore, useProjectStore} from "./pinia";
const storeMenu = useMenuStore()
// 當執行下面的操作時就會補全 storeMenu.flatMenuArray 和 storeMenu.flatMenuPathNameMap
storeMenu.generateMenuArrayAndMap()
路由樹的基礎數據是這樣的:
平化后的路由數組是這樣的:
最終生成的 Map 數據是這樣的:
2. 拆分當前路由 path,并匹配
比如當前路由是 /system/normal-setup/auto-login
,把它通過 /
拆分之后就是這樣的結果
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
自動登錄
代碼是這樣的:
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
<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
<template><el-breadcrumb separator="/"><el-breadcrumb-item :to="{ path: '/' }">主頁</el-breadcrumb-item><el-breadcrumb-itemv-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>
實際頁面中使用時這樣:
<template><Container><Toolbar><template #left></template><template #center></template><template #right></template></Toolbar><Content></Content></Container>
<template>