front/scripts/import-api-menus.ts
2026-01-24 11:55:01 +08:00

283 lines
8.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* API 菜单批量导入脚本
* 从路由配置中读取 API 相关的路由,并批量导入到后端
*
* 使用方法:
* 1. 在浏览器控制台中运行(需要先登录)
* 2. 或者通过 Node.js 运行(需要配置环境变量)
*/
import { MenuService } from '@/api/rbac';
import type { MenuCreateDto } from '@/api/rbac';
import SYSTEM_ROUTES from '@/router/routes/newModules/system';
// 菜单标题映射(从 locale key 到中文)
const menuTitleMap: Record<string, string> = {
'menu.api': 'API',
'menu.api.upstreamInterface': '上游接口',
'menu.api.downstreamInterface': '下游接口',
'menu.api.appList': 'App列表',
'menu.api.requestLog': '请求日志',
'menu.api.callStatistics': '调用统计',
'menu.api.apiDebugger': 'API调试',
'menu.api.createInterface': '创建接口',
'menu.api.editInterface': '修改接口',
'menu.api.createApplication': '创建应用',
};
/**
* 从动态导入函数中提取组件路径
*/
function extractComponentPath(component: any): string | undefined {
if (typeof component === 'string') {
return component;
}
if (typeof component === 'function') {
try {
const componentStr = component.toString();
// 匹配 import('@/views/...') 或 import("@/views/...")
const match = componentStr.match(/import\(['"]@\/views\/([^'"]+)\.vue['"]\)/);
if (match && match[1]) {
return `views/${match[1]}`;
}
} catch (error) {
console.error('提取组件路径失败:', error);
}
}
return undefined;
}
/**
* 获取菜单标题
*/
function getMenuTitle(route: any): string {
const localeKey = route.meta?.locale as string;
if (localeKey && menuTitleMap[localeKey]) {
return menuTitleMap[localeKey];
}
return route.name as string || route.path;
}
/**
* 构建路由路径
*/
function buildRoutePath(route: any, parentPath?: string): string {
let routePath = route.path;
if (!routePath.startsWith('/')) {
if (parentPath) {
routePath = `${parentPath}/${routePath}`;
} else {
routePath = `/${routePath}`;
}
}
return routePath;
}
/**
* 将路由配置转换为菜单数据
*/
function convertRouteToMenuData(
route: any,
parentId?: string | null,
parentPath?: string
): MenuCreateDto[] {
const menus: MenuCreateDto[] = [];
// 跳过没有 meta 的路由
if (!route.meta) {
return menus;
}
// 跳过隐藏的菜单(hideInMenu: true),这些不在导航中显示
if (route.meta.hideInMenu) {
return menus;
}
const routePath = buildRoutePath(route, parentPath);
const componentPath = extractComponentPath(route.component);
const menuTitle = getMenuTitle(route);
const menuData: MenuCreateDto = {
pId: parentId || null,
name: menuTitle,
type: 1, // 菜单类型:1=菜单
permissionCode: route.meta.permission as string || null,
icon: route.meta.icon as string || null,
route: routePath,
component: componentPath || null,
order: route.meta.order as number || 0,
isHidden: false,
isCached: false,
description: `从路由配置导入: ${routePath}`,
};
menus.push(menuData);
// 处理子路由(只处理一级子路由)
if (route.children && route.children.length > 0) {
route.children.forEach((child: any) => {
const childMenus = convertRouteToMenuData(child, null, routePath);
menus.push(...childMenus);
});
}
return menus;
}
/**
* 批量导入 API 菜单
*/
export async function importApiMenus(): Promise<void> {
try {
console.log('开始导入 API 菜单...');
// 1. 先获取已有菜单,用于去重
let existingMenus: any[] = [];
try {
existingMenus = await MenuService.getRbacMenuGetMenuTree();
console.log('获取到已有菜单数量:', existingMenus.length);
} catch (error) {
console.warn('获取已有菜单失败,将继续导入:', error);
}
// 收集已有菜单的 route
const existingRoutes = new Set<string>();
function collectRoutes(menus: any[]) {
menus.forEach(menu => {
if (menu.route) {
existingRoutes.add(menu.route);
}
if (menu.children && menu.children.length > 0) {
collectRoutes(menu.children);
}
});
}
collectRoutes(existingMenus);
// 2. 转换路由配置为菜单数据
const allMenus: MenuCreateDto[] = [];
// 处理 API 主菜单(目录)
const apiMainMenu: MenuCreateDto = {
pId: null,
name: 'API',
type: 1,
permissionCode: null,
icon: 'icon-computer',
route: '/api',
component: null,
order: 1,
isHidden: false,
isCached: false,
description: 'API 管理模块',
};
allMenus.push(apiMainMenu);
// 处理 API 子菜单
if (SYSTEM_ROUTES.children && SYSTEM_ROUTES.children.length > 0) {
SYSTEM_ROUTES.children.forEach((child: any) => {
const childMenus = convertRouteToMenuData(child, null, '/api');
allMenus.push(...childMenus);
});
}
// 3. 去重:过滤掉已存在的菜单
const menusToImport = allMenus.filter(menu => {
if (menu.route && existingRoutes.has(menu.route)) {
console.log(`菜单已存在,跳过: ${menu.name} (${menu.route})`);
return false;
}
return true;
});
if (menusToImport.length === 0) {
console.log('所有菜单已存在,无需导入');
return;
}
console.log(`准备导入 ${menusToImport.length} 个菜单`);
// 4. 先创建父菜单(API 主菜单)
let apiParentId: string | null = null;
const parentMenu = menusToImport.find(m => m.route === '/api' && !m.pId);
if (parentMenu) {
try {
// 检查父菜单是否已存在
const existingParent = existingMenus.find((m: any) => m.route === '/api');
if (existingParent) {
apiParentId = existingParent.id;
console.log(`API 主菜单已存在,ID: ${apiParentId}`);
} else {
const result = await MenuService.postRbacMenuCreate(parentMenu);
if (result.id) {
apiParentId = result.id;
console.log(`✅ 成功创建 API 主菜单,ID: ${apiParentId}`);
}
}
} catch (error: any) {
if (error.status === 400 || error.status === 409) {
// 菜单已存在,尝试查找
const existingParent = existingMenus.find((m: any) => m.route === '/api');
if (existingParent) {
apiParentId = existingParent.id;
console.log(`API 主菜单已存在,ID: ${apiParentId}`);
}
} else {
console.error('创建 API 主菜单失败:', error);
throw error;
}
}
}
// 5. 创建子菜单
const childMenus = menusToImport.filter(m => m.route !== '/api');
let successCount = 0;
let skipCount = 0;
let failCount = 0;
// 按 order 排序
const sortedChildMenus = [...childMenus].sort((a, b) => (a.order || 0) - (b.order || 0));
for (const menu of sortedChildMenus) {
try {
// 设置父菜单ID
if (menu.route?.startsWith('/api/') && apiParentId) {
menu.pId = apiParentId;
}
const result = await MenuService.postRbacMenuCreate(menu);
successCount++;
console.log(`✅ 成功导入菜单: ${menu.name} (${menu.route})`);
} catch (error: any) {
if (error.status === 400 || error.status === 409) {
skipCount++;
console.log(`⚠️ 菜单已存在,跳过: ${menu.name} (${menu.route})`);
} else {
failCount++;
console.error(`❌ 导入菜单失败: ${menu.name} (${menu.route})`, error);
}
}
}
console.log('\n=== 导入完成 ===');
console.log(`成功: ${successCount} 个`);
console.log(`跳过: ${skipCount} 个`);
console.log(`失败: ${failCount} 个`);
console.log(`总计: ${menusToImport.length} 个`);
} catch (error) {
console.error('批量导入菜单失败:', error);
throw error;
}
}
// 如果在浏览器环境中运行
if (typeof window !== 'undefined') {
(window as any).importApiMenus = importApiMenus;
console.log('API 菜单导入函数已挂载到 window.importApiMenus');
console.log('使用方法: await window.importApiMenus()');
}