front/scripts/add-menus.js

452 lines
16 KiB
JavaScript
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添加到菜单管理列表
*/
const fs = require('fs');
const path = require('path');
const axios = require('axios');
// 配置API基础URL(需要根据实际情况修改)
const API_BASE_URL = process.env.API_BASE_URL || 'http://localhost:5173';
const API_PREFIX = '/api';
// 标准按钮配置
const STANDARD_BUTTONS = [
{ title: '新增', code: 'add', sortCode: 1 },
{ title: '编辑', code: 'edit', sortCode: 2 },
{ title: '删除', code: 'delete', sortCode: 3 },
{ title: '查询', code: 'query', sortCode: 4 },
{ title: '导入', code: 'import', sortCode: 5 },
{ title: '导出', code: 'export', sortCode: 6 },
];
// 读取路由配置文件
function readRouteFiles() {
const routesDir = path.join(__dirname, '../src/router/routes/newModules');
const files = fs.readdirSync(routesDir);
const routes = [];
files.forEach(file => {
if (file.endsWith('.ts')) {
try {
const filePath = path.join(routesDir, file);
const content = fs.readFileSync(filePath, 'utf-8');
// 简单的解析路由配置(实际应该使用TypeScript解析器)
// 这里使用正则表达式提取基本信息
const routeMatch = content.match(/const\s+(\w+):\s*AppRouteRecordRaw\s*=\s*\{([\s\S]*?)\};/);
if (routeMatch) {
const routeContent = routeMatch[2];
// 提取path
const pathMatch = routeContent.match(/path:\s*['"]([^'"]+)['"]/);
// 提取name
const nameMatch = routeContent.match(/name:\s*['"]([^'"]+)['"]/);
// 提取locale
const localeMatch = routeContent.match(/locale:\s*['"]([^'"]+)['"]/);
// 提取icon
const iconMatch = routeContent.match(/icon:\s*['"]([^'"]+)['"]/);
// 提取order
const orderMatch = routeContent.match(/order:\s*(\d+)/);
if (pathMatch && nameMatch) {
const mainMenu = {
path: pathMatch[1],
name: nameMatch[1],
title: localeMatch ? localeMatch[1] : nameMatch[1],
icon: iconMatch ? iconMatch[1] : '',
order: orderMatch ? parseInt(orderMatch[1]) : 999,
children: []
};
// 提取children
const childrenMatch = routeContent.match(/children:\s*\[([\s\S]*?)\]/);
if (childrenMatch) {
const childrenContent = childrenMatch[1];
// 使用正则表达式提取每个子路由
const childMatches = childrenContent.matchAll(/path:\s*['"]([^'"]+)['"][\s\S]*?name:\s*['"]([^'"]+)['"][\s\S]*?locale:\s*['"]([^'"]+)['"]/g);
for (const childMatch of childMatches) {
const childPath = childMatch[1];
const childName = childMatch[2];
const childLocale = childMatch[3];
// 检查是否隐藏
const hideMatch = childrenContent.match(new RegExp(`name:\\s*['"]${childName}['"][\\s\\S]*?hideInMenu:\\s*(true|false)`));
const isHide = hideMatch && hideMatch[1] === 'true';
if (!isHide) {
mainMenu.children.push({
path: childPath.startsWith('/') ? childPath : `${mainMenu.path}/${childPath}`,
name: childName,
title: childLocale
});
}
}
}
routes.push(mainMenu);
}
}
} catch (error) {
console.error(`解析文件 ${file} 时出错:`, error.message);
}
}
});
return routes.sort((a, b) => a.order - b.order);
}
// 解析路由配置(更准确的方法)
function parseRouteConfig() {
const routesDir = path.join(__dirname, '../src/router/routes/newModules');
const routes = [];
// 手动定义路由结构(因为TypeScript文件需要编译才能解析)
const routeConfig = [
{
path: '/system',
name: 'system',
title: 'API',
icon: 'icon-computer',
order: 1,
children: [
{ path: 'interface', name: 'SystemInterface', title: '上游接口' },
{ path: 'serviceProvidersList', name: 'ServiceProvidersList', title: '下游接口' },
{ path: '/system/newList/adhibition-list', name: 'AdhibitionList', title: 'App列表' },
{ path: 'log-record', name: 'LogRecord', title: '请求日志' },
{ path: '/system/applicationStatistics', name: 'ApplicationStatistics', title: '调用统计' },
{ path: 'api-debugger', name: 'ApiDebugger', title: 'API调试' },
]
},
{
path: '/trajectoryCenter',
name: 'TrajectoryCenter',
title: '轨迹',
icon: 'icon-computer',
order: 2,
children: [
{ path: 'trajectoryOrder', name: 'TrajectoryOrder', title: '轨迹订单', order: 1 },
{ path: 'trajectoryWarningOrder', name: 'TrajectoryWarningOrder', title: '预警订单', order: 2 },
{ path: 'trajectoryState', name: 'TrajectoryState', title: '轨迹状态', order: 3 },
{ path: 'trajectoryFlag', name: 'TrajectoryFlag', title: '轨迹标记', order: 4 },
{ path: 'trajectoryContent', name: 'TrajectoryContent', title: '轨迹内容', order: 5 },
{ path: 'trajectoryPull', name: 'TrajectoryPull', title: '轨迹拉取', order: 6 },
{ path: 'trajectoryPush', name: 'TrajectoryPush', title: '轨迹推送', order: 7 },
{ path: 'kpiRegion', name: 'KpiRegion', title: '预警分区', order: 8 },
{ path: 'kpiEarlyWarningConfig', name: 'KpiEarlyWarningConfig', title: '预警配置', order: 9 },
]
},
{
path: '/order',
name: 'Order',
title: '订单',
icon: 'icon-file',
order: 3,
children: [
{ path: 'price-check', name: 'PriceCheck', title: '我要查价' },
{ path: 'place-order', name: 'PlaceOrder', title: '我要下单' },
{ path: 'my-orders', name: 'MyOrders', title: '我的订单' },
]
},
{
path: '/channel',
name: 'Channel',
title: '渠道',
icon: 'icon-link',
order: 4,
children: [
{ path: 'sales-products', name: 'SalesProducts', title: '销售产品' },
{ path: 'service-channels', name: 'ServiceChannels', title: '服务渠道' },
{ path: 'blacklist', name: 'Blacklist', title: '黑名单区' },
{ path: 'remote-zones', name: 'RemoteZones', title: '偏远分区' },
{ path: 'weight-rules', name: 'WeightRules', title: '计重规则' },
{ path: 'weight-rounding', name: 'WeightRounding', title: '计重进位' },
{ path: 'auto-trigger', name: 'AutoTrigger', title: '自动触发' },
]
},
{
path: '/organization',
name: 'organization',
title: '系统',
icon: 'icon-settings',
order: 999,
children: [
{ path: 'org', name: 'OrganizationOrg', title: '组织管理' },
{ path: 'user', name: 'OrganizationUser', title: '用户管理' },
{ path: 'position', name: 'OrganizationPosition', title: '职位管理' },
{ path: 'role', name: 'OrganizationRole', title: '角色管理' },
{ path: 'module', name: 'OrganizationModule', title: '模块管理' },
{ path: 'menu', name: 'OrganizationMenu', title: '菜单管理' },
{ path: 'profile', name: 'OrganizationProfile', title: '个人中心' },
]
},
];
return routeConfig;
}
// 创建axios实例
function createAxiosInstance(token) {
return axios.create({
baseURL: API_BASE_URL + API_PREFIX,
headers: {
'Content-Type': 'application/json',
'Authorization': token ? `Bearer ${token}` : '',
},
timeout: 30000,
});
}
// 获取模块列表
async function getModuleList(api) {
try {
const response = await api.get('/sys/limit/module/list');
if (response.data && response.data.data) {
const modules = Array.isArray(response.data.data) ? response.data.data : [];
return modules.length > 0 ? modules[0].id : 0;
}
return 0;
} catch (error) {
console.error('获取模块列表失败:', error.message);
return 0;
}
}
// 获取菜单树(用于检查菜单是否已存在)
async function getMenuTree(api, moduleId) {
try {
const response = await api.get('/sys/limit/menu/tree', {
params: { Module: moduleId }
});
if (response.data && response.data.data) {
return Array.isArray(response.data.data) ? response.data.data : [];
}
return [];
} catch (error) {
console.error('获取菜单树失败:', error.message);
return [];
}
}
// 检查菜单是否存在
function findMenuInTree(tree, code) {
for (const item of tree) {
if (item.code === code) {
return item.id;
}
if (item.children && item.children.length > 0) {
const found = findMenuInTree(item.children, code);
if (found) return found;
}
}
return null;
}
// 生成菜单编码
function generateMenuCode(path, name) {
// 使用路径和名称生成编码
const parts = path.split('/').filter(p => p);
if (parts.length > 0) {
return parts.join('_').replace(/-/g, '_');
}
return name.replace(/([A-Z])/g, '_$1').toLowerCase().replace(/^_/, '');
}
// 添加菜单
async function addMenu(api, menuData, moduleId) {
try {
const response = await api.post('/sys/limit/menu/add', menuData);
if (response.data && response.data.code === 200) {
return response.data.data;
}
throw new Error(response.data?.message || '添加菜单失败');
} catch (error) {
if (error.response && error.response.data) {
throw new Error(error.response.data.message || error.message);
}
throw error;
}
}
// 添加按钮
async function addButton(api, buttonData) {
try {
const response = await api.post('/sys/limit/button/add', buttonData);
if (response.data && response.data.code === 200) {
return response.data.data;
}
throw new Error(response.data?.message || '添加按钮失败');
} catch (error) {
if (error.response && error.response.data) {
throw new Error(error.response.data.message || error.message);
}
throw error;
}
}
// 主函数
async function main() {
console.log('开始添加菜单和按钮...\n');
// 获取token(如果需要)
const token = process.env.AUTH_TOKEN || '';
const api = createAxiosInstance(token);
// 获取模块ID
console.log('正在获取模块列表...');
const moduleId = await getModuleList(api);
if (moduleId === 0) {
console.warn('警告: 未找到模块,将使用模块ID 0');
} else {
console.log(`使用模块ID: ${moduleId}\n`);
}
// 获取现有菜单树
console.log('正在获取现有菜单树...');
const existingMenuTree = await getMenuTree(api, moduleId);
console.log(`已找到 ${existingMenuTree.length} 个现有菜单\n`);
// 解析路由配置
const routes = parseRouteConfig();
// 存储已创建的菜单ID映射
const menuIdMap = new Map();
// 添加主菜单和子菜单
for (const route of routes) {
console.log(`处理主菜单: ${route.title} (${route.path})`);
// 检查主菜单是否已存在
const mainMenuCode = generateMenuCode(route.path, route.name);
let mainMenuId = findMenuInTree(existingMenuTree, mainMenuCode);
if (!mainMenuId) {
// 添加主菜单
try {
const mainMenuData = {
title: route.title,
code: mainMenuCode,
menuType: 'CATALOG', // 目录类型
parentId: 0,
path: route.path,
component: '',
icon: route.icon || '',
sortCode: route.order || 999,
status: 'ENABLE',
isHide: false,
description: `主菜单: ${route.title}`,
module: moduleId,
};
console.log(` 添加主菜单: ${route.title}`);
const result = await addMenu(api, mainMenuData, moduleId);
mainMenuId = result?.id || result;
console.log(` ✓ 主菜单添加成功,ID: ${mainMenuId}`);
} catch (error) {
console.error(` ✗ 主菜单添加失败: ${error.message}`);
continue;
}
} else {
console.log(` 主菜单已存在,ID: ${mainMenuId}`);
}
menuIdMap.set(route.path, mainMenuId);
// 添加子菜单
if (route.children && route.children.length > 0) {
// 按order排序子菜单
const sortedChildren = route.children.sort((a, b) => (a.order || 999) - (b.order || 999));
for (const child of sortedChildren) {
const childMenuCode = generateMenuCode(child.path, child.name);
let childMenuId = findMenuInTree(existingMenuTree, childMenuCode);
if (!childMenuId) {
try {
// 提取组件路径
let component = '';
if (child.path.startsWith('/')) {
// 如果路径以/开头,可能是完整路径
component = `views${child.path}/index.vue`;
} else {
component = `views${route.path}/${child.path}/index.vue`;
}
const childMenuData = {
title: child.title,
code: childMenuCode,
menuType: 'MENU', // 菜单类型
parentId: mainMenuId,
path: child.path.startsWith('/') ? child.path : `${route.path}/${child.path}`,
component: component,
icon: '',
sortCode: child.order || 999,
status: 'ENABLE',
isHide: false,
description: `子菜单: ${child.title}`,
module: moduleId,
};
console.log(` 添加子菜单: ${child.title}`);
const result = await addMenu(api, childMenuData, moduleId);
childMenuId = result?.id || result;
console.log(` ✓ 子菜单添加成功,ID: ${childMenuId}`);
// 重新获取菜单树以包含新添加的菜单
const updatedTree = await getMenuTree(api, moduleId);
existingMenuTree.length = 0;
existingMenuTree.push(...updatedTree);
} catch (error) {
console.error(` ✗ 子菜单添加失败: ${error.message}`);
continue;
}
} else {
console.log(` 子菜单已存在,ID: ${childMenuId}`);
}
// 为子菜单添加标准按钮
console.log(` 为子菜单添加标准按钮...`);
for (const button of STANDARD_BUTTONS) {
try {
const buttonCode = `${childMenuCode}_${button.code}`;
const buttonData = {
title: button.title,
code: buttonCode,
parentId: childMenuId,
sortCode: button.sortCode,
description: `${child.title} - ${button.title}`,
};
await addButton(api, buttonData);
console.log(` ✓ 按钮 "${button.title}" 添加成功`);
} catch (error) {
// 如果按钮已存在,忽略错误
if (error.message.includes('已存在') || error.message.includes('exist')) {
console.log(` - 按钮 "${button.title}" 已存在,跳过`);
} else {
console.error(` ✗ 按钮 "${button.title}" 添加失败: ${error.message}`);
}
}
}
}
}
console.log('');
}
console.log('\n菜单和按钮添加完成!');
}
// 运行主函数
if (require.main === module) {
main().catch(error => {
console.error('执行失败:', error);
process.exit(1);
});
}
module.exports = { main, parseRouteConfig };