<template>
|
<div :class="[prefixCls, `${prefixCls}--${theme}`]">
|
<a-breadcrumb :routes="routes">
|
<template #itemRender="{ route, routes: routesMatched, paths }">
|
<Icon :icon="getIcon(route)" v-if="getShowBreadCrumbIcon && 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>
|
</a-breadcrumb>
|
</div>
|
</template>
|
<script lang="ts">
|
import type { RouteLocationMatched } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
import type { Menu } from '/@/router/types';
|
|
import { defineComponent, ref, watchEffect } from 'vue';
|
|
import { Breadcrumb } from 'ant-design-vue';
|
import Icon from '@/components/Icon/Icon.vue';
|
|
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';
|
|
export default defineComponent({
|
name: 'LayoutBreadcrumb',
|
components: { Icon, [Breadcrumb.name]: Breadcrumb },
|
props: {
|
theme: propTypes.oneOf(['dark', 'light']),
|
},
|
setup() {
|
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;
|
}
|
|
return { routes, t, prefixCls, getIcon, getShowBreadCrumbIcon, handleClick, hasRedirect };
|
},
|
});
|
</script>
|
<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: @breadcrumb-item-normal-color;
|
|
a {
|
color: rgb(0 0 0 / 65%);
|
|
&:hover {
|
color: @primary-color;
|
}
|
}
|
}
|
|
.ant-breadcrumb-separator {
|
color: @breadcrumb-item-normal-color;
|
}
|
}
|
|
&--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>
|