Files
cn-rdms-web/src/service/api/route.ts

244 lines
8.2 KiB
TypeScript

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<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;
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])
);
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;
}
// 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)}`
};
// 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
};
}
// Recursively process children
if (route.children?.length) {
baseRoute.children = route.children.map(child => cloneStaticRoutePreservingBackendMeta(child, idPrefix));
}
return baseRoute;
}
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
};
}
const entryRouteIndex = normalizedRoutes.findIndex(route => route.id === entryRoute.id);
const domainRouteIds = new Set(domainTopLevelRoutes.map(route => route.id));
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<Api.Route.MenuRoute[]>({
...safeJsonRequestConfig,
url: '/route/getConstantRoutes'
});
}
/** 获取用户路由 */
export async function fetchGetUserRoutes(force = false): Promise<ServiceRequestResult<Api.Route.UserRoute>> {
if (!userRoutePromise || force) {
userRoutePromise = request<BackendUserRouteDTO>({
...safeJsonRequestConfig,
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;
}