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
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>
|
|
|