2026-04-23 09:05:55 +08:00
|
|
|
import type { RouteMeta } from 'vue-router';
|
|
|
|
|
import type { ElegantConstRoute, LastLevelRouteKey } from '@elegant-router/types';
|
|
|
|
|
import { objectContextDomainConfigs } from '@/constants/object-context';
|
2026-03-26 20:18:20 +08:00
|
|
|
import { SYSTEM_SERVICE_PREFIX } from '@/constants/service';
|
2026-04-23 09:05:55 +08:00
|
|
|
import { createStaticRoutes } from '@/router/routes';
|
2026-03-26 20:18:20 +08:00
|
|
|
import { request } from '../request';
|
2026-04-23 09:05:55 +08:00
|
|
|
import { type ServiceRequestResult, safeJsonRequestConfig } from './shared';
|
2026-03-26 20:18:20 +08:00
|
|
|
|
|
|
|
|
type BackendMenuRoute = Omit<Api.Route.MenuRoute, 'id' | 'children'> & {
|
|
|
|
|
id: string | number;
|
|
|
|
|
children?: BackendMenuRoute[];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
interface BackendUserRouteDTO {
|
|
|
|
|
routes?: BackendMenuRoute[] | null;
|
|
|
|
|
home?: string | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let userRoutePromise: Promise<ServiceRequestResult<BackendUserRouteDTO>> | null = null;
|
|
|
|
|
|
2026-04-23 09:05:55 +08:00
|
|
|
const staticObjectContextRouteMap = new Map<App.ObjectContext.DomainKey, ElegantConstRoute>(
|
|
|
|
|
createStaticRoutes()
|
|
|
|
|
.authRoutes.filter(route => objectContextDomainConfigs.some(config => config.domainKey === route.name))
|
|
|
|
|
.map(route => [route.name as App.ObjectContext.DomainKey, route])
|
|
|
|
|
);
|
|
|
|
|
|
2026-03-26 20:18:20 +08:00
|
|
|
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))
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 09:05:55 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-09 11:30:34 +08:00
|
|
|
// Create a map of backend routes by name for quick lookup
|
|
|
|
|
const backendRouteMap = new Map<string, Api.Route.MenuRoute>();
|
|
|
|
|
domainTopLevelRoutes.forEach(route => {
|
|
|
|
|
if (route.name) {
|
|
|
|
|
backendRouteMap.set(String(route.name), route);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Clone static route but preserve backend route's meta for children
|
|
|
|
|
// 待重构:拆 helper 以降低复杂度,暂以 disable 注释临时放行
|
|
|
|
|
// eslint-disable-next-line complexity
|
|
|
|
|
function cloneStaticRoutePreservingBackendMeta(route: ElegantConstRoute, idPrefix: string): Api.Route.MenuRoute {
|
|
|
|
|
const backendRoute = route.name ? backendRouteMap.get(String(route.name)) : undefined;
|
|
|
|
|
const { children: _children, ...routeWithoutChildren } = route;
|
|
|
|
|
const baseRoute: Api.Route.MenuRoute = {
|
|
|
|
|
...routeWithoutChildren,
|
|
|
|
|
id: `${idPrefix}:${String(route.name || route.path)}`
|
2026-04-23 09:05:55 +08:00
|
|
|
};
|
|
|
|
|
|
2026-05-09 11:30:34 +08:00
|
|
|
// If there's a backend route, preserve its meta
|
|
|
|
|
if (backendRoute?.meta) {
|
|
|
|
|
baseRoute.meta = {
|
|
|
|
|
...baseRoute.meta,
|
|
|
|
|
title: backendRoute.meta.title || baseRoute.meta?.title || String(route.name || route.path),
|
|
|
|
|
icon: backendRoute.meta.icon || baseRoute.meta?.icon,
|
|
|
|
|
localIcon: backendRoute.meta.localIcon || baseRoute.meta?.localIcon,
|
|
|
|
|
order:
|
|
|
|
|
backendRoute.meta.order !== undefined && backendRoute.meta.order !== null
|
|
|
|
|
? backendRoute.meta.order
|
|
|
|
|
: baseRoute.meta?.order,
|
|
|
|
|
keepAlive:
|
|
|
|
|
backendRoute.meta.keepAlive !== undefined && backendRoute.meta.keepAlive !== null
|
|
|
|
|
? backendRoute.meta.keepAlive
|
|
|
|
|
: baseRoute.meta?.keepAlive,
|
|
|
|
|
i18nKey: backendRoute.meta.i18nKey || baseRoute.meta?.i18nKey
|
|
|
|
|
};
|
2026-04-23 09:05:55 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-09 11:30:34 +08:00
|
|
|
// Recursively process children
|
|
|
|
|
if (route.children?.length) {
|
|
|
|
|
baseRoute.children = route.children.map(child => cloneStaticRoutePreservingBackendMeta(child, idPrefix));
|
2026-04-23 09:05:55 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-09 11:30:34 +08:00
|
|
|
return baseRoute;
|
|
|
|
|
}
|
2026-04-23 09:05:55 +08:00
|
|
|
|
2026-05-09 11:30:34 +08:00
|
|
|
const wrappedDomainRoute = cloneStaticRoutePreservingBackendMeta(
|
|
|
|
|
staticDomainRoute,
|
|
|
|
|
`object-context:${config.domainKey}`
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Merge entry route's meta to domain route
|
|
|
|
|
if (entryRoute.meta) {
|
|
|
|
|
wrappedDomainRoute.meta = {
|
|
|
|
|
...wrappedDomainRoute.meta,
|
|
|
|
|
title: entryRoute.meta.title || wrappedDomainRoute.meta?.title || config.domainKey,
|
|
|
|
|
icon: entryRoute.meta.icon || wrappedDomainRoute.meta?.icon,
|
|
|
|
|
localIcon: entryRoute.meta.localIcon || wrappedDomainRoute.meta?.localIcon,
|
|
|
|
|
order:
|
|
|
|
|
entryRoute.meta.order !== undefined && entryRoute.meta.order !== null
|
|
|
|
|
? entryRoute.meta.order
|
|
|
|
|
: wrappedDomainRoute.meta?.order,
|
|
|
|
|
keepAlive:
|
|
|
|
|
entryRoute.meta.keepAlive !== undefined && entryRoute.meta.keepAlive !== null
|
|
|
|
|
? entryRoute.meta.keepAlive
|
|
|
|
|
: wrappedDomainRoute.meta?.keepAlive
|
|
|
|
|
};
|
2026-04-23 09:05:55 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-09 11:30:34 +08:00
|
|
|
const entryRouteIndex = normalizedRoutes.findIndex(route => route.id === entryRoute.id);
|
|
|
|
|
const domainRouteIds = new Set(domainTopLevelRoutes.map(route => route.id));
|
|
|
|
|
|
2026-04-23 09:05:55 +08:00
|
|
|
normalizedRoutes = normalizedRoutes.filter(route => !domainRouteIds.has(route.id));
|
|
|
|
|
normalizedRoutes.splice(entryRouteIndex < 0 ? normalizedRoutes.length : entryRouteIndex, 0, wrappedDomainRoute);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return normalizedRoutes;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 20:18:20 +08:00
|
|
|
function normalizeUserRoute(data: BackendUserRouteDTO): Api.Route.UserRoute {
|
|
|
|
|
return {
|
2026-04-23 09:05:55 +08:00
|
|
|
routes: replaceWithStaticObjectContextDomainRoute((data.routes ?? []).map(route => normalizeMenuRoute(route))),
|
2026-03-26 20:18:20 +08:00
|
|
|
home: (data.home || 'system_user') as LastLevelRouteKey
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 获取常量路由 */
|
|
|
|
|
export function fetchGetConstantRoutes() {
|
2026-04-23 09:05:55 +08:00
|
|
|
return request<Api.Route.MenuRoute[]>({
|
|
|
|
|
...safeJsonRequestConfig,
|
|
|
|
|
url: '/route/getConstantRoutes'
|
|
|
|
|
});
|
2026-03-26 20:18:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 获取用户路由 */
|
|
|
|
|
export async function fetchGetUserRoutes(force = false): Promise<ServiceRequestResult<Api.Route.UserRoute>> {
|
|
|
|
|
if (!userRoutePromise || force) {
|
|
|
|
|
userRoutePromise = request<BackendUserRouteDTO>({
|
2026-04-23 09:05:55 +08:00
|
|
|
...safeJsonRequestConfig,
|
2026-03-26 20:18:20 +08:00
|
|
|
url: `${SYSTEM_SERVICE_PREFIX}/auth/get-user-routes`
|
|
|
|
|
}).then(result => result as ServiceRequestResult<BackendUserRouteDTO>);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const result = await userRoutePromise;
|
|
|
|
|
|
|
|
|
|
if (result.error || !result.data) {
|
|
|
|
|
userRoutePromise = null;
|
|
|
|
|
return result as ServiceRequestResult<Api.Route.UserRoute>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...result,
|
|
|
|
|
data: normalizeUserRoute(result.data)
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 判断路由是否存在
|
|
|
|
|
*
|
|
|
|
|
* @param routeName 路由名称
|
|
|
|
|
*/
|
|
|
|
|
export async function fetchIsRouteExist(routeName: string): Promise<ServiceRequestResult<boolean>> {
|
|
|
|
|
const result = await fetchGetUserRoutes();
|
|
|
|
|
|
|
|
|
|
if (result.error || !result.data) {
|
|
|
|
|
return {
|
|
|
|
|
...result,
|
|
|
|
|
data: false
|
|
|
|
|
} as unknown as ServiceRequestResult<boolean>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|