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