diff --git a/src/layouts/components/Topbar/Toolbar/Breadcrumb/index.vue b/src/layouts/components/Topbar/Toolbar/Breadcrumb/index.vue index cfe0fc6c..e0e5549c 100644 --- a/src/layouts/components/Topbar/Toolbar/Breadcrumb/index.vue +++ b/src/layouts/components/Topbar/Toolbar/Breadcrumb/index.vue @@ -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 }) diff --git a/src/layouts/components/Topbar/Toolbar/NavSearch/search.vue b/src/layouts/components/Topbar/Toolbar/NavSearch/search.vue index ce23d6c6..78d524ff 100644 --- a/src/layouts/components/Topbar/Toolbar/NavSearch/search.vue +++ b/src/layouts/components/Topbar/Toolbar/NavSearch/search.vue @@ -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({ 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']) {
{{ (typeof item.title === 'function' ? item.title() : item.title) ?? '[ 无标题 ]' }}
- - - {{ (typeof bc.title === 'function' ? bc.title() : bc.title) ?? '[ 无标题 ]' }} + + + {{ (typeof bc.meta?.title === 'function' ? bc.meta?.title() : bc.meta?.title) ?? '[ 无标题 ]' }}
diff --git a/src/router/guards.ts b/src/router/guards.ts index 4bf3659f..91432116 100644 --- a/src/router/guards.ts +++ b/src/router/guards.ts @@ -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) diff --git a/src/router/modules/breadcrumb.example.ts b/src/router/modules/breadcrumb.example.ts index 1205cac0..5c1ce9dc 100755 --- a/src/router/modules/breadcrumb.example.ts +++ b/src/router/modules/breadcrumb.example.ts @@ -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(层级模式)', diff --git a/src/store/modules/route.ts b/src/store/modules/route.ts index b90f6b53..dc29926d 100755 --- a/src/store/modules/route.ts +++ b/src/store/modules/route.ts @@ -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([]) + // 文件系统原始路由 const filesystemRoutesRaw = ref([]) + // 已注册的路由,用于登出时删除路由 const currentRemoveRoutes = ref<(() => void)[]>([]) - // 将多层嵌套路由处理成两层,保留顶层和最子层路由,中间层级将被拍平 - function flatAsyncRoutes(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() + // 根据路径获取匹配的路由 + 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, diff --git a/src/store/modules/tabbar.ts b/src/store/modules/tabbar.ts index 83fb0b92..fb83e2f6 100755 --- a/src/store/modules/tabbar.ts +++ b/src/store/modules/tabbar.ts @@ -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) { diff --git a/src/types/global.d.ts b/src/types/global.d.ts index 9da5580a..45ac3ab8 100755 --- a/src/types/global.d.ts +++ b/src/types/global.d.ts @@ -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 {