重构注册路由数据结构

This commit is contained in:
Hooray
2025-02-16 02:48:36 +08:00
parent 9f0bdf49b9
commit d80178c409
7 changed files with 83 additions and 130 deletions
@@ -16,16 +16,14 @@ const breadcrumbList = computed(() => {
title: settingsStore.settings.home.title,
})
}
if (route.meta.breadcrumbNeste) {
route.meta.breadcrumbNeste.forEach((item) => {
if (item.hide === false) {
breadcrumbList.push({
path: item.path,
title: item.title,
})
}
})
}
route.matched.forEach((item) => {
if (item.meta?.breadcrumb !== false) {
breadcrumbList.push({
path: item.path,
title: item.meta?.title,
})
}
})
return breadcrumbList
})
@@ -3,9 +3,9 @@ import type { Menu } from '@/types/global'
import Breadcrumb from '@/layouts/components/Breadcrumb/index.vue'
import BreadcrumbItem from '@/layouts/components/Breadcrumb/item.vue'
import useMenuStore from '@/store/modules/menu'
import useRouteStore from '@/store/modules/route'
import useSettingsStore from '@/store/modules/settings'
import { resolveRoutePath } from '@/utils'
import { cloneDeep } from 'es-toolkit'
import hotkeys from 'hotkeys-js'
defineOptions({
@@ -18,6 +18,7 @@ const isShow = defineModel<boolean>({
const router = useRouter()
const settingsStore = useSettingsStore()
const routeStore = useRouteStore()
const menuStore = useMenuStore()
interface listTypes {
@@ -25,9 +26,6 @@ interface listTypes {
icon?: string
title?: string | (() => string)
link?: string
breadcrumb: {
title?: string | (() => string)
}[]
}
const searchInput = ref('')
@@ -57,18 +55,8 @@ const resultList = computed(() => {
if (item.path.includes(searchInput.value)) {
flag = true
}
if (item.breadcrumb.some((b) => {
if (typeof b.title === 'function') {
if (b.title().includes(searchInput.value)) {
return true
}
}
else {
if (b.title?.includes(searchInput.value)) {
return true
}
}
return false
if (routeStore.getRouteMatchedByPath(item.path).some((b) => {
return typeof b.meta?.title === 'function' ? b.meta?.title().includes(searchInput.value) : b.meta?.title?.includes(searchInput.value)
})) {
flag = true
}
@@ -122,7 +110,7 @@ onUnmounted(() => {
function initSourceList() {
sourceList.value = []
menuStore.allMenus.forEach((item) => {
getSourceListByMenus(item.children)
getSourceList(item.children)
})
}
@@ -133,26 +121,18 @@ function hasChildren(item: Menu.recordRaw) {
}
return flag
}
function getSourceListByMenus(arr: Menu.recordRaw[], basePath?: string, icon?: string, breadcrumb?: { title?: string | (() => string) }[]) {
function getSourceList(arr: Menu.recordRaw[], basePath?: string, icon?: string) {
arr.forEach((item) => {
if (item.meta?.menu !== false) {
const breadcrumbTemp = cloneDeep(breadcrumb) || []
if (item.children && hasChildren(item)) {
breadcrumbTemp.push({
title: item.meta?.title,
})
getSourceListByMenus(item.children, resolveRoutePath(basePath, item.path), item.meta?.icon ?? icon, breadcrumbTemp)
getSourceList(item.children, resolveRoutePath(basePath, item.path), item.meta?.icon ?? icon)
}
else {
breadcrumbTemp.push({
title: item.meta?.title,
})
sourceList.value.push({
path: resolveRoutePath(basePath, item.path),
icon: item.meta?.icon ?? icon,
title: item.meta?.title,
link: item.meta?.link,
breadcrumb: breadcrumbTemp,
})
}
}
@@ -263,9 +243,9 @@ function pageJump(path: listTypes['path'], link: listTypes['link']) {
<FaIcon v-if="item.icon" :name="item.icon" class="size-5 basis-16 transition" :class="{ 'scale-120 text-primary': index === actived }" />
<div class="flex flex-1 flex-col gap-1 truncate border-s px-4 py-3">
<div class="truncate text-start text-base font-bold">{{ (typeof item.title === 'function' ? item.title() : item.title) ?? '[ 无标题 ]' }}</div>
<Breadcrumb v-if="item.breadcrumb.length" class="truncate">
<BreadcrumbItem v-for="(bc, bcIndex) in item.breadcrumb" :key="bcIndex" class="text-xs">
{{ (typeof bc.title === 'function' ? bc.title() : bc.title) ?? '[ 无标题 ]' }}
<Breadcrumb v-if="routeStore.getRouteMatchedByPath(item.path).length" class="truncate">
<BreadcrumbItem v-for="(bc, bcIndex) in routeStore.getRouteMatchedByPath(item.path)" :key="bcIndex" class="text-xs">
{{ (typeof bc.meta?.title === 'function' ? bc.meta?.title() : bc.meta?.title) ?? '[ 无标题 ]' }}
</BreadcrumbItem>
</Breadcrumb>
</div>
+3 -3
View File
@@ -73,13 +73,13 @@ function setupRoutes(router: Router) {
// 注册并记录路由数据
// 记录的数据会在登出时会使用到,不使用 router.removeRoute 是考虑配置的路由可能不一定有设置 name ,则通过调用 router.addRoute() 返回的回调进行删除
const removeRoutes: (() => void)[] = []
routeStore.flatRoutes.forEach((route) => {
routeStore.routes.forEach((route) => {
if (!/^(?:https?:|mailto:|tel:)/.test(route.path)) {
removeRoutes.push(router.addRoute(route as RouteRecordRaw))
}
})
if (settingsStore.settings.app.routeBaseOn !== 'filesystem') {
routeStore.flatSystemRoutes.forEach((route) => {
routeStore.systemRoutes.forEach((route) => {
removeRoutes.push(router.addRoute(route as RouteRecordRaw))
})
}
@@ -133,7 +133,7 @@ function setupTitle(router: Router) {
router.afterEach((to) => {
const settingsStore = useSettingsStore()
if (settingsStore.settings.app.routeBaseOn !== 'filesystem') {
settingsStore.setTitle(to.meta.breadcrumbNeste?.at(-1)?.title ?? to.meta.title)
settingsStore.setTitle(to.matched?.at(-1)?.meta?.title ?? to.meta.title)
}
else {
settingsStore.setTitle(to.meta.title)
+1 -1
View File
@@ -34,7 +34,6 @@ const routes: RouteRecordRaw = {
},
{
path: 'list2',
name: 'breadcrumbExampleList2',
redirect: '/breadcrumb_example/list2',
meta: {
title: '列表2(层级模式)',
@@ -42,6 +41,7 @@ const routes: RouteRecordRaw = {
children: [
{
path: '',
name: 'breadcrumbExampleList2',
component: () => import('@/views/breadcrumb_example/list2.vue'),
meta: {
title: '列表2(层级模式)',
+60 -78
View File
@@ -1,9 +1,9 @@
import type { Route } from '#/global'
import type { RouteRecordRaw } from 'vue-router'
import type { RouteRecordRaw, RouterMatcher } from 'vue-router'
import apiApp from '@/api/modules/app'
import { systemRoutes } from '@/router/routes'
import { resolveRoutePath } from '@/utils'
import { systemRoutes as systemRoutesRaw } from '@/router/routes'
import { cloneDeep } from 'es-toolkit'
import { createRouterMatcher } from 'vue-router'
import useSettingsStore from './settings'
const useRouteStore = defineStore(
@@ -13,79 +13,15 @@ const useRouteStore = defineStore(
const settingsStore = useSettingsStore()
const isGenerate = ref(false)
// 原始路由
const routesRaw = ref<Route.recordMainRaw[]>([])
// 文件系统原始路由
const filesystemRoutesRaw = ref<RouteRecordRaw[]>([])
// 已注册的路由,用于登出时删除路由
const currentRemoveRoutes = ref<(() => void)[]>([])
// 将多层嵌套路由处理成两层,保留顶层和最子层路由,中间层级将被拍平
function flatAsyncRoutes<T extends RouteRecordRaw>(route: T): T {
if (route.children) {
route.children = flatAsyncRoutesRecursive(route.children, [{
path: route.path,
title: route.meta?.title,
icon: route.meta?.icon,
hide: !route.meta?.breadcrumb && route.meta?.breadcrumb === false,
}], route.path)
}
return route
}
function flatAsyncRoutesRecursive(routes: RouteRecordRaw[], breadcrumb: Route.breadcrumb[] = [], baseUrl = ''): RouteRecordRaw[] {
const res: RouteRecordRaw[] = []
routes.forEach((route) => {
if (route.children) {
const childrenBaseUrl = resolveRoutePath(baseUrl, route.path)
const tmpBreadcrumb = cloneDeep(breadcrumb)
tmpBreadcrumb.push({
path: childrenBaseUrl,
title: route.meta?.title,
icon: route.meta?.icon,
hide: !route.meta?.breadcrumb && route.meta?.breadcrumb === false,
})
const tmpRoute = cloneDeep(route)
tmpRoute.path = childrenBaseUrl
if (!tmpRoute.meta) {
tmpRoute.meta = {}
}
tmpRoute.meta.breadcrumbNeste = tmpBreadcrumb
delete tmpRoute.children
res.push(tmpRoute)
const childrenRoutes = flatAsyncRoutesRecursive(route.children, tmpBreadcrumb, childrenBaseUrl)
childrenRoutes.forEach((item) => {
// 如果 path 一样则覆盖,因为子路由的 path 可能设置为空,导致和父路由一样,直接注册会提示路由重复
if (res.some(v => v.path === item.path)) {
res.forEach((v, i) => {
if (v.path === item.path) {
res[i] = item
}
})
}
else {
res.push(item)
}
})
}
else {
const tmpRoute = cloneDeep(route)
tmpRoute.path = resolveRoutePath(baseUrl, tmpRoute.path)
// 处理面包屑导航
const tmpBreadcrumb = cloneDeep(breadcrumb)
tmpBreadcrumb.push({
path: tmpRoute.path,
title: tmpRoute.meta?.title,
icon: tmpRoute.meta?.icon,
hide: !tmpRoute.meta?.breadcrumb && tmpRoute.meta?.breadcrumb === false,
})
if (!tmpRoute.meta) {
tmpRoute.meta = {}
}
tmpRoute.meta.breadcrumbNeste = tmpBreadcrumb
res.push(tmpRoute)
}
})
return res
}
// 扁平化路由(将三级及以上路由数据拍平成二级)
const flatRoutes = computed(() => {
// 实际路由
const routes = computed(() => {
const returnRoutes: RouteRecordRaw[] = []
if (settingsStore.settings.app.routeBaseOn !== 'filesystem') {
if (routesRaw.value) {
@@ -100,7 +36,12 @@ const useRouteStore = defineStore(
})
returnRoutes.push(...tmpRoutes)
})
returnRoutes.forEach(item => flatAsyncRoutes(item))
returnRoutes.forEach((item) => {
if (item.children) {
item.children = deleteMiddleRouteComponent(item.children)
}
return item
})
}
}
else {
@@ -108,16 +49,48 @@ const useRouteStore = defineStore(
}
return returnRoutes
})
const flatSystemRoutes = computed(() => {
const routes = [...systemRoutes]
routes.forEach(item => flatAsyncRoutes(item))
// 系统路由
const systemRoutes = computed(() => {
const routes = [...systemRoutesRaw]
routes.forEach((item) => {
if (item.children) {
item.children = deleteMiddleRouteComponent(item.children)
}
})
return routes
})
// 删除路由中间层级对应的组件
function deleteMiddleRouteComponent(routes: RouteRecordRaw[]) {
const res: RouteRecordRaw[] = []
routes.forEach((route) => {
if (route.children) {
delete route.component
route.children = deleteMiddleRouteComponent(route.children)
}
res.push(route)
})
return res
}
// 路由匹配器
const routesMatcher = ref<RouterMatcher>()
// 根据路径获取匹配的路由
function getRouteMatchedByPath(path: string) {
return routesMatcher.value?.resolve({ path }, undefined!)?.matched ?? []
}
// 生成路由(前端生成)
function generateRoutesAtFront(asyncRoutes: Route.recordMainRaw[]) {
// 设置 routes 数据
routesRaw.value = cloneDeep(asyncRoutes) as any
// 创建路由匹配器
const routes: RouteRecordRaw[] = []
routesRaw.value.forEach((route) => {
if (route.children) {
routes.push(...route.children)
}
})
routesMatcher.value = createRouterMatcher(routes, {})
isGenerate.value = true
}
// 格式化后端路由数据
@@ -146,6 +119,14 @@ const useRouteStore = defineStore(
await apiApp.routeList().then((res) => {
// 设置 routes 数据
routesRaw.value = formatBackRoutes(res.data) as any
// 创建路由匹配器
const routes: RouteRecordRaw[] = []
routesRaw.value.forEach((route) => {
if (route.children) {
routes.push(...route.children)
}
})
routesMatcher.value = createRouterMatcher(routes, {})
isGenerate.value = true
}).catch(() => {})
}
@@ -174,8 +155,9 @@ const useRouteStore = defineStore(
isGenerate,
routesRaw,
currentRemoveRoutes,
flatRoutes,
flatSystemRoutes,
routes,
systemRoutes,
getRouteMatchedByPath,
generateRoutesAtFront,
generateRoutesAtBack,
generateRoutesAtFilesystem,
+1 -1
View File
@@ -32,7 +32,7 @@ const useTabbarStore = defineStore(
tabId,
fullPath: route.fullPath,
title: typeof meta?.title === 'function' ? meta.title() : meta?.title,
icon: meta?.icon ?? meta?.breadcrumbNeste?.findLast(item => item.icon)?.icon,
icon: meta?.icon ?? route.matched?.findLast(item => item.meta?.icon)?.meta?.icon,
name: names,
}
if (leaveIndex.value >= 0) {
-7
View File
@@ -251,7 +251,6 @@ declare module 'vue-router' {
cache?: boolean | string | string[]
noCache?: string | string[]
link?: string
breadcrumbNeste?: Route.breadcrumb[]
}
}
@@ -264,12 +263,6 @@ declare namespace Route {
}
children: RouteRecordRaw[]
}
interface breadcrumb {
path: string
title?: string | (() => string)
icon?: string
hide: boolean
}
}
declare namespace Menu {