/** * 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 = { '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 { 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(); 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()'); }