7 changed files with 150 additions and 1 deletions
@ -0,0 +1,6 @@ |
|||
--- |
|||
'@vben-core/typings': minor |
|||
'@vben/layouts': minor |
|||
--- |
|||
|
|||
feat: support using the activePath route hierarchy in breadcrumbs |
|||
@ -0,0 +1,69 @@ |
|||
import type { RouteLocationMatched } from 'vue-router'; |
|||
|
|||
import { describe, expect, it, vi } from 'vitest'; |
|||
|
|||
import { resolveBreadcrumbMatches } from '../breadcrumb-routes'; |
|||
|
|||
function createMatch(name: string, path: string): RouteLocationMatched { |
|||
return { name, path } as RouteLocationMatched; |
|||
} |
|||
|
|||
describe('resolveBreadcrumbMatches', () => { |
|||
it('preserves the current matches when activePath breadcrumbs are disabled', () => { |
|||
const matches = [createMatch('Detail', '/detail')]; |
|||
const resolveRoute = vi.fn(); |
|||
|
|||
expect( |
|||
resolveBreadcrumbMatches( |
|||
{ |
|||
matched: matches, |
|||
meta: { |
|||
activePath: '/list', |
|||
breadcrumbUseActivePath: false, |
|||
title: 'Detail', |
|||
}, |
|||
}, |
|||
resolveRoute, |
|||
), |
|||
).toBe(matches); |
|||
expect(resolveRoute).not.toHaveBeenCalled(); |
|||
}); |
|||
|
|||
it('preserves the current matches when activePath cannot be resolved', () => { |
|||
const matches = [createMatch('Detail', '/detail')]; |
|||
|
|||
expect( |
|||
resolveBreadcrumbMatches( |
|||
{ |
|||
matched: matches, |
|||
meta: { |
|||
activePath: '/missing', |
|||
breadcrumbUseActivePath: true, |
|||
title: 'Detail', |
|||
}, |
|||
}, |
|||
() => ({ matched: [] }), |
|||
), |
|||
).toBe(matches); |
|||
}); |
|||
|
|||
it('prepends activePath matches and removes duplicate route records', () => { |
|||
const root = createMatch('Root', '/'); |
|||
const list = createMatch('List', '/list'); |
|||
const detail = createMatch('Detail', '/detail'); |
|||
|
|||
expect( |
|||
resolveBreadcrumbMatches( |
|||
{ |
|||
matched: [root, detail], |
|||
meta: { |
|||
activePath: '/list', |
|||
breadcrumbUseActivePath: true, |
|||
title: 'Detail', |
|||
}, |
|||
}, |
|||
() => ({ matched: [root, list] }), |
|||
), |
|||
).toEqual([root, list, detail]); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,41 @@ |
|||
import type { RouteLocationNormalizedLoaded } from 'vue-router'; |
|||
|
|||
type MatchedRoutes = RouteLocationNormalizedLoaded['matched']; |
|||
|
|||
interface BreadcrumbRoute { |
|||
matched: MatchedRoutes; |
|||
meta: RouteLocationNormalizedLoaded['meta']; |
|||
} |
|||
|
|||
type BreadcrumbRouteResolver = (path: string) => { matched: MatchedRoutes }; |
|||
|
|||
/** |
|||
* 解析用于渲染当前面包屑的路由匹配记录。 |
|||
*/ |
|||
export function resolveBreadcrumbMatches( |
|||
route: BreadcrumbRoute, |
|||
resolveRoute: BreadcrumbRouteResolver, |
|||
): MatchedRoutes { |
|||
const { activePath, breadcrumbUseActivePath } = route.meta; |
|||
|
|||
if (!breadcrumbUseActivePath || !activePath) { |
|||
return route.matched; |
|||
} |
|||
|
|||
const activeMatches = resolveRoute(activePath).matched; |
|||
if (activeMatches.length === 0) { |
|||
return route.matched; |
|||
} |
|||
|
|||
const seen = new Set(activeMatches.map((match) => match.name ?? match.path)); |
|||
const currentMatches = route.matched.filter((match) => { |
|||
const key = match.name ?? match.path; |
|||
if (seen.has(key)) { |
|||
return false; |
|||
} |
|||
seen.add(key); |
|||
return true; |
|||
}); |
|||
|
|||
return [...activeMatches, ...currentMatches]; |
|||
} |
|||
Loading…
Reference in new issue