|
|
|
@ -1,26 +1,45 @@ |
|
|
|
<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.meta.title || route.name) }} |
|
|
|
</span> |
|
|
|
<router-link v-else to="" @click="handleClick(route, paths, $event as Event)"> |
|
|
|
{{ t(route.meta.title || route.name) }} |
|
|
|
</router-link> |
|
|
|
<Breadcrumb> |
|
|
|
<template v-for="routeItem in routes" :key="routeItem.name"> |
|
|
|
<BreadcrumbItem> |
|
|
|
<Icon :icon="getIcon(routeItem)" v-if="getShowBreadCrumbIcon && getIcon(routeItem)" /> |
|
|
|
<span v-if="!hasRedirect(routes, routeItem)"> |
|
|
|
{{ t((routeItem.meta.title || routeItem.name) as string) }} |
|
|
|
</span> |
|
|
|
<router-link v-else to="" @click="handleClick(routeItem)"> |
|
|
|
{{ t((routeItem.meta.title || routeItem.name) as string) }} |
|
|
|
</router-link> |
|
|
|
<template v-if="routeItem.children" #overlay> |
|
|
|
<Menu> |
|
|
|
<template v-for="childItem in routeItem.children" :key="childItem.name"> |
|
|
|
<MenuItem> |
|
|
|
<Icon |
|
|
|
:icon="getIcon(childItem)" |
|
|
|
v-if="getShowBreadCrumbIcon && getIcon(childItem)" |
|
|
|
/> |
|
|
|
<span v-if="!hasRedirect(routes, childItem)"> |
|
|
|
{{ t((childItem.meta?.title || childItem.name) as string) }} |
|
|
|
</span> |
|
|
|
<router-link v-else to="" @click="handleClick(childItem)"> |
|
|
|
{{ t((childItem.meta?.title || childItem.name) as string) }} |
|
|
|
</router-link> |
|
|
|
</MenuItem> |
|
|
|
</template> |
|
|
|
</Menu> |
|
|
|
</template> |
|
|
|
</BreadcrumbItem> |
|
|
|
</template> |
|
|
|
</a-breadcrumb> |
|
|
|
</Breadcrumb> |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
<script lang="ts"> |
|
|
|
<script lang="ts" setup> |
|
|
|
import type { RouteLocationMatched } from 'vue-router'; |
|
|
|
import { useRouter } from 'vue-router'; |
|
|
|
import type { Menu } from '@/router/types'; |
|
|
|
|
|
|
|
import { defineComponent, ref, watchEffect } from 'vue'; |
|
|
|
import { ref, watchEffect } from 'vue'; |
|
|
|
|
|
|
|
import { Breadcrumb } from 'ant-design-vue'; |
|
|
|
import { Breadcrumb, BreadcrumbItem, Menu, MenuItem } from 'ant-design-vue'; |
|
|
|
import Icon from '@/components/Icon/Icon.vue'; |
|
|
|
|
|
|
|
import { useDesign } from '@/hooks/web/useDesign'; |
|
|
|
@ -36,121 +55,114 @@ |
|
|
|
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; |
|
|
|
} |
|
|
|
defineOptions({ name: 'LayoutBreadcrumb' }); |
|
|
|
|
|
|
|
const parent = getAllParentPath(menus, path); |
|
|
|
const filterMenus = menus.filter((item) => item.path === parent[0]); |
|
|
|
const matched = getMatched(filterMenus, parent) as any; |
|
|
|
defineProps({ |
|
|
|
theme: propTypes.oneOf(['dark', 'light']), |
|
|
|
}); |
|
|
|
|
|
|
|
if (!matched || matched.length === 0){ |
|
|
|
routes.value = []; |
|
|
|
return; |
|
|
|
} |
|
|
|
const routes = ref<RouteLocationMatched[]>([]); |
|
|
|
const { currentRoute } = useRouter(); |
|
|
|
const { prefixCls } = useDesign('layout-breadcrumb'); |
|
|
|
const { getShowBreadCrumbIcon } = useRootSetting(); |
|
|
|
const go = useGo(); |
|
|
|
|
|
|
|
const breadcrumbList = filterItem(matched); |
|
|
|
const { t } = useI18n(); |
|
|
|
watchEffect(async () => { |
|
|
|
if (currentRoute.value.name === REDIRECT_NAME) return; |
|
|
|
const menus = await getMenus(); |
|
|
|
|
|
|
|
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; |
|
|
|
} |
|
|
|
const routeMatched = currentRoute.value.matched; |
|
|
|
const cur = routeMatched?.[routeMatched.length - 1]; |
|
|
|
let path = currentRoute.value.path; |
|
|
|
|
|
|
|
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); |
|
|
|
} |
|
|
|
if (cur && cur?.meta?.currentActiveMenu) { |
|
|
|
path = cur.meta.currentActiveMenu as string; |
|
|
|
} |
|
|
|
|
|
|
|
function handleClick(route: RouteLocationMatched, paths: string[], e: Event) { |
|
|
|
e?.preventDefault(); |
|
|
|
const { children, redirect, meta } = route; |
|
|
|
const parent = getAllParentPath(menus, path); |
|
|
|
const filterMenus = menus.filter((item) => item.path === parent[0]); |
|
|
|
const matched = getMatched(filterMenus, parent) as any; |
|
|
|
|
|
|
|
if (children?.length && !redirect) { |
|
|
|
e?.stopPropagation(); |
|
|
|
return; |
|
|
|
} |
|
|
|
if (meta?.carryParam) { |
|
|
|
return; |
|
|
|
} |
|
|
|
if (!matched || matched.length === 0) { |
|
|
|
routes.value = []; |
|
|
|
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); |
|
|
|
} |
|
|
|
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, parent: string[]) { |
|
|
|
const matched: any[] = []; |
|
|
|
menus.forEach((item) => { |
|
|
|
if (parent.includes(item.path)) { |
|
|
|
matched.push({ |
|
|
|
...item, |
|
|
|
name: item.meta?.title || item.name, |
|
|
|
}); |
|
|
|
} |
|
|
|
if (item.children?.length) { |
|
|
|
matched.push(...getMatched(item.children, parent)); |
|
|
|
} |
|
|
|
}); |
|
|
|
return matched; |
|
|
|
} |
|
|
|
|
|
|
|
function hasRedirect(routes: RouteLocationMatched[], route: RouteLocationMatched) { |
|
|
|
return routes.indexOf(route) !== routes.length - 1; |
|
|
|
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) { |
|
|
|
console.log(route); |
|
|
|
const { children, redirect, meta } = route; |
|
|
|
|
|
|
|
function getIcon(route) { |
|
|
|
return route.icon || route.meta?.icon; |
|
|
|
if (children?.length && !redirect) { |
|
|
|
return; |
|
|
|
} |
|
|
|
if (meta?.carryParam) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (redirect && isString(redirect)) { |
|
|
|
go(redirect); |
|
|
|
} else { |
|
|
|
let goPath = ''; |
|
|
|
if (route.path) { |
|
|
|
goPath = route.path; |
|
|
|
} else { |
|
|
|
const lastPath = ''; |
|
|
|
goPath = `${lastPath}`; |
|
|
|
} |
|
|
|
goPath = /^\//.test(goPath) ? goPath : `/${goPath}`; |
|
|
|
go(goPath); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return { routes, t, prefixCls, getIcon, getShowBreadCrumbIcon, handleClick, hasRedirect }; |
|
|
|
}, |
|
|
|
}); |
|
|
|
function hasRedirect(routes, route) { |
|
|
|
return routes.indexOf(route) !== routes.length - 1; |
|
|
|
} |
|
|
|
|
|
|
|
function getIcon(route) { |
|
|
|
return route.icon || route.meta?.icon; |
|
|
|
} |
|
|
|
</script> |
|
|
|
<style lang="less"> |
|
|
|
@prefix-cls: ~'@{namespace}-layout-breadcrumb'; |
|
|
|
|