import type { RouteMeta } from 'vue-router'; import type { ElegantConstRoute, LastLevelRouteKey } from '@elegant-router/types'; import { objectContextDomainConfigs } from '@/constants/object-context'; import { SYSTEM_SERVICE_PREFIX } from '@/constants/service'; import { createStaticRoutes } from '@/router/routes'; import { request } from '../request'; import { type ServiceRequestResult, safeJsonRequestConfig } from './shared'; type BackendMenuRoute = Omit & { id: string | number; children?: BackendMenuRoute[]; }; interface BackendUserRouteDTO { routes?: BackendMenuRoute[] | null; home?: string | null; } let userRoutePromise: Promise> | null = null; const staticObjectContextRouteMap = new Map( createStaticRoutes() .authRoutes.filter(route => objectContextDomainConfigs.some(config => config.domainKey === route.name)) .map(route => [route.name as App.ObjectContext.DomainKey, route]) ); export function clearUserRouteCache() { userRoutePromise = null; } function normalizeMenuRoute(route: BackendMenuRoute): Api.Route.MenuRoute { return { ...route, id: String(route.id), children: route.children?.map(child => normalizeMenuRoute(child)) }; } function normalizePath(path?: string | null) { if (!path) { return '/'; } return path.endsWith('/') && path !== '/' ? path.slice(0, -1) : path; } function isPathMatchedByPrefix(path: string, prefix: string) { const normalizedPath = normalizePath(path); const normalizedPrefix = normalizePath(prefix); return normalizedPath === normalizedPrefix || normalizedPath.startsWith(`${normalizedPrefix}/`); } function isTopLevelObjectContextEntryRoute(route: Api.Route.MenuRoute, config: App.ObjectContext.DomainConfig) { const routePath = normalizePath(route.path); return ( route.component?.startsWith('view.') && !route.children?.length && (route.name === config.entryRouteKey || routePath === normalizePath(config.entryRoutePath)) ); } function cloneStaticRouteAsMenuRoute(route: ElegantConstRoute, idPrefix: string): Api.Route.MenuRoute { return { ...route, id: `${idPrefix}:${String(route.name || route.path)}`, children: route.children?.map(child => cloneStaticRouteAsMenuRoute(child, idPrefix)) }; } function replaceWithStaticObjectContextDomainRoute(routes: Api.Route.MenuRoute[]) { let normalizedRoutes = [...routes]; objectContextDomainConfigs.forEach(config => { const hasDomainRootRoute = normalizedRoutes.some(route => route.name === config.domainKey); if (hasDomainRootRoute) { return; } const domainTopLevelRoutes = normalizedRoutes.filter(route => config.routePathPrefixes.some(prefix => isPathMatchedByPrefix(route.path, prefix)) ); const entryRoute = domainTopLevelRoutes.find(route => isTopLevelObjectContextEntryRoute(route, config)); if (!entryRoute) { return; } const staticDomainRoute = staticObjectContextRouteMap.get(config.domainKey); if (!staticDomainRoute) { return; } const wrappedDomainRoute = cloneStaticRouteAsMenuRoute(staticDomainRoute, `object-context:${config.domainKey}`); const entryRouteIndex = normalizedRoutes.findIndex(route => route.id === entryRoute.id); const domainRouteIds = new Set(domainTopLevelRoutes.map(route => route.id)); if (entryRoute.meta) { const nextMeta: RouteMeta = { title: wrappedDomainRoute.meta?.title || config.domainKey, ...(wrappedDomainRoute.meta || {}) }; if (entryRoute.meta.icon) { nextMeta.icon = entryRoute.meta.icon; } if (entryRoute.meta.localIcon) { nextMeta.localIcon = entryRoute.meta.localIcon; } if (entryRoute.meta.order !== undefined) { nextMeta.order = entryRoute.meta.order; } wrappedDomainRoute.meta = nextMeta; } normalizedRoutes = normalizedRoutes.filter(route => !domainRouteIds.has(route.id)); normalizedRoutes.splice(entryRouteIndex < 0 ? normalizedRoutes.length : entryRouteIndex, 0, wrappedDomainRoute); }); return normalizedRoutes; } function normalizeUserRoute(data: BackendUserRouteDTO): Api.Route.UserRoute { return { routes: replaceWithStaticObjectContextDomainRoute((data.routes ?? []).map(route => normalizeMenuRoute(route))), home: (data.home || 'system_user') as LastLevelRouteKey }; } /** 获取常量路由 */ export function fetchGetConstantRoutes() { return request({ ...safeJsonRequestConfig, url: '/route/getConstantRoutes' }); } /** 获取用户路由 */ export async function fetchGetUserRoutes(force = false): Promise> { if (!userRoutePromise || force) { userRoutePromise = request({ ...safeJsonRequestConfig, url: `${SYSTEM_SERVICE_PREFIX}/auth/get-user-routes` }).then(result => result as ServiceRequestResult); } const result = await userRoutePromise; if (result.error || !result.data) { userRoutePromise = null; return result as ServiceRequestResult; } return { ...result, data: normalizeUserRoute(result.data) }; } /** * 判断路由是否存在 * * @param routeName 路由名称 */ export async function fetchIsRouteExist(routeName: string): Promise> { const result = await fetchGetUserRoutes(); if (result.error || !result.data) { return { ...result, data: false } as unknown as ServiceRequestResult; } const isExist = result.data.routes.some(route => recursiveIsRouteExist(route, routeName)); return { ...result, data: isExist }; } function recursiveIsRouteExist(route: Api.Route.MenuRoute, routeName: string): boolean { if (route.name === routeName) { return true; } return route.children?.some(child => recursiveIsRouteExist(child, routeName)) || false; }