Преглед изворни кода

feat(prisma): add seed script for menu data

Dave пре 1 месец
родитељ
комит
69313e2a8b
2 измењених фајлова са 1114 додато и 1 уклоњено
  1. 2 1
      package.json
  2. 1112 0
      prisma/mongo/seed-menus.ts

+ 2 - 1
package.json

@@ -18,6 +18,7 @@
     "prisma:generate:box-stats": "dotenv -e .env -- prisma generate --schema=prisma/mongo-stats/schema",
     "prisma:generate": "pnpm prisma:generate:box-admin && pnpm prisma:generate:box-stats",
     "prisma:seed:box-admin:admin": "dotenv -e .env -- ts-node -P tsconfig.seed.json prisma/mongo/seed-admin.ts",
+    "prisma:seed:box-admin:menus": "dotenv -e .env -- ts-node -P tsconfig.seed.json prisma/mongo/seed-menus.ts",
     "prisma:seed:box-admin": "dotenv -e .env -- ts-node -P tsconfig.seed.json prisma/mongo/seed.ts",
     "prisma:seed:box-stats": "dotenv -e .env -- ts-node -P tsconfig.seed.json prisma/mongo-stats/seed.ts",
     "prisma:seed:box-admin:ads": "dotenv -e .env -- ts-node -T prisma/mongo/seed-ads.ts",
@@ -128,4 +129,4 @@
     "tsx": "^4.20.6",
     "typescript": "^5.4.5"
   }
-}
+}

+ 1112 - 0
prisma/mongo/seed-menus.ts

@@ -0,0 +1,1112 @@
+// prisma/mongo/seed-admin.ts
+import { PrismaClient, MenuType, AdType } from '@prisma/mongo/client';
+import type { Prisma } from '@prisma/mongo/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;
+  componentKey?: string | null;
+  order: number;
+  meta?: Prisma.JsonValue;
+}
+
+const MENU_SEEDS: SeedMenu[] = [
+  // ======================
+  // 营销中心 (Marketing)
+  // ======================
+  {
+    legacyId: 1,
+    legacyParentId: null,
+    title: '营销中心',
+    type: 'DIRECTORY',
+    name: 'Marketing Management',
+    path: '/marketing',
+    icon: 'i-carbon:application-web',
+    order: 100,
+    meta: {
+      title: '营销中心',
+      i18n: 'route.general.root',
+      icon: 'i-carbon:application-web',
+    },
+  },
+  {
+    legacyId: 2,
+    legacyParentId: 1,
+    title: '视频管理',
+    type: 'MENU',
+    name: 'videoList',
+    path: '/marketing/video',
+    icon: 'i-carbon:video',
+    order: 1,
+    meta: {
+      title: '视频管理',
+      i18n: 'route.general.video.root',
+      icon: 'i-carbon:video',
+    },
+  },
+  {
+    legacyId: 3,
+    legacyParentId: 2,
+    title: '视频列表',
+    type: 'SUBMENU',
+    name: 'videoList',
+    path: '/marketing/videoList',
+    icon: null,
+    componentKey: '@/views/marketing_center/video_mgnt/list.vue',
+    order: 1,
+    meta: {
+      title: '视频列表',
+      i18n: 'route.general.video.list',
+      sidebar: false,
+      breadcrumb: false,
+      cache: ['videoCreate', 'videoEdit'],
+    },
+  },
+  {
+    legacyId: 4,
+    legacyParentId: 2,
+    title: '新增视频',
+    type: 'SUBMENU',
+    name: 'videoCreate',
+    path: '/marketing/video/detail',
+    icon: null,
+    componentKey: '@/views/marketing_center/video_mgnt/detail.vue',
+    order: 2,
+    meta: {
+      title: '新增视频',
+      i18n: 'route.general.video.create',
+      sidebar: false,
+      activeMenu: '/marketing/videoList',
+      cache: true,
+      noCache: 'videoList',
+    },
+  },
+  {
+    legacyId: 5,
+    legacyParentId: 2,
+    title: '编辑视频',
+    type: 'SUBMENU',
+    name: 'videoEdit',
+    path: '/marketing/video/detail/:id',
+    icon: null,
+    componentKey: '@/views/marketing_center/video_mgnt/detail.vue',
+    order: 3,
+    meta: {
+      title: '编辑视频',
+      i18n: 'route.general.video.edit',
+      sidebar: false,
+      activeMenu: '/marketing/videoList',
+      cache: true,
+      noCache: 'videoList',
+    },
+  },
+  {
+    legacyId: 6,
+    legacyParentId: 1,
+    title: '分类管理',
+    type: 'MENU',
+    name: 'categoryList',
+    path: '/marketing/category',
+    icon: 'i-carbon:category',
+    order: 2,
+    meta: {
+      title: '分类管理',
+      i18n: 'route.general.category.root',
+      icon: 'i-carbon:category',
+    },
+  },
+  {
+    legacyId: 7,
+    legacyParentId: 6,
+    title: '分类列表',
+    type: 'SUBMENU',
+    name: 'categoryList',
+    path: '/marketing/categoryList',
+    icon: null,
+    componentKey: '@/views/marketing_center/category_mgnt/list.vue',
+    order: 1,
+    meta: {
+      title: '分类列表',
+      i18n: 'route.general.category.list',
+      sidebar: false,
+      breadcrumb: false,
+      cache: ['categoryCreate', 'categoryEdit'],
+    },
+  },
+  {
+    legacyId: 8,
+    legacyParentId: 6,
+    title: '新增分类',
+    type: 'SUBMENU',
+    name: 'categoryCreate',
+    path: '/marketing/category/detail',
+    icon: null,
+    componentKey: '@/views/marketing_center/category_mgnt/detail.vue',
+    order: 2,
+    meta: {
+      title: '新增分类',
+      i18n: 'route.general.category.create',
+      sidebar: false,
+      activeMenu: '/marketing/categoryList',
+      cache: true,
+      noCache: 'categoryList',
+    },
+  },
+  {
+    legacyId: 9,
+    legacyParentId: 6,
+    title: '编辑分类',
+    type: 'SUBMENU',
+    name: 'categoryEdit',
+    path: '/marketing/category/detail/:id',
+    icon: null,
+    componentKey: '@/views/marketing_center/category_mgnt/detail.vue',
+    order: 3,
+    meta: {
+      title: '编辑分类',
+      i18n: 'route.general.category.edit',
+      sidebar: false,
+      activeMenu: '/marketing/categoryList',
+      cache: true,
+      noCache: 'categoryList',
+    },
+  },
+  {
+    legacyId: 10,
+    legacyParentId: 1,
+    title: '标签管理',
+    type: 'MENU',
+    name: 'tagList',
+    path: '/marketing/tag',
+    icon: 'i-carbon:tag',
+    order: 3,
+    meta: {
+      title: '标签管理',
+      i18n: 'route.general.tag.root',
+      icon: 'i-carbon:tag',
+    },
+  },
+  {
+    legacyId: 11,
+    legacyParentId: 10,
+    title: '标签列表',
+    type: 'SUBMENU',
+    name: 'tagList',
+    path: '/marketing/tagList',
+    icon: null,
+    componentKey: '@/views/marketing_center/tag_mgnt/list.vue',
+    order: 1,
+    meta: {
+      title: '标签列表',
+      i18n: 'route.general.tag.list',
+      sidebar: false,
+      breadcrumb: false,
+      cache: ['tagCreate', 'tagEdit'],
+    },
+  },
+  {
+    legacyId: 12,
+    legacyParentId: 10,
+    title: '新增标签',
+    type: 'SUBMENU',
+    name: 'tagCreate',
+    path: '/marketing/tag/detail',
+    icon: null,
+    componentKey: '@/views/marketing_center/tag_mgnt/detail.vue',
+    order: 2,
+    meta: {
+      title: '新增标签',
+      i18n: 'route.general.tag.create',
+      sidebar: false,
+      activeMenu: '/marketing/tagList',
+      cache: true,
+      noCache: 'tagList',
+    },
+  },
+  {
+    legacyId: 13,
+    legacyParentId: 10,
+    title: '编辑标签',
+    type: 'SUBMENU',
+    name: 'tagEdit',
+    path: '/marketing/tag/detail/:id',
+    icon: null,
+    componentKey: '@/views/marketing_center/tag_mgnt/detail.vue',
+    order: 3,
+    meta: {
+      title: '编辑标签',
+      i18n: 'route.general.tag.edit',
+      sidebar: false,
+      activeMenu: '/marketing/tagList',
+      cache: true,
+      noCache: 'tagList',
+    },
+  },
+  {
+    legacyId: 14,
+    legacyParentId: 1,
+    title: '广告管理',
+    type: 'MENU',
+    name: 'adsList',
+    path: '/marketing/ads',
+    icon: 'i-carbon:image-copy',
+    order: 4,
+    meta: {
+      title: '广告管理',
+      i18n: 'route.general.ads.root',
+      icon: 'i-carbon:image-copy',
+    },
+  },
+  {
+    legacyId: 15,
+    legacyParentId: 14,
+    title: '广告列表',
+    type: 'SUBMENU',
+    name: 'adsList',
+    path: '/marketing/adsList',
+    icon: null,
+    componentKey: '@/views/marketing_center/ads_mgnt/list.vue',
+    order: 1,
+    meta: {
+      title: '广告列表',
+      i18n: 'route.general.ads.list',
+      sidebar: false,
+      breadcrumb: false,
+      cache: ['adsCreate', 'adsEdit'],
+    },
+  },
+  {
+    legacyId: 16,
+    legacyParentId: 14,
+    title: '新增广告',
+    type: 'SUBMENU',
+    name: 'adsCreate',
+    path: '/marketing/ads/detail',
+    icon: null,
+    componentKey: '@/views/marketing_center/ads_mgnt/detail.vue',
+    order: 2,
+    meta: {
+      title: '新增广告',
+      i18n: 'route.general.ads.create',
+      sidebar: false,
+      activeMenu: '/marketing/adsList',
+      cache: true,
+      noCache: 'adsList',
+    },
+  },
+  {
+    legacyId: 17,
+    legacyParentId: 14,
+    title: '编辑广告',
+    type: 'SUBMENU',
+    name: 'adsEdit',
+    path: '/marketing/ads/detail/:id',
+    icon: null,
+    componentKey: '@/views/marketing_center/ads_mgnt/detail.vue',
+    order: 3,
+    meta: {
+      title: '编辑广告',
+      i18n: 'route.general.ads.edit',
+      sidebar: false,
+      activeMenu: '/marketing/adsList',
+      cache: true,
+      noCache: 'adsList',
+    },
+  },
+  {
+    legacyId: 18,
+    legacyParentId: 1,
+    title: '参数管理',
+    type: 'MENU',
+    name: 'paramList',
+    path: '/marketing/param',
+    icon: 'i-carbon:settings',
+    order: 5,
+    meta: {
+      title: '参数管理',
+      i18n: 'route.general.param.root',
+      icon: 'i-carbon:settings',
+    },
+  },
+  {
+    legacyId: 19,
+    legacyParentId: 18,
+    title: '参数列表',
+    type: 'SUBMENU',
+    name: 'paramList',
+    path: '/marketing/paramList',
+    icon: null,
+    componentKey: '@/views/marketing_center/param_mgnt/list.vue',
+    order: 1,
+    meta: {
+      title: '参数列表',
+      i18n: 'route.general.param.list',
+      sidebar: false,
+      breadcrumb: false,
+      cache: ['paramCreate', 'paramEdit'],
+    },
+  },
+  {
+    legacyId: 20,
+    legacyParentId: 18,
+    title: '新增参数',
+    type: 'SUBMENU',
+    name: 'paramCreate',
+    path: '/marketing/param/detail',
+    icon: null,
+    componentKey: '@/views/marketing_center/param_mgnt/detail.vue',
+    order: 2,
+    meta: {
+      title: '新增参数',
+      i18n: 'route.general.param.create',
+      sidebar: false,
+      activeMenu: '/marketing/paramList',
+      cache: true,
+      noCache: 'paramList',
+    },
+  },
+  {
+    legacyId: 21,
+    legacyParentId: 18,
+    title: '编辑参数',
+    type: 'SUBMENU',
+    name: 'paramEdit',
+    path: '/marketing/param/detail/:id',
+    icon: null,
+    componentKey: '@/views/marketing_center/param_mgnt/detail.vue',
+    order: 3,
+    meta: {
+      title: '编辑参数',
+      i18n: 'route.general.param.edit',
+      sidebar: false,
+      activeMenu: '/marketing/paramList',
+      cache: true,
+      noCache: 'paramList',
+    },
+  },
+  {
+    legacyId: 22,
+    legacyParentId: 1,
+    title: '渠道管理',
+    type: 'MENU',
+    name: 'channelList',
+    path: '/marketing/channel',
+    icon: 'i-carbon:network-3',
+    order: 6,
+    meta: {
+      title: '渠道管理',
+      i18n: 'route.general.channel.root',
+      icon: 'i-carbon:network-3',
+    },
+  },
+  {
+    legacyId: 23,
+    legacyParentId: 22,
+    title: '渠道列表',
+    type: 'SUBMENU',
+    name: 'channelList',
+    path: '/marketing/channelList',
+    icon: null,
+    componentKey: '@/views/marketing_center/channel_mgnt/list.vue',
+    order: 1,
+    meta: {
+      title: '渠道列表',
+      i18n: 'route.general.channel.list',
+      sidebar: false,
+      breadcrumb: false,
+      cache: ['channelCreate', 'channelEdit'],
+    },
+  },
+  {
+    legacyId: 24,
+    legacyParentId: 22,
+    title: '新增渠道',
+    type: 'SUBMENU',
+    name: 'channelCreate',
+    path: '/marketing/channel/detail',
+    icon: null,
+    componentKey: '@/views/marketing_center/channel_mgnt/detail.vue',
+    order: 2,
+    meta: {
+      title: '新增渠道',
+      i18n: 'route.general.channel.create',
+      sidebar: false,
+      activeMenu: '/marketing/channelList',
+      cache: true,
+      noCache: 'channelList',
+    },
+  },
+  {
+    legacyId: 25,
+    legacyParentId: 22,
+    title: '编辑渠道',
+    type: 'SUBMENU',
+    name: 'channelEdit',
+    path: '/marketing/channel/detail/:id',
+    icon: null,
+    componentKey: '@/views/marketing_center/channel_mgnt/detail.vue',
+    order: 3,
+    meta: {
+      title: '编辑渠道',
+      i18n: 'route.general.channel.edit',
+      sidebar: false,
+      activeMenu: '/marketing/channelList',
+      cache: true,
+      noCache: 'channelList',
+    },
+  },
+
+  // ======================
+  // 数据中心 (Data Center)
+  // ======================
+  {
+    legacyId: 26,
+    legacyParentId: null,
+    title: '数据中心',
+    type: 'DIRECTORY',
+    name: 'Data Center',
+    path: '/datacenter',
+    icon: 'i-carbon:data-vis-4',
+    order: 200,
+    meta: {
+      title: '数据中心',
+      i18n: 'route.general.root',
+      icon: 'i-carbon:data-vis-4',
+    },
+  },
+  {
+    legacyId: 27,
+    legacyParentId: 26,
+    title: 'APP访问记录',
+    type: 'MENU',
+    name: 'appAccessList',
+    path: '/datacenter/appAccessRecord',
+    icon: 'i-carbon:mobile',
+    order: 1,
+    meta: {
+      title: 'APP访问记录',
+      i18n: 'route.general.appAccessRecord.root',
+      icon: 'i-carbon:mobile',
+    },
+  },
+  {
+    legacyId: 28,
+    legacyParentId: 27,
+    title: 'APP访问记录',
+    type: 'SUBMENU',
+    name: 'appAccessList',
+    path: '/datacenter/appAccessList',
+    icon: null,
+    componentKey: '@/views/data_center/appAccess_records/list.vue',
+    order: 1,
+    meta: {
+      title: 'APP访问记录',
+      i18n: 'route.general.appAccessRecord.list',
+      sidebar: false,
+      breadcrumb: false,
+      cache: ['appAccessRecordCreate', 'appAccessRecordEdit'],
+    },
+  },
+  {
+    legacyId: 29,
+    legacyParentId: 26,
+    title: '广告点击记录',
+    type: 'MENU',
+    name: 'adsAccessList',
+    path: '/datacenter/adsAccessRecord',
+    icon: 'i-carbon:touch-1',
+    order: 2,
+    meta: {
+      title: '广告点击记录',
+      i18n: 'route.general.adsAccessRecord.root',
+      icon: 'i-carbon:touch-1',
+    },
+  },
+  {
+    legacyId: 30,
+    legacyParentId: 29,
+    title: '广告点击记录',
+    type: 'SUBMENU',
+    name: 'adsAccessList',
+    path: '/datacenter/adsAccessList',
+    icon: null,
+    componentKey: '@/views/data_center/adsAccess_records/list.vue',
+    order: 1,
+    meta: {
+      title: '广告点击记录',
+      i18n: 'route.general.category.list',
+      sidebar: false,
+      breadcrumb: false,
+      cache: ['adsAccessRecordCreate', 'adsAccessRecordEdit'],
+    },
+  },
+
+  // ======================
+  // 统计中心 (Stats Center)
+  // ======================
+  {
+    legacyId: 31,
+    legacyParentId: null,
+    title: '统计中心',
+    type: 'DIRECTORY',
+    name: 'Statistics Management',
+    path: '/stats',
+    icon: 'i-carbon:chart-line',
+    order: 300,
+    meta: {
+      title: '统计中心',
+      i18n: 'route.general.root',
+      icon: 'i-carbon:chart-line',
+    },
+  },
+  {
+    legacyId: 32,
+    legacyParentId: 31,
+    title: '每日统计',
+    type: 'MENU',
+    name: 'dailyStats',
+    path: '/stats/dailyStats',
+    icon: 'i-carbon:calendar',
+    order: 1,
+    meta: {
+      title: '每日统计',
+      i18n: 'route.general.dailyStats.root',
+      icon: 'i-carbon:calendar',
+    },
+  },
+  {
+    legacyId: 33,
+    legacyParentId: 32,
+    title: '每日统计',
+    type: 'SUBMENU',
+    name: 'dailyStatsList',
+    path: '/stats/dailyStatsList',
+    icon: null,
+    componentKey: '@/views/stats_center/daily_stats/list.vue',
+    order: 1,
+    meta: {
+      title: '每日统计',
+      i18n: 'route.general.video.list',
+      sidebar: false,
+      breadcrumb: false,
+      cache: ['videoCreate', 'videoEdit'],
+    },
+  },
+  {
+    legacyId: 34,
+    legacyParentId: 31,
+    title: '广告统计',
+    type: 'MENU',
+    name: 'adsStatsList',
+    path: '/stats/adsStats',
+    icon: 'i-carbon:chart-bar',
+    order: 2,
+    meta: {
+      title: '广告统计',
+      i18n: 'route.general.adsStats.root',
+      icon: 'i-carbon:chart-bar',
+    },
+  },
+  {
+    legacyId: 35,
+    legacyParentId: 34,
+    title: '广告统计',
+    type: 'SUBMENU',
+    name: 'adsStatsList',
+    path: '/stats/adsStatsList',
+    icon: null,
+    componentKey: '@/views/stats_center/ads_stats/list.vue',
+    order: 1,
+    meta: {
+      title: '广告统计',
+      i18n: 'route.general.adsStats.list',
+      sidebar: false,
+      breadcrumb: false,
+      cache: ['adsStatsCreate', 'adsStatsEdit'],
+    },
+  },
+  {
+    legacyId: 36,
+    legacyParentId: 31,
+    title: '广告汇总',
+    type: 'MENU',
+    name: 'adsStatsSummary',
+    path: '/stats/adsStatsSummary',
+    icon: 'i-carbon:chart-pie',
+    componentKey: '@/views/stats_center/ads_stats_summary/list.vue',
+    order: 3,
+    meta: {
+      title: '广告汇总',
+      i18n: 'route.general.adsStatsSummary.root',
+      icon: 'i-carbon:chart-pie',
+    },
+  },
+  {
+    legacyId: 37,
+    legacyParentId: 36,
+    title: '广告汇总',
+    type: 'SUBMENU',
+    name: 'adsStatsSummaryList',
+    path: '/stats/adsStatsSummaryList',
+    icon: null,
+    componentKey: '@/views/stats_center/ads_stats_summary/list.vue',
+    order: 1,
+    meta: {
+      title: '广告汇总',
+      i18n: 'route.general.adsStatsSummary.list',
+      sidebar: false,
+      breadcrumb: false,
+      cache: ['adsStatsSummaryCreate', 'adsStatsSummaryEdit'],
+    },
+  },
+  {
+    legacyId: 38,
+    legacyParentId: 31,
+    title: '渠道统计',
+    type: 'MENU',
+    name: 'channelStatsList',
+    path: '/stats/channelStats',
+    icon: 'i-carbon:flow',
+    order: 4,
+    meta: {
+      title: '渠道统计',
+      i18n: 'route.general.channelStats.root',
+      icon: 'i-carbon:flow',
+    },
+  },
+  {
+    legacyId: 39,
+    legacyParentId: 38,
+    title: '渠道统计',
+    type: 'SUBMENU',
+    name: 'channelStatsList',
+    path: '/stats/channelStatsList',
+    icon: null,
+    componentKey: '@/views/stats_center/channel_stats/list.vue',
+    order: 1,
+    meta: {
+      title: '渠道统计',
+      i18n: 'route.general.channel.list',
+      sidebar: false,
+      breadcrumb: false,
+      cache: ['channelCreate', 'channelEdit'],
+    },
+  },
+  {
+    legacyId: 40,
+    legacyParentId: 31,
+    title: '渠道汇总',
+    type: 'MENU',
+    name: 'channelStatsSummaryList',
+    path: '/stats/channelStatsSummary',
+    icon: 'i-carbon:report',
+    order: 5,
+    meta: {
+      title: '渠道汇总',
+      i18n: 'route.general.channelStatsSummary.root',
+      icon: 'i-carbon:report',
+    },
+  },
+  {
+    legacyId: 41,
+    legacyParentId: 40,
+    title: '渠道汇总',
+    type: 'SUBMENU',
+    name: 'channelStatsSummaryList',
+    path: '/stats/channelStatsSummaryList',
+    icon: null,
+    componentKey: '@/views/stats_center/channel_stats_summary/list.vue',
+    order: 1,
+    meta: {
+      title: '参数列表',
+      i18n: 'route.general.param.list',
+      sidebar: false,
+      breadcrumb: false,
+      cache: ['channelStatsSummaryCreate', 'channelStatsSummaryEdit'],
+    },
+  },
+
+  // ======================
+  // 系统管理 (System)
+  // ======================
+  {
+    legacyId: 42,
+    legacyParentId: null,
+    title: '账号管理',
+    type: 'DIRECTORY',
+    name: 'Sytem Management',
+    path: '/system',
+    icon: 'i-carbon:user-admin',
+    order: 400,
+    meta: {
+      title: '账号管理',
+      i18n: 'route.general.root',
+      icon: 'i-carbon:user-admin',
+    },
+  },
+  {
+    legacyId: 43,
+    legacyParentId: 42,
+    title: '账号列表',
+    type: 'MENU',
+    name: 'userList',
+    path: '/system/users',
+    icon: 'i-carbon:user-multiple',
+    componentKey: '@/views/system_mgnt/users/list.vue',
+    order: 1,
+    meta: {
+      title: '账号列表',
+      i18n: 'route.general.manager.root',
+      icon: 'i-carbon:user-multiple',
+    },
+  },
+  {
+    legacyId: 44,
+    legacyParentId: 42,
+    title: '角色列表',
+    type: 'MENU',
+    name: 'roleList',
+    path: '/system/roles',
+    icon: 'i-carbon:user-role',
+    order: 2,
+    meta: {
+      title: '角色列表',
+      i18n: 'route.general.role.root',
+      icon: 'i-carbon:user-role',
+    },
+  },
+  {
+    legacyId: 45,
+    legacyParentId: 44,
+    title: '角色列表',
+    type: 'SUBMENU',
+    name: 'roleList',
+    path: '/system/roleList',
+    icon: null,
+    componentKey: '@/views/system_mgnt/roles/list.vue',
+    order: 1,
+    meta: {
+      title: '角色列表',
+      i18n: 'route.general.role.list',
+      sidebar: false,
+      breadcrumb: false,
+      cache: ['roleCreate', 'roleEdit'],
+    },
+  },
+  {
+    legacyId: 46,
+    legacyParentId: 44,
+    title: '新增角色',
+    type: 'SUBMENU',
+    name: 'roleCreate',
+    path: '/system/roles/detail',
+    icon: null,
+    componentKey: '@/views/system_mgnt/roles/detail.vue',
+    order: 2,
+    meta: {
+      title: '新增角色',
+      i18n: 'route.general.role.create',
+      sidebar: false,
+      activeMenu: '/system/roleList',
+      cache: true,
+      noCache: 'roleList',
+    },
+  },
+  {
+    legacyId: 47,
+    legacyParentId: 44,
+    title: '编辑角色',
+    type: 'SUBMENU',
+    name: 'roleEdit',
+    path: '/system/roles/detail/:id',
+    icon: null,
+    componentKey: '@/views/system_mgnt/roles/detail.vue',
+    order: 3,
+    meta: {
+      title: '编辑角色',
+      i18n: 'route.general.role.edit',
+      sidebar: false,
+      activeMenu: '/system/roleList',
+      cache: true,
+      noCache: 'roleList',
+    },
+  },
+  {
+    legacyId: 48,
+    legacyParentId: 42,
+    title: '权限列表',
+    type: 'MENU',
+    name: 'menus',
+    path: '/system/menus',
+    icon: 'i-carbon:list-boxes',
+    order: 3,
+    meta: {
+      title: '权限列表',
+      i18n: 'route.general.menu.root',
+      icon: 'i-carbon:list-boxes',
+    },
+  },
+  {
+    legacyId: 49,
+    legacyParentId: 48,
+    title: '菜单列表',
+    type: 'SUBMENU',
+    name: 'menuList',
+    path: '/system/menuList',
+    icon: null,
+    componentKey: '@/views/system_mgnt/menus/list.vue',
+    order: 1,
+    meta: {
+      title: '菜单列表',
+      i18n: 'route.general.menu.list',
+      sidebar: false,
+      breadcrumb: false,
+      cache: ['menuCreate', 'menuEdit'],
+    },
+  },
+  {
+    legacyId: 50,
+    legacyParentId: 48,
+    title: '新增菜单',
+    type: 'SUBMENU',
+    name: 'menuCreate',
+    path: '/system/menus/detail',
+    icon: null,
+    componentKey: '@/views/system_mgnt/menus/detail.vue',
+    order: 2,
+    meta: {
+      title: '新增菜单',
+      i18n: 'route.general.menu.create',
+      sidebar: false,
+      activeMenu: '/system/menuList',
+      cache: true,
+      noCache: 'menuList',
+    },
+  },
+  {
+    legacyId: 51,
+    legacyParentId: 48,
+    title: '编辑菜单',
+    type: 'SUBMENU',
+    name: 'menuEdit',
+    path: '/system/menus/detail/:id',
+    icon: null,
+    componentKey: '@/views/system_mgnt/menus/detail.vue',
+    order: 3,
+    meta: {
+      title: '编辑菜单',
+      i18n: 'route.general.menu.edit',
+      sidebar: false,
+      activeMenu: '/system/menuList',
+      cache: true,
+      noCache: 'menuList',
+    },
+  },
+  {
+    legacyId: 52,
+    legacyParentId: 42,
+    title: 'Redis监控',
+    type: 'MENU',
+    name: 'redisMonitor',
+    path: '/system/redisMonitor',
+    icon: 'i-carbon:list-boxes',
+    order: 4,
+    meta: {
+      title: 'Redis监控',
+      i18n: 'route.general.redisMonitor.list',
+      icon: 'i-carbon:list-boxes',
+    },
+  },
+  {
+    legacyId: 53,
+    legacyParentId: 52,
+    title: 'Redis列表',
+    type: 'SUBMENU',
+    name: 'redisList',
+    path: '/system/redisList',
+    icon: null,
+    componentKey: '@/views/system_mgnt/redisMonitor/list.vue',
+    order: 1,
+    meta: {
+      title: 'Redis列表',
+      i18n: 'route.general.redisMonitor.list',
+      sidebar: false,
+      breadcrumb: false,
+      cache: ['redisCreate', 'redisEdit'],
+    },
+  },
+];
+
+// =============================================================================
+// 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 menus with multi-pass insertion to handle parent-child relationships
+ */
+async function seedMenus() {
+  console.log('📝 Seeding menus...');
+
+  await prisma.$runCommandRaw({
+    delete: 'sys_api_permission',
+    deletes: [{ q: {}, limit: 0 }],
+  });
+  await prisma.$runCommandRaw({
+    delete: 'sys_menu',
+    deletes: [{ q: {}, limit: 0 }],
+  });
+
+  const legacyIdToNewId = new Map<number, string>();
+  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 menuCreateData: Prisma.SysMenuUncheckedCreateInput = {
+        parentId,
+        title: seed.title,
+        status: true,
+        type: seed.type,
+        order: seed.order,
+        frontendAuth: seed.path,
+        path: seed.path,
+        name: seed.name,
+        icon: seed.icon,
+        redirect: null,
+        componentKey: seed.componentKey ?? null,
+        meta: seed.meta ?? undefined,
+        ...permissions,
+      };
+
+      const created = await prisma.sysMenu.upsert({
+        // Use unique index on frontendAuth to identify menu
+        where: { frontendAuth: seed.path },
+        update: {
+          parentId,
+          title: seed.title,
+          status: true,
+          type: seed.type,
+          order: seed.order,
+          // keep frontendAuth as path
+          path: seed.path,
+          name: seed.name,
+          icon: seed.icon,
+          redirect: null,
+          componentKey: seed.componentKey ?? null,
+          // meta undefined means do not overwrite; if provided, update
+          ...(seed.meta !== undefined
+            ? { meta: seed.meta as Prisma.InputJsonValue }
+            : {}),
+          ...permissions,
+        },
+        create: {
+          ...menuCreateData,
+        },
+      });
+
+      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 menus seeding...\n');
+
+  try {
+    console.log('seeding menus...');
+    await seedMenus();
+    console.log('');
+
+    console.log('\n🎉 Menus 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);
+  });