seed-admin.ts 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332
  1. // prisma/mongo/seed-admin.ts
  2. import { PrismaClient, MenuType, AdType } from '@prisma/mongo/client';
  3. import type { Prisma } from '@prisma/mongo/client';
  4. const prisma = new PrismaClient();
  5. type ImgSource = 'LOCAL_ONLY' | 'S3_ONLY' | 'S3_AND_LOCAL' | 'PROVIDER';
  6. async function seedSysConfig(): Promise<void> {
  7. const nowSec = Math.floor(Date.now() / 1000);
  8. // Equivalent to:
  9. // db.sysConfig.updateOne({ _id: -1 }, { $set: {...} }, { upsert: true })
  10. await prisma.$runCommandRaw({
  11. update: 'sysConfig',
  12. updates: [
  13. {
  14. q: { _id: -1 },
  15. u: {
  16. $set: {
  17. appConfig: {
  18. videoCdn: {
  19. image: 'https://vm.rvakc.xyz/res/decode',
  20. video: 'https://vm.rvakc.xyz/api/web/media/m3u8/',
  21. },
  22. adsCdn: {
  23. s3: 'https://s3.ap-east-1.amazonaws.com/mybucket-imgs',
  24. local: 'https://man.boxt3yk.com/images',
  25. },
  26. },
  27. imageConfig: {
  28. s3Enabled: false,
  29. storageStrategy: 'LOCAL_ONLY' as ImgSource,
  30. local: {
  31. rootPath: '/usr/local/app/box-project/box-images',
  32. baseUrl: 'https://man.boxt3yk.com/images',
  33. },
  34. limitsMb: { image: 10, video: 100 },
  35. s3: {
  36. accessKeyId: 'AKIA6GSNGR5PISMIKCJ4',
  37. secretAccessKey: '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',
  38. bucket: 'mybucket-imgs',
  39. region: 'ap-east-1',
  40. endpointUrl: 'https://s3.ap-east-1.amazonaws.com',
  41. imageBaseUrl:
  42. 'https://s3.ap-east-1.amazonaws.com/mybucket-imgs',
  43. },
  44. },
  45. provider: {
  46. providerCode: 'PARTNER',
  47. apiUrl: 'https://vm.rvakc.xyz/api/web/mediafile/search',
  48. itemsLimit: 300,
  49. },
  50. updatedAt: nowSec,
  51. },
  52. },
  53. upsert: true,
  54. multi: false,
  55. },
  56. ],
  57. });
  58. }
  59. // =============================================================================
  60. // MENU SEEDS DATA
  61. // =============================================================================
  62. interface SeedMenu {
  63. legacyId: number;
  64. legacyParentId: number | null;
  65. title: string;
  66. type: MenuType;
  67. name: string;
  68. path: string;
  69. icon: string | null;
  70. componentKey?: string | null;
  71. order: number;
  72. meta?: Prisma.JsonValue;
  73. }
  74. const MENU_SEEDS: SeedMenu[] = [
  75. // ======================
  76. // 营销中心 (Marketing)
  77. // ======================
  78. {
  79. legacyId: 1,
  80. legacyParentId: null,
  81. title: '营销中心',
  82. type: 'DIRECTORY',
  83. name: 'Marketing Management',
  84. path: '/marketing',
  85. icon: 'i-carbon:application-web',
  86. order: 100,
  87. meta: {
  88. title: '营销中心',
  89. i18n: 'route.general.root',
  90. icon: 'i-carbon:application-web',
  91. },
  92. },
  93. {
  94. legacyId: 2,
  95. legacyParentId: 1,
  96. title: '视频管理',
  97. type: 'MENU',
  98. name: 'videoListMenu',
  99. path: '/marketing/video',
  100. icon: 'i-carbon:video',
  101. order: 1,
  102. meta: {
  103. title: '视频管理',
  104. i18n: 'route.general.video.root',
  105. icon: 'i-carbon:video',
  106. },
  107. },
  108. {
  109. legacyId: 3,
  110. legacyParentId: 2,
  111. title: '视频列表',
  112. type: 'SUBMENU',
  113. name: 'videoList',
  114. path: '/marketing/videoList',
  115. icon: null,
  116. componentKey: '@/views/marketing_center/video_mgnt/list.vue',
  117. order: 1,
  118. meta: {
  119. title: '视频列表',
  120. i18n: 'route.general.video.list',
  121. sidebar: false,
  122. breadcrumb: false,
  123. cache: ['videoCreate', 'videoEdit'],
  124. },
  125. },
  126. {
  127. legacyId: 4,
  128. legacyParentId: 2,
  129. title: '新增视频',
  130. type: 'SUBMENU',
  131. name: 'videoCreate',
  132. path: '/marketing/video/detail',
  133. icon: null,
  134. componentKey: '@/views/marketing_center/video_mgnt/detail.vue',
  135. order: 2,
  136. meta: {
  137. title: '新增视频',
  138. i18n: 'route.general.video.create',
  139. sidebar: false,
  140. activeMenu: '/marketing/videoList',
  141. cache: true,
  142. noCache: 'videoList',
  143. },
  144. },
  145. {
  146. legacyId: 5,
  147. legacyParentId: 2,
  148. title: '编辑视频',
  149. type: 'SUBMENU',
  150. name: 'videoEdit',
  151. path: '/marketing/video/detail/:id',
  152. icon: null,
  153. componentKey: '@/views/marketing_center/video_mgnt/detail.vue',
  154. order: 3,
  155. meta: {
  156. title: '编辑视频',
  157. i18n: 'route.general.video.edit',
  158. sidebar: false,
  159. activeMenu: '/marketing/videoList',
  160. cache: true,
  161. noCache: 'videoList',
  162. },
  163. },
  164. {
  165. legacyId: 6,
  166. legacyParentId: 1,
  167. title: '分类管理',
  168. type: 'MENU',
  169. name: 'categoryListMenu',
  170. path: '/marketing/category',
  171. icon: 'i-carbon:category',
  172. order: 2,
  173. meta: {
  174. title: '分类管理',
  175. i18n: 'route.general.category.root',
  176. icon: 'i-carbon:category',
  177. },
  178. },
  179. {
  180. legacyId: 7,
  181. legacyParentId: 6,
  182. title: '分类列表',
  183. type: 'SUBMENU',
  184. name: 'categoryList',
  185. path: '/marketing/categoryList',
  186. icon: null,
  187. componentKey: '@/views/marketing_center/category_mgnt/list.vue',
  188. order: 1,
  189. meta: {
  190. title: '分类列表',
  191. i18n: 'route.general.category.list',
  192. sidebar: false,
  193. breadcrumb: false,
  194. cache: ['categoryCreate', 'categoryEdit'],
  195. },
  196. },
  197. {
  198. legacyId: 8,
  199. legacyParentId: 6,
  200. title: '新增分类',
  201. type: 'SUBMENU',
  202. name: 'categoryCreate',
  203. path: '/marketing/category/detail',
  204. icon: null,
  205. componentKey: '@/views/marketing_center/category_mgnt/detail.vue',
  206. order: 2,
  207. meta: {
  208. title: '新增分类',
  209. i18n: 'route.general.category.create',
  210. sidebar: false,
  211. activeMenu: '/marketing/categoryList',
  212. cache: true,
  213. noCache: 'categoryList',
  214. },
  215. },
  216. {
  217. legacyId: 9,
  218. legacyParentId: 6,
  219. title: '编辑分类',
  220. type: 'SUBMENU',
  221. name: 'categoryEdit',
  222. path: '/marketing/category/detail/:id',
  223. icon: null,
  224. componentKey: '@/views/marketing_center/category_mgnt/detail.vue',
  225. order: 3,
  226. meta: {
  227. title: '编辑分类',
  228. i18n: 'route.general.category.edit',
  229. sidebar: false,
  230. activeMenu: '/marketing/categoryList',
  231. cache: true,
  232. noCache: 'categoryList',
  233. },
  234. },
  235. {
  236. legacyId: 10,
  237. legacyParentId: 1,
  238. title: '标签管理',
  239. type: 'MENU',
  240. name: 'tagListMenu',
  241. path: '/marketing/tag',
  242. icon: 'i-carbon:tag',
  243. order: 3,
  244. meta: {
  245. title: '标签管理',
  246. i18n: 'route.general.tag.root',
  247. icon: 'i-carbon:tag',
  248. },
  249. },
  250. {
  251. legacyId: 11,
  252. legacyParentId: 10,
  253. title: '标签列表',
  254. type: 'SUBMENU',
  255. name: 'tagList',
  256. path: '/marketing/tagList',
  257. icon: null,
  258. componentKey: '@/views/marketing_center/tag_mgnt/list.vue',
  259. order: 1,
  260. meta: {
  261. title: '标签列表',
  262. i18n: 'route.general.tag.list',
  263. sidebar: false,
  264. breadcrumb: false,
  265. cache: ['tagCreate', 'tagEdit'],
  266. },
  267. },
  268. {
  269. legacyId: 12,
  270. legacyParentId: 10,
  271. title: '新增标签',
  272. type: 'SUBMENU',
  273. name: 'tagCreate',
  274. path: '/marketing/tag/detail',
  275. icon: null,
  276. componentKey: '@/views/marketing_center/tag_mgnt/detail.vue',
  277. order: 2,
  278. meta: {
  279. title: '新增标签',
  280. i18n: 'route.general.tag.create',
  281. sidebar: false,
  282. activeMenu: '/marketing/tagList',
  283. cache: true,
  284. noCache: 'tagList',
  285. },
  286. },
  287. {
  288. legacyId: 13,
  289. legacyParentId: 10,
  290. title: '编辑标签',
  291. type: 'SUBMENU',
  292. name: 'tagEdit',
  293. path: '/marketing/tag/detail/:id',
  294. icon: null,
  295. componentKey: '@/views/marketing_center/tag_mgnt/detail.vue',
  296. order: 3,
  297. meta: {
  298. title: '编辑标签',
  299. i18n: 'route.general.tag.edit',
  300. sidebar: false,
  301. activeMenu: '/marketing/tagList',
  302. cache: true,
  303. noCache: 'tagList',
  304. },
  305. },
  306. {
  307. legacyId: 14,
  308. legacyParentId: 1,
  309. title: '广告管理',
  310. type: 'MENU',
  311. name: 'adsListMenu',
  312. path: '/marketing/ads',
  313. icon: 'i-carbon:image-copy',
  314. order: 4,
  315. meta: {
  316. title: '广告管理',
  317. i18n: 'route.general.ads.root',
  318. icon: 'i-carbon:image-copy',
  319. },
  320. },
  321. {
  322. legacyId: 15,
  323. legacyParentId: 14,
  324. title: '广告列表',
  325. type: 'SUBMENU',
  326. name: 'adsList',
  327. path: '/marketing/adsList',
  328. icon: null,
  329. componentKey: '@/views/marketing_center/ads_mgnt/list.vue',
  330. order: 1,
  331. meta: {
  332. title: '广告列表',
  333. i18n: 'route.general.ads.list',
  334. sidebar: false,
  335. breadcrumb: false,
  336. cache: ['adsCreate', 'adsEdit'],
  337. },
  338. },
  339. {
  340. legacyId: 16,
  341. legacyParentId: 14,
  342. title: '新增广告',
  343. type: 'SUBMENU',
  344. name: 'adsCreate',
  345. path: '/marketing/ads/detail',
  346. icon: null,
  347. componentKey: '@/views/marketing_center/ads_mgnt/detail.vue',
  348. order: 2,
  349. meta: {
  350. title: '新增广告',
  351. i18n: 'route.general.ads.create',
  352. sidebar: false,
  353. activeMenu: '/marketing/adsList',
  354. cache: true,
  355. noCache: 'adsList',
  356. },
  357. },
  358. {
  359. legacyId: 17,
  360. legacyParentId: 14,
  361. title: '编辑广告',
  362. type: 'SUBMENU',
  363. name: 'adsEdit',
  364. path: '/marketing/ads/detail/:id',
  365. icon: null,
  366. componentKey: '@/views/marketing_center/ads_mgnt/detail.vue',
  367. order: 3,
  368. meta: {
  369. title: '编辑广告',
  370. i18n: 'route.general.ads.edit',
  371. sidebar: false,
  372. activeMenu: '/marketing/adsList',
  373. cache: true,
  374. noCache: 'adsList',
  375. },
  376. },
  377. {
  378. legacyId: 18,
  379. legacyParentId: 1,
  380. title: '参数管理',
  381. type: 'MENU',
  382. name: 'paramListMenu',
  383. path: '/setting/param',
  384. icon: 'i-carbon:settings',
  385. order: 5,
  386. meta: {
  387. title: '参数管理',
  388. i18n: 'route.general.param.root',
  389. icon: 'i-carbon:settings',
  390. },
  391. },
  392. {
  393. legacyId: 19,
  394. legacyParentId: 18,
  395. title: '参数列表',
  396. type: 'SUBMENU',
  397. name: 'paramList',
  398. path: '/setting/paramList',
  399. icon: null,
  400. componentKey: '@/views/setting_mgnt/param_mgnt/list.vue',
  401. order: 1,
  402. meta: {
  403. title: '参数列表',
  404. i18n: 'route.general.param.list',
  405. sidebar: false,
  406. breadcrumb: false,
  407. cache: ['paramCreate', 'paramEdit'],
  408. },
  409. },
  410. {
  411. legacyId: 20,
  412. legacyParentId: 18,
  413. title: '新增参数',
  414. type: 'SUBMENU',
  415. name: 'paramCreate',
  416. path: '/setting/param/detail',
  417. icon: null,
  418. componentKey: '@/views/setting_mgnt/param_mgnt/detail.vue',
  419. order: 2,
  420. meta: {
  421. title: '新增参数',
  422. i18n: 'route.general.param.create',
  423. sidebar: false,
  424. activeMenu: '/setting/paramList',
  425. cache: true,
  426. noCache: 'paramList',
  427. },
  428. },
  429. {
  430. legacyId: 21,
  431. legacyParentId: 18,
  432. title: '编辑参数',
  433. type: 'SUBMENU',
  434. name: 'paramEdit',
  435. path: '/setting/param/detail/:id',
  436. icon: null,
  437. componentKey: '@/views/setting_mgnt/param_mgnt/detail.vue',
  438. order: 3,
  439. meta: {
  440. title: '编辑参数',
  441. i18n: 'route.general.param.edit',
  442. sidebar: false,
  443. activeMenu: '/setting/paramList',
  444. cache: true,
  445. noCache: 'paramList',
  446. },
  447. },
  448. {
  449. legacyId: 22,
  450. legacyParentId: 1,
  451. title: '渠道管理',
  452. type: 'MENU',
  453. name: 'channelListMenu',
  454. path: '/marketing/channel',
  455. icon: 'i-carbon:network-3',
  456. order: 6,
  457. meta: {
  458. title: '渠道管理',
  459. i18n: 'route.general.channel.root',
  460. icon: 'i-carbon:network-3',
  461. },
  462. },
  463. {
  464. legacyId: 23,
  465. legacyParentId: 22,
  466. title: '渠道列表',
  467. type: 'SUBMENU',
  468. name: 'channelList',
  469. path: '/marketing/channelList',
  470. icon: null,
  471. componentKey: '@/views/setting_mgnt/channel_mgnt/list.vue',
  472. order: 1,
  473. meta: {
  474. title: '渠道列表',
  475. i18n: 'route.general.channel.list',
  476. sidebar: false,
  477. breadcrumb: false,
  478. cache: ['channelCreate', 'channelEdit'],
  479. },
  480. },
  481. {
  482. legacyId: 24,
  483. legacyParentId: 22,
  484. title: '新增渠道',
  485. type: 'SUBMENU',
  486. name: 'channelCreate',
  487. path: '/marketing/channel/detail',
  488. icon: null,
  489. componentKey: '@/views/setting_mgnt/channel_mgnt/detail.vue',
  490. order: 2,
  491. meta: {
  492. title: '新增渠道',
  493. i18n: 'route.general.channel.create',
  494. sidebar: false,
  495. activeMenu: '/marketing/channelList',
  496. cache: true,
  497. noCache: 'channelList',
  498. },
  499. },
  500. {
  501. legacyId: 25,
  502. legacyParentId: 22,
  503. title: '编辑渠道',
  504. type: 'SUBMENU',
  505. name: 'channelEdit',
  506. path: '/marketing/channel/detail/:id',
  507. icon: null,
  508. componentKey: '@/views/setting_mgnt/channel_mgnt/detail.vue',
  509. order: 3,
  510. meta: {
  511. title: '编辑渠道',
  512. i18n: 'route.general.channel.edit',
  513. sidebar: false,
  514. activeMenu: '/marketing/channelList',
  515. cache: true,
  516. noCache: 'channelList',
  517. },
  518. },
  519. // ======================
  520. // 数据中心 (Data Center)
  521. // ======================
  522. {
  523. legacyId: 26,
  524. legacyParentId: null,
  525. title: '数据中心',
  526. type: 'DIRECTORY',
  527. name: 'Data Center',
  528. path: '/datacenter',
  529. icon: 'i-carbon:data-vis-4',
  530. order: 200,
  531. meta: {
  532. title: '数据中心',
  533. i18n: 'route.general.root',
  534. icon: 'i-carbon:data-vis-4',
  535. },
  536. },
  537. {
  538. legacyId: 27,
  539. legacyParentId: 26,
  540. title: 'APP访问记录',
  541. type: 'MENU',
  542. name: 'appAccessListMenu',
  543. path: '/datacenter/appAccessRecord',
  544. icon: 'i-carbon:mobile',
  545. order: 1,
  546. meta: {
  547. title: 'APP访问记录',
  548. i18n: 'route.general.appAccessRecord.root',
  549. icon: 'i-carbon:mobile',
  550. },
  551. },
  552. {
  553. legacyId: 28,
  554. legacyParentId: 27,
  555. title: 'APP访问记录',
  556. type: 'SUBMENU',
  557. name: 'appAccessList',
  558. path: '/datacenter/appAccessList',
  559. icon: null,
  560. componentKey: '@/views/data_center/appAccess_records/list.vue',
  561. order: 1,
  562. meta: {
  563. title: 'APP访问记录',
  564. i18n: 'route.general.appAccessRecord.list',
  565. sidebar: false,
  566. breadcrumb: false,
  567. cache: ['appAccessRecordCreate', 'appAccessRecordEdit'],
  568. },
  569. },
  570. {
  571. legacyId: 29,
  572. legacyParentId: 26,
  573. title: '广告点击记录',
  574. type: 'MENU',
  575. name: 'adsAccessListMenu',
  576. path: '/datacenter/adsAccessRecord',
  577. icon: 'i-carbon:touch-1',
  578. order: 2,
  579. meta: {
  580. title: '广告点击记录',
  581. i18n: 'route.general.adsAccessRecord.root',
  582. icon: 'i-carbon:touch-1',
  583. },
  584. },
  585. {
  586. legacyId: 30,
  587. legacyParentId: 29,
  588. title: '广告点击记录',
  589. type: 'SUBMENU',
  590. name: 'adsAccessList',
  591. path: '/datacenter/adsAccessList',
  592. icon: null,
  593. componentKey: '@/views/data_center/adsAccess_records/list.vue',
  594. order: 1,
  595. meta: {
  596. title: '广告点击记录',
  597. i18n: 'route.general.category.list',
  598. sidebar: false,
  599. breadcrumb: false,
  600. cache: ['adsAccessRecordCreate', 'adsAccessRecordEdit'],
  601. },
  602. },
  603. // ======================
  604. // 统计中心 (Stats Center)
  605. // ======================
  606. {
  607. legacyId: 31,
  608. legacyParentId: null,
  609. title: '统计中心',
  610. type: 'DIRECTORY',
  611. name: 'Statistics Management',
  612. path: '/stats',
  613. icon: 'i-carbon:chart-line',
  614. order: 300,
  615. meta: {
  616. title: '统计中心',
  617. i18n: 'route.general.root',
  618. icon: 'i-carbon:chart-line',
  619. },
  620. },
  621. {
  622. legacyId: 32,
  623. legacyParentId: 31,
  624. title: '每日统计',
  625. type: 'MENU',
  626. name: 'dailyStats',
  627. path: '/stats/dailyStats',
  628. icon: 'i-carbon:calendar',
  629. order: 1,
  630. meta: {
  631. title: '每日统计',
  632. i18n: 'route.general.dailyStats.root',
  633. icon: 'i-carbon:calendar',
  634. },
  635. },
  636. {
  637. legacyId: 33,
  638. legacyParentId: 32,
  639. title: '每日统计',
  640. type: 'SUBMENU',
  641. name: 'dailyStatsList',
  642. path: '/stats/dailyStatsList',
  643. icon: null,
  644. componentKey: '@/views/stats_center/daily_stats/list.vue',
  645. order: 1,
  646. meta: {
  647. title: '每日统计',
  648. i18n: 'route.general.video.list',
  649. sidebar: false,
  650. breadcrumb: false,
  651. cache: ['videoCreate', 'videoEdit'],
  652. },
  653. },
  654. {
  655. legacyId: 34,
  656. legacyParentId: 31,
  657. title: '广告统计',
  658. type: 'MENU',
  659. name: 'adsStatsListMenu',
  660. path: '/stats/adsStats',
  661. icon: 'i-carbon:chart-bar',
  662. order: 2,
  663. meta: {
  664. title: '广告统计',
  665. i18n: 'route.general.adsStats.root',
  666. icon: 'i-carbon:chart-bar',
  667. },
  668. },
  669. {
  670. legacyId: 35,
  671. legacyParentId: 34,
  672. title: '广告统计',
  673. type: 'SUBMENU',
  674. name: 'adsStatsList',
  675. path: '/stats/adsStatsList',
  676. icon: null,
  677. componentKey: '@/views/stats_center/ads_stats/list.vue',
  678. order: 1,
  679. meta: {
  680. title: '广告统计',
  681. i18n: 'route.general.adsStats.list',
  682. sidebar: false,
  683. breadcrumb: false,
  684. cache: ['adsStatsCreate', 'adsStatsEdit'],
  685. },
  686. },
  687. {
  688. legacyId: 36,
  689. legacyParentId: 31,
  690. title: '广告汇总',
  691. type: 'MENU',
  692. name: 'adsStatsSummary',
  693. path: '/stats/adsStatsSummary',
  694. icon: 'i-carbon:chart-pie',
  695. componentKey: '@/views/stats_center/ads_stats_summary/list.vue',
  696. order: 3,
  697. meta: {
  698. title: '广告汇总',
  699. i18n: 'route.general.adsStatsSummary.root',
  700. icon: 'i-carbon:chart-pie',
  701. },
  702. },
  703. {
  704. legacyId: 37,
  705. legacyParentId: 36,
  706. title: '广告汇总',
  707. type: 'SUBMENU',
  708. name: 'adsStatsSummaryList',
  709. path: '/stats/adsStatsSummaryList',
  710. icon: null,
  711. componentKey: '@/views/stats_center/ads_stats_summary/list.vue',
  712. order: 1,
  713. meta: {
  714. title: '广告汇总',
  715. i18n: 'route.general.adsStatsSummary.list',
  716. sidebar: false,
  717. breadcrumb: false,
  718. cache: ['adsStatsSummaryCreate', 'adsStatsSummaryEdit'],
  719. },
  720. },
  721. {
  722. legacyId: 38,
  723. legacyParentId: 31,
  724. title: '渠道统计',
  725. type: 'MENU',
  726. name: 'channelStatsListMenu',
  727. path: '/stats/channelStats',
  728. icon: 'i-carbon:flow',
  729. order: 4,
  730. meta: {
  731. title: '渠道统计',
  732. i18n: 'route.general.channelStats.root',
  733. icon: 'i-carbon:flow',
  734. },
  735. },
  736. {
  737. legacyId: 39,
  738. legacyParentId: 38,
  739. title: '渠道统计',
  740. type: 'SUBMENU',
  741. name: 'channelStatsList',
  742. path: '/stats/channelStatsList',
  743. icon: null,
  744. componentKey: '@/views/stats_center/channel_stats/list.vue',
  745. order: 1,
  746. meta: {
  747. title: '渠道统计',
  748. i18n: 'route.general.channel.list',
  749. sidebar: false,
  750. breadcrumb: false,
  751. cache: ['channelCreate', 'channelEdit'],
  752. },
  753. },
  754. {
  755. legacyId: 40,
  756. legacyParentId: 31,
  757. title: '渠道汇总',
  758. type: 'MENU',
  759. name: 'channelStatsSummaryListMenu',
  760. path: '/stats/channelStatsSummary',
  761. icon: 'i-carbon:report',
  762. order: 5,
  763. meta: {
  764. title: '渠道汇总',
  765. i18n: 'route.general.channelStatsSummary.root',
  766. icon: 'i-carbon:report',
  767. },
  768. },
  769. {
  770. legacyId: 41,
  771. legacyParentId: 40,
  772. title: '渠道汇总',
  773. type: 'SUBMENU',
  774. name: 'channelStatsSummaryList',
  775. path: '/stats/channelStatsSummaryList',
  776. icon: null,
  777. componentKey: '@/views/stats_center/channel_stats_summary/list.vue',
  778. order: 1,
  779. meta: {
  780. title: '参数列表',
  781. i18n: 'route.general.param.list',
  782. sidebar: false,
  783. breadcrumb: false,
  784. cache: ['channelStatsSummaryCreate', 'channelStatsSummaryEdit'],
  785. },
  786. },
  787. // ======================
  788. // 系统管理 (System)
  789. // ======================
  790. {
  791. legacyId: 42,
  792. legacyParentId: null,
  793. title: '账号管理',
  794. type: 'DIRECTORY',
  795. name: 'Sytem Management',
  796. path: '/system',
  797. icon: 'i-carbon:user-admin',
  798. order: 400,
  799. meta: {
  800. title: '账号管理',
  801. i18n: 'route.general.root',
  802. icon: 'i-carbon:user-admin',
  803. },
  804. },
  805. {
  806. legacyId: 43,
  807. legacyParentId: 42,
  808. title: '账号列表',
  809. type: 'MENU',
  810. name: 'userList',
  811. path: '/system/users',
  812. icon: 'i-carbon:user-multiple',
  813. componentKey: '@/views/system_mgnt/users/list.vue',
  814. order: 1,
  815. meta: {
  816. title: '账号列表',
  817. i18n: 'route.general.manager.root',
  818. icon: 'i-carbon:user-multiple',
  819. },
  820. },
  821. {
  822. legacyId: 44,
  823. legacyParentId: 42,
  824. title: '角色列表',
  825. type: 'MENU',
  826. name: 'roleListMenu',
  827. path: '/system/roles',
  828. icon: 'i-carbon:user-role',
  829. order: 2,
  830. meta: {
  831. title: '角色列表',
  832. i18n: 'route.general.role.root',
  833. icon: 'i-carbon:user-role',
  834. },
  835. },
  836. {
  837. legacyId: 45,
  838. legacyParentId: 44,
  839. title: '角色列表',
  840. type: 'SUBMENU',
  841. name: 'roleList',
  842. path: '/system/roleList',
  843. icon: null,
  844. componentKey: '@/views/system_mgnt/roles/list.vue',
  845. order: 1,
  846. meta: {
  847. title: '角色列表',
  848. i18n: 'route.general.role.list',
  849. sidebar: false,
  850. breadcrumb: false,
  851. cache: ['roleCreate', 'roleEdit'],
  852. },
  853. },
  854. {
  855. legacyId: 46,
  856. legacyParentId: 44,
  857. title: '新增角色',
  858. type: 'SUBMENU',
  859. name: 'roleCreate',
  860. path: '/system/roles/detail',
  861. icon: null,
  862. componentKey: '@/views/system_mgnt/roles/detail.vue',
  863. order: 2,
  864. meta: {
  865. title: '新增角色',
  866. i18n: 'route.general.role.create',
  867. sidebar: false,
  868. activeMenu: '/system/roleList',
  869. cache: true,
  870. noCache: 'roleList',
  871. },
  872. },
  873. {
  874. legacyId: 47,
  875. legacyParentId: 44,
  876. title: '编辑角色',
  877. type: 'SUBMENU',
  878. name: 'roleEdit',
  879. path: '/system/roles/detail/:id',
  880. icon: null,
  881. componentKey: '@/views/system_mgnt/roles/detail.vue',
  882. order: 3,
  883. meta: {
  884. title: '编辑角色',
  885. i18n: 'route.general.role.edit',
  886. sidebar: false,
  887. activeMenu: '/system/roleList',
  888. cache: true,
  889. noCache: 'roleList',
  890. },
  891. },
  892. {
  893. legacyId: 48,
  894. legacyParentId: 42,
  895. title: '权限列表',
  896. type: 'MENU',
  897. name: 'menus',
  898. path: '/system/menus',
  899. icon: 'i-carbon:list-boxes',
  900. order: 3,
  901. meta: {
  902. title: '权限列表',
  903. i18n: 'route.general.menu.root',
  904. icon: 'i-carbon:list-boxes',
  905. },
  906. },
  907. {
  908. legacyId: 49,
  909. legacyParentId: 48,
  910. title: '菜单列表',
  911. type: 'SUBMENU',
  912. name: 'menuList',
  913. path: '/system/menuList',
  914. icon: null,
  915. componentKey: '@/views/system_mgnt/menus/list.vue',
  916. order: 1,
  917. meta: {
  918. title: '菜单列表',
  919. i18n: 'route.general.menu.list',
  920. sidebar: false,
  921. breadcrumb: false,
  922. cache: ['menuCreate', 'menuEdit'],
  923. },
  924. },
  925. {
  926. legacyId: 50,
  927. legacyParentId: 48,
  928. title: '新增菜单',
  929. type: 'SUBMENU',
  930. name: 'menuCreate',
  931. path: '/system/menus/detail',
  932. icon: null,
  933. componentKey: '@/views/system_mgnt/menus/detail.vue',
  934. order: 2,
  935. meta: {
  936. title: '新增菜单',
  937. i18n: 'route.general.menu.create',
  938. sidebar: false,
  939. activeMenu: '/system/menuList',
  940. cache: true,
  941. noCache: 'menuList',
  942. },
  943. },
  944. {
  945. legacyId: 51,
  946. legacyParentId: 48,
  947. title: '编辑菜单',
  948. type: 'SUBMENU',
  949. name: 'menuEdit',
  950. path: '/system/menus/detail/:id',
  951. icon: null,
  952. componentKey: '@/views/system_mgnt/menus/detail.vue',
  953. order: 3,
  954. meta: {
  955. title: '编辑菜单',
  956. i18n: 'route.general.menu.edit',
  957. sidebar: false,
  958. activeMenu: '/system/menuList',
  959. cache: true,
  960. noCache: 'menuList',
  961. },
  962. },
  963. {
  964. legacyId: 52,
  965. legacyParentId: 42,
  966. title: 'Redis监控',
  967. type: 'MENU',
  968. name: 'redisMonitor',
  969. path: '/system/redisMonitor',
  970. icon: 'i-carbon:list-boxes',
  971. order: 4,
  972. meta: {
  973. title: 'Redis监控',
  974. i18n: 'route.general.redisMonitor.list',
  975. icon: 'i-carbon:list-boxes',
  976. },
  977. },
  978. {
  979. legacyId: 53,
  980. legacyParentId: 52,
  981. title: 'Redis列表',
  982. type: 'SUBMENU',
  983. name: 'redisList',
  984. path: '/system/redisList',
  985. icon: null,
  986. componentKey: '@/views/system_mgnt/redisMonitor/list.vue',
  987. order: 1,
  988. meta: {
  989. title: 'Redis列表',
  990. i18n: 'route.general.redisMonitor.list',
  991. sidebar: false,
  992. breadcrumb: false,
  993. cache: ['redisCreate', 'redisEdit'],
  994. },
  995. },
  996. ];
  997. // =============================================================================
  998. // HELPER FUNCTIONS
  999. // =============================================================================
  1000. /**
  1001. * Get default action permissions based on menu type
  1002. */
  1003. function getDefaultPermissions(type: MenuType) {
  1004. switch (type) {
  1005. case 'DIRECTORY':
  1006. return { canView: 1, canCreate: 0, canUpdate: 0, canDelete: 0 };
  1007. case 'MENU':
  1008. case 'SUBMENU':
  1009. return { canView: 1, canCreate: 1, canUpdate: 1, canDelete: 1 };
  1010. case 'BUTTON':
  1011. return { canView: 0, canCreate: 1, canUpdate: 0, canDelete: 0 };
  1012. default:
  1013. return { canView: 0, canCreate: 0, canUpdate: 0, canDelete: 0 };
  1014. }
  1015. }
  1016. // =============================================================================
  1017. // SEED FUNCTIONS
  1018. // =============================================================================
  1019. /**
  1020. * Seed users and roles
  1021. */
  1022. async function seedUsers() {
  1023. console.log('📝 Seeding users and roles...');
  1024. await prisma.$transaction([
  1025. prisma.sysUserRole.deleteMany(),
  1026. prisma.sysUser.deleteMany(),
  1027. prisma.sysRole.deleteMany(),
  1028. ]);
  1029. // Upsert role by unique name
  1030. const roleCreateData: Prisma.SysRoleUncheckedCreateInput = {
  1031. id: '6946c613ea4266475e73d074',
  1032. name: '管理员',
  1033. status: true,
  1034. remark: '管理员专用',
  1035. };
  1036. const role = await prisma.sysRole.upsert({
  1037. where: { name: '管理员' },
  1038. update: { remark: '管理员专用', status: true },
  1039. create: roleCreateData,
  1040. });
  1041. // Upsert admin user by unique username
  1042. const adminUserCreateData: Prisma.SysUserUncheckedCreateInput = {
  1043. username: 'admin',
  1044. password: '$2b$12$iS0UJ1YqSal0N3uwin/OvOABUINAclcZGjHNyGFC7mlwRYTFjGQ26',
  1045. remark: '默认拥有所有菜单权限,不需要配置角色',
  1046. };
  1047. const adminUser = await prisma.sysUser.upsert({
  1048. where: { username: 'admin' },
  1049. // Do not overwrite password on existing user
  1050. update: { remark: '默认拥有所有菜单权限,不需要配置角色' },
  1051. create: adminUserCreateData,
  1052. });
  1053. // Link user and role if not already linked
  1054. const existingLink = await prisma.sysUserRole.findFirst({
  1055. where: { userId: adminUser.id, roleId: role.id },
  1056. });
  1057. if (!existingLink) {
  1058. const userRoleCreateData: Prisma.SysUserRoleUncheckedCreateInput = {
  1059. userId: adminUser.id,
  1060. roleId: role.id,
  1061. };
  1062. await prisma.sysUserRole.create({
  1063. data: userRoleCreateData,
  1064. });
  1065. }
  1066. console.log('✅ Users and roles seeded successfully');
  1067. console.log(` - Admin user: ${adminUser.username}`);
  1068. console.log(` - Role: ${role.name}`);
  1069. }
  1070. /**
  1071. * Seed menus with multi-pass insertion to handle parent-child relationships
  1072. */
  1073. async function seedMenus() {
  1074. console.log('📝 Seeding menus...');
  1075. await prisma.$runCommandRaw({
  1076. delete: 'sys_api_permission',
  1077. deletes: [{ q: {}, limit: 0 }],
  1078. });
  1079. await prisma.$runCommandRaw({
  1080. delete: 'sys_menu',
  1081. deletes: [{ q: {}, limit: 0 }],
  1082. });
  1083. const legacyIdToNewId = new Map<number, string>();
  1084. const inserted = new Set<number>();
  1085. // Multi-pass insertion: insert records whose parent is either null or already inserted
  1086. while (inserted.size < MENU_SEEDS.length) {
  1087. let progress = false;
  1088. for (const seed of MENU_SEEDS) {
  1089. if (inserted.has(seed.legacyId)) {
  1090. continue;
  1091. }
  1092. // If has a parent, but parent not inserted yet → skip this round
  1093. if (
  1094. seed.legacyParentId !== null &&
  1095. !legacyIdToNewId.has(seed.legacyParentId)
  1096. ) {
  1097. continue;
  1098. }
  1099. const parentId =
  1100. seed.legacyParentId === null
  1101. ? null
  1102. : legacyIdToNewId.get(seed.legacyParentId)!;
  1103. const permissions = getDefaultPermissions(seed.type);
  1104. const menuCreateData: Prisma.SysMenuUncheckedCreateInput = {
  1105. parentId,
  1106. title: seed.title,
  1107. status: true,
  1108. type: seed.type,
  1109. order: seed.order,
  1110. frontendAuth: seed.path,
  1111. path: seed.path,
  1112. name: seed.name,
  1113. icon: seed.icon,
  1114. redirect: null,
  1115. componentKey: seed.componentKey ?? null,
  1116. meta: seed.meta ?? undefined,
  1117. ...permissions,
  1118. };
  1119. const created = await prisma.sysMenu.upsert({
  1120. // Use unique index on frontendAuth to identify menu
  1121. where: { frontendAuth: seed.path },
  1122. update: {
  1123. parentId,
  1124. title: seed.title,
  1125. status: true,
  1126. type: seed.type,
  1127. order: seed.order,
  1128. // keep frontendAuth as path
  1129. path: seed.path,
  1130. name: seed.name,
  1131. icon: seed.icon,
  1132. redirect: null,
  1133. componentKey: seed.componentKey ?? null,
  1134. // meta undefined means do not overwrite; if provided, update
  1135. ...(seed.meta !== undefined
  1136. ? { meta: seed.meta as Prisma.InputJsonValue }
  1137. : {}),
  1138. ...permissions,
  1139. },
  1140. create: {
  1141. ...menuCreateData,
  1142. },
  1143. });
  1144. legacyIdToNewId.set(seed.legacyId, created.id);
  1145. inserted.add(seed.legacyId);
  1146. progress = true;
  1147. console.log(
  1148. ` ✓ Menu [${seed.type.padEnd(9)}] ${seed.name.padEnd(25)} → ID: ${created.id}`,
  1149. );
  1150. }
  1151. if (!progress) {
  1152. // Nothing could be inserted in this pass → some parentId is invalid or cyclic
  1153. const pending = MENU_SEEDS.filter((s) => !inserted.has(s.legacyId)).map(
  1154. (s) => ({
  1155. legacyId: s.legacyId,
  1156. legacyParentId: s.legacyParentId,
  1157. name: s.name,
  1158. }),
  1159. );
  1160. console.error('❌ Could not resolve parents for menus:', pending);
  1161. throw new Error(
  1162. 'Menu seeding aborted: unresolved parent relationships. Check legacyParentId values.',
  1163. );
  1164. }
  1165. }
  1166. console.log('✅ Menus seeded successfully');
  1167. console.log(` - Total menus: ${inserted.size}`);
  1168. }
  1169. // =============================================================================
  1170. // MAIN EXECUTION
  1171. // =============================================================================
  1172. async function main() {
  1173. const adsModules = [
  1174. {
  1175. adType: AdType.STARTUP,
  1176. adsModule: '启动页',
  1177. moduleDesc: '启动页(10:21)',
  1178. seq: 1,
  1179. },
  1180. {
  1181. adType: AdType.CAROUSEL,
  1182. adsModule: '轮播',
  1183. moduleDesc: '轮播(2:1)',
  1184. seq: 2,
  1185. },
  1186. {
  1187. adType: AdType.POPUP_ICON,
  1188. adsModule: '弹窗-图标',
  1189. moduleDesc: '弹窗-图标(1:1)',
  1190. seq: 3,
  1191. },
  1192. {
  1193. adType: AdType.POPUP_IMAGE,
  1194. adsModule: '弹窗-图片',
  1195. moduleDesc: '弹窗-图片(2:3)',
  1196. seq: 4,
  1197. },
  1198. {
  1199. adType: AdType.POPUP_OFFICIAL,
  1200. adsModule: '弹窗-官方',
  1201. moduleDesc: '弹窗-官方(2:3)',
  1202. seq: 5,
  1203. },
  1204. {
  1205. adType: AdType.WATERFALL_ICON,
  1206. adsModule: '瀑布流-图标',
  1207. moduleDesc: '瀑布流-图标(1:1)',
  1208. seq: 6,
  1209. },
  1210. {
  1211. adType: AdType.WATERFALL_TEXT,
  1212. adsModule: '瀑布流-文字',
  1213. moduleDesc: '瀑布流-文字',
  1214. seq: 7,
  1215. },
  1216. {
  1217. adType: AdType.WATERFALL_VIDEO,
  1218. adsModule: '瀑布流-视频',
  1219. moduleDesc: '瀑布流-视频(8:5)',
  1220. seq: 8,
  1221. },
  1222. {
  1223. adType: AdType.FLOATING_BOTTOM,
  1224. adsModule: '悬浮-底部',
  1225. moduleDesc: '悬浮-底部(1:1)',
  1226. seq: 9,
  1227. },
  1228. {
  1229. adType: AdType.FLOATING_EDGE,
  1230. adsModule: '悬浮-边缘',
  1231. moduleDesc: '悬浮-边缘(1:1)',
  1232. seq: 10,
  1233. },
  1234. {
  1235. adType: AdType.BANNER,
  1236. adsModule: 'banner',
  1237. moduleDesc: 'banner(4:1)',
  1238. seq: 11,
  1239. },
  1240. {
  1241. adType: AdType.PREROLL,
  1242. adsModule: '片头',
  1243. moduleDesc: '片头(8:5)',
  1244. seq: 12,
  1245. },
  1246. {
  1247. adType: AdType.PAUSE,
  1248. adsModule: '暂停',
  1249. moduleDesc: '暂停(2:1)',
  1250. seq: 13,
  1251. },
  1252. ];
  1253. console.log('🌱 Starting database seeding...\n');
  1254. try {
  1255. console.log('cleaning ads modules...');
  1256. await prisma.adsModule.deleteMany();
  1257. console.log('seeding ads modules...');
  1258. for (const module of adsModules) {
  1259. await prisma.adsModule.create({
  1260. data: module,
  1261. });
  1262. }
  1263. // Seed in order: users first, then menus
  1264. console.log('seeding users...');
  1265. await seedUsers();
  1266. console.log('');
  1267. console.log('seeding menus...');
  1268. await seedMenus();
  1269. console.log('');
  1270. console.log('seeding system config...');
  1271. await seedSysConfig();
  1272. console.log('\n🎉 Database seeding completed successfully!');
  1273. } catch (error) {
  1274. console.error('\n❌ Error during seeding:', error);
  1275. throw error;
  1276. }
  1277. }
  1278. main()
  1279. .then(async () => {
  1280. await prisma.$disconnect();
  1281. })
  1282. .catch(async (e) => {
  1283. console.error(e);
  1284. await prisma.$disconnect();
  1285. process.exit(1);
  1286. });