Skip to content

Commit cfb4f23

Browse files
committed
refactor!: 重构注册路由数据结构
将原先多级路由拍平成二级路由的模式,调整为将中间层级的component移除,保持原有数据结构
1 parent eff07c2 commit cfb4f23

File tree

6 files changed

+82
-129
lines changed

6 files changed

+82
-129
lines changed

‎src/layouts/components/Topbar/Toolbar/Breadcrumb/index.vue‎

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,14 @@ const breadcrumbList = computed(() => {
1616
title: settingsStore.settings.home.title,
1717
})
1818
}
19-
if (route.meta.breadcrumbNeste) {
20-
route.meta.breadcrumbNeste.forEach((item) => {
21-
if (item.hide === false) {
22-
breadcrumbList.push({
23-
path: item.path,
24-
title: item.title,
25-
})
26-
}
27-
})
28-
}
19+
route.matched.forEach((item) => {
20+
if (item.meta?.breadcrumb !== false) {
21+
breadcrumbList.push({
22+
path: item.path,
23+
title: item.meta?.title,
24+
})
25+
}
26+
})
2927
return breadcrumbList
3028
})
3129

‎src/layouts/components/Topbar/Toolbar/NavSearch/search.vue‎

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import type { Menu } from '@/types/global'
33
import Breadcrumb from '@/layouts/components/Breadcrumb/index.vue'
44
import BreadcrumbItem from '@/layouts/components/Breadcrumb/item.vue'
55
import useMenuStore from '@/store/modules/menu'
6+
import useRouteStore from '@/store/modules/route'
67
import useSettingsStore from '@/store/modules/settings'
78
import { resolveRoutePath } from '@/utils'
8-
import { cloneDeep } from 'es-toolkit'
99
import hotkeys from 'hotkeys-js'
1010
1111
defineOptions({
@@ -18,16 +18,14 @@ const isShow = defineModel<boolean>({
1818
1919
const router = useRouter()
2020
const settingsStore = useSettingsStore()
21+
const routeStore = useRouteStore()
2122
const menuStore = useMenuStore()
2223
2324
interface listTypes {
2425
path: string
2526
icon?: string
2627
title?: string | (() => string)
2728
link?: string
28-
breadcrumb: {
29-
title?: string | (() => string)
30-
}[]
3129
}
3230
3331
const searchInput = ref('')
@@ -57,18 +55,8 @@ const resultList = computed(() => {
5755
if (item.path.includes(searchInput.value)) {
5856
flag = true
5957
}
60-
if (item.breadcrumb.some((b) => {
61-
if (typeof b.title === 'function') {
62-
if (b.title().includes(searchInput.value)) {
63-
return true
64-
}
65-
}
66-
else {
67-
if (b.title?.includes(searchInput.value)) {
68-
return true
69-
}
70-
}
71-
return false
58+
if (routeStore.getRouteMatchedByPath(item.path).some((b) => {
59+
return typeof b.meta?.title === 'function' ? b.meta?.title().includes(searchInput.value) : b.meta?.title?.includes(searchInput.value)
7260
})) {
7361
flag = true
7462
}
@@ -122,7 +110,7 @@ onUnmounted(() => {
122110
function initSourceList() {
123111
sourceList.value = []
124112
menuStore.allMenus.forEach((item) => {
125-
getSourceListByMenus(item.children)
113+
getSourceList(item.children)
126114
})
127115
}
128116
@@ -133,26 +121,18 @@ function hasChildren(item: Menu.recordRaw) {
133121
}
134122
return flag
135123
}
136-
function getSourceListByMenus(arr: Menu.recordRaw[], basePath?: string, icon?: string, breadcrumb?: { title?: string | (() => string) }[]) {
124+
function getSourceList(arr: Menu.recordRaw[], basePath?: string, icon?: string) {
137125
arr.forEach((item) => {
138126
if (item.meta?.menu !== false) {
139-
const breadcrumbTemp = cloneDeep(breadcrumb) || []
140127
if (item.children && hasChildren(item)) {
141-
breadcrumbTemp.push({
142-
title: item.meta?.title,
143-
})
144-
getSourceListByMenus(item.children, resolveRoutePath(basePath, item.path), item.meta?.icon ?? icon, breadcrumbTemp)
128+
getSourceList(item.children, resolveRoutePath(basePath, item.path), item.meta?.icon ?? icon)
145129
}
146130
else {
147-
breadcrumbTemp.push({
148-
title: item.meta?.title,
149-
})
150131
sourceList.value.push({
151132
path: resolveRoutePath(basePath, item.path),
152133
icon: item.meta?.icon ?? icon,
153134
title: item.meta?.title,
154135
link: item.meta?.link,
155-
breadcrumb: breadcrumbTemp,
156136
})
157137
}
158138
}
@@ -263,9 +243,9 @@ function pageJump(path: listTypes['path'], link: listTypes['link']) {
263243
<FaIcon v-if="item.icon" :name="item.icon" class="size-5 basis-16 transition" :class="{ 'scale-120 text-primary': index === actived }" />
264244
<div class="flex flex-1 flex-col gap-1 truncate border-s px-4 py-3">
265245
<div class="truncate text-start text-base font-bold">{{ (typeof item.title === 'function' ? item.title() : item.title) ?? '[ 无标题 ]' }}</div>
266-
<Breadcrumb v-if="item.breadcrumb.length" class="truncate">
267-
<BreadcrumbItem v-for="(bc, bcIndex) in item.breadcrumb" :key="bcIndex" class="text-xs">
268-
{{ (typeof bc.title === 'function' ? bc.title() : bc.title) ?? '[ 无标题 ]' }}
246+
<Breadcrumb v-if="routeStore.getRouteMatchedByPath(item.path).length" class="truncate">
247+
<BreadcrumbItem v-for="(bc, bcIndex) in routeStore.getRouteMatchedByPath(item.path)" :key="bcIndex" class="text-xs">
248+
{{ (typeof bc.meta?.title === 'function' ? bc.meta?.title() : bc.meta?.title) ?? '[ 无标题 ]' }}
269249
</BreadcrumbItem>
270250
</Breadcrumb>
271251
</div>

‎src/router/guards.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,13 @@ function setupRoutes(router: Router) {
7373
// 注册并记录路由数据
7474
// 记录的数据会在登出时会使用到,不使用 router.removeRoute 是考虑配置的路由可能不一定有设置 name ,则通过调用 router.addRoute() 返回的回调进行删除
7575
const removeRoutes: (() => void)[] = []
76-
routeStore.flatRoutes.forEach((route) => {
76+
routeStore.routes.forEach((route) => {
7777
if (!/^(?:https?:|mailto:|tel:)/.test(route.path)) {
7878
removeRoutes.push(router.addRoute(route as RouteRecordRaw))
7979
}
8080
})
8181
if (settingsStore.settings.app.routeBaseOn !== 'filesystem') {
82-
routeStore.flatSystemRoutes.forEach((route) => {
82+
routeStore.systemRoutes.forEach((route) => {
8383
removeRoutes.push(router.addRoute(route as RouteRecordRaw))
8484
})
8585
}
@@ -133,7 +133,7 @@ function setupTitle(router: Router) {
133133
router.afterEach((to) => {
134134
const settingsStore = useSettingsStore()
135135
if (settingsStore.settings.app.routeBaseOn !== 'filesystem') {
136-
settingsStore.setTitle(to.meta.breadcrumbNeste?.at(-1)?.title ?? to.meta.title)
136+
settingsStore.setTitle(to.matched?.at(-1)?.meta?.title ?? to.meta.title)
137137
}
138138
else {
139139
settingsStore.setTitle(to.meta.title)

‎src/store/modules/route.ts‎

Lines changed: 60 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import type { Route } from '#/global'
2-
import type { RouteRecordRaw } from 'vue-router'
2+
import type { RouteRecordRaw, RouterMatcher } from 'vue-router'
33
import apiApp from '@/api/modules/app'
4-
import { systemRoutes } from '@/router/routes'
5-
import { resolveRoutePath } from '@/utils'
4+
import { systemRoutes as systemRoutesRaw } from '@/router/routes'
65
import { cloneDeep } from 'es-toolkit'
6+
import { createRouterMatcher } from 'vue-router'
77
import useSettingsStore from './settings'
88

99
const useRouteStore = defineStore(
@@ -13,79 +13,15 @@ const useRouteStore = defineStore(
1313
const settingsStore = useSettingsStore()
1414

1515
const isGenerate = ref(false)
16+
// 原始路由
1617
const routesRaw = ref<Route.recordMainRaw[]>([])
18+
// 文件系统原始路由
1719
const filesystemRoutesRaw = ref<RouteRecordRaw[]>([])
20+
// 已注册的路由,用于登出时删除路由
1821
const currentRemoveRoutes = ref<(() => void)[]>([])
1922

20-
// 将多层嵌套路由处理成两层,保留顶层和最子层路由,中间层级将被拍平
21-
function flatAsyncRoutes<T extends RouteRecordRaw>(route: T): T {
22-
if (route.children) {
23-
route.children = flatAsyncRoutesRecursive(route.children, [{
24-
path: route.path,
25-
title: route.meta?.title,
26-
icon: route.meta?.icon,
27-
hide: !route.meta?.breadcrumb && route.meta?.breadcrumb === false,
28-
}], route.path)
29-
}
30-
return route
31-
}
32-
function flatAsyncRoutesRecursive(routes: RouteRecordRaw[], breadcrumb: Route.breadcrumb[] = [], baseUrl = ''): RouteRecordRaw[] {
33-
const res: RouteRecordRaw[] = []
34-
routes.forEach((route) => {
35-
if (route.children) {
36-
const childrenBaseUrl = resolveRoutePath(baseUrl, route.path)
37-
const tmpBreadcrumb = cloneDeep(breadcrumb)
38-
tmpBreadcrumb.push({
39-
path: childrenBaseUrl,
40-
title: route.meta?.title,
41-
icon: route.meta?.icon,
42-
hide: !route.meta?.breadcrumb && route.meta?.breadcrumb === false,
43-
})
44-
const tmpRoute = cloneDeep(route)
45-
tmpRoute.path = childrenBaseUrl
46-
if (!tmpRoute.meta) {
47-
tmpRoute.meta = {}
48-
}
49-
tmpRoute.meta.breadcrumbNeste = tmpBreadcrumb
50-
delete tmpRoute.children
51-
res.push(tmpRoute)
52-
const childrenRoutes = flatAsyncRoutesRecursive(route.children, tmpBreadcrumb, childrenBaseUrl)
53-
childrenRoutes.forEach((item) => {
54-
// 如果 path 一样则覆盖,因为子路由的 path 可能设置为空,导致和父路由一样,直接注册会提示路由重复
55-
if (res.some(v => v.path === item.path)) {
56-
res.forEach((v, i) => {
57-
if (v.path === item.path) {
58-
res[i] = item
59-
}
60-
})
61-
}
62-
else {
63-
res.push(item)
64-
}
65-
})
66-
}
67-
else {
68-
const tmpRoute = cloneDeep(route)
69-
tmpRoute.path = resolveRoutePath(baseUrl, tmpRoute.path)
70-
// 处理面包屑导航
71-
const tmpBreadcrumb = cloneDeep(breadcrumb)
72-
tmpBreadcrumb.push({
73-
path: tmpRoute.path,
74-
title: tmpRoute.meta?.title,
75-
icon: tmpRoute.meta?.icon,
76-
hide: !tmpRoute.meta?.breadcrumb && tmpRoute.meta?.breadcrumb === false,
77-
})
78-
if (!tmpRoute.meta) {
79-
tmpRoute.meta = {}
80-
}
81-
tmpRoute.meta.breadcrumbNeste = tmpBreadcrumb
82-
res.push(tmpRoute)
83-
}
84-
})
85-
return res
86-
}
87-
// 扁平化路由(将三级及以上路由数据拍平成二级)
88-
const flatRoutes = computed(() => {
23+
// 实际路由
24+
const routes = computed(() => {
8925
const returnRoutes: RouteRecordRaw[] = []
9026
if (settingsStore.settings.app.routeBaseOn !== 'filesystem') {
9127
if (routesRaw.value) {
@@ -100,24 +36,61 @@ const useRouteStore = defineStore(
10036
})
10137
returnRoutes.push(...tmpRoutes)
10238
})
103-
returnRoutes.forEach(item => flatAsyncRoutes(item))
39+
returnRoutes.forEach((item) => {
40+
if (item.children) {
41+
item.children = deleteMiddleRouteComponent(item.children)
42+
}
43+
return item
44+
})
10445
}
10546
}
10647
else {
10748
returnRoutes.push(...cloneDeep(filesystemRoutesRaw.value) as RouteRecordRaw[])
10849
}
10950
return returnRoutes
11051
})
111-
const flatSystemRoutes = computed(() => {
112-
const routes = [...systemRoutes]
113-
routes.forEach(item => flatAsyncRoutes(item))
52+
// 系统路由
53+
const systemRoutes = computed(() => {
54+
const routes = [...systemRoutesRaw]
55+
routes.forEach((item) => {
56+
if (item.children) {
57+
item.children = deleteMiddleRouteComponent(item.children)
58+
}
59+
})
11460
return routes
11561
})
62+
// 删除路由中间层级对应的组件
63+
function deleteMiddleRouteComponent(routes: RouteRecordRaw[]) {
64+
const res: RouteRecordRaw[] = []
65+
routes.forEach((route) => {
66+
if (route.children) {
67+
delete route.component
68+
route.children = deleteMiddleRouteComponent(route.children)
69+
}
70+
res.push(route)
71+
})
72+
return res
73+
}
74+
75+
// 路由匹配器
76+
const routesMatcher = ref<RouterMatcher>()
77+
// 根据路径获取匹配的路由
78+
function getRouteMatchedByPath(path: string) {
79+
return routesMatcher.value?.resolve({ path }, undefined!)?.matched ?? []
80+
}
11681

11782
// 生成路由(前端生成)
11883
function generateRoutesAtFront(asyncRoutes: Route.recordMainRaw[]) {
11984
// 设置 routes 数据
12085
routesRaw.value = cloneDeep(asyncRoutes) as any
86+
// 创建路由匹配器
87+
const routes: RouteRecordRaw[] = []
88+
routesRaw.value.forEach((route) => {
89+
if (route.children) {
90+
routes.push(...route.children)
91+
}
92+
})
93+
routesMatcher.value = createRouterMatcher(routes, {})
12194
isGenerate.value = true
12295
}
12396
// 格式化后端路由数据
@@ -146,6 +119,14 @@ const useRouteStore = defineStore(
146119
await apiApp.routeList().then((res) => {
147120
// 设置 routes 数据
148121
routesRaw.value = formatBackRoutes(res.data) as any
122+
// 创建路由匹配器
123+
const routes: RouteRecordRaw[] = []
124+
routesRaw.value.forEach((route) => {
125+
if (route.children) {
126+
routes.push(...route.children)
127+
}
128+
})
129+
routesMatcher.value = createRouterMatcher(routes, {})
149130
isGenerate.value = true
150131
}).catch(() => {})
151132
}
@@ -174,8 +155,9 @@ const useRouteStore = defineStore(
174155
isGenerate,
175156
routesRaw,
176157
currentRemoveRoutes,
177-
flatRoutes,
178-
flatSystemRoutes,
158+
routes,
159+
systemRoutes,
160+
getRouteMatchedByPath,
179161
generateRoutesAtFront,
180162
generateRoutesAtBack,
181163
generateRoutesAtFilesystem,

‎src/store/modules/tabbar.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const useTabbarStore = defineStore(
3232
tabId,
3333
fullPath: route.fullPath,
3434
title: typeof meta?.title === 'function' ? meta.title() : meta?.title,
35-
icon: meta?.icon ?? meta?.breadcrumbNeste?.findLast(item => item.icon)?.icon,
35+
icon: meta?.icon ?? route.matched?.findLast(item => item.meta?.icon)?.meta?.icon,
3636
name: names,
3737
}
3838
if (leaveIndex.value >= 0) {

‎src/types/global.d.ts‎

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,6 @@ declare module 'vue-router' {
251251
cache?: boolean | string | string[]
252252
noCache?: string | string[]
253253
link?: string
254-
breadcrumbNeste?: Route.breadcrumb[]
255254
}
256255
}
257256

@@ -264,12 +263,6 @@ declare namespace Route {
264263
}
265264
children: RouteRecordRaw[]
266265
}
267-
interface breadcrumb {
268-
path: string
269-
title?: string | (() => string)
270-
icon?: string
271-
hide: boolean
272-
}
273266
}
274267

275268
declare namespace Menu {

0 commit comments

Comments
 (0)