效果预览
技术方案
vue3 ( vite | TS | vueUse | AutoImport ) + Element Plus + UnoCSS
技术要点
根据当前路由查询所有父级路由
/**
* 从树状列表中获取指定节点的所有父节点
*
* @param treeList 树状列表,包含多个节点
* @param value 目标节点的路径值
* @param parents 存储父节点的数组
* @returns 如果找到目标节点,返回包含所有父节点的数组;否则返回null
*/
function getAllParents(treeList: menu[], value: string, parents: menu[]) {
// 遍历树中的每个节点
for (const node of treeList) {
// 如果找到目标节点
if (node.path === value) {
parents.push(node)
// 返回所有父节点
return parents
}
if (node.children && node.children.length > 0) {
parents.push(node)
const result: any = getAllParents(node.children, value, parents)
if (result) {
return result
}
// 如果递归未找到,则移除当前节点
parents.pop()
}
}
// 如果遍历完整棵树未找到,返回null
return null
}
transition-group 实现过渡动画
注意:动画元素需添加 key
<transition-group name="breadcrumb">
<el-breadcrumb-item
:data-index="index"
v-for="(item, index) in parentsList"
:key="item.path"
:to="{ path: item.path }"
>{{ item.name }}</el-breadcrumb-item
>
</transition-group>
<style scoped lang="scss">
.breadcrumb-move,
.breadcrumb-enter-active,
.breadcrumb-leave-active {
transition: all 0.5s;
}
.breadcrumb-enter-from,
.breadcrumb-leave-to {
opacity: 0;
transform: translateX(20px);
}
.breadcrumb-leave-active {
position: absolute;
}
</style>
代码实现
src/components/Breadcrumb.vue
<script setup lang="ts">
// 导入自定义的数据类型
import type { MenuProps, menu } from './Menu/types'
/** 父组件传参
* @param menu_list 菜单列表
*/
const { menu_list } = defineProps<MenuProps>()
const route = useRoute()
let parentsList = ref()
parentsList.value = getData()
function getData() {
let result: menu[] = []
getAllParents(menu_list, route.path, result)
return result
}
watch(
() => route.path,
() => {
parentsList.value = getData()
},
{ immediate: true }
)
/**
* 从树状列表中获取指定节点的所有父节点
*
* @param treeList 树状列表,包含多个节点
* @param value 目标节点的路径值
* @param parents 存储父节点的数组
* @returns 如果找到目标节点,返回包含所有父节点的数组;否则返回null
*/
function getAllParents(treeList: menu[], value: string, parents: menu[]) {
// 遍历树中的每个节点
for (const node of treeList) {
// 如果找到目标节点
if (node.path === value) {
parents.push(node)
// 返回所有父节点
return parents
}
if (node.children && node.children.length > 0) {
parents.push(node)
const result: any = getAllParents(node.children, value, parents)
if (result) {
return result
}
// 如果递归未找到,则移除当前节点
parents.pop()
}
}
// 如果遍历完整棵树未找到,返回null
return null
}
</script>
<template>
<el-breadcrumb separator="/">
<transition-group name="breadcrumb">
<el-breadcrumb-item
:data-index="index"
v-for="(item, index) in parentsList"
:key="item.path"
:to="{ path: item.path }"
>{{ item.name }}</el-breadcrumb-item
>
</transition-group>
</el-breadcrumb>
</template>
<style scoped lang="scss">
.breadcrumb-move,
.breadcrumb-enter-active,
.breadcrumb-leave-active {
transition: all 0.5s;
}
.breadcrumb-enter-from,
.breadcrumb-leave-to {
opacity: 0;
transform: translateX(20px);
}
.breadcrumb-leave-active {
position: absolute;
}
</style>
src/components/Menu/types.ts
export interface menu {
id: number
name: string
path: string
nickName?: string
icon?: string
index?: string
children?: menu[]
}
export interface MenuProps {
menu_list: menu[]
default_openeds?: string[]
collapse?: boolean
}
页面使用
<Breadcrumb class="ml-4" :menu_list="menu_list" />
const menu_list = [
{
name: '前端基础',
icon: 'logos:codecov-icon',
children: [
{
name: 'HTML',
path: '/notes/HTML',
icon: 'devicon:html5'
},
{
name: 'CSS',
path: '/notes/CSS',
icon: 'devicon:css3'
},
{
name: 'SCSS',
path: '/notes/SCSS',
icon: 'vscode-icons:file-type-scss'
},
{
name: 'Javascript',
path: '/notes/Javascript',
icon: 'skill-icons:javascript',
nickName: 'JS'
},
{
name: 'Typescript',
path: '/notes/Typescript',
icon: 'logos:typescript-icon',
nickName: 'TS',
url: 'https://blog.csdn.net/weixin_41192489/article/details/139948123'
}
]
},
{
name: '前端框架',
icon: 'logos:codeigniter-icon',
children: [
{
name: 'Vue',
path: '/notes/Vue',
icon: 'logos:vue',
children: [
{
name: 'Vue2',
path: '/notes/Vue2'
},
{
name: 'Vue3',
path: '/notes/Vue3'
}
]
},
{
name: 'React',
path: '/notes/React',
icon: 'skill-icons:react-dark',
children: [
{
name: 'react18',
path: '/notes/React'
}
]
}
]
},
{
name: 'moxb',
path: '/notes/moxb'
}
]