| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635 |
- // 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<number, number>();
- const inserted = new Set<number>();
- // 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);
- });
|