You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

193 lines
4.7 KiB

<script lang="ts" setup>
import type { RouteLocationMatched } from 'vue-router'
import { useRouter } from 'vue-router'
import { ref, watchEffect } from 'vue'
import { Breadcrumb } from 'ant-design-vue'
import type { Menu } from '@/router/types'
import { useDesign } from '@/hooks/web/useDesign'
import { useRootSetting } from '@/hooks/setting/useRootSetting'
import { useGo } from '@/hooks/web/usePage'
import { useI18n } from '@/hooks/web/useI18n'
import { propTypes } from '@/utils/propTypes'
import { isString } from '@/utils/is'
import { filter } from '@/utils/helper/treeHelper'
import { getMenus } from '@/router/menus'
import { REDIRECT_NAME } from '@/router/constant'
import { getAllParentPath } from '@/router/helper/menuHelper'
import { Icon } from '@/components/Icon'
defineOptions({ name: 'LayoutBreadcrumb' })
defineProps({
theme: propTypes.oneOf(['dark', 'light']),
})
const routes = ref<RouteLocationMatched[]>([])
const { currentRoute } = useRouter()
const { prefixCls } = useDesign('layout-breadcrumb')
const { getShowBreadCrumbIcon } = useRootSetting()
const go = useGo()
const { t } = useI18n()
watchEffect(async () => {
if (currentRoute.value.name === REDIRECT_NAME)
return
const menus = await getMenus()
const routeMatched = currentRoute.value.matched
const cur = routeMatched?.[routeMatched.length - 1]
let path = currentRoute.value.path
if (cur && cur?.meta?.currentActiveMenu)
path = cur.meta.currentActiveMenu as string
const parent = getAllParentPath(menus, path)
const filterMenus = menus.filter(item => item.path === parent[0])
const matched = getMatched(filterMenus, parent) as any
if (!matched || matched.length === 0)
return
const breadcrumbList = filterItem(matched)
if (currentRoute.value.meta?.currentActiveMenu) {
breadcrumbList.push({
...currentRoute.value,
name: currentRoute.value.meta?.title || currentRoute.value.name,
} as unknown as RouteLocationMatched)
}
routes.value = breadcrumbList
})
function getMatched(menus: Menu[], parent: string[]) {
const metched: Menu[] = []
menus.forEach((item) => {
if (parent.includes(item.path)) {
metched.push({
...item,
name: item.meta?.title || item.name,
})
}
if (item.children?.length)
metched.push(...getMatched(item.children, parent))
})
return metched
}
function filterItem(list: RouteLocationMatched[]) {
return filter(list, (item) => {
const { meta, name } = item
if (!meta)
return !!name
const { title, hideBreadcrumb, hideMenu } = meta
if (!title || hideBreadcrumb || hideMenu)
return false
return true
}).filter(item => !item.meta?.hideBreadcrumb)
}
function handleClick(route: RouteLocationMatched, paths: string[], e: Event) {
e?.preventDefault()
const { children, redirect, meta } = route
if (children?.length && !redirect) {
e?.stopPropagation()
return
}
if (meta?.carryParam)
return
if (redirect && isString(redirect)) {
go(redirect)
}
else {
let goPath = ''
if (paths.length === 1) {
goPath = paths[0]
}
else {
const ps = paths.slice(1)
const lastPath = ps.pop() || ''
goPath = `${lastPath}`
}
goPath = /^\//.test(goPath) ? goPath : `/${goPath}`
go(goPath)
}
}
function hasRedirect(routes: RouteLocationMatched[], route: RouteLocationMatched) {
return routes.indexOf(route) !== routes.length - 1
}
function getIcon(route) {
return route.icon || route.meta?.icon
}
</script>
<template>
<div :class="[prefixCls, `${prefixCls}--${theme}`]">
<Breadcrumb :routes="routes">
<template #itemRender="{ route, routes: routesMatched, paths }">
<Icon v-if="getShowBreadCrumbIcon && getIcon(route)" :icon="getIcon(route)" />
<span v-if="!hasRedirect(routesMatched, route)">
{{ t(route.name || route.meta.title) }}
</span>
<router-link v-else to="" @click="handleClick(route, paths, $event)">
{{ t(route.name || route.meta.title) }}
</router-link>
</template>
</Breadcrumb>
</div>
</template>
<style lang="less">
@prefix-cls: ~'@{namespace}-layout-breadcrumb';
.@{prefix-cls} {
display: flex;
align-items: center;
padding: 0 8px;
.ant-breadcrumb-link {
.anticon {
margin-right: 4px;
margin-bottom: 2px;
}
}
&--light {
.ant-breadcrumb-link {
color: #999;
a {
color: rgb(0 0 0 / 65%);
}
}
.ant-breadcrumb-separator {
color: #999;
}
}
&--dark {
.ant-breadcrumb-link {
color: rgb(255 255 255 / 60%);
a {
color: rgb(255 255 255 / 80%);
&:hover {
color: @white;
}
}
}
.ant-breadcrumb-separator,
.anticon {
color: rgb(255 255 255 / 80%);
}
}
}
</style>