// prisma/mysql/seed.ts import { PrismaClient, MenuType } from '@prisma/mysql/client'; import type { Prisma } from '@prisma/mysql/client'; const prisma = new PrismaClient(); // ============================================================================= // MENU SEEDS DATA // ============================================================================= interface SeedMenu { legacyId: number; legacyParentId: number | null; title: string; type: MenuType; name: string; path: string; icon: string | null; component_key?: string | null; order: number; meta?: Prisma.JsonValue; } const MENU_SEEDS: SeedMenu[] = [ // ====================== // 营销中心 (Marketing) // ====================== { legacyId: 1, legacyParentId: null, title: '营销中心', type: 'DIRECTORY', name: 'marketingRoot', path: '/marketing', icon: 'i-ri:megaphone-line', order: 100, meta: { title: '营销中心', i18n: 'route.marketing.root', icon: 'i-ri:megaphone-line', }, }, { legacyId: 2, legacyParentId: 1, title: '视频管理', type: 'MENU', name: 'marketingVideos', path: '/marketing/videos', icon: 'i-ri:video-line', order: 1, meta: { title: '视频管理', i18n: 'route.marketing.videos', icon: 'i-ri:video-line', }, }, { legacyId: 3, legacyParentId: 1, title: '分类管理', type: 'MENU', name: 'marketingCategories', path: '/marketing/categories', icon: 'i-ri:folder-3-line', order: 2, meta: { title: '分类管理', i18n: 'route.marketing.categories', icon: 'i-ri:folder-3-line', }, }, { legacyId: 4, legacyParentId: 1, title: '标签管理', type: 'MENU', name: 'marketingTags', path: '/marketing/tags', icon: 'i-ri:price-tag-3-line', order: 3, meta: { title: '标签管理', i18n: 'route.marketing.tags', icon: 'i-ri:price-tag-3-line', }, }, { legacyId: 5, legacyParentId: 1, title: '广告管理', type: 'MENU', name: 'marketingAds', path: '/marketing/ads', icon: 'i-ri:advertisement-line', order: 4, meta: { title: '广告管理', i18n: 'route.marketing.ads', icon: 'i-ri:advertisement-line', }, }, { legacyId: 6, legacyParentId: 1, title: '参数管理', type: 'MENU', name: 'marketingParams', path: '/marketing/params', icon: 'i-ri:settings-3-line', order: 5, meta: { title: '参数管理', i18n: 'route.marketing.params', icon: 'i-ri:settings-3-line', }, }, { legacyId: 7, legacyParentId: 1, title: '渠道管理', type: 'MENU', name: 'marketingChannels', path: '/marketing/channels', icon: 'i-ri:settings-3-line', order: 6, meta: { title: '渠道管理', i18n: 'route.marketing.channels', icon: 'i-ri:settings-3-line', }, }, // ====================== // 数据中心 (Data Center) // ====================== { legacyId: 8, legacyParentId: null, title: '数据中心', type: 'DIRECTORY', name: 'dataCenterRoot', path: '/data', icon: 'i-ri:database-2-line', order: 200, meta: { title: '数据中心', i18n: 'route.data.root', icon: 'i-ri:database-2-line', }, }, { legacyId: 9, legacyParentId: 8, title: 'APP访问记录', type: 'MENU', name: 'dataAppAccessLogs', path: '/data/app-access-logs', icon: 'i-ri:smartphone-line', order: 1, meta: { title: 'APP访问记录', i18n: 'route.data.appAccessLogs', icon: 'i-ri:smartphone-line', }, }, { legacyId: 10, legacyParentId: 8, title: '广告点击记录', type: 'MENU', name: 'dataAdsClickLogs', path: '/data/ads-click-logs', icon: 'i-ri:cursor-line', order: 2, meta: { title: '广告点击记录', i18n: 'route.data.adsClickLogs', icon: 'i-ri:cursor-line', }, }, // ====================== // 统计中心 (Stats Center) // ====================== { legacyId: 11, legacyParentId: null, title: '统计中心', type: 'DIRECTORY', name: 'statsCenterRoot', path: '/stats', icon: 'i-ri:line-chart-line', order: 300, meta: { title: '统计中心', i18n: 'route.stats.root', icon: 'i-ri:line-chart-line', }, }, { legacyId: 12, legacyParentId: 11, title: '每日统计', type: 'MENU', name: 'statsDaily', path: '/stats/daily', icon: 'i-ri:calendar-line', order: 1, meta: { title: '每日统计', i18n: 'route.stats.daily', icon: 'i-ri:calendar-line', }, }, { legacyId: 13, legacyParentId: 11, title: '广告统计', type: 'MENU', name: 'statsAds', path: '/stats/ads', icon: 'i-ri:bar-chart-box-line', order: 2, meta: { title: '广告统计', i18n: 'route.stats.ads', icon: 'i-ri:bar-chart-box-line', }, }, { legacyId: 14, legacyParentId: 11, title: '广告汇总', type: 'MENU', name: 'statsAdsSummary', path: '/stats/ads-summary', icon: 'i-ri:pie-chart-line', order: 3, meta: { title: '广告汇总', i18n: 'route.stats.adsSummary', icon: 'i-ri:pie-chart-line', }, }, { legacyId: 15, legacyParentId: 11, title: '网站统计', type: 'MENU', name: 'statsSite', path: '/stats/site', icon: 'i-ri:global-line', order: 4, meta: { title: '网站统计', i18n: 'route.stats.site', icon: 'i-ri:global-line', }, }, // ====================== // 系统管理 (System) // ====================== { legacyId: 16, legacyParentId: null, title: '系统管理', type: 'DIRECTORY', name: 'systemRoot', path: '/system', icon: 'i-ri:settings-4-line', order: 400, meta: { title: '系统管理', i18n: 'route.system.root', icon: 'i-ri:settings-4-line', }, }, { legacyId: 17, legacyParentId: 16, title: '系统用户', type: 'MENU', name: 'systemUsers', path: '/system/users', icon: 'i-ri:user-line', component_key: '@/views/system/users/list.vue', order: 1, meta: { title: '系统用户', i18n: 'route.system.users', icon: 'i-ri:user-line', }, }, { legacyId: 18, legacyParentId: 16, title: '角色列表', type: 'MENU', name: 'systemRoles', path: '/system/roles', icon: 'i-ri:shield-user-line', order: 20, meta: { title: '角色列表', i18n: 'route.system.roles', icon: 'i-ri:shield-user-line', }, }, { legacyId: 19, legacyParentId: 16, title: '菜单管理', type: 'MENU', name: 'systemMenus', path: '/system/menus', icon: 'i-ri:menu-line', order: 30, meta: { title: '菜单管理', i18n: 'route.system.menus', icon: 'i-ri:menu-line', }, }, { legacyId: 20, legacyParentId: 16, title: '操作日志', type: 'MENU', name: 'systemOperationLogs', path: '/system/operation-logs', icon: 'i-ri:time-line', order: 40, meta: { title: '操作日志', i18n: 'route.system.operationLogs', icon: 'i-ri:time-line', }, }, // ====================== // SUBMENUs under Roles // ====================== { legacyId: 21, legacyParentId: 18, title: '角色列表', type: 'SUBMENU', name: 'systemRoleList', path: '/system/roles/list', icon: 'i-ri:shield-user-line', component_key: '@/views/system/roles/list.vue', order: 1, meta: { title: '角色列表', i18n: 'route.system.role.list', sidebar: false, breadcrumb: false, cache: ['systemRoleCreate', 'systemRoleEdit'], }, }, { legacyId: 22, legacyParentId: 18, title: '新增角色', type: 'SUBMENU', name: 'systemRoleCreate', path: '/system/roles/create', icon: 'i-ri:add-circle-line', component_key: '@/views/system/roles/detail.vue', order: 2, meta: { title: '新增角色', i18n: 'route.system.role.create', sidebar: false, cache: true, activeMenu: '/system/roles', noCache: 'systemRoleList', }, }, { legacyId: 23, legacyParentId: 18, title: '编辑角色', type: 'SUBMENU', name: 'systemRoleEdit', path: '/system/roles/:id/edit', icon: 'i-ri:pencil-line', component_key: '@/views/system/roles/detail.vue', order: 3, meta: { title: '编辑角色', i18n: 'route.system.role.edit', sidebar: false, cache: true, activeMenu: '/system/roles', noCache: 'systemRoleList', }, }, // ====================== // SUBMENUs under Menus // ====================== { legacyId: 24, legacyParentId: 19, title: '菜单列表', type: 'SUBMENU', name: 'systemMenuList', path: '/system/menus/list', icon: 'i-ri:menu-line', component_key: '@/views/system/menus/list.vue', order: 1, meta: { title: '菜单列表', i18n: 'route.system.menu.list', sidebar: false, breadcrumb: false, cache: ['systemMenuCreate', 'systemMenuEdit'], }, }, { legacyId: 25, legacyParentId: 19, title: '新增菜单', type: 'SUBMENU', name: 'systemMenuCreate', path: '/system/menus/create', icon: 'i-ri:add-circle-line', component_key: '@/views/system/menus/detail.vue', order: 2, meta: { title: '新增菜单', i18n: 'route.system.menu.create', sidebar: false, cache: true, activeMenu: '/system/menus', noCache: 'systemMenuList', }, }, { legacyId: 26, legacyParentId: 19, title: '编辑菜单', type: 'SUBMENU', name: 'systemMenuEdit', path: '/system/menus/:id/edit', icon: 'i-ri:pencil-line', component_key: '@/views/system/menus/detail.vue', order: 3, meta: { title: '编辑菜单', i18n: 'route.system.menu.edit', sidebar: false, cache: true, activeMenu: '/system/menus', noCache: 'systemMenuList', }, }, ]; // ============================================================================= // HELPER FUNCTIONS // ============================================================================= /** * Get default action permissions based on menu type */ function getDefaultPermissions(type: MenuType) { switch (type) { case 'DIRECTORY': return { canView: 1, canCreate: 0, canUpdate: 0, canDelete: 0 }; case 'MENU': case 'SUBMENU': return { canView: 1, canCreate: 1, canUpdate: 1, canDelete: 1 }; case 'BUTTON': return { canView: 0, canCreate: 1, canUpdate: 0, canDelete: 0 }; default: return { canView: 0, canCreate: 0, canUpdate: 0, canDelete: 0 }; } } // ============================================================================= // SEED FUNCTIONS // ============================================================================= /** * Seed users and roles */ async function seedUsers() { console.log('📝 Seeding users and roles...'); const role = await prisma.role.create({ data: { name: '管理员', remark: '管理员专用', }, }); const adminUser = await prisma.user.create({ data: { username: 'admin', password: '$2b$12$iS0UJ1YqSal0N3uwin/OvOABUINAclcZGjHNyGFC7mlwRYTFjGQ26', remark: '默认拥有所有菜单权限,不需要配置角色', }, }); await prisma.userRole.create({ data: { userId: adminUser.id, roleId: role.id, }, }); console.log('✅ Users and roles seeded successfully'); console.log(` - Admin user: ${adminUser.username}`); console.log(` - Role: ${role.name}`); } /** * Seed menus with multi-pass insertion to handle parent-child relationships */ async function seedMenus() { console.log('📝 Seeding menus...'); const legacyIdToNewId = new Map(); const inserted = new Set(); // Multi-pass insertion: insert records whose parent is either null or already inserted while (inserted.size < MENU_SEEDS.length) { let progress = false; for (const seed of MENU_SEEDS) { if (inserted.has(seed.legacyId)) { continue; } // If has a parent, but parent not inserted yet → skip this round if ( seed.legacyParentId !== null && !legacyIdToNewId.has(seed.legacyParentId) ) { continue; } const parentId = seed.legacyParentId === null ? null : legacyIdToNewId.get(seed.legacyParentId)!; const permissions = getDefaultPermissions(seed.type); const created = await prisma.menu.create({ data: { parentId, title: seed.title, status: true, type: seed.type, order: seed.order, // Use path as frontendAuth for RBAC tracking frontendAuth: seed.path, path: seed.path, name: seed.name, icon: seed.icon, redirect: null, component_key: seed.component_key ?? null, meta: seed.meta ?? undefined, // Set default permissions based on type ...permissions, }, }); legacyIdToNewId.set(seed.legacyId, created.id); inserted.add(seed.legacyId); progress = true; console.log( ` ✓ Menu [${seed.type.padEnd(9)}] ${seed.name.padEnd(25)} → ID: ${created.id}`, ); } if (!progress) { // Nothing could be inserted in this pass → some parentId is invalid or cyclic const pending = MENU_SEEDS.filter((s) => !inserted.has(s.legacyId)).map( (s) => ({ legacyId: s.legacyId, legacyParentId: s.legacyParentId, name: s.name, }), ); console.error('❌ Could not resolve parents for menus:', pending); throw new Error( 'Menu seeding aborted: unresolved parent relationships. Check legacyParentId values.', ); } } console.log('✅ Menus seeded successfully'); console.log(` - Total menus: ${inserted.size}`); } // ============================================================================= // MAIN EXECUTION // ============================================================================= async function main() { console.log('🌱 Starting database seeding...\n'); try { // Seed in order: users first, then menus await seedUsers(); console.log(''); await seedMenus(); console.log('\n🎉 Database seeding completed successfully!'); } catch (error) { console.error('\n❌ Error during seeding:', error); throw error; } } main() .then(async () => { await prisma.$disconnect(); }) .catch(async (e) => { console.error(e); await prisma.$disconnect(); process.exit(1); });