199 lines
5.4 KiB
TypeScript
199 lines
5.4 KiB
TypeScript
import type {
|
|
ComponentRecordType,
|
|
GenerateMenuAndRoutesOptions,
|
|
} from '@vben/types';
|
|
|
|
import { generateAccessible } from '@vben/access';
|
|
import { preferences } from '@vben/preferences';
|
|
|
|
import { getAllMenusApi } from '#/api/core/menu';
|
|
import { BasicLayout, IFrameView } from '#/layouts';
|
|
import { $t } from '#/locales';
|
|
import { useAppContextStore } from '#/store/app-context';
|
|
|
|
const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue');
|
|
|
|
const lightMenuNames = new Set([
|
|
'AIAgent',
|
|
'AIKnowledgeDetail',
|
|
'AIModelConfig',
|
|
'AIPlatform',
|
|
'AIWorkflow',
|
|
'AIWorkflowRuns',
|
|
'AnnouncementList',
|
|
'AnnouncementManage',
|
|
'Codex',
|
|
'ControlCenter',
|
|
'KnowledgeBase',
|
|
'Message',
|
|
'MessageList',
|
|
'SystemManagement',
|
|
'SystemMenu',
|
|
'SystemPermission',
|
|
'SystemRole',
|
|
'UserManagement',
|
|
]);
|
|
|
|
function normalizeViewPath(path: string) {
|
|
const normalizedPath = path.replace(/^(\.\/|\.\.\/)+/, '');
|
|
const viewPath = normalizedPath.startsWith('/')
|
|
? normalizedPath
|
|
: `/${normalizedPath}`;
|
|
return viewPath.replace(/^\/views/, '');
|
|
}
|
|
|
|
function hasPageComponent(
|
|
component: string | undefined,
|
|
pageMap: ComponentRecordType,
|
|
layoutMap: ComponentRecordType,
|
|
) {
|
|
if (!component) return true;
|
|
if (layoutMap[component]) return true;
|
|
|
|
const normalizedPath = normalizeViewPath(component);
|
|
const pageKey = normalizedPath.endsWith('.vue')
|
|
? normalizedPath
|
|
: `${normalizedPath}.vue`;
|
|
|
|
return Boolean(pageMap[pageKey]);
|
|
}
|
|
|
|
function normalizePageMap(pageMap: ComponentRecordType) {
|
|
return Object.fromEntries(
|
|
Object.entries(pageMap).map(([key, value]) => [
|
|
normalizeViewPath(key),
|
|
value,
|
|
]),
|
|
) as ComponentRecordType;
|
|
}
|
|
|
|
function normalizeBackendRoute<
|
|
T extends {
|
|
component?: string;
|
|
name?: string;
|
|
path?: string;
|
|
query?: unknown;
|
|
},
|
|
>(route: T) {
|
|
if (route.name !== 'ControlCenter') return route;
|
|
|
|
const normalized = { ...route };
|
|
normalized.path = '/page-render/main_home';
|
|
normalized.component = 'control-center/index';
|
|
normalized.query = undefined;
|
|
return normalized;
|
|
}
|
|
|
|
function isLightMenuRoute(route: { name?: string }) {
|
|
return !route.name || lightMenuNames.has(route.name);
|
|
}
|
|
|
|
function filterAvailableRoutes<
|
|
T extends {
|
|
children?: T[];
|
|
component?: string;
|
|
name?: string;
|
|
path?: string;
|
|
query?: unknown;
|
|
},
|
|
>(
|
|
routes: T[],
|
|
pageMap: ComponentRecordType,
|
|
layoutMap: ComponentRecordType,
|
|
): T[] {
|
|
return routes
|
|
.filter(isLightMenuRoute)
|
|
.map((route) => {
|
|
const normalizedRoute = normalizeBackendRoute(route);
|
|
const children = normalizedRoute.children
|
|
? filterAvailableRoutes(normalizedRoute.children, pageMap, layoutMap)
|
|
: undefined;
|
|
|
|
if (
|
|
!normalizedRoute.component &&
|
|
normalizedRoute.children &&
|
|
!children?.length
|
|
) {
|
|
return undefined;
|
|
}
|
|
|
|
if (!hasPageComponent(normalizedRoute.component, pageMap, layoutMap)) {
|
|
if (!children?.length) return undefined;
|
|
|
|
const groupRoute = { ...normalizedRoute, children };
|
|
delete groupRoute.component;
|
|
return groupRoute;
|
|
}
|
|
|
|
return children ? { ...normalizedRoute, children } : normalizedRoute;
|
|
})
|
|
.filter(Boolean) as T[];
|
|
}
|
|
|
|
async function generateAccess(options: GenerateMenuAndRoutesOptions) {
|
|
const pageMap: ComponentRecordType = import.meta.glob([
|
|
'../views/_core/agent-chat/**/*.vue',
|
|
'../views/_core/announcement/index.vue',
|
|
'../views/_core/announcement/list.vue',
|
|
'../views/_core/authentication/login.vue',
|
|
'../views/_core/authentication/oauth-callback.vue',
|
|
'../views/_core/fallback/**/*.vue',
|
|
'../views/_core/file-preview/index.vue',
|
|
'../views/_core/menu/index.vue',
|
|
'../views/_core/message/index.vue',
|
|
'../views/_core/permission/index.vue',
|
|
'../views/_core/role/index.vue',
|
|
'../views/_core/user/index.vue',
|
|
'../views/ai-platform/agent/editor/index.vue',
|
|
'../views/ai-platform/agent/index.vue',
|
|
'../views/ai-platform/knowledge/detail/index.vue',
|
|
'../views/ai-platform/knowledge/index.vue',
|
|
'../views/ai-platform/model/index.vue',
|
|
'../views/ai-platform/workflow/editor/index.vue',
|
|
'../views/ai-platform/workflow/index.vue',
|
|
'../views/ai-platform/workflow-runs/index.vue',
|
|
'../views/control-center/index.vue',
|
|
]);
|
|
|
|
const layoutMap: ComponentRecordType = {
|
|
BasicLayout,
|
|
IFrameView,
|
|
Layout: BasicLayout,
|
|
'Layout.vue': BasicLayout,
|
|
'/Layout.vue': BasicLayout,
|
|
};
|
|
const routePageMap = normalizePageMap(pageMap);
|
|
|
|
return await generateAccessible(preferences.app.accessMode, {
|
|
...options,
|
|
fetchMenuListAsync: async () => {
|
|
const { ElMessage } =
|
|
await import('element-plus/es/components/message/index');
|
|
ElMessage({
|
|
duration: 1500,
|
|
message: `${$t('common.loadingMenu')}...`,
|
|
});
|
|
|
|
// 获取应用上下文
|
|
const appContextStore = useAppContextStore();
|
|
|
|
// 如果是子应用模式,传递 appCode 过滤应用专属菜单
|
|
if (appContextStore.appCode) {
|
|
const menus = await getAllMenusApi(appContextStore.appCode);
|
|
return filterAvailableRoutes(menus, routePageMap, layoutMap);
|
|
}
|
|
|
|
// 主应用模式,不传参数
|
|
const menus = await getAllMenusApi();
|
|
return filterAvailableRoutes(menus, routePageMap, layoutMap);
|
|
},
|
|
// 可以指定没有权限跳转403页面
|
|
forbiddenComponent,
|
|
// 如果 route.meta.menuVisibleWithForbidden = true
|
|
layoutMap,
|
|
pageMap,
|
|
});
|
|
}
|
|
|
|
export { generateAccess };
|