// eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-nocheck // 加载环境变量(必须在其他导入之前) import * as dotenv from 'dotenv'; import * as path from 'path'; // 根据 NODE_ENV 加载对应的环境配置文件 const nodeEnv = process.env.NODE_ENV || 'development'; const envFile = `.env.${nodeEnv}`; // scripts 目录的父目录就是 backend 目录 const backendDir = path.resolve(__dirname, '..'); const envPath = path.resolve(backendDir, envFile); // 尝试加载环境特定的配置文件 dotenv.config({ path: envPath }); // 如果环境特定文件不存在,尝试加载默认的 .env 文件 if (!process.env.DATABASE_URL) { dotenv.config({ path: path.resolve(backendDir, '.env') }); } // 验证必要的环境变量 if (!process.env.DATABASE_URL) { console.error('❌ 错误: 未找到 DATABASE_URL 环境变量'); console.error(` 请确保存在以下文件之一:`); console.error(` - ${envPath}`); console.error(` - ${path.resolve(backendDir, '.env')}`); console.error(` 或者设置 NODE_ENV 环境变量(当前: ${nodeEnv})`); process.exit(1); } import { PrismaClient } from '@prisma/client'; import * as fs from 'fs'; const prisma = new PrismaClient(); // 从 JSON 文件加载菜单数据 const menusFilePath = path.resolve(backendDir, 'data', 'menus.json'); if (!fs.existsSync(menusFilePath)) { console.error(`❌ 错误: 菜单数据文件不存在: ${menusFilePath}`); process.exit(1); } const menus = JSON.parse(fs.readFileSync(menusFilePath, 'utf-8')); // 超级租户可见的菜单名称(工作台只对普通租户可见) const SUPER_TENANT_MENUS = ['我的评审', '活动管理', '系统管理']; // 普通租户可见的菜单名称 const NORMAL_TENANT_MENUS = ['工作台', '学校管理', '我的评审', '作业管理', '系统管理']; // 普通租户在系统管理下排除的子菜单(只保留用户管理和角色管理) const NORMAL_TENANT_EXCLUDED_SYSTEM_MENUS = ['租户管理', '数据字典', '系统配置', '日志记录', '菜单管理', '权限管理']; // 普通租户在我的评审下排除的子菜单(只保留活动列表) const NORMAL_TENANT_EXCLUDED_ACTIVITY_MENUS = ['我的报名', '我的作品']; async function initMenus() { try { console.log('🚀 开始初始化菜单数据...\n'); // 递归创建菜单 async function createMenu(menuData: any, parentId: number | null = null) { const { children, ...menuFields } = menuData; // 查找是否已存在相同名称和父菜单的菜单 const existingMenu = await prisma.menu.findFirst({ where: { name: menuFields.name, parentId: parentId, }, }); let menu; if (existingMenu) { // 更新现有菜单 menu = await prisma.menu.update({ where: { id: existingMenu.id }, data: { name: menuFields.name, path: menuFields.path || null, icon: menuFields.icon || null, component: menuFields.component || null, permission: menuFields.permission || null, parentId: parentId, sort: menuFields.sort || 0, validState: 1, }, }); } else { // 创建新菜单 menu = await prisma.menu.create({ data: { name: menuFields.name, path: menuFields.path || null, icon: menuFields.icon || null, component: menuFields.component || null, permission: menuFields.permission || null, parentId: parentId, sort: menuFields.sort || 0, validState: 1, }, }); } console.log(` ✓ ${menu.name} (${menu.path || '无路径'})`); // 如果有子菜单,递归创建 if (children && children.length > 0) { for (const child of children) { await createMenu(child, menu.id); } } return menu; } // 清空现有菜单(重新初始化) console.log('🗑️ 清空现有菜单和租户菜单关联...'); // 先删除租户菜单关联 await prisma.tenantMenu.deleteMany({}); // 再删除所有子菜单,再删除父菜单(避免外键约束问题) await prisma.menu.deleteMany({ where: { parentId: { not: null, }, }, }); await prisma.menu.deleteMany({ where: { parentId: null, }, }); console.log('✅ 已清空现有菜单\n'); // 创建所有菜单 console.log('📝 创建菜单...\n'); for (const menu of menus) { await createMenu(menu); } // 验证结果 console.log('\n🔍 验证结果...'); const allMenus = await prisma.menu.findMany({ orderBy: [{ sort: 'asc' }, { id: 'asc' }], include: { children: { orderBy: { sort: 'asc', }, }, }, }); const topLevelMenus = allMenus.filter((m) => !m.parentId); const totalMenus = allMenus.length; console.log(`\n📊 初始化结果:`); console.log(` 顶级菜单数量: ${topLevelMenus.length}`); console.log(` 总菜单数量: ${totalMenus}`); console.log(`\n📋 菜单结构:`); function printMenuTree(menu: any, indent: string = '') { console.log(`${indent}├─ ${menu.name} (${menu.path || '无路径'})`); if (menu.children && menu.children.length > 0) { menu.children.forEach((child: any, index: number) => { const isLast = index === menu.children.length - 1; const childIndent = indent + (isLast ? ' ' : '│ '); printMenuTree(child, childIndent); }); } } topLevelMenus.forEach((menu) => { printMenuTree(menu); }); // 为所有现有租户分配菜单(区分超级租户和普通租户) console.log(`\n📋 为所有租户分配菜单...`); const allTenants = await prisma.tenant.findMany({ where: { validState: 1 }, }); if (allTenants.length === 0) { console.log('⚠️ 没有找到任何有效租户,跳过菜单分配\n'); } else { console.log(` 找到 ${allTenants.length} 个租户\n`); // 获取超级租户菜单ID(工作台、我的评审、活动管理、系统管理及其子菜单) const superTenantMenuIds = new Set(); for (const menu of allMenus) { // 顶级菜单 if (!menu.parentId && SUPER_TENANT_MENUS.includes(menu.name)) { superTenantMenuIds.add(menu.id); } // 子菜单(检查父菜单是否在超级租户菜单中) if (menu.parentId) { const parentMenu = allMenus.find(m => m.id === menu.parentId); if (parentMenu && SUPER_TENANT_MENUS.includes(parentMenu.name)) { superTenantMenuIds.add(menu.id); } } } // 获取普通租户菜单ID(工作台、学校管理、我的评审、作业管理、部分系统管理) const normalTenantMenuIds = new Set(); for (const menu of allMenus) { // 顶级菜单 if (!menu.parentId && NORMAL_TENANT_MENUS.includes(menu.name)) { normalTenantMenuIds.add(menu.id); } // 子菜单 if (menu.parentId) { const parentMenu = allMenus.find(m => m.id === menu.parentId); if (parentMenu && NORMAL_TENANT_MENUS.includes(parentMenu.name)) { // 系统管理下排除部分子菜单 if (parentMenu.name === '系统管理' && NORMAL_TENANT_EXCLUDED_SYSTEM_MENUS.includes(menu.name)) { continue; // 跳过排除的菜单 } // 我的评审下排除部分子菜单(只保留活动列表) if (parentMenu.name === '我的评审' && NORMAL_TENANT_EXCLUDED_ACTIVITY_MENUS.includes(menu.name)) { continue; // 跳过排除的菜单 } normalTenantMenuIds.add(menu.id); } } } for (const tenant of allTenants) { const isSuperTenant = tenant.isSuper === 1; // 确定要分配的菜单 const menusToAssign = isSuperTenant ? allMenus.filter(m => superTenantMenuIds.has(m.id)) : allMenus.filter(m => normalTenantMenuIds.has(m.id)); // 为租户分配菜单 let addedMenuCount = 0; for (const menu of menusToAssign) { await prisma.tenantMenu.create({ data: { tenantId: tenant.id, menuId: menu.id, }, }); addedMenuCount++; } const tenantType = isSuperTenant ? '(超级租户)' : '(普通租户)'; console.log( ` ✓ 租户 "${tenant.name}" ${tenantType}: 分配了 ${addedMenuCount} 个菜单`, ); } console.log(`\n✅ 菜单分配完成!`); } console.log(`\n✅ 菜单初始化完成!`); } catch (error) { console.error('\n💥 初始化菜单失败:', error); throw error; } finally { await prisma.$disconnect(); } } // 执行初始化 initMenus() .then(() => { console.log('\n🎉 菜单初始化脚本执行完成!'); process.exit(0); }) .catch((error) => { console.error('\n💥 菜单初始化脚本执行失败:', error); process.exit(1); });