@gadmin2n/schematics 0.0.64 → 0.0.66
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.
- package/dist/lib/application/files/gadmin2-game-angle-demo/.dockerignore +2 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile +40 -26
- package/dist/lib/application/files/gadmin2-game-angle-demo/Jenkinsfile +30 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/example.prisma +33 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/system.prisma +170 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Event.ts +70 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Game.ts +6 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/ITActivityDay.ts +70 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Log.ts +2 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Role.ts +2 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/.env +20 -9
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/.env.local +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/.eslintrc.js +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/.prettierignore +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/README.md +2 -18
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/gadmin-cli.json +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/migrate-between-pg-schemas.js +1232 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +65 -38
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/prisma/.generator.prisma +4 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/prisma.config.ts +16 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/games.ts +1 -71
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/index.ts +17 -21
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/permissions.ts +278 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/seedDataMngtPages.ts +258 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/users.ts +7 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/alias.config.ts +7 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/app.controller.ts +151 -11
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/app.module.ts +29 -13
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/app.service.ts +151 -12
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/auth.guard.ts +87 -41
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/http-cache.interceptor.ts +21 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/logger.ts +19 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/safe-log.util.ts +176 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/{yufuid.ts → taihu.ts} +49 -34
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/tracing.ts +174 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/trim.pipe.ts +51 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/utils.ts +91 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/woaAuth.ts +25 -12
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/main.ts +22 -12
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.controller.spec.ts +20 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.controller.ts +190 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.module.ts +10 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.service.spec.ts +338 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.service.ts +83 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.controller.spec.ts +20 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.controller.ts +188 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.module.ts +10 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.service.spec.ts +18 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.service.ts +83 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.controller.spec.ts +20 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.controller.ts +250 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.module.ts +10 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.service.spec.ts +18 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.service.ts +1051 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.controller.spec.ts +20 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.controller.ts +196 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.module.ts +13 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.service.spec.ts +18 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.service.ts +219 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.controller.spec.ts +20 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.controller.ts +196 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.module.ts +10 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.service.spec.ts +18 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.service.ts +199 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.controller.spec.ts +20 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.controller.ts +210 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.module.ts +12 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.service.spec.ts +18 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.service.ts +849 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/roles-refresher.service.ts +133 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.controller.spec.ts +20 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.controller.ts +196 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.module.ts +10 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.service.spec.ts +18 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.service.ts +201 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.controller.spec.ts +20 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.controller.ts +196 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.module.ts +10 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.service.spec.ts +18 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.service.ts +216 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.controller.spec.ts +20 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.controller.ts +198 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.module.ts +10 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.service.spec.ts +18 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.service.ts +104 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/start-prod.sh +130 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/tsconfig.json +18 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/index.html +19 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/package.json +34 -42
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/postcss.config.cjs +6 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/App.tsx +111 -185
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/auditLogProvider.ts +5 -5
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/authProvider.ts +2 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/SqlModal.tsx +419 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/contexts/business/index.tsx +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/contexts/color-mode/index.tsx +49 -51
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/custom-avatar.tsx +38 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/index.ts +4 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/{header/index.tsx → header.tsx} +22 -31
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/index.ts +3 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/layout.tsx +32 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/logo.tsx +19 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/sider.tsx +331 -166
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/title.tsx +61 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/pagination-total.tsx +21 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/tags/index.ts +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/tags/role-tag.tsx +44 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/text.tsx +74 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/http.ts +28 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +258 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/constants/layout.ts +16 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/enums/audit-log.enum.ts +13 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/enums/index.ts +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/get-name-initials.ts +8 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/get-random-color.ts +27 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/http.ts +87 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/index.tsx +6 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/login.ts +22 -59
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/utils.tsx +5 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useDynamicResources.tsx +211 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useFetchData.ts +33 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useRoles.ts +30 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useUserPageAccess.ts +339 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/i18n.ts +8 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/index.tsx +3 -11
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/{public → src}/locales/en/common.json +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/{public → src}/locales/zh_CN/common.json +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/components/action-cell.css +3 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/components/action-cell.tsx +134 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/create.tsx +113 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/edit.tsx +122 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/index.ts +8 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/index.tsx +6 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/list.tsx +213 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/show.tsx +61 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/AssignRolesModal.tsx +168 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/CreatePageModal.tsx +42 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/EditPageModal.tsx +42 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/PageDetailDrawer.tsx +101 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/PageFormModal.tsx +731 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/hooks/usePageManagement.ts +36 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/index.ts +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/list.tsx +1215 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/queries.ts +17 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/types.ts +45 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/permissionReadme/index.tsx +1089 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/Components/CreateModal.tsx +25 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/Components/EditModal.tsx +28 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/Components/ResourceDetailDrawer.tsx +160 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/Components/modal.tsx +202 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/index.ts +2 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/list.tsx +212 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/queries.ts +10 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/types.ts +9 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/Components/CreateModal.tsx +30 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/Components/EditModal.tsx +47 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/Components/RoleDetailDrawer.tsx +56 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/Components/modal.tsx +302 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/hooks/useRolePage.ts +35 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/index.ts +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/list.tsx +431 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/queries.ts +8 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/types.ts +8 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/create-modal.tsx +17 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/edit-modal.tsx +19 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/form-modal.tsx +188 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/index.ts +5 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/role-tag.tsx +48 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/show-drawer.tsx +140 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/index.ts +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/list.tsx +372 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/queries.ts +14 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/antd.css +132 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/fc.css +58 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/index.css +128 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/show-drawer.module.css +18 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/show-page.module.css +21 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/types/audit-log.ts +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/types/index.ts +3 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/types/role.ts +7 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/types/user.ts +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/vite-env.d.ts +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/tsconfig.json +5 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/vite.config.ts +31 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/yarn.lock +8321 -0
- package/package.json +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/sample.prisma +0 -65
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Source.ts +0 -76
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Tasklog.ts +0 -76
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/roles.ts +0 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/craco.config.js +0 -27
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/public/index.html +0 -53
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/styles.ts +0 -10
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/utils.ts +0 -76
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/react-app-env.d.ts +0 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/reportWebVitals.ts +0 -15
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/antd.less +0 -79
- /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/VanillaJSONEditor/{index.js → index.jsx} +0 -0
package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useDynamicResources.tsx
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { useMemo, useRef } from "react";
|
|
2
|
+
import type { IResourceItem, ResourceProps } from "@refinedev/core";
|
|
3
|
+
import {
|
|
4
|
+
DashboardOutlined,
|
|
5
|
+
SettingOutlined,
|
|
6
|
+
} from "@ant-design/icons";
|
|
7
|
+
import { getRoutePath } from "config/routeRegistry";
|
|
8
|
+
import { useUserPageAccess } from "./useUserPageAccess";
|
|
9
|
+
import generatedResources from "generated/resources";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Dynamic Resources Hook for erp-backend/web
|
|
13
|
+
*
|
|
14
|
+
* Features:
|
|
15
|
+
* 1. Get user accessible page resources from backend (via useUserPageAccess)
|
|
16
|
+
* 2. Convert backend data to Refine's IResourceItem[] format
|
|
17
|
+
* 3. Auto-generate show/edit/create paths for all pages
|
|
18
|
+
*
|
|
19
|
+
* Data Flow:
|
|
20
|
+
* useUserPageAccess → Accessible Pages → Resources → Sider Menu
|
|
21
|
+
*
|
|
22
|
+
* Performance:
|
|
23
|
+
* - Uses module-level cache - computed ONCE per page load
|
|
24
|
+
* - Returns same reference on every call after initial computation
|
|
25
|
+
* - Does NOT rebuild on route navigation or component re-renders
|
|
26
|
+
* - Permission changes require page refresh (handled by user)
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
// Module-level cache - computed once per page load
|
|
30
|
+
let cachedResources: IResourceItem[] | null = null;
|
|
31
|
+
let isComputing = false;
|
|
32
|
+
|
|
33
|
+
// 将 snake_case 转换为 camelCase
|
|
34
|
+
function snakeToCamel(str: string): string {
|
|
35
|
+
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Icon 映射:page.code 到 React Icon 组件
|
|
39
|
+
export const iconMapping: Record<string, React.ReactNode> = {
|
|
40
|
+
dashboard: <DashboardOutlined />,
|
|
41
|
+
administration: <SettingOutlined />,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Pages whose children are tabs (not separate menu items)
|
|
45
|
+
const PAGES_WITH_TABS: string[] = [];
|
|
46
|
+
|
|
47
|
+
export const useDynamicResources = (): IResourceItem[] => {
|
|
48
|
+
// Use shared page access data (single call, reused below)
|
|
49
|
+
const { accessiblePageIds, pagesData, pageMap, isLoading, roleNames } = useUserPageAccess();
|
|
50
|
+
|
|
51
|
+
// If we already have cached resources, return immediately
|
|
52
|
+
if (cachedResources !== null) {
|
|
53
|
+
return cachedResources;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Return empty array while loading (will retry on next render)
|
|
57
|
+
if (isLoading || !pagesData || isComputing) {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Mark as computing to prevent concurrent computation
|
|
62
|
+
isComputing = true;
|
|
63
|
+
|
|
64
|
+
if (import.meta.env.DEV) {
|
|
65
|
+
console.log('[useDynamicResources] 🔨 Computing resources (first time)');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Filter accessible pages and build hierarchical sorted list
|
|
69
|
+
const accessiblePages = pagesData.filter(page => accessiblePageIds.has(page.id));
|
|
70
|
+
|
|
71
|
+
// Group pages by parentId
|
|
72
|
+
const pagesByParent = new Map<number | null, typeof accessiblePages>();
|
|
73
|
+
accessiblePages.forEach(page => {
|
|
74
|
+
const parentId = page.parentId ?? null;
|
|
75
|
+
if (!pagesByParent.has(parentId)) {
|
|
76
|
+
pagesByParent.set(parentId, []);
|
|
77
|
+
}
|
|
78
|
+
pagesByParent.get(parentId)!.push(page);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Sort each group by sortOrder
|
|
82
|
+
pagesByParent.forEach(pages => {
|
|
83
|
+
pages.sort((a, b) => (a.sortOrder ?? Infinity) - (b.sortOrder ?? Infinity));
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Build sorted list using depth-first traversal
|
|
87
|
+
const sortedAccessiblePages: typeof accessiblePages = [];
|
|
88
|
+
const visited = new Set<number>();
|
|
89
|
+
|
|
90
|
+
const addPageWithChildren = (parentId: number | null) => {
|
|
91
|
+
const children = pagesByParent.get(parentId) || [];
|
|
92
|
+
children.forEach(page => {
|
|
93
|
+
if (visited.has(page.id)) return;
|
|
94
|
+
visited.add(page.id);
|
|
95
|
+
sortedAccessiblePages.push(page);
|
|
96
|
+
addPageWithChildren(page.id);
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Start from root level (parentId = null)
|
|
101
|
+
addPageWithChildren(null);
|
|
102
|
+
|
|
103
|
+
// Add any orphaned pages
|
|
104
|
+
accessiblePages.forEach(page => {
|
|
105
|
+
if (!visited.has(page.id)) {
|
|
106
|
+
sortedAccessiblePages.push(page);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// 检测哪些父页面有"index 子页面"(子页面路径与父页面相同,即 path: "" 的 index 路由)
|
|
111
|
+
// 这类父页面应作为纯分组节点(不注册 list 路径),否则 Refine 会优先选中父节点
|
|
112
|
+
// 导致 "全部" 等 index 子菜单项无法高亮
|
|
113
|
+
const parentIdsWithIndexChild = new Set<number>();
|
|
114
|
+
accessiblePages.forEach(childPage => {
|
|
115
|
+
if (!childPage.parentId) return;
|
|
116
|
+
const childPath = getRoutePath(childPage.code);
|
|
117
|
+
if (!childPath) return;
|
|
118
|
+
const parentPage = pageMap.get(childPage.parentId);
|
|
119
|
+
if (!parentPage) return;
|
|
120
|
+
const parentPath = getRoutePath(parentPage.code);
|
|
121
|
+
if (childPath === parentPath) {
|
|
122
|
+
parentIdsWithIndexChild.add(childPage.parentId);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Build resources
|
|
127
|
+
const resultResources: IResourceItem[] = [];
|
|
128
|
+
const addedResourceNames = new Set<string>();
|
|
129
|
+
|
|
130
|
+
sortedAccessiblePages.forEach(page => {
|
|
131
|
+
if (!page) return;
|
|
132
|
+
|
|
133
|
+
// Skip virtual pages
|
|
134
|
+
if (page.isVirtual) return;
|
|
135
|
+
|
|
136
|
+
// Skip children of pages that use tabs
|
|
137
|
+
if (page.parentId) {
|
|
138
|
+
const parentPage = pageMap.get(page.parentId);
|
|
139
|
+
if (parentPage && PAGES_WITH_TABS.includes(parentPage.code)) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const permissionRes = snakeToCamel(page.code);
|
|
145
|
+
|
|
146
|
+
// Avoid duplicates
|
|
147
|
+
if (addedResourceNames.has(permissionRes)) return;
|
|
148
|
+
|
|
149
|
+
let parentPermissionRes: string | undefined;
|
|
150
|
+
if (page.parentId) {
|
|
151
|
+
const parentPage = pageMap.get(page.parentId);
|
|
152
|
+
if (parentPage) {
|
|
153
|
+
parentPermissionRes = snakeToCamel(parentPage.code);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 优先从 routeRegistry 获取路径
|
|
158
|
+
// fallback 到 page.path(用于 gadmin2 页面)
|
|
159
|
+
const registryPath = getRoutePath(page.code);
|
|
160
|
+
const dbPath = page.path
|
|
161
|
+
? page.path.startsWith("/") ? page.path : `/${page.path}`
|
|
162
|
+
: undefined;
|
|
163
|
+
const normalizedPath = registryPath ?? dbPath;
|
|
164
|
+
|
|
165
|
+
// 如果该页面是"有 index 子页面的父节点",则不注册 list/show/edit/create
|
|
166
|
+
// 使其成为纯分组节点(SubMenu),子页面(如"全部")的路径才负责 URL 匹配与菜单高亮
|
|
167
|
+
const isGroupParent = parentIdsWithIndexChild.has(page.id);
|
|
168
|
+
|
|
169
|
+
// Build resource item with auto-generated CRUD paths
|
|
170
|
+
// Store both zhLabel/enLabel in meta; Sider resolves at render time
|
|
171
|
+
const resource: IResourceItem = {
|
|
172
|
+
name: permissionRes,
|
|
173
|
+
...(!isGroupParent && normalizedPath && {
|
|
174
|
+
list: normalizedPath,
|
|
175
|
+
show: `${normalizedPath}/show/:id`,
|
|
176
|
+
edit: `${normalizedPath}/edit/:id`,
|
|
177
|
+
create: `${normalizedPath}/create`,
|
|
178
|
+
}),
|
|
179
|
+
meta: {
|
|
180
|
+
label: page.enName || page.code,
|
|
181
|
+
zhLabel: page.zhName || page.code,
|
|
182
|
+
enLabel: page.enName || page.code,
|
|
183
|
+
icon: iconMapping[page.code] || "",
|
|
184
|
+
parent: parentPermissionRes,
|
|
185
|
+
route: normalizedPath,
|
|
186
|
+
pageCode: page.code,
|
|
187
|
+
parentPageCode: page.parentId
|
|
188
|
+
? pageMap.get(page.parentId)?.code
|
|
189
|
+
: undefined,
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
resultResources.push(resource);
|
|
194
|
+
addedResourceNames.add(permissionRes);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Build final resources array with auto-crud if needed
|
|
198
|
+
const dynamicResources = resultResources;
|
|
199
|
+
const allResources = dynamicResources;
|
|
200
|
+
|
|
201
|
+
// Cache the result - will be used for all subsequent calls
|
|
202
|
+
cachedResources = allResources;
|
|
203
|
+
isComputing = false;
|
|
204
|
+
|
|
205
|
+
if (import.meta.env.DEV) {
|
|
206
|
+
console.log('[useDynamicResources] ✅ Resources cached. Count:', cachedResources.length);
|
|
207
|
+
console.log('[useDynamicResources] 📌 This reference will be reused for all future calls');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return cachedResources;
|
|
211
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
import { customRequest } from "helpers/login";
|
|
4
|
+
|
|
5
|
+
export const useFetchData = (
|
|
6
|
+
url: string,
|
|
7
|
+
type: "post" | "get",
|
|
8
|
+
defaultQuery?: any,
|
|
9
|
+
disableReq?: any,
|
|
10
|
+
errorCallBack?: () => void
|
|
11
|
+
) => {
|
|
12
|
+
const [data, setData] = useState<any>();
|
|
13
|
+
const [reqQuery, setReqQuery] = useState<any>(defaultQuery);
|
|
14
|
+
const [loading, setLoading] = useState<boolean>(false);
|
|
15
|
+
const fetchData = async (query?: any) => {
|
|
16
|
+
if (!url) return;
|
|
17
|
+
setLoading(true);
|
|
18
|
+
try {
|
|
19
|
+
const res = await customRequest(url, type, query);
|
|
20
|
+
setData(res);
|
|
21
|
+
setReqQuery(query);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.error(error);
|
|
24
|
+
errorCallBack && errorCallBack();
|
|
25
|
+
}
|
|
26
|
+
setLoading(false);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
!disableReq && url && fetchData(defaultQuery);
|
|
31
|
+
}, []);
|
|
32
|
+
return { data, loading, fetchData, reqQuery };
|
|
33
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
import { useFetchData } from "./useFetchData";
|
|
4
|
+
|
|
5
|
+
export interface RoleOption {
|
|
6
|
+
label: string;
|
|
7
|
+
value: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const useRoles = () => {
|
|
11
|
+
const { data: rolesData, loading } = useFetchData("role/findMany", "post", {});
|
|
12
|
+
const [roleOptions, setRoleOptions] = useState<RoleOption[]>([]);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (rolesData?.data) {
|
|
16
|
+
const roles = Array.isArray(rolesData.data) ? rolesData.data : [];
|
|
17
|
+
const options = roles.map((role: any) => ({
|
|
18
|
+
label: `${role.name} (${role.description || ""})`,
|
|
19
|
+
value: role.name,
|
|
20
|
+
}));
|
|
21
|
+
setRoleOptions(options);
|
|
22
|
+
}
|
|
23
|
+
}, [rolesData]);
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
roleOptions,
|
|
27
|
+
loading: loading || !rolesData,
|
|
28
|
+
rolesData,
|
|
29
|
+
};
|
|
30
|
+
};
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { useMemo, useState, useEffect, useRef, useCallback } from "react";
|
|
2
|
+
import { customRequest } from "helpers/login";
|
|
3
|
+
import type { Role } from "types/role";
|
|
4
|
+
|
|
5
|
+
const RoleCacheKey = "oiterproles";
|
|
6
|
+
const PagesCacheKey = "oiterpages";
|
|
7
|
+
const RolePagesCacheKey = "oiterprolepages";
|
|
8
|
+
const RolesDataCacheKey = "oiterprolesdatacache";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Base Hook: User Page Access Data
|
|
12
|
+
*
|
|
13
|
+
* This hook fetches and provides user's accessible page data.
|
|
14
|
+
* It's designed to be reused by:
|
|
15
|
+
* - useDynamicResources (for menu generation)
|
|
16
|
+
* - Any other component that needs page access info
|
|
17
|
+
*
|
|
18
|
+
* Data Flow:
|
|
19
|
+
* SessionStorage(roles) → Role IDs → RolePages → Accessible Page IDs → Page Codes
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export interface Page {
|
|
23
|
+
id: number;
|
|
24
|
+
code: string;
|
|
25
|
+
name: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
zhName?: string;
|
|
28
|
+
enName?: string;
|
|
29
|
+
iframeUrl?: string;
|
|
30
|
+
externalRedirectUrl?: string;
|
|
31
|
+
icon?: string;
|
|
32
|
+
path: string;
|
|
33
|
+
parentId?: number | null;
|
|
34
|
+
isVirtual?: boolean;
|
|
35
|
+
sortOrder: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface RolePage {
|
|
39
|
+
id: number;
|
|
40
|
+
roleId: number;
|
|
41
|
+
pageId: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface UseUserPageAccessResult {
|
|
45
|
+
/** Set of page IDs the user can access */
|
|
46
|
+
accessiblePageIds: Set<number>;
|
|
47
|
+
/** Set of page codes the user can access */
|
|
48
|
+
accessiblePageCodes: Set<string>;
|
|
49
|
+
/** All pages data from backend */
|
|
50
|
+
pagesData: Page[] | null;
|
|
51
|
+
/** Role pages mapping data */
|
|
52
|
+
rolePagesData: RolePage[] | null;
|
|
53
|
+
/** Map of pageId -> Page object */
|
|
54
|
+
pageMap: Map<number, Page>;
|
|
55
|
+
/** Map of pageCode -> Page object */
|
|
56
|
+
pageCodeMap: Map<string, Page>;
|
|
57
|
+
/** User's role names */
|
|
58
|
+
roleNames: string[];
|
|
59
|
+
/** User's role IDs */
|
|
60
|
+
roleIds: number[];
|
|
61
|
+
/** Whether data is still loading */
|
|
62
|
+
isLoading: boolean;
|
|
63
|
+
/** Check if user can access a page by code */
|
|
64
|
+
canAccessPage: (pageCode: string) => boolean;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function readCache<T>(key: string): T | null {
|
|
68
|
+
try {
|
|
69
|
+
const raw = sessionStorage.getItem(key);
|
|
70
|
+
return raw ? (JSON.parse(raw) as T) : null;
|
|
71
|
+
} catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function writeCache(key: string, value: unknown): void {
|
|
77
|
+
try {
|
|
78
|
+
sessionStorage.setItem(key, JSON.stringify(value));
|
|
79
|
+
} catch {
|
|
80
|
+
// ignore quota errors
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const useUserPageAccess = (): UseUserPageAccessResult => {
|
|
85
|
+
// Role names — initialise from cache so first render is non-empty
|
|
86
|
+
const [roleNames, setRoleNames] = useState<string[]>(
|
|
87
|
+
() => readCache<string[]>(RoleCacheKey) ?? []
|
|
88
|
+
);
|
|
89
|
+
// isRoleLoading only stays true while we wait for the live /userinfo response
|
|
90
|
+
const [isRoleLoading, setIsRoleLoading] = useState(true);
|
|
91
|
+
|
|
92
|
+
// Data states — initialise from cache for instant first render
|
|
93
|
+
const [rolesData, setRolesData] = useState<Role[] | null>(
|
|
94
|
+
() => readCache<Role[]>(RolesDataCacheKey)
|
|
95
|
+
);
|
|
96
|
+
const [rolePagesData, setRolePagesData] = useState<RolePage[] | null>(
|
|
97
|
+
() => readCache<RolePage[]>(RolePagesCacheKey)
|
|
98
|
+
);
|
|
99
|
+
const [pagesData, setPagesData] = useState<Page[] | null>(
|
|
100
|
+
() => readCache<Page[]>(PagesCacheKey)
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
// Track previous roles to detect changes
|
|
104
|
+
const prevRolesRef = useRef<string>("");
|
|
105
|
+
|
|
106
|
+
// Function to update roles in state and sessionStorage
|
|
107
|
+
const updateRoles = useCallback((newRoles: string[]) => {
|
|
108
|
+
const rolesKey = JSON.stringify(newRoles.sort());
|
|
109
|
+
if (rolesKey !== prevRolesRef.current) {
|
|
110
|
+
console.log("[useUserPageAccess] Roles changed:", newRoles);
|
|
111
|
+
prevRolesRef.current = rolesKey;
|
|
112
|
+
setRoleNames(newRoles);
|
|
113
|
+
writeCache(RoleCacheKey, newRoles);
|
|
114
|
+
}
|
|
115
|
+
}, []);
|
|
116
|
+
|
|
117
|
+
// Step 1: Fetch roles directly from backend to ensure fresh data
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
const fetchRolesFromBackend = async () => {
|
|
120
|
+
try {
|
|
121
|
+
const res = await customRequest<any>("userinfo", "GET", {});
|
|
122
|
+
const roles = res?.configPerms?.roles;
|
|
123
|
+
if (roles && Array.isArray(roles)) {
|
|
124
|
+
updateRoles(roles as string[]);
|
|
125
|
+
}
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.error("[useUserPageAccess] Failed to fetch roles from backend:", error);
|
|
128
|
+
// Fallback to sessionStorage if backend request fails
|
|
129
|
+
try {
|
|
130
|
+
const cached = readCache<string[]>(RoleCacheKey);
|
|
131
|
+
if (cached) {
|
|
132
|
+
updateRoles(cached);
|
|
133
|
+
}
|
|
134
|
+
} catch (e) {
|
|
135
|
+
console.error("[useUserPageAccess] Failed to parse cached roles:", e);
|
|
136
|
+
}
|
|
137
|
+
} finally {
|
|
138
|
+
setIsRoleLoading(false);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
fetchRolesFromBackend();
|
|
143
|
+
}, [updateRoles]);
|
|
144
|
+
|
|
145
|
+
// Step 2: Fetch all page definitions
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
const fetchPages = async () => {
|
|
148
|
+
try {
|
|
149
|
+
const res = await customRequest<any>("page/findMany", "post", {});
|
|
150
|
+
const pages = res?.data as Page[];
|
|
151
|
+
// Keep existing reference if page IDs haven't changed — prevents
|
|
152
|
+
// downstream memos (accessiblePageIds, dynamicResources, allResources)
|
|
153
|
+
// from rebuilding and giving Refine a new resources prop.
|
|
154
|
+
setPagesData(prev => {
|
|
155
|
+
if (prev && prev.length === pages.length &&
|
|
156
|
+
prev.every((p, i) => p.id === pages[i].id)) {
|
|
157
|
+
return prev;
|
|
158
|
+
}
|
|
159
|
+
writeCache(PagesCacheKey, pages);
|
|
160
|
+
return pages;
|
|
161
|
+
});
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.error("[useUserPageAccess] Failed to fetch pages:", error);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
fetchPages();
|
|
167
|
+
}, []);
|
|
168
|
+
|
|
169
|
+
// Step 3: Fetch role IDs when roleNames are available
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
if (roleNames.length === 0) {
|
|
172
|
+
setRolesData(null);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const fetchRoles = async () => {
|
|
177
|
+
try {
|
|
178
|
+
const query = {
|
|
179
|
+
where: { name: { in: roleNames } },
|
|
180
|
+
select: { id: true, name: true },
|
|
181
|
+
};
|
|
182
|
+
const res = await customRequest<any>("role/findMany", "post", query);
|
|
183
|
+
const roles = res?.data as Role[];
|
|
184
|
+
setRolesData(roles);
|
|
185
|
+
writeCache(RolesDataCacheKey, roles);
|
|
186
|
+
} catch (error) {
|
|
187
|
+
console.error("[useUserPageAccess] Failed to fetch roles:", error);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
fetchRoles();
|
|
191
|
+
}, [roleNames.join(",")]);
|
|
192
|
+
|
|
193
|
+
// Calculate roleIds
|
|
194
|
+
const roleIds = useMemo(() => {
|
|
195
|
+
if (!rolesData) return [];
|
|
196
|
+
return rolesData.map(role => role.id);
|
|
197
|
+
}, [rolesData]);
|
|
198
|
+
|
|
199
|
+
// Step 4: Fetch role-page relationships when roleIds are available
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
if (roleIds.length === 0) {
|
|
202
|
+
setRolePagesData(null);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const fetchRolePages = async () => {
|
|
207
|
+
try {
|
|
208
|
+
const query = { where: { roleId: { in: roleIds } } };
|
|
209
|
+
const res = await customRequest<any>("rolePages/findMany", "post", query);
|
|
210
|
+
const rolePages = res?.data as RolePage[];
|
|
211
|
+
// Same stable-reference trick: keep existing ref if content unchanged.
|
|
212
|
+
setRolePagesData(prev => {
|
|
213
|
+
if (prev && prev.length === rolePages.length &&
|
|
214
|
+
prev.every((rp, i) => rp.id === rolePages[i].id)) {
|
|
215
|
+
return prev;
|
|
216
|
+
}
|
|
217
|
+
writeCache(RolePagesCacheKey, rolePages);
|
|
218
|
+
return rolePages;
|
|
219
|
+
});
|
|
220
|
+
} catch (error) {
|
|
221
|
+
console.error("[useUserPageAccess] Failed to fetch rolePages:", error);
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
fetchRolePages();
|
|
226
|
+
}, [roleIds.join(",")]);
|
|
227
|
+
|
|
228
|
+
// Build page maps
|
|
229
|
+
const { pageMap, pageCodeMap } = useMemo(() => {
|
|
230
|
+
const idMap = new Map<number, Page>();
|
|
231
|
+
const codeMap = new Map<string, Page>();
|
|
232
|
+
|
|
233
|
+
if (pagesData) {
|
|
234
|
+
pagesData.forEach(page => {
|
|
235
|
+
idMap.set(page.id, page);
|
|
236
|
+
codeMap.set(page.code, page);
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return { pageMap: idMap, pageCodeMap: codeMap };
|
|
241
|
+
}, [pagesData]);
|
|
242
|
+
|
|
243
|
+
// Track previous accessiblePageIds to prevent unnecessary reference changes
|
|
244
|
+
const prevAccessiblePageIdsRef = useRef<Set<number>>(new Set());
|
|
245
|
+
|
|
246
|
+
// Build accessible page IDs (with ancestor pages)
|
|
247
|
+
const accessiblePageIds = useMemo(() => {
|
|
248
|
+
const ids = new Set<number>();
|
|
249
|
+
|
|
250
|
+
if (!rolePagesData || !pagesData) {
|
|
251
|
+
return prevAccessiblePageIdsRef.current;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Add directly accessible pages
|
|
255
|
+
rolePagesData.forEach(rolePage => {
|
|
256
|
+
ids.add(rolePage.pageId);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Recursively add ancestor pages
|
|
260
|
+
const addAncestorPages = (pageId: number, visited: Set<number>) => {
|
|
261
|
+
if (visited.has(pageId)) return;
|
|
262
|
+
visited.add(pageId);
|
|
263
|
+
|
|
264
|
+
const page = pageMap.get(pageId);
|
|
265
|
+
if (!page) return;
|
|
266
|
+
|
|
267
|
+
if (page.parentId) {
|
|
268
|
+
ids.add(page.parentId);
|
|
269
|
+
addAncestorPages(page.parentId, visited);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const originalIds = Array.from(ids);
|
|
274
|
+
const visited = new Set<number>();
|
|
275
|
+
originalIds.forEach(pageId => {
|
|
276
|
+
addAncestorPages(pageId, visited);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// Keep existing reference if IDs haven't changed
|
|
280
|
+
const prev = prevAccessiblePageIdsRef.current;
|
|
281
|
+
if (prev.size === ids.size && Array.from(ids).every(id => prev.has(id))) {
|
|
282
|
+
return prev;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
prevAccessiblePageIdsRef.current = ids;
|
|
286
|
+
return ids;
|
|
287
|
+
}, [rolePagesData, pagesData, pageMap]);
|
|
288
|
+
|
|
289
|
+
// Build accessible page codes
|
|
290
|
+
const accessiblePageCodes = useMemo(() => {
|
|
291
|
+
const codes = new Set<string>();
|
|
292
|
+
|
|
293
|
+
accessiblePageIds.forEach(pageId => {
|
|
294
|
+
const page = pageMap.get(pageId);
|
|
295
|
+
if (page) {
|
|
296
|
+
codes.add(page.code);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
return codes;
|
|
301
|
+
}, [accessiblePageIds, pageMap]);
|
|
302
|
+
|
|
303
|
+
// Check if user can access a specific page
|
|
304
|
+
const canAccessPage = useMemo(() => {
|
|
305
|
+
return (pageCode: string): boolean => {
|
|
306
|
+
return accessiblePageCodes.has(pageCode);
|
|
307
|
+
};
|
|
308
|
+
}, [accessiblePageCodes]);
|
|
309
|
+
|
|
310
|
+
// Calculate loading state
|
|
311
|
+
// If we have cached data, isLoading is false even while background-refreshing
|
|
312
|
+
const hasCache = useMemo(
|
|
313
|
+
() => !!(readCache(PagesCacheKey) && readCache(RolePagesCacheKey)),
|
|
314
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
315
|
+
[]
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
const isLoading = useMemo(() => {
|
|
319
|
+
// If cache was available on mount, never block rendering
|
|
320
|
+
if (hasCache && pagesData && rolePagesData) return false;
|
|
321
|
+
if (isRoleLoading) return true;
|
|
322
|
+
if (!pagesData) return true;
|
|
323
|
+
if (roleNames.length > 0 && roleIds.length > 0 && !rolePagesData) return true;
|
|
324
|
+
return false;
|
|
325
|
+
}, [isRoleLoading, pagesData, roleNames, roleIds, rolePagesData, hasCache]);
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
accessiblePageIds,
|
|
329
|
+
accessiblePageCodes,
|
|
330
|
+
pagesData,
|
|
331
|
+
rolePagesData,
|
|
332
|
+
pageMap,
|
|
333
|
+
pageCodeMap,
|
|
334
|
+
roleNames,
|
|
335
|
+
roleIds,
|
|
336
|
+
isLoading,
|
|
337
|
+
canAccessPage,
|
|
338
|
+
};
|
|
339
|
+
};
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import i18n from "i18next";
|
|
2
2
|
import { initReactI18next } from "react-i18next";
|
|
3
|
-
import Backend from "i18next-xhr-backend";
|
|
4
3
|
import detector from "i18next-browser-languagedetector";
|
|
4
|
+
import en from "./locales/en/common.json";
|
|
5
|
+
import zh_CN from "./locales/zh_CN/common.json";
|
|
5
6
|
|
|
6
7
|
i18n
|
|
7
|
-
.use(Backend)
|
|
8
8
|
.use(detector)
|
|
9
9
|
.use(initReactI18next)
|
|
10
10
|
.init({
|
|
11
11
|
supportedLngs: ["en", "zh_CN"],
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
resources: {
|
|
13
|
+
en: { common: en },
|
|
14
|
+
zh_CN: { common: zh_CN },
|
|
14
15
|
},
|
|
15
16
|
defaultNS: "common",
|
|
16
17
|
fallbackLng: ["en", "zh_CN"],
|
|
18
|
+
react: {
|
|
19
|
+
useSuspense: false,
|
|
20
|
+
},
|
|
17
21
|
});
|
|
18
22
|
|
|
19
23
|
export default i18n;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { createRoot } from 'react-dom/client';
|
|
3
3
|
|
|
4
|
-
import reportWebVitals from './reportWebVitals';
|
|
5
4
|
import App from './App';
|
|
6
5
|
import './i18n';
|
|
7
6
|
|
|
@@ -14,15 +13,8 @@ if (!checkLogin()) {
|
|
|
14
13
|
const root = createRoot(container);
|
|
15
14
|
|
|
16
15
|
root.render(
|
|
17
|
-
<React.
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
</React.Suspense>
|
|
21
|
-
</React.StrictMode>
|
|
16
|
+
<React.Suspense fallback='loading'>
|
|
17
|
+
<App />
|
|
18
|
+
</React.Suspense>
|
|
22
19
|
);
|
|
23
20
|
}
|
|
24
|
-
|
|
25
|
-
// If you want to start measuring performance in your app, pass a function
|
|
26
|
-
// to log results (for example: reportWebVitals(console.log))
|
|
27
|
-
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
|
28
|
-
reportWebVitals();
|