mirror of
https://github.com/fantastic-admin/basic.git
synced 2026-10-07 13:48:38 +08:00
重构注册路由数据结构
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Vendored
-7
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user