@gadmin2n/schematics 0.0.64 → 0.0.65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/dist/lib/application/files/gadmin2-game-angle-demo/.dockerignore +2 -2
  2. package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile +40 -26
  3. package/dist/lib/application/files/gadmin2-game-angle-demo/Jenkinsfile +30 -4
  4. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/example.prisma +33 -0
  5. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/system.prisma +163 -0
  6. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Event.ts +70 -0
  7. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Game.ts +6 -6
  8. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/ITActivityDay.ts +70 -0
  9. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Log.ts +2 -2
  10. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Role.ts +2 -2
  11. package/dist/lib/application/files/gadmin2-game-angle-demo/server/.env +20 -9
  12. package/dist/lib/application/files/gadmin2-game-angle-demo/server/.env.local +1 -0
  13. package/dist/lib/application/files/gadmin2-game-angle-demo/server/.eslintrc.js +1 -0
  14. package/dist/lib/application/files/gadmin2-game-angle-demo/server/.prettierignore +1 -0
  15. package/dist/lib/application/files/gadmin2-game-angle-demo/server/README.md +2 -18
  16. package/dist/lib/application/files/gadmin2-game-angle-demo/server/gadmin-cli.json +1 -1
  17. package/dist/lib/application/files/gadmin2-game-angle-demo/server/migrate-between-pg-schemas.js +1232 -0
  18. package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +65 -38
  19. package/dist/lib/application/files/gadmin2-game-angle-demo/server/prisma/.generator.prisma +4 -3
  20. package/dist/lib/application/files/gadmin2-game-angle-demo/server/prisma.config.ts +16 -0
  21. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/games.ts +1 -71
  22. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/index.ts +17 -21
  23. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/permissions.ts +278 -0
  24. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/seedDataMngtPages.ts +258 -0
  25. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/users.ts +7 -0
  26. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/alias.config.ts +7 -0
  27. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/app.controller.ts +151 -11
  28. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/app.module.ts +29 -13
  29. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/app.service.ts +151 -12
  30. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/auth.guard.ts +87 -41
  31. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/http-cache.interceptor.ts +21 -0
  32. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/logger.ts +19 -0
  33. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/safe-log.util.ts +176 -0
  34. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/{yufuid.ts → taihu.ts} +49 -34
  35. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/tracing.ts +174 -0
  36. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/trim.pipe.ts +51 -0
  37. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/utils.ts +91 -0
  38. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/woaAuth.ts +25 -12
  39. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/main.ts +22 -12
  40. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.controller.spec.ts +20 -0
  41. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.controller.ts +190 -0
  42. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.module.ts +10 -0
  43. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.service.spec.ts +338 -0
  44. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.service.ts +83 -0
  45. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.controller.spec.ts +20 -0
  46. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.controller.ts +188 -0
  47. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.module.ts +10 -0
  48. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.service.spec.ts +18 -0
  49. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.service.ts +83 -0
  50. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.controller.spec.ts +20 -0
  51. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.controller.ts +250 -0
  52. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.module.ts +10 -0
  53. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.service.spec.ts +18 -0
  54. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.service.ts +1051 -0
  55. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.controller.spec.ts +20 -0
  56. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.controller.ts +196 -0
  57. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.module.ts +13 -0
  58. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.service.spec.ts +18 -0
  59. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.service.ts +219 -0
  60. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.controller.spec.ts +20 -0
  61. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.controller.ts +196 -0
  62. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.module.ts +10 -0
  63. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.service.spec.ts +18 -0
  64. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.service.ts +199 -0
  65. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.controller.spec.ts +20 -0
  66. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.controller.ts +210 -0
  67. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.module.ts +12 -0
  68. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.service.spec.ts +18 -0
  69. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.service.ts +849 -0
  70. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/roles-refresher.service.ts +133 -0
  71. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.controller.spec.ts +20 -0
  72. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.controller.ts +196 -0
  73. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.module.ts +10 -0
  74. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.service.spec.ts +18 -0
  75. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.service.ts +201 -0
  76. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.controller.spec.ts +20 -0
  77. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.controller.ts +196 -0
  78. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.module.ts +10 -0
  79. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.service.spec.ts +18 -0
  80. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.service.ts +216 -0
  81. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.controller.spec.ts +20 -0
  82. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.controller.ts +198 -0
  83. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.module.ts +10 -0
  84. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.service.spec.ts +18 -0
  85. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.service.ts +104 -0
  86. package/dist/lib/application/files/gadmin2-game-angle-demo/server/start-prod.sh +130 -0
  87. package/dist/lib/application/files/gadmin2-game-angle-demo/server/tsconfig.json +18 -3
  88. package/dist/lib/application/files/gadmin2-game-angle-demo/web/index.html +19 -0
  89. package/dist/lib/application/files/gadmin2-game-angle-demo/web/package.json +34 -42
  90. package/dist/lib/application/files/gadmin2-game-angle-demo/web/postcss.config.cjs +6 -0
  91. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/App.tsx +111 -185
  92. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/auditLogProvider.ts +5 -5
  93. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/contexts/color-mode/index.tsx +49 -51
  94. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/custom-avatar.tsx +38 -0
  95. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/index.ts +4 -0
  96. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/{header/index.tsx → header.tsx} +22 -31
  97. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/index.ts +3 -1
  98. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/layout.tsx +32 -0
  99. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/logo.tsx +19 -0
  100. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/sider.tsx +331 -166
  101. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/title.tsx +61 -0
  102. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/pagination-total.tsx +21 -0
  103. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/tags/index.ts +1 -0
  104. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/tags/role-tag.tsx +44 -0
  105. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/text.tsx +74 -0
  106. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +258 -0
  107. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/constants/layout.ts +16 -0
  108. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/enums/audit-log.enum.ts +13 -0
  109. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/enums/index.ts +1 -0
  110. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/login.ts +22 -4
  111. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useDynamicResources.tsx +211 -0
  112. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useFetchData.ts +33 -0
  113. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useRoles.ts +30 -0
  114. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useUserPageAccess.ts +339 -0
  115. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/i18n.ts +8 -4
  116. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/index.tsx +3 -11
  117. package/dist/lib/application/files/gadmin2-game-angle-demo/web/{public → src}/locales/en/common.json +1 -1
  118. package/dist/lib/application/files/gadmin2-game-angle-demo/web/{public → src}/locales/zh_CN/common.json +1 -1
  119. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/components/action-cell.css +3 -0
  120. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/components/action-cell.tsx +134 -0
  121. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/create.tsx +113 -0
  122. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/edit.tsx +122 -0
  123. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/index.ts +8 -0
  124. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/index.tsx +6 -0
  125. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/list.tsx +213 -0
  126. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/show.tsx +61 -0
  127. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/AssignRolesModal.tsx +168 -0
  128. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/CreatePageModal.tsx +42 -0
  129. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/EditPageModal.tsx +42 -0
  130. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/PageDetailDrawer.tsx +101 -0
  131. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/PageFormModal.tsx +731 -0
  132. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/create.tsx +113 -0
  133. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/edit.tsx +122 -0
  134. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/hooks/usePageManagement.ts +36 -0
  135. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/index.ts +8 -0
  136. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/index.tsx +6 -0
  137. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/list.tsx +1113 -0
  138. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/queries.ts +17 -0
  139. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/show.tsx +61 -0
  140. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/types.ts +44 -0
  141. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/create.tsx +113 -0
  142. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/edit.tsx +122 -0
  143. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/index.tsx +6 -0
  144. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/list.tsx +243 -0
  145. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/show.tsx +61 -0
  146. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/permission-readme/index.tsx +1088 -0
  147. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/Components/CreateModal.tsx +25 -0
  148. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/Components/EditModal.tsx +28 -0
  149. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/Components/ResourceDetailDrawer.tsx +160 -0
  150. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/Components/modal.tsx +202 -0
  151. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/create.tsx +113 -0
  152. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/edit.tsx +122 -0
  153. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/index.ts +9 -0
  154. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/index.tsx +6 -0
  155. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/list.tsx +184 -0
  156. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/queries.ts +10 -0
  157. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/show.tsx +61 -0
  158. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/types.ts +9 -0
  159. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/Components/CreateModal.tsx +30 -0
  160. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/Components/EditModal.tsx +47 -0
  161. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/Components/RoleDetailDrawer.tsx +56 -0
  162. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/Components/modal.tsx +302 -0
  163. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/create.tsx +113 -0
  164. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/edit.tsx +122 -0
  165. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/hooks/useRolePage.ts +35 -0
  166. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/index.ts +8 -0
  167. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/index.tsx +6 -0
  168. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/list.tsx +382 -0
  169. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/queries.ts +8 -0
  170. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/show.tsx +61 -0
  171. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/types.ts +8 -0
  172. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/create.tsx +113 -0
  173. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/edit.tsx +122 -0
  174. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/index.tsx +6 -0
  175. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/list.tsx +243 -0
  176. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/show.tsx +61 -0
  177. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/create.tsx +113 -0
  178. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/edit.tsx +122 -0
  179. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/index.tsx +6 -0
  180. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/list.tsx +243 -0
  181. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/show.tsx +61 -0
  182. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/create-modal.tsx +17 -0
  183. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/edit-modal.tsx +19 -0
  184. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/form-modal.tsx +188 -0
  185. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/index.ts +5 -0
  186. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/role-tag.tsx +48 -0
  187. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/show-drawer.tsx +140 -0
  188. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/create.tsx +113 -0
  189. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/edit.tsx +122 -0
  190. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/index.ts +8 -0
  191. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/index.tsx +6 -0
  192. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/list.tsx +342 -0
  193. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/queries.ts +14 -0
  194. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/show.tsx +61 -0
  195. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/antd.css +132 -0
  196. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/fc.css +58 -0
  197. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/index.css +128 -0
  198. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/show-drawer.module.css +18 -0
  199. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/show-page.module.css +21 -0
  200. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/types/audit-log.ts +1 -0
  201. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/types/index.ts +3 -0
  202. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/types/role.ts +7 -0
  203. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/types/user.ts +1 -0
  204. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/utilities/get-name-initials.ts +8 -0
  205. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/utilities/get-random-color.ts +27 -0
  206. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/utilities/index.ts +2 -0
  207. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/utilities/utils.tsx +5 -0
  208. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/vite-env.d.ts +1 -0
  209. package/dist/lib/application/files/gadmin2-game-angle-demo/web/tsconfig.json +3 -3
  210. package/dist/lib/application/files/gadmin2-game-angle-demo/web/vite.config.ts +31 -0
  211. package/package.json +1 -1
  212. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/sample.prisma +0 -65
  213. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Source.ts +0 -76
  214. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Tasklog.ts +0 -76
  215. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/roles.ts +0 -4
  216. package/dist/lib/application/files/gadmin2-game-angle-demo/web/craco.config.js +0 -27
  217. package/dist/lib/application/files/gadmin2-game-angle-demo/web/public/index.html +0 -53
  218. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/styles.ts +0 -10
  219. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/react-app-env.d.ts +0 -1
  220. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/reportWebVitals.ts +0 -15
  221. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/antd.less +0 -79
  222. /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/VanillaJSONEditor/{index.js → index.jsx} +0 -0
@@ -0,0 +1,731 @@
1
+ import React, { useEffect, useMemo, useState } from "react";
2
+ import type { PageMenuTreeNode, PageResourceAssignment, ResourceDefinition } from "../types";
3
+ import { useModalForm } from "@refinedev/antd";
4
+ import { type HttpError, type RedirectAction } from "@refinedev/core";
5
+ import {
6
+ Form,
7
+ Input,
8
+ Modal,
9
+ Space,
10
+ Tag,
11
+ TreeSelect,
12
+ Typography,
13
+ Transfer,
14
+ Checkbox,
15
+ Table,
16
+ Divider,
17
+ } from "antd";
18
+ import type { TransferProps } from "antd";
19
+ import { PageFieldSelector } from "../queries";
20
+
21
+ type PageFormFieldValues = {
22
+ name?: string;
23
+ zhName?: string;
24
+ enName?: string;
25
+ code?: string;
26
+ path?: string | null;
27
+ isVirtual?: boolean;
28
+ parentId?: number | null;
29
+ description?: string;
30
+ iframeUrl?: string;
31
+ externalRedirectUrl?: string;
32
+ selectedResourceIds?: number[];
33
+ resourcePermissionAssignments?: Record<number, string[]>;
34
+ };
35
+
36
+ const AVAILABLE_PERMISSION_ACTIONS = ["readAny", "createAny", "updateAny", "deleteAny"];
37
+
38
+ const SPECIAL_CHARACTER_VALIDATION_PATTERN = /[^\u4e00-\u9fa5a-zA-Z0-9\s\-_/]/;
39
+
40
+ interface PageFormModalProps {
41
+ formActionType: "create" | "edit";
42
+ editingPageId?: number;
43
+ redirectAfterSubmit?: RedirectAction;
44
+ onFormSubmitSuccess?: () => void;
45
+ onModalCancel?: () => void;
46
+ parentPageTreeOptions?: PageMenuTreeNode[];
47
+ allAvailableResources?: ResourceDefinition[];
48
+ currentResourceAssignments?: PageResourceAssignment[];
49
+ allPagesData?: any[]; // All pages data for duplication check
50
+ defaultParentId?: number | null; // Default parent ID for create mode
51
+ defaultPathPrefix?: string; // Default path prefix for create mode
52
+ }
53
+
54
+ const buildPageTreeNodeLookupMap = (
55
+ treeNodes?: PageMenuTreeNode[],
56
+ ): Map<string, PageMenuTreeNode> => {
57
+ const nodeLookupMap = new Map<string, PageMenuTreeNode>();
58
+
59
+ const traverseAndAddToMap = (nodes?: PageMenuTreeNode[]) => {
60
+ nodes?.forEach(node => {
61
+ nodeLookupMap.set(String(node.value), node);
62
+ if (node.children?.length) {
63
+ traverseAndAddToMap(node.children);
64
+ }
65
+ });
66
+ };
67
+
68
+ traverseAndAddToMap(treeNodes);
69
+ return nodeLookupMap;
70
+ };
71
+
72
+ export const PageFormModal: React.FC<PageFormModalProps> = ({
73
+ formActionType,
74
+ editingPageId,
75
+ redirectAfterSubmit,
76
+ onModalCancel,
77
+ onFormSubmitSuccess,
78
+ parentPageTreeOptions,
79
+ allAvailableResources = [],
80
+ currentResourceAssignments = [],
81
+ allPagesData = [],
82
+ defaultParentId,
83
+ defaultPathPrefix = "",
84
+ }) => {
85
+ const { formProps, modalProps, onFinish, close } = useModalForm<
86
+ any,
87
+ HttpError,
88
+ PageFormFieldValues
89
+ >({
90
+ resource: "page",
91
+ action: formActionType,
92
+ id: editingPageId,
93
+ defaultVisible: true,
94
+ redirect: redirectAfterSubmit,
95
+ onMutationSuccess: () => {
96
+ onFormSubmitSuccess?.();
97
+ },
98
+ meta: {
99
+ select: PageFieldSelector,
100
+ },
101
+ });
102
+
103
+ // Watch form values for conditional field visibility
104
+ const isVirtual = Form.useWatch("isVirtual", formProps.form);
105
+ const iframeUrl = Form.useWatch("iframeUrl", formProps.form);
106
+ const externalRedirectUrl = Form.useWatch("externalRedirectUrl", formProps.form);
107
+ const hasExternalUrl = !!(
108
+ (iframeUrl && /^(https?:\/\/|\/\w)/.test(iframeUrl.trim())) ||
109
+ (externalRedirectUrl && /^https?:\/\//.test(externalRedirectUrl.trim()))
110
+ );
111
+
112
+ const [selectedResourceIds, setSelectedResourceIds] = useState<string[]>([]);
113
+ const [resourcePermissionMap, setResourcePermissionMap] = useState<Record<number, string[]>>({});
114
+ const [permissionValidationError, setPermissionValidationError] = useState<string>("");
115
+ const [initialResourceAssignments, setInitialResourceAssignments] = useState<{
116
+ resourceIds: string[];
117
+ permissionMap: Record<number, string[]>;
118
+ }>({ resourceIds: [], permissionMap: {} });
119
+ const [initialFormValues, setInitialFormValues] = useState<PageFormFieldValues>({});
120
+ const [isCodeManuallyEdited, setIsCodeManuallyEdited] = useState(false);
121
+ const [isPathManuallyEdited, setIsPathManuallyEdited] = useState(false);
122
+
123
+ // Set default values for create mode with parent
124
+ useEffect(() => {
125
+ if (formActionType === "create" && formProps.form) {
126
+ const initialValues: Partial<PageFormFieldValues> = {};
127
+
128
+ if (defaultParentId) {
129
+ initialValues.parentId = defaultParentId;
130
+ }
131
+ if (defaultPathPrefix) {
132
+ initialValues.path = defaultPathPrefix;
133
+ }
134
+
135
+ if (Object.keys(initialValues).length > 0) {
136
+ formProps.form.setFieldsValue(initialValues);
137
+ }
138
+ }
139
+ }, [formActionType, defaultParentId, defaultPathPrefix, formProps.form]);
140
+
141
+ useEffect(() => {
142
+ if (formActionType === "edit" && currentResourceAssignments.length > 0) {
143
+ const initialSelectedIds = currentResourceAssignments.map(assignment =>
144
+ String(assignment.resourceId),
145
+ );
146
+ setSelectedResourceIds(initialSelectedIds);
147
+
148
+ const initialPermissionMap: Record<number, string[]> = {};
149
+ currentResourceAssignments.forEach(assignment => {
150
+ initialPermissionMap[assignment.resourceId] = [...assignment.assignedPermissions];
151
+ });
152
+ setResourcePermissionMap(initialPermissionMap);
153
+
154
+ // Store initial state for comparison
155
+ setInitialResourceAssignments({
156
+ resourceIds: [...initialSelectedIds],
157
+ permissionMap: JSON.parse(JSON.stringify(initialPermissionMap)),
158
+ });
159
+ }
160
+ }, [formActionType, currentResourceAssignments]);
161
+
162
+ // Store initial form values when form is loaded in edit mode
163
+ useEffect(() => {
164
+ if (formActionType === "edit" && formProps.form) {
165
+ const currentValues = formProps.form.getFieldsValue();
166
+ setInitialFormValues({
167
+ name: currentValues.name,
168
+ zhName: currentValues.zhName,
169
+ enName: currentValues.enName,
170
+ code: currentValues.code,
171
+ path: currentValues.path,
172
+ isVirtual: currentValues.isVirtual,
173
+ parentId: currentValues.parentId,
174
+ description: currentValues.description,
175
+ iframeUrl: currentValues.iframeUrl,
176
+ externalRedirectUrl: currentValues.externalRedirectUrl,
177
+ });
178
+ }
179
+ }, [formActionType, formProps.form, formProps.initialValues]);
180
+
181
+ const resourceTransferDataSource = useMemo(() => {
182
+ return allAvailableResources.map(resource => ({
183
+ key: String(resource.id),
184
+ title: resource.name,
185
+ description: resource.description || "",
186
+ }));
187
+ }, [allAvailableResources]);
188
+
189
+ const handleResourceTransferChange: TransferProps["onChange"] = newTargetKeys => {
190
+ setSelectedResourceIds(newTargetKeys as string[]);
191
+
192
+ const newResourceIds = new Set(newTargetKeys.map(key => Number(key)));
193
+ setResourcePermissionMap(prevMap => {
194
+ const updatedMap: Record<number, string[]> = {};
195
+ Object.keys(prevMap).forEach(resourceIdStr => {
196
+ const resourceId = Number(resourceIdStr);
197
+ if (newResourceIds.has(resourceId)) {
198
+ updatedMap[resourceId] = prevMap[resourceId];
199
+ }
200
+ });
201
+ newTargetKeys.forEach(key => {
202
+ const resourceId = Number(key);
203
+ if (!updatedMap[resourceId]) {
204
+ updatedMap[resourceId] = []; // Initialize with empty array, user must select at least one permission
205
+ }
206
+ });
207
+ return updatedMap;
208
+ });
209
+
210
+ // Clear validation error when user changes resource selection
211
+ if (permissionValidationError) {
212
+ setPermissionValidationError("");
213
+ }
214
+ };
215
+
216
+ const handleResourcePermissionChange = (
217
+ resourceId: number,
218
+ permissionAction: string,
219
+ isChecked: boolean,
220
+ ) => {
221
+ setResourcePermissionMap(prevMap => {
222
+ const currentPermissions = prevMap[resourceId] || [];
223
+ let updatedPermissions: string[];
224
+
225
+ if (isChecked) {
226
+ updatedPermissions = [...currentPermissions, permissionAction];
227
+ } else {
228
+ updatedPermissions = currentPermissions.filter(action => action !== permissionAction);
229
+ }
230
+
231
+ return {
232
+ ...prevMap,
233
+ [resourceId]: updatedPermissions,
234
+ };
235
+ });
236
+
237
+ // Clear validation error when user makes changes
238
+ if (permissionValidationError) {
239
+ setPermissionValidationError("");
240
+ }
241
+ };
242
+
243
+ const filteredParentPageTreeOptions = useMemo(() => {
244
+ if (formActionType !== "edit" || !editingPageId || !parentPageTreeOptions) {
245
+ return parentPageTreeOptions;
246
+ }
247
+
248
+ const filterOutCurrentPage = (nodes: PageMenuTreeNode[]): PageMenuTreeNode[] => {
249
+ return nodes
250
+ .filter(node => String(node.value) !== String(editingPageId))
251
+ .map(node => ({
252
+ ...node,
253
+ children: node.children ? filterOutCurrentPage(node.children) : undefined,
254
+ }));
255
+ };
256
+
257
+ return filterOutCurrentPage(parentPageTreeOptions);
258
+ }, [parentPageTreeOptions, formActionType, editingPageId]);
259
+
260
+ formProps.onFinish = values => {
261
+ // Clear path when external URL is configured (iframe or redirect)
262
+ if (!hasExternalUrl) {
263
+ values.path = null;
264
+ }
265
+
266
+ // Validate that each selected resource has at least one permission
267
+ const resourcesWithoutPermissions: string[] = [];
268
+ selectedResourceIds.forEach(resourceIdStr => {
269
+ const resourceId = Number(resourceIdStr);
270
+ const permissions = resourcePermissionMap[resourceId] || [];
271
+ if (permissions.length === 0) {
272
+ const resource = allAvailableResources.find(r => r.id === resourceId);
273
+ resourcesWithoutPermissions.push(resource?.name || `Resource ${resourceId}`);
274
+ }
275
+ });
276
+
277
+ if (resourcesWithoutPermissions.length > 0) {
278
+ setPermissionValidationError(
279
+ `The following resources must have at least one permission selected: ${resourcesWithoutPermissions.join(", ")}`,
280
+ );
281
+ return;
282
+ }
283
+
284
+ // In edit mode, check if anything has changed
285
+ if (formActionType === "edit") {
286
+ // Check if form fields have changed
287
+ const formFieldsChanged =
288
+ values.name !== initialFormValues.name ||
289
+ values.zhName !== initialFormValues.zhName ||
290
+ values.enName !== initialFormValues.enName ||
291
+ values.code !== initialFormValues.code ||
292
+ values.path !== initialFormValues.path ||
293
+ values.description !== initialFormValues.description ||
294
+ values.isVirtual !== initialFormValues.isVirtual ||
295
+ values.parentId !== initialFormValues.parentId ||
296
+ values.iframeUrl !== initialFormValues.iframeUrl ||
297
+ values.externalRedirectUrl !== initialFormValues.externalRedirectUrl;
298
+
299
+ // Check if resource assignments have changed
300
+ const resourceIdsChanged =
301
+ selectedResourceIds.length !== initialResourceAssignments.resourceIds.length ||
302
+ selectedResourceIds.some(id => !initialResourceAssignments.resourceIds.includes(id));
303
+
304
+ let permissionsChanged = false;
305
+ if (!resourceIdsChanged) {
306
+ // Check if permissions for any resource have changed
307
+ for (const resourceIdStr of selectedResourceIds) {
308
+ const resourceId = Number(resourceIdStr);
309
+ const currentPermissions = resourcePermissionMap[resourceId] || [];
310
+ const initialPermissions = initialResourceAssignments.permissionMap[resourceId] || [];
311
+
312
+ if (
313
+ currentPermissions.length !== initialPermissions.length ||
314
+ currentPermissions.some(p => !initialPermissions.includes(p))
315
+ ) {
316
+ permissionsChanged = true;
317
+ break;
318
+ }
319
+ }
320
+ }
321
+
322
+ // If nothing has changed, don't submit
323
+ if (!formFieldsChanged && !resourceIdsChanged && !permissionsChanged) {
324
+ Modal.info({
325
+ title: "No Changes Detected",
326
+ content:
327
+ "No changes have been made to the form. The form will be closed without submitting.",
328
+ });
329
+ close();
330
+ return;
331
+ }
332
+
333
+ // Build the submit payload with only changed data
334
+ const formattedValuesForSubmit: any = {
335
+ ...values,
336
+ parentId: values.parentId ? Number(values.parentId) : null,
337
+ };
338
+
339
+ // Only include resourceAssignments if they changed
340
+ if (resourceIdsChanged || permissionsChanged) {
341
+ formattedValuesForSubmit.resourceAssignments = selectedResourceIds.map(resourceIdStr => {
342
+ const resourceId = Number(resourceIdStr);
343
+ const resource = allAvailableResources.find(r => r.id === resourceId);
344
+ return {
345
+ resourceId,
346
+ resourceName: resource?.name || "",
347
+ assignedPermissions: resourcePermissionMap[resourceId] || [],
348
+ };
349
+ });
350
+ }
351
+ onFinish(formattedValuesForSubmit);
352
+ } else {
353
+ // In create mode, always submit all data
354
+ const resourceAssignmentsForSubmit = selectedResourceIds.map(resourceIdStr => {
355
+ const resourceId = Number(resourceIdStr);
356
+ const resource = allAvailableResources.find(r => r.id === resourceId);
357
+ return {
358
+ resourceId,
359
+ resourceName: resource?.name || "",
360
+ assignedPermissions: resourcePermissionMap[resourceId] || [],
361
+ };
362
+ });
363
+
364
+ const formattedValuesForSubmit = {
365
+ ...values,
366
+ parentId: values.parentId ? Number(values.parentId) : null,
367
+ resourceAssignments: resourceAssignmentsForSubmit,
368
+ };
369
+
370
+ onFinish(formattedValuesForSubmit);
371
+ }
372
+ };
373
+
374
+ const resourcePermissionTableColumns = [
375
+ {
376
+ title: "Resource Name",
377
+ dataIndex: "resourceName",
378
+ key: "resourceName",
379
+ width: "30%",
380
+ },
381
+ {
382
+ title: "Permissions",
383
+ key: "permissions",
384
+ render: (_: any, record: { resourceId: number; resourceName: string }) => (
385
+ <Space>
386
+ {AVAILABLE_PERMISSION_ACTIONS.map(permissionAction => (
387
+ <Checkbox
388
+ key={`${record.resourceId}-${permissionAction}`}
389
+ checked={resourcePermissionMap[record.resourceId]?.includes(permissionAction)}
390
+ onChange={e =>
391
+ handleResourcePermissionChange(
392
+ record.resourceId,
393
+ permissionAction,
394
+ e.target.checked,
395
+ )
396
+ }
397
+ >
398
+ {permissionAction}
399
+ </Checkbox>
400
+ ))}
401
+ </Space>
402
+ ),
403
+ },
404
+ ];
405
+
406
+ const selectedResourcesForPermissionTable = useMemo(() => {
407
+ return selectedResourceIds.map(resourceIdStr => {
408
+ const resourceId = Number(resourceIdStr);
409
+ const resource = allAvailableResources.find(r => r.id === resourceId);
410
+ return {
411
+ resourceId,
412
+ resourceName: resource?.name || `Resource ${resourceId}`,
413
+ };
414
+ });
415
+ }, [selectedResourceIds, allAvailableResources]);
416
+
417
+ return (
418
+ <Modal
419
+ {...modalProps}
420
+ width={800}
421
+ onCancel={() => {
422
+ if (onModalCancel) {
423
+ onModalCancel();
424
+ return;
425
+ }
426
+ close();
427
+ }}
428
+ title={formActionType === "create" ? "Create New Page" : "Edit Page"}
429
+ >
430
+ <Form {...formProps} layout="vertical">
431
+ <Form.Item
432
+ rules={[
433
+ { required: true, message: "Page name is required" },
434
+ {
435
+ validator: (_, value) => {
436
+ if (!value || !SPECIAL_CHARACTER_VALIDATION_PATTERN.test(value)) {
437
+ return Promise.resolve();
438
+ }
439
+ return Promise.reject(
440
+ new Error("Special characters are not allowed in page name."),
441
+ );
442
+ },
443
+ },
444
+ {
445
+ validator: async (_, value) => {
446
+ if (!value) {
447
+ return Promise.resolve();
448
+ }
449
+
450
+ const trimmedValue = value.trim();
451
+
452
+ // Check if page name already exists (case-insensitive)
453
+ const isDuplicate = allPagesData.some((page: any) => {
454
+ // Skip current page when editing
455
+ if (formActionType === "edit" && page.id === editingPageId) {
456
+ return false;
457
+ }
458
+ return page.name?.toLowerCase() === trimmedValue.toLowerCase();
459
+ });
460
+
461
+ if (isDuplicate) {
462
+ return Promise.reject(
463
+ new Error(
464
+ "⚠️ Warning: Page Name already exists. It's recommended to use a unique name.",
465
+ ),
466
+ );
467
+ }
468
+
469
+ return Promise.resolve();
470
+ },
471
+ },
472
+ ]}
473
+ name="name"
474
+ label="Page Name"
475
+ >
476
+ <Input
477
+ placeholder="Enter the page name (e.g., User Management)."
478
+ onChange={e => {
479
+ const nameValue = e.target.value;
480
+ // Auto-generate code from name if code hasn't been manually edited
481
+ if (!isCodeManuallyEdited && nameValue) {
482
+ const generatedCode = nameValue
483
+ .trim()
484
+ .toLowerCase()
485
+ .replace(/[\s-]+/g, "_") // Replace spaces and hyphens with underscores
486
+ .replace(/[^a-z0-9_]/g, ""); // Remove invalid characters
487
+ formProps.form?.setFieldsValue({ code: generatedCode });
488
+ }
489
+ // Auto-generate path from name if path hasn't been manually edited (only in create mode)
490
+ if (!isPathManuallyEdited && nameValue && formActionType === "create") {
491
+ const generatedPathSegment = nameValue
492
+ .trim()
493
+ .toLowerCase()
494
+ .replace(/[\s_]+/g, "-") // Replace spaces and underscores with hyphens
495
+ .replace(/[^a-z0-9-/]/g, ""); // Remove invalid characters
496
+ const newPath = defaultPathPrefix + generatedPathSegment;
497
+ formProps.form?.setFieldsValue({ path: newPath });
498
+ }
499
+ }}
500
+ />
501
+ </Form.Item>
502
+ <Form.Item
503
+ rules={[
504
+ { required: true, message: "Page Code is required" },
505
+ {
506
+ pattern: /^[a-zA-Z0-9_]+$/,
507
+ message: "Page Code can only contain letters, numbers and underscores",
508
+ },
509
+ {
510
+ validator: async (_, value) => {
511
+ if (!value) {
512
+ return Promise.resolve();
513
+ }
514
+
515
+ const trimmedValue = value.trim();
516
+
517
+ // Check if page code already exists (case-insensitive)
518
+ const isDuplicate = allPagesData.some((page: any) => {
519
+ // Skip current page when editing
520
+ if (formActionType === "edit" && page.id === editingPageId) {
521
+ return false;
522
+ }
523
+ return page.code?.toLowerCase() === trimmedValue.toLowerCase();
524
+ });
525
+
526
+ if (isDuplicate) {
527
+ return Promise.reject(
528
+ new Error("Page Code already exists. Please use a different code."),
529
+ );
530
+ }
531
+
532
+ return Promise.resolve();
533
+ },
534
+ },
535
+ ]}
536
+ name="code"
537
+ label="Page Code(建议使用根据Page Name自动生成的值。一旦保存,不建议修改,因为前端会使用它作为菜单标识)"
538
+ >
539
+ <Input
540
+ placeholder="Enter the page code (e.g., user_management)"
541
+ onChange={() => {
542
+ // Mark as manually edited when user types in this field
543
+ if (!isCodeManuallyEdited) {
544
+ setIsCodeManuallyEdited(true);
545
+ }
546
+ }}
547
+ />
548
+ </Form.Item>
549
+ <Form.Item name="isVirtual" valuePropName="checked">
550
+ <Checkbox>Is Virtual Page</Checkbox>
551
+ </Form.Item>
552
+ {!isVirtual && (
553
+ <>
554
+ <Form.Item name="parentId" label="Parent Page (Menu Position)">
555
+ {filteredParentPageTreeOptions?.length ? (
556
+ <TreeSelect
557
+ style={{ width: "100%" }}
558
+ treeData={filteredParentPageTreeOptions}
559
+ treeDefaultExpandAll
560
+ allowClear
561
+ placeholder="Select parent page (leave empty for root level)"
562
+ />
563
+ ) : (
564
+ <>
565
+ <Typography.Text type="secondary">
566
+ No parent pages available. This page will be created at root level.
567
+ </Typography.Text>
568
+ <Input type="hidden" />
569
+ </>
570
+ )}
571
+ </Form.Item>
572
+ <Form.Item
573
+ name="zhName"
574
+ label="Chinese Name (中文菜单名)"
575
+ tooltip="用于菜单显示的中文名称"
576
+ >
577
+ <Input placeholder="输入中文菜单名 (例如: 用户管理)" />
578
+ </Form.Item>
579
+ <Form.Item
580
+ name="enName"
581
+ label="English Name (英文菜单名)"
582
+ tooltip="Menu display name in English"
583
+ >
584
+ <Input placeholder="Enter English menu name (e.g., User Management)" />
585
+ </Form.Item>
586
+ <Form.Item
587
+ name="iframeUrl"
588
+ label={"Iframe URL"}
589
+ tooltip={{
590
+ title: (
591
+ <div>
592
+ <p style={{ marginBottom: 8 }}>
593
+ 设置后,页面将以 iframe 形式加载该 URL,而非内部组件渲染。
594
+ </p>
595
+ <p style={{ marginBottom: 4 }}>
596
+ <strong>支持的模板变量:</strong>
597
+ </p>
598
+ <ul style={{ margin: 0, paddingLeft: 16 }}>
599
+ <li>
600
+ <code>{"{{locale}}"}</code> → 当前语言 (en/zh)
601
+ </li>
602
+ </ul>
603
+ <p style={{ marginTop: 8 }}>
604
+ 支持完整 URL 或相对路径(如 /path/to/page)
605
+ </p>
606
+ </div>
607
+ ),
608
+ overlayStyle: { maxWidth: 320 },
609
+ }}
610
+ dependencies={["externalRedirectUrl"]}
611
+ rules={[
612
+ {
613
+ validator: (_, value) => {
614
+ if (value?.trim() && externalRedirectUrl?.trim()) {
615
+ return Promise.reject(new Error("不能同时填写 Iframe URL 和 External Redirect URL,请只填其中一个"));
616
+ }
617
+ return Promise.resolve();
618
+ },
619
+ },
620
+ ]}
621
+ >
622
+ <Input placeholder="e.g. https://example.com/page or /relative/path" />
623
+ </Form.Item>
624
+ <Form.Item
625
+ name="externalRedirectUrl"
626
+ dependencies={["iframeUrl"]}
627
+ label={"External Redirect URL"}
628
+ tooltip={{
629
+ title: <p style={{ marginBottom: 8 }}>设置后,页面将打开一个新标签并跳转到指定的外部链接。</p>,
630
+ overlayStyle: { maxWidth: 320 },
631
+ }}
632
+ rules={[
633
+ {
634
+ type: "url",
635
+ message: "Please enter a valid URL",
636
+ },
637
+ {
638
+ validator: (_, value) => {
639
+ if (value?.trim() && iframeUrl?.trim()) {
640
+ return Promise.reject(new Error("不能同时填写 Iframe URL 和 External Redirect URL,请只填其中一个"));
641
+ }
642
+ return Promise.resolve();
643
+ },
644
+ },
645
+ ]}
646
+ >
647
+ <Input placeholder="e.g. https://oit-erp.woa.com" />
648
+ </Form.Item>
649
+ {hasExternalUrl && (
650
+ <Form.Item
651
+ rules={[
652
+ {
653
+ validator: (_, value) => {
654
+ if (!value || !SPECIAL_CHARACTER_VALIDATION_PATTERN.test(value)) {
655
+ return Promise.resolve();
656
+ }
657
+ return Promise.reject(
658
+ new Error("Special characters are not allowed in path.")
659
+ );
660
+ },
661
+ },
662
+ ]}
663
+ name="path"
664
+ label="Page Path"
665
+ tooltip="仅当前端 routeRegistry 中未注册该 page code 时需要填写(如 iframe/外部链接页面)。前端已注册的页面会自动匹配路径,无需手动填写。"
666
+ >
667
+ <Input
668
+ placeholder="Enter the URL path (e.g., /administration/page-management)"
669
+ onChange={() => {
670
+ // Mark as manually edited when user types in this field
671
+ if (!isPathManuallyEdited) {
672
+ setIsPathManuallyEdited(true);
673
+ }
674
+ }}
675
+ />
676
+ </Form.Item>
677
+ )}
678
+ </>
679
+ )}
680
+ <Form.Item name="description" label="Page Description">
681
+ <Input.TextArea rows={2} placeholder="Enter a brief description of this page's purpose" />
682
+ </Form.Item>
683
+ <Divider>Resource Assignment & Permissions</Divider>
684
+ <Form.Item label="Select Resources to Assign">
685
+ <Transfer
686
+ dataSource={resourceTransferDataSource}
687
+ titles={["Available Resources", "Assigned Resources"]}
688
+ targetKeys={selectedResourceIds}
689
+ onChange={handleResourceTransferChange}
690
+ render={item => item.title}
691
+ showSearch
692
+ filterOption={(inputValue, item) =>
693
+ item.title.toLowerCase().includes(inputValue.toLowerCase()) ||
694
+ item.description.toLowerCase().includes(inputValue.toLowerCase())
695
+ }
696
+ listStyle={{ width: 280, height: 250 }}
697
+ />
698
+ </Form.Item>
699
+ {selectedResourceIds.length > 0 && (
700
+ <Form.Item
701
+ label="Configure Permissions for Each Resource"
702
+ validateStatus={permissionValidationError ? "error" : ""}
703
+ help={permissionValidationError || ""}
704
+ >
705
+ <Table
706
+ dataSource={selectedResourcesForPermissionTable}
707
+ columns={resourcePermissionTableColumns}
708
+ rowKey="resourceId"
709
+ pagination={false}
710
+ size="small"
711
+ bordered
712
+ />
713
+ </Form.Item>
714
+ )}
715
+ {formActionType === "edit" && currentResourceAssignments.length > 0 && (
716
+ <Form.Item label="Current Permission Overview">
717
+ <Space wrap>
718
+ {Array.from(
719
+ new Set(currentResourceAssignments.flatMap(a => a.assignedPermissions)),
720
+ ).map(permissionAction => (
721
+ <Tag key={permissionAction} color="blue">
722
+ {permissionAction}
723
+ </Tag>
724
+ ))}
725
+ </Space>
726
+ </Form.Item>
727
+ )}
728
+ </Form>
729
+ </Modal>
730
+ );
731
+ };