From f58c5d2e43e4d8fc04596bddb781128295cc9241 Mon Sep 17 00:00:00 2001 From: ZuoZuo <68836346+Mrz-sakura@users.noreply.github.com> Date: Sat, 2 May 2026 13:02:57 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 48 +- README.md | 2 +- admin-module.example.json | 25 + contracts/admin-openapi.json | 3191 ++++++++++++++++ eslint.config.js | 9 +- package.json | 21 +- pnpm-lock.yaml | 1146 +++++- public/vendor/libpag.wasm | Bin 0 -> 1574939 bytes scripts/generate-admin-module.mjs | 595 +++ scripts/generate-api-client.mjs | 102 + scripts/scaffold-feature.mjs | 147 + scripts/sync-openapi-from-backend.mjs | 151 + scripts/validate-admin-architecture.ts | 129 + src/App.jsx | 155 - src/App.test.jsx | 58 +- src/api/auth.js | 21 - src/api/dashboard.js | 5 - src/api/logs.js | 9 - src/api/navigation.js | 5 - src/api/notifications.js | 9 - src/api/request.js | 105 - src/api/roles.js | 25 - src/api/search.js | 5 - src/api/services.js | 25 - src/api/users.js | 33 - src/app/App.jsx | 34 + src/app/ErrorBoundary.jsx | 41 + src/app/ErrorBoundary.module.css | 62 + src/app/app-scope/AppScopeProvider.jsx | 71 + src/{ => app}/auth/AuthProvider.jsx | 25 +- src/app/layout/AdminLayout.jsx | 58 + src/{components => app}/layout/Header.jsx | 78 +- src/{components => app}/layout/Sidebar.jsx | 0 src/app/navigation/menu.js | 224 ++ src/app/navigation/menu.test.jsx | 47 + src/app/permissions.ts | 117 + src/app/providers.jsx | 26 + src/app/router/DeferredPage.jsx | 35 + src/app/router/guards.jsx | 29 + src/app/router/routeConfig.test.ts | 45 + src/app/router/routeConfig.ts | 38 + src/app/router/types.ts | 27 + src/components/service/DetailPanel.jsx | 137 - src/components/service/ResourceCell.jsx | 12 - src/components/service/RowActions.jsx | 44 - src/components/service/ServerRail.jsx | 27 - src/components/service/ServiceTable.jsx | 55 - src/components/service/StatusBadge.jsx | 16 - src/components/user/UserTable.jsx | 148 - src/constants/navigation.js | 105 - src/constants/status.js | 8 - src/data/services.js | 108 - src/features/app-config/api.ts | 18 + src/features/app-config/app-config.module.css | 7 + src/features/app-config/constants.ts | 9 + .../app-config/hooks/useH5ConfigPage.js | 68 + .../app-config/pages/H5ConfigPage.jsx | 97 + src/features/app-config/permissions.js | 11 + src/features/app-config/routes.js | 12 + src/features/app-config/schema.ts | 15 + src/features/app-registry/api.ts | 10 + src/features/app-users/api.ts | 51 + src/features/app-users/app-users.module.css | 200 + src/features/app-users/constants.js | 18 + .../app-users/hooks/useAppUsersPage.js | 197 + .../app-users/pages/AppUserListPage.jsx | 266 ++ src/features/app-users/permissions.js | 14 + src/features/app-users/routes.js | 12 + src/features/app-users/schema.ts | 19 + src/features/auth/api.ts | 37 + src/{ => features/auth}/pages/LoginPage.jsx | 6 +- src/features/auth/routes.js | 8 + src/features/dashboard/api.ts | 7 + .../dashboard/components/DashboardAlerts.jsx | 16 + .../dashboard/components/DashboardCharts.jsx | 18 + .../dashboard/components/DashboardStats.jsx | 16 + .../dashboard/components/DashboardToolbar.jsx | 16 + src/features/dashboard/dashboard.css | 360 ++ .../dashboard/hooks/useDashboardPage.js | 49 + src/features/dashboard/pages/OverviewPage.jsx | 28 + src/features/dashboard/permissions.js | 3 + src/features/dashboard/routes.js | 12 + src/features/host-org/api.test.ts | 101 + src/features/host-org/api.ts | 247 ++ .../components/HostOrgActionModal.jsx | 29 + .../host-org/components/HostOrgFilters.jsx | 50 + .../host-org/components/HostOrgStatus.jsx | 23 + .../host-org/components/HostOrgTable.jsx | 5 + .../host-org/components/HostOrgToolbar.jsx | 46 + src/features/host-org/constants.js | 47 + .../host-org/hooks/useCountryOptions.js | 28 + .../host-org/hooks/useHostAgenciesPage.js | 178 + src/features/host-org/hooks/useHostBdsPage.js | 166 + .../host-org/hooks/useHostCoinSellersPage.js | 198 + .../host-org/hooks/useHostCountriesPage.js | 161 + .../host-org/hooks/useHostHostsPage.js | 75 + .../host-org/hooks/useHostRegionsPage.js | 175 + src/features/host-org/host-org.module.css | 223 ++ .../host-org/pages/HostAgenciesPage.jsx | 142 + .../host-org/pages/HostBdLeadersPage.jsx | 103 + src/features/host-org/pages/HostBdsPage.jsx | 111 + .../host-org/pages/HostCoinSellersPage.jsx | 169 + .../host-org/pages/HostCountriesPage.jsx | 143 + src/features/host-org/pages/HostHostsPage.jsx | 66 + .../host-org/pages/HostRegionsPage.jsx | 194 + src/features/host-org/permissions.js | 62 + src/features/host-org/routes.js | 60 + src/features/host-org/schema.ts | 121 + src/features/logs/api.ts | 19 + src/features/logs/components/LogFilters.jsx | 9 + src/features/logs/components/LogTable.jsx | 64 + src/features/logs/components/LogToolbar.jsx | 16 + src/features/logs/hooks/useLogsPage.js | 54 + src/features/logs/pages/LogsPage.jsx | 22 + src/features/logs/permissions.js | 10 + src/features/logs/routes.js | 22 + src/features/menus/api.ts | 81 + .../menus/components/MenuFormDrawer.jsx | 30 + src/features/menus/components/MenuStats.jsx | 12 + src/features/menus/components/MenuTable.jsx | 121 + src/features/menus/components/MenuToolbar.jsx | 29 + .../menus/components/PermissionFormDrawer.jsx | 27 + .../menus/components/PermissionTable.jsx | 51 + src/features/menus/constants.js | 9 + src/features/menus/hooks/useMenusPage.js | 276 ++ src/features/menus/menus.css | 13 + .../menus/pages/MenuManagementPage.jsx | 86 + src/features/menus/permissions.js | 19 + src/features/menus/routes.js | 12 + src/features/menus/schema.ts | 27 + src/features/notifications/api.ts | 28 + .../components/NotificationList.jsx | 46 + .../components/NotificationToolbar.jsx | 16 + .../hooks/useNotificationsPage.js | 62 + src/features/notifications/notifications.css | 25 + .../notifications/pages/NotificationsPage.jsx | 17 + src/features/notifications/permissions.js | 12 + src/features/notifications/routes.js | 12 + src/features/resources/api.test.ts | 204 ++ src/features/resources/api.ts | 300 ++ src/features/resources/constants.js | 52 + .../resources/hooks/useResourcePages.js | 811 ++++ src/features/resources/pages/GiftListPage.jsx | 382 ++ .../resources/pages/ResourceGrantListPage.jsx | 263 ++ .../resources/pages/ResourceGroupListPage.jsx | 380 ++ .../resources/pages/ResourceListPage.jsx | 316 ++ src/features/resources/permissions.js | 20 + src/features/resources/resources.module.css | 72 + src/features/resources/routes.js | 36 + src/features/resources/schema.js | 235 ++ src/features/resources/schema.test.ts | 80 + src/features/roles/api.ts | 69 + .../roles/components/RoleFormDrawer.jsx | 87 + src/features/roles/components/RoleTable.jsx | 41 + src/features/roles/components/RoleToolbar.jsx | 16 + src/features/roles/hooks/useRolesPage.js | 236 ++ .../roles/pages/RoleManagementPage.jsx | 53 + src/features/roles/permissions.js | 15 + src/features/roles/routes.js | 12 + src/features/roles/schema.ts | 10 + src/features/rooms/api.ts | 32 + src/features/rooms/constants.js | 10 + src/features/rooms/hooks/useRoomsPage.js | 176 + src/features/rooms/pages/RoomListPage.jsx | 240 ++ src/features/rooms/permissions.js | 13 + src/features/rooms/rooms.module.css | 186 + src/features/rooms/routes.js | 12 + src/features/rooms/schema.ts | 17 + src/features/search/api.ts | 7 + .../search/components}/SearchDialog.jsx | 6 +- src/features/users/api.test.ts | 39 + src/features/users/api.ts | 65 + src/features/users/components/UserFilters.jsx | 44 + .../users/components/UserFormDrawer.jsx | 57 + .../users/components/UserProfileDrawer.jsx | 50 + src/features/users/components/UserStats.jsx | 17 + .../users/components}/UserStatusBadge.jsx | 2 +- src/features/users/components/UserTable.jsx | 146 + .../users/components/UserTable.test.jsx | 40 + src/features/users/components/UserToolbar.jsx | 30 + .../users/components/UserToolbar.test.jsx | 35 + .../users.js => features/users/constants.js} | 7 + src/features/users/hooks/useUsersPage.js | 268 ++ .../users/pages/UserManagementPage.jsx | 104 + src/features/users/permissions.js | 15 + src/features/users/routes.js | 12 + src/features/users/schema.test.ts | 39 + src/features/users/schema.ts | 22 + src/features/users/users.css | 121 + src/main.jsx | 27 +- src/pages/LogsPage.jsx | 107 - src/pages/MenuManagementPage.jsx | 81 - src/pages/NotificationsPage.jsx | 76 - src/pages/OverviewPage.jsx | 173 - src/pages/RoleManagementPage.jsx | 194 - src/pages/ServicesPage.jsx | 232 -- src/pages/UserManagementPage.jsx | 335 -- src/shared/api/download.ts | 14 + src/shared/api/generated/endpoints.ts | 899 +++++ src/shared/api/generated/schema.d.ts | 3257 +++++++++++++++++ src/shared/api/query.ts | 15 + src/shared/api/regions.ts | 11 + src/shared/api/request.test.ts | 32 + src/shared/api/request.ts | 170 + src/shared/api/types.ts | 470 +++ src/shared/api/upload.test.ts | 39 + src/shared/api/upload.ts | 25 + .../charts/BarChart.jsx | 20 +- .../charts/ChartCard.jsx | 8 +- src/{components => shared}/charts/EChart.jsx | 0 .../charts/LineChart.jsx | 17 +- .../charts/StatusDonut.jsx | 20 +- src/{ => shared}/config/env.js | 0 src/shared/forms/validation.ts | 27 + src/shared/hooks/useAdminMutation.js | 25 + src/shared/hooks/useAdminQuery.js | 47 + src/shared/hooks/useAdminQuery.test.jsx | 26 + src/shared/hooks/usePaginatedQuery.js | 20 + src/shared/hooks/useRefreshSignal.js | 28 + src/shared/hooks/useRegionOptions.js | 31 + src/shared/query/QueryProvider.jsx | 6 + src/shared/query/client.ts | 19 + src/shared/ui/AdminFormDialog.jsx | 83 + src/shared/ui/AdminFormDialog.module.css | 161 + src/shared/ui/AdminListLayout.jsx | 70 + src/shared/ui/AdminListLayout.module.css | 135 + src/{components/base => shared/ui}/Button.jsx | 14 +- src/{components/base => shared/ui}/Card.jsx | 0 .../base => shared/ui}/ConfirmProvider.jsx | 0 .../base => shared/ui}/DataState.jsx | 0 src/shared/ui/DataTable.jsx | 64 + .../base => shared/ui}/FilterChip.jsx | 12 +- .../base => shared/ui}/IconButton.jsx | 10 +- .../base => shared/ui}/KpiCard.jsx | 0 .../layout => shared/ui}/PageHead.jsx | 0 .../layout => shared/ui}/PageSkeleton.jsx | 0 .../base => shared/ui}/PaginationBar.jsx | 0 src/shared/ui/RegionSelect.jsx | 54 + .../base => shared/ui}/SearchBox.jsx | 2 +- .../base => shared/ui}/SegmentControl.jsx | 14 +- src/shared/ui/SideDrawer.jsx | 41 + src/shared/ui/StatusSelect.jsx | 29 + .../base => shared/ui}/TimeRangeSelect.jsx | 2 +- .../base => shared/ui}/ToastProvider.jsx | 0 src/shared/ui/UploadField.jsx | 369 ++ src/shared/ui/UploadField.module.css | 176 + src/shared/ui/selectAllBehavior.js | 16 + src/shared/utils/cssTokens.js | 7 + src/shared/utils/time.js | 22 + src/styles/app.css | 2363 +----------- src/styles/base.css | 4 +- src/styles/layout.css | 867 +++++ src/styles/modules.d.ts | 4 + src/styles/responsive.css | 137 + src/styles/shared-ui.css | 944 +++++ src/styles/tokens.css | 70 +- src/theme.js | 164 +- src/utils/time.js | 8 - tsconfig.json | 20 + vite.config.js | 11 + 260 files changed, 26381 insertions(+), 4833 deletions(-) create mode 100644 admin-module.example.json create mode 100644 contracts/admin-openapi.json create mode 100755 public/vendor/libpag.wasm create mode 100644 scripts/generate-admin-module.mjs create mode 100644 scripts/generate-api-client.mjs create mode 100644 scripts/scaffold-feature.mjs create mode 100644 scripts/sync-openapi-from-backend.mjs create mode 100644 scripts/validate-admin-architecture.ts delete mode 100644 src/App.jsx delete mode 100644 src/api/auth.js delete mode 100644 src/api/dashboard.js delete mode 100644 src/api/logs.js delete mode 100644 src/api/navigation.js delete mode 100644 src/api/notifications.js delete mode 100644 src/api/request.js delete mode 100644 src/api/roles.js delete mode 100644 src/api/search.js delete mode 100644 src/api/services.js delete mode 100644 src/api/users.js create mode 100644 src/app/App.jsx create mode 100644 src/app/ErrorBoundary.jsx create mode 100644 src/app/ErrorBoundary.module.css create mode 100644 src/app/app-scope/AppScopeProvider.jsx rename src/{ => app}/auth/AuthProvider.jsx (78%) create mode 100644 src/app/layout/AdminLayout.jsx rename src/{components => app}/layout/Header.jsx (66%) rename src/{components => app}/layout/Sidebar.jsx (100%) create mode 100644 src/app/navigation/menu.js create mode 100644 src/app/navigation/menu.test.jsx create mode 100644 src/app/permissions.ts create mode 100644 src/app/providers.jsx create mode 100644 src/app/router/DeferredPage.jsx create mode 100644 src/app/router/guards.jsx create mode 100644 src/app/router/routeConfig.test.ts create mode 100644 src/app/router/routeConfig.ts create mode 100644 src/app/router/types.ts delete mode 100644 src/components/service/DetailPanel.jsx delete mode 100644 src/components/service/ResourceCell.jsx delete mode 100644 src/components/service/RowActions.jsx delete mode 100644 src/components/service/ServerRail.jsx delete mode 100644 src/components/service/ServiceTable.jsx delete mode 100644 src/components/service/StatusBadge.jsx delete mode 100644 src/components/user/UserTable.jsx delete mode 100644 src/constants/navigation.js delete mode 100644 src/constants/status.js delete mode 100644 src/data/services.js create mode 100644 src/features/app-config/api.ts create mode 100644 src/features/app-config/app-config.module.css create mode 100644 src/features/app-config/constants.ts create mode 100644 src/features/app-config/hooks/useH5ConfigPage.js create mode 100644 src/features/app-config/pages/H5ConfigPage.jsx create mode 100644 src/features/app-config/permissions.js create mode 100644 src/features/app-config/routes.js create mode 100644 src/features/app-config/schema.ts create mode 100644 src/features/app-registry/api.ts create mode 100644 src/features/app-users/api.ts create mode 100644 src/features/app-users/app-users.module.css create mode 100644 src/features/app-users/constants.js create mode 100644 src/features/app-users/hooks/useAppUsersPage.js create mode 100644 src/features/app-users/pages/AppUserListPage.jsx create mode 100644 src/features/app-users/permissions.js create mode 100644 src/features/app-users/routes.js create mode 100644 src/features/app-users/schema.ts create mode 100644 src/features/auth/api.ts rename src/{ => features/auth}/pages/LoginPage.jsx (92%) create mode 100644 src/features/auth/routes.js create mode 100644 src/features/dashboard/api.ts create mode 100644 src/features/dashboard/components/DashboardAlerts.jsx create mode 100644 src/features/dashboard/components/DashboardCharts.jsx create mode 100644 src/features/dashboard/components/DashboardStats.jsx create mode 100644 src/features/dashboard/components/DashboardToolbar.jsx create mode 100644 src/features/dashboard/dashboard.css create mode 100644 src/features/dashboard/hooks/useDashboardPage.js create mode 100644 src/features/dashboard/pages/OverviewPage.jsx create mode 100644 src/features/dashboard/permissions.js create mode 100644 src/features/dashboard/routes.js create mode 100644 src/features/host-org/api.test.ts create mode 100644 src/features/host-org/api.ts create mode 100644 src/features/host-org/components/HostOrgActionModal.jsx create mode 100644 src/features/host-org/components/HostOrgFilters.jsx create mode 100644 src/features/host-org/components/HostOrgStatus.jsx create mode 100644 src/features/host-org/components/HostOrgTable.jsx create mode 100644 src/features/host-org/components/HostOrgToolbar.jsx create mode 100644 src/features/host-org/constants.js create mode 100644 src/features/host-org/hooks/useCountryOptions.js create mode 100644 src/features/host-org/hooks/useHostAgenciesPage.js create mode 100644 src/features/host-org/hooks/useHostBdsPage.js create mode 100644 src/features/host-org/hooks/useHostCoinSellersPage.js create mode 100644 src/features/host-org/hooks/useHostCountriesPage.js create mode 100644 src/features/host-org/hooks/useHostHostsPage.js create mode 100644 src/features/host-org/hooks/useHostRegionsPage.js create mode 100644 src/features/host-org/host-org.module.css create mode 100644 src/features/host-org/pages/HostAgenciesPage.jsx create mode 100644 src/features/host-org/pages/HostBdLeadersPage.jsx create mode 100644 src/features/host-org/pages/HostBdsPage.jsx create mode 100644 src/features/host-org/pages/HostCoinSellersPage.jsx create mode 100644 src/features/host-org/pages/HostCountriesPage.jsx create mode 100644 src/features/host-org/pages/HostHostsPage.jsx create mode 100644 src/features/host-org/pages/HostRegionsPage.jsx create mode 100644 src/features/host-org/permissions.js create mode 100644 src/features/host-org/routes.js create mode 100644 src/features/host-org/schema.ts create mode 100644 src/features/logs/api.ts create mode 100644 src/features/logs/components/LogFilters.jsx create mode 100644 src/features/logs/components/LogTable.jsx create mode 100644 src/features/logs/components/LogToolbar.jsx create mode 100644 src/features/logs/hooks/useLogsPage.js create mode 100644 src/features/logs/pages/LogsPage.jsx create mode 100644 src/features/logs/permissions.js create mode 100644 src/features/logs/routes.js create mode 100644 src/features/menus/api.ts create mode 100644 src/features/menus/components/MenuFormDrawer.jsx create mode 100644 src/features/menus/components/MenuStats.jsx create mode 100644 src/features/menus/components/MenuTable.jsx create mode 100644 src/features/menus/components/MenuToolbar.jsx create mode 100644 src/features/menus/components/PermissionFormDrawer.jsx create mode 100644 src/features/menus/components/PermissionTable.jsx create mode 100644 src/features/menus/constants.js create mode 100644 src/features/menus/hooks/useMenusPage.js create mode 100644 src/features/menus/menus.css create mode 100644 src/features/menus/pages/MenuManagementPage.jsx create mode 100644 src/features/menus/permissions.js create mode 100644 src/features/menus/routes.js create mode 100644 src/features/menus/schema.ts create mode 100644 src/features/notifications/api.ts create mode 100644 src/features/notifications/components/NotificationList.jsx create mode 100644 src/features/notifications/components/NotificationToolbar.jsx create mode 100644 src/features/notifications/hooks/useNotificationsPage.js create mode 100644 src/features/notifications/notifications.css create mode 100644 src/features/notifications/pages/NotificationsPage.jsx create mode 100644 src/features/notifications/permissions.js create mode 100644 src/features/notifications/routes.js create mode 100644 src/features/resources/api.test.ts create mode 100644 src/features/resources/api.ts create mode 100644 src/features/resources/constants.js create mode 100644 src/features/resources/hooks/useResourcePages.js create mode 100644 src/features/resources/pages/GiftListPage.jsx create mode 100644 src/features/resources/pages/ResourceGrantListPage.jsx create mode 100644 src/features/resources/pages/ResourceGroupListPage.jsx create mode 100644 src/features/resources/pages/ResourceListPage.jsx create mode 100644 src/features/resources/permissions.js create mode 100644 src/features/resources/resources.module.css create mode 100644 src/features/resources/routes.js create mode 100644 src/features/resources/schema.js create mode 100644 src/features/resources/schema.test.ts create mode 100644 src/features/roles/api.ts create mode 100644 src/features/roles/components/RoleFormDrawer.jsx create mode 100644 src/features/roles/components/RoleTable.jsx create mode 100644 src/features/roles/components/RoleToolbar.jsx create mode 100644 src/features/roles/hooks/useRolesPage.js create mode 100644 src/features/roles/pages/RoleManagementPage.jsx create mode 100644 src/features/roles/permissions.js create mode 100644 src/features/roles/routes.js create mode 100644 src/features/roles/schema.ts create mode 100644 src/features/rooms/api.ts create mode 100644 src/features/rooms/constants.js create mode 100644 src/features/rooms/hooks/useRoomsPage.js create mode 100644 src/features/rooms/pages/RoomListPage.jsx create mode 100644 src/features/rooms/permissions.js create mode 100644 src/features/rooms/rooms.module.css create mode 100644 src/features/rooms/routes.js create mode 100644 src/features/rooms/schema.ts create mode 100644 src/features/search/api.ts rename src/{components/search => features/search/components}/SearchDialog.jsx (91%) create mode 100644 src/features/users/api.test.ts create mode 100644 src/features/users/api.ts create mode 100644 src/features/users/components/UserFilters.jsx create mode 100644 src/features/users/components/UserFormDrawer.jsx create mode 100644 src/features/users/components/UserProfileDrawer.jsx create mode 100644 src/features/users/components/UserStats.jsx rename src/{components/user => features/users/components}/UserStatusBadge.jsx (84%) create mode 100644 src/features/users/components/UserTable.jsx create mode 100644 src/features/users/components/UserTable.test.jsx create mode 100644 src/features/users/components/UserToolbar.jsx create mode 100644 src/features/users/components/UserToolbar.test.jsx rename src/{data/users.js => features/users/constants.js} (89%) create mode 100644 src/features/users/hooks/useUsersPage.js create mode 100644 src/features/users/pages/UserManagementPage.jsx create mode 100644 src/features/users/permissions.js create mode 100644 src/features/users/routes.js create mode 100644 src/features/users/schema.test.ts create mode 100644 src/features/users/schema.ts create mode 100644 src/features/users/users.css delete mode 100644 src/pages/LogsPage.jsx delete mode 100644 src/pages/MenuManagementPage.jsx delete mode 100644 src/pages/NotificationsPage.jsx delete mode 100644 src/pages/OverviewPage.jsx delete mode 100644 src/pages/RoleManagementPage.jsx delete mode 100644 src/pages/ServicesPage.jsx delete mode 100644 src/pages/UserManagementPage.jsx create mode 100644 src/shared/api/download.ts create mode 100644 src/shared/api/generated/endpoints.ts create mode 100644 src/shared/api/generated/schema.d.ts create mode 100644 src/shared/api/query.ts create mode 100644 src/shared/api/regions.ts create mode 100644 src/shared/api/request.test.ts create mode 100644 src/shared/api/request.ts create mode 100644 src/shared/api/types.ts create mode 100644 src/shared/api/upload.test.ts create mode 100644 src/shared/api/upload.ts rename src/{components => shared}/charts/BarChart.jsx (54%) rename src/{components => shared}/charts/ChartCard.jsx (52%) rename src/{components => shared}/charts/EChart.jsx (100%) rename src/{components => shared}/charts/LineChart.jsx (57%) rename src/{components => shared}/charts/StatusDonut.jsx (69%) rename src/{ => shared}/config/env.js (100%) create mode 100644 src/shared/forms/validation.ts create mode 100644 src/shared/hooks/useAdminMutation.js create mode 100644 src/shared/hooks/useAdminQuery.js create mode 100644 src/shared/hooks/useAdminQuery.test.jsx create mode 100644 src/shared/hooks/usePaginatedQuery.js create mode 100644 src/shared/hooks/useRefreshSignal.js create mode 100644 src/shared/hooks/useRegionOptions.js create mode 100644 src/shared/query/QueryProvider.jsx create mode 100644 src/shared/query/client.ts create mode 100644 src/shared/ui/AdminFormDialog.jsx create mode 100644 src/shared/ui/AdminFormDialog.module.css create mode 100644 src/shared/ui/AdminListLayout.jsx create mode 100644 src/shared/ui/AdminListLayout.module.css rename src/{components/base => shared/ui}/Button.jsx (71%) rename src/{components/base => shared/ui}/Card.jsx (100%) rename src/{components/base => shared/ui}/ConfirmProvider.jsx (100%) rename src/{components/base => shared/ui}/DataState.jsx (100%) create mode 100644 src/shared/ui/DataTable.jsx rename src/{components/base => shared/ui}/FilterChip.jsx (54%) rename src/{components/base => shared/ui}/IconButton.jsx (64%) rename src/{components/base => shared/ui}/KpiCard.jsx (100%) rename src/{components/layout => shared/ui}/PageHead.jsx (100%) rename src/{components/layout => shared/ui}/PageSkeleton.jsx (100%) rename src/{components/base => shared/ui}/PaginationBar.jsx (100%) create mode 100644 src/shared/ui/RegionSelect.jsx rename src/{components/base => shared/ui}/SearchBox.jsx (85%) rename src/{components/base => shared/ui}/SegmentControl.jsx (79%) create mode 100644 src/shared/ui/SideDrawer.jsx create mode 100644 src/shared/ui/StatusSelect.jsx rename src/{components/base => shared/ui}/TimeRangeSelect.jsx (88%) rename src/{components/base => shared/ui}/ToastProvider.jsx (100%) create mode 100644 src/shared/ui/UploadField.jsx create mode 100644 src/shared/ui/UploadField.module.css create mode 100644 src/shared/ui/selectAllBehavior.js create mode 100644 src/shared/utils/cssTokens.js create mode 100644 src/shared/utils/time.js create mode 100644 src/styles/layout.css create mode 100644 src/styles/modules.d.ts create mode 100644 src/styles/responsive.css create mode 100644 src/styles/shared-ui.css delete mode 100644 src/utils/time.js create mode 100644 tsconfig.json diff --git a/AGENTS.md b/AGENTS.md index 8676a71..027c99c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,24 +24,28 @@ - UI 组件库:MUI。 - 图表库:ECharts。 - 包管理器:pnpm。 -- 页面:`src/pages/OverviewPage.jsx`、`src/pages/ServicesPage.jsx` 和 `src/pages/UserManagementPage.jsx`。 -- 应用壳:`src/App.jsx`,只做状态编排、懒加载页面、抽屉挂载和事件分发。 -- 页面加载占位:`src/components/layout/PageSkeleton.jsx`。 -- 顶部搜索弹窗:`src/components/search/SearchDialog.jsx`。 +- 页面:按业务放在 `src/features/*/pages/`。 +- 应用壳:`src/app/App.jsx`、`src/app/layout/AdminLayout.jsx`、`src/app/router/routeConfig.ts`。 +- 页面加载占位:`src/shared/ui/PageSkeleton.jsx`。 +- 顶部搜索弹窗:`src/features/search/components/SearchDialog.jsx`。 - MUI 主题:`src/theme.js`。 - CSS token:`src/styles/tokens.css`。 -- 主要样式和动画:`src/styles/app.css`。 +- 样式入口:`src/styles/app.css`,实际样式按 `src/styles/*.css`、`src/features/*/*.css` 和 `*.module.css` 拆分。 +- 服务端状态:使用 `@tanstack/react-query`,共享入口在 `src/shared/query/` 和 `src/shared/hooks/useAdminQuery.js`。 +- 接口契约:`pnpm gen:api` 会先从 `../hyapp-server/server/admin` 路由同步 `contracts/admin-openapi.json`,再生成 `src/shared/api/generated/schema.d.ts` 和 `src/shared/api/generated/endpoints.ts`。 ## 目录规范 -- `src/components/base/`:项目基础组件封装,内部可以使用 MUI。 -- `src/components/layout/`:布局组件。 -- `src/components/charts/`:ECharts 封装和图表组件。 -- `src/components/service/`:服务管理相关业务组件。 -- `src/pages/`:页面级模块。 -- `src/constants/`:导航、状态等常量。 -- `src/data/`:演示数据。 -- `src/utils/`:工具函数。 +- `src/app/`:应用壳、Provider、鉴权上下文、路由守卫、统一路由配置和布局。 +- `src/shared/`:跨业务共享的 API 基础层、UI 组件、hooks、工具和图表封装。 +- `src/features//`:业务模块,内部放 `api.ts`、`routes.js`、`permissions.js`、`schema.ts`、`hooks/`、`components/`、`pages/`。 +- 新增后台业务模块必须优先落在 `src/features/`,不要重新创建横向 `pages/`、`api/`、`components/` 目录。 +- 新增模块优先复制 `admin-module.example.json`,改成业务配置后运行 `pnpm gen:module `;它会生成 feature、权限常量、路由接入、OpenAPI 契约和后端 seed/routes 片段。 +- 只需要预览生成内容时运行 `pnpm gen:module --dry-run`。 +- `pnpm scaffold:feature ` 是低层级骨架命令,只有在不需要权限/菜单/契约自动接入时使用。 +- 后端路由或权限变更后先运行 `pnpm gen:api`,只需要同步契约时运行 `pnpm sync:contract`。 +- 前端 API 路径必须从 `src/shared/api/generated/endpoints.ts` 取,不要在 feature API 中重新手写路径。 +- 表单提交前必须走模块内 `schema.ts` 或共享 schema 校验。 ## 按需导入规则 @@ -64,7 +68,7 @@ import { Button } from "@mui/material"; import { Refresh } from "@mui/icons-material"; ``` -- ECharts 只在 `src/components/charts/` 内封装和加载,不要在页面里直接散落图表初始化逻辑。 +- ECharts 只在 `src/shared/charts/` 内封装和加载,不要在页面里直接散落图表初始化逻辑。 - 页面和抽屉继续使用 `React.lazy` 做拆分加载。 - 顶部搜索弹窗继续使用 lazy 加载,不要静态塞进主入口。 @@ -80,11 +84,27 @@ import { Refresh } from "@mui/icons-material"; - 不添加卡片套卡片。 - 不添加无业务意义的说明区、营销区、空泛介绍区。 +## 后台表格交互规则 + +- 表格空态只显示 `当前无数据`,创建入口放在页面工具栏,不放在空态里。 +- 表格里的启用/禁用状态优先使用行内 Switch 快捷操作,不使用单独的启用/禁用图标按钮。 +- 表格状态筛选使用 Select 下拉框,默认值为 `全部状态`,不使用状态 chip 组。 +- 区域编辑弹窗必须同时包含区域基础字段和国家码字段;国家码使用可搜索多选下拉框,不再拆成独立“区域国家”弹窗。 +- 头像、图片、封面等图片资源字段必须使用 `src/shared/ui/UploadField.jsx` 上传表单组件,不使用普通文本输入框承载 URL。 +- 图片上传表单必须提供统一预览;`webp` 使用浏览器原生动效预览,`svga` 使用 SVGA 播放器预览,`pag` 使用 libpag + wasm 预览。 +- BD Leader 创建弹窗必须使用区域下拉选择 `regionId`;目标用户原区域可以不同,创建成功后后端会把目标用户当前区域同步到所选区域。 +- 区域创建和区域启用/禁用调用后台管理后端接口,不要求 user-service 新增对应 RPC。 +- 国家创建和国家启用/禁用调用后台管理后端接口,不要求 user-service 新增对应 RPC。 + ## 验证要求 每次完成前端改动后至少运行: ```bash +pnpm check:contracts +pnpm typecheck +pnpm lint +pnpm test pnpm build ``` diff --git a/README.md b/README.md index b55f435..d8e4603 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ - 写代码不要猜:不确定的依赖、API、文件结构、现有约定,先查本地项目或依赖实际导出。 - 写前端或客户端项目时,不添加无意义的描述文案。 - 使用 `pnpm`。 -- 页面包含:`总览`、`服务管理` 和 `用户管理`。 +- 页面包含:`总览`、`用户管理`、`房间管理`、`地区管理`、`团队管理` 等后台模块。 - 所有组件都要模块化,包括基础组件、布局组件、业务组件、图表组件。 - 基础 UI 使用 MUI,但整体风格必须统一为白色后台管理平台风格。 - 修改 MUI 组件颜色和样式时注意兼容性,优先通过主题和封装组件统一处理。 diff --git a/admin-module.example.json b/admin-module.example.json new file mode 100644 index 0000000..8c4da9d --- /dev/null +++ b/admin-module.example.json @@ -0,0 +1,25 @@ +{ + "name": "orders", + "singularName": "order", + "label": "订单管理", + "path": "/orders", + "menuCode": "orders", + "menuKey": "orders", + "permissionPrefix": "order", + "icon": "receipt", + "sort": 80, + "permissions": [ + { "action": "view", "name": "订单查看", "kind": "menu" }, + { "action": "create", "name": "订单创建", "kind": "button" }, + { "action": "update", "name": "订单更新", "kind": "button" }, + { "action": "delete", "name": "订单删除", "kind": "button" }, + { "action": "export", "name": "订单导出", "kind": "button" } + ], + "apiRoutes": [ + { "method": "GET", "path": "/orders", "action": "view", "kind": "list", "operationId": "listOrders", "handler": "ListOrders" }, + { "method": "POST", "path": "/orders", "action": "create", "operationId": "createOrder", "handler": "CreateOrder" }, + { "method": "PATCH", "path": "/orders/{id}", "action": "update", "operationId": "updateOrder", "handler": "UpdateOrder" }, + { "method": "DELETE", "path": "/orders/{id}", "action": "delete", "operationId": "deleteOrder", "handler": "DeleteOrder" }, + { "method": "GET", "path": "/orders/export", "action": "export", "operationId": "exportOrders", "handler": "ExportOrders", "raw": true } + ] +} diff --git a/contracts/admin-openapi.json b/contracts/admin-openapi.json new file mode 100644 index 0000000..bf82489 --- /dev/null +++ b/contracts/admin-openapi.json @@ -0,0 +1,3191 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "HYApp Admin API", + "version": "0.1.0" + }, + "servers": [ + { + "url": "/api/v1" + } + ], + "paths": { + "/admin/agencies": { + "get": { + "operationId": "listAgencies", + "parameters": [ + { + "$ref": "#/components/parameters/Page" + }, + { + "$ref": "#/components/parameters/PageSize" + }, + { + "$ref": "#/components/parameters/Keyword" + }, + { + "$ref": "#/components/parameters/Status" + }, + { + "$ref": "#/components/parameters/RegionId" + }, + { + "$ref": "#/components/parameters/ParentBdUserId" + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/AgencyPageResponse" + } + }, + "x-permission": "agency:view", + "x-permissions": [ + "agency:view" + ] + }, + "post": { + "operationId": "createAgency", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permission": "agency:create", + "x-permissions": [ + "agency:create" + ] + } + }, + "/admin/agencies/{agency_id}/close": { + "post": { + "operationId": "closeAgency", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "agency_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "x-permission": "agency:status", + "x-permissions": [ + "agency:status" + ] + } + }, + "/admin/agencies/{agency_id}/join-enabled": { + "post": { + "operationId": "setAgencyJoinEnabled", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "agency_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "x-permission": "agency:status", + "x-permissions": [ + "agency:status" + ] + } + }, + "/admin/app-config/h5-links": { + "get": { + "operationId": "listH5Links", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permission": "app-config:view", + "x-permissions": [ + "app-config:view" + ] + }, + "put": { + "operationId": "updateH5Links", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permission": "app-config:update", + "x-permissions": [ + "app-config:update" + ] + } + }, + "/admin/apps": { + "get": { + "operationId": "listApps", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + } + } + }, + "/admin/bd-leaders": { + "get": { + "operationId": "listBDLeaders", + "parameters": [ + { + "$ref": "#/components/parameters/Page" + }, + { + "$ref": "#/components/parameters/PageSize" + }, + { + "$ref": "#/components/parameters/Keyword" + }, + { + "$ref": "#/components/parameters/Status" + }, + { + "$ref": "#/components/parameters/RegionId" + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/BDProfilePageResponse" + } + }, + "x-permission": "bd:view", + "x-permissions": [ + "bd:view" + ] + }, + "post": { + "operationId": "createBDLeader", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permission": "bd:create", + "x-permissions": [ + "bd:create" + ] + } + }, + "/admin/bd-leaders/{user_id}/status": { + "patch": { + "operationId": "setBDLeaderStatus", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "user_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "x-permission": "bd:update", + "x-permissions": [ + "bd:update" + ] + } + }, + "/admin/bds": { + "get": { + "operationId": "listBDs", + "parameters": [ + { + "$ref": "#/components/parameters/Page" + }, + { + "$ref": "#/components/parameters/PageSize" + }, + { + "$ref": "#/components/parameters/Keyword" + }, + { + "$ref": "#/components/parameters/Status" + }, + { + "$ref": "#/components/parameters/RegionId" + }, + { + "$ref": "#/components/parameters/ParentLeaderUserId" + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/BDProfilePageResponse" + } + }, + "x-permission": "bd:view", + "x-permissions": [ + "bd:view" + ] + }, + "post": { + "operationId": "createBD", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permission": "bd:create", + "x-permissions": [ + "bd:create" + ] + } + }, + "/admin/bds/{user_id}/status": { + "patch": { + "operationId": "setBDStatus", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "user_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "x-permission": "bd:update", + "x-permissions": [ + "bd:update" + ] + } + }, + "/admin/coin-sellers": { + "get": { + "operationId": "listCoinSellers", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permission": "coin-seller:view", + "x-permissions": [ + "coin-seller:view" + ] + }, + "post": { + "operationId": "createCoinSeller", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permission": "coin-seller:create", + "x-permissions": [ + "coin-seller:create" + ] + } + }, + "/admin/coin-sellers/{user_id}/status": { + "patch": { + "operationId": "setCoinSellerStatus", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "user_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "x-permission": "coin-seller:update", + "x-permissions": [ + "coin-seller:update" + ] + } + }, + "/admin/countries": { + "get": { + "operationId": "listCountries", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permission": "country:view", + "x-permissions": [ + "country:view" + ] + }, + "post": { + "operationId": "createCountry", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permission": "country:create", + "x-permissions": [ + "country:create" + ] + } + }, + "/admin/countries/{country_id}": { + "get": { + "operationId": "getCountry", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "country_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "x-permission": "country:view", + "x-permissions": [ + "country:view" + ] + }, + "patch": { + "operationId": "updateCountry", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "country_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "x-permission": "country:update", + "x-permissions": [ + "country:update" + ] + }, + "delete": { + "operationId": "deleteCountry", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "country_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "x-permission": "country:status", + "x-permissions": [ + "country:status" + ] + } + }, + "/admin/countries/{country_id}/disable": { + "post": { + "operationId": "disableCountry", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "country_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "x-permission": "country:status", + "x-permissions": [ + "country:status" + ] + } + }, + "/admin/countries/{country_id}/enable": { + "post": { + "operationId": "enableCountry", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "country_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "x-permission": "country:status", + "x-permissions": [ + "country:status" + ] + } + }, + "/admin/files/image/upload": { + "post": { + "operationId": "uploadImage", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permission": "upload:create", + "x-permissions": [ + "upload:create" + ] + } + }, + "/admin/files/upload": { + "post": { + "operationId": "uploadFile", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permission": "upload:create", + "x-permissions": [ + "upload:create" + ] + } + }, + "/admin/gifts": { + "get": { + "operationId": "listGifts", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permission": "gift:view", + "x-permissions": [ + "gift:view" + ] + }, + "post": { + "operationId": "createGift", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permission": "gift:create", + "x-permissions": [ + "gift:create" + ] + } + }, + "/admin/gifts/{gift_id}": { + "put": { + "operationId": "updateGift", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "gift_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "x-permission": "gift:update", + "x-permissions": [ + "gift:update" + ] + } + }, + "/admin/gifts/{gift_id}/disable": { + "post": { + "operationId": "disableGift", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "gift_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "x-permission": "gift:status", + "x-permissions": [ + "gift:status" + ] + } + }, + "/admin/gifts/{gift_id}/enable": { + "post": { + "operationId": "enableGift", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "gift_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "x-permission": "gift:status", + "x-permissions": [ + "gift:status" + ] + } + }, + "/admin/hosts": { + "get": { + "operationId": "listHosts", + "parameters": [ + { + "$ref": "#/components/parameters/Page" + }, + { + "$ref": "#/components/parameters/PageSize" + }, + { + "$ref": "#/components/parameters/Keyword" + }, + { + "$ref": "#/components/parameters/Status" + }, + { + "$ref": "#/components/parameters/RegionId" + }, + { + "$ref": "#/components/parameters/AgencyId" + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/HostProfilePageResponse" + } + }, + "x-permission": "host:view", + "x-permissions": [ + "host:view" + ] + } + }, + "/admin/regions": { + "get": { + "operationId": "listRegions", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permission": "region:view", + "x-permissions": [ + "region:view" + ] + }, + "post": { + "operationId": "createRegion", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permission": "region:create", + "x-permissions": [ + "region:create" + ] + } + }, + "/admin/regions/{region_id}": { + "get": { + "operationId": "getRegion", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "region_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "x-permission": "region:view", + "x-permissions": [ + "region:view" + ] + }, + "patch": { + "operationId": "updateRegion", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "region_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "x-permission": "region:update", + "x-permissions": [ + "region:update" + ] + }, + "delete": { + "operationId": "disableRegion", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "region_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "x-permission": "region:status", + "x-permissions": [ + "region:status" + ] + } + }, + "/admin/regions/{region_id}/countries": { + "put": { + "operationId": "replaceRegionCountries", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "region_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "x-permission": "region:update", + "x-permissions": [ + "region:update" + ] + } + }, + "/admin/regions/{region_id}/enable": { + "post": { + "operationId": "enableRegion", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "region_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "x-permission": "region:status", + "x-permissions": [ + "region:status" + ] + } + }, + "/admin/resource-grants": { + "get": { + "operationId": "listResourceGrants", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permission": "resource-grant:view", + "x-permissions": [ + "resource-grant:view" + ] + } + }, + "/admin/resource-grants/group": { + "post": { + "operationId": "grantResourceGroup", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permission": "resource-grant:create", + "x-permissions": [ + "resource-grant:create" + ] + } + }, + "/admin/resource-grants/resource": { + "post": { + "operationId": "grantResource", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permission": "resource-grant:create", + "x-permissions": [ + "resource-grant:create" + ] + } + }, + "/admin/resource-groups": { + "get": { + "operationId": "listResourceGroups", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permission": "resource-group:view", + "x-permissions": [ + "resource-group:view" + ] + }, + "post": { + "operationId": "createResourceGroup", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permission": "resource-group:create", + "x-permissions": [ + "resource-group:create" + ] + } + }, + "/admin/resource-groups/{group_id}": { + "get": { + "operationId": "getResourceGroup", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "group_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "x-permission": "resource-group:view", + "x-permissions": [ + "resource-group:view" + ] + }, + "put": { + "operationId": "updateResourceGroup", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "group_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "x-permission": "resource-group:update", + "x-permissions": [ + "resource-group:update" + ] + } + }, + "/admin/resource-groups/{group_id}/disable": { + "post": { + "operationId": "disableResourceGroup", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "group_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "x-permission": "resource-group:update", + "x-permissions": [ + "resource-group:update" + ] + } + }, + "/admin/resource-groups/{group_id}/enable": { + "post": { + "operationId": "enableResourceGroup", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "group_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "x-permission": "resource-group:update", + "x-permissions": [ + "resource-group:update" + ] + } + }, + "/admin/resource-groups/{group_id}/items": { + "put": { + "operationId": "updateResourceGroupItems", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "group_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "x-permission": "resource-group:update", + "x-permissions": [ + "resource-group:update" + ] + } + }, + "/admin/resources": { + "get": { + "operationId": "listResources", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permission": "resource:view", + "x-permissions": [ + "resource:view" + ] + }, + "post": { + "operationId": "createResource", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permission": "resource:create", + "x-permissions": [ + "resource:create" + ] + } + }, + "/admin/resources/{resource_id}": { + "get": { + "operationId": "getResource", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "resource_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "x-permission": "resource:view", + "x-permissions": [ + "resource:view" + ] + }, + "put": { + "operationId": "updateResource", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "resource_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "x-permission": "resource:update", + "x-permissions": [ + "resource:update" + ] + } + }, + "/admin/resources/{resource_id}/disable": { + "post": { + "operationId": "disableResource", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "resource_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "x-permission": "resource:update", + "x-permissions": [ + "resource:update" + ] + } + }, + "/admin/resources/{resource_id}/enable": { + "post": { + "operationId": "enableResource", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "resource_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "x-permission": "resource:update", + "x-permissions": [ + "resource:update" + ] + } + }, + "/admin/rooms": { + "get": { + "operationId": "listRooms", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permission": "room:view", + "x-permissions": [ + "room:view" + ] + } + }, + "/admin/rooms/{room_id}": { + "patch": { + "operationId": "updateRoom", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "room_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "x-permission": "room:update", + "x-permissions": [ + "room:update" + ] + }, + "delete": { + "operationId": "deleteRoom", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "room_id", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "x-permission": "room:delete", + "x-permissions": [ + "room:delete" + ] + } + }, + "/app/users": { + "get": { + "operationId": "appListUsers", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permission": "app-user:view", + "x-permissions": [ + "app-user:view" + ] + } + }, + "/app/users/{id}": { + "get": { + "operationId": "appGetUser", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "$ref": "#/components/parameters/Id" + } + ], + "x-permission": "app-user:view", + "x-permissions": [ + "app-user:view" + ] + }, + "patch": { + "operationId": "appUpdateUser", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "$ref": "#/components/parameters/Id" + } + ], + "x-permission": "app-user:update", + "x-permissions": [ + "app-user:update" + ] + } + }, + "/app/users/{id}/ban": { + "post": { + "operationId": "appBanUser", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "$ref": "#/components/parameters/Id" + } + ], + "x-permission": "app-user:status", + "x-permissions": [ + "app-user:status" + ] + } + }, + "/app/users/{id}/password": { + "post": { + "operationId": "appSetPassword", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "$ref": "#/components/parameters/Id" + } + ], + "x-permission": "app-user:password", + "x-permissions": [ + "app-user:password" + ] + } + }, + "/app/users/{id}/unban": { + "post": { + "operationId": "appUnbanUser", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "$ref": "#/components/parameters/Id" + } + ], + "x-permission": "app-user:status", + "x-permissions": [ + "app-user:status" + ] + } + }, + "/auth/change-password": { + "post": { + "operationId": "changePassword", + "requestBody": { + "$ref": "#/components/requestBodies/ChangePasswordRequest" + }, + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + } + } + }, + "/auth/login": { + "post": { + "operationId": "login", + "requestBody": { + "$ref": "#/components/requestBodies/LoginRequest" + }, + "responses": { + "200": { + "$ref": "#/components/responses/SessionResponse" + } + } + } + }, + "/auth/logout": { + "post": { + "operationId": "logout", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + } + } + }, + "/auth/me": { + "get": { + "operationId": "me", + "responses": { + "200": { + "$ref": "#/components/responses/SessionResponse" + } + } + } + }, + "/auth/refresh": { + "post": { + "operationId": "refresh", + "responses": { + "200": { + "$ref": "#/components/responses/SessionResponse" + } + } + } + }, + "/dashboard/overview": { + "get": { + "operationId": "dashboardOverview", + "x-permission": "overview:view", + "responses": { + "200": { + "$ref": "#/components/responses/DashboardOverviewResponse" + } + }, + "x-permissions": [ + "overview:view" + ] + } + }, + "/exports/users": { + "post": { + "operationId": "createUserExportJob", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permission": "export:create", + "x-permissions": [ + "export:create" + ] + } + }, + "/jobs": { + "get": { + "operationId": "listJobs", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permission": "job:view", + "x-permissions": [ + "job:view" + ] + } + }, + "/jobs/{id}/artifact": { + "get": { + "operationId": "downloadJobArtifact", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "$ref": "#/components/parameters/Id" + } + ], + "x-permission": "job:view", + "x-permissions": [ + "job:view" + ] + } + }, + "/jobs/{id}/cancel": { + "post": { + "operationId": "cancelJob", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "$ref": "#/components/parameters/Id" + } + ], + "x-permission": "job:cancel", + "x-permissions": [ + "job:cancel" + ] + } + }, + "/logs/login": { + "get": { + "operationId": "listLoginLogs", + "x-permission": "log:view", + "parameters": [ + { + "$ref": "#/components/parameters/Page" + }, + { + "$ref": "#/components/parameters/PageSize" + }, + { + "$ref": "#/components/parameters/Keyword" + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/LogPageResponse" + } + }, + "x-permissions": [ + "log:view" + ] + } + }, + "/logs/login/export": { + "get": { + "operationId": "exportLoginLogs", + "x-permission": "log:export", + "responses": { + "200": { + "description": "CSV" + } + }, + "x-permissions": [ + "log:export" + ] + } + }, + "/logs/operations": { + "get": { + "operationId": "listOperationLogs", + "x-permission": "log:view", + "parameters": [ + { + "$ref": "#/components/parameters/Page" + }, + { + "$ref": "#/components/parameters/PageSize" + }, + { + "$ref": "#/components/parameters/Keyword" + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/LogPageResponse" + } + }, + "x-permissions": [ + "log:view" + ] + } + }, + "/logs/operations/export": { + "get": { + "operationId": "exportOperationLogs", + "x-permission": "log:export", + "responses": { + "200": { + "description": "CSV" + } + }, + "x-permissions": [ + "log:export" + ] + } + }, + "/navigation/menus": { + "get": { + "operationId": "navigationMenus", + "responses": { + "200": { + "$ref": "#/components/responses/MenuListResponse" + } + } + } + }, + "/notifications": { + "get": { + "operationId": "listNotifications", + "x-permission": "notification:view", + "responses": { + "200": { + "$ref": "#/components/responses/NotificationListResponse" + } + }, + "x-permissions": [ + "notification:view" + ] + } + }, + "/notifications/{id}": { + "delete": { + "operationId": "deleteNotification", + "x-permission": "notification:delete", + "parameters": [ + { + "$ref": "#/components/parameters/Id" + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permissions": [ + "notification:delete" + ] + } + }, + "/notifications/{id}/read": { + "patch": { + "operationId": "markNotificationRead", + "x-permission": "notification:read", + "parameters": [ + { + "$ref": "#/components/parameters/Id" + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/NotificationResponse" + } + }, + "x-permissions": [ + "notification:read" + ] + } + }, + "/notifications/read-all": { + "patch": { + "operationId": "markAllNotificationsRead", + "x-permission": "notification:read-all", + "responses": { + "200": { + "$ref": "#/components/responses/NotificationListResponse" + } + }, + "x-permissions": [ + "notification:read-all" + ] + } + }, + "/permissions": { + "get": { + "operationId": "listPermissions", + "x-permission": "permission:view", + "responses": { + "200": { + "$ref": "#/components/responses/PermissionListResponse" + } + }, + "x-permissions": [ + "permission:view", + "role:permission", + "role:manage" + ] + }, + "post": { + "operationId": "createPermission", + "x-permission": "permission:create", + "requestBody": { + "$ref": "#/components/requestBodies/PermissionRequest" + }, + "responses": { + "200": { + "$ref": "#/components/responses/PermissionResponse" + } + }, + "x-permissions": [ + "permission:create" + ] + } + }, + "/permissions/{id}": { + "patch": { + "operationId": "updatePermission", + "x-permission": "permission:update", + "parameters": [ + { + "$ref": "#/components/parameters/Id" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/PermissionRequest" + }, + "responses": { + "200": { + "$ref": "#/components/responses/PermissionResponse" + } + }, + "x-permissions": [ + "permission:update" + ] + }, + "delete": { + "operationId": "deletePermission", + "x-permission": "permission:delete", + "parameters": [ + { + "$ref": "#/components/parameters/Id" + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permissions": [ + "permission:delete" + ] + } + }, + "/permissions/sync": { + "post": { + "operationId": "syncPermissions", + "x-permission": "permission:sync", + "responses": { + "200": { + "$ref": "#/components/responses/PermissionListResponse" + } + }, + "x-permissions": [ + "permission:sync" + ] + } + }, + "/roles": { + "get": { + "operationId": "listRoles", + "x-permission": "role:view", + "responses": { + "200": { + "$ref": "#/components/responses/RoleListResponse" + } + }, + "x-permissions": [ + "role:view" + ] + }, + "post": { + "operationId": "createRole", + "x-permission": "role:create", + "requestBody": { + "$ref": "#/components/requestBodies/RoleRequest" + }, + "responses": { + "200": { + "$ref": "#/components/responses/RoleResponse" + } + }, + "x-permissions": [ + "role:create", + "role:manage" + ] + } + }, + "/roles/{id}": { + "patch": { + "operationId": "updateRole", + "x-permission": "role:update", + "parameters": [ + { + "$ref": "#/components/parameters/Id" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/RoleRequest" + }, + "responses": { + "200": { + "$ref": "#/components/responses/RoleResponse" + } + }, + "x-permissions": [ + "role:update", + "role:manage" + ] + }, + "delete": { + "operationId": "deleteRole", + "x-permission": "role:delete", + "parameters": [ + { + "$ref": "#/components/parameters/Id" + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permissions": [ + "role:delete", + "role:manage" + ] + } + }, + "/roles/{id}/data-scopes": { + "get": { + "operationId": "getRoleDataScopes", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "$ref": "#/components/parameters/Id" + } + ], + "x-permission": "role:data-scope", + "x-permissions": [ + "role:data-scope" + ] + }, + "put": { + "operationId": "replaceRoleDataScopes", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "$ref": "#/components/parameters/Id" + } + ], + "x-permission": "role:data-scope", + "x-permissions": [ + "role:data-scope" + ] + } + }, + "/roles/{id}/permissions": { + "put": { + "operationId": "replaceRolePermissions", + "x-permission": "role:permission", + "parameters": [ + { + "$ref": "#/components/parameters/Id" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/RolePermissionRequest" + }, + "responses": { + "200": { + "$ref": "#/components/responses/RoleResponse" + } + }, + "x-permissions": [ + "role:permission", + "role:manage" + ] + } + }, + "/search": { + "get": { + "operationId": "search", + "parameters": [ + { + "$ref": "#/components/parameters/Keyword" + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/SearchResponse" + } + } + } + }, + "/system/menus": { + "get": { + "operationId": "listSystemMenus", + "x-permission": "menu:view", + "responses": { + "200": { + "$ref": "#/components/responses/MenuListResponse" + } + }, + "x-permissions": [ + "menu:view" + ] + }, + "post": { + "operationId": "createMenu", + "x-permission": "menu:create", + "requestBody": { + "$ref": "#/components/requestBodies/MenuRequest" + }, + "responses": { + "200": { + "$ref": "#/components/responses/MenuResponse" + } + }, + "x-permissions": [ + "menu:create" + ] + } + }, + "/system/menus/{id}": { + "patch": { + "operationId": "updateMenu", + "x-permission": "menu:update", + "parameters": [ + { + "$ref": "#/components/parameters/Id" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/MenuRequest" + }, + "responses": { + "200": { + "$ref": "#/components/responses/MenuResponse" + } + }, + "x-permissions": [ + "menu:update" + ] + }, + "delete": { + "operationId": "deleteMenu", + "x-permission": "menu:delete", + "parameters": [ + { + "$ref": "#/components/parameters/Id" + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permissions": [ + "menu:delete" + ] + } + }, + "/system/menus/{id}/visible": { + "patch": { + "operationId": "updateMenuVisible", + "x-permission": "menu:visible", + "parameters": [ + { + "$ref": "#/components/parameters/Id" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/MenuVisibleRequest" + }, + "responses": { + "200": { + "$ref": "#/components/responses/MenuResponse" + } + }, + "x-permissions": [ + "menu:visible" + ] + } + }, + "/system/menus/sort": { + "put": { + "operationId": "sortMenus", + "x-permission": "menu:sort", + "requestBody": { + "$ref": "#/components/requestBodies/MenuSortRequest" + }, + "responses": { + "200": { + "$ref": "#/components/responses/MenuListResponse" + } + }, + "x-permissions": [ + "menu:sort" + ] + } + }, + "/users": { + "get": { + "operationId": "listUsers", + "x-permission": "user:view", + "parameters": [ + { + "$ref": "#/components/parameters/Page" + }, + { + "$ref": "#/components/parameters/PageSize" + }, + { + "$ref": "#/components/parameters/Keyword" + }, + { + "name": "role", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/Status" + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/UserPageResponse" + } + }, + "x-permissions": [ + "user:view" + ] + }, + "post": { + "operationId": "createUser", + "x-permission": "user:create", + "requestBody": { + "$ref": "#/components/requestBodies/UserRequest" + }, + "responses": { + "200": { + "$ref": "#/components/responses/CreateUserResponse" + } + }, + "x-permissions": [ + "user:create" + ] + } + }, + "/users/{id}": { + "get": { + "operationId": "getUser", + "x-permission": "user:view", + "parameters": [ + { + "$ref": "#/components/parameters/Id" + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/UserResponse" + } + }, + "x-permissions": [ + "user:view" + ] + }, + "patch": { + "operationId": "updateUser", + "x-permission": "user:update", + "parameters": [ + { + "$ref": "#/components/parameters/Id" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/UserRequest" + }, + "responses": { + "200": { + "$ref": "#/components/responses/UserResponse" + } + }, + "x-permissions": [ + "user:update" + ] + } + }, + "/users/{id}/reset-password": { + "post": { + "operationId": "resetUserPassword", + "x-permission": "user:reset-password", + "parameters": [ + { + "$ref": "#/components/parameters/Id" + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/ResetPasswordResponse" + } + }, + "x-permissions": [ + "user:reset-password" + ] + } + }, + "/users/{id}/status": { + "patch": { + "operationId": "updateUserStatus", + "x-permission": "user:status", + "parameters": [ + { + "$ref": "#/components/parameters/Id" + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/StatusRequest" + }, + "responses": { + "200": { + "$ref": "#/components/responses/UserResponse" + } + }, + "x-permissions": [ + "user:status" + ] + } + }, + "/users/batch/status": { + "post": { + "operationId": "batchUpdateUserStatus", + "x-permission": "user:status", + "requestBody": { + "$ref": "#/components/requestBodies/BatchStatusRequest" + }, + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permissions": [ + "user:status" + ] + } + }, + "/users/export": { + "get": { + "operationId": "exportUsers", + "x-permission": "user:export", + "responses": { + "200": { + "description": "CSV" + } + }, + "x-permissions": [ + "user:export" + ] + } + } + }, + "components": { + "parameters": { + "AgencyId": { + "name": "agency_id", + "in": "query", + "schema": { + "type": "integer" + } + }, + "Id": { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + "Keyword": { + "name": "keyword", + "in": "query", + "schema": { + "type": "string" + } + }, + "Page": { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1 + } + }, + "PageSize": { + "name": "page_size", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1 + } + }, + "ParentBdUserId": { + "name": "parent_bd_user_id", + "in": "query", + "schema": { + "type": "integer" + } + }, + "ParentLeaderUserId": { + "name": "parent_leader_user_id", + "in": "query", + "schema": { + "type": "integer" + } + }, + "RegionId": { + "name": "region_id", + "in": "query", + "schema": { + "type": "integer" + } + }, + "Status": { + "name": "status", + "in": "query", + "schema": { + "type": "string" + } + } + }, + "requestBodies": { + "BatchStatusRequest": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ids", + "status" + ], + "properties": { + "ids": { + "type": "array", + "items": { + "type": "integer" + } + }, + "status": { + "type": "string" + } + } + } + } + } + }, + "ChangePasswordRequest": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "oldPassword", + "newPassword" + ], + "properties": { + "oldPassword": { + "type": "string" + }, + "newPassword": { + "type": "string" + } + } + } + } + } + }, + "LoginRequest": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "username", + "password" + ], + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + } + } + } + } + } + }, + "MenuRequest": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MenuInput" + } + } + } + }, + "MenuSortRequest": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "items" + ], + "properties": { + "items": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "sort" + ], + "properties": { + "id": { + "type": "integer" + }, + "sort": { + "type": "integer" + } + } + } + } + } + } + } + } + }, + "MenuVisibleRequest": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "visible" + ], + "properties": { + "visible": { + "type": "boolean" + } + } + } + } + } + }, + "PermissionRequest": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PermissionInput" + } + } + } + }, + "RolePermissionRequest": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "permissionIds" + ], + "properties": { + "permissionIds": { + "type": "array", + "items": { + "type": "integer" + } + } + } + } + } + } + }, + "RoleRequest": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoleInput" + } + } + } + }, + "StatusRequest": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string" + } + } + } + } + } + }, + "UserRequest": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserInput" + } + } + } + } + }, + "responses": { + "AgencyPageResponse": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseAgencyPage" + } + } + } + }, + "BDProfilePageResponse": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseBDProfilePage" + } + } + } + }, + "CreateUserResponse": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseCreateUserResult" + } + } + } + }, + "DashboardOverviewResponse": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseDashboardOverview" + } + } + } + }, + "EmptyResponse": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseEmpty" + } + } + } + }, + "HostProfilePageResponse": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseHostProfilePage" + } + } + } + }, + "LogPageResponse": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseLogPage" + } + } + } + }, + "MenuListResponse": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseMenuList" + } + } + } + }, + "MenuResponse": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseMenu" + } + } + } + }, + "NotificationListResponse": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseNotificationList" + } + } + } + }, + "NotificationResponse": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseNotification" + } + } + } + }, + "PermissionListResponse": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponsePermissionList" + } + } + } + }, + "PermissionResponse": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponsePermission" + } + } + } + }, + "ResetPasswordResponse": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseResetPasswordResult" + } + } + } + }, + "RoleListResponse": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseRoleList" + } + } + } + }, + "RoleResponse": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseRole" + } + } + } + }, + "SearchResponse": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseSearchList" + } + } + } + }, + "SessionResponse": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseSession" + } + } + } + }, + "UserPageResponse": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseUserPage" + } + } + } + }, + "UserResponse": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseUser" + } + } + } + } + }, + "schemas": { + "ApiPageAgency": { + "type": "object", + "required": [ + "items", + "page", + "pageSize", + "total" + ], + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Agency" + } + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "ApiPageBDProfile": { + "type": "object", + "required": [ + "items", + "page", + "pageSize", + "total" + ], + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BDProfile" + } + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "ApiPageHostProfile": { + "type": "object", + "required": [ + "items", + "page", + "pageSize", + "total" + ], + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HostProfile" + } + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "ApiPageLog": { + "type": "object", + "required": [ + "items", + "page", + "pageSize", + "total" + ], + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Log" + } + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "ApiPageUser": { + "type": "object", + "required": [ + "items", + "page", + "pageSize", + "total" + ], + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AdminUser" + } + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "ApiResponseCreateUserResult": { + "allOf": [ + { + "$ref": "#/components/schemas/Envelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/CreateUserResult" + } + } + } + ] + }, + "ApiResponseAgencyPage": { + "allOf": [ + { + "$ref": "#/components/schemas/Envelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/ApiPageAgency" + } + } + } + ] + }, + "ApiResponseBDProfilePage": { + "allOf": [ + { + "$ref": "#/components/schemas/Envelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/ApiPageBDProfile" + } + } + } + ] + }, + "ApiResponseDashboardOverview": { + "allOf": [ + { + "$ref": "#/components/schemas/Envelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/DashboardOverview" + } + } + } + ] + }, + "ApiResponseEmpty": { + "$ref": "#/components/schemas/Envelope" + }, + "ApiResponseHostProfilePage": { + "allOf": [ + { + "$ref": "#/components/schemas/Envelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/ApiPageHostProfile" + } + } + } + ] + }, + "ApiResponseLogPage": { + "allOf": [ + { + "$ref": "#/components/schemas/Envelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/ApiPageLog" + } + } + } + ] + }, + "ApiResponseMenu": { + "allOf": [ + { + "$ref": "#/components/schemas/Envelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/Menu" + } + } + } + ] + }, + "ApiResponseMenuList": { + "allOf": [ + { + "$ref": "#/components/schemas/Envelope" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Menu" + } + } + } + } + ] + }, + "ApiResponseNotification": { + "allOf": [ + { + "$ref": "#/components/schemas/Envelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/Notification" + } + } + } + ] + }, + "ApiResponseNotificationList": { + "allOf": [ + { + "$ref": "#/components/schemas/Envelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/NotificationList" + } + } + } + ] + }, + "ApiResponsePermission": { + "allOf": [ + { + "$ref": "#/components/schemas/Envelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/Permission" + } + } + } + ] + }, + "ApiResponsePermissionList": { + "allOf": [ + { + "$ref": "#/components/schemas/Envelope" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Permission" + } + } + } + } + ] + }, + "ApiResponseResetPasswordResult": { + "allOf": [ + { + "$ref": "#/components/schemas/Envelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/ResetPasswordResult" + } + } + } + ] + }, + "ApiResponseRole": { + "allOf": [ + { + "$ref": "#/components/schemas/Envelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/Role" + } + } + } + ] + }, + "ApiResponseRoleList": { + "allOf": [ + { + "$ref": "#/components/schemas/Envelope" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Role" + } + } + } + } + ] + }, + "ApiResponseSearchList": { + "allOf": [ + { + "$ref": "#/components/schemas/Envelope" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SearchResult" + } + } + } + } + ] + }, + "ApiResponseSession": { + "allOf": [ + { + "$ref": "#/components/schemas/Envelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/Session" + } + } + } + ] + }, + "ApiResponseUser": { + "allOf": [ + { + "$ref": "#/components/schemas/Envelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AdminUser" + } + } + } + ] + }, + "ApiResponseUserPage": { + "allOf": [ + { + "$ref": "#/components/schemas/Envelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/ApiPageUser" + } + } + } + ] + }, + "AdminUser": { + "type": "object", + "required": [ + "account", + "id", + "name", + "status" + ], + "properties": { + "account": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "lastLogin": { + "type": "string" + }, + "mfa": { + "type": "string" + }, + "mfaEnabled": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "role": { + "type": "string" + }, + "roleIds": { + "type": "array", + "items": { + "type": "integer" + } + }, + "status": { + "type": "string" + }, + "team": { + "type": "string" + } + } + }, + "Agency": { + "type": "object", + "required": [ + "agencyId" + ], + "properties": { + "agencyId": { + "type": "integer" + }, + "createdAtMs": { + "type": "integer" + }, + "createdByUserId": { + "type": "integer" + }, + "joinEnabled": { + "type": "boolean" + }, + "maxHosts": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "ownerUserId": { + "type": "integer" + }, + "parentBdUserId": { + "type": "integer" + }, + "regionId": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "updatedAtMs": { + "type": "integer" + } + } + }, + "BDProfile": { + "type": "object", + "required": [ + "userId" + ], + "properties": { + "createdAtMs": { + "type": "integer" + }, + "createdByUserId": { + "type": "integer" + }, + "parentLeaderUserId": { + "type": "integer" + }, + "regionId": { + "type": "integer" + }, + "role": { + "type": "string" + }, + "status": { + "type": "string" + }, + "updatedAtMs": { + "type": "integer" + }, + "userId": { + "type": "integer" + } + } + }, + "CreateUserResult": { + "type": "object", + "properties": { + "initialPassword": { + "type": "string" + } + } + }, + "DashboardOverview": { + "type": "object", + "additionalProperties": true + }, + "Envelope": { + "type": "object", + "properties": { + "code": { + "type": "integer" + }, + "data": {}, + "message": { + "type": "string" + } + } + }, + "HostProfile": { + "type": "object", + "required": [ + "userId" + ], + "properties": { + "createdAtMs": { + "type": "integer" + }, + "currentAgencyId": { + "type": "integer" + }, + "currentMembershipId": { + "type": "integer" + }, + "firstBecameHostAtMs": { + "type": "integer" + }, + "regionId": { + "type": "integer" + }, + "source": { + "type": "string" + }, + "status": { + "type": "string" + }, + "updatedAtMs": { + "type": "integer" + }, + "userId": { + "type": "integer" + } + } + }, + "Log": { + "type": "object", + "additionalProperties": true + }, + "Menu": { + "type": "object", + "additionalProperties": true + }, + "MenuInput": { + "type": "object", + "required": [ + "code", + "label" + ], + "additionalProperties": true + }, + "Notification": { + "type": "object", + "additionalProperties": true + }, + "NotificationList": { + "type": "object", + "required": [ + "items", + "unread" + ], + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Notification" + } + }, + "unread": { + "type": "integer" + } + } + }, + "Permission": { + "type": "object", + "additionalProperties": true + }, + "PermissionInput": { + "type": "object", + "required": [ + "code", + "kind", + "name" + ], + "additionalProperties": true + }, + "ResetPasswordResult": { + "type": "object", + "properties": { + "initialPassword": { + "type": "string" + } + } + }, + "Role": { + "type": "object", + "additionalProperties": true + }, + "RoleInput": { + "type": "object", + "required": [ + "code", + "name" + ], + "additionalProperties": true + }, + "SearchResult": { + "type": "object", + "additionalProperties": true + }, + "Session": { + "type": "object", + "additionalProperties": true + }, + "UserInput": { + "type": "object", + "required": [ + "name" + ], + "additionalProperties": true + } + } + } +} diff --git a/eslint.config.js b/eslint.config.js index 5619615..63cbe5b 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -23,10 +23,17 @@ export default [ rules: { ...reactHooks.configs.recommended.rules, "no-unused-vars": ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }], - "react-hooks/exhaustive-deps": "off", "react-hooks/set-state-in-effect": "off", "react-hooks/use-memo": "off", "react-refresh/only-export-components": "off" } + }, + { + files: ["scripts/**/*.{js,mjs}"], + languageOptions: { + ecmaVersion: "latest", + globals: globals.node, + sourceType: "module" + } } ]; diff --git a/package.json b/package.json index c7f6ae7..1726df6 100644 --- a/package.json +++ b/package.json @@ -3,13 +3,19 @@ "version": "0.1.0", "private": true, "type": "module", - "packageManager": "pnpm@9.15.9", + "packageManager": "pnpm@10.33.2+sha512.a90faf6feeab71ad6c6e57f94e0fe1a12f5dcc22cd754db40ae9593eb6a3e0b6b12e3540218bb37ae083404b1f2ce6db2a4121e979829b4aff94b99f49da1cf8", "scripts": { "dev": "vite --host 0.0.0.0", "build": "vite build", + "check:contracts": "tsx scripts/validate-admin-architecture.ts", + "gen:api": "node scripts/sync-openapi-from-backend.mjs && openapi-typescript contracts/admin-openapi.json -o src/shared/api/generated/schema.d.ts && node scripts/generate-api-client.mjs", + "gen:module": "node scripts/generate-admin-module.mjs", "lint": "eslint .", "format": "prettier --write .", + "scaffold:feature": "node scripts/scaffold-feature.mjs", + "sync:contract": "node scripts/sync-openapi-from-backend.mjs", "test": "vitest run", + "typecheck": "tsc --noEmit", "preview": "vite preview --host 0.0.0.0" }, "dependencies": { @@ -17,22 +23,33 @@ "@emotion/styled": "11.14.1", "@mui/icons-material": "9.0.0", "@mui/material": "9.0.0", + "@tanstack/react-query": "^5.100.6", "echarts": "6.0.0", + "libpag": "4.5.52", "react": "19.2.5", "react-dom": "19.2.5", - "react-router-dom": "^7.14.2" + "react-router-dom": "^7.14.2", + "svgaplayerweb": "2.3.2", + "zod": "^4.4.1" }, "devDependencies": { "@eslint/js": "^10.0.1", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", + "@types/node": "^25.6.0", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "6.0.1", "eslint": "^10.2.1", "eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-refresh": "^0.5.2", "globals": "^17.5.0", "jsdom": "^29.1.1", + "openapi-typescript": "^7.13.0", "prettier": "^3.8.3", + "tsx": "^4.21.0", + "typescript": "^5.9.3", "vite": "8.0.10", "vitest": "^4.1.5" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 200e7f5..22758b6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,9 +20,15 @@ importers: '@mui/material': specifier: 9.0.0 version: 9.0.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@tanstack/react-query': + specifier: ^5.100.6 + version: 5.100.6(react@19.2.5) echarts: specifier: 6.0.0 version: 6.0.0 + libpag: + specifier: 4.5.52 + version: 4.5.52 react: specifier: 19.2.5 version: 19.2.5 @@ -32,6 +38,12 @@ importers: react-router-dom: specifier: ^7.14.2 version: 7.14.2(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + svgaplayerweb: + specifier: 2.3.2 + version: 2.3.2 + zod: + specifier: ^4.4.1 + version: 4.4.1 devDependencies: '@eslint/js': specifier: ^10.0.1 @@ -41,10 +53,22 @@ importers: version: 6.9.1 '@testing-library/react': specifier: ^16.3.2 - version: 16.3.2(@testing-library/dom@10.4.1)(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@testing-library/user-event': + specifier: ^14.6.1 + version: 14.6.1(@testing-library/dom@10.4.1) + '@types/node': + specifier: ^25.6.0 + version: 25.6.0 + '@types/react': + specifier: ^19.2.14 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.14) '@vitejs/plugin-react': specifier: 6.0.1 - version: 6.0.1(vite@8.0.10) + version: 6.0.1(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0)) eslint: specifier: ^10.2.1 version: 10.2.1 @@ -60,15 +84,24 @@ importers: jsdom: specifier: ^29.1.1 version: 29.1.1 + openapi-typescript: + specifier: ^7.13.0 + version: 7.13.0(typescript@5.9.3) prettier: specifier: ^3.8.3 version: 3.8.3 + tsx: + specifier: ^4.21.0 + version: 4.21.0 + typescript: + specifier: ^5.9.3 + version: 5.9.3 vite: specifier: 8.0.10 - version: 8.0.10 + version: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0) vitest: specifier: ^4.1.5 - version: 4.1.5(jsdom@29.1.1)(vite@8.0.10) + version: 4.1.5(@types/node@25.6.0)(jsdom@29.1.1)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0)) packages: @@ -264,6 +297,162 @@ packages: '@emotion/weak-memoize@0.4.0': resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==} + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.9.1': resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -451,6 +640,16 @@ packages: '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + '@redocly/ajv@8.11.2': + resolution: {integrity: sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==} + + '@redocly/config@0.22.0': + resolution: {integrity: sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ==} + + '@redocly/openapi-core@1.34.14': + resolution: {integrity: sha512-y+xFx+Zz54Xhr8jUdnLENYnt7Y7GEDL6Q03ga7rTtX8DVwefX9H+hQEPgJp1nda7vdH+wJ9/HBVvyfBuW9x6rA==} + engines: {node: '>=18.17.0', npm: '>=9.5.0'} + '@rolldown/binding-android-arm64@1.0.0-rc.17': resolution: {integrity: sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -486,36 +685,42 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': resolution: {integrity: sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': resolution: {integrity: sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': resolution: {integrity: sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': resolution: {integrity: sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': resolution: {integrity: sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': resolution: {integrity: sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==} @@ -549,6 +754,14 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@tanstack/query-core@5.100.6': + resolution: {integrity: sha512-Os2CPUr98to98RYm+D4qGqGkiffn7MGSyl2547a4MljVkHE30AMJRqTiyCqBfMwzAx/I91vCkAxp5tHSla6Twg==} + + '@tanstack/react-query@5.100.6': + resolution: {integrity: sha512-uVSrps0PV16Cxmcn2rvL+dUhwTpTUtiRW347AEeYxMZXO2pZe9ja7E24PAMGoQ5u2g89DD8u4QhOviBk+RN8RA==} + peerDependencies: + react: ^18 || ^19 + '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} engines: {node: '>=18'} @@ -572,6 +785,12 @@ packages: '@types/react-dom': optional: true + '@testing-library/user-event@14.6.1': + resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} @@ -593,12 +812,20 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/node@25.6.0': + resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==} + '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} '@types/prop-types@15.7.15': resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + '@types/react-transition-group@4.4.12': resolution: {integrity: sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==} peerDependencies: @@ -649,6 +876,14 @@ packages: '@vitest/utils@4.1.5': resolution: {integrity: sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==} + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -659,9 +894,17 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + ajv@6.15.0: resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -670,6 +913,9 @@ packages: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} @@ -685,6 +931,9 @@ packages: resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} engines: {node: '>=10', npm: '>=6'} + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} @@ -697,6 +946,13 @@ packages: bidi-js@1.0.3: resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + engines: {node: '>=18'} + + brace-expansion@2.1.0: + resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} + brace-expansion@5.0.5: resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} engines: {node: 18 || 20 || >=22} @@ -706,6 +962,18 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -717,16 +985,38 @@ packages: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + colorette@1.4.0: + resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} + + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + convert-source-map@1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + cookie@1.1.1: resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} @@ -768,6 +1058,10 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -785,12 +1079,23 @@ packages: dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + echarts@6.0.0: resolution: {integrity: sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==} + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + electron-to-chromium@1.5.345: resolution: {integrity: sha512-F9JXQGiMrz6yVNPI2qOVPvB9HzjH5cGzhs8oJ6A28V5L/YnzN/0KsuiibqF+F1Fd9qxFzD1BUnYSd8JfULxTwg==} + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + entities@8.0.0: resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} engines: {node: '>=20.19.0'} @@ -798,6 +1103,10 @@ packages: error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + es-errors@1.3.0: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} @@ -805,10 +1114,22 @@ packages: es-module-lexer@2.1.0: resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -869,10 +1190,22 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -895,6 +1228,10 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + find-root@1.1.0: resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} @@ -909,6 +1246,14 @@ packages: flatted@3.4.2: resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -921,6 +1266,17 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} @@ -929,6 +1285,14 @@ packages: resolution: {integrity: sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==} engines: {node: '>=18'} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + hasown@2.0.3: resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} engines: {node: '>= 0.4'} @@ -946,6 +1310,18 @@ packages: resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -962,6 +1338,17 @@ packages: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} + index-to-position@1.2.0: + resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} + engines: {node: '>=18'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} @@ -980,12 +1367,23 @@ packages: is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + js-levenshtein@1.1.6: + resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} + engines: {node: '>=0.10.0'} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + jsdom@29.1.1: resolution: {integrity: sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==} engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} @@ -1009,6 +1407,9 @@ packages: json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -1024,6 +1425,9 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + libpag@4.5.52: + resolution: {integrity: sha512-b5cWKNSKCAGv/Tm6IR8nWvAE/2PmHzTeXz+mX8rqf2AgOO5BH5PGJptSElrawTYY31fB6m0wGRDGpfgVwJLOtA==} + lightningcss-android-arm64@1.32.0: resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} engines: {node: '>= 12.0.0'} @@ -1059,24 +1463,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} @@ -1119,9 +1527,29 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + mdn-data@2.27.1: resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -1130,6 +1558,10 @@ packages: resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} engines: {node: 18 || 20 || >=22} + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1141,6 +1573,10 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + node-releases@2.0.38: resolution: {integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==} @@ -1148,9 +1584,26 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + openapi-typescript@7.13.0: + resolution: {integrity: sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ==} + hasBin: true + peerDependencies: + typescript: ^5.x + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1171,9 +1624,17 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + parse-json@8.3.0: + resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} + engines: {node: '>=18'} + parse5@8.0.1: resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -1185,6 +1646,9 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -1199,6 +1663,10 @@ packages: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + postcss@8.5.12: resolution: {integrity: sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==} engines: {node: ^10 || ^12 || >=14} @@ -1219,10 +1687,26 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + qs@6.15.1: + resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} + engines: {node: '>=0.6'} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + react-dom@19.2.5: resolution: {integrity: sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==} peerDependencies: @@ -1276,6 +1760,9 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve@1.22.12: resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} engines: {node: '>= 0.4'} @@ -1286,6 +1773,13 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + saxes@6.0.0: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} @@ -1297,9 +1791,20 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + set-cookie-parser@2.7.2: resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1308,6 +1813,22 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -1322,6 +1843,10 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + std-env@4.1.0: resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} @@ -1332,10 +1857,17 @@ packages: stylis@4.2.0: resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} + supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + svgaplayerweb@2.3.2: + resolution: {integrity: sha512-QuTvNIgy3W6Mi4h74SczEHUtAwb8m3ax7Ai7xRLUuN6hjJh49RGtWOWq1IuF2I7ECcl0HAYn8FcTn99UDz9UiQ==} + symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} @@ -1361,6 +1893,10 @@ packages: resolution: {integrity: sha512-JIXCerhudr/N6OWLwLF1HVsTTUo7ry6qHa5eWZEkiMuxsIiAACL55tGLfqfHfoH7QaMQUW8fngD7u7TxWexYQg==} hasBin: true + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + tough-cookie@6.0.1: resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} engines: {node: '>=16'} @@ -1375,23 +1911,55 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.19.2: + resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} + undici@7.25.0: resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==} engines: {node: '>=20.18.1'} + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + update-browserslist-db@1.2.3: resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' + uri-js-replace@1.0.1: + resolution: {integrity: sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==} + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + vite@8.0.10: resolution: {integrity: sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1506,6 +2074,9 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + xml-name-validator@5.0.0: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} @@ -1516,10 +2087,17 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yaml-ast-parser@0.0.43: + resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==} + yaml@1.10.3: resolution: {integrity: sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==} engines: {node: '>= 6'} + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -1581,7 +2159,7 @@ snapshots: '@babel/types': 7.29.0 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -1653,7 +2231,7 @@ snapshots: '@babel/parser': 7.29.2 '@babel/template': 7.28.6 '@babel/types': 7.29.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) transitivePeerDependencies: - supports-color @@ -1789,6 +2367,84 @@ snapshots: '@emotion/weak-memoize@0.4.0': {} + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + '@eslint-community/eslint-utils@4.9.1(eslint@10.2.1)': dependencies: eslint: 10.2.1 @@ -1799,7 +2455,7 @@ snapshots: '@eslint/config-array@0.23.5': dependencies: '@eslint/object-schema': 3.0.5 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) minimatch: 10.2.5 transitivePeerDependencies: - supports-color @@ -1958,6 +2614,29 @@ snapshots: '@popperjs/core@2.11.8': {} + '@redocly/ajv@8.11.2': + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js-replace: 1.0.1 + + '@redocly/config@0.22.0': {} + + '@redocly/openapi-core@1.34.14(supports-color@10.2.2)': + dependencies: + '@redocly/ajv': 8.11.2 + '@redocly/config': 0.22.0 + colorette: 1.4.0 + https-proxy-agent: 7.0.6(supports-color@10.2.2) + js-levenshtein: 1.1.6 + js-yaml: 4.1.1 + minimatch: 5.1.9 + pluralize: 8.0.0 + yaml-ast-parser: 0.0.43 + transitivePeerDependencies: + - supports-color + '@rolldown/binding-android-arm64@1.0.0-rc.17': optional: true @@ -2013,6 +2692,13 @@ snapshots: '@standard-schema/spec@1.1.0': {} + '@tanstack/query-core@5.100.6': {} + + '@tanstack/react-query@5.100.6(react@19.2.5)': + dependencies: + '@tanstack/query-core': 5.100.6 + react: 19.2.5 + '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.29.0 @@ -2033,7 +2719,7 @@ snapshots: picocolors: 1.1.1 redent: 3.0.0 - '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: '@babel/runtime': 7.29.2 '@testing-library/dom': 10.4.1 @@ -2041,6 +2727,11 @@ snapshots: react-dom: 19.2.5(react@19.2.5) optionalDependencies: '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': + dependencies: + '@testing-library/dom': 10.4.1 '@tybys/wasm-util@0.10.1': dependencies: @@ -2062,10 +2753,18 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/node@25.6.0': + dependencies: + undici-types: 7.19.2 + '@types/parse-json@4.0.2': {} '@types/prop-types@15.7.15': {} + '@types/react-dom@19.2.3(@types/react@19.2.14)': + dependencies: + '@types/react': 19.2.14 + '@types/react-transition-group@4.4.12(@types/react@19.2.14)': dependencies: '@types/react': 19.2.14 @@ -2074,10 +2773,10 @@ snapshots: dependencies: csstype: 3.2.3 - '@vitejs/plugin-react@6.0.1(vite@8.0.10)': + '@vitejs/plugin-react@6.0.1(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.7 - vite: 8.0.10 + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0) '@vitest/expect@4.1.5': dependencies: @@ -2088,13 +2787,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.5(vite@8.0.10)': + '@vitest/mocker@4.1.5(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0))': dependencies: '@vitest/spy': 4.1.5 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.10 + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0) '@vitest/pretty-format@4.1.5': dependencies: @@ -2120,12 +2819,23 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.0 + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + acorn-jsx@5.3.2(acorn@8.16.0): dependencies: acorn: 8.16.0 acorn@8.16.0: {} + agent-base@7.1.4: {} + ajv@6.15.0: dependencies: fast-deep-equal: 3.1.3 @@ -2133,10 +2843,14 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ansi-colors@4.1.3: {} + ansi-regex@5.0.1: {} ansi-styles@5.2.0: {} + argparse@2.0.1: {} + aria-query@5.3.0: dependencies: dequal: 2.0.3 @@ -2151,6 +2865,8 @@ snapshots: cosmiconfig: 7.1.0 resolve: 1.22.12 + balanced-match@1.0.2: {} + balanced-match@4.0.4: {} baseline-browser-mapping@2.10.24: {} @@ -2159,6 +2875,24 @@ snapshots: dependencies: require-from-string: 2.0.2 + body-parser@2.2.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3(supports-color@10.2.2) + http-errors: 2.0.1 + iconv-lite: 0.7.2 + on-finished: 2.4.1 + qs: 6.15.1 + raw-body: 3.0.2 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + + brace-expansion@2.1.0: + dependencies: + balanced-match: 1.0.2 + brace-expansion@5.0.5: dependencies: balanced-match: 4.0.4 @@ -2171,18 +2905,42 @@ snapshots: node-releases: 2.0.38 update-browserslist-db: 1.2.3(browserslist@4.28.2) + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + callsites@3.1.0: {} caniuse-lite@1.0.30001791: {} chai@6.2.2: {} + change-case@5.4.4: {} + clsx@2.1.1: {} + colorette@1.4.0: {} + + content-disposition@1.1.0: {} + + content-type@1.0.5: {} + convert-source-map@1.9.0: {} convert-source-map@2.0.0: {} + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + cookie@1.1.1: {} cosmiconfig@7.1.0: @@ -2215,14 +2973,18 @@ snapshots: transitivePeerDependencies: - '@noble/hashes' - debug@4.4.3: + debug@4.4.3(supports-color@10.2.2): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 10.2.2 decimal.js@10.6.0: {} deep-is@0.1.4: {} + depd@2.0.0: {} + dequal@2.0.3: {} detect-libc@2.1.2: {} @@ -2236,25 +2998,72 @@ snapshots: '@babel/runtime': 7.29.2 csstype: 3.2.3 + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + echarts@6.0.0: dependencies: tslib: 2.3.0 zrender: 6.0.0 + ee-first@1.1.1: {} + electron-to-chromium@1.5.345: {} + encodeurl@2.0.0: {} + entities@8.0.0: {} error-ex@1.3.4: dependencies: is-arrayish: 0.2.1 + es-define-property@1.0.1: {} + es-errors@1.3.0: {} es-module-lexer@2.1.0: {} + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + escalade@3.2.0: {} + escape-html@1.0.3: {} + escape-string-regexp@4.0.0: {} eslint-plugin-react-hooks@7.1.1(eslint@10.2.1): @@ -2297,7 +3106,7 @@ snapshots: '@types/estree': 1.0.8 ajv: 6.15.0 cross-spawn: 7.0.6 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) escape-string-regexp: 4.0.0 eslint-scope: 9.1.2 eslint-visitor-keys: 5.0.1 @@ -2340,8 +3149,45 @@ snapshots: esutils@2.0.3: {} + etag@1.8.1: {} + + event-target-shim@5.0.1: {} + expect-type@1.3.0: {} + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.2 + content-disposition: 1.1.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3(supports-color@10.2.2) + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.15.1 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + fast-deep-equal@3.1.3: {} fast-json-stable-stringify@2.1.0: {} @@ -2356,6 +3202,17 @@ snapshots: dependencies: flat-cache: 4.0.1 + finalhandler@2.1.1: + dependencies: + debug: 4.4.3(supports-color@10.2.2) + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + find-root@1.1.0: {} find-up@5.0.0: @@ -2370,6 +3227,10 @@ snapshots: flatted@3.4.2: {} + forwarded@0.2.0: {} + + fresh@2.0.0: {} + fsevents@2.3.3: optional: true @@ -2377,12 +3238,38 @@ snapshots: gensync@1.0.0-beta.2: {} + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.3 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-tsconfig@4.14.0: + dependencies: + resolve-pkg-maps: 1.0.0 + glob-parent@6.0.2: dependencies: is-glob: 4.0.3 globals@17.5.0: {} + gopd@1.2.0: {} + + has-symbols@1.1.0: {} + hasown@2.0.3: dependencies: function-bind: 1.1.2 @@ -2403,6 +3290,25 @@ snapshots: transitivePeerDependencies: - '@noble/hashes' + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + https-proxy-agent@7.0.6(supports-color@10.2.2): + dependencies: + agent-base: 7.1.4 + debug: 4.4.3(supports-color@10.2.2) + transitivePeerDependencies: + - supports-color + + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + ignore@5.3.2: {} import-fresh@3.3.1: @@ -2414,6 +3320,12 @@ snapshots: indent-string@4.0.0: {} + index-to-position@1.2.0: {} + + inherits@2.0.4: {} + + ipaddr.js@1.9.1: {} + is-arrayish@0.2.1: {} is-core-module@2.16.1: @@ -2428,10 +3340,18 @@ snapshots: is-potential-custom-element-name@1.0.1: {} + is-promise@4.0.0: {} + isexe@2.0.0: {} + js-levenshtein@1.1.6: {} + js-tokens@4.0.0: {} + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + jsdom@29.1.1: dependencies: '@asamuzakjp/css-color': 5.1.11 @@ -2466,6 +3386,8 @@ snapshots: json-schema-traverse@0.4.1: {} + json-schema-traverse@1.0.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} json5@2.2.3: {} @@ -2479,6 +3401,14 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + libpag@4.5.52: + dependencies: + abort-controller: 3.0.0 + cross-spawn: 7.0.6 + express: 5.2.1 + transitivePeerDependencies: + - supports-color + lightningcss-android-arm64@1.32.0: optional: true @@ -2550,26 +3480,64 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + math-intrinsics@1.1.0: {} + mdn-data@2.27.1: {} + media-typer@1.1.0: {} + + merge-descriptors@2.0.0: {} + + mime-db@1.54.0: {} + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + min-indent@1.0.1: {} minimatch@10.2.5: dependencies: brace-expansion: 5.0.5 + minimatch@5.1.9: + dependencies: + brace-expansion: 2.1.0 + ms@2.1.3: {} nanoid@3.3.11: {} natural-compare@1.4.0: {} + negotiator@1.0.0: {} + node-releases@2.0.38: {} object-assign@4.1.1: {} + object-inspect@1.13.4: {} + obug@2.1.1: {} + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + openapi-typescript@7.13.0(typescript@5.9.3): + dependencies: + '@redocly/openapi-core': 1.34.14(supports-color@10.2.2) + ansi-colors: 4.1.3 + change-case: 5.4.4 + parse-json: 8.3.0 + supports-color: 10.2.2 + typescript: 5.9.3 + yargs-parser: 21.1.1 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -2598,16 +3566,26 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + parse-json@8.3.0: + dependencies: + '@babel/code-frame': 7.29.0 + index-to-position: 1.2.0 + type-fest: 4.41.0 + parse5@8.0.1: dependencies: entities: 8.0.0 + parseurl@1.3.3: {} + path-exists@4.0.0: {} path-key@3.1.1: {} path-parse@1.0.7: {} + path-to-regexp@8.4.2: {} + path-type@4.0.0: {} pathe@2.0.3: {} @@ -2616,6 +3594,8 @@ snapshots: picomatch@4.0.4: {} + pluralize@8.0.0: {} + postcss@8.5.12: dependencies: nanoid: 3.3.11 @@ -2638,8 +3618,26 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + punycode@2.3.1: {} + qs@6.15.1: + dependencies: + side-channel: 1.1.0 + + range-parser@1.2.1: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + react-dom@19.2.5(react@19.2.5): dependencies: react: 19.2.5 @@ -2685,6 +3683,8 @@ snapshots: resolve-from@4.0.0: {} + resolve-pkg-maps@1.0.0: {} + resolve@1.22.12: dependencies: es-errors: 1.3.0 @@ -2713,6 +3713,18 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.17 '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.17 + router@2.2.0: + dependencies: + debug: 4.4.3(supports-color@10.2.2) + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + + safer-buffer@2.1.2: {} + saxes@6.0.0: dependencies: xmlchars: 2.2.0 @@ -2721,14 +3733,69 @@ snapshots: semver@6.3.1: {} + send@1.2.1: + dependencies: + debug: 4.4.3(supports-color@10.2.2) + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + set-cookie-parser@2.7.2: {} + setprototypeof@1.2.0: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 shebang-regex@3.0.0: {} + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} source-map-js@1.2.1: {} @@ -2737,6 +3804,8 @@ snapshots: stackback@0.0.2: {} + statuses@2.0.2: {} + std-env@4.1.0: {} strip-indent@3.0.0: @@ -2745,8 +3814,12 @@ snapshots: stylis@4.2.0: {} + supports-color@10.2.2: {} + supports-preserve-symlinks-flag@1.0.0: {} + svgaplayerweb@2.3.2: {} + symbol-tree@3.2.4: {} tinybench@2.9.0: {} @@ -2766,6 +3839,8 @@ snapshots: dependencies: tldts-core: 7.0.29 + toidentifier@1.0.1: {} + tough-cookie@6.0.1: dependencies: tldts: 7.0.29 @@ -2779,23 +3854,48 @@ snapshots: tslib@2.8.1: optional: true + tsx@4.21.0: + dependencies: + esbuild: 0.27.7 + get-tsconfig: 4.14.0 + optionalDependencies: + fsevents: 2.3.3 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 + type-fest@4.41.0: {} + + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.2 + + typescript@5.9.3: {} + + undici-types@7.19.2: {} + undici@7.25.0: {} + unpipe@1.0.0: {} + update-browserslist-db@1.2.3(browserslist@4.28.2): dependencies: browserslist: 4.28.2 escalade: 3.2.0 picocolors: 1.1.1 + uri-js-replace@1.0.1: {} + uri-js@4.4.1: dependencies: punycode: 2.3.1 - vite@8.0.10: + vary@1.1.2: {} + + vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -2803,12 +3903,15 @@ snapshots: rolldown: 1.0.0-rc.17 tinyglobby: 0.2.16 optionalDependencies: + '@types/node': 25.6.0 + esbuild: 0.27.7 fsevents: 2.3.3 + tsx: 4.21.0 - vitest@4.1.5(jsdom@29.1.1)(vite@8.0.10): + vitest@4.1.5(@types/node@25.6.0)(jsdom@29.1.1)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0)): dependencies: '@vitest/expect': 4.1.5 - '@vitest/mocker': 4.1.5(vite@8.0.10) + '@vitest/mocker': 4.1.5(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0)) '@vitest/pretty-format': 4.1.5 '@vitest/runner': 4.1.5 '@vitest/snapshot': 4.1.5 @@ -2825,9 +3928,10 @@ snapshots: tinyexec: 1.1.2 tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vite: 8.0.10 + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0) why-is-node-running: 2.3.0 optionalDependencies: + '@types/node': 25.6.0 jsdom: 29.1.1 transitivePeerDependencies: - msw @@ -2859,14 +3963,20 @@ snapshots: word-wrap@1.2.5: {} + wrappy@1.0.2: {} + xml-name-validator@5.0.0: {} xmlchars@2.2.0: {} yallist@3.1.1: {} + yaml-ast-parser@0.0.43: {} + yaml@1.10.3: {} + yargs-parser@21.1.1: {} + yocto-queue@0.1.0: {} zod-validation-error@4.0.2(zod@4.4.1): diff --git a/public/vendor/libpag.wasm b/public/vendor/libpag.wasm new file mode 100755 index 0000000000000000000000000000000000000000..576589b7db046bcf0401c7e7e791510c6f93133c GIT binary patch literal 1574939 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� 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

71zJvkP@_x*$1N=Y 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

ZJMMdJ{n%l<&yh&U_8kAx!>_I#) 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%0Abhe~au6@mk{fTL|^mgGGY|Z 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%0D{NE``jrpng(F9@NG?*ah4wZd}_E2yx~E%fh^q& zc}8>0K1Y)qb)Or`^NIdH(ECl=)Op4^M2FWfO=4k;`UpMF>!o9Hsaw(wKroVx60^8k zc+XxAvgEuyK>=%|y*Oz1Ga9BgAjcY%;ezjF)C~BbVK%b&;YLwht+CpMZ%wi(Xq?bN z7(Qz;N@DH4eG<|ehx@mXKB3mhrliM8NS+t*zu%x*dYJt{F{YRsNF$!2!a!(LgD%7*2VAO10eHAQpAexD z`8z|@@wsKS+NNFrwaGSnAjp%wH8DY$o*C|H ziu+B0#Xd0`7|7KW#)^NM{#@55x{;z;X7QbE(#>ecO4BuKIfmVy>p*$Ee)%jD!Px17 zd#WS#6;Zz|c9$rsP0yvPVKQwa`ZB7yt7w=Iv6sAQH;w^RBl_^XylJyRirI)uqVEh} zvtcQfr)|{`=E4F4vQH3tV)EQB{i6Yhqk>c`A)<}aD8fRItF&jCY3g~dv`RU zwBGbznpU((^h!t|sZ=vO5UoJJLpeyWiO7hCE|y`J4FuP~Zej7Dj*b2hT7k&Zx6mg2 zlX0Af8WVRjf-8wr%fWRonk$z=H4uTtHo;DCXI(1f5EO2y6Ru^p?SWr{0v!yGhqRXh^Z(7t(GVMv|^UFA*hBI-j>oKmAFcrZ=VtJ7D< zuYq+)F^%02jECf3=Ip2kz2aBNoIDe4w#gy>nDfV4r)(SaIY2;RfN_Z*4e7LH%YUi; zFm>!pNRo4@<(NkM5|VJs7v-teAtD7N05gnnRl&sU>>!W{chDzRT~X*kJXt`PIhpGL{4#ttyu}R$?M3W0iC8OqI`g zlO zb4GF=zR(mrBbnR8z8ek3Cfg@7$XR~T54v*(21)w{U50*bDZg1O7}z)KdGHN$p>O8t zvFWXu=o=dGBzguR826(&o~H6tfX*aP8lf-4M+=yz&_S)f0eoZ|$UM8fm=&od?^Ud~ zqRpWJgawm!`3Pns!pR#zZjM?~%(PuKH_M_f%VN25i?KblQAf!jm4hi<>|vSXtvkX5RIC)Yb$zXq zn+yup*qq&wLLDegn*uPDba@NCE?CUcuq@$B_gLRFqV5?c#)<-IqGBUOtOu(Eg>o>m zYk#k+-D4}}0(RH0E6DYBO^@7cu*z*SW&#xZ9bvj1#=jQR|JbbL^7B z6uazMkM#pP;_PBJOof+_p>1~X8A+JQH_;8?NhrX4_YjH#F`rZ!pb*GdHN!8+wYM}Y z3n&fbgM?w)jsOo7RAs?7iLp$W#WY%PG~H|g*Y`usaz&b0fcL4!>9X3ZcrBHo*O zifQ)TT#@=f>59WOWn-loCDN45XZA_xidx>}gqzek13)CFz8Zr!p~HH3&Im(}5WNh; z4S}7x)O>V=G1=eC3kH-lm5YdO6Qm_${gGphe12GajFrEJ)#X^-Bi3rNu`+-7XU!+L zo0MbKz0ckM%rZwLvim7+7y$(HvD(MA`(z6(f!m=9y0r1@yJ`+>mmkUJoFZ1tjPBN za!c#zjVwfw-biB&(Q3IPEIyes+wnlUN79e@@e%)3a}^P;(~z zpAxBuwhgnCfdL!p$D2vh*&RJG1L`=tW1d#^TXYpmmdWd~(j?gpP${~|I-W1g-brdL zDJ}P=PO7Lo~oEBE~Uo!66~T_Aw~yDShfTL6zV~ z_;yCxDUrW|sTp}AL82ccnTyZz3gMLg4GR?!|KjYywz^|3dc{K@f~sLfEA}aYt$B=>=roS6mIxO#@>~i0SM=v@K);09dcPh}!~5 zRfH?bg%XLw(H=u7iOeI#qe}7-#4c3b=3`at{40ur|p@iwut#6j` zR|HXkwP+0~f>}3M(`y!EN1`Vohk=Hn6ZUHDo~ZyFC~nc{!um1IFguLm0n5w-)PS2% z$S}qTh4@;ZmFmb*?{*#K7wfhg;%rYIFf3HUzS=QNm|J2P*m^=3)^H5_RGh?)VNgMo z%tv4tV$ffhYDcpI*LS+tb_764q;a7#67AGmj(Um=A_qaa5Wcf-O(G|-x{w#zYp{CT zQn9ZHLr@N+%|NvE)lxx3S$iT&rEM#*`<^&%$-t&14sqkrlZU9KW-#z|UZovtn+xQ@ zAB44nw03ve7OOPt9hZk;F%^ocLW<9ByPAF^IhvOPruZT`#W3vFY9ONxJe3ibdclsq z(@di;D!{%TML{2pHck0fHtv2_GL5%On^%^EjQdMmF2KXjw&?QhSlDw{ zD%sGXnB62Ry2uuQ=~VP4 zPtS<4+iD8@XS4FnDfwbg0sjk0rDj`tk*8RLFYy#^%r8?4+&DA2Tr3ZYtH)fPtR^K) zcPKCiVDu(!ALrPF@|YiJng~l(aeM7}aBGkP8$j}*HZCXv%b%HkvcJsJ+oXvz{*&=D z;pZ&tzh{~NrSMC=-!y`&th|V*u*x>ekaCa^^lfFI!N@GcU>hySSr^ZvK?415Og7V- zy0xdGx0?AyWDFz!i+-R*V9P+Di1=YL3FWbmQ&$P5h~dFc)Y%%_(jv1LbjMCSI>bgK z9?LR7Q~QK!r!a(Y#?5#xQH^}DSbvsz^_usylC2S9=m zdnGROXMOfJiD{)g2l2rNVeeSF1O|Mkyc6ixa1r1ZMCV{}RG^d)y5S>;8G$*C+UjKz z9CG+Gp-OA7Hsh>TmPVkV4QDWB;Pbx4GLaqwJnfsJhr&H+#f%OzSo5xBKLxlutJMj1 zm3e2iff-0(miRR!aPp-Zq^;myq}52JJaDxIYALwEmL5g3I)t|rQhWAj8~rcJTI*t` zYLzR0)#~ZkjcIklT4n8W5lq=?@cp7ae82eXzF*#*3&9BQj*k4A8&b1);u=E-&<0;G zx@FkF_R4M9Ol03`gj^=0_Fi6gUqv@vMthc1^o#(v#VX#Jy>~c|c9Z ztbW$OQ!)1Fs?C+W5y52T*x>ytwM7P>`{3tH)hr;74Bnbta`1>l8oVu(a`3Q)sv}36 zg(C;=F8VfjD%IeDfeqfKhr4PcxR#GRwvji`e1RSsd82A@@sTrYLt?VZh>7N0rcbt} zGTzFwB%Jq!z-Wz#Lb7T(+n~n`N5V|E8^aTQJ#Sz=Gw>sph7@@$%IW$-mX2?@kf(im zSg$%lNk5KQuh0x=ldad0WvUCoQV|t8foy~Rl)k7FVv+$hor{Q+sS}V#a}E=6Ag|?+ z(yK)~(W#I!72yh<2+9<^UDW2 z?1sE=By8H;*{uPG7o5VOia9Gk|kYH&}A_kJY9++qKn6pQ;hA*|<86??*sdgg>_f8dYHIPS#@-_Jo zMIB+Vhko^NBmURo|00mFNfLS<;-LS&sbWeu2pIkg9hel*q5IiHs0jw_V)Mrw&nNq1 zm!Uy6vu?(QchrNRZA=GZDC8Z_Mh;ypySI3@ogY845_s%lm9S4i({QQw!BZZ`c#!La`9bof$h@W2-0HzS8Qi$CO%1-?7(fgT8bT{4z82O4suQ@nuZ3sZW)n5 z>pd>+@jC4M#sVc(k1bDxU*D?p3yNSncF`Eo0C73dmJ?6{bsLbF?hI&FBru(*NX}m^ zq&t5#nZhy*GJ5cT?dZEV#-Jh1z@cQejka7Nu4-_^N05GqV5uG+f|13Bq4UCVXrB6v zaHBDD7F^U+6t9F1X+dY#m+ebaAP5M8MfV$tA75qP;F3jZgJ0;N zVs#J_9iT=72%y;5;3^LU!MN6H?davtp;bG6X|2Lc5PCwZ4jm{#Z9K<(JZLiILd;W=%rgwc_XMm zv)q*!U>cs-FSY>19bXvEb~q@Nd)aw^tmhL#vakX;LXN81TwubbW}uo~B+vc9!ooqa)va&t z|L|M?@J(O4+HHwFrh4gCif_L$R)?by(TCUNrOdDV-}_F_}{;yKle z_4Z=EeDRFx1xTqy;4g}MU+;~v^asQU$dM=X8fC2ejQ6BMk{|-hdlqWV3ITAUV zi)>@6O*Sd;Q93fyW*BC=YOT~vE*SPUMRA}%ee{w>Lv4@6R>e&CYk45n&GP_N=3OBP z{!eH}Q9NMo0gm5?+(SSf<-+f_UOImBMn)d++w>cf+08kiS={k81Mrv%(nH{mgg99OTmYot&egIfb=mRsnT4ok4^dfT*3p5 z-xeO6Cp(V^H3trA zSh%9>)@ihvEGwvq9>;Md$^HsMgPW~@XW(9}z2H76_5wLV26w|v z)IB%WsV=-kbpnN6@#nfX4rmMqPyK4}OYFMw>k170;yp4;r&K;&6vX2(u< zr}x;7{afK-$5Buu-gV9tJYOF|YPfUU`Ym@a02u-luFmMH=1<>L{k2a*ATc#rDtyjcP-OE7qibs#yapB}Imt)WdCHu>|`Lga!Bknx`j;aK= z5d4o3tg?sGAPh?3q4-FHIQtQVNr5LZS0D`E=1TZJ6V8fBg8as$qpQJZ0Tmk6x!)K| z&RByLk_Fei_^3#Plh#;FpTZ{LE&*@{ZS2$T4~=)}Wq?{* zWXU4q)K}a{o#%o0pf{Thap{oqAhYTLN&*IIgjaY@3dvX>MgowbmVE`LeJvsW0FB;@ zek;)o_{kdMF77+5$)IxBmU%g*n-dxnrGhs125o9YwrCXYI@6N74|A4WsIx&5jHO7k zJ$~u!9EG}7rQAD`aO&~@Nbv4$dt?Em1G9Z(KW--iAULqWB@{$x9Un2uG^czCh$#`C zw}Spf+w9rX+vVHKEIpNOFVY=bb=o@3^^K{Mi-Y%o9jtXAO+XXyz@xzA;&ntbXnKPe zszKrJpd}1W?Z8T^rj3*dZ#1#&Ejbw~N-RfVCW$_+Dv!}=-rXqbAqmjX85dB;ON5t{ z#A-#*A=5COc2%I4Yem2wuo!(F)=z(MLdB)oo7F1mY=sZVzaI6soIYctf1DeqGE@ap zc@UYV94^CWW!q;q>Ju3&;0t4Amr50QEB{N46$ShvW0h$Spn)cwUNQ>EuQ?#XgA1Qi z`PVOv9>pFxE?0PA4!(kx;f3#w%-g8M$RUCR4J(vJOpZ#h#+ovki2+)soE$=HNsaixs6P$0!qfT?T?egT1A%OL zv?f{m9zK!N-FuQzORkY5K{*hh98OhBA|Fxczz9}An)@LNQ#Dhu8Lwea{OuV}ZEzSlBx}D;t zM_ zEDxL*!O1*lG$ss4z7R>JCIC1=WsJ&O-V13~61n|Mg8W(hI1^qfT^FyP9e2)-_o*br zVIO_kq~sdn9Bxe4GY-y`@fVXRCoV0TtLjDuw|Y%*aM8hhNSPj+#Jg zlnA;;OO@cNmpz4UU`=vmpF4ZK$pL5O2_^T z!<|vR#@W7UP)Or(lCB|uPgTdugm zJ?5pYG1qzAv)Cic3V&=gm8PI%`rHWzUZ`Ojb41MO-Y`MgWENI8{FdCmV zc4X2NI2Sq_L&BtPqc!z8g~21pAtQ=pEHbIMkZq~?Mjbm&o(N5yPeL!PfrMEq5uv91 zZfd(C%WC(}V`eCR4ZziI+Zn`nx}aY%C-R*Z_xN-KHPZ<|Y9T2~?V)&` z1bDM3u?q)EH4Rdr4<^qbR@L&nt&snP6nf^NG(~Q{{8APy zskJ$U)`lPJ^<#bbu|YpJ=!d*f2c3MG6thLtXMl0uMVk*9?LOaKJmu2Vb&ZdyUh(qc zw?aQTdK$J0%WNmhP`HKwK|vY=;SOwe^Tlt7)ho4j z!Fo=_6bEoG!4Gmrv3`5OYR9+biYxLS0sXMcn9^MVt?=d2Nixq6@s+?ZMtnn-(1CrF z5nsl-RR_fS+-wC!NeS_HwIL4`l1I@Vj?6rCIQ2E! zlC+)|@xQ&?SJMnBs7gq2`A0~tU`m)PL~=pj4&jT?OH%0DjpJik>1GtXE42|olD^A> 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

16N#vmoL7GxazgV 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%0DXnxpcQ(yM8Vp#(E#TeVk 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%0DTgonS-5=*vDWjKOI-807e1&mh`u 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�}b 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 literal 0 HcmV?d00001 diff --git a/scripts/generate-admin-module.mjs b/scripts/generate-admin-module.mjs new file mode 100644 index 0000000..21a7dc8 --- /dev/null +++ b/scripts/generate-admin-module.mjs @@ -0,0 +1,595 @@ +import { existsSync } from "node:fs"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; +import path from "node:path"; +import { spawnSync } from "node:child_process"; +import { fileURLToPath } from "node:url"; + +const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const args = process.argv.slice(2); +const configArg = args.find((arg) => !arg.startsWith("--")); +const dryRun = args.includes("--dry-run"); +const skipApiGenerate = args.includes("--skip-api-generate"); + +if (!configArg) { + console.error("Usage: pnpm gen:module [--dry-run] [--skip-api-generate]"); + process.exit(1); +} + +const configPath = path.resolve(process.cwd(), configArg); +const rawConfig = JSON.parse(await readFile(configPath, "utf8")); +const moduleConfig = normalizeConfig(rawConfig); +const plannedWrites = []; + +queueFileWrites(moduleConfig); +await updatePermissions(moduleConfig); +await updateRouteConfig(moduleConfig); +await updateOpenApiContract(moduleConfig); + +if (dryRun) { + console.log(`Dry run: ${plannedWrites.length} file changes planned.`); + for (const write of plannedWrites) { + console.log(`${write.exists ? "Update" : "Create"} ${path.relative(rootDir, write.filePath)}`); + } + process.exit(0); +} + +for (const write of plannedWrites) { + await mkdir(path.dirname(write.filePath), { recursive: true }); + if (!write.overwrite && existsSync(write.filePath)) { + console.log(`Skipped existing ${path.relative(rootDir, write.filePath)}`); + continue; + } + await writeFile(write.filePath, write.content); + console.log(`${write.exists ? "Updated" : "Created"} ${path.relative(rootDir, write.filePath)}`); +} + +if (!skipApiGenerate) { + run("pnpm", ["exec", "openapi-typescript", "contracts/admin-openapi.json", "-o", "src/shared/api/generated/schema.d.ts"]); + run(process.execPath, ["scripts/generate-api-client.mjs"]); +} + +console.log(`Module ${moduleConfig.name} generated. Run pnpm check:contracts after backend routes are implemented.`); + +function normalizeConfig(config) { + const name = toKebabCase(required(config.name, "name")); + const camelName = toCamelCase(name); + const pascalName = toPascalCase(name); + const singularName = toPascalCase(config.singularName || singularize(name)); + const singularCamel = lowerFirst(singularName); + const label = required(config.label, "label"); + const routePath = config.path || `/${name}`; + const menuCode = config.menuCode || name; + const permissionPrefix = config.permissionPrefix || singularize(name); + const pageKey = config.pageKey || name; + const menuKey = config.menuKey || toCamelCase(menuCode); + const permissions = normalizePermissions(config.permissions || config.actions || defaultActions(), { + label, + permissionPrefix + }); + const viewPermission = permissions.find((permission) => permission.action === "view") || permissions[0]; + + if (!viewPermission) { + throw new Error("At least one permission is required"); + } + + const apiRoutes = normalizeApiRoutes(config.apiRoutes || defaultApiRoutes(routePath, viewPermission.code), { + name, + permissions, + routePath, + singularName + }); + + return { + apiRoutes, + camelName, + icon: config.icon || "menu", + label, + menuCode, + menuKey, + name, + pageKey, + parentMenuCode: config.parentMenuCode || "", + pascalName, + permissionPrefix, + permissions, + routePath, + singularCamel, + singularName, + sort: Number(config.sort || 100), + viewPermission + }; +} + +function queueFileWrites(config) { + const featureDir = path.join(rootDir, "src/features", config.name); + const files = new Map([ + ["api.ts", renderApi(config)], + ["routes.js", renderRoutes(config)], + ["permissions.js", renderFeaturePermissions(config)], + ["schema.ts", renderSchema(config)], + [`hooks/use${config.pascalName}Page.js`, renderPageHook(config)], + [`pages/${config.pascalName}Page.jsx`, renderPage(config)], + [`${config.name}.module.css`, renderCss()], + ["components/.gitkeep", ""] + ]); + + for (const [relativePath, content] of files) { + const filePath = path.join(featureDir, relativePath); + planWrite(filePath, content, { overwrite: false }); + } + + const generatedDir = path.join(rootDir, "generated/admin-modules", config.name); + planWrite(path.join(generatedDir, "backend-seed-snippet.go.txt"), renderBackendSeedSnippet(config), { overwrite: true }); + planWrite(path.join(generatedDir, "backend-routes-snippet.go.txt"), renderBackendRoutesSnippet(config), { overwrite: true }); + planWrite(path.join(generatedDir, "permissions.md"), renderPermissionChecklist(config), { overwrite: true }); +} + +async function updatePermissions(config) { + const filePath = path.join(rootDir, "src/app/permissions.ts"); + let source = await readFile(filePath, "utf8"); + const permissionEntries = config.permissions.map((permission) => ({ + key: permission.key, + value: permission.code + })); + const menuEntry = { key: config.menuKey, value: config.menuCode }; + + source = insertObjectEntries(source, "PERMISSIONS", permissionEntries); + source = insertObjectEntries(source, "MENU_CODES", [menuEntry]); + planWrite(filePath, source, { overwrite: true }); +} + +async function updateRouteConfig(config) { + const filePath = path.join(rootDir, "src/app/router/routeConfig.ts"); + let source = await readFile(filePath, "utf8"); + const importLine = `import { ${config.camelName}Routes } from "@/features/${config.name}/routes.js";`; + const routeSpread = ` ...${config.camelName}Routes,`; + + if (!source.includes(importLine)) { + source = source.replace(/(import type \{ MenuCode \} from "@\/app\/permissions";)/, `${importLine}\n$1`); + } + + if (!source.includes(routeSpread)) { + const routesStart = source.indexOf("export const adminRoutes: AdminRoute[] = ["); + const routesEnd = source.indexOf("\n];", routesStart); + if (routesStart < 0 || routesEnd < 0) { + throw new Error("Cannot find adminRoutes block in routeConfig.ts"); + } + const beforeRoutesEnd = source.slice(0, routesEnd); + const afterRoutesEnd = source.slice(routesEnd); + const separator = beforeRoutesEnd.trimEnd().endsWith(",") ? "\n" : ",\n"; + source = `${beforeRoutesEnd}${separator}${routeSpread}${afterRoutesEnd}`; + } + + planWrite(filePath, source, { overwrite: true }); +} + +async function updateOpenApiContract(config) { + const filePath = path.join(rootDir, "contracts/admin-openapi.json"); + const contract = JSON.parse(await readFile(filePath, "utf8")); + contract.paths = contract.paths || {}; + + for (const route of config.apiRoutes) { + const openApiPath = toOpenApiPath(route.path); + contract.paths[openApiPath] = contract.paths[openApiPath] || {}; + contract.paths[openApiPath][route.method.toLowerCase()] = { + operationId: route.operationId, + ...(route.permission + ? { + "x-permission": route.permission, + "x-permissions": [route.permission] + } + : {}), + ...(pathParams(openApiPath).length + ? { + parameters: pathParams(openApiPath).map((name) => + name === "id" + ? { "$ref": "#/components/parameters/Id" } + : { in: "path", name, required: true, schema: { type: "integer" } } + ) + } + : {}), + responses: { "200": { "$ref": "#/components/responses/EmptyResponse" } } + }; + } + + contract.paths = sortObject(contract.paths, (pathItem) => sortObject(pathItem, undefined, ["get", "post", "put", "patch", "delete"])); + planWrite(filePath, `${JSON.stringify(contract, null, 2)}\n`, { overwrite: true }); +} + +function renderApi(config) { + const imports = new Set(["apiRequest"]); + const typeImports = new Set(); + const functions = config.apiRoutes.map((route) => renderApiFunction(route, config, typeImports)); + const needsEndpointObject = config.apiRoutes.some((route) => route.method !== "GET" || route.raw); + const endpointImports = needsEndpointObject ? "API_ENDPOINTS, API_OPERATIONS, apiEndpointPath" : "API_OPERATIONS, apiEndpointPath"; + + return `import { ${[...imports].join(", ")} } from "@/shared/api/request"; +import { ${endpointImports} } from "@/shared/api/generated/endpoints"; +${typeImports.size ? `import type { ${[...typeImports].sort().join(", ")} } from "@/shared/api/types";\n` : ""} +type ${config.singularName}Record = Record; + +${functions.join("\n\n")} +`; +} + +function renderApiFunction(route, config, typeImports) { + const operationKey = `API_OPERATIONS.${route.operationId}`; + const endpointLine = ` const endpoint = API_ENDPOINTS.${route.operationId};`; + + if (route.raw) { + typeImports.add("PageQuery"); + return `export function ${route.operationId}(query?: PageQuery): Promise { + return apiRequest(apiEndpointPath(${operationKey}), { query, raw: true }); +}`; + } + + if (route.kind === "list") { + typeImports.add("ApiPage"); + typeImports.add("PageQuery"); + return `export function ${route.operationId}(query?: PageQuery): Promise> { + return apiRequest>(apiEndpointPath(${operationKey}), { query }); +}`; + } + + if (route.hasId) { + typeImports.add("EntityId"); + } + + if (route.method === "DELETE") { + return `export function ${route.operationId}(id: EntityId): Promise { +${endpointLine} + return apiRequest(apiEndpointPath(${operationKey}, { id }), { method: endpoint.method }); +}`; + } + + if (route.method === "GET") { + const params = route.hasId ? "id: EntityId" : ""; + const pathParams = route.hasId ? ", { id }" : ""; + return `export function ${route.operationId}(${params}): Promise<${config.singularName}Record> { + return apiRequest<${config.singularName}Record>(apiEndpointPath(${operationKey}${pathParams})); +}`; + } + + const params = route.hasId ? "id: EntityId, payload: Record" : "payload: Record"; + const pathParams = route.hasId ? ", { id }" : ""; + return `export function ${route.operationId}(${params}): Promise<${config.singularName}Record> { +${endpointLine} + return apiRequest<${config.singularName}Record, Record>(apiEndpointPath(${operationKey}${pathParams}), { + body: payload, + method: endpoint.method + }); +}`; +} + +function renderRoutes(config) { + return `import { MENU_CODES, PERMISSIONS } from "@/app/permissions"; + +export const ${config.camelName}Routes = [ + { + label: ${JSON.stringify(config.label)}, + loader: () => import("./pages/${config.pascalName}Page.jsx").then((module) => module.${config.pascalName}Page), + menuCode: MENU_CODES.${config.menuKey}, + pageKey: ${JSON.stringify(config.pageKey)}, + path: ${JSON.stringify(config.routePath)}, + permission: PERMISSIONS.${config.viewPermission.key} + } +]; +`; +} + +function renderFeaturePermissions(config) { + const entries = config.permissions + .map((permission) => ` ${abilityName(permission.action)}: can(PERMISSIONS.${permission.key})`) + .join(",\n"); + + return `import { useAuth } from "@/app/auth/AuthProvider.jsx"; +import { PERMISSIONS } from "@/app/permissions"; + +export function use${config.singularName}Abilities() { + const { can } = useAuth(); + + return { +${entries} + }; +} +`; +} + +function renderSchema(config) { + return `import { z } from "zod"; + +export const ${config.singularCamel}FormSchema = z.record(z.string(), z.unknown()); + +export type ${config.singularName}Form = z.infer; +`; +} + +function renderPageHook(config) { + const listRoute = config.apiRoutes.find((route) => route.kind === "list") || config.apiRoutes[0]; + return `import { useCallback } from "react"; +import { useAdminQuery } from "@/shared/hooks/useAdminQuery.js"; +import { ${listRoute.operationId} } from "@/features/${config.name}/api"; +import { use${config.singularName}Abilities } from "@/features/${config.name}/permissions.js"; + +const emptyData = { items: [], total: 0, page: 1, pageSize: 10 }; + +export function use${config.pascalName}Page() { + const abilities = use${config.singularName}Abilities(); + const queryFn = useCallback(() => ${listRoute.operationId}({ page: 1, page_size: 10 }), []); + const { data, error, loading, reload } = useAdminQuery(queryFn, { + errorMessage: "加载失败", + initialData: emptyData, + queryKey: [${JSON.stringify(config.name)}] + }); + + return { abilities, data, error, loading, reload }; +} +`; +} + +function renderPage(config) { + return `import { DataState } from "@/shared/ui/DataState.jsx"; +import { use${config.pascalName}Page } from "@/features/${config.name}/hooks/use${config.pascalName}Page.js"; +import styles from "@/features/${config.name}/${config.name}.module.css"; + +export function ${config.pascalName}Page() { + const page = use${config.pascalName}Page(); + + return ( + +
+ + ); +} +`; +} + +function renderCss() { + return `.root { + min-height: 100%; +} +`; +} + +function renderBackendSeedSnippet(config) { + const permissions = config.permissions + .map((permission) => `\t{Name: "${permission.name}", Code: "${permission.code}", Kind: "${permission.kind}"},`) + .join("\n"); + return `// Add to hyapp-server/server/admin/internal/repository/seed.go defaultPermissions. +${permissions} + +// Add to defaultMenus. +{Title: "${config.label}", Code: "${config.menuCode}", Path: "${config.routePath}", Icon: "${config.icon}", PermissionCode: "${config.viewPermission.code}", Sort: ${config.sort}, Visible: true}, +`; +} + +function renderBackendRoutesSnippet(config) { + const lines = config.apiRoutes.map((route) => { + const permissionGuard = route.permission + ? `, middleware.RequirePermission("${route.permission}")` + : ""; + return `protected.${route.method}("${toGinPath(route.path)}"${permissionGuard}, h.${route.handler})`; + }); + return `// Add to the module RegisterRoutes function. +${lines.join("\n")} +`; +} + +function renderPermissionChecklist(config) { + const rows = config.permissions + .map((permission) => `| \`${permission.code}\` | ${permission.kind} | ${permission.name} |`) + .join("\n"); + const routeRows = config.apiRoutes + .map((route) => `| ${route.method} | \`${route.path}\` | \`${route.permission || "登录态"}\` | ${route.handler} |`) + .join("\n"); + return `# ${config.label} + +## 权限 + +| Code | Kind | Name | +| --- | --- | --- | +${rows} + +## 后端路由 + +| Method | Path | Permission | Handler | +| --- | --- | --- | --- | +${routeRows} +`; +} + +function normalizePermissions(value, context) { + return value.map((item) => { + const permission = typeof item === "string" ? { action: item } : item; + const action = permission.action || permission.key; + const code = permission.code || `${context.permissionPrefix}:${action}`; + const key = permission.permissionKey || toPermissionKey(code); + return { + action, + code, + key, + kind: permission.kind || (action === "view" ? "menu" : "button"), + name: permission.name || `${context.label}${actionLabel(action)}` + }; + }); +} + +function normalizeApiRoutes(value, context) { + const permissionByAction = new Map(context.permissions.map((permission) => [permission.action, permission.code])); + return value.map((route) => { + const method = (route.method || "GET").toUpperCase(); + const routePath = route.path || context.routePath; + const permission = route.permission || permissionByAction.get(route.action || "view") || ""; + const operationId = route.operationId || inferOperationId({ method, routePath, route, context }); + return { + handler: route.handler || toPascalCase(operationId), + hasId: routePath.includes("{id}") || routePath.includes(":id"), + kind: route.kind || inferRouteKind(method, routePath), + method, + operationId, + path: toOpenApiPath(routePath), + permission, + raw: Boolean(route.raw) || routePath.includes("export") + }; + }); +} + +function defaultActions() { + return ["view", "create", "update", "delete", "export"]; +} + +function defaultApiRoutes(routePath, viewPermission) { + return [{ action: "view", kind: "list", method: "GET", path: routePath, permission: viewPermission }]; +} + +function inferOperationId({ method, routePath, route, context }) { + if (route.action === "export" || routePath.includes("export")) { + return `export${context.pascalName}`; + } + if (route.kind === "list" || (method === "GET" && !routePath.includes("{id}") && !routePath.includes(":id"))) { + return `list${context.pascalName}`; + } + if (method === "GET") { + return `get${context.singularName}`; + } + if (method === "POST") { + return routePath.includes(":id") || routePath.includes("{id}") ? `${route.action || "run"}${context.singularName}` : `create${context.singularName}`; + } + if (method === "PATCH" || method === "PUT") { + return `update${context.singularName}`; + } + if (method === "DELETE") { + return `delete${context.singularName}`; + } + return `${method.toLowerCase()}${context.singularName}`; +} + +function inferRouteKind(method, routePath) { + if (method === "GET" && !routePath.includes("{id}") && !routePath.includes(":id")) { + return "list"; + } + return "item"; +} + +function insertObjectEntries(source, objectName, entries) { + const objectStart = source.indexOf(`export const ${objectName} = {`); + if (objectStart < 0) { + throw new Error(`Cannot find ${objectName} in permissions.ts`); + } + const objectEnd = source.indexOf("\n} as const;", objectStart); + if (objectEnd < 0) { + throw new Error(`Cannot find ${objectName} closing in permissions.ts`); + } + + const before = source.slice(0, objectEnd); + const after = source.slice(objectEnd); + const existingBlock = source.slice(objectStart, objectEnd); + const additions = entries + .filter((entry) => !existingBlock.includes(`${entry.key}:`) && !existingBlock.includes(`"${entry.value}"`)) + .map((entry) => ` ${entry.key}: "${entry.value}"`); + + if (!additions.length) { + return source; + } + + const separator = before.trimEnd().endsWith(",") ? "\n" : ",\n"; + return `${before}${separator}${additions.join(",\n")}${after}`; +} + +function planWrite(filePath, content, options) { + plannedWrites.push({ + content, + exists: existsSync(filePath), + filePath, + overwrite: options.overwrite + }); +} + +function run(command, commandArgs) { + const result = spawnSync(command, commandArgs, { cwd: rootDir, stdio: "inherit" }); + if (result.status !== 0) { + process.exit(result.status || 1); + } +} + +function required(value, name) { + if (!value) { + throw new Error(`Missing required config field: ${name}`); + } + return value; +} + +function pathParams(routePath) { + return [...routePath.matchAll(/\{([^}]+)\}/g)].map((match) => match[1]); +} + +function toOpenApiPath(routePath) { + return routePath.replace(/:([A-Za-z0-9_]+)/g, "{$1}"); +} + +function toGinPath(routePath) { + return routePath.replace(/\{([^}]+)\}/g, ":$1"); +} + +function toKebabCase(value) { + return value + .replace(/([a-z0-9])([A-Z])/g, "$1-$2") + .replace(/[^a-zA-Z0-9]+/g, "-") + .replace(/^-|-$/g, "") + .toLowerCase(); +} + +function toCamelCase(value) { + return value.replace(/-([a-z0-9])/g, (_, letter) => letter.toUpperCase()); +} + +function toPascalCase(value) { + const camel = toCamelCase(toKebabCase(value)); + return camel.charAt(0).toUpperCase() + camel.slice(1); +} + +function lowerFirst(value) { + return value.charAt(0).toLowerCase() + value.slice(1); +} + +function singularize(value) { + return value.endsWith("ies") ? `${value.slice(0, -3)}y` : value.endsWith("s") ? value.slice(0, -1) : value; +} + +function toPermissionKey(code) { + return toCamelCase(code.replace(/:/g, "-")); +} + +function abilityName(action) { + return `can${toPascalCase(action)}`; +} + +function actionLabel(action) { + const labels = { + audit: "审核", + config: "配置", + create: "创建", + delete: "删除", + export: "导出", + import: "导入", + status: "状态变更", + update: "更新", + view: "查看" + }; + return labels[action] || action; +} + +function sortObject(object, mapValue = (value) => value, preferredOrder = []) { + return Object.fromEntries( + Object.entries(object) + .sort(([left], [right]) => { + const leftIndex = preferredOrder.indexOf(left); + const rightIndex = preferredOrder.indexOf(right); + if (leftIndex >= 0 || rightIndex >= 0) { + return (leftIndex < 0 ? Number.MAX_SAFE_INTEGER : leftIndex) - (rightIndex < 0 ? Number.MAX_SAFE_INTEGER : rightIndex); + } + return left.localeCompare(right); + }) + .map(([key, value]) => [key, mapValue(value)]) + ); +} diff --git a/scripts/generate-api-client.mjs b/scripts/generate-api-client.mjs new file mode 100644 index 0000000..8f2883f --- /dev/null +++ b/scripts/generate-api-client.mjs @@ -0,0 +1,102 @@ +import { mkdir, readFile, writeFile } from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const contractPath = path.join(rootDir, "contracts/admin-openapi.json"); +const outputPath = path.join(rootDir, "src/shared/api/generated/endpoints.ts"); +const methods = ["get", "post", "put", "patch", "delete"]; + +const contract = JSON.parse(await readFile(contractPath, "utf8")); +const serverUrl = contract.servers?.[0]?.url || ""; +const apiPathPrefix = serverUrl.startsWith("/api") ? serverUrl.slice("/api".length) : serverUrl; +const endpoints = []; + +for (const [openapiPath, pathItem] of Object.entries(contract.paths || {})) { + for (const method of methods) { + const operation = pathItem[method]; + if (!operation?.operationId) { + continue; + } + const permissions = Array.isArray(operation["x-permissions"]) + ? operation["x-permissions"] + : operation["x-permission"] + ? [operation["x-permission"]] + : []; + + endpoints.push({ + method: method.toUpperCase(), + operationId: operation.operationId, + path: `${apiPathPrefix}${openapiPath}`, + permission: operation["x-permission"] || permissions[0], + permissions + }); + } +} + +endpoints.sort((left, right) => left.operationId.localeCompare(right.operationId)); + +const operationLines = endpoints.map((endpoint) => { + return ` ${endpoint.operationId}: ${JSON.stringify(endpoint.operationId)}`; +}); + +const endpointLines = endpoints.map((endpoint) => { + const permission = endpoint.permission ? `,\n permission: ${JSON.stringify(endpoint.permission)}` : ""; + const permissions = endpoint.permissions.length ? `,\n permissions: ${JSON.stringify(endpoint.permissions)}` : ""; + return ` ${endpoint.operationId}: { + method: ${JSON.stringify(endpoint.method)}, + operationId: API_OPERATIONS.${endpoint.operationId}, + path: ${JSON.stringify(endpoint.path)}${permission}${permissions} + }`; +}); + +const output = `/* eslint-disable */ +// Generated by scripts/generate-api-client.mjs. Do not edit manually. +import type { PermissionCode } from "@/app/permissions"; + +export type ApiHttpMethod = "DELETE" | "GET" | "PATCH" | "POST" | "PUT"; + +export interface ApiEndpoint { + method: ApiHttpMethod; + operationId: string; + path: string; + permission?: PermissionCode; + permissions?: PermissionCode[]; +} + +export const API_OPERATIONS = { +${operationLines.join(",\n")} +} as const; + +export type ApiOperationId = (typeof API_OPERATIONS)[keyof typeof API_OPERATIONS]; + +export const API_ENDPOINTS: Record = { +${endpointLines.join(",\n")} +}; + +export const API_OPERATION_IDS = Object.values(API_OPERATIONS); + +export const API_PERMISSION_CODES = Array.from( + new Set( + Object.values(API_ENDPOINTS) + .flatMap((endpoint) => endpoint.permissions || (endpoint.permission ? [endpoint.permission] : [])) + .filter((permission): permission is PermissionCode => Boolean(permission)) + ) +); + +export type ApiPathParams = Record; + +export function apiEndpointPath(operationId: ApiOperationId, params: ApiPathParams = {}) { + let endpointPath = API_ENDPOINTS[operationId].path; + + for (const [key, value] of Object.entries(params)) { + endpointPath = endpointPath.replace(\`{\${key}}\`, encodeURIComponent(String(value))); + } + + return endpointPath; +} +`; + +await mkdir(path.dirname(outputPath), { recursive: true }); +await writeFile(outputPath, output); +console.log(`Generated ${path.relative(rootDir, outputPath)} from ${path.relative(rootDir, contractPath)}`); diff --git a/scripts/scaffold-feature.mjs b/scripts/scaffold-feature.mjs new file mode 100644 index 0000000..784714a --- /dev/null +++ b/scripts/scaffold-feature.mjs @@ -0,0 +1,147 @@ +import { mkdir, writeFile } from "node:fs/promises"; +import { existsSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const args = process.argv.slice(2); +const featureName = args.find((arg) => !arg.startsWith("--")); + +if (!featureName) { + console.error("Usage: pnpm scaffold:feature --label=模块名称 --path=/path --menu-code=menu-code --permission=module:view"); + process.exit(1); +} + +const options = Object.fromEntries( + args + .filter((arg) => arg.startsWith("--")) + .map((arg) => { + const [key, ...valueParts] = arg.slice(2).split("="); + return [key, valueParts.join("=")]; + }) +); + +const kebabName = toKebabCase(featureName); +const camelName = toCamelCase(kebabName); +const pascalName = toPascalCase(kebabName); +const routePath = options.path || `/${kebabName}`; +const label = options.label || pascalName; +const menuCode = options["menu-code"] || kebabName; +const permission = options.permission || `${kebabName}:view`; +const featureDir = path.join(rootDir, "src/features", kebabName); + +const files = new Map([ + [ + "api.ts", + `import { apiRequest } from "@/shared/api/request"; + +export function list${pascalName}(query?: Record) { + return apiRequest("${routePath}", { query }); +} +` + ], + [ + "routes.js", + `export const ${camelName}Routes = [ + { + label: ${JSON.stringify(label)}, + loader: () => import("./pages/${pascalName}Page.jsx").then((module) => module.${pascalName}Page), + menuCode: ${JSON.stringify(menuCode)}, + pageKey: ${JSON.stringify(kebabName)}, + path: ${JSON.stringify(routePath)}, + permission: ${JSON.stringify(permission)} + } +]; +` + ], + [ + "permissions.js", + `import { useAuth } from "@/app/auth/AuthProvider.jsx"; + +export function use${pascalName}Abilities() { + const { can } = useAuth(); + + return { + canView: can(${JSON.stringify(permission)}) + }; +} +` + ], + [ + `hooks/use${pascalName}Page.js`, + `import { useCallback } from "react"; +import { useAdminQuery } from "@/shared/hooks/useAdminQuery.js"; +import { list${pascalName} } from "@/features/${kebabName}/api"; +import { use${pascalName}Abilities } from "@/features/${kebabName}/permissions.js"; + +const emptyData = { items: [], total: 0, page: 1, pageSize: 10 }; + +export function use${pascalName}Page() { + const abilities = use${pascalName}Abilities(); + const queryFn = useCallback(() => list${pascalName}({ page: 1, page_size: 10 }), []); + const { data, error, loading, reload } = useAdminQuery(queryFn, { + errorMessage: "加载失败", + initialData: emptyData, + queryKey: [${JSON.stringify(kebabName)}] + }); + + return { abilities, data, error, loading, reload }; +} +` + ], + [ + `pages/${pascalName}Page.jsx`, + `import { DataState } from "@/shared/ui/DataState.jsx"; +import { use${pascalName}Page } from "@/features/${kebabName}/hooks/use${pascalName}Page.js"; +import styles from "@/features/${kebabName}/${kebabName}.module.css"; + +export function ${pascalName}Page() { + const page = use${pascalName}Page(); + + return ( + +
+ + ); +} +` + ], + [ + `${kebabName}.module.css`, + `.root { + min-height: 100%; +} +` + ], + ["components/.gitkeep", ""] +]); + +await mkdir(featureDir, { recursive: true }); + +for (const [relativePath, content] of files) { + const absolutePath = path.join(featureDir, relativePath); + await mkdir(path.dirname(absolutePath), { recursive: true }); + if (existsSync(absolutePath)) { + console.log(`Skipped existing ${path.relative(rootDir, absolutePath)}`); + continue; + } + await writeFile(absolutePath, content); + console.log(`Created ${path.relative(rootDir, absolutePath)}`); +} + +function toKebabCase(value) { + return value + .replace(/([a-z0-9])([A-Z])/g, "$1-$2") + .replace(/[^a-zA-Z0-9]+/g, "-") + .replace(/^-|-$/g, "") + .toLowerCase(); +} + +function toCamelCase(value) { + return value.replace(/-([a-z0-9])/g, (_, letter) => letter.toUpperCase()); +} + +function toPascalCase(value) { + const camel = toCamelCase(value); + return camel.charAt(0).toUpperCase() + camel.slice(1); +} diff --git a/scripts/sync-openapi-from-backend.mjs b/scripts/sync-openapi-from-backend.mjs new file mode 100644 index 0000000..a1f418c --- /dev/null +++ b/scripts/sync-openapi-from-backend.mjs @@ -0,0 +1,151 @@ +import { readdir, readFile, writeFile } from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const defaultBackendDir = path.resolve(rootDir, "../hyapp-server/server/admin"); +const backendDir = process.env.HYAPP_ADMIN_SERVER_DIR + ? path.resolve(process.env.HYAPP_ADMIN_SERVER_DIR) + : defaultBackendDir; +const contractPath = path.join(rootDir, "contracts/admin-openapi.json"); +const methods = ["get", "post", "put", "patch", "delete"]; + +const contract = JSON.parse(await readFile(contractPath, "utf8")); +const routes = await readBackendRoutes(path.join(backendDir, "internal/modules")); + +for (const route of routes) { + contract.paths[route.path] = contract.paths[route.path] || {}; + const operation = contract.paths[route.path][route.method] || {}; + operation.operationId = route.operationId; + operation.responses = operation.responses || { "200": { "$ref": "#/components/responses/EmptyResponse" } }; + + const pathParams = [...route.path.matchAll(/\{([^}]+)\}/g)].map((match) => match[1]); + if (pathParams.length && !operation.parameters) { + operation.parameters = pathParams.map((name) => + name === "id" + ? { "$ref": "#/components/parameters/Id" } + : { + in: "path", + name, + required: true, + schema: { type: "integer" } + } + ); + } + + if (route.permissions.length) { + operation["x-permission"] = operation["x-permission"] || route.permissions[0]; + operation["x-permissions"] = route.permissions; + } + + contract.paths[route.path][route.method] = operation; +} + +contract.paths = sortObject(contract.paths, (pathItem) => sortObject(pathItem, undefined, methods)); + +await writeFile(contractPath, `${JSON.stringify(contract, null, 2)}\n`); +console.log(`Synced ${routes.length} backend routes into ${path.relative(rootDir, contractPath)}`); + +async function readBackendRoutes(modulesDir) { + const modules = await readdir(modulesDir, { withFileTypes: true }); + const routes = []; + + for (const moduleEntry of modules.filter((entry) => entry.isDirectory())) { + const moduleDir = path.join(modulesDir, moduleEntry.name); + const files = await readdir(moduleDir, { withFileTypes: true }); + for (const fileEntry of files.filter((entry) => entry.isFile() && entry.name.endsWith(".go"))) { + const source = await readFile(path.join(moduleDir, fileEntry.name), "utf8"); + if (!source.includes("RegisterRoutes")) { + continue; + } + routes.push(...parseRoutes(source, moduleEntry.name)); + } + } + + return routes.sort((left, right) => `${left.path}:${left.method}`.localeCompare(`${right.path}:${right.method}`)); +} + +function parseRoutes(source, moduleName) { + const groupPrefixes = new Map([ + ["api", ""], + ["protected", ""] + ]); + const routes = []; + + for (const line of source.split("\n")) { + const groupMatch = line.match(/^\s*(\w+)\s*:=\s*(\w+)\.Group\("([^"]+)"\)/); + if (groupMatch) { + const [, groupName, parentName, prefix] = groupMatch; + groupPrefixes.set(groupName, `${groupPrefixes.get(parentName) || ""}${prefix}`); + continue; + } + + const routeMatch = line.match(/^\s*(\w+)\.(GET|POST|PUT|PATCH|DELETE)\("([^"]+)"(?:,\s*(.*))?\)/); + if (!routeMatch) { + continue; + } + + const [, groupName, method, routePath, args = ""] = routeMatch; + const handlerMatch = args.match(/h\.([A-Za-z0-9_]+)/g); + const handlerName = handlerMatch?.at(-1)?.replace("h.", "") || `${method}${routePath}`; + const permissions = readPermissions(args); + + routes.push({ + method: method.toLowerCase(), + operationId: operationIdForHandler(moduleName, handlerName, routePath, method), + path: toOpenApiPath(`${groupPrefixes.get(groupName) || ""}${routePath}`), + permissions + }); + } + + return routes; +} + +function readPermissions(args) { + const anyMatch = args.match(/RequireAnyPermission\(([^)]*)\)/); + if (anyMatch) { + return [...anyMatch[1].matchAll(/"([^"]+)"/g)].map((match) => match[1]); + } + + const match = args.match(/RequirePermission\("([^"]+)"\)/); + return match ? [match[1]] : []; +} + +function toOpenApiPath(routePath) { + return routePath.replace(/:([A-Za-z0-9_]+)/g, "{$1}"); +} + +function lowerFirst(value) { + return value.charAt(0).toLowerCase() + value.slice(1); +} + +function operationIdForHandler(moduleName, handlerName, routePath, method) { + if (moduleName === "appuser") { + return `app${handlerName}`; + } + if (moduleName === "countryregion" && handlerName === "DisableCountry" && method === "DELETE") { + return "deleteCountry"; + } + if (moduleName === "hostorg" && handlerName === "SetBDStatus" && routePath.includes("bd-leaders")) { + return "setBDLeaderStatus"; + } + if (moduleName === "resource" && handlerName === "UpdateResourceGroup" && routePath.includes("/items")) { + return "updateResourceGroupItems"; + } + return lowerFirst(handlerName); +} + +function sortObject(object, mapValue = (value) => value, preferredOrder = []) { + return Object.fromEntries( + Object.entries(object) + .sort(([left], [right]) => { + const leftIndex = preferredOrder.indexOf(left); + const rightIndex = preferredOrder.indexOf(right); + if (leftIndex >= 0 || rightIndex >= 0) { + return (leftIndex < 0 ? Number.MAX_SAFE_INTEGER : leftIndex) - (rightIndex < 0 ? Number.MAX_SAFE_INTEGER : rightIndex); + } + return left.localeCompare(right); + }) + .map(([key, value]) => [key, mapValue(value)]) + ); +} diff --git a/scripts/validate-admin-architecture.ts b/scripts/validate-admin-architecture.ts new file mode 100644 index 0000000..349ade1 --- /dev/null +++ b/scripts/validate-admin-architecture.ts @@ -0,0 +1,129 @@ +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { MENU_CODE_VALUES, PERMISSION_CODES } from "../src/app/permissions"; +import { fallbackNavigation } from "../src/app/navigation/menu"; +import { adminRoutes } from "../src/app/router/routeConfig"; + +type OpenApiOperation = { + operationId?: string; + "x-permission"?: string; + "x-permissions"?: string[]; +}; + +type OpenApiDocument = { + paths?: Record>; +}; + +type NavItem = { + children?: NavItem[]; + code?: string; + path?: string; + permissionCode?: string; +}; + +const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const contract = JSON.parse(readFileSync(path.join(rootDir, "contracts/admin-openapi.json"), "utf8")) as OpenApiDocument; +const errors: string[] = []; +const knownPermissions = new Set(PERMISSION_CODES); +const knownMenuCodes = new Set(MENU_CODE_VALUES); +const routesByPath = new Map(); +const routesByMenuCode = new Map(); +const routePermissions = new Set(); + +for (const route of adminRoutes) { + if (!route.path) { + errors.push(`Route ${route.pageKey} is missing path`); + } + if (!route.menuCode) { + errors.push(`Route ${route.path} is missing menuCode`); + } + if (!route.permission) { + errors.push(`Route ${route.path} is missing permission`); + } + if (route.menuCode && !knownMenuCodes.has(route.menuCode)) { + errors.push(`Route ${route.path} uses unknown menuCode: ${route.menuCode}`); + } + if (route.permission && !knownPermissions.has(route.permission)) { + errors.push(`Route ${route.path} uses unknown permission: ${route.permission}`); + } + if (routesByPath.has(route.path)) { + errors.push(`Duplicate route path: ${route.path}`); + } + if (routesByMenuCode.has(route.menuCode)) { + errors.push(`Duplicate route menuCode: ${route.menuCode}`); + } + + routesByPath.set(route.path, route); + routesByMenuCode.set(route.menuCode, route); + routePermissions.add(route.permission); +} + +for (const item of flattenNavItems(fallbackNavigation as NavItem[])) { + if (item.code && !knownMenuCodes.has(item.code)) { + errors.push(`Fallback menu uses unknown menu code: ${item.code}`); + } + + if (!item.path) { + continue; + } + + const route = item.code ? routesByMenuCode.get(item.code) : undefined; + if (!route) { + errors.push(`Fallback menu ${item.code || item.path} has no matching route`); + continue; + } + if (route.path !== item.path) { + errors.push(`Fallback menu ${item.code} path mismatch: ${item.path} !== ${route.path}`); + } + if (item.permissionCode !== route.permission) { + errors.push(`Fallback menu ${item.code} permission mismatch: ${item.permissionCode} !== ${route.permission}`); + } +} + +const operationIds = new Set(); +const apiPermissions = new Set(); +const httpMethods = new Set(["delete", "get", "patch", "post", "put"]); + +for (const [endpointPath, pathItem] of Object.entries(contract.paths || {})) { + for (const [method, operation] of Object.entries(pathItem)) { + if (!httpMethods.has(method)) { + continue; + } + if (!operation.operationId) { + errors.push(`${method.toUpperCase()} ${endpointPath} is missing operationId`); + } else if (operationIds.has(operation.operationId)) { + errors.push(`Duplicate OpenAPI operationId: ${operation.operationId}`); + } else { + operationIds.add(operation.operationId); + } + + const permissions = operation["x-permissions"]?.length + ? operation["x-permissions"] + : operation["x-permission"] + ? [operation["x-permission"]] + : []; + for (const permission of permissions) { + apiPermissions.add(permission); + if (!knownPermissions.has(permission)) { + errors.push(`${method.toUpperCase()} ${endpointPath} uses unknown x-permission: ${permission}`); + } + } + } +} + +if (errors.length) { + console.error("Admin architecture validation failed:"); + for (const error of errors) { + console.error(`- ${error}`); + } + process.exit(1); +} + +console.log( + `Admin architecture validation passed: ${adminRoutes.length} routes, ${operationIds.size} operations, ${apiPermissions.size} protected permissions.` +); + +function flattenNavItems(items: NavItem[]): NavItem[] { + return items.flatMap((item) => [item, ...flattenNavItems(item.children || [])]); +} diff --git a/src/App.jsx b/src/App.jsx deleted file mode 100644 index 0c39e36..0000000 --- a/src/App.jsx +++ /dev/null @@ -1,155 +0,0 @@ -import { useEffect, useMemo, useState } from "react"; -import { Navigate, Outlet, Route, Routes, useLocation, useNavigate } from "react-router-dom"; -import { getMenus } from "./api/navigation.js"; -import { useAuth } from "./auth/AuthProvider.jsx"; -import { Header } from "./components/layout/Header.jsx"; -import { PageSkeleton } from "./components/layout/PageSkeleton.jsx"; -import { Sidebar } from "./components/layout/Sidebar.jsx"; -import { findNavItemByPath, mapBackendMenus, navItems } from "./constants/navigation.js"; - -const pageModules = { - login: () => import("./pages/LoginPage.jsx").then((module) => module.LoginPage), - overview: () => import("./pages/OverviewPage.jsx").then((module) => module.OverviewPage), - services: () => import("./pages/ServicesPage.jsx").then((module) => module.ServicesPage), - users: () => import("./pages/UserManagementPage.jsx").then((module) => module.UserManagementPage), - roles: () => import("./pages/RoleManagementPage.jsx").then((module) => module.RoleManagementPage), - menus: () => import("./pages/MenuManagementPage.jsx").then((module) => module.MenuManagementPage), - logs: () => import("./pages/LogsPage.jsx").then((module) => module.LogsPage), - notifications: () => import("./pages/NotificationsPage.jsx").then((module) => module.NotificationsPage) -}; - -const pageCache = new Map(); - -function App() { - return ( - - } /> - }> - }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - } /> - - ); -} - -function DeferredPage({ pageKey, ...props }) { - const [Page, setPage] = useState(() => pageCache.get(pageKey) || null); - - useEffect(() => { - let mounted = true; - - if (pageCache.has(pageKey)) { - setPage(() => pageCache.get(pageKey)); - return undefined; - } - - setPage(null); - pageModules[pageKey]().then((component) => { - pageCache.set(pageKey, component); - if (mounted) { - setPage(() => component); - } - }); - - return () => { - mounted = false; - }; - }, [pageKey]); - - if (!Page) { - return ; - } - - return ; -} - -function RequireAuth() { - const { loading, user } = useAuth(); - const location = useLocation(); - - if (loading) { - return ; - } - - if (!user) { - return ; - } - - return ; -} - -function RequirePermission({ children, code }) { - const { can } = useAuth(); - - if (!can(code)) { - return ; - } - - return children; -} - -function AdminLayout() { - const [sidebarCollapsed, setSidebarCollapsed] = useState(false); - const [refreshTick, setRefreshTick] = useState(0); - const [backendMenus, setBackendMenus] = useState([]); - const location = useLocation(); - const navigate = useNavigate(); - const activeNav = useMemo(() => findNavItemByPath(location.pathname, backendMenus.length ? backendMenus : navItems), [backendMenus, location.pathname]); - - useEffect(() => { - let mounted = true; - - async function loadMenus() { - try { - const data = await getMenus(); - if (mounted) { - setBackendMenus(mapBackendMenus(data)); - } - } catch { - if (mounted) { - setBackendMenus(navItems); - } - } - } - - loadMenus(); - return () => { - mounted = false; - }; - }, []); - - const menus = backendMenus.length ? backendMenus : navItems; - - return ( -
-
setRefreshTick((current) => current + 1)} - onToggleSidebar={() => setSidebarCollapsed((current) => !current)} - /> - -
- - -
-
- -
-
-
-
- ); -} - -export default App; diff --git a/src/App.test.jsx b/src/App.test.jsx index 5a536dc..63c19e5 100644 --- a/src/App.test.jsx +++ b/src/App.test.jsx @@ -1,27 +1,45 @@ import { render, screen } from "@testing-library/react"; -import { expect, test } from "vitest"; -import { ThemeProvider } from "@mui/material/styles"; +import { afterEach, beforeEach, expect, test, vi } from "vitest"; import { MemoryRouter } from "react-router-dom"; -import { AuthProvider } from "./auth/AuthProvider.jsx"; -import { ConfirmProvider } from "./components/base/ConfirmProvider.jsx"; -import { ToastProvider } from "./components/base/ToastProvider.jsx"; -import { theme } from "./theme.js"; -import App from "./App.jsx"; +import App from "@/app/App.jsx"; +import { AppProviders } from "@/app/providers.jsx"; +import { adminRoutes } from "@/app/router/routeConfig"; + +beforeEach(() => { + window.localStorage.clear(); + vi.stubGlobal( + "fetch", + vi.fn(async () => new Response(JSON.stringify({ code: 401, message: "unauthorized" }), { status: 401 })) + ); +}); + +afterEach(() => { + vi.unstubAllGlobals(); +}); test("renders login route", async () => { - render( - - - - - - - - - - - - ); + renderWithRoute("/login"); expect(await screen.findByText("HYApp 管理平台")).toBeInTheDocument(); }); + +test("redirects protected route to login when unauthenticated", async () => { + renderWithRoute("/system/users"); + + expect(await screen.findByText("HYApp 管理平台")).toBeInTheDocument(); +}); + +test("admin routes declare menu code and permission", () => { + expect(adminRoutes.length).toBeGreaterThan(0); + expect(adminRoutes.every((route) => route.menuCode && route.permission)).toBe(true); +}); + +function renderWithRoute(route) { + return render( + + + + + + ); +} diff --git a/src/api/auth.js b/src/api/auth.js deleted file mode 100644 index 95c3037..0000000 --- a/src/api/auth.js +++ /dev/null @@ -1,21 +0,0 @@ -import { apiRequest } from "./request.js"; - -export function login(payload) { - return apiRequest("/v1/auth/login", { body: payload, skipAuth: true }); -} - -export function logout() { - return apiRequest("/v1/auth/logout", { method: "POST", skipAuth: true }); -} - -export function refreshSession() { - return apiRequest("/v1/auth/refresh", { method: "POST", skipAuth: true }); -} - -export function getMe() { - return apiRequest("/v1/auth/me"); -} - -export function changePassword(payload) { - return apiRequest("/v1/auth/change-password", { body: payload }); -} diff --git a/src/api/dashboard.js b/src/api/dashboard.js deleted file mode 100644 index be78e80..0000000 --- a/src/api/dashboard.js +++ /dev/null @@ -1,5 +0,0 @@ -import { apiRequest } from "./request.js"; - -export function getDashboardOverview() { - return apiRequest("/v1/dashboard/overview"); -} diff --git a/src/api/logs.js b/src/api/logs.js deleted file mode 100644 index 1fbaffd..0000000 --- a/src/api/logs.js +++ /dev/null @@ -1,9 +0,0 @@ -import { apiRequest } from "./request.js"; - -export function listLoginLogs(query) { - return apiRequest("/v1/logs/login", { query }); -} - -export function listOperationLogs(query) { - return apiRequest("/v1/logs/operations", { query }); -} diff --git a/src/api/navigation.js b/src/api/navigation.js deleted file mode 100644 index c16c885..0000000 --- a/src/api/navigation.js +++ /dev/null @@ -1,5 +0,0 @@ -import { apiRequest } from "./request.js"; - -export function getMenus() { - return apiRequest("/v1/navigation/menus"); -} diff --git a/src/api/notifications.js b/src/api/notifications.js deleted file mode 100644 index 12d9e86..0000000 --- a/src/api/notifications.js +++ /dev/null @@ -1,9 +0,0 @@ -import { apiRequest } from "./request.js"; - -export function listNotifications() { - return apiRequest("/v1/notifications"); -} - -export function markNotificationRead(id) { - return apiRequest(`/v1/notifications/${id}/read`, { method: "PATCH", body: {} }); -} diff --git a/src/api/request.js b/src/api/request.js deleted file mode 100644 index 3bc1a7b..0000000 --- a/src/api/request.js +++ /dev/null @@ -1,105 +0,0 @@ -import { API_BASE_URL } from "../config/env.js"; - -const TOKEN_KEY = "hyapp-admin.access-token"; - -let accessToken = window.localStorage.getItem(TOKEN_KEY) || ""; -let refreshHandler = null; -let unauthorizedHandler = null; - -export function getAccessToken() { - return accessToken; -} - -export function setAccessToken(token) { - accessToken = token || ""; - if (accessToken) { - window.localStorage.setItem(TOKEN_KEY, accessToken); - } else { - window.localStorage.removeItem(TOKEN_KEY); - } -} - -export function setRefreshHandler(handler) { - refreshHandler = handler; -} - -export function setUnauthorizedHandler(handler) { - unauthorizedHandler = handler; -} - -export async function apiRequest(path, options = {}) { - const { - body, - headers = {}, - method = body ? "POST" : "GET", - query, - retry = true, - raw = false, - skipAuth = false - } = options; - const url = buildURL(path, query); - const requestHeaders = { ...headers }; - - if (body !== undefined && !(body instanceof FormData)) { - requestHeaders["Content-Type"] = "application/json"; - } - if (accessToken && !skipAuth) { - requestHeaders.Authorization = `Bearer ${accessToken}`; - } - - const response = await fetch(url, { - method, - headers: requestHeaders, - body: body === undefined ? undefined : body instanceof FormData ? body : JSON.stringify(body), - credentials: "include" - }); - - if (response.status === 401 && retry && refreshHandler && !skipAuth) { - const refreshed = await refreshHandler(); - if (refreshed) { - return apiRequest(path, { ...options, retry: false }); - } - } - - if (response.status === 401 && unauthorizedHandler && !skipAuth) { - unauthorizedHandler(); - } - - if (raw) { - if (!response.ok) { - throw new Error(response.statusText || "请求失败"); - } - return response; - } - - const payload = await readJSON(response); - if (!response.ok || payload.code !== 0) { - throw new Error(payload.message || response.statusText || "请求失败"); - } - - return payload.data; -} - -function buildURL(path, query) { - const url = new URL(`${API_BASE_URL}${path}`, window.location.origin); - if (query) { - Object.entries(query).forEach(([key, value]) => { - if (value !== undefined && value !== null && value !== "") { - url.searchParams.set(key, value); - } - }); - } - return url.toString(); -} - -async function readJSON(response) { - const text = await response.text(); - if (!text) { - return {}; - } - try { - return JSON.parse(text); - } catch { - return { code: response.ok ? 0 : response.status, message: text, data: text }; - } -} diff --git a/src/api/roles.js b/src/api/roles.js deleted file mode 100644 index c0178b4..0000000 --- a/src/api/roles.js +++ /dev/null @@ -1,25 +0,0 @@ -import { apiRequest } from "./request.js"; - -export function listRoles() { - return apiRequest("/v1/roles"); -} - -export function createRole(payload) { - return apiRequest("/v1/roles", { body: payload }); -} - -export function updateRole(id, payload) { - return apiRequest(`/v1/roles/${id}`, { method: "PATCH", body: payload }); -} - -export function deleteRole(id) { - return apiRequest(`/v1/roles/${id}`, { method: "DELETE" }); -} - -export function replaceRolePermissions(id, permissionIds) { - return apiRequest(`/v1/roles/${id}/permissions`, { method: "PUT", body: { permissionIds } }); -} - -export function listPermissions() { - return apiRequest("/v1/permissions"); -} diff --git a/src/api/search.js b/src/api/search.js deleted file mode 100644 index f1dd1cc..0000000 --- a/src/api/search.js +++ /dev/null @@ -1,5 +0,0 @@ -import { apiRequest } from "./request.js"; - -export function searchGlobal(keyword) { - return apiRequest("/v1/search", { query: { keyword } }); -} diff --git a/src/api/services.js b/src/api/services.js deleted file mode 100644 index 8f3675f..0000000 --- a/src/api/services.js +++ /dev/null @@ -1,25 +0,0 @@ -import { apiRequest } from "./request.js"; - -export function listServices(query) { - return apiRequest("/v1/services", { query }); -} - -export function getService(id) { - return apiRequest(`/v1/services/${id}`); -} - -export function createService(payload) { - return apiRequest("/v1/services", { body: payload }); -} - -export function updateService(id, payload) { - return apiRequest(`/v1/services/${id}`, { method: "PATCH", body: payload }); -} - -export function updateServiceStatus(id, status) { - return apiRequest(`/v1/services/${id}/status`, { method: "PATCH", body: { status } }); -} - -export function restartService(id) { - return apiRequest(`/v1/services/${id}/restart`, { body: {} }); -} diff --git a/src/api/users.js b/src/api/users.js deleted file mode 100644 index 17309cf..0000000 --- a/src/api/users.js +++ /dev/null @@ -1,33 +0,0 @@ -import { apiRequest } from "./request.js"; - -export function listUsers(query) { - return apiRequest("/v1/users", { query }); -} - -export function getUser(id) { - return apiRequest(`/v1/users/${id}`); -} - -export function createUser(payload) { - return apiRequest("/v1/users", { body: payload }); -} - -export function updateUser(id, payload) { - return apiRequest(`/v1/users/${id}`, { method: "PATCH", body: payload }); -} - -export function updateUserStatus(id, status) { - return apiRequest(`/v1/users/${id}/status`, { method: "PATCH", body: { status } }); -} - -export function resetUserPassword(id, password) { - return apiRequest(`/v1/users/${id}/reset-password`, { body: { password } }); -} - -export function batchUpdateUserStatus(ids, status) { - return apiRequest("/v1/users/batch/status", { body: { ids, status } }); -} - -export function exportUsers(query) { - return apiRequest("/v1/users/export", { query, raw: true }); -} diff --git a/src/app/App.jsx b/src/app/App.jsx new file mode 100644 index 0000000..32dc0e4 --- /dev/null +++ b/src/app/App.jsx @@ -0,0 +1,34 @@ +import { Navigate, Route, Routes } from "react-router-dom"; +import { AdminLayout } from "@/app/layout/AdminLayout.jsx"; +import { DeferredPage } from "@/app/router/DeferredPage.jsx"; +import { RequireAuth, RequirePermission } from "@/app/router/guards.jsx"; +import { adminRoutes, defaultAdminPath, publicRoutes } from "@/app/router/routeConfig"; + +function App() { + return ( + + {publicRoutes.map((route) => ( + } /> + ))} + }> + }> + } /> + {adminRoutes.map((route) => ( + + + + } + /> + ))} + + + } /> + + ); +} + +export default App; diff --git a/src/app/ErrorBoundary.jsx b/src/app/ErrorBoundary.jsx new file mode 100644 index 0000000..733e02b --- /dev/null +++ b/src/app/ErrorBoundary.jsx @@ -0,0 +1,41 @@ +import { Component } from "react"; +import styles from "./ErrorBoundary.module.css"; + +export class ErrorBoundary extends Component { + state = { error: null }; + + static getDerivedStateFromError(error) { + return { error }; + } + + reset = () => { + this.setState({ error: null }); + }; + + reload = () => { + window.location.reload(); + }; + + render() { + if (!this.state.error) { + return this.props.children; + } + + return ( +
+
+

页面加载失败

+

{this.state.error.message || "请刷新后重试"}

+
+ + +
+
+
+ ); + } +} diff --git a/src/app/ErrorBoundary.module.css b/src/app/ErrorBoundary.module.css new file mode 100644 index 0000000..e27c1b2 --- /dev/null +++ b/src/app/ErrorBoundary.module.css @@ -0,0 +1,62 @@ +.root { + align-items: center; + background: var(--bg-page); + display: flex; + min-height: 100vh; + padding: var(--space-6); +} + +.panel { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius-card); + box-shadow: var(--shadow-panel); + margin: 0 auto; + max-width: 420px; + padding: var(--space-6); + width: 100%; +} + +.title { + color: var(--text-primary); + font-size: var(--admin-font-size); + line-height: var(--admin-line-height); + margin: 0; +} + +.message { + color: var(--text-secondary); + line-height: var(--admin-line-height); + margin: var(--space-3) 0 0; + overflow-wrap: anywhere; +} + +.actions { + display: flex; + gap: var(--space-3); + justify-content: flex-end; + margin-top: var(--space-5); +} + +.primaryButton, +.secondaryButton { + align-items: center; + border: 1px solid transparent; + border-radius: var(--radius-control); + cursor: pointer; + display: inline-flex; + font: inherit; + min-height: var(--control-height); + padding: 0 var(--space-4); +} + +.primaryButton { + background: var(--primary); + color: var(--active-contrast); +} + +.secondaryButton { + background: var(--bg-card); + border-color: var(--border); + color: var(--text-primary); +} diff --git a/src/app/app-scope/AppScopeProvider.jsx b/src/app/app-scope/AppScopeProvider.jsx new file mode 100644 index 0000000..0aac587 --- /dev/null +++ b/src/app/app-scope/AppScopeProvider.jsx @@ -0,0 +1,71 @@ +import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react"; +import { useQueryClient } from "@tanstack/react-query"; +import { useAuth } from "@/app/auth/AuthProvider.jsx"; +import { listAdminApps } from "@/features/app-registry/api"; +import { getSelectedAppCode, setSelectedAppCode as setRequestAppCode } from "@/shared/api/request"; +import { useAdminQuery } from "@/shared/hooks/useAdminQuery.js"; + +const AppScopeContext = createContext(null); +const fallbackAppCode = "lalu"; +const emptyAppState = { items: [], total: 0 }; + +export function AppScopeProvider({ children }) { + const { user } = useAuth(); + const queryClient = useQueryClient(); + const [appCode, setAppCodeState] = useState(() => getSelectedAppCode() || fallbackAppCode); + const { data = emptyAppState, error, loading } = useAdminQuery(() => listAdminApps(), { + enabled: Boolean(user), + errorMessage: "加载 App 列表失败", + initialData: emptyAppState, + queryKey: ["admin-apps"], + staleTime: 5 * 60 * 1000 + }); + const apps = useMemo(() => data?.items || [], [data?.items]); + + const applyAppCode = useCallback( + async (nextAppCode, options = {}) => { + const normalized = normalizeAppCode(nextAppCode) || fallbackAppCode; + setRequestAppCode(normalized); + setAppCodeState(normalized); + if (!options.skipRefresh) { + await queryClient.invalidateQueries({ refetchType: "active" }); + } + }, + [queryClient] + ); + + useEffect(() => { + if (!apps.length) { + return; + } + const selectedExists = apps.some((item) => normalizeAppCode(item.appCode) === normalizeAppCode(appCode)); + if (!selectedExists) { + void applyAppCode(apps[0].appCode, { skipRefresh: false }); + } + }, [appCode, applyAppCode, apps]); + + const value = useMemo( + () => ({ + appCode, + apps, + error, + loading, + setAppCode: applyAppCode + }), + [appCode, applyAppCode, apps, error, loading] + ); + + return {children}; +} + +export function useAppScope() { + const context = useContext(AppScopeContext); + if (!context) { + throw new Error("useAppScope must be used inside AppScopeProvider"); + } + return context; +} + +function normalizeAppCode(value) { + return String(value || "").trim().toLowerCase(); +} diff --git a/src/auth/AuthProvider.jsx b/src/app/auth/AuthProvider.jsx similarity index 78% rename from src/auth/AuthProvider.jsx rename to src/app/auth/AuthProvider.jsx index 8ef3a71..120bde9 100644 --- a/src/auth/AuthProvider.jsx +++ b/src/app/auth/AuthProvider.jsx @@ -1,6 +1,6 @@ import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react"; -import * as authApi from "../api/auth.js"; -import { getAccessToken, setAccessToken, setRefreshHandler, setUnauthorizedHandler } from "../api/request.js"; +import * as authApi from "@/features/auth/api"; +import { getAccessToken, setAccessToken, setRefreshHandler, setUnauthorizedHandler } from "@/shared/api/request"; const AuthContext = createContext(null); @@ -49,9 +49,22 @@ export function AuthProvider({ children }) { async function bootstrap() { try { if (getAccessToken()) { - const session = await authApi.getMe(); - if (mounted) { - applySession(session); + try { + const session = await authApi.refreshSession(); + if (mounted) { + applySession(session); + } + return; + } catch { + // Keep the existing access token as a fallback when the refresh cookie is unavailable. + } + try { + const session = await authApi.getMe(); + if (mounted) { + applySession(session); + } + } catch { + clearSession(); } return; } @@ -67,7 +80,7 @@ export function AuthProvider({ children }) { return () => { mounted = false; }; - }, [applySession, refresh]); + }, [applySession, clearSession, refresh]); const login = useCallback( async (payload) => { diff --git a/src/app/layout/AdminLayout.jsx b/src/app/layout/AdminLayout.jsx new file mode 100644 index 0000000..b055c55 --- /dev/null +++ b/src/app/layout/AdminLayout.jsx @@ -0,0 +1,58 @@ +import { useMemo, useState } from "react"; +import { Outlet, useLocation, useNavigate } from "react-router-dom"; +import { useAuth } from "@/app/auth/AuthProvider.jsx"; +import { getMenus } from "@/features/menus/api"; +import { useAdminQuery } from "@/shared/hooks/useAdminQuery.js"; +import { useRefreshSignal } from "@/shared/hooks/useRefreshSignal.js"; +import { + fallbackNavigation, + filterNavigationByPermission, + findNavItemByPath, + mapBackendMenus, + mergeNavigationItems +} from "@/app/navigation/menu.js"; +import { Header } from "./Header.jsx"; +import { Sidebar } from "./Sidebar.jsx"; + +export function AdminLayout() { + const [sidebarCollapsed, setSidebarCollapsed] = useState(false); + const location = useLocation(); + const navigate = useNavigate(); + const { can } = useAuth(); + const { refresh } = useRefreshSignal(); + const { data: backendMenus } = useAdminQuery(() => getMenus(), { + errorMessage: "加载菜单失败", + initialData: [], + queryKey: ["navigation", "menus"], + staleTime: 5 * 60 * 1000 + }); + const menus = useMemo(() => { + const fallbackMenus = filterNavigationByPermission(fallbackNavigation, can); + if (!backendMenus?.length) { + return fallbackMenus; + } + return mergeNavigationItems(mapBackendMenus(backendMenus), fallbackMenus); + }, [backendMenus, can]); + const activeNav = useMemo(() => findNavItemByPath(location.pathname, menus), [location.pathname, menus]); + + return ( +
+
setSidebarCollapsed((current) => !current)} + /> + +
+ + +
+
+ +
+
+
+
+ ); +} diff --git a/src/components/layout/Header.jsx b/src/app/layout/Header.jsx similarity index 66% rename from src/components/layout/Header.jsx rename to src/app/layout/Header.jsx index 3b773f1..7b747d5 100644 --- a/src/components/layout/Header.jsx +++ b/src/app/layout/Header.jsx @@ -9,25 +9,37 @@ import Refresh from "@mui/icons-material/Refresh"; import Search from "@mui/icons-material/Search"; import MuiMenu from "@mui/material/Menu"; import MenuItem from "@mui/material/MenuItem"; +import TextField from "@mui/material/TextField"; import { lazy, Suspense, useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; -import { listNotifications } from "../../api/notifications.js"; -import { useAuth } from "../../auth/AuthProvider.jsx"; -import { IconButton } from "../base/IconButton.jsx"; -import { useToast } from "../base/ToastProvider.jsx"; +import { useAppScope } from "@/app/app-scope/AppScopeProvider.jsx"; +import { useAuth } from "@/app/auth/AuthProvider.jsx"; +import { PERMISSIONS } from "@/app/permissions"; +import { listNotifications } from "@/features/notifications/api"; +import { useAdminQuery } from "@/shared/hooks/useAdminQuery.js"; +import { IconButton } from "@/shared/ui/IconButton.jsx"; +import { useToast } from "@/shared/ui/ToastProvider.jsx"; -const SearchDialog = lazy(() => import("../search/SearchDialog.jsx").then((module) => ({ default: module.SearchDialog }))); +const SearchDialog = lazy(() => import("@/features/search/components/SearchDialog.jsx").then((module) => ({ default: module.SearchDialog }))); export function Header({ activeLabel, isSidebarCollapsed, onRefresh, onToggleSidebar }) { const [searchOpen, setSearchOpen] = useState(false); const [searchMounted, setSearchMounted] = useState(false); const [userMenuAnchor, setUserMenuAnchor] = useState(null); - const [unread, setUnread] = useState(0); const navigate = useNavigate(); - const { logout, user } = useAuth(); + const { can, logout, user } = useAuth(); + const appScope = useAppScope(); const { showToast } = useToast(); const ToggleIcon = isSidebarCollapsed ? MenuOpen : Menu; const userMenuOpen = Boolean(userMenuAnchor); + const canViewNotifications = can(PERMISSIONS.notificationView); + const { data: notificationState } = useAdminQuery(() => listNotifications(), { + enabled: canViewNotifications, + initialData: { items: [], unread: 0 }, + queryKey: ["notifications", "unread"], + refetchInterval: 30000 + }); + const unread = notificationState?.unread || 0; useEffect(() => { if (searchOpen) { @@ -64,30 +76,6 @@ export function Header({ activeLabel, isSidebarCollapsed, onRefresh, onToggleSid navigate("/notifications"); }; - useEffect(() => { - let mounted = true; - - async function loadUnread() { - try { - const data = await listNotifications(); - if (mounted) { - setUnread(data.unread || 0); - } - } catch { - if (mounted) { - setUnread(0); - } - } - } - - loadUnread(); - const timer = window.setInterval(loadUnread, 30000); - return () => { - mounted = false; - window.clearInterval(timer); - }; - }, []); - const refresh = () => { onRefresh(); showToast("已刷新当前页面", "success"); @@ -114,16 +102,36 @@ export function Header({ activeLabel, isSidebarCollapsed, onRefresh, onToggleSid
+ appScope.setAppCode(event.target.value)} + > + {appScope.apps.length ? ( + appScope.apps.map((app) => ( + + {app.appName || app.appCode} + + )) + ) : ( + {appScope.appCode} + )} + - - - + {canViewNotifications ? ( + + + + ) : null} - ))} -
- - - - ); -} - -function PanelHead({ service }) { - return ( -
- -
- ); -} - -function PanelTitle({ service }) { - return ( -
-

{service.name}

- -
- ); -} - -function StaticTabs() { - return ( -
- {detailTabs.map((tab, index) => ( - - ))} -
- ); -} - -function DetailBody({ activeTab, service }) { - return ( -
- {activeTab === "日志" ? ( -
    - {service.logs.map((log) => ( -
  • {log}
  • - ))} -
- ) : ( -
- - - - - - - - -
- )} -
- ); -} - -function DetailRow({ label, value }) { - return ( -
- {label} - {value} -
- ); -} - -function DrawerActions({ canOperate, restartService, service, setServiceStatus }) { - return ( -
- - - -
- ); -} diff --git a/src/components/service/ResourceCell.jsx b/src/components/service/ResourceCell.jsx deleted file mode 100644 index 96fa73b..0000000 --- a/src/components/service/ResourceCell.jsx +++ /dev/null @@ -1,12 +0,0 @@ -export function ResourceCell({ memory = false, value }) { - return ( -
-
- {value}% -
-
- -
-
- ); -} diff --git a/src/components/service/RowActions.jsx b/src/components/service/RowActions.jsx deleted file mode 100644 index be609ba..0000000 --- a/src/components/service/RowActions.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import MoreHoriz from "@mui/icons-material/MoreHoriz"; -import PlayArrow from "@mui/icons-material/PlayArrow"; -import Refresh from "@mui/icons-material/Refresh"; -import Stop from "@mui/icons-material/Stop"; -import { IconButton } from "../base/IconButton.jsx"; - -export function RowActions({ canOperate = true, restartService, service, setServiceStatus }) { - const isRunning = service.status === "running"; - const isStopped = service.status === "stopped"; - - return ( -
event.stopPropagation()}> - setServiceStatus(service.id, "running")} - label="启动" - tone="success" - > - - - restartService(service.id)} - label="重启" - > - - - setServiceStatus(service.id, "stopped")} - label="停止" - tone="danger" - > - - - - - -
- ); -} diff --git a/src/components/service/ServerRail.jsx b/src/components/service/ServerRail.jsx deleted file mode 100644 index 2d21a51..0000000 --- a/src/components/service/ServerRail.jsx +++ /dev/null @@ -1,27 +0,0 @@ -import { Card } from "../base/Card.jsx"; -import { StatusBadge } from "./StatusBadge.jsx"; - -export function ServerRail({ openDetail, services }) { - return ( - -
-
服务器列表
- -
-
- {services.slice(0, 4).map((service) => ( - - ))} -
-
- ); -} diff --git a/src/components/service/ServiceTable.jsx b/src/components/service/ServiceTable.jsx deleted file mode 100644 index c2655a2..0000000 --- a/src/components/service/ServiceTable.jsx +++ /dev/null @@ -1,55 +0,0 @@ -import Storage from "@mui/icons-material/Storage"; -import { Card } from "../base/Card.jsx"; -import { ResourceCell } from "./ResourceCell.jsx"; -import { RowActions } from "./RowActions.jsx"; -import { StatusBadge } from "./StatusBadge.jsx"; - -export function ServiceTable({ canOperate = true, openDetail, restartService, services, setServiceStatus, title }) { - return ( - -
-
{title}
-
共 {services.length} 条
-
-
-
-
-
服务名称
-
状态
-
部署
-
CPU
-
内存
-
操作
-
- {services.length ? ( - services.map((service) => ( -
openDetail(service.id)} role="button" tabIndex={0}> -
-
- -
-
-
{service.name}
-
{service.desc}
-
-
-
- -
-
- {service.deploy} -
{service.version}
-
- - - -
- )) - ) : ( -
暂无匹配服务
- )} -
-
-
- ); -} diff --git a/src/components/service/StatusBadge.jsx b/src/components/service/StatusBadge.jsx deleted file mode 100644 index 8c92577..0000000 --- a/src/components/service/StatusBadge.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import Chip from "@mui/material/Chip"; -import { statusMeta } from "../../constants/status.js"; - -export function StatusBadge({ service }) { - const meta = statusMeta[service.status]; - - return ( - } - label={service.statusLabel} - size="small" - variant="outlined" - /> - ); -} diff --git a/src/components/user/UserTable.jsx b/src/components/user/UserTable.jsx deleted file mode 100644 index 91319b7..0000000 --- a/src/components/user/UserTable.jsx +++ /dev/null @@ -1,148 +0,0 @@ -import EditOutlined from "@mui/icons-material/EditOutlined"; -import LockOutlined from "@mui/icons-material/LockOutlined"; -import LockOpenOutlined from "@mui/icons-material/LockOpenOutlined"; -import PasswordOutlined from "@mui/icons-material/PasswordOutlined"; -import Person from "@mui/icons-material/Person"; -import PersonRemoveOutlined from "@mui/icons-material/PersonRemoveOutlined"; -import VerifiedUserOutlined from "@mui/icons-material/VerifiedUserOutlined"; -import Checkbox from "@mui/material/Checkbox"; -import { Card } from "../base/Card.jsx"; -import { IconButton } from "../base/IconButton.jsx"; -import { UserStatusBadge } from "./UserStatusBadge.jsx"; - -export function UserTable({ - canEdit, - canResetPassword, - canStatus, - onEdit, - onResetPassword, - onToggleDisabled, - onToggleLocked, - selectedIds, - setSelectedIds, - total, - users -}) { - const allChecked = users.length > 0 && users.every((user) => selectedIds.includes(user.id)); - - const toggleAll = (checked) => { - if (!checked) { - setSelectedIds([]); - return; - } - setSelectedIds(users.map((user) => user.id)); - }; - - return ( - -
-
用户清单
-
共 {total} 条
-
-
-
-
-
- toggleAll(event.target.checked)} size="small" /> -
-
用户
-
角色
-
团队
-
状态
-
MFA
-
最后登录
-
操作
-
- {users.length ? ( - users.map((user) => ( -
-
- { - setSelectedIds((current) => - event.target.checked ? [...current, user.id] : current.filter((id) => id !== user.id) - ); - }} - size="small" - /> -
-
-
- -
-
-
{user.name}
-
{user.account}
-
-
-
{user.role || "-"}
-
{user.team || "-"}
-
- -
-
{user.mfa}
-
{user.lastLogin}
- -
- )) - ) : ( -
暂无匹配用户
- )} -
-
-
- ); -} - -function UserRowActions({ - canEdit, - canResetPassword, - canStatus, - onEdit, - onResetPassword, - onToggleDisabled, - onToggleLocked, - user -}) { - const isDisabled = user.status === "disabled"; - const isLocked = user.status === "locked"; - - return ( -
- onEdit(user)}> - - - onResetPassword(user)}> - - - onToggleLocked(user)} - tone={isLocked ? "success" : undefined} - > - {isLocked ? : } - - onToggleDisabled(user)} - tone={isDisabled ? "success" : "danger"} - > - {isDisabled ? : } - -
- ); -} diff --git a/src/constants/navigation.js b/src/constants/navigation.js deleted file mode 100644 index f72c2b5..0000000 --- a/src/constants/navigation.js +++ /dev/null @@ -1,105 +0,0 @@ -import DashboardOutlined from "@mui/icons-material/DashboardOutlined"; -import HistoryOutlined from "@mui/icons-material/HistoryOutlined"; -import Inventory2Outlined from "@mui/icons-material/Inventory2Outlined"; -import LoginOutlined from "@mui/icons-material/LoginOutlined"; -import ManageAccountsOutlined from "@mui/icons-material/ManageAccountsOutlined"; -import MenuOpenOutlined from "@mui/icons-material/MenuOpenOutlined"; -import NotificationsNoneOutlined from "@mui/icons-material/NotificationsNoneOutlined"; -import ReceiptLongOutlined from "@mui/icons-material/ReceiptLongOutlined"; -import ShieldOutlined from "@mui/icons-material/ShieldOutlined"; -import SettingsApplicationsOutlined from "@mui/icons-material/SettingsApplicationsOutlined"; - -export const navItems = [ - { id: "overview", code: "overview", label: "总览", path: "/overview", icon: DashboardOutlined }, - { id: "services", code: "services", label: "服务管理", path: "/services", icon: Inventory2Outlined }, - { - id: "system", - code: "system", - label: "系统管理", - icon: SettingsApplicationsOutlined, - children: [ - { id: "system-users", code: "system-users", label: "用户管理", path: "/system/users", icon: ManageAccountsOutlined }, - { id: "system-roles", code: "system-roles", label: "角色配置", path: "/system/roles", icon: ShieldOutlined }, - { id: "system-menus", code: "system-menus", label: "菜单权限", path: "/system/menus", icon: MenuOpenOutlined } - ] - }, - { - id: "logs", - code: "logs", - label: "日志审计", - icon: HistoryOutlined, - children: [ - { id: "logs-login", code: "logs-login", label: "登录日志", path: "/logs/login", icon: LoginOutlined }, - { id: "logs-operations", code: "logs-operations", label: "操作日志", path: "/logs/operations", icon: ReceiptLongOutlined } - ] - }, - { - id: "notifications", - code: "notifications", - label: "通知中心", - path: "/notifications", - icon: NotificationsNoneOutlined - } -]; - -const iconMap = { - dashboard: DashboardOutlined, - history: HistoryOutlined, - inventory: Inventory2Outlined, - login: LoginOutlined, - menu: MenuOpenOutlined, - notifications: NotificationsNoneOutlined, - receipt: ReceiptLongOutlined, - settings: SettingsApplicationsOutlined, - shield: ShieldOutlined, - users: ManageAccountsOutlined -}; - -export function mapBackendMenus(menus = []) { - return menus.map((item) => ({ - id: item.code || String(item.id), - code: item.code, - label: item.label, - path: item.path, - icon: iconMap[item.icon] || MenuOpenOutlined, - permissionCode: item.permissionCode, - children: item.children?.length ? mapBackendMenus(item.children) : undefined - })); -} - -export function findNavItemById(id, items = navItems) { - for (const item of items) { - if (item.id === id) { - return item; - } - - if (item.children?.length) { - const child = findNavItemById(id, item.children); - - if (child) { - return child; - } - } - } - - return null; -} - -export function findNavItemByPath(pathname, items = navItems) { - let fallback = null; - - for (const item of items) { - if (item.path && pathname.startsWith(item.path)) { - fallback = item; - } - - if (item.children?.length) { - const child = findNavItemByPath(pathname, item.children); - if (child) { - return child; - } - } - } - - return fallback; -} diff --git a/src/constants/status.js b/src/constants/status.js deleted file mode 100644 index 3893948..0000000 --- a/src/constants/status.js +++ /dev/null @@ -1,8 +0,0 @@ -export const statusMeta = { - running: { label: "运行中", className: "running" }, - warning: { label: "警告", className: "warning" }, - error: { label: "异常", className: "error" }, - stopped: { label: "已停止", className: "stopped" } -}; - -export const detailTabs = ["基础信息", "配置", "监控", "日志"]; diff --git a/src/data/services.js b/src/data/services.js deleted file mode 100644 index 73b6e24..0000000 --- a/src/data/services.js +++ /dev/null @@ -1,108 +0,0 @@ -export const initialServices = [ - { - id: "svc-gateway", - name: "API Gateway", - desc: "流量入口 / 鉴权 / 限流", - status: "running", - statusLabel: "运行中", - cpu: 36, - memory: 58, - deploy: "prod-a", - version: "v2.8.4", - port: "8080", - replicas: "6/6", - response: "42ms", - updated: "14:22", - trend: [34, 38, 35, 42, 39, 44, 36], - logs: ["14:22 健康检查通过", "14:18 配置热更新完成", "13:55 副本扩容至 6"] - }, - { - id: "svc-auth", - name: "Auth Service", - desc: "账号 / 权限 / Token", - status: "warning", - statusLabel: "警告", - cpu: 68, - memory: 74, - deploy: "prod-a", - version: "v1.14.0", - port: "7001", - replicas: "4/4", - response: "96ms", - updated: "14:19", - trend: [48, 52, 55, 62, 71, 66, 68], - logs: ["14:19 P95 延迟超过阈值", "14:07 慢查询 8 条", "13:42 健康检查通过"] - }, - { - id: "svc-order", - name: "Order Worker", - desc: "订单队列 / 任务分发", - status: "running", - statusLabel: "运行中", - cpu: 52, - memory: 63, - deploy: "prod-b", - version: "v3.2.1", - port: "9100", - replicas: "8/8", - response: "61ms", - updated: "14:21", - trend: [41, 44, 46, 49, 52, 56, 52], - logs: ["14:21 消费速率 2.4k/min", "14:10 队列积压清零", "13:36 副本滚动更新完成"] - }, - { - id: "svc-billing", - name: "Billing Core", - desc: "计费 / 对账 / 回调", - status: "error", - statusLabel: "异常", - cpu: 84, - memory: 81, - deploy: "prod-b", - version: "v2.4.9", - port: "7300", - replicas: "2/4", - response: "318ms", - updated: "14:16", - trend: [56, 64, 73, 78, 82, 89, 84], - logs: ["14:16 2 个副本健康检查失败", "14:12 回调超时率 6.3%", "13:58 自动重试已触发"] - }, - { - id: "svc-cache", - name: "Redis Cluster", - desc: "缓存 / 分布式锁", - status: "running", - statusLabel: "运行中", - cpu: 29, - memory: 47, - deploy: "infra", - version: "v7.2.5", - port: "6379", - replicas: "3/3", - response: "5ms", - updated: "14:23", - trend: [24, 28, 27, 31, 29, 32, 29], - logs: ["14:23 主从延迟 8ms", "14:00 快照完成", "13:30 内存碎片率正常"] - }, - { - id: "svc-search", - name: "Search Indexer", - desc: "索引构建 / 查询同步", - status: "stopped", - statusLabel: "已停止", - cpu: 0, - memory: 12, - deploy: "staging", - version: "v0.9.7", - port: "9200", - replicas: "0/2", - response: "-", - updated: "13:45", - trend: [23, 18, 14, 8, 0, 0, 0], - logs: ["13:45 手动停止", "13:40 索引任务完成", "13:20 灰度环境同步完成"] - } -]; - -export const trafficSeries = [36, 42, 39, 54, 49, 62, 58, 73, 68, 76, 71, 82]; -export const cpuSeries = [42, 44, 48, 52, 50, 57, 61, 58, 64, 69, 66, 72]; -export const alertSeries = [4, 5, 3, 8, 6, 9, 7, 12, 10, 14, 11, 9]; diff --git a/src/features/app-config/api.ts b/src/features/app-config/api.ts new file mode 100644 index 0000000..46d235f --- /dev/null +++ b/src/features/app-config/api.ts @@ -0,0 +1,18 @@ +import { apiRequest } from "@/shared/api/request"; +import { API_ENDPOINTS, API_OPERATIONS, apiEndpointPath } from "@/shared/api/generated/endpoints"; +import type { ApiList, H5LinkConfigDto, H5LinkConfigUpdatePayload } from "@/shared/api/types"; + +export function listH5Links(): Promise> { + const endpoint = API_ENDPOINTS.listH5Links; + return apiRequest>(apiEndpointPath(API_OPERATIONS.listH5Links), { + method: endpoint.method + }); +} + +export function updateH5Links(payload: H5LinkConfigUpdatePayload): Promise> { + const endpoint = API_ENDPOINTS.updateH5Links; + return apiRequest, H5LinkConfigUpdatePayload>(apiEndpointPath(API_OPERATIONS.updateH5Links), { + body: payload, + method: endpoint.method + }); +} diff --git a/src/features/app-config/app-config.module.css b/src/features/app-config/app-config.module.css new file mode 100644 index 0000000..4cf26cb --- /dev/null +++ b/src/features/app-config/app-config.module.css @@ -0,0 +1,7 @@ +.linkText { + overflow: hidden; + max-width: 100%; + color: var(--text-secondary); + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/src/features/app-config/constants.ts b/src/features/app-config/constants.ts new file mode 100644 index 0000000..8fc3a99 --- /dev/null +++ b/src/features/app-config/constants.ts @@ -0,0 +1,9 @@ +export const H5_LINK_KEYS = [ + "host-center", + "bd-center", + "bd-leader-center", + "agency-center", + "invite-user" +] as const; + +export type H5LinkKey = (typeof H5_LINK_KEYS)[number]; diff --git a/src/features/app-config/hooks/useH5ConfigPage.js b/src/features/app-config/hooks/useH5ConfigPage.js new file mode 100644 index 0000000..e9fe7f7 --- /dev/null +++ b/src/features/app-config/hooks/useH5ConfigPage.js @@ -0,0 +1,68 @@ +import { useCallback, useState } from "react"; +import { parseForm } from "@/shared/forms/validation"; +import { useAdminQuery } from "@/shared/hooks/useAdminQuery.js"; +import { useToast } from "@/shared/ui/ToastProvider.jsx"; +import { listH5Links, updateH5Links } from "@/features/app-config/api"; +import { useAppConfigAbilities } from "@/features/app-config/permissions.js"; +import { h5LinkUpdateSchema } from "@/features/app-config/schema"; + +const emptyData = { items: [], total: 0 }; +const emptyForm = () => ({ url: "" }); + +export function useH5ConfigPage() { + const abilities = useAppConfigAbilities(); + const { showToast } = useToast(); + const [editingItem, setEditingItem] = useState(null); + const [form, setForm] = useState(emptyForm); + const [loadingAction, setLoadingAction] = useState(""); + const queryFn = useCallback(() => listH5Links(), []); + const { data = emptyData, error, loading, reload } = useAdminQuery(queryFn, { + errorMessage: "加载 H5 配置失败", + initialData: emptyData, + queryKey: ["app-config", "h5-links"] + }); + + const openEdit = (item) => { + setEditingItem(item); + setForm({ url: item.url || "" }); + }; + + const closeEdit = () => { + setEditingItem(null); + setForm(emptyForm()); + }; + + const submitEdit = async (event) => { + event.preventDefault(); + if (!editingItem) { + return; + } + const payload = parseForm(h5LinkUpdateSchema, { key: editingItem.key, url: form.url }); + setLoadingAction("edit"); + try { + await updateH5Links({ items: [payload] }); + closeEdit(); + await reload(); + showToast("H5配置已更新", "success"); + } catch (err) { + showToast(err.message || "操作失败", "error"); + } finally { + setLoadingAction(""); + } + }; + + return { + abilities, + closeEdit, + data, + editingItem, + error, + form, + loading, + loadingAction, + openEdit, + reload, + setForm, + submitEdit + }; +} diff --git a/src/features/app-config/pages/H5ConfigPage.jsx b/src/features/app-config/pages/H5ConfigPage.jsx new file mode 100644 index 0000000..311ccb1 --- /dev/null +++ b/src/features/app-config/pages/H5ConfigPage.jsx @@ -0,0 +1,97 @@ +import EditOutlined from "@mui/icons-material/EditOutlined"; +import LinkOutlined from "@mui/icons-material/LinkOutlined"; +import TextField from "@mui/material/TextField"; +import { formatMillis } from "@/shared/utils/time.js"; +import { AdminFormDialog } from "@/shared/ui/AdminFormDialog.jsx"; +import { DataState } from "@/shared/ui/DataState.jsx"; +import { DataTable } from "@/shared/ui/DataTable.jsx"; +import { + AdminActionIconButton, + AdminListBody, + AdminListPage, + AdminRowActions +} from "@/shared/ui/AdminListLayout.jsx"; +import { useH5ConfigPage } from "@/features/app-config/hooks/useH5ConfigPage.js"; +import styles from "@/features/app-config/app-config.module.css"; + +const baseColumns = [ + { + key: "label", + label: "配置项", + width: "minmax(180px, 0.8fr)" + }, + { + key: "key", + label: "Key", + width: "minmax(200px, 0.9fr)" + }, + { + key: "url", + label: "H5链接", + render: (item) => {item.url || "-"}, + width: "minmax(360px, 1.8fr)" + }, + { + key: "updatedAtMs", + label: "更新时间", + render: (item) => formatMillis(item.updatedAtMs), + width: "minmax(170px, 0.8fr)" + } +]; + +export function H5ConfigPage() { + const page = useH5ConfigPage(); + const items = page.data.items || []; + const columns = page.abilities.canUpdate + ? [ + ...baseColumns, + { + key: "actions", + label: "操作", + render: (item) => , + width: "minmax(88px, 0.4fr)" + } + ] + : baseColumns; + + return ( + + + + item.key} /> +
+ {items.length} 条 +
+
+
+ + + page.setForm({ url: event.target.value })} + /> + +
+ ); +} + +function H5LinkActions({ item, page }) { + return ( + + page.openEdit(item)}> + {item.url ? : } + + + ); +} diff --git a/src/features/app-config/permissions.js b/src/features/app-config/permissions.js new file mode 100644 index 0000000..7f31115 --- /dev/null +++ b/src/features/app-config/permissions.js @@ -0,0 +1,11 @@ +import { useAuth } from "@/app/auth/AuthProvider.jsx"; +import { PERMISSIONS } from "@/app/permissions"; + +export function useAppConfigAbilities() { + const { can } = useAuth(); + + return { + canUpdate: can(PERMISSIONS.appConfigUpdate), + canView: can(PERMISSIONS.appConfigView) + }; +} diff --git a/src/features/app-config/routes.js b/src/features/app-config/routes.js new file mode 100644 index 0000000..7afc396 --- /dev/null +++ b/src/features/app-config/routes.js @@ -0,0 +1,12 @@ +import { MENU_CODES, PERMISSIONS } from "@/app/permissions"; + +export const appConfigRoutes = [ + { + label: "H5配置", + loader: () => import("./pages/H5ConfigPage.jsx").then((module) => module.H5ConfigPage), + menuCode: MENU_CODES.appConfigH5, + pageKey: "app-config-h5", + path: "/app-config/h5", + permission: PERMISSIONS.appConfigView + } +]; diff --git a/src/features/app-config/schema.ts b/src/features/app-config/schema.ts new file mode 100644 index 0000000..bad434f --- /dev/null +++ b/src/features/app-config/schema.ts @@ -0,0 +1,15 @@ +import { z } from "zod"; +import { H5_LINK_KEYS } from "@/features/app-config/constants"; + +const h5LinkURLSchema = z.string() + .trim() + .max(2048, "H5 链接不能超过 2048 个字符") + .refine((value) => !/\s/.test(value), "H5 链接不能包含空白字符") + .default(""); + +export const h5LinkUpdateSchema = z.object({ + key: z.enum(H5_LINK_KEYS), + url: h5LinkURLSchema +}); + +export type H5LinkUpdateForm = z.infer; diff --git a/src/features/app-registry/api.ts b/src/features/app-registry/api.ts new file mode 100644 index 0000000..c55cf62 --- /dev/null +++ b/src/features/app-registry/api.ts @@ -0,0 +1,10 @@ +import { apiRequest } from "@/shared/api/request"; +import { API_ENDPOINTS, API_OPERATIONS, apiEndpointPath } from "@/shared/api/generated/endpoints"; +import type { ApiList, AdminAppDto } from "@/shared/api/types"; + +export function listAdminApps(): Promise> { + const endpoint = API_ENDPOINTS.listApps; + return apiRequest>(apiEndpointPath(API_OPERATIONS.listApps), { + method: endpoint.method + }); +} diff --git a/src/features/app-users/api.ts b/src/features/app-users/api.ts new file mode 100644 index 0000000..b30ad04 --- /dev/null +++ b/src/features/app-users/api.ts @@ -0,0 +1,51 @@ +import { apiRequest } from "@/shared/api/request"; +import { API_ENDPOINTS, API_OPERATIONS, apiEndpointPath } from "@/shared/api/generated/endpoints"; +import type { + ApiPage, + AppUserDto, + AppUserPasswordPayload, + AppUserUpdatePayload, + EntityId, + PageQuery +} from "@/shared/api/types"; + +export function listAppUsers(query: PageQuery = {}): Promise> { + const endpoint = API_ENDPOINTS.appListUsers; + return apiRequest>(apiEndpointPath(API_OPERATIONS.appListUsers), { + method: endpoint.method, + query + }); +} + +export function updateAppUser(userId: EntityId, payload: AppUserUpdatePayload): Promise { + const endpoint = API_ENDPOINTS.appUpdateUser; + return apiRequest(apiEndpointPath(API_OPERATIONS.appUpdateUser, { id: userId }), { + body: payload, + method: endpoint.method + }); +} + +export function banAppUser(userId: EntityId): Promise { + const endpoint = API_ENDPOINTS.appBanUser; + return apiRequest(apiEndpointPath(API_OPERATIONS.appBanUser, { id: userId }), { + method: endpoint.method + }); +} + +export function unbanAppUser(userId: EntityId): Promise { + const endpoint = API_ENDPOINTS.appUnbanUser; + return apiRequest(apiEndpointPath(API_OPERATIONS.appUnbanUser, { id: userId }), { + method: endpoint.method + }); +} + +export function setAppUserPassword(userId: EntityId, payload: AppUserPasswordPayload): Promise<{ passwordSet: boolean }> { + const endpoint = API_ENDPOINTS.appSetPassword; + return apiRequest<{ passwordSet: boolean }, AppUserPasswordPayload>( + apiEndpointPath(API_OPERATIONS.appSetPassword, { id: userId }), + { + body: payload, + method: endpoint.method + } + ); +} diff --git a/src/features/app-users/app-users.module.css b/src/features/app-users/app-users.module.css new file mode 100644 index 0000000..a10d49e --- /dev/null +++ b/src/features/app-users/app-users.module.css @@ -0,0 +1,200 @@ +.root { + display: flex; + height: calc(100vh - var(--header-height) - var(--space-8) - var(--space-4)); + height: calc(100dvh - var(--header-height) - var(--space-8) - var(--space-4)); + min-height: 0; + flex-direction: column; +} + +.contentPanel { + display: flex; + flex: 1; + flex-direction: column; + min-height: 0; + overflow: hidden; + border: 1px solid var(--border); + border-radius: var(--radius-card); + background: var(--bg-card); +} + +.contentPanel :global(.data-state), +.contentPanel :global(.table-frame) { + flex: 1; + min-height: 0; + border: 0; + border-radius: 0; + background: transparent; +} + +.contentPanel :global(.table-scroll) { + overflow: auto; +} + +.toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-4); + padding: var(--space-4) var(--space-5); + border-bottom: 1px solid var(--border); +} + +.filters { + display: flex; + min-width: 0; + flex: 1; + align-items: center; + flex-wrap: wrap; + gap: var(--space-3); +} + +.search { + flex: 0 1 320px; +} + +.statusSelect { + flex: 0 0 150px; +} + +.listBlock { + display: flex; + flex: 1; + min-height: 0; + flex-direction: column; + background: var(--bg-card); +} + +.listBlock :global(.pagination-bar) { + margin: var(--space-3) var(--space-5) var(--space-4); +} + +.identity { + display: flex; + min-width: 0; + align-items: center; + gap: var(--space-3); +} + +.avatar { + display: inline-flex; + width: 36px; + height: 36px; + flex: 0 0 auto; + align-items: center; + justify-content: center; + overflow: hidden; + border: 1px solid var(--border); + border-radius: 50%; + background: var(--bg-card-strong); + color: var(--text-secondary); + font-weight: 700; +} + +.avatar img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.identityText { + min-width: 0; +} + +.nameLine { + display: flex; + min-width: 0; + align-items: center; + gap: var(--space-2); + color: var(--text-primary); + font-weight: 650; +} + +.name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.gender { + display: inline-flex; + width: 18px; + height: 18px; + align-items: center; + justify-content: center; + border-radius: 50%; + font-weight: 700; + line-height: 1; +} + +.genderMale { + background: rgba(37, 99, 235, 0.1); + color: #2563eb; +} + +.genderFemale { + background: rgba(219, 39, 119, 0.12); + color: #db2777; +} + +.genderUnknown { + background: var(--neutral-surface); + color: var(--text-tertiary); +} + +.meta { + color: var(--text-tertiary); + font-size: var(--admin-font-size); +} + +.stack { + display: grid; + gap: 2px; +} + +.primaryText { + color: var(--text-primary); + font-weight: 650; +} + +.countryButton { + display: inline-flex; + width: fit-content; + max-width: 100%; + align-items: center; + justify-content: flex-start; + overflow: hidden; + border: 0; + background: transparent; + color: var(--primary); + cursor: pointer; + font: inherit; + font-weight: 650; + padding: 0; + text-align: left; + text-overflow: ellipsis; + white-space: nowrap; +} + +.countryButton:hover { + color: var(--primary-strong); +} + +.rowActions { + display: inline-flex; + align-items: center; + gap: var(--space-2); +} + +@media (max-width: 760px) { + .toolbar, + .filters { + align-items: stretch; + flex-direction: column; + } + + .search, + .statusSelect { + flex: 1 1 auto; + width: 100%; + } +} diff --git a/src/features/app-users/constants.js b/src/features/app-users/constants.js new file mode 100644 index 0000000..57945ca --- /dev/null +++ b/src/features/app-users/constants.js @@ -0,0 +1,18 @@ +export const appUserStatusFilters = [ + ["", "全部"], + ["active", "正常"], + ["banned", "封禁"] +]; + +export const appUserStatusLabels = { + active: "正常", + banned: "封禁", + disabled: "封禁" +}; + +export const genderOptions = [ + ["", "未设置"], + ["male", "男"], + ["female", "女"], + ["unknown", "未知"] +]; diff --git a/src/features/app-users/hooks/useAppUsersPage.js b/src/features/app-users/hooks/useAppUsersPage.js new file mode 100644 index 0000000..f99348e --- /dev/null +++ b/src/features/app-users/hooks/useAppUsersPage.js @@ -0,0 +1,197 @@ +import { useMemo, useState } from "react"; +import { parseForm } from "@/shared/forms/validation"; +import { usePaginatedQuery } from "@/shared/hooks/usePaginatedQuery.js"; +import { useConfirm } from "@/shared/ui/ConfirmProvider.jsx"; +import { useToast } from "@/shared/ui/ToastProvider.jsx"; +import { + banAppUser, + listAppUsers, + setAppUserPassword, + unbanAppUser, + updateAppUser +} from "@/features/app-users/api"; +import { useAppUserAbilities } from "@/features/app-users/permissions.js"; +import { appUserCountrySchema, appUserPasswordSchema, appUserUpdateSchema } from "@/features/app-users/schema"; +import { useCountryOptions } from "@/features/host-org/hooks/useCountryOptions.js"; + +const pageSize = 10; +const emptyData = { items: [], page: 1, pageSize, total: 0 }; +const emptyEditForm = () => ({ avatar: "", gender: "", username: "" }); +const emptyCountryForm = () => ({ country: "" }); +const emptyPasswordForm = () => ({ password: "" }); + +export function useAppUsersPage() { + const abilities = useAppUserAbilities(); + const confirm = useConfirm(); + const { showToast } = useToast(); + const { countryOptions, loadingCountries } = useCountryOptions(); + const [query, setQuery] = useState(""); + const [status, setStatus] = useState(""); + const [page, setPage] = useState(1); + const [activeAction, setActiveAction] = useState(""); + const [activeUser, setActiveUser] = useState(null); + const [editForm, setEditForm] = useState(emptyEditForm); + const [countryForm, setCountryForm] = useState(emptyCountryForm); + const [passwordForm, setPasswordForm] = useState(emptyPasswordForm); + const [loadingAction, setLoadingAction] = useState(""); + const filters = useMemo( + () => ({ + keyword: query, + status + }), + [query, status] + ); + const { data = emptyData, error, loading, reload } = usePaginatedQuery({ + errorMessage: "加载用户列表失败", + fetcher: listAppUsers, + filters, + page, + pageSize, + queryKey: ["app-users", filters, page] + }); + + const changeQuery = (value) => { + setQuery(value); + setPage(1); + }; + + const changeStatus = (value) => { + setStatus(value); + setPage(1); + }; + + const openEdit = (user) => { + setActiveUser(user); + setEditForm({ + avatar: user.avatar || "", + gender: user.gender || "", + username: user.username || "" + }); + setActiveAction("edit"); + }; + + const openPassword = (user) => { + setActiveUser(user); + setPasswordForm(emptyPasswordForm()); + setActiveAction("password"); + }; + + const openCountry = (user) => { + setActiveUser(user); + setCountryForm({ country: user.country || "" }); + setActiveAction("country"); + }; + + const closeAction = () => { + setActiveAction(""); + setActiveUser(null); + }; + + const submitEdit = async (event) => { + event.preventDefault(); + if (!activeUser) { + return; + } + await runAction("edit", "用户已更新", async () => { + const payload = parseForm(appUserUpdateSchema, editForm); + await updateAppUser(activeUser.userId, payload); + closeAction(); + await reload(); + }); + }; + + const submitCountry = async (event) => { + event.preventDefault(); + if (!activeUser) { + return; + } + await runAction("country", "国家已更新", async () => { + const payload = parseForm(appUserCountrySchema, countryForm); + await updateAppUser(activeUser.userId, payload); + closeAction(); + await reload(); + }); + }; + + const submitPassword = async (event) => { + event.preventDefault(); + if (!activeUser) { + return; + } + await runAction("password", "密码已设置", async () => { + const payload = parseForm(appUserPasswordSchema, passwordForm); + await setAppUserPassword(activeUser.userId, payload); + closeAction(); + }); + }; + + const toggleBan = async (user) => { + const banned = isBanned(user); + const ok = await confirm({ + confirmText: banned ? "解封" : "封禁", + message: `${user.username || user.displayUserId || user.userId} 的状态会立即变更。`, + title: banned ? "解封用户" : "封禁用户", + tone: banned ? "primary" : "danger" + }); + if (!ok) { + return; + } + await runAction(`status-${user.userId}`, banned ? "用户已解封" : "用户已封禁", async () => { + if (banned) { + await unbanAppUser(user.userId); + } else { + await banAppUser(user.userId); + } + await reload(); + }); + }; + + const runAction = async (action, successMessage, submitter) => { + setLoadingAction(action); + try { + await submitter(); + showToast(successMessage, "success"); + } catch (err) { + showToast(err.message || "操作失败", "error"); + } finally { + setLoadingAction(""); + } + }; + + return { + abilities, + activeAction, + activeUser, + changeQuery, + changeStatus, + closeAction, + countryForm, + countryOptions, + data, + editForm, + error, + loading, + loadingAction, + loadingCountries, + openCountry, + openEdit, + openPassword, + page, + passwordForm, + query, + reload, + setCountryForm, + setEditForm, + setPage, + setPasswordForm, + status, + submitCountry, + submitEdit, + submitPassword, + toggleBan + }; +} + +function isBanned(user) { + return user.status === "banned" || user.status === "disabled"; +} diff --git a/src/features/app-users/pages/AppUserListPage.jsx b/src/features/app-users/pages/AppUserListPage.jsx new file mode 100644 index 0000000..b236183 --- /dev/null +++ b/src/features/app-users/pages/AppUserListPage.jsx @@ -0,0 +1,266 @@ +import BlockOutlined from "@mui/icons-material/BlockOutlined"; +import EditOutlined from "@mui/icons-material/EditOutlined"; +import LockOpenOutlined from "@mui/icons-material/LockOpenOutlined"; +import PasswordOutlined from "@mui/icons-material/PasswordOutlined"; +import PersonOutlineOutlined from "@mui/icons-material/PersonOutlineOutlined"; +import Autocomplete from "@mui/material/Autocomplete"; +import MenuItem from "@mui/material/MenuItem"; +import TextField from "@mui/material/TextField"; +import Tooltip from "@mui/material/Tooltip"; +import { AdminFormDialog } from "@/shared/ui/AdminFormDialog.jsx"; +import { DataState } from "@/shared/ui/DataState.jsx"; +import { DataTable } from "@/shared/ui/DataTable.jsx"; +import { IconButton } from "@/shared/ui/IconButton.jsx"; +import { PaginationBar } from "@/shared/ui/PaginationBar.jsx"; +import { SearchBox } from "@/shared/ui/SearchBox.jsx"; +import { StatusSelect } from "@/shared/ui/StatusSelect.jsx"; +import { UploadField } from "@/shared/ui/UploadField.jsx"; +import { formatMillis } from "@/shared/utils/time.js"; +import { + appUserStatusFilters, + appUserStatusLabels, + genderOptions +} from "@/features/app-users/constants.js"; +import { useAppUsersPage } from "@/features/app-users/hooks/useAppUsersPage.js"; +import styles from "@/features/app-users/app-users.module.css"; + +const columns = [ + { + key: "identity", + label: "用户", + width: "minmax(240px, 1.5fr)", + render: (user) => + }, + { key: "coin", label: "金币", width: "minmax(90px, 0.6fr)", render: (user) => formatNumber(user.coin) }, + { key: "diamond", label: "钻石", width: "minmax(90px, 0.6fr)", render: (user) => formatNumber(user.diamond) }, + { + key: "status", + label: "状态", + width: "minmax(92px, 0.6fr)", + render: (user) => + }, + { + key: "time", + label: "创建 / 活跃", + width: "minmax(180px, 1fr)", + render: (user) => ( +
+ {formatMillis(user.createdAtMs)} + {formatMillis(user.lastActiveAtMs)} +
+ ) + } +]; + +export function AppUserListPage() { + const page = useAppUsersPage(); + const items = page.data.items || []; + const total = page.data.total || 0; + const tableColumns = [ + columns[0], + { + key: "location", + label: "国家 / 区域", + width: "minmax(170px, 1fr)", + render: (user) => + }, + ...columns.slice(1), + { + key: "actions", + label: "操作", + width: "minmax(148px, 0.8fr)", + render: (user) => + } + ]; + + return ( +
+
+
+
+ + +
+
+ + +
+ user.userId} /> + {total > 0 ? : null} +
+
+
+ + + page.setEditForm({ ...page.editForm, username: event.target.value })} /> + page.setEditForm({ ...page.editForm, avatar })} /> + page.setEditForm({ ...page.editForm, gender: event.target.value })}> + {genderOptions.map(([value, label]) => ( + {label} + ))} + + + + + option.label || ""} + isOptionEqualToValue={(option, value) => option.value === value.value} + loading={page.loadingCountries} + options={page.countryOptions} + renderInput={(params) => } + size="small" + value={page.countryOptions.find((option) => option.value === page.countryForm.country) || null} + onChange={(_, option) => page.setCountryForm({ country: option?.value || "" })} + /> + + + + page.setPasswordForm({ ...page.passwordForm, password: event.target.value })} /> + +
+ ); +} + +function UserLocation({ page, user }) { + const countryLabel = formatCountry(user); + const regionLabel = user.regionName || (user.regionId ? `区域 ${user.regionId}` : "-"); + return ( +
+ {page.abilities.canUpdate ? ( + + ) : ( + {countryLabel} + )} + {regionLabel} +
+ ); +} + +function UserIdentity({ user }) { + const name = user.username || "-"; + const shortId = user.displayUserId || user.userId; + const gender = genderView(user.gender); + return ( +
+ + {user.avatar ? : } + +
+
+ {name} + {gender.symbol} +
+
{shortId}
+
+
+ ); +} + +function UserActions({ page, user }) { + const banned = user.status === "banned" || user.status === "disabled"; + return ( +
+ {page.abilities.canUpdate ? ( + + + page.openEdit(user)}> + + + + + ) : null} + {page.abilities.canStatus ? ( + + + page.toggleBan(user)}> + {banned ? : } + + + + ) : null} + {page.abilities.canPassword ? ( + + + page.openPassword(user)}> + + + + + ) : null} +
+ ); +} + +function UserStatus({ status }) { + const tone = status === "active" ? "running" : "danger"; + return ( + + + {appUserStatusLabels[status] || status || "-"} + + ); +} + +function ActionModal({ children, disabled, loading, onClose, onSubmit, open, title }) { + return ( + + {children} + + ); +} + +function genderView(gender) { + const value = String(gender || "").toLowerCase(); + if (value === "male" || value === "m" || value === "男") { + return { className: "genderMale", symbol: "♂" }; + } + if (value === "female" || value === "f" || value === "女") { + return { className: "genderFemale", symbol: "♀" }; + } + return { className: "genderUnknown", symbol: "-" }; +} + +function formatNumber(value) { + return Number(value || 0).toLocaleString("zh-CN"); +} + +function formatCountry(user) { + return user.countryDisplayName || user.countryName || user.country || "-"; +} diff --git a/src/features/app-users/permissions.js b/src/features/app-users/permissions.js new file mode 100644 index 0000000..eea5498 --- /dev/null +++ b/src/features/app-users/permissions.js @@ -0,0 +1,14 @@ +import { useAuth } from "@/app/auth/AuthProvider.jsx"; +import { PERMISSIONS } from "@/app/permissions"; + +export function useAppUserAbilities() { + const { can } = useAuth(); + + return { + canPassword: can(PERMISSIONS.appUserPassword), + canStatus: can(PERMISSIONS.appUserStatus), + canUpdate: can(PERMISSIONS.appUserUpdate), + canUpload: can(PERMISSIONS.uploadCreate), + canView: can(PERMISSIONS.appUserView) + }; +} diff --git a/src/features/app-users/routes.js b/src/features/app-users/routes.js new file mode 100644 index 0000000..1c5d27b --- /dev/null +++ b/src/features/app-users/routes.js @@ -0,0 +1,12 @@ +import { MENU_CODES, PERMISSIONS } from "@/app/permissions"; + +export const appUserRoutes = [ + { + label: "用户列表", + loader: () => import("./pages/AppUserListPage.jsx").then((module) => module.AppUserListPage), + menuCode: MENU_CODES.appUserList, + pageKey: "app-user-list", + path: "/app/users", + permission: PERMISSIONS.appUserView + } +]; diff --git a/src/features/app-users/schema.ts b/src/features/app-users/schema.ts new file mode 100644 index 0000000..9da6adb --- /dev/null +++ b/src/features/app-users/schema.ts @@ -0,0 +1,19 @@ +import { z } from "zod"; + +export const appUserUpdateSchema = z.object({ + avatar: z.string().trim().max(512, "头像不能超过 512 个字符").optional().default(""), + gender: z.string().trim().max(32, "性别不能超过 32 个字符").optional().default(""), + username: z.string().trim().max(64, "用户名称不能超过 64 个字符").optional().default("") +}); + +export const appUserCountrySchema = z.object({ + country: z.string().trim().min(2, "请选择国家").max(16, "国家码不能超过 16 个字符") +}); + +export const appUserPasswordSchema = z.object({ + password: z.string().trim().min(6, "密码至少 6 位").max(128, "密码不能超过 128 个字符") +}); + +export type AppUserUpdateForm = z.infer; +export type AppUserCountryForm = z.infer; +export type AppUserPasswordForm = z.infer; diff --git a/src/features/auth/api.ts b/src/features/auth/api.ts new file mode 100644 index 0000000..3135d07 --- /dev/null +++ b/src/features/auth/api.ts @@ -0,0 +1,37 @@ +import { apiRequest } from "@/shared/api/request"; +import { API_ENDPOINTS, API_OPERATIONS, apiEndpointPath } from "@/shared/api/generated/endpoints"; +import type { ChangePasswordPayload, LoginPayload, SessionDto } from "@/shared/api/types"; + +export function login(payload: LoginPayload): Promise { + const endpoint = API_ENDPOINTS.login; + return apiRequest(apiEndpointPath(API_OPERATIONS.login), { + body: payload, + method: endpoint.method, + skipAuth: true + }); +} + +export function logout(): Promise { + const endpoint = API_ENDPOINTS.logout; + return apiRequest(apiEndpointPath(API_OPERATIONS.logout), { method: endpoint.method, skipAuth: true }); +} + +export function refreshSession(): Promise { + const endpoint = API_ENDPOINTS.refresh; + return apiRequest(apiEndpointPath(API_OPERATIONS.refresh), { + method: endpoint.method, + skipAuth: true + }); +} + +export function getMe(): Promise { + return apiRequest(apiEndpointPath(API_OPERATIONS.me)); +} + +export function changePassword(payload: ChangePasswordPayload): Promise { + const endpoint = API_ENDPOINTS.changePassword; + return apiRequest(apiEndpointPath(API_OPERATIONS.changePassword), { + body: payload, + method: endpoint.method + }); +} diff --git a/src/pages/LoginPage.jsx b/src/features/auth/pages/LoginPage.jsx similarity index 92% rename from src/pages/LoginPage.jsx rename to src/features/auth/pages/LoginPage.jsx index c481437..ea81fdd 100644 --- a/src/pages/LoginPage.jsx +++ b/src/features/auth/pages/LoginPage.jsx @@ -3,9 +3,9 @@ import LoginOutlined from "@mui/icons-material/LoginOutlined"; import TextField from "@mui/material/TextField"; import { useState } from "react"; import { Navigate, useLocation, useNavigate } from "react-router-dom"; -import { useAuth } from "../auth/AuthProvider.jsx"; -import { Button } from "../components/base/Button.jsx"; -import { useToast } from "../components/base/ToastProvider.jsx"; +import { useAuth } from "@/app/auth/AuthProvider.jsx"; +import { Button } from "@/shared/ui/Button.jsx"; +import { useToast } from "@/shared/ui/ToastProvider.jsx"; export function LoginPage() { const [username, setUsername] = useState("admin"); diff --git a/src/features/auth/routes.js b/src/features/auth/routes.js new file mode 100644 index 0000000..239cabe --- /dev/null +++ b/src/features/auth/routes.js @@ -0,0 +1,8 @@ +export const authRoutes = [ + { + label: "登录", + loader: () => import("./pages/LoginPage.jsx").then((module) => module.LoginPage), + pageKey: "login", + path: "/login" + } +]; diff --git a/src/features/dashboard/api.ts b/src/features/dashboard/api.ts new file mode 100644 index 0000000..c6a28f6 --- /dev/null +++ b/src/features/dashboard/api.ts @@ -0,0 +1,7 @@ +import { apiRequest } from "@/shared/api/request"; +import { API_OPERATIONS, apiEndpointPath } from "@/shared/api/generated/endpoints"; +import type { DashboardOverviewDto } from "@/shared/api/types"; + +export function getDashboardOverview(): Promise { + return apiRequest(apiEndpointPath(API_OPERATIONS.dashboardOverview)); +} diff --git a/src/features/dashboard/components/DashboardAlerts.jsx b/src/features/dashboard/components/DashboardAlerts.jsx new file mode 100644 index 0000000..6c14b4e --- /dev/null +++ b/src/features/dashboard/components/DashboardAlerts.jsx @@ -0,0 +1,16 @@ +export function DashboardAlerts({ alerts = [] }) { + return ( +
+ {alerts.map((alert) => ( +
+ +
+
{alert.name}
+
{alert.desc}
+
+
{alert.time}
+
+ ))} +
+ ); +} diff --git a/src/features/dashboard/components/DashboardCharts.jsx b/src/features/dashboard/components/DashboardCharts.jsx new file mode 100644 index 0000000..2d72a0f --- /dev/null +++ b/src/features/dashboard/components/DashboardCharts.jsx @@ -0,0 +1,18 @@ +import { Card } from "@/shared/ui/Card.jsx"; +import { BarChart } from "@/shared/charts/BarChart.jsx"; +import { ChartCard } from "@/shared/charts/ChartCard.jsx"; + +export function DashboardCharts({ overview }) { + return ( +
+ + + +
+
通知趋势
+
+ +
+
+ ); +} diff --git a/src/features/dashboard/components/DashboardStats.jsx b/src/features/dashboard/components/DashboardStats.jsx new file mode 100644 index 0000000..816c967 --- /dev/null +++ b/src/features/dashboard/components/DashboardStats.jsx @@ -0,0 +1,16 @@ +import DeveloperBoardOutlined from "@mui/icons-material/DeveloperBoardOutlined"; +import DnsOutlined from "@mui/icons-material/DnsOutlined"; +import SecurityOutlined from "@mui/icons-material/SecurityOutlined"; +import StorageOutlined from "@mui/icons-material/StorageOutlined"; +import { KpiCard } from "@/shared/ui/KpiCard.jsx"; + +export function DashboardStats({ overview, stats }) { + return ( +
+ 启用 {stats.activeUsers}} /> + + + 锁定 {stats.lockedUsers} / 停用 {stats.disabledUsers}} /> +
+ ); +} diff --git a/src/features/dashboard/components/DashboardToolbar.jsx b/src/features/dashboard/components/DashboardToolbar.jsx new file mode 100644 index 0000000..f617923 --- /dev/null +++ b/src/features/dashboard/components/DashboardToolbar.jsx @@ -0,0 +1,16 @@ +import Refresh from "@mui/icons-material/Refresh"; +import { Button } from "@/shared/ui/Button.jsx"; +import { PageHead } from "@/shared/ui/PageHead.jsx"; +import { TimeRangeSelect } from "@/shared/ui/TimeRangeSelect.jsx"; + +export function DashboardToolbar({ onRefresh, onRangeChange, overview, range }) { + return ( + + + + + ); +} diff --git a/src/features/dashboard/dashboard.css b/src/features/dashboard/dashboard.css new file mode 100644 index 0000000..fa10cb8 --- /dev/null +++ b/src/features/dashboard/dashboard.css @@ -0,0 +1,360 @@ +.overview-charts { + display: grid; + grid-template-columns: repeat(3, minmax(260px, 1fr)); + gap: var(--space-4); + margin-top: var(--space-4); +} + +.chart-card { + min-height: 304px; + padding: var(--space-5); + animation: panel-enter var(--motion-slow) var(--ease-emphasized) both; + transition: + border-color var(--motion-base) var(--ease-standard), + box-shadow var(--motion-base) var(--ease-standard), + transform var(--motion-base) var(--ease-emphasized); +} + +.chart-card--compact { + min-height: 150px; +} + +.chart-card--compact .line-chart { + height: 78px; +} + +.chart-card--compact .card-head { + margin-bottom: var(--space-2); +} + +.chart-card--compact .segmented { + display: none; +} + +.card-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-3); + margin-bottom: var(--space-5); +} + +.segmented { + display: inline-flex; + height: 30px; + padding: calc(var(--space-1) / 2); + border: 1px solid var(--border); + border-radius: var(--radius-control); + background: var(--bg-card-strong); +} + +.segment { + min-width: 52px; + padding: 0 var(--space-3); + border-radius: var(--radius-control); + background: transparent; + color: var(--text-tertiary); + cursor: pointer; + font-size: var(--admin-font-size); + transition: + background var(--motion-fast) var(--ease-standard), + color var(--motion-fast) var(--ease-standard), + transform var(--motion-base) var(--ease-emphasized); +} + +.segment:hover { + transform: translateY(-1px); +} + +.segment--active { + background: var(--primary-surface); + color: var(--text-primary); +} + +.line-chart { + width: 100%; + height: 216px; + opacity: 0; + animation: panel-enter 420ms var(--ease-emphasized) 120ms both; +} + +.echart { + width: 100%; + min-width: 0; +} + +.line-chart__grid { + stroke: var(--chart-grid); + stroke-width: 1; +} + +.line-chart__area { + opacity: 0.18; +} + +.line-chart__line { + fill: none; + stroke-width: 2; + stroke-linecap: round; + stroke-linejoin: round; +} + +.donut-wrap { + display: grid; + grid-template-columns: 140px 1fr; + align-items: center; + gap: var(--space-6); + min-height: 216px; +} + +.donut-echart { + width: 140px; + height: 140px; + opacity: 0; + animation: panel-enter 420ms var(--ease-emphasized) 120ms both; +} + +.donut { + display: grid; + width: 140px; + height: 140px; + place-items: center; + border-radius: var(--radius-pill); +} + +.donut-core { + display: grid; + width: 78px; + height: 78px; + place-items: center; + border-radius: var(--radius-pill); + background: var(--bg-card); + color: var(--text-secondary); + font-size: var(--admin-font-size); +} + +.donut-core strong { + display: block; + color: var(--text-primary); + font-size: var(--admin-font-size); + line-height: 1; +} + +.donut-legend { + display: grid; + gap: var(--space-3); +} + +.legend-row { + display: grid; + grid-template-columns: 8px 1fr auto; + align-items: center; + gap: var(--space-3); + color: var(--text-secondary); + font-size: var(--admin-font-size); +} + +.legend-row strong { + color: var(--text-primary); +} + +.legend-dot { + width: 8px; + height: 8px; + border-radius: var(--radius-pill); +} + +.legend-dot--success { + background: var(--success); +} + +.legend-dot--danger { + background: var(--danger); +} + +.legend-dot--stopped { + background: var(--stopped); +} + +.bar-chart { + display: block; + width: 100%; + height: 216px; + opacity: 0; + animation: panel-enter 420ms var(--ease-emphasized) 120ms both; +} + +.alert-list { + display: grid; + gap: var(--space-3); +} + +.alert-item { + display: grid; + grid-template-columns: 8px 1fr auto; + align-items: center; + gap: var(--space-3); + min-height: 42px; + animation: row-enter var(--motion-slow) var(--ease-emphasized) both; + transition: + background var(--motion-base) var(--ease-standard), + transform var(--motion-base) var(--ease-emphasized); +} + +.alert-item:nth-child(2) { + animation-delay: 50ms; +} + +.alert-item:nth-child(3) { + animation-delay: 100ms; +} + +.alert-item:hover { + transform: translateX(2px); +} + +.alert-dot { + width: 8px; + height: 8px; + border-radius: var(--radius-pill); +} + +.alert-dot--danger { + background: var(--danger); +} + +.alert-dot--warning { + background: var(--warning); +} + +.alert-dot--info { + background: var(--info); +} + +.alert-name { + color: var(--text-primary); + font-size: var(--admin-font-size); + font-weight: 620; +} + +.alert-desc, +.alert-time { + color: var(--text-tertiary); + font-size: var(--admin-font-size); +} + +.table-card { + margin-top: var(--space-4); + overflow: hidden; + animation: panel-enter var(--motion-slow) var(--ease-emphasized) both; + transition: + border-color var(--motion-base) var(--ease-standard), + box-shadow var(--motion-base) var(--ease-standard), + transform var(--motion-base) var(--ease-emphasized); +} + +.split-grid { + display: grid; + grid-template-columns: 280px minmax(0, 1fr); + gap: var(--space-4); + margin-top: var(--space-4); +} + +.server-rail { + overflow: hidden; + animation: panel-enter var(--motion-slow) var(--ease-emphasized) both; + transition: + border-color var(--motion-base) var(--ease-standard), + box-shadow var(--motion-base) var(--ease-standard), + transform var(--motion-base) var(--ease-emphasized); +} + +.server-list { + display: grid; + gap: 0; + padding: var(--space-2); +} + +.server-item { + display: grid; + grid-template-columns: 8px 1fr auto; + align-items: center; + gap: var(--space-3); + width: 100%; + min-height: 64px; + padding: var(--space-3); + border-radius: var(--radius-control); + background: transparent; + color: var(--text-primary); + cursor: pointer; + text-align: left; + animation: row-enter var(--motion-slow) var(--ease-emphasized) both; + transition: + background var(--motion-base) var(--ease-standard), + transform var(--motion-base) var(--ease-emphasized); +} + +.server-item:hover { + background: var(--bg-hover); + transform: translateX(3px); +} + +.server-item:nth-child(2) { + animation-delay: 40ms; +} + +.server-item:nth-child(3) { + animation-delay: 80ms; +} + +.server-item:nth-child(4) { + animation-delay: 120ms; +} + +.server-item strong, +.server-item small { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.server-item small { + margin-top: var(--space-1); + color: var(--text-tertiary); + font-size: var(--admin-font-size); +} + +.server-status { + width: 8px; + height: 8px; + border-radius: var(--radius-pill); + background: var(--stopped); +} + +.server-status--running { + background: var(--success); +} + +.server-status--warning { + background: var(--warning); +} + +.server-status--error { + background: var(--danger); +} + +.link-button { + background: transparent; + color: var(--primary); + cursor: pointer; + font-size: var(--admin-font-size); +} + +.alert-strip { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: var(--space-4); + margin-top: var(--space-4); + padding-bottom: var(--space-2); +} diff --git a/src/features/dashboard/hooks/useDashboardPage.js b/src/features/dashboard/hooks/useDashboardPage.js new file mode 100644 index 0000000..2ce6aa7 --- /dev/null +++ b/src/features/dashboard/hooks/useDashboardPage.js @@ -0,0 +1,49 @@ +import { useCallback, useMemo, useState } from "react"; +import { useAdminQuery } from "@/shared/hooks/useAdminQuery.js"; +import { getDashboardOverview } from "@/features/dashboard/api"; + +const emptyData = { overview: null }; + +export function useDashboardPage() { + const [range, setRange] = useState("近 1 小时"); + + const queryFn = useCallback(async () => { + const overview = await getDashboardOverview(); + return { overview }; + }, []); + + const { data, error, loading, reload } = useAdminQuery(queryFn, { + errorMessage: "加载总览失败", + initialData: emptyData, + keepPreviousData: true, + queryKey: ["dashboard", "overview"] + }); + + const overview = data?.overview; + + const stats = useMemo(() => { + if (!overview) { + return { activeUsers: 0, disabledUsers: 0, lockedUsers: 0, logsToday: 0, menuTotal: 0, rolesTotal: 0, unreadNotifications: 0, usersTotal: 0 }; + } + return { + activeUsers: overview.activeUsers, + disabledUsers: overview.disabledUsers, + lockedUsers: overview.lockedUsers, + logsToday: overview.logsToday, + menuTotal: overview.menuTotal, + rolesTotal: overview.rolesTotal, + unreadNotifications: overview.unreadNotifications, + usersTotal: overview.usersTotal + }; + }, [overview]); + + return { + error, + loading, + overview, + range, + reload, + setRange, + stats + }; +} diff --git a/src/features/dashboard/pages/OverviewPage.jsx b/src/features/dashboard/pages/OverviewPage.jsx new file mode 100644 index 0000000..0ace248 --- /dev/null +++ b/src/features/dashboard/pages/OverviewPage.jsx @@ -0,0 +1,28 @@ +import { DataState } from "@/shared/ui/DataState.jsx"; +import { DashboardAlerts } from "@/features/dashboard/components/DashboardAlerts.jsx"; +import { DashboardCharts } from "@/features/dashboard/components/DashboardCharts.jsx"; +import { DashboardStats } from "@/features/dashboard/components/DashboardStats.jsx"; +import { DashboardToolbar } from "@/features/dashboard/components/DashboardToolbar.jsx"; +import { useDashboardPage } from "@/features/dashboard/hooks/useDashboardPage.js"; + +export function OverviewPage() { + const page = useDashboardPage(); + + return ( + <> + + + + + + + + + + ); +} diff --git a/src/features/dashboard/permissions.js b/src/features/dashboard/permissions.js new file mode 100644 index 0000000..e4f271a --- /dev/null +++ b/src/features/dashboard/permissions.js @@ -0,0 +1,3 @@ +export function useDashboardAbilities() { + return {}; +} diff --git a/src/features/dashboard/routes.js b/src/features/dashboard/routes.js new file mode 100644 index 0000000..e27635f --- /dev/null +++ b/src/features/dashboard/routes.js @@ -0,0 +1,12 @@ +import { MENU_CODES, PERMISSIONS } from "@/app/permissions"; + +export const dashboardRoutes = [ + { + label: "系统概览", + loader: () => import("./pages/OverviewPage.jsx").then((module) => module.OverviewPage), + menuCode: MENU_CODES.overview, + pageKey: "overview", + path: "/overview", + permission: PERMISSIONS.overviewView + } +]; diff --git a/src/features/host-org/api.test.ts b/src/features/host-org/api.test.ts new file mode 100644 index 0000000..2c9c22d --- /dev/null +++ b/src/features/host-org/api.test.ts @@ -0,0 +1,101 @@ +import { afterEach, expect, test, vi } from "vitest"; +import { setAccessToken } from "@/shared/api/request"; +import { + createCountry, + createCoinSeller, + createRegion, + disableRegion, + listAgencies, + listBDs, + listCoinSellers, + listCountries, + listHosts, + listRegions, + replaceRegionCountries, + setCoinSellerStatus, + updateCountry +} from "./api"; + +afterEach(() => { + setAccessToken(""); + vi.unstubAllGlobals(); +}); + +test("host org list APIs use generated admin paths and filters", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async () => new Response(JSON.stringify({ code: 0, data: { items: [], page: 1, pageSize: 10, total: 0 } }))) + ); + + await listBDs({ page: 1, page_size: 10, parent_leader_user_id: 21, region_id: 7, status: "active" }); + await listAgencies({ keyword: "agency", page: 2, page_size: 10, parent_bd_user_id: 31 }); + await listHosts({ agency_id: 41, page: 1, page_size: 10 }); + await listCoinSellers({ page: 1, page_size: 10, region_id: 7, status: "active" }); + await createCoinSeller({ commandId: "coin-seller-test", reason: "contact", targetUserId: 1001 }); + await setCoinSellerStatus(1001, { commandId: "coin-seller-status-test", reason: "disable", status: "disabled" }); + + const [bdUrl, bdInit] = vi.mocked(fetch).mock.calls[0]; + const [agencyUrl] = vi.mocked(fetch).mock.calls[1]; + const [hostUrl] = vi.mocked(fetch).mock.calls[2]; + const [coinSellerUrl, coinSellerInit] = vi.mocked(fetch).mock.calls[3]; + const [coinSellerCreateUrl, coinSellerCreateInit] = vi.mocked(fetch).mock.calls[4]; + const [coinSellerStatusUrl, coinSellerStatusInit] = vi.mocked(fetch).mock.calls[5]; + + expect(String(bdUrl)).toContain("/api/v1/admin/bds?"); + expect(String(bdUrl)).toContain("parent_leader_user_id=21"); + expect(String(bdUrl)).toContain("region_id=7"); + expect(String(bdUrl)).toContain("status=active"); + expect(bdInit?.method).toBe("GET"); + expect(String(agencyUrl)).toContain("/api/v1/admin/agencies?"); + expect(String(agencyUrl)).toContain("parent_bd_user_id=31"); + expect(String(hostUrl)).toContain("/api/v1/admin/hosts?"); + expect(String(hostUrl)).toContain("agency_id=41"); + expect(String(coinSellerUrl)).toContain("/api/v1/admin/coin-sellers?"); + expect(String(coinSellerUrl)).toContain("region_id=7"); + expect(String(coinSellerUrl)).toContain("status=active"); + expect(coinSellerInit?.method).toBe("GET"); + expect(String(coinSellerCreateUrl)).toContain("/api/v1/admin/coin-sellers"); + expect(coinSellerCreateInit?.method).toBe("POST"); + expect(String(coinSellerStatusUrl)).toContain("/api/v1/admin/coin-sellers/1001/status"); + expect(coinSellerStatusInit?.method).toBe("PATCH"); +}); + +test("country and region APIs use generated admin paths", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async () => new Response(JSON.stringify({ code: 0, data: { items: [], total: 0 } }))) + ); + + await listCountries({ enabled: true }); + await createCountry({ countryCode: "US", countryDisplayName: "United States", countryName: "United States", enabled: true }); + await updateCountry(12, { countryDisplayName: "United States", countryName: "United States" }); + await listRegions({ status: "active" }); + await createRegion({ countries: ["US"], name: "North America", regionCode: "NA" }); + await replaceRegionCountries(7, { countries: ["US", "CA"] }); + await disableRegion(7); + + const [countryListUrl, countryListInit] = vi.mocked(fetch).mock.calls[0]; + const [countryCreateUrl, countryCreateInit] = vi.mocked(fetch).mock.calls[1]; + const [countryUpdateUrl, countryUpdateInit] = vi.mocked(fetch).mock.calls[2]; + const [regionListUrl, regionListInit] = vi.mocked(fetch).mock.calls[3]; + const [regionCreateUrl, regionCreateInit] = vi.mocked(fetch).mock.calls[4]; + const [regionCountriesUrl, regionCountriesInit] = vi.mocked(fetch).mock.calls[5]; + const [regionDisableUrl, regionDisableInit] = vi.mocked(fetch).mock.calls[6]; + + expect(String(countryListUrl)).toContain("/api/v1/admin/countries?"); + expect(String(countryListUrl)).toContain("enabled=true"); + expect(countryListInit?.method).toBe("GET"); + expect(String(countryCreateUrl)).toContain("/api/v1/admin/countries"); + expect(countryCreateInit?.method).toBe("POST"); + expect(String(countryUpdateUrl)).toContain("/api/v1/admin/countries/12"); + expect(countryUpdateInit?.method).toBe("PATCH"); + expect(String(regionListUrl)).toContain("/api/v1/admin/regions?"); + expect(String(regionListUrl)).toContain("status=active"); + expect(regionListInit?.method).toBe("GET"); + expect(String(regionCreateUrl)).toContain("/api/v1/admin/regions"); + expect(regionCreateInit?.method).toBe("POST"); + expect(String(regionCountriesUrl)).toContain("/api/v1/admin/regions/7/countries"); + expect(regionCountriesInit?.method).toBe("PUT"); + expect(String(regionDisableUrl)).toContain("/api/v1/admin/regions/7"); + expect(regionDisableInit?.method).toBe("DELETE"); +}); diff --git a/src/features/host-org/api.ts b/src/features/host-org/api.ts new file mode 100644 index 0000000..abf24c7 --- /dev/null +++ b/src/features/host-org/api.ts @@ -0,0 +1,247 @@ +import { apiRequest } from "@/shared/api/request"; +import { API_ENDPOINTS, API_OPERATIONS, apiEndpointPath } from "@/shared/api/generated/endpoints"; +export { listRegions } from "@/shared/api/regions"; +import type { + AgencyDto, + AgencyJoinEnabledPayload, + ApiList, + ApiPage, + BDProfileDto, + BDStatusPayload, + CoinSellerDto, + CoinSellerStatusPayload, + CountryDto, + CountryPayload, + CountryUpdatePayload, + CreateAgencyPayload, + CreateAgencyResultDto, + CreateBDLeaderPayload, + CreateBDPayload, + CreateCoinSellerPayload, + EntityId, + HostCommandPayload, + HostProfileDto, + PageQuery, + RegionCountriesPayload, + RegionDto, + RegionPayload, + RegionUpdatePayload +} from "@/shared/api/types"; + +export function listCountries(query: PageQuery = {}): Promise> { + const endpoint = API_ENDPOINTS.listCountries; + return apiRequest>(apiEndpointPath(API_OPERATIONS.listCountries), { + method: endpoint.method, + query + }); +} + +export function getCountry(countryId: EntityId): Promise { + const endpoint = API_ENDPOINTS.getCountry; + return apiRequest(apiEndpointPath(API_OPERATIONS.getCountry, { country_id: countryId }), { + method: endpoint.method + }); +} + +export function createCountry(payload: CountryPayload): Promise { + const endpoint = API_ENDPOINTS.createCountry; + return apiRequest(apiEndpointPath(API_OPERATIONS.createCountry), { + body: payload, + method: endpoint.method + }); +} + +export function updateCountry(countryId: EntityId, payload: CountryUpdatePayload): Promise { + const endpoint = API_ENDPOINTS.updateCountry; + return apiRequest(apiEndpointPath(API_OPERATIONS.updateCountry, { country_id: countryId }), { + body: payload, + method: endpoint.method + }); +} + +export function enableCountry(countryId: EntityId): Promise { + const endpoint = API_ENDPOINTS.enableCountry; + return apiRequest(apiEndpointPath(API_OPERATIONS.enableCountry, { country_id: countryId }), { + method: endpoint.method + }); +} + +export function disableCountry(countryId: EntityId): Promise { + const endpoint = API_ENDPOINTS.disableCountry; + return apiRequest(apiEndpointPath(API_OPERATIONS.disableCountry, { country_id: countryId }), { + method: endpoint.method + }); +} + +export function deleteCountry(countryId: EntityId): Promise { + const endpoint = API_ENDPOINTS.deleteCountry; + return apiRequest(apiEndpointPath(API_OPERATIONS.deleteCountry, { country_id: countryId }), { + method: endpoint.method + }); +} + +export function getRegion(regionId: EntityId): Promise { + const endpoint = API_ENDPOINTS.getRegion; + return apiRequest(apiEndpointPath(API_OPERATIONS.getRegion, { region_id: regionId }), { + method: endpoint.method + }); +} + +export function createRegion(payload: RegionPayload): Promise { + const endpoint = API_ENDPOINTS.createRegion; + return apiRequest(apiEndpointPath(API_OPERATIONS.createRegion), { + body: payload, + method: endpoint.method + }); +} + +export function updateRegion(regionId: EntityId, payload: RegionUpdatePayload): Promise { + const endpoint = API_ENDPOINTS.updateRegion; + return apiRequest(apiEndpointPath(API_OPERATIONS.updateRegion, { region_id: regionId }), { + body: payload, + method: endpoint.method + }); +} + +export function replaceRegionCountries(regionId: EntityId, payload: RegionCountriesPayload): Promise { + const endpoint = API_ENDPOINTS.replaceRegionCountries; + return apiRequest( + apiEndpointPath(API_OPERATIONS.replaceRegionCountries, { region_id: regionId }), + { + body: payload, + method: endpoint.method + } + ); +} + +export function enableRegion(regionId: EntityId): Promise { + const endpoint = API_ENDPOINTS.enableRegion; + return apiRequest(apiEndpointPath(API_OPERATIONS.enableRegion, { region_id: regionId }), { + method: endpoint.method + }); +} + +export function disableRegion(regionId: EntityId): Promise { + const endpoint = API_ENDPOINTS.disableRegion; + return apiRequest(apiEndpointPath(API_OPERATIONS.disableRegion, { region_id: regionId }), { + method: endpoint.method + }); +} + +export function listBDLeaders(query: PageQuery = {}): Promise> { + const endpoint = API_ENDPOINTS.listBDLeaders; + return apiRequest>(apiEndpointPath(API_OPERATIONS.listBDLeaders), { + method: endpoint.method, + query + }); +} + +export function listBDs(query: PageQuery = {}): Promise> { + const endpoint = API_ENDPOINTS.listBDs; + return apiRequest>(apiEndpointPath(API_OPERATIONS.listBDs), { + method: endpoint.method, + query + }); +} + +export function listAgencies(query: PageQuery = {}): Promise> { + const endpoint = API_ENDPOINTS.listAgencies; + return apiRequest>(apiEndpointPath(API_OPERATIONS.listAgencies), { + method: endpoint.method, + query + }); +} + +export function listHosts(query: PageQuery = {}): Promise> { + const endpoint = API_ENDPOINTS.listHosts; + return apiRequest>(apiEndpointPath(API_OPERATIONS.listHosts), { + method: endpoint.method, + query + }); +} + +export function listCoinSellers(query: PageQuery = {}): Promise> { + const endpoint = API_ENDPOINTS.listCoinSellers; + return apiRequest>(apiEndpointPath(API_OPERATIONS.listCoinSellers), { + method: endpoint.method, + query + }); +} + +export function createBDLeader(payload: CreateBDLeaderPayload): Promise { + const endpoint = API_ENDPOINTS.createBDLeader; + return apiRequest(apiEndpointPath(API_OPERATIONS.createBDLeader), { + body: payload, + method: endpoint.method + }); +} + +export function createBD(payload: CreateBDPayload): Promise { + const endpoint = API_ENDPOINTS.createBD; + return apiRequest(apiEndpointPath(API_OPERATIONS.createBD), { + body: payload, + method: endpoint.method + }); +} + +export function setBDLeaderStatus(userId: EntityId, payload: BDStatusPayload): Promise { + const endpoint = API_ENDPOINTS.setBDLeaderStatus; + return apiRequest(apiEndpointPath(API_OPERATIONS.setBDLeaderStatus, { user_id: userId }), { + body: payload, + method: endpoint.method + }); +} + +export function setBDStatus(userId: EntityId, payload: BDStatusPayload): Promise { + const endpoint = API_ENDPOINTS.setBDStatus; + return apiRequest(apiEndpointPath(API_OPERATIONS.setBDStatus, { user_id: userId }), { + body: payload, + method: endpoint.method + }); +} + +export function createCoinSeller(payload: CreateCoinSellerPayload): Promise { + const endpoint = API_ENDPOINTS.createCoinSeller; + return apiRequest(apiEndpointPath(API_OPERATIONS.createCoinSeller), { + body: payload, + method: endpoint.method + }); +} + +export function setCoinSellerStatus(userId: EntityId, payload: CoinSellerStatusPayload): Promise { + const endpoint = API_ENDPOINTS.setCoinSellerStatus; + return apiRequest( + apiEndpointPath(API_OPERATIONS.setCoinSellerStatus, { user_id: userId }), + { + body: payload, + method: endpoint.method + } + ); +} + +export function createAgency(payload: CreateAgencyPayload): Promise { + const endpoint = API_ENDPOINTS.createAgency; + return apiRequest(apiEndpointPath(API_OPERATIONS.createAgency), { + body: payload, + method: endpoint.method + }); +} + +export function closeAgency(agencyId: EntityId, payload: HostCommandPayload): Promise { + const endpoint = API_ENDPOINTS.closeAgency; + return apiRequest(apiEndpointPath(API_OPERATIONS.closeAgency, { agency_id: agencyId }), { + body: payload, + method: endpoint.method + }); +} + +export function setAgencyJoinEnabled(agencyId: EntityId, payload: AgencyJoinEnabledPayload): Promise { + const endpoint = API_ENDPOINTS.setAgencyJoinEnabled; + return apiRequest( + apiEndpointPath(API_OPERATIONS.setAgencyJoinEnabled, { agency_id: agencyId }), + { + body: payload, + method: endpoint.method + } + ); +} diff --git a/src/features/host-org/components/HostOrgActionModal.jsx b/src/features/host-org/components/HostOrgActionModal.jsx new file mode 100644 index 0000000..6a38015 --- /dev/null +++ b/src/features/host-org/components/HostOrgActionModal.jsx @@ -0,0 +1,29 @@ +import { AdminFormDialog } from "@/shared/ui/AdminFormDialog.jsx"; + +export function HostOrgActionModal({ + children, + disabled, + loading, + onClose, + onSubmit, + open, + submitLabel = "提交", + submitVariant = "primary", + title +}) { + return ( + + {children} + + ); +} diff --git a/src/features/host-org/components/HostOrgFilters.jsx b/src/features/host-org/components/HostOrgFilters.jsx new file mode 100644 index 0000000..190e78b --- /dev/null +++ b/src/features/host-org/components/HostOrgFilters.jsx @@ -0,0 +1,50 @@ +import TextField from "@mui/material/TextField"; +import { RegionSelect } from "@/shared/ui/RegionSelect.jsx"; +import { SearchBox } from "@/shared/ui/SearchBox.jsx"; +import { StatusSelect } from "@/shared/ui/StatusSelect.jsx"; +import styles from "@/features/host-org/host-org.module.css"; + +export function HostOrgFilters({ + extraFilters = [], + loadingRegions = false, + onQueryChange, + onRegionIdChange, + onStatusChange, + query, + regionId, + regionOptions = [], + searchPlaceholder, + status, + statusOptions +}) { + return ( +
+ + +
+ + {extraFilters.map((filter) => ( + filter.onChange(event.target.value)} + /> + ))} +
+
+ ); +} diff --git a/src/features/host-org/components/HostOrgStatus.jsx b/src/features/host-org/components/HostOrgStatus.jsx new file mode 100644 index 0000000..deb4892 --- /dev/null +++ b/src/features/host-org/components/HostOrgStatus.jsx @@ -0,0 +1,23 @@ +import { statusLabels } from "@/features/host-org/constants.js"; + +export function HostOrgStatus({ value }) { + const status = value || "-"; + const tone = getStatusTone(status); + + return ( + + + {statusLabels[status] || status} + + ); +} + +function getStatusTone(status) { + if (status === "active") { + return "running"; + } + if (status === "closed" || status === "disabled") { + return "danger"; + } + return "stopped"; +} diff --git a/src/features/host-org/components/HostOrgTable.jsx b/src/features/host-org/components/HostOrgTable.jsx new file mode 100644 index 0000000..e40b2b1 --- /dev/null +++ b/src/features/host-org/components/HostOrgTable.jsx @@ -0,0 +1,5 @@ +import { DataTable } from "@/shared/ui/DataTable.jsx"; + +export function HostOrgTable(props) { + return ; +} diff --git a/src/features/host-org/components/HostOrgToolbar.jsx b/src/features/host-org/components/HostOrgToolbar.jsx new file mode 100644 index 0000000..946238e --- /dev/null +++ b/src/features/host-org/components/HostOrgToolbar.jsx @@ -0,0 +1,46 @@ +import Tooltip from "@mui/material/Tooltip"; +import { HostOrgFilters } from "@/features/host-org/components/HostOrgFilters.jsx"; +import styles from "@/features/host-org/host-org.module.css"; +import { IconButton } from "@/shared/ui/IconButton.jsx"; + +export function HostOrgToolbar({ actions = [], filters }) { + return ( +
+
+ +
+ + {actions.length ? ( +
+ {actions.map((action) => ( + + + + {action.icon} + + + + ))} +
+ ) : null} +
+ ); +} + +const primaryActionSx = { + borderColor: "var(--primary)", + backgroundColor: "var(--primary)", + color: "var(--active-contrast)", + "&:hover": { + borderColor: "var(--primary-strong)", + backgroundColor: "var(--primary-strong)", + color: "var(--active-contrast)" + } +}; diff --git a/src/features/host-org/constants.js b/src/features/host-org/constants.js new file mode 100644 index 0000000..4599bde --- /dev/null +++ b/src/features/host-org/constants.js @@ -0,0 +1,47 @@ +export const agencyStatusFilters = [ + ["", "全部"], + ["active", "启用"], + ["closed", "已关闭"] +]; + +export const countryEnabledFilters = [ + ["", "全部"], + ["enabled", "启用"], + ["disabled", "停用"] +]; + +export const regionStatusFilters = [ + ["", "全部"], + ["active", "启用"], + ["disabled", "停用"] +]; + +export const bdStatusFilters = [ + ["", "全部"], + ["active", "启用"], + ["disabled", "停用"] +]; + +export const hostStatusFilters = [ + ["", "全部"], + ["active", "启用"], + ["disabled", "停用"] +]; + +export const coinSellerStatusFilters = [ + ["", "全部"], + ["active", "启用"], + ["disabled", "停用"] +]; + +export const hostSourceLabels = { + agency_application: "Agency 申请", + agency_invitation: "Agency 邀请", + admin_create_agency: "后台创建 Agency" +}; + +export const statusLabels = { + active: "启用", + closed: "已关闭", + disabled: "停用" +}; diff --git a/src/features/host-org/hooks/useCountryOptions.js b/src/features/host-org/hooks/useCountryOptions.js new file mode 100644 index 0000000..eb12fd5 --- /dev/null +++ b/src/features/host-org/hooks/useCountryOptions.js @@ -0,0 +1,28 @@ +import { useCallback, useMemo } from "react"; +import { useAdminQuery } from "@/shared/hooks/useAdminQuery.js"; +import { listCountries } from "@/features/host-org/api"; + +const emptyData = { items: [], total: 0 }; + +export function useCountryOptions() { + const queryFn = useCallback(() => listCountries({ enabled: true }), []); + const { data = emptyData, loading: loadingCountries } = useAdminQuery(queryFn, { + errorMessage: "加载国家选项失败", + initialData: emptyData, + queryKey: ["host-org", "countries", "enabled-options"], + staleTime: 5 * 60 * 1000 + }); + + const countryOptions = useMemo(() => { + return (data?.items || []).map((country) => ({ + label: formatCountryOptionLabel(country), + value: country.countryCode + })); + }, [data?.items]); + + return { countryOptions, loadingCountries }; +} + +function formatCountryOptionLabel(country) { + return [country.flag, country.countryDisplayName || country.countryName, country.countryCode].filter(Boolean).join(" · "); +} diff --git a/src/features/host-org/hooks/useHostAgenciesPage.js b/src/features/host-org/hooks/useHostAgenciesPage.js new file mode 100644 index 0000000..1715eb6 --- /dev/null +++ b/src/features/host-org/hooks/useHostAgenciesPage.js @@ -0,0 +1,178 @@ +import { useMemo, useState } from "react"; +import { parseForm } from "@/shared/forms/validation"; +import { usePaginatedQuery } from "@/shared/hooks/usePaginatedQuery.js"; +import { useToast } from "@/shared/ui/ToastProvider.jsx"; +import { + closeAgency, + createAgency, + listAgencies, + setAgencyJoinEnabled +} from "@/features/host-org/api"; +import { useRegionOptions } from "@/shared/hooks/useRegionOptions.js"; +import { useAgencyAbilities } from "@/features/host-org/permissions.js"; +import { + agencyCloseSchema, + agencyJoinEnabledSchema, + createAgencySchema +} from "@/features/host-org/schema"; + +const emptyAgencyForm = () => ({ + commandId: makeCommandId("agency"), + joinEnabled: true, + maxHosts: 0, + name: "", + ownerUserId: "", + parentBdUserId: "", + reason: "" +}); +const emptyCloseForm = () => ({ agencyId: "", commandId: makeCommandId("agency-close"), reason: "" }); +const emptyJoinEnabledForm = () => ({ + agencyId: "", + commandId: makeCommandId("agency-join"), + joinEnabled: true, + reason: "" +}); +const pageSize = 10; +const emptyData = { items: [], page: 1, pageSize, total: 0 }; + +export function useHostAgenciesPage() { + const abilities = useAgencyAbilities(); + const { showToast } = useToast(); + const { loadingRegions, regionOptions } = useRegionOptions(); + const [query, setQuery] = useState(""); + const [status, setStatus] = useState(""); + const [regionId, setRegionId] = useState(""); + const [parentBdUserId, setParentBdUserId] = useState(""); + const [page, setPage] = useState(1); + const [agencyForm, setAgencyForm] = useState(emptyAgencyForm); + const [closeForm, setCloseForm] = useState(emptyCloseForm); + const [joinEnabledForm, setJoinEnabledForm] = useState(emptyJoinEnabledForm); + const [loadingAction, setLoadingAction] = useState(""); + const [activeAction, setActiveAction] = useState(""); + const filters = useMemo( + () => ({ + keyword: query, + parent_bd_user_id: parentBdUserId, + region_id: regionId, + status + }), + [parentBdUserId, query, regionId, status] + ); + const { data = emptyData, error, loading, reload } = usePaginatedQuery({ + errorMessage: "加载 Agency 列表失败", + fetcher: listAgencies, + filters, + page, + pageSize, + queryKey: ["host-org", "agencies", filters, page] + }); + + const changeQuery = (value) => { + setQuery(value); + setPage(1); + }; + + const changeStatus = (value) => { + setStatus(value); + setPage(1); + }; + + const changeRegionId = (value) => { + setRegionId(value); + setPage(1); + }; + + const changeParentBdUserId = (value) => { + setParentBdUserId(value); + setPage(1); + }; + + const submitAgency = async (event) => { + event.preventDefault(); + await runAction("agency", "Agency 已创建", async () => { + const payload = parseForm(createAgencySchema, agencyForm); + const data = await createAgency(payload); + setAgencyForm(emptyAgencyForm()); + setActiveAction(""); + await reload(); + return data; + }); + }; + + const submitClose = async (event) => { + event.preventDefault(); + await runAction("agency-close", "Agency 已关闭", async () => { + const payload = parseForm(agencyCloseSchema, closeForm); + const { agencyId, ...body } = payload; + const data = await closeAgency(agencyId, body); + setCloseForm(emptyCloseForm()); + setActiveAction(""); + await reload(); + return data; + }); + }; + + const submitJoinEnabled = async (event) => { + event.preventDefault(); + await runAction("agency-join", "入会开关已更新", async () => { + const payload = parseForm(agencyJoinEnabledSchema, joinEnabledForm); + const { agencyId, ...body } = payload; + const data = await setAgencyJoinEnabled(agencyId, body); + setJoinEnabledForm(emptyJoinEnabledForm()); + setActiveAction(""); + await reload(); + return data; + }); + }; + + const runAction = async (action, successMessage, submitter) => { + setLoadingAction(action); + try { + await submitter(); + showToast(successMessage, "success"); + } catch (err) { + showToast(err.message || "操作失败", "error"); + } finally { + setLoadingAction(""); + } + }; + + return { + abilities, + activeAction, + agencyForm, + changeParentBdUserId, + changeQuery, + changeRegionId, + changeStatus, + closeForm, + data, + error, + joinEnabledForm, + loading, + loadingAction, + loadingRegions, + page, + parentBdUserId, + query, + regionId, + regionOptions, + reload, + closeAction: () => setActiveAction(""), + openAgencyForm: () => setActiveAction("agency"), + openCloseForm: () => setActiveAction("agency-close"), + openJoinEnabledForm: () => setActiveAction("agency-join"), + setAgencyForm, + setCloseForm, + setJoinEnabledForm, + setPage, + status, + submitAgency, + submitClose, + submitJoinEnabled + }; +} + +function makeCommandId(prefix) { + return `${prefix}-${Date.now()}`; +} diff --git a/src/features/host-org/hooks/useHostBdsPage.js b/src/features/host-org/hooks/useHostBdsPage.js new file mode 100644 index 0000000..dfe3371 --- /dev/null +++ b/src/features/host-org/hooks/useHostBdsPage.js @@ -0,0 +1,166 @@ +import { useMemo, useState } from "react"; +import { parseForm } from "@/shared/forms/validation"; +import { usePaginatedQuery } from "@/shared/hooks/usePaginatedQuery.js"; +import { useToast } from "@/shared/ui/ToastProvider.jsx"; +import { + createBD, + createBDLeader, + listBDLeaders, + listBDs, + setBDLeaderStatus, + setBDStatus +} from "@/features/host-org/api"; +import { useRegionOptions } from "@/shared/hooks/useRegionOptions.js"; +import { useBDAbilities } from "@/features/host-org/permissions.js"; +import { bdStatusSchema, createBDSchema, createBDLeaderSchema } from "@/features/host-org/schema"; + +const emptyBDLeaderForm = () => ({ commandId: makeCommandId("bd-leader"), reason: "", regionId: "", targetUserId: "" }); +const emptyBDForm = () => ({ commandId: makeCommandId("bd"), parentLeaderUserId: "", reason: "", targetUserId: "" }); +const emptyBDStatusForm = (targetType = "bd") => ({ commandId: makeCommandId("bd-status"), reason: "", status: "active", targetType, targetUserId: "" }); +const pageSize = 10; +const emptyData = { items: [], page: 1, pageSize, total: 0 }; + +export function useHostBdsPage({ profileType = "bd" } = {}) { + const abilities = useBDAbilities(); + const { showToast } = useToast(); + const { loadingRegions, regionOptions } = useRegionOptions(); + const [query, setQuery] = useState(""); + const [status, setStatus] = useState(""); + const [regionId, setRegionId] = useState(""); + const [parentLeaderUserId, setParentLeaderUserId] = useState(""); + const [page, setPage] = useState(1); + const [bdLeaderForm, setBDLeaderForm] = useState(emptyBDLeaderForm); + const [bdForm, setBDForm] = useState(emptyBDForm); + const [bdStatusForm, setBDStatusForm] = useState(() => emptyBDStatusForm(profileType)); + const [loadingAction, setLoadingAction] = useState(""); + const [activeAction, setActiveAction] = useState(""); + const filters = useMemo( + () => ({ + keyword: query, + parent_leader_user_id: profileType === "bd" ? parentLeaderUserId : "", + region_id: regionId, + status + }), + [parentLeaderUserId, profileType, query, regionId, status] + ); + const fetcher = profileType === "bd" ? listBDs : listBDLeaders; + const { data = emptyData, error, loading, reload } = usePaginatedQuery({ + errorMessage: profileType === "leader" ? "加载 BD Leader 列表失败" : "加载 BD 列表失败", + fetcher, + filters, + page, + pageSize, + queryKey: ["host-org", "bds", profileType, filters, page] + }); + + const changeQuery = (value) => { + setQuery(value); + setPage(1); + }; + + const changeStatus = (value) => { + setStatus(value); + setPage(1); + }; + + const changeRegionId = (value) => { + setRegionId(value); + setPage(1); + }; + + const changeParentLeaderUserId = (value) => { + setParentLeaderUserId(value); + setPage(1); + }; + + const submitBDLeader = async (event) => { + event.preventDefault(); + await runAction("bd-leader", "BD Leader 已创建", async () => { + const payload = parseForm(createBDLeaderSchema, bdLeaderForm); + const data = await createBDLeader(payload); + setBDLeaderForm(emptyBDLeaderForm()); + setActiveAction(""); + await reload(); + return data; + }); + }; + + const submitBD = async (event) => { + event.preventDefault(); + await runAction("bd", "BD 已创建", async () => { + const payload = parseForm(createBDSchema, bdForm); + const data = await createBD(payload); + setBDForm(emptyBDForm()); + setActiveAction(""); + await reload(); + return data; + }); + }; + + const submitBDStatus = async (event) => { + event.preventDefault(); + await runAction("bd-status", profileType === "leader" ? "BD Leader 状态已更新" : "BD 状态已更新", async () => { + const payload = parseForm(bdStatusSchema, { ...bdStatusForm, targetType: profileType }); + const { targetType, targetUserId, ...body } = payload; + const data = targetType === "leader" + ? await setBDLeaderStatus(targetUserId, body) + : await setBDStatus(targetUserId, body); + setBDStatusForm(emptyBDStatusForm(profileType)); + setActiveAction(""); + await reload(); + return data; + }); + }; + + const runAction = async (action, successMessage, submitter) => { + setLoadingAction(action); + try { + await submitter(); + showToast(successMessage, "success"); + } catch (err) { + showToast(err.message || "操作失败", "error"); + } finally { + setLoadingAction(""); + } + }; + + return { + abilities, + activeAction, + bdForm, + bdLeaderForm, + bdStatusForm, + changeParentLeaderUserId, + changeQuery, + changeRegionId, + changeStatus, + data, + error, + loading, + loadingAction, + loadingRegions, + page, + parentLeaderUserId, + profileType, + query, + regionId, + regionOptions, + reload, + closeAction: () => setActiveAction(""), + openBDForm: () => setActiveAction("bd"), + openBDLeaderForm: () => setActiveAction("bd-leader"), + openBDStatusForm: () => setActiveAction("bd-status"), + setBDForm, + setBDLeaderForm, + setBDStatusForm, + setPage, + status, + submitBD, + submitBDLeader, + submitBDStatus + }; +} + +function makeCommandId(prefix) { + return `${prefix}-${Date.now()}`; +} diff --git a/src/features/host-org/hooks/useHostCoinSellersPage.js b/src/features/host-org/hooks/useHostCoinSellersPage.js new file mode 100644 index 0000000..a633ba5 --- /dev/null +++ b/src/features/host-org/hooks/useHostCoinSellersPage.js @@ -0,0 +1,198 @@ +import { useMemo, useState } from "react"; +import { parseForm } from "@/shared/forms/validation"; +import { usePaginatedQuery } from "@/shared/hooks/usePaginatedQuery.js"; +import { useRegionOptions } from "@/shared/hooks/useRegionOptions.js"; +import { useConfirm } from "@/shared/ui/ConfirmProvider.jsx"; +import { useToast } from "@/shared/ui/ToastProvider.jsx"; +import { createCoinSeller, listCoinSellers, setCoinSellerStatus } from "@/features/host-org/api"; +import { useCoinSellerAbilities } from "@/features/host-org/permissions.js"; +import { coinSellerStatusSchema, createCoinSellerSchema } from "@/features/host-org/schema"; + +const pageSize = 10; +const emptyData = { items: [], page: 1, pageSize, total: 0 }; +const emptyCreateForm = () => ({ commandId: makeCommandId("coin-seller"), reason: "", targetUserId: "" }); +const emptyEditForm = () => ({ commandId: makeCommandId("coin-seller-status"), reason: "", status: "active", targetUserId: "" }); + +export function useHostCoinSellersPage() { + const abilities = useCoinSellerAbilities(); + const confirm = useConfirm(); + const { showToast } = useToast(); + const { loadingRegions, regionOptions } = useRegionOptions(); + const [query, setQuery] = useState(""); + const [status, setStatus] = useState(""); + const [regionId, setRegionId] = useState(""); + const [page, setPage] = useState(1); + const [activeAction, setActiveAction] = useState(""); + const [createForm, setCreateForm] = useState(emptyCreateForm); + const [editForm, setEditForm] = useState(emptyEditForm); + const [selectedSeller, setSelectedSeller] = useState(null); + const [loadingAction, setLoadingAction] = useState(""); + const filters = useMemo( + () => ({ + keyword: query, + region_id: regionId, + status + }), + [query, regionId, status] + ); + const { data = emptyData, error, loading, reload } = usePaginatedQuery({ + errorMessage: "加载币商列表失败", + fetcher: listCoinSellers, + filters, + page, + pageSize, + queryKey: ["host-org", "coin-sellers", filters, page] + }); + + const changeQuery = (value) => { + setQuery(value); + setPage(1); + }; + + const changeStatus = (value) => { + setStatus(value); + setPage(1); + }; + + const changeRegionId = (value) => { + setRegionId(value); + setPage(1); + }; + + const openCreateSeller = () => { + setCreateForm(emptyCreateForm()); + setSelectedSeller(null); + setActiveAction("create"); + }; + + const openEditSeller = (seller) => { + if (!seller?.userId) { + return; + } + setSelectedSeller(seller); + setEditForm({ + commandId: makeCommandId("coin-seller-status"), + reason: "", + status: seller.status || "active", + targetUserId: seller.userId + }); + setActiveAction("edit"); + }; + + const closeAction = () => { + setActiveAction(""); + setSelectedSeller(null); + }; + + const submitCreateSeller = async (event) => { + event.preventDefault(); + await runAction("coin-seller-create", "币商已添加", async () => { + const payload = parseForm(createCoinSellerSchema, createForm); + await createCoinSeller(payload); + setCreateForm(emptyCreateForm()); + closeAction(); + await reload(); + }); + }; + + const submitEditSeller = async (event) => { + event.preventDefault(); + await runAction(`coin-seller-edit-${editForm.targetUserId || selectedSeller?.userId || ""}`, "币商已更新", async () => { + const payload = parseForm(coinSellerStatusSchema, editForm); + const { targetUserId, ...body } = payload; + await setCoinSellerStatus(targetUserId, body); + setEditForm(emptyEditForm()); + closeAction(); + await reload(); + }); + }; + + const toggleSeller = async (seller, nextEnabled = seller.status !== "active") => { + if (!abilities.canUpdate || !seller?.userId) { + return; + } + const nextStatus = nextEnabled ? "active" : "disabled"; + if (seller.status === nextStatus) { + return; + } + await runAction(`coin-seller-status-${seller.userId}`, nextEnabled ? "币商已启用" : "币商已停用", async () => { + await setCoinSellerStatus(seller.userId, { + commandId: makeCommandId("coin-seller-status"), + reason: "", + status: nextStatus + }); + await reload(); + }); + }; + + const deleteSeller = async (seller) => { + if (!abilities.canUpdate || !seller?.userId) { + return; + } + const ok = await confirm({ + confirmText: "删除", + message: seller.username || seller.displayUserId || String(seller.userId), + title: "删除币商", + tone: "danger" + }); + if (!ok) { + return; + } + await runAction(`coin-seller-delete-${seller.userId}`, "币商已删除", async () => { + await setCoinSellerStatus(seller.userId, { + commandId: makeCommandId("coin-seller-delete"), + reason: "delete coin seller", + status: "disabled" + }); + await reload(); + }); + }; + + const runAction = async (action, successMessage, submitter) => { + setLoadingAction(action); + try { + await submitter(); + showToast(successMessage, "success"); + } catch (err) { + showToast(err.message || "操作失败", "error"); + } finally { + setLoadingAction(""); + } + }; + + return { + abilities, + activeAction, + changeQuery, + changeRegionId, + changeStatus, + closeAction, + createForm, + data, + deleteSeller, + editForm, + error, + loading, + loadingAction, + loadingRegions, + openCreateSeller, + openEditSeller, + page, + query, + regionId, + regionOptions, + reload, + selectedSeller, + setCreateForm, + setEditForm, + setPage, + status, + submitCreateSeller, + submitEditSeller, + toggleSeller + }; +} + +function makeCommandId(prefix) { + return `${prefix}-${Date.now()}`; +} diff --git a/src/features/host-org/hooks/useHostCountriesPage.js b/src/features/host-org/hooks/useHostCountriesPage.js new file mode 100644 index 0000000..9018c27 --- /dev/null +++ b/src/features/host-org/hooks/useHostCountriesPage.js @@ -0,0 +1,161 @@ +import { useCallback, useMemo, useState } from "react"; +import { parseForm } from "@/shared/forms/validation"; +import { useAdminQuery } from "@/shared/hooks/useAdminQuery.js"; +import { useToast } from "@/shared/ui/ToastProvider.jsx"; +import { + createCountry, + disableCountry, + enableCountry, + listCountries, + updateCountry +} from "@/features/host-org/api"; +import { useCountryAbilities } from "@/features/host-org/permissions.js"; +import { countryCreateSchema, countryUpdateSchema } from "@/features/host-org/schema"; + +const emptyCountryForm = () => ({ + countryCode: "", + countryDisplayName: "", + countryName: "", + enabled: true, + flag: "", + isoAlpha3: "", + isoNumeric: "", + phoneCountryCode: "", + sortOrder: 0 +}); +const emptyData = { items: [], total: 0 }; + +export function useHostCountriesPage() { + const abilities = useCountryAbilities(); + const { showToast } = useToast(); + const [query, setQuery] = useState(""); + const [enabledStatus, setEnabledStatus] = useState(""); + const [countryForm, setCountryForm] = useState(emptyCountryForm); + const [editingCountry, setEditingCountry] = useState(null); + const [loadingAction, setLoadingAction] = useState(""); + const [activeAction, setActiveAction] = useState(""); + const filters = useMemo( + () => ({ + enabled: enabledStatus === "enabled" ? true : enabledStatus === "disabled" ? false : undefined + }), + [enabledStatus] + ); + const queryFn = useCallback(() => listCountries(filters), [filters]); + const { data = emptyData, error, loading, reload } = useAdminQuery(queryFn, { + errorMessage: "加载国家列表失败", + initialData: emptyData, + keepPreviousData: true, + queryKey: ["host-org", "countries", filters] + }); + const items = useMemo(() => filterCountries(data?.items || [], query), [data?.items, query]); + + const changeQuery = (value) => { + setQuery(value); + }; + + const changeEnabledStatus = (value) => { + setEnabledStatus(value); + }; + + const openCreateCountry = () => { + setEditingCountry(null); + setCountryForm(emptyCountryForm()); + setActiveAction("create"); + }; + + const openEditCountry = (country) => { + setEditingCountry(country); + setCountryForm({ + countryCode: country.countryCode || "", + countryDisplayName: country.countryDisplayName || "", + countryName: country.countryName || "", + enabled: Boolean(country.enabled), + flag: country.flag || "", + isoAlpha3: country.isoAlpha3 || "", + isoNumeric: country.isoNumeric || "", + phoneCountryCode: country.phoneCountryCode || "", + sortOrder: country.sortOrder || 0 + }); + setActiveAction("edit"); + }; + + const submitCountry = async (event) => { + event.preventDefault(); + const isEdit = activeAction === "edit"; + await runAction(isEdit ? "country-edit" : "country-create", isEdit ? "国家已更新" : "国家已创建", async () => { + const payload = parseForm(isEdit ? countryUpdateSchema : countryCreateSchema, countryForm); + const data = isEdit + ? await updateCountry(editingCountry.countryId, payload) + : await createCountry(payload); + setCountryForm(emptyCountryForm()); + setEditingCountry(null); + setActiveAction(""); + await reload(); + return data; + }); + }; + + const toggleCountry = async (country, nextEnabled = !country.enabled) => { + if (!abilities.canStatus || Boolean(country.enabled) === nextEnabled) { + return; + } + await runAction(`country-status-${country.countryId}`, nextEnabled ? "国家已启用" : "国家已禁用", async () => { + const data = nextEnabled ? await enableCountry(country.countryId) : await disableCountry(country.countryId); + await reload(); + return data; + }); + }; + + const runAction = async (action, successMessage, submitter) => { + setLoadingAction(action); + try { + await submitter(); + showToast(successMessage, "success"); + } catch (err) { + showToast(err.message || "操作失败", "error"); + } finally { + setLoadingAction(""); + } + }; + + return { + abilities, + activeAction, + changeEnabledStatus, + changeQuery, + closeAction: () => setActiveAction(""), + countryForm, + data: { ...data, items, total: items.length }, + editingCountry, + enabledStatus, + error, + loading, + loadingAction, + openCreateCountry, + openEditCountry, + query, + reload, + setCountryForm, + submitCountry, + toggleCountry + }; +} + +function filterCountries(items, query) { + const keyword = query.trim().toLowerCase(); + if (!keyword) { + return items; + } + return items.filter((item) => { + return [ + item.countryCode, + item.countryDisplayName, + item.countryName, + item.isoAlpha3, + item.isoNumeric, + item.phoneCountryCode + ] + .filter(Boolean) + .some((value) => String(value).toLowerCase().includes(keyword)); + }); +} diff --git a/src/features/host-org/hooks/useHostHostsPage.js b/src/features/host-org/hooks/useHostHostsPage.js new file mode 100644 index 0000000..88e8e10 --- /dev/null +++ b/src/features/host-org/hooks/useHostHostsPage.js @@ -0,0 +1,75 @@ +import { useMemo, useState } from "react"; +import { usePaginatedQuery } from "@/shared/hooks/usePaginatedQuery.js"; +import { listHosts } from "@/features/host-org/api"; +import { useRegionOptions } from "@/shared/hooks/useRegionOptions.js"; +import { useHostAbilities } from "@/features/host-org/permissions.js"; + +const pageSize = 10; +const emptyData = { items: [], page: 1, pageSize, total: 0 }; + +export function useHostHostsPage() { + const abilities = useHostAbilities(); + const { loadingRegions, regionOptions } = useRegionOptions(); + const [query, setQuery] = useState(""); + const [status, setStatus] = useState(""); + const [regionId, setRegionId] = useState(""); + const [agencyId, setAgencyId] = useState(""); + const [page, setPage] = useState(1); + const filters = useMemo( + () => ({ + agency_id: agencyId, + keyword: query, + region_id: regionId, + status + }), + [agencyId, query, regionId, status] + ); + const { data = emptyData, error, loading, reload } = usePaginatedQuery({ + errorMessage: "加载主播列表失败", + fetcher: listHosts, + filters, + page, + pageSize, + queryKey: ["host-org", "hosts", filters, page] + }); + + const changeQuery = (value) => { + setQuery(value); + setPage(1); + }; + + const changeStatus = (value) => { + setStatus(value); + setPage(1); + }; + + const changeRegionId = (value) => { + setRegionId(value); + setPage(1); + }; + + const changeAgencyId = (value) => { + setAgencyId(value); + setPage(1); + }; + + return { + abilities, + agencyId, + changeAgencyId, + changeQuery, + changeRegionId, + changeStatus, + data, + error, + loading, + loadingRegions, + page, + query, + regionId, + regionOptions, + reload, + setPage, + status + }; +} diff --git a/src/features/host-org/hooks/useHostRegionsPage.js b/src/features/host-org/hooks/useHostRegionsPage.js new file mode 100644 index 0000000..d43c577 --- /dev/null +++ b/src/features/host-org/hooks/useHostRegionsPage.js @@ -0,0 +1,175 @@ +import { useCallback, useMemo, useState } from "react"; +import { parseForm } from "@/shared/forms/validation"; +import { useAdminQuery } from "@/shared/hooks/useAdminQuery.js"; +import { useToast } from "@/shared/ui/ToastProvider.jsx"; +import { + createRegion, + disableRegion, + enableRegion, + listCountries, + listRegions, + replaceRegionCountries, + updateRegion +} from "@/features/host-org/api"; +import { useRegionAbilities } from "@/features/host-org/permissions.js"; +import { regionCreateSchema, regionUpdateSchema } from "@/features/host-org/schema"; + +const emptyRegionForm = () => ({ + countries: [], + name: "", + regionCode: "", + sortOrder: 0 +}); +const emptyData = { items: [], total: 0 }; + +export function useHostRegionsPage() { + const abilities = useRegionAbilities(); + const { showToast } = useToast(); + const [query, setQuery] = useState(""); + const [status, setStatus] = useState(""); + const [regionForm, setRegionForm] = useState(emptyRegionForm); + const [editingRegion, setEditingRegion] = useState(null); + const [loadingAction, setLoadingAction] = useState(""); + const [activeAction, setActiveAction] = useState(""); + const filters = useMemo(() => ({ status }), [status]); + const queryFn = useCallback(() => listRegions(filters), [filters]); + const { data = emptyData, error, loading, reload } = useAdminQuery(queryFn, { + errorMessage: "加载区域列表失败", + initialData: emptyData, + keepPreviousData: true, + queryKey: ["host-org", "regions", filters] + }); + const countryQueryFn = useCallback(() => listCountries(), []); + const { data: countriesData = emptyData, loading: loadingCountries } = useAdminQuery(countryQueryFn, { + errorMessage: "加载国家列表失败", + initialData: emptyData, + queryKey: ["host-org", "countries", "region-options"], + staleTime: 5 * 60 * 1000 + }); + const items = useMemo(() => filterRegions(data?.items || [], query), [data?.items, query]); + const countryOptions = useMemo(() => { + return [...new Set((countriesData?.items || []).map((country) => country.countryCode).filter(Boolean))].sort(); + }, [countriesData?.items]); + const countryLabels = useMemo(() => { + return (countriesData?.items || []).reduce((labels, country) => { + if (country.countryCode) { + labels[country.countryCode] = [country.countryCode, country.countryDisplayName || country.countryName].filter(Boolean).join(" · "); + } + return labels; + }, {}); + }, [countriesData?.items]); + + const changeQuery = (value) => { + setQuery(value); + }; + + const changeStatus = (value) => { + setStatus(value); + }; + + const openCreateRegion = () => { + setEditingRegion(null); + setRegionForm(emptyRegionForm()); + setActiveAction("create"); + }; + + const openEditRegion = (region) => { + setEditingRegion(region); + setRegionForm({ + countries: region.countries || [], + name: region.name || "", + regionCode: region.regionCode || "", + sortOrder: region.sortOrder || 0 + }); + setActiveAction("edit"); + }; + + const submitRegion = async (event) => { + event.preventDefault(); + const isEdit = activeAction === "edit"; + await runAction(isEdit ? "region-edit" : "region-create", isEdit ? "区域已更新" : "区域已创建", async () => { + const payload = parseForm(isEdit ? regionUpdateSchema : regionCreateSchema, regionForm); + const data = isEdit ? await updateExistingRegion(editingRegion, payload) : await createRegion(payload); + setRegionForm(emptyRegionForm()); + setEditingRegion(null); + setActiveAction(""); + await reload(); + return data; + }); + }; + + const toggleRegion = async (region, nextActive = region.status !== "active") => { + if (!abilities.canStatus || (region.status === "active") === nextActive) { + return; + } + await runAction(`region-status-${region.regionId}`, nextActive ? "区域已启用" : "区域已停用", async () => { + const data = nextActive ? await enableRegion(region.regionId) : await disableRegion(region.regionId); + await reload(); + return data; + }); + }; + + const runAction = async (action, successMessage, submitter) => { + setLoadingAction(action); + try { + await submitter(); + showToast(successMessage, "success"); + } catch (err) { + showToast(err.message || "操作失败", "error"); + } finally { + setLoadingAction(""); + } + }; + + return { + abilities, + activeAction, + changeQuery, + changeStatus, + closeAction: () => setActiveAction(""), + countryLabels, + countryOptions, + data: { ...data, items, total: items.length }, + editingRegion, + error, + loading, + loadingAction, + loadingCountries, + openCreateRegion, + openEditRegion, + query, + regionForm, + reload, + setRegionForm, + status, + submitRegion, + toggleRegion + }; +} + +async function updateExistingRegion(region, payload) { + const { countries, ...regionPayload } = payload; + const updatedRegion = await updateRegion(region.regionId, regionPayload); + if (hasCountryChanges(region.countries || [], countries || [])) { + return replaceRegionCountries(region.regionId, { countries }); + } + return updatedRegion; +} + +function hasCountryChanges(previousCountries, nextCountries) { + const previous = [...previousCountries].sort(); + const next = [...nextCountries].sort(); + return previous.length !== next.length || previous.some((country, index) => country !== next[index]); +} + +function filterRegions(items, query) { + const keyword = query.trim().toLowerCase(); + if (!keyword) { + return items; + } + return items.filter((item) => { + return [item.regionCode, item.name, ...(item.countries || [])] + .filter(Boolean) + .some((value) => String(value).toLowerCase().includes(keyword)); + }); +} diff --git a/src/features/host-org/host-org.module.css b/src/features/host-org/host-org.module.css new file mode 100644 index 0000000..25afe30 --- /dev/null +++ b/src/features/host-org/host-org.module.css @@ -0,0 +1,223 @@ +.root { + display: flex; + height: calc(100vh - var(--header-height) - var(--space-8) - var(--space-4)); + height: calc(100dvh - var(--header-height) - var(--space-8) - var(--space-4)); + min-height: 0; + flex-direction: column; +} + +.contentPanel { + display: flex; + flex: 1; + flex-direction: column; + min-height: 0; + border: 1px solid var(--border); + border-radius: var(--radius-card); + background: var(--bg-card); + overflow: hidden; +} + +.contentPanel :global(.data-state) { + flex: 1; + min-height: 0; + border: 0; + border-radius: 0; + background: transparent; +} + +.contentPanel :global(.table-frame) { + min-height: 0; + border: 0; + border-radius: 0; +} + +.contentPanel :global(.table-scroll) { + overflow: auto; +} + +.contentPanel :global(.admin-row--head) { + position: sticky; + top: 0; + z-index: 1; +} + +.toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-4); + padding: var(--space-4) var(--space-5); + border-bottom: 1px solid var(--border); +} + +.toolbarLeft { + display: flex; + min-width: 0; + flex: 1; + align-items: center; + gap: var(--space-3); +} + +.toolbarActions { + display: flex; + flex: 0 0 auto; + align-items: center; + gap: var(--space-2); +} + +.filters { + display: flex; + min-width: 0; + flex: 1; + align-items: center; + flex-wrap: wrap; + gap: var(--space-3); +} + +.search { + flex: 0 1 300px; +} + +.statusSelect { + flex: 0 0 150px; +} + +.filterFields { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: var(--space-3); +} + +.filterFields :global(.MuiTextField-root) { + width: 170px; +} + +.rowActions { + display: inline-flex; + align-items: center; + gap: var(--space-2); +} + +.countryList { + display: flex; + min-width: 0; + align-items: center; + flex-wrap: wrap; + gap: var(--space-2); +} + +.countryBadge { + display: inline-flex; + height: var(--status-height); + align-items: center; + border: 1px solid var(--border); + border-radius: var(--radius-pill); + background: var(--bg-input); + color: var(--text-secondary); + font-size: var(--admin-font-size); + font-weight: 650; + padding: 0 var(--space-2); +} + +.flagCell { + font-size: var(--admin-font-size); +} + +.listBlock { + display: flex; + flex: 1; + flex-direction: column; + min-height: 0; + background: var(--bg-card); +} + +.listBlock :global(.pagination-bar) { + margin: var(--space-3) var(--space-5) var(--space-4); +} + +.inlineValue { + color: var(--text-primary); + font-weight: 650; +} + +.sellerIdentity { + display: inline-flex; + min-width: 0; + align-items: center; + gap: var(--space-3); +} + +.sellerAvatar { + display: inline-flex; + width: 32px; + height: 32px; + flex: 0 0 auto; + align-items: center; + justify-content: center; + border: 1px solid var(--border); + border-radius: 50%; + background: var(--bg-input); + color: var(--text-secondary); + font-weight: 700; + object-fit: cover; +} + +.sellerText { + display: inline-flex; + min-width: 0; + flex-direction: column; + gap: 2px; +} + +.sellerName { + overflow: hidden; + color: var(--text-primary); + font-weight: 700; + text-overflow: ellipsis; + white-space: nowrap; +} + +.sellerMeta { + overflow: hidden; + color: var(--text-secondary); + font-size: var(--admin-font-size); + text-overflow: ellipsis; + white-space: nowrap; +} + +.positive { + color: var(--success); +} + +.negative { + color: var(--danger); +} + +@media (max-width: 760px) { + .toolbar, + .toolbarLeft, + .filters, + .filterFields { + align-items: stretch; + flex-direction: column; + } + + .toolbarActions { + align-self: flex-end; + } + + .search { + flex: 1 1 auto; + width: 100%; + } + + .statusSelect { + flex: 1 1 auto; + width: 100%; + } + + .filterFields :global(.MuiTextField-root) { + width: 100%; + } +} diff --git a/src/features/host-org/pages/HostAgenciesPage.jsx b/src/features/host-org/pages/HostAgenciesPage.jsx new file mode 100644 index 0000000..d33f8fd --- /dev/null +++ b/src/features/host-org/pages/HostAgenciesPage.jsx @@ -0,0 +1,142 @@ +import Add from "@mui/icons-material/Add"; +import BlockOutlined from "@mui/icons-material/BlockOutlined"; +import ToggleOnOutlined from "@mui/icons-material/ToggleOnOutlined"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import Switch from "@mui/material/Switch"; +import TextField from "@mui/material/TextField"; +import { formatMillis } from "@/shared/utils/time.js"; +import { DataState } from "@/shared/ui/DataState.jsx"; +import { PaginationBar } from "@/shared/ui/PaginationBar.jsx"; +import { agencyStatusFilters } from "@/features/host-org/constants.js"; +import { HostOrgActionModal } from "@/features/host-org/components/HostOrgActionModal.jsx"; +import { HostOrgStatus } from "@/features/host-org/components/HostOrgStatus.jsx"; +import { HostOrgTable } from "@/features/host-org/components/HostOrgTable.jsx"; +import { HostOrgToolbar } from "@/features/host-org/components/HostOrgToolbar.jsx"; +import { useHostAgenciesPage } from "@/features/host-org/hooks/useHostAgenciesPage.js"; +import styles from "@/features/host-org/host-org.module.css"; + +const agencyColumns = [ + { key: "agencyId", label: "Agency ID", width: "minmax(110px, 0.8fr)" }, + { key: "name", label: "名称", width: "minmax(160px, 1.2fr)" }, + { key: "ownerUserId", label: "Owner 用户 ID" }, + { key: "parentBdUserId", label: "上级 BD 用户 ID" }, + { key: "regionId", label: "区域 ID", width: "minmax(90px, 0.7fr)" }, + { key: "status", label: "状态", render: (item) => , width: "minmax(90px, 0.7fr)" }, + { + key: "joinEnabled", + label: "入会", + render: (item) => {item.joinEnabled ? "允许" : "禁止"}, + width: "minmax(80px, 0.6fr)" + }, + { key: "maxHosts", label: "最大主播数", width: "minmax(90px, 0.7fr)" }, + { key: "updatedAtMs", label: "更新时间", render: (item) => formatMillis(item.updatedAtMs), width: "minmax(170px, 1fr)" } +]; + +export function HostAgenciesPage() { + const page = useHostAgenciesPage(); + const createDisabled = !page.abilities.canCreate; + const statusDisabled = !page.abilities.canStatus; + const items = page.data.items || []; + const total = page.data.total || 0; + const toolbarActions = [ + page.abilities.canStatus + ? { icon: , label: "入会开关", onClick: page.openJoinEnabledForm } + : null, + page.abilities.canStatus + ? { icon: , label: "关闭 Agency", onClick: page.openCloseForm, tone: "danger" } + : null, + page.abilities.canCreate + ? { icon: , label: "创建 Agency", onClick: page.openAgencyForm, variant: "primary" } + : null + ].filter(Boolean); + return ( +
+
+ + + +
+ item.agencyId} + /> + {total > 0 ? : null} +
+
+
+ + + page.setAgencyForm({ ...page.agencyForm, name: event.target.value })} /> + page.setAgencyForm({ ...page.agencyForm, ownerUserId: event.target.value })} /> + page.setAgencyForm({ ...page.agencyForm, parentBdUserId: event.target.value })} /> + page.setAgencyForm({ ...page.agencyForm, maxHosts: event.target.value })} /> + page.setAgencyForm({ ...page.agencyForm, joinEnabled: event.target.checked })} />} + label={page.agencyForm.joinEnabled ? "允许入会" : "禁止入会"} + /> + page.setAgencyForm({ ...page.agencyForm, reason: event.target.value })} /> + + + + page.setJoinEnabledForm({ ...page.joinEnabledForm, agencyId: event.target.value })} /> + page.setJoinEnabledForm({ ...page.joinEnabledForm, joinEnabled: event.target.checked })} />} + label={page.joinEnabledForm.joinEnabled ? "允许入会" : "禁止入会"} + /> + page.setJoinEnabledForm({ ...page.joinEnabledForm, reason: event.target.value })} /> + + + + page.setCloseForm({ ...page.closeForm, agencyId: event.target.value })} /> + page.setCloseForm({ ...page.closeForm, reason: event.target.value })} /> + + +
+ ); +} diff --git a/src/features/host-org/pages/HostBdLeadersPage.jsx b/src/features/host-org/pages/HostBdLeadersPage.jsx new file mode 100644 index 0000000..c82276d --- /dev/null +++ b/src/features/host-org/pages/HostBdLeadersPage.jsx @@ -0,0 +1,103 @@ +import Add from "@mui/icons-material/Add"; +import SyncAltOutlined from "@mui/icons-material/SyncAltOutlined"; +import MenuItem from "@mui/material/MenuItem"; +import TextField from "@mui/material/TextField"; +import { formatMillis } from "@/shared/utils/time.js"; +import { DataState } from "@/shared/ui/DataState.jsx"; +import { PaginationBar } from "@/shared/ui/PaginationBar.jsx"; +import { bdStatusFilters } from "@/features/host-org/constants.js"; +import { HostOrgActionModal } from "@/features/host-org/components/HostOrgActionModal.jsx"; +import { HostOrgStatus } from "@/features/host-org/components/HostOrgStatus.jsx"; +import { HostOrgTable } from "@/features/host-org/components/HostOrgTable.jsx"; +import { HostOrgToolbar } from "@/features/host-org/components/HostOrgToolbar.jsx"; +import { RegionSelect } from "@/shared/ui/RegionSelect.jsx"; +import { useHostBdsPage } from "@/features/host-org/hooks/useHostBdsPage.js"; +import styles from "@/features/host-org/host-org.module.css"; + +const bdLeaderColumns = [ + { key: "userId", label: "用户 ID", width: "minmax(110px, 0.8fr)" }, + { key: "role", label: "角色", width: "minmax(110px, 0.8fr)" }, + { key: "regionId", label: "区域 ID", width: "minmax(90px, 0.7fr)" }, + { key: "status", label: "状态", render: (item) => , width: "minmax(90px, 0.7fr)" }, + { key: "createdByUserId", label: "创建人用户 ID" }, + { key: "createdAtMs", label: "创建时间", render: (item) => formatMillis(item.createdAtMs), width: "minmax(170px, 1fr)" }, + { key: "updatedAtMs", label: "更新时间", render: (item) => formatMillis(item.updatedAtMs), width: "minmax(170px, 1fr)" } +]; + +export function HostBdLeadersPage() { + const page = useHostBdsPage({ profileType: "leader" }); + const createDisabled = !page.abilities.canCreate; + const updateDisabled = !page.abilities.canUpdate; + const items = page.data.items || []; + const total = page.data.total || 0; + const toolbarActions = [ + page.abilities.canUpdate + ? { icon: , label: "BD Leader 状态", onClick: page.openBDStatusForm } + : null, + page.abilities.canCreate + ? { icon: , label: "创建 BD Leader", onClick: page.openBDLeaderForm, variant: "primary" } + : null + ].filter(Boolean); + return ( +
+
+ + + +
+ `bd-leader-${item.userId}`} + /> + {total > 0 ? : null} +
+
+
+ + + page.setBDLeaderForm({ ...page.bdLeaderForm, targetUserId: event.target.value })} /> + page.setBDLeaderForm({ ...page.bdLeaderForm, regionId: value })} /> + page.setBDLeaderForm({ ...page.bdLeaderForm, reason: event.target.value })} /> + + + + page.setBDStatusForm({ ...page.bdStatusForm, targetUserId: event.target.value })} /> + page.setBDStatusForm({ ...page.bdStatusForm, status: event.target.value })}> + 启用 + 停用 + + page.setBDStatusForm({ ...page.bdStatusForm, reason: event.target.value })} /> + + +
+ ); +} diff --git a/src/features/host-org/pages/HostBdsPage.jsx b/src/features/host-org/pages/HostBdsPage.jsx new file mode 100644 index 0000000..4b9a680 --- /dev/null +++ b/src/features/host-org/pages/HostBdsPage.jsx @@ -0,0 +1,111 @@ +import Add from "@mui/icons-material/Add"; +import SyncAltOutlined from "@mui/icons-material/SyncAltOutlined"; +import MenuItem from "@mui/material/MenuItem"; +import TextField from "@mui/material/TextField"; +import { formatMillis } from "@/shared/utils/time.js"; +import { DataState } from "@/shared/ui/DataState.jsx"; +import { PaginationBar } from "@/shared/ui/PaginationBar.jsx"; +import { bdStatusFilters } from "@/features/host-org/constants.js"; +import { HostOrgActionModal } from "@/features/host-org/components/HostOrgActionModal.jsx"; +import { HostOrgStatus } from "@/features/host-org/components/HostOrgStatus.jsx"; +import { HostOrgTable } from "@/features/host-org/components/HostOrgTable.jsx"; +import { HostOrgToolbar } from "@/features/host-org/components/HostOrgToolbar.jsx"; +import { useHostBdsPage } from "@/features/host-org/hooks/useHostBdsPage.js"; +import styles from "@/features/host-org/host-org.module.css"; + +const bdColumns = [ + { key: "userId", label: "用户 ID", width: "minmax(110px, 0.8fr)" }, + { key: "role", label: "角色", width: "minmax(110px, 0.8fr)" }, + { key: "parentLeaderUserId", label: "上级 Leader 用户 ID", render: (item) => item.parentLeaderUserId || "-" }, + { key: "regionId", label: "区域 ID", width: "minmax(90px, 0.7fr)" }, + { key: "status", label: "状态", render: (item) => , width: "minmax(90px, 0.7fr)" }, + { key: "createdByUserId", label: "创建人用户 ID" }, + { key: "createdAtMs", label: "创建时间", render: (item) => formatMillis(item.createdAtMs), width: "minmax(170px, 1fr)" }, + { key: "updatedAtMs", label: "更新时间", render: (item) => formatMillis(item.updatedAtMs), width: "minmax(170px, 1fr)" } +]; + +export function HostBdsPage() { + const page = useHostBdsPage({ profileType: "bd" }); + const createDisabled = !page.abilities.canCreate; + const updateDisabled = !page.abilities.canUpdate; + const items = page.data.items || []; + const total = page.data.total || 0; + const toolbarActions = [ + page.abilities.canUpdate + ? { icon: , label: "BD 状态", onClick: page.openBDStatusForm } + : null, + page.abilities.canCreate + ? { icon: , label: "创建 BD", onClick: page.openBDForm, variant: "primary" } + : null + ].filter(Boolean); + return ( +
+
+ + + +
+ `bd-${item.userId}`} + /> + {total > 0 ? : null} +
+
+
+ + + page.setBDForm({ ...page.bdForm, targetUserId: event.target.value })} /> + page.setBDForm({ ...page.bdForm, parentLeaderUserId: event.target.value })} /> + page.setBDForm({ ...page.bdForm, reason: event.target.value })} /> + + + + page.setBDStatusForm({ ...page.bdStatusForm, targetUserId: event.target.value })} /> + page.setBDStatusForm({ ...page.bdStatusForm, status: event.target.value })}> + 启用 + 停用 + + page.setBDStatusForm({ ...page.bdStatusForm, reason: event.target.value })} /> + + +
+ ); +} diff --git a/src/features/host-org/pages/HostCoinSellersPage.jsx b/src/features/host-org/pages/HostCoinSellersPage.jsx new file mode 100644 index 0000000..f111219 --- /dev/null +++ b/src/features/host-org/pages/HostCoinSellersPage.jsx @@ -0,0 +1,169 @@ +import Add from "@mui/icons-material/Add"; +import DeleteOutlineOutlined from "@mui/icons-material/DeleteOutlineOutlined"; +import EditOutlined from "@mui/icons-material/EditOutlined"; +import MenuItem from "@mui/material/MenuItem"; +import Switch from "@mui/material/Switch"; +import TextField from "@mui/material/TextField"; +import { formatMillis } from "@/shared/utils/time.js"; +import { DataState } from "@/shared/ui/DataState.jsx"; +import { PaginationBar } from "@/shared/ui/PaginationBar.jsx"; +import { AdminActionIconButton, AdminRowActions } from "@/shared/ui/AdminListLayout.jsx"; +import { coinSellerStatusFilters } from "@/features/host-org/constants.js"; +import { HostOrgActionModal } from "@/features/host-org/components/HostOrgActionModal.jsx"; +import { HostOrgTable } from "@/features/host-org/components/HostOrgTable.jsx"; +import { HostOrgToolbar } from "@/features/host-org/components/HostOrgToolbar.jsx"; +import { useHostCoinSellersPage } from "@/features/host-org/hooks/useHostCoinSellersPage.js"; +import styles from "@/features/host-org/host-org.module.css"; + +const baseColumns = [ + { + key: "seller", + label: "币商", + render: (item) => , + width: "minmax(220px, 1.3fr)" + }, + { key: "regionId", label: "区域", render: (item, _index, context) => regionName(item, context?.regionOptions), width: "minmax(140px, 0.8fr)" }, + { key: "merchantAssetType", label: "资产类型", render: (item) => item.merchantAssetType || "-", width: "minmax(150px, 0.9fr)" }, + { key: "merchantBalance", label: "币商余额", render: (item) => formatNumber(item.merchantBalance), width: "minmax(120px, 0.8fr)" }, + { key: "createdByUserId", label: "创建人 ID", render: (item) => item.createdByUserId || "-", width: "minmax(110px, 0.7fr)" }, + { key: "updatedAtMs", label: "更新时间", render: (item) => formatMillis(item.updatedAtMs), width: "minmax(170px, 1fr)" } +]; + +export function HostCoinSellersPage() { + const page = useHostCoinSellersPage(); + const items = page.data.items || []; + const total = page.data.total || 0; + const createDisabled = !page.abilities.canCreate; + const updateDisabled = !page.abilities.canUpdate; + const columns = [ + ...baseColumns.slice(0, 2), + { + key: "status", + label: "状态", + render: (item) => , + width: "minmax(90px, 0.6fr)" + }, + ...baseColumns.slice(2), + page.abilities.canUpdate + ? { + key: "actions", + label: "操作", + render: (item) => , + width: "minmax(120px, 0.8fr)" + } + : null + ].filter(Boolean); + const toolbarActions = page.abilities.canCreate + ? [{ icon: , label: "添加币商", onClick: page.openCreateSeller, variant: "primary" }] + : []; + + return ( +
+
+ + + +
+ item.userId} /> + {total > 0 ? : null} +
+
+
+ + + page.setCreateForm({ ...page.createForm, targetUserId: event.target.value })} /> + page.setCreateForm({ ...page.createForm, reason: event.target.value })} /> + + + + + page.setEditForm({ ...page.editForm, status: event.target.value })}> + 启用 + 停用 + + page.setEditForm({ ...page.editForm, reason: event.target.value })} /> + +
+ ); +} + +function SellerIdentity({ item }) { + const name = item.username || "-"; + const meta = item.displayUserId || item.userId; + + return ( +
+ {item.avatar ? : {name.slice(0, 1).toUpperCase()}} + + {name} + {meta} + +
+ ); +} + +function SellerStatusSwitch({ item, page }) { + return ( + page.toggleSeller(item, event.target.checked)} + /> + ); +} + +function SellerActions({ item, page }) { + const deleting = page.loadingAction === `coin-seller-delete-${item.userId}`; + + return ( + + page.openEditSeller(item)}> + + + page.deleteSeller(item)}> + + + + ); +} + +function regionName(item, regionOptions = []) { + if (item.regionName) { + return item.regionName; + } + const option = regionOptions.find((region) => String(region.value) === String(item.regionId)); + return option?.name || item.regionId || "-"; +} + +function formatNumber(value) { + return Number(value || 0).toLocaleString("zh-CN"); +} diff --git a/src/features/host-org/pages/HostCountriesPage.jsx b/src/features/host-org/pages/HostCountriesPage.jsx new file mode 100644 index 0000000..df868b0 --- /dev/null +++ b/src/features/host-org/pages/HostCountriesPage.jsx @@ -0,0 +1,143 @@ +import Add from "@mui/icons-material/Add"; +import EditOutlined from "@mui/icons-material/EditOutlined"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import Switch from "@mui/material/Switch"; +import TextField from "@mui/material/TextField"; +import { formatMillis } from "@/shared/utils/time.js"; +import { DataState } from "@/shared/ui/DataState.jsx"; +import { + AdminActionIconButton, + AdminFilterSelect, + AdminListBody, + AdminListPage, + AdminListToolbar, + AdminRowActions, + AdminSearchBox +} from "@/shared/ui/AdminListLayout.jsx"; +import { countryEnabledFilters } from "@/features/host-org/constants.js"; +import { HostOrgActionModal } from "@/features/host-org/components/HostOrgActionModal.jsx"; +import { HostOrgTable } from "@/features/host-org/components/HostOrgTable.jsx"; +import { useHostCountriesPage } from "@/features/host-org/hooks/useHostCountriesPage.js"; +import styles from "@/features/host-org/host-org.module.css"; + +const baseCountryColumns = [ + { key: "countryId", label: "国家 ID", width: "minmax(90px, 0.6fr)" }, + { key: "flag", label: "国旗", render: (item) => {item.flag || "-"}, width: "minmax(70px, 0.4fr)" }, + { key: "countryCode", label: "国家码", width: "minmax(90px, 0.7fr)" }, + { key: "countryDisplayName", label: "展示名称", width: "minmax(150px, 1fr)" }, + { key: "countryName", label: "国家名称", width: "minmax(150px, 1fr)" }, + { key: "phoneCountryCode", label: "电话区号", width: "minmax(90px, 0.7fr)" }, + { key: "sortOrder", label: "排序", width: "minmax(80px, 0.5fr)" }, + { key: "updatedAtMs", label: "更新时间", render: (item) => formatMillis(item.updatedAtMs), width: "minmax(170px, 1fr)" } +]; + +export function HostCountriesPage() { + const page = useHostCountriesPage(); + const createDisabled = !page.abilities.canCreate; + const updateDisabled = !page.abilities.canUpdate; + const items = page.data.items || []; + const total = page.data.total || 0; + const countryColumns = [ + ...baseCountryColumns.slice(0, 6), + { + key: "enabled", + label: "状态", + render: (item) => , + width: "minmax(90px, 0.6fr)" + }, + ...baseCountryColumns.slice(6) + ]; + const columns = page.abilities.canUpdate + ? [ + ...countryColumns, + { + key: "actions", + label: "操作", + render: (item) => , + width: "minmax(104px, 0.7fr)" + } + ] + : countryColumns; + return ( + + + + + + )} + actions={page.abilities.canCreate ? ( + + + + ) : null} + /> + + + + item.countryId} + /> +
+ {total} 条 +
+
+
+ + + page.setCountryForm({ ...page.countryForm, countryCode: event.target.value })} /> + page.setCountryForm({ ...page.countryForm, countryDisplayName: event.target.value })} /> + page.setCountryForm({ ...page.countryForm, countryName: event.target.value })} /> + page.setCountryForm({ ...page.countryForm, isoAlpha3: event.target.value })} /> + page.setCountryForm({ ...page.countryForm, isoNumeric: event.target.value })} /> + page.setCountryForm({ ...page.countryForm, phoneCountryCode: event.target.value })} /> + page.setCountryForm({ ...page.countryForm, flag: event.target.value })} /> + page.setCountryForm({ ...page.countryForm, sortOrder: event.target.value })} /> + {page.activeAction === "create" ? ( + page.setCountryForm({ ...page.countryForm, enabled: event.target.checked })} />} + label={page.countryForm.enabled ? "启用" : "停用"} + /> + ) : null} + + +
+ ); +} + +function CountryActions({ item, page }) { + return ( + + {page.abilities.canUpdate ? ( + page.openEditCountry(item)}> + + + ) : null} + + ); +} + +function CountryStatusSwitch({ item, page }) { + return ( + page.toggleCountry(item, event.target.checked)} + /> + ); +} diff --git a/src/features/host-org/pages/HostHostsPage.jsx b/src/features/host-org/pages/HostHostsPage.jsx new file mode 100644 index 0000000..8abadbe --- /dev/null +++ b/src/features/host-org/pages/HostHostsPage.jsx @@ -0,0 +1,66 @@ +import { formatMillis } from "@/shared/utils/time.js"; +import { DataState } from "@/shared/ui/DataState.jsx"; +import { PaginationBar } from "@/shared/ui/PaginationBar.jsx"; +import { hostSourceLabels, hostStatusFilters } from "@/features/host-org/constants.js"; +import { HostOrgStatus } from "@/features/host-org/components/HostOrgStatus.jsx"; +import { HostOrgTable } from "@/features/host-org/components/HostOrgTable.jsx"; +import { HostOrgToolbar } from "@/features/host-org/components/HostOrgToolbar.jsx"; +import { useHostHostsPage } from "@/features/host-org/hooks/useHostHostsPage.js"; +import styles from "@/features/host-org/host-org.module.css"; + +const hostColumns = [ + { key: "userId", label: "用户 ID", width: "minmax(110px, 0.8fr)" }, + { key: "status", label: "状态", render: (item) => , width: "minmax(90px, 0.7fr)" }, + { key: "regionId", label: "区域 ID", width: "minmax(90px, 0.7fr)" }, + { key: "currentAgencyId", label: "当前 Agency ID", render: (item) => item.currentAgencyId || "-" }, + { key: "currentMembershipId", label: "当前 Membership ID", render: (item) => item.currentMembershipId || "-" }, + { key: "source", label: "来源", render: (item) => hostSourceLabels[item.source] || item.source || "-", width: "minmax(150px, 1fr)" }, + { key: "firstBecameHostAtMs", label: "首次成为主播", render: (item) => formatMillis(item.firstBecameHostAtMs), width: "minmax(170px, 1fr)" }, + { key: "updatedAtMs", label: "更新时间", render: (item) => formatMillis(item.updatedAtMs), width: "minmax(170px, 1fr)" } +]; + +export function HostHostsPage() { + const page = useHostHostsPage(); + const items = page.data.items || []; + const total = page.data.total || 0; + + return ( +
+
+ + + +
+ item.userId} + /> + {total > 0 ? : null} +
+
+
+
+ ); +} diff --git a/src/features/host-org/pages/HostRegionsPage.jsx b/src/features/host-org/pages/HostRegionsPage.jsx new file mode 100644 index 0000000..8a2e8b6 --- /dev/null +++ b/src/features/host-org/pages/HostRegionsPage.jsx @@ -0,0 +1,194 @@ +import Add from "@mui/icons-material/Add"; +import EditOutlined from "@mui/icons-material/EditOutlined"; +import Autocomplete from "@mui/material/Autocomplete"; +import Switch from "@mui/material/Switch"; +import TextField from "@mui/material/TextField"; +import Tooltip from "@mui/material/Tooltip"; +import { formatMillis } from "@/shared/utils/time.js"; +import { DataState } from "@/shared/ui/DataState.jsx"; +import { IconButton } from "@/shared/ui/IconButton.jsx"; +import { SearchBox } from "@/shared/ui/SearchBox.jsx"; +import { StatusSelect } from "@/shared/ui/StatusSelect.jsx"; +import { regionStatusFilters } from "@/features/host-org/constants.js"; +import { HostOrgActionModal } from "@/features/host-org/components/HostOrgActionModal.jsx"; +import { HostOrgTable } from "@/features/host-org/components/HostOrgTable.jsx"; +import { useHostRegionsPage } from "@/features/host-org/hooks/useHostRegionsPage.js"; +import styles from "@/features/host-org/host-org.module.css"; + +const baseRegionColumns = [ + { key: "regionId", label: "区域 ID", width: "minmax(90px, 0.6fr)" }, + { key: "regionCode", label: "区域编码", width: "minmax(110px, 0.8fr)" }, + { key: "name", label: "区域名称", width: "minmax(150px, 1fr)" }, + { key: "countries", label: "国家码", render: (item) => , width: "minmax(220px, 1.5fr)" }, + { key: "sortOrder", label: "排序", width: "minmax(80px, 0.5fr)" }, + { key: "updatedAtMs", label: "更新时间", render: (item) => formatMillis(item.updatedAtMs), width: "minmax(170px, 1fr)" } +]; + +export function HostRegionsPage() { + const page = useHostRegionsPage(); + const createDisabled = !page.abilities.canCreate; + const updateDisabled = !page.abilities.canUpdate; + const items = page.data.items || []; + const total = page.data.total || 0; + const regionColumns = [ + ...baseRegionColumns.slice(0, 3), + { + key: "status", + label: "状态", + render: (item) => , + width: "minmax(90px, 0.6fr)" + }, + ...baseRegionColumns.slice(3) + ]; + const columns = page.abilities.canUpdate + ? [ + ...regionColumns, + { + key: "actions", + label: "操作", + render: (item) => , + width: "minmax(80px, 0.5fr)" + } + ] + : regionColumns; + return ( +
+
+
+
+
+ + +
+
+ + {page.abilities.canCreate ? ( +
+ + + + + + + +
+ ) : null} +
+ + +
+ item.regionId} + /> +
+ {total} 条 +
+
+
+
+ + + page.setRegionForm({ ...page.regionForm, regionCode: event.target.value })} /> + page.setRegionForm({ ...page.regionForm, name: event.target.value })} /> + page.setRegionForm({ ...page.regionForm, sortOrder: event.target.value })} /> + page.setRegionForm({ ...page.regionForm, countries })} + /> + + +
+ ); +} + +function RegionActions({ item, page }) { + return ( +
+ {page.abilities.canUpdate ? ( + + + page.openEditRegion(item)}> + + + + + ) : null} +
+ ); +} + +function RegionStatusSwitch({ item, page }) { + const checked = item.status === "active"; + return ( + page.toggleRegion(item, event.target.checked)} + /> + ); +} + +function CountryCodeSelect({ disabled, labels, loading, onChange, options, value }) { + const selected = value || []; + const mergedOptions = [...new Set([...options, ...selected])].sort(); + return ( + labels[option] || option} + loading={loading} + options={mergedOptions} + renderInput={(params) => } + size="small" + value={selected} + onChange={(_, countries) => onChange(countries)} + /> + ); +} + +function CountryCodes({ countries = [] }) { + if (!countries.length) { + return "-"; + } + return ( +
+ {countries.map((country) => ( + + {country} + + ))} +
+ ); +} + +const primaryActionSx = { + borderColor: "var(--primary)", + backgroundColor: "var(--primary)", + color: "var(--active-contrast)", + "&:hover": { + borderColor: "var(--primary-strong)", + backgroundColor: "var(--primary-strong)", + color: "var(--active-contrast)" + } +}; diff --git a/src/features/host-org/permissions.js b/src/features/host-org/permissions.js new file mode 100644 index 0000000..816809a --- /dev/null +++ b/src/features/host-org/permissions.js @@ -0,0 +1,62 @@ +import { useAuth } from "@/app/auth/AuthProvider.jsx"; +import { PERMISSIONS } from "@/app/permissions"; + +export function useCountryAbilities() { + const { can } = useAuth(); + + return { + canCreate: can(PERMISSIONS.countryCreate), + canStatus: can(PERMISSIONS.countryStatus), + canUpdate: can(PERMISSIONS.countryUpdate), + canView: can(PERMISSIONS.countryView) + }; +} + +export function useRegionAbilities() { + const { can } = useAuth(); + + return { + canCreate: can(PERMISSIONS.regionCreate), + canStatus: can(PERMISSIONS.regionStatus), + canUpdate: can(PERMISSIONS.regionUpdate), + canView: can(PERMISSIONS.regionView) + }; +} + +export function useAgencyAbilities() { + const { can } = useAuth(); + + return { + canCreate: can(PERMISSIONS.agencyCreate), + canStatus: can(PERMISSIONS.agencyStatus), + canView: can(PERMISSIONS.agencyView) + }; +} + +export function useHostAbilities() { + const { can } = useAuth(); + + return { + canView: can(PERMISSIONS.hostView) + }; +} + +export function useBDAbilities() { + const { can } = useAuth(); + + return { + canCreate: can(PERMISSIONS.bdCreate), + canUpdate: can(PERMISSIONS.bdUpdate), + canView: can(PERMISSIONS.bdView) + }; +} + +export function useCoinSellerAbilities() { + const { can } = useAuth(); + + return { + canCreate: can(PERMISSIONS.coinSellerCreate), + canUpdate: can(PERMISSIONS.coinSellerUpdate), + canView: can(PERMISSIONS.coinSellerView) + }; +} diff --git a/src/features/host-org/routes.js b/src/features/host-org/routes.js new file mode 100644 index 0000000..815ed7a --- /dev/null +++ b/src/features/host-org/routes.js @@ -0,0 +1,60 @@ +import { MENU_CODES, PERMISSIONS } from "@/app/permissions"; + +export const hostOrgRoutes = [ + { + label: "国家管理", + loader: () => import("./pages/HostCountriesPage.jsx").then((module) => module.HostCountriesPage), + menuCode: MENU_CODES.hostOrgCountries, + pageKey: "host-org-countries", + path: "/host/countries", + permission: PERMISSIONS.countryView + }, + { + label: "区域管理", + loader: () => import("./pages/HostRegionsPage.jsx").then((module) => module.HostRegionsPage), + menuCode: MENU_CODES.hostOrgRegions, + pageKey: "host-org-regions", + path: "/host/regions", + permission: PERMISSIONS.regionView + }, + { + label: "Agency 管理", + loader: () => import("./pages/HostAgenciesPage.jsx").then((module) => module.HostAgenciesPage), + menuCode: MENU_CODES.hostOrgAgencies, + pageKey: "host-org-agencies", + path: "/host/agencies", + permission: PERMISSIONS.agencyView + }, + { + label: "BD Leader 管理", + loader: () => import("./pages/HostBdLeadersPage.jsx").then((module) => module.HostBdLeadersPage), + menuCode: MENU_CODES.hostOrgBdLeaders, + pageKey: "host-org-bd-leaders", + path: "/host/bd-leaders", + permission: PERMISSIONS.bdView + }, + { + label: "BD 管理", + loader: () => import("./pages/HostBdsPage.jsx").then((module) => module.HostBdsPage), + menuCode: MENU_CODES.hostOrgBds, + pageKey: "host-org-bds", + path: "/host/bds", + permission: PERMISSIONS.bdView + }, + { + label: "主播管理", + loader: () => import("./pages/HostHostsPage.jsx").then((module) => module.HostHostsPage), + menuCode: MENU_CODES.hostOrgHosts, + pageKey: "host-org-hosts", + path: "/host/hosts", + permission: PERMISSIONS.hostView + }, + { + label: "币商管理", + loader: () => import("./pages/HostCoinSellersPage.jsx").then((module) => module.HostCoinSellersPage), + menuCode: MENU_CODES.hostOrgCoinSellers, + pageKey: "host-org-coin-sellers", + path: "/host/coin-sellers", + permission: PERMISSIONS.coinSellerView + } +]; diff --git a/src/features/host-org/schema.ts b/src/features/host-org/schema.ts new file mode 100644 index 0000000..cce29b9 --- /dev/null +++ b/src/features/host-org/schema.ts @@ -0,0 +1,121 @@ +import { z } from "zod"; + +const commandBaseSchema = z.object({ + commandId: z.string().trim().min(1, "请输入 Command ID").max(120, "Command ID 不能超过 120 个字符"), + reason: z.string().trim().max(200, "原因不能超过 200 个字符").optional().default("") +}); + +const commandContactSchema = z.object({ + commandId: z.string().trim().min(1, "请输入 Command ID").max(120, "Command ID 不能超过 120 个字符"), + reason: z.string().trim().max(200, "联系方式不能超过 200 个字符").optional().default("") +}); + +const userIdSchema = z.coerce.number().int().positive("请输入有效用户短 ID"); +const regionIdSchema = z.coerce.number().int().positive("请输入有效区域 ID"); +const sortOrderSchema = z.coerce.number().int().min(0, "排序不能小于 0").default(0); +const optionalTextSchema = (max: number, message: string) => z.string().trim().max(max, message).optional().default(""); +const codeSchema = (label: string) => z.string().trim().min(1, `请输入${label}`).max(40, `${label}不能超过 40 个字符`); +const countryCodesSchema = z.array(z.string()).transform((values, ctx) => { + const countries = values + .map((item) => item.trim().toUpperCase()) + .filter(Boolean); + if (!countries.length) { + ctx.addIssue({ code: "custom", message: "请选择国家码" }); + return countries; + } + if (new Set(countries).size !== countries.length) { + ctx.addIssue({ code: "custom", message: "国家码不能重复" }); + } + return countries; +}); + +export const createBDLeaderSchema = commandContactSchema.extend({ + regionId: regionIdSchema, + targetUserId: userIdSchema +}); + +export const createBDSchema = commandBaseSchema.extend({ + parentLeaderUserId: userIdSchema, + targetUserId: userIdSchema +}); + +export const bdStatusSchema = commandBaseSchema.extend({ + status: z.string().trim().min(1, "请输入状态").max(40, "状态不能超过 40 个字符"), + targetType: z.enum(["bd", "leader"]), + targetUserId: userIdSchema +}); + +export const createCoinSellerSchema = commandContactSchema.extend({ + targetUserId: userIdSchema +}); + +export const coinSellerStatusSchema = commandContactSchema.extend({ + status: z.enum(["active", "disabled"]), + targetUserId: userIdSchema +}); + +export const createAgencySchema = commandBaseSchema.extend({ + joinEnabled: z.boolean().default(true), + maxHosts: z.coerce.number().int().min(0, "最大主播数不能小于 0").default(0), + name: z.string().trim().min(1, "请输入 Agency 名称").max(80, "Agency 名称不能超过 80 个字符"), + ownerUserId: userIdSchema, + parentBdUserId: userIdSchema +}); + +export const agencyCloseSchema = commandBaseSchema.extend({ + agencyId: z.coerce.number().int().positive("请输入有效 Agency ID") +}); + +export const agencyJoinEnabledSchema = commandBaseSchema.extend({ + agencyId: z.coerce.number().int().positive("请输入有效 Agency ID"), + joinEnabled: z.boolean().default(true) +}); + +export const countryCreateSchema = z.object({ + countryCode: z.string().trim().regex(/^[A-Za-z]{2,3}$/, "国家码必须是 2-3 位字母").transform((value) => value.toUpperCase()), + countryDisplayName: z.string().trim().min(1, "请输入展示名称").max(80, "展示名称不能超过 80 个字符"), + countryName: z.string().trim().min(1, "请输入国家名称").max(80, "国家名称不能超过 80 个字符"), + enabled: z.boolean().default(true), + flag: optionalTextSchema(16, "国旗不能超过 16 个字符"), + isoAlpha3: optionalTextSchema(8, "ISO Alpha-3 不能超过 8 个字符").transform((value) => value.toUpperCase()), + isoNumeric: optionalTextSchema(8, "ISO Numeric 不能超过 8 个字符"), + phoneCountryCode: optionalTextSchema(16, "电话区号不能超过 16 个字符"), + sortOrder: sortOrderSchema +}); + +export const countryUpdateSchema = countryCreateSchema.omit({ + countryCode: true, + enabled: true +}); + +export const regionCreateSchema = z.object({ + countries: countryCodesSchema, + name: z.string().trim().min(1, "请输入区域名称").max(80, "区域名称不能超过 80 个字符"), + regionCode: codeSchema("区域编码").transform((value) => value.toUpperCase()), + sortOrder: sortOrderSchema +}); + +export const regionUpdateSchema = z.object({ + countries: countryCodesSchema, + name: z.string().trim().min(1, "请输入区域名称").max(80, "区域名称不能超过 80 个字符"), + regionCode: codeSchema("区域编码").transform((value) => value.toUpperCase()), + sortOrder: sortOrderSchema +}); + +export const regionCountriesSchema = z.object({ + countries: countryCodesSchema +}); + +export type CreateBDLeaderForm = z.infer; +export type CreateBDForm = z.infer; +export type BDStatusForm = z.infer; +export type CreateCoinSellerForm = z.infer; +export type CoinSellerStatusForm = z.infer; +export type CreateAgencyForm = z.infer; +export type AgencyCloseForm = z.infer; +export type AgencyJoinEnabledForm = z.infer; +export type CountryCreateForm = z.infer; +export type CountryUpdateForm = z.infer; +export type RegionCreateForm = z.infer; +export type RegionUpdateForm = z.infer; +export type RegionCountriesForm = z.infer; diff --git a/src/features/logs/api.ts b/src/features/logs/api.ts new file mode 100644 index 0000000..f680b98 --- /dev/null +++ b/src/features/logs/api.ts @@ -0,0 +1,19 @@ +import { apiRequest } from "@/shared/api/request"; +import { API_OPERATIONS, apiEndpointPath } from "@/shared/api/generated/endpoints"; +import type { ApiPage, LogDto, PageQuery } from "@/shared/api/types"; + +export function listLoginLogs(query?: PageQuery): Promise> { + return apiRequest>(apiEndpointPath(API_OPERATIONS.listLoginLogs), { query }); +} + +export function listOperationLogs(query?: PageQuery): Promise> { + return apiRequest>(apiEndpointPath(API_OPERATIONS.listOperationLogs), { query }); +} + +export function exportLoginLogs(query?: PageQuery): Promise { + return apiRequest(apiEndpointPath(API_OPERATIONS.exportLoginLogs), { query, raw: true }); +} + +export function exportOperationLogs(query?: PageQuery): Promise { + return apiRequest(apiEndpointPath(API_OPERATIONS.exportOperationLogs), { query, raw: true }); +} diff --git a/src/features/logs/components/LogFilters.jsx b/src/features/logs/components/LogFilters.jsx new file mode 100644 index 0000000..ad9b547 --- /dev/null +++ b/src/features/logs/components/LogFilters.jsx @@ -0,0 +1,9 @@ +import { SearchBox } from "@/shared/ui/SearchBox.jsx"; + +export function LogFilters({ onQueryChange, query }) { + return ( +
+ +
+ ); +} diff --git a/src/features/logs/components/LogTable.jsx b/src/features/logs/components/LogTable.jsx new file mode 100644 index 0000000..a940510 --- /dev/null +++ b/src/features/logs/components/LogTable.jsx @@ -0,0 +1,64 @@ +import { DataTable } from "@/shared/ui/DataTable.jsx"; + +export function LogTable({ data, isLogin }) { + const items = data.items || []; + const columns = isLogin + ? [ + { key: "username", label: "用户", width: "minmax(140px, 1fr)" }, + { key: "ip", label: "IP", width: "minmax(140px, 1fr)", render: (item) => {item.ip || "-"} }, + { key: "status", label: "状态", width: "110px" }, + { + key: "message", + label: "消息", + width: "minmax(220px, 1.5fr)", + render: (item) => {item.message || "-"} + }, + { + key: "createdAt", + label: "时间", + width: "180px", + render: (item) => {formatDate(item.createdAt)} + } + ] + : [ + { key: "username", label: "用户", width: "minmax(140px, 1fr)" }, + { key: "action", label: "动作", width: "minmax(120px, 1fr)" }, + { + key: "resource", + label: "资源", + width: "minmax(160px, 1fr)", + render: (item) => {item.resource || "-"} + }, + { key: "status", label: "状态", width: "110px" }, + { + key: "detail", + label: "详情", + width: "minmax(240px, 1.6fr)", + render: (item) => {item.detail || "-"} + }, + { + key: "createdAt", + label: "时间", + width: "180px", + render: (item) => {formatDate(item.createdAt)} + } + ]; + + return ( + item.id} + title={isLogin ? "登录记录" : "操作记录"} + total={data.total} + /> + ); +} + +function formatDate(value) { + if (!value) { + return "-"; + } + return new Date(value).toLocaleString("zh-CN", { hour12: false }); +} diff --git a/src/features/logs/components/LogToolbar.jsx b/src/features/logs/components/LogToolbar.jsx new file mode 100644 index 0000000..efe7b61 --- /dev/null +++ b/src/features/logs/components/LogToolbar.jsx @@ -0,0 +1,16 @@ +import DownloadOutlined from "@mui/icons-material/DownloadOutlined"; +import { Button } from "@/shared/ui/Button.jsx"; +import { PageHead } from "@/shared/ui/PageHead.jsx"; + +export function LogToolbar({ canExport, isLogin, onExport }) { + return ( + + {canExport ? ( + + ) : null} + + ); +} diff --git a/src/features/logs/hooks/useLogsPage.js b/src/features/logs/hooks/useLogsPage.js new file mode 100644 index 0000000..87295ce --- /dev/null +++ b/src/features/logs/hooks/useLogsPage.js @@ -0,0 +1,54 @@ +import { useCallback, useMemo, useState } from "react"; +import { downloadCsv } from "@/shared/api/download"; +import { toPageQuery } from "@/shared/api/query"; +import { useAdminQuery } from "@/shared/hooks/useAdminQuery.js"; +import { exportLoginLogs, exportOperationLogs, listLoginLogs, listOperationLogs } from "@/features/logs/api"; +import { useLogAbilities } from "@/features/logs/permissions.js"; + +const pageSize = 10; +const emptyData = { items: [], total: 0, page: 1, pageSize }; + +export function useLogsPage(type) { + const [query, setQuery] = useState(""); + const [page, setPage] = useState(1); + const abilities = useLogAbilities(); + const isLogin = type === "login"; + + const requestQuery = useMemo(() => toPageQuery({ keyword: query, page, pageSize }), [page, query]); + const queryFn = useCallback(async () => { + const loader = isLogin ? listLoginLogs : listOperationLogs; + return loader(requestQuery); + }, [isLogin, requestQuery]); + + const { data, error, loading, reload } = useAdminQuery(queryFn, { + errorMessage: "加载日志失败", + initialData: emptyData, + keepPreviousData: true, + queryKey: ["logs", type, requestQuery] + }); + + const changeQuery = (value) => { + setQuery(value); + setPage(1); + }; + + const downloadLogs = () => { + const exporter = isLogin ? exportLoginLogs : exportOperationLogs; + const filename = isLogin ? "hyapp-login-logs.csv" : "hyapp-operation-logs.csv"; + return downloadCsv(() => exporter({ keyword: query }), filename); + }; + + return { + abilities, + changeQuery, + data, + downloadLogs, + error, + isLogin, + loading, + page, + query, + reload, + setPage + }; +} diff --git a/src/features/logs/pages/LogsPage.jsx b/src/features/logs/pages/LogsPage.jsx new file mode 100644 index 0000000..b047ace --- /dev/null +++ b/src/features/logs/pages/LogsPage.jsx @@ -0,0 +1,22 @@ +import { DataState } from "@/shared/ui/DataState.jsx"; +import { PaginationBar } from "@/shared/ui/PaginationBar.jsx"; +import { LogFilters } from "@/features/logs/components/LogFilters.jsx"; +import { LogTable } from "@/features/logs/components/LogTable.jsx"; +import { LogToolbar } from "@/features/logs/components/LogToolbar.jsx"; +import { useLogsPage } from "@/features/logs/hooks/useLogsPage.js"; + +export function LogsPage({ type }) { + const page = useLogsPage(type); + + return ( + <> + + + + + + + + + ); +} diff --git a/src/features/logs/permissions.js b/src/features/logs/permissions.js new file mode 100644 index 0000000..a488e65 --- /dev/null +++ b/src/features/logs/permissions.js @@ -0,0 +1,10 @@ +import { useAuth } from "@/app/auth/AuthProvider.jsx"; +import { PERMISSIONS } from "@/app/permissions"; + +export function useLogAbilities() { + const { can } = useAuth(); + + return { + canExport: can(PERMISSIONS.logExport) + }; +} diff --git a/src/features/logs/routes.js b/src/features/logs/routes.js new file mode 100644 index 0000000..06e2cc2 --- /dev/null +++ b/src/features/logs/routes.js @@ -0,0 +1,22 @@ +import { MENU_CODES, PERMISSIONS } from "@/app/permissions"; + +export const logsRoutes = [ + { + label: "登录日志", + loader: () => import("./pages/LogsPage.jsx").then((module) => module.LogsPage), + menuCode: MENU_CODES.logsLogin, + pageKey: "logs-login", + path: "/logs/login", + permission: PERMISSIONS.logView, + props: { type: "login" } + }, + { + label: "操作日志", + loader: () => import("./pages/LogsPage.jsx").then((module) => module.LogsPage), + menuCode: MENU_CODES.logsOperations, + pageKey: "logs-operations", + path: "/logs/operations", + permission: PERMISSIONS.logView, + props: { type: "operations" } + } +]; diff --git a/src/features/menus/api.ts b/src/features/menus/api.ts new file mode 100644 index 0000000..aae7e06 --- /dev/null +++ b/src/features/menus/api.ts @@ -0,0 +1,81 @@ +import { apiRequest } from "@/shared/api/request"; +import { API_ENDPOINTS, API_OPERATIONS, apiEndpointPath } from "@/shared/api/generated/endpoints"; +import type { EntityId, MenuDto, MenuPayload, MenuSortItem, PermissionDto, PermissionPayload } from "@/shared/api/types"; + +export function getMenus(): Promise { + return apiRequest(apiEndpointPath(API_OPERATIONS.navigationMenus)); +} + +export function listSystemMenus(): Promise { + return apiRequest(apiEndpointPath(API_OPERATIONS.listSystemMenus)); +} + +export function createMenu(payload: MenuPayload): Promise { + const endpoint = API_ENDPOINTS.createMenu; + return apiRequest(apiEndpointPath(API_OPERATIONS.createMenu), { + body: payload, + method: endpoint.method + }); +} + +export function updateMenu(id: EntityId, payload: MenuPayload): Promise { + const endpoint = API_ENDPOINTS.updateMenu; + return apiRequest(apiEndpointPath(API_OPERATIONS.updateMenu, { id }), { + body: payload, + method: endpoint.method + }); +} + +export function deleteMenu(id: EntityId): Promise { + const endpoint = API_ENDPOINTS.deleteMenu; + return apiRequest(apiEndpointPath(API_OPERATIONS.deleteMenu, { id }), { method: endpoint.method }); +} + +export function updateMenuVisible(id: EntityId, visible: boolean): Promise { + const endpoint = API_ENDPOINTS.updateMenuVisible; + return apiRequest(apiEndpointPath(API_OPERATIONS.updateMenuVisible, { id }), { + body: { visible }, + method: endpoint.method + }); +} + +export function sortMenus(items: MenuSortItem[]): Promise { + const endpoint = API_ENDPOINTS.sortMenus; + return apiRequest(apiEndpointPath(API_OPERATIONS.sortMenus), { + body: { items }, + method: endpoint.method + }); +} + +export function listPermissions(): Promise { + return apiRequest(apiEndpointPath(API_OPERATIONS.listPermissions)); +} + +export function createPermission(payload: PermissionPayload): Promise { + const endpoint = API_ENDPOINTS.createPermission; + return apiRequest(apiEndpointPath(API_OPERATIONS.createPermission), { + body: payload, + method: endpoint.method + }); +} + +export function updatePermission(id: EntityId, payload: PermissionPayload): Promise { + const endpoint = API_ENDPOINTS.updatePermission; + return apiRequest(apiEndpointPath(API_OPERATIONS.updatePermission, { id }), { + body: payload, + method: endpoint.method + }); +} + +export function deletePermission(id: EntityId): Promise { + const endpoint = API_ENDPOINTS.deletePermission; + return apiRequest(apiEndpointPath(API_OPERATIONS.deletePermission, { id }), { method: endpoint.method }); +} + +export function syncPermissions(): Promise { + const endpoint = API_ENDPOINTS.syncPermissions; + return apiRequest>(apiEndpointPath(API_OPERATIONS.syncPermissions), { + body: {}, + method: endpoint.method + }); +} diff --git a/src/features/menus/components/MenuFormDrawer.jsx b/src/features/menus/components/MenuFormDrawer.jsx new file mode 100644 index 0000000..9832a2c --- /dev/null +++ b/src/features/menus/components/MenuFormDrawer.jsx @@ -0,0 +1,30 @@ +import Drawer from "@mui/material/Drawer"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import Switch from "@mui/material/Switch"; +import TextField from "@mui/material/TextField"; +import { Button } from "@/shared/ui/Button.jsx"; + +export function MenuFormDrawer({ editingMenu, form, onClose, onSubmit, open, setForm }) { + return ( + +
+

{editingMenu ? "编辑菜单" : "新增菜单"}

+ setForm({ ...form, label: event.target.value })} /> + setForm({ ...form, code: event.target.value })} /> + setForm({ ...form, parentId: event.target.value })} /> + setForm({ ...form, path: event.target.value })} /> + setForm({ ...form, icon: event.target.value })} /> + setForm({ ...form, permissionCode: event.target.value })} /> + setForm({ ...form, sort: event.target.value })} /> + setForm({ ...form, visible: event.target.checked })} />} + label={form.visible ? "显示" : "隐藏"} + /> +
+ + +
+ +
+ ); +} diff --git a/src/features/menus/components/MenuStats.jsx b/src/features/menus/components/MenuStats.jsx new file mode 100644 index 0000000..75ce85c --- /dev/null +++ b/src/features/menus/components/MenuStats.jsx @@ -0,0 +1,12 @@ +import MenuOpenOutlined from "@mui/icons-material/MenuOpenOutlined"; +import ShieldOutlined from "@mui/icons-material/ShieldOutlined"; +import { KpiCard } from "@/shared/ui/KpiCard.jsx"; + +export function MenuStats({ menuTotal, permissionTotal }) { + return ( +
+ 菜单树} /> + 菜单 / 按钮 / 接口} /> +
+ ); +} diff --git a/src/features/menus/components/MenuTable.jsx b/src/features/menus/components/MenuTable.jsx new file mode 100644 index 0000000..7c7211b --- /dev/null +++ b/src/features/menus/components/MenuTable.jsx @@ -0,0 +1,121 @@ +import { useState } from "react"; +import DeleteOutlineOutlined from "@mui/icons-material/DeleteOutlineOutlined"; +import DragIndicatorOutlined from "@mui/icons-material/DragIndicatorOutlined"; +import EditOutlined from "@mui/icons-material/EditOutlined"; +import VisibilityOffOutlined from "@mui/icons-material/VisibilityOffOutlined"; +import VisibilityOutlined from "@mui/icons-material/VisibilityOutlined"; +import { + AdminActionIconButton, + AdminRowActions, + adminListClasses +} from "@/shared/ui/AdminListLayout.jsx"; +import { DataTable } from "@/shared/ui/DataTable.jsx"; + +export function MenuTable({ abilities, flatMenus, onEdit, onReorder, onRemove, onToggleVisible }) { + const [draggingMenu, setDraggingMenu] = useState(null); + const [dragOverId, setDragOverId] = useState(null); + const columns = [ + { + key: "drag", + header: "", + width: "44px", + render: () => abilities.canSortMenu ? : null + }, + { + key: "label", + label: "菜单", + width: "minmax(180px, 1.2fr)", + render: (menu) => {menu.label} + }, + { key: "path", label: "路由", width: "minmax(180px, 1.2fr)", render: (menu) => {menu.path || "-"} }, + { key: "permissionCode", label: "权限码", width: "minmax(180px, 1.2fr)", render: (menu) => menu.permissionCode || "-" }, + { key: "sort", label: "排序", width: "88px", render: (menu) => {menu.sort} }, + { key: "visible", label: "状态", width: "100px", render: (menu) => (menu.visible ? "显示" : "隐藏") }, + { + key: "actions", + label: "操作", + width: "minmax(150px, 0.8fr)", + render: (menu) => ( + + {abilities.canUpdateMenu ? ( + onEdit(menu)}> + + + ) : null} + {abilities.canVisibleMenu ? ( + onToggleVisible(menu)}> + {menu.visible ? : } + + ) : null} + {abilities.canDeleteMenu ? ( + onRemove(menu)}> + + + ) : null} + + ) + } + ]; + + return ( + getDragRowProps({ + canSort: abilities.canSortMenu, + dragOverId, + draggingMenu, + menu, + onDragEnd: () => { + setDraggingMenu(null); + setDragOverId(null); + }, + onDragOverRow: setDragOverId, + onDragStart: setDraggingMenu, + onReorder + })} + items={flatMenus} + minWidth="1120px" + rowKey={(menu) => menu.id || menu.code} + /> + ); +} + +function getDragRowProps({ canSort, dragOverId, draggingMenu, menu, onDragEnd, onDragOverRow, onDragStart, onReorder }) { + if (!canSort) { + return {}; + } + + const canDrop = draggingMenu && draggingMenu.id !== menu.id && (draggingMenu.parentId || null) === (menu.parentId || null); + const isDropTarget = canDrop && dragOverId === menu.id; + + return { + className: [adminListClasses.dragRow, isDropTarget ? adminListClasses.dragRowActive : ""].filter(Boolean).join(" "), + draggable: true, + onDragEnd, + onDragLeave: () => { + if (dragOverId === menu.id) { + onDragOverRow(null); + } + }, + onDragOver: (event) => { + if (canDrop) { + event.preventDefault(); + event.dataTransfer.dropEffect = "move"; + onDragOverRow(menu.id); + } + }, + onDragStart: (event) => { + event.dataTransfer.effectAllowed = "move"; + event.dataTransfer.setData("text/plain", String(menu.id)); + onDragStart(menu); + }, + onDrop: (event) => { + event.preventDefault(); + if (canDrop) { + onReorder(draggingMenu, menu); + } + onDragOverRow(null); + onDragEnd(); + } + }; +} diff --git a/src/features/menus/components/MenuToolbar.jsx b/src/features/menus/components/MenuToolbar.jsx new file mode 100644 index 0000000..70428eb --- /dev/null +++ b/src/features/menus/components/MenuToolbar.jsx @@ -0,0 +1,29 @@ +import Add from "@mui/icons-material/Add"; +import SyncOutlined from "@mui/icons-material/SyncOutlined"; +import { Button } from "@/shared/ui/Button.jsx"; +import { PageHead } from "@/shared/ui/PageHead.jsx"; + +export function MenuToolbar({ abilities, onCreateMenu, onCreatePermission, onSyncPermissions }) { + return ( + + {abilities.canSyncPermission ? ( + + ) : null} + {abilities.canCreatePermission ? ( + + ) : null} + {abilities.canCreateMenu ? ( + + ) : null} + + ); +} diff --git a/src/features/menus/components/PermissionFormDrawer.jsx b/src/features/menus/components/PermissionFormDrawer.jsx new file mode 100644 index 0000000..49d3b00 --- /dev/null +++ b/src/features/menus/components/PermissionFormDrawer.jsx @@ -0,0 +1,27 @@ +import Drawer from "@mui/material/Drawer"; +import MenuItem from "@mui/material/MenuItem"; +import TextField from "@mui/material/TextField"; +import { Button } from "@/shared/ui/Button.jsx"; +import { permissionKinds } from "@/features/menus/constants.js"; + +export function PermissionFormDrawer({ editingPermission, form, onClose, onSubmit, open, setForm }) { + return ( + +
+

{editingPermission ? "编辑权限" : "新增权限"}

+ setForm({ ...form, name: event.target.value })} /> + setForm({ ...form, code: event.target.value })} /> + setForm({ ...form, kind: event.target.value })}> + {permissionKinds.map(([value, label]) => ( + {label} + ))} + + setForm({ ...form, description: event.target.value })} /> +
+ + +
+ +
+ ); +} diff --git a/src/features/menus/components/PermissionTable.jsx b/src/features/menus/components/PermissionTable.jsx new file mode 100644 index 0000000..8537ff9 --- /dev/null +++ b/src/features/menus/components/PermissionTable.jsx @@ -0,0 +1,51 @@ +import DeleteOutlineOutlined from "@mui/icons-material/DeleteOutlineOutlined"; +import EditOutlined from "@mui/icons-material/EditOutlined"; +import { AdminActionIconButton, AdminRowActions } from "@/shared/ui/AdminListLayout.jsx"; +import { DataTable } from "@/shared/ui/DataTable.jsx"; +import { kindLabel } from "@/features/menus/constants.js"; + +export function PermissionTable({ abilities, onEdit, onRemove, permissions }) { + if (!abilities.canLoadPermissions) { + return null; + } + + const columns = [ + { key: "name", label: "权限", width: "minmax(160px, 1fr)" }, + { key: "code", label: "编码", width: "minmax(180px, 1.2fr)", render: (permission) => {permission.code} }, + { key: "kind", label: "类型", width: "110px", render: (permission) => kindLabel(permission.kind) }, + { + key: "description", + label: "说明", + width: "minmax(240px, 1.6fr)", + render: (permission) => {permission.description || "-"} + }, + { + key: "actions", + label: "操作", + width: "120px", + render: (permission) => ( + + {abilities.canUpdatePermission ? ( + onEdit(permission)}> + + + ) : null} + {abilities.canDeletePermission ? ( + onRemove(permission)}> + + + ) : null} + + ) + } + ]; + + return ( + permission.id} + /> + ); +} diff --git a/src/features/menus/constants.js b/src/features/menus/constants.js new file mode 100644 index 0000000..39743ba --- /dev/null +++ b/src/features/menus/constants.js @@ -0,0 +1,9 @@ +export const permissionKinds = [ + ["menu", "菜单"], + ["button", "按钮"], + ["api", "接口"] +]; + +export function kindLabel(kind) { + return permissionKinds.find(([value]) => value === kind)?.[1] || kind; +} diff --git a/src/features/menus/hooks/useMenusPage.js b/src/features/menus/hooks/useMenusPage.js new file mode 100644 index 0000000..13f718d --- /dev/null +++ b/src/features/menus/hooks/useMenusPage.js @@ -0,0 +1,276 @@ +import { useCallback, useMemo, useState } from "react"; +import { parseForm } from "@/shared/forms/validation"; +import { useAdminQuery } from "@/shared/hooks/useAdminQuery.js"; +import { useConfirm } from "@/shared/ui/ConfirmProvider.jsx"; +import { useToast } from "@/shared/ui/ToastProvider.jsx"; +import { + createMenu, + createPermission, + deleteMenu, + deletePermission, + listPermissions, + listSystemMenus, + sortMenus, + syncPermissions, + updateMenu, + updateMenuVisible, + updatePermission +} from "@/features/menus/api"; +import { useMenuAbilities } from "@/features/menus/permissions.js"; +import { menuFormSchema, permissionFormSchema } from "@/features/menus/schema"; + +const emptyMenu = { + code: "", + icon: "menu", + label: "", + parentId: "", + path: "", + permissionCode: "", + sort: 0, + visible: true +}; + +const emptyPermission = { code: "", description: "", kind: "button", name: "" }; +const emptyData = { menus: [], permissions: [] }; + +export function useMenusPage() { + const [query, setQuery] = useState(""); + const [menuDrawerOpen, setMenuDrawerOpen] = useState(false); + const [permissionDrawerOpen, setPermissionDrawerOpen] = useState(false); + const [editingMenu, setEditingMenu] = useState(null); + const [editingPermission, setEditingPermission] = useState(null); + const [menuForm, setMenuForm] = useState(emptyMenu); + const [permissionForm, setPermissionForm] = useState(emptyPermission); + const abilities = useMenuAbilities(); + const confirm = useConfirm(); + const { showToast } = useToast(); + + const queryFn = useCallback(async () => { + const menuData = await listSystemMenus(); + const permissionData = abilities.canLoadPermissions ? await listPermissions() : []; + return { menus: menuData || [], permissions: permissionData || [] }; + }, [abilities.canLoadPermissions]); + + const { data, error, loading, reload } = useAdminQuery(queryFn, { + errorMessage: "加载菜单权限失败", + initialData: emptyData, + keepPreviousData: true, + queryKey: ["menus", abilities.canLoadPermissions] + }); + + const flatMenus = useMemo(() => flattenMenus(data.menus || []), [data.menus]); + const filteredFlatMenus = useMemo(() => { + const keyword = query.trim().toLowerCase(); + if (!keyword) { + return flatMenus; + } + return flatMenus.filter((menu) => { + return [menu.label, menu.code, menu.path, menu.permissionCode].some((value) => String(value || "").toLowerCase().includes(keyword)); + }); + }, [flatMenus, query]); + const filteredPermissions = useMemo(() => { + const keyword = query.trim().toLowerCase(); + const items = data.permissions || []; + if (!keyword) { + return items; + } + return items.filter((permission) => { + return [permission.name, permission.code, permission.kind, permission.description].some((value) => String(value || "").toLowerCase().includes(keyword)); + }); + }, [data.permissions, query]); + + const changeQuery = (value) => { + setQuery(value); + }; + + const openCreateMenu = () => { + setEditingMenu(null); + setMenuForm(emptyMenu); + setMenuDrawerOpen(true); + }; + + const openEditMenu = (menu) => { + setEditingMenu(menu); + setMenuForm({ + code: menu.code, + icon: menu.icon || "menu", + label: menu.label, + parentId: menu.parentId ? String(menu.parentId) : "", + path: menu.path || "", + permissionCode: menu.permissionCode || "", + sort: menu.sort || 0, + visible: Boolean(menu.visible) + }); + setMenuDrawerOpen(true); + }; + + const openCreatePermission = () => { + setEditingPermission(null); + setPermissionForm(emptyPermission); + setPermissionDrawerOpen(true); + }; + + const openEditPermission = (permission) => { + setEditingPermission(permission); + setPermissionForm({ + code: permission.code, + description: permission.description || "", + kind: permission.kind, + name: permission.name + }); + setPermissionDrawerOpen(true); + }; + + const submitMenu = async (event) => { + event.preventDefault(); + try { + const payload = parseForm(menuFormSchema, menuForm); + if (editingMenu) { + await updateMenu(editingMenu.id, payload); + showToast("菜单已更新", "success"); + } else { + await createMenu(payload); + showToast("菜单已创建", "success"); + } + setMenuDrawerOpen(false); + reload(); + } catch (err) { + showToast(err.message || "保存菜单失败", "error"); + } + }; + + const submitPermission = async (event) => { + event.preventDefault(); + try { + const payload = parseForm(permissionFormSchema, permissionForm); + if (editingPermission) { + await updatePermission(editingPermission.id, payload); + showToast("权限已更新", "success"); + } else { + await createPermission(payload); + showToast("权限已创建", "success"); + } + setPermissionDrawerOpen(false); + reload(); + } catch (err) { + showToast(err.message || "保存权限失败", "error"); + } + }; + + const removeMenu = async (menu) => { + const ok = await confirm({ confirmText: "删除", message: `${menu.label} 删除前需要先删除子菜单。`, title: "删除菜单", tone: "danger" }); + if (!ok) { + return; + } + try { + await deleteMenu(menu.id); + showToast("菜单已删除", "success"); + reload(); + } catch (err) { + showToast(err.message || "删除菜单失败", "error"); + } + }; + + const removePermission = async (permission) => { + const ok = await confirm({ confirmText: "删除", message: `${permission.name} 删除前不能被角色或菜单引用。`, title: "删除权限", tone: "danger" }); + if (!ok) { + return; + } + try { + await deletePermission(permission.id); + showToast("权限已删除", "success"); + reload(); + } catch (err) { + showToast(err.message || "删除权限失败", "error"); + } + }; + + const toggleVisible = async (menu) => { + try { + await updateMenuVisible(menu.id, !menu.visible); + showToast("菜单状态已更新", "success"); + reload(); + } catch (err) { + showToast(err.message || "更新菜单失败", "error"); + } + }; + + const reorderMenu = async (source, target) => { + if (!source || !target || source.id === target.id || (source.parentId || null) !== (target.parentId || null)) { + return; + } + const siblings = flatMenus + .filter((item) => (item.parentId || null) === (source.parentId || null)) + .sort((a, b) => (a.sort || 0) - (b.sort || 0) || a.id - b.id); + const fromIndex = siblings.findIndex((item) => item.id === source.id); + const toIndex = siblings.findIndex((item) => item.id === target.id); + if (fromIndex < 0 || toIndex < 0 || fromIndex === toIndex) { + return; + } + const reordered = [...siblings]; + const [moved] = reordered.splice(fromIndex, 1); + reordered.splice(toIndex, 0, moved); + + try { + await sortMenus(reordered.map((item, index) => ({ id: item.id, sort: (index + 1) * 10 }))); + showToast("菜单排序已更新", "success"); + reload(); + } catch (err) { + showToast(err.message || "排序失败", "error"); + } + }; + + const syncDefaultPermissions = async () => { + const ok = await confirm({ confirmText: "同步", message: "将同步系统默认权限定义。", title: "同步权限" }); + if (!ok) { + return; + } + try { + await syncPermissions(); + showToast("权限已同步", "success"); + reload(); + } catch (err) { + showToast(err.message || "同步权限失败", "error"); + } + }; + + return { + abilities, + changeQuery, + closeMenuDrawer: () => setMenuDrawerOpen(false), + closePermissionDrawer: () => setPermissionDrawerOpen(false), + editingMenu, + editingPermission, + error, + flatMenus: filteredFlatMenus, + loading, + menuDrawerOpen, + menuForm, + menus: data.menus || [], + openCreateMenu, + openCreatePermission, + openEditMenu, + openEditPermission, + permissionDrawerOpen, + permissionForm, + permissions: filteredPermissions, + query, + reload, + removeMenu, + removePermission, + reorderMenu, + setMenuForm, + setPermissionForm, + submitMenu, + submitPermission, + syncDefaultPermissions, + toggleVisible + }; +} + +function flattenMenus(menus, depth = 0) { + return menus.flatMap((menu) => [ + { ...menu, depth }, + ...flattenMenus(menu.children || [], depth + 1) + ]); +} diff --git a/src/features/menus/menus.css b/src/features/menus/menus.css new file mode 100644 index 0000000..aa35c57 --- /dev/null +++ b/src/features/menus/menus.css @@ -0,0 +1,13 @@ +.permission-page-grid { + display: grid; + flex: 1; + grid-template-rows: minmax(0, 1fr) minmax(0, 1fr); + gap: 0; + min-width: 0; + min-height: 0; + overflow: hidden; +} + +.permission-page-grid .table-frame + .table-frame { + border-top: 1px solid var(--border); +} diff --git a/src/features/menus/pages/MenuManagementPage.jsx b/src/features/menus/pages/MenuManagementPage.jsx new file mode 100644 index 0000000..29c3e7f --- /dev/null +++ b/src/features/menus/pages/MenuManagementPage.jsx @@ -0,0 +1,86 @@ +import Add from "@mui/icons-material/Add"; +import SyncOutlined from "@mui/icons-material/SyncOutlined"; +import { DataState } from "@/shared/ui/DataState.jsx"; +import { + AdminActionIconButton, + AdminListBody, + AdminListPage, + AdminListToolbar, + AdminSearchBox +} from "@/shared/ui/AdminListLayout.jsx"; +import { MenuFormDrawer } from "@/features/menus/components/MenuFormDrawer.jsx"; +import { MenuTable } from "@/features/menus/components/MenuTable.jsx"; +import { PermissionFormDrawer } from "@/features/menus/components/PermissionFormDrawer.jsx"; +import { PermissionTable } from "@/features/menus/components/PermissionTable.jsx"; +import { useMenusPage } from "@/features/menus/hooks/useMenusPage.js"; + +export function MenuManagementPage() { + const page = useMenusPage(); + + return ( + <> + + } + actions={( + <> + {page.abilities.canSyncPermission ? ( + + + + ) : null} + {page.abilities.canCreatePermission ? ( + + + + ) : null} + {page.abilities.canCreateMenu ? ( + + + + ) : null} + + )} + /> + + + +
+ + +
+
+
+
+ + + + + ); +} diff --git a/src/features/menus/permissions.js b/src/features/menus/permissions.js new file mode 100644 index 0000000..a8e41b9 --- /dev/null +++ b/src/features/menus/permissions.js @@ -0,0 +1,19 @@ +import { useAuth } from "@/app/auth/AuthProvider.jsx"; +import { PERMISSIONS } from "@/app/permissions"; + +export function useMenuAbilities() { + const { can } = useAuth(); + + return { + canCreateMenu: can(PERMISSIONS.menuCreate), + canCreatePermission: can(PERMISSIONS.permissionCreate), + canDeleteMenu: can(PERMISSIONS.menuDelete), + canDeletePermission: can(PERMISSIONS.permissionDelete), + canLoadPermissions: can(PERMISSIONS.permissionView) || can(PERMISSIONS.rolePermission) || can(PERMISSIONS.roleManage), + canSortMenu: can(PERMISSIONS.menuSort), + canSyncPermission: can(PERMISSIONS.permissionSync), + canUpdateMenu: can(PERMISSIONS.menuUpdate), + canUpdatePermission: can(PERMISSIONS.permissionUpdate), + canVisibleMenu: can(PERMISSIONS.menuVisible) + }; +} diff --git a/src/features/menus/routes.js b/src/features/menus/routes.js new file mode 100644 index 0000000..12c04af --- /dev/null +++ b/src/features/menus/routes.js @@ -0,0 +1,12 @@ +import { MENU_CODES, PERMISSIONS } from "@/app/permissions"; + +export const menusRoutes = [ + { + label: "菜单权限", + loader: () => import("./pages/MenuManagementPage.jsx").then((module) => module.MenuManagementPage), + menuCode: MENU_CODES.systemMenus, + pageKey: "menus", + path: "/system/menus", + permission: PERMISSIONS.menuView + } +]; diff --git a/src/features/menus/schema.ts b/src/features/menus/schema.ts new file mode 100644 index 0000000..a710612 --- /dev/null +++ b/src/features/menus/schema.ts @@ -0,0 +1,27 @@ +import { z } from "zod"; + +const optionalText = (max: number, message: string) => z.string().trim().max(max, message).optional().default(""); + +export const menuFormSchema = z.object({ + code: z.string().trim().min(1, "请输入菜单编码").max(80, "菜单编码不能超过 80 个字符"), + icon: optionalText(40, "图标不能超过 40 个字符"), + label: z.string().trim().min(1, "请输入菜单名称").max(60, "菜单名称不能超过 60 个字符"), + parentId: z + .union([z.literal(""), z.coerce.number().int().positive(), z.null()]) + .optional() + .transform((value) => (value === "" || value === undefined ? null : value)), + path: optionalText(120, "路径不能超过 120 个字符"), + permissionCode: optionalText(120, "权限码不能超过 120 个字符"), + sort: z.coerce.number().int().min(0, "排序不能小于 0").default(0), + visible: z.boolean().default(true) +}); + +export const permissionFormSchema = z.object({ + code: z.string().trim().min(1, "请输入权限码").max(120, "权限码不能超过 120 个字符"), + description: optionalText(200, "描述不能超过 200 个字符"), + kind: z.enum(["api", "button", "menu"]), + name: z.string().trim().min(1, "请输入权限名称").max(60, "权限名称不能超过 60 个字符") +}); + +export type MenuForm = z.infer; +export type PermissionForm = z.infer; diff --git a/src/features/notifications/api.ts b/src/features/notifications/api.ts new file mode 100644 index 0000000..f83cba5 --- /dev/null +++ b/src/features/notifications/api.ts @@ -0,0 +1,28 @@ +import { apiRequest } from "@/shared/api/request"; +import { API_ENDPOINTS, API_OPERATIONS, apiEndpointPath } from "@/shared/api/generated/endpoints"; +import type { EntityId, NotificationDto, NotificationListDto } from "@/shared/api/types"; + +export function listNotifications(): Promise { + return apiRequest(apiEndpointPath(API_OPERATIONS.listNotifications)); +} + +export function markNotificationRead(id: EntityId): Promise { + const endpoint = API_ENDPOINTS.markNotificationRead; + return apiRequest>(apiEndpointPath(API_OPERATIONS.markNotificationRead, { id }), { + body: {}, + method: endpoint.method + }); +} + +export function markAllNotificationsRead(): Promise { + const endpoint = API_ENDPOINTS.markAllNotificationsRead; + return apiRequest>(apiEndpointPath(API_OPERATIONS.markAllNotificationsRead), { + body: {}, + method: endpoint.method + }); +} + +export function deleteNotification(id: EntityId): Promise { + const endpoint = API_ENDPOINTS.deleteNotification; + return apiRequest(apiEndpointPath(API_OPERATIONS.deleteNotification, { id }), { method: endpoint.method }); +} diff --git a/src/features/notifications/components/NotificationList.jsx b/src/features/notifications/components/NotificationList.jsx new file mode 100644 index 0000000..c625ba2 --- /dev/null +++ b/src/features/notifications/components/NotificationList.jsx @@ -0,0 +1,46 @@ +import DeleteOutlineOutlined from "@mui/icons-material/DeleteOutlineOutlined"; +import DoneAllOutlined from "@mui/icons-material/DoneAllOutlined"; +import { Button } from "@/shared/ui/Button.jsx"; +import { Card } from "@/shared/ui/Card.jsx"; + +export function NotificationList({ abilities, items, onDelete, onRead }) { + return ( + +
+ {(items || []).map((item) => ( +
+
+
{item.title}
+
{item.content}
+
{formatDate(item.createdAt)}
+
+
+ {!item.readAt && abilities.canRead ? ( + + ) : ( + {item.readAt ? "已读" : "未读"} + )} + {abilities.canDelete ? ( + + ) : null} +
+
+ ))} + {!items?.length ?
暂无通知
: null} +
+
+ ); +} + +function formatDate(value) { + if (!value) { + return "-"; + } + return new Date(value).toLocaleString("zh-CN", { hour12: false }); +} diff --git a/src/features/notifications/components/NotificationToolbar.jsx b/src/features/notifications/components/NotificationToolbar.jsx new file mode 100644 index 0000000..9777085 --- /dev/null +++ b/src/features/notifications/components/NotificationToolbar.jsx @@ -0,0 +1,16 @@ +import DoneAllOutlined from "@mui/icons-material/DoneAllOutlined"; +import { Button } from "@/shared/ui/Button.jsx"; +import { PageHead } from "@/shared/ui/PageHead.jsx"; + +export function NotificationToolbar({ canReadAll, onReadAll, unread }) { + return ( + + {canReadAll ? ( + + ) : null} + + ); +} diff --git a/src/features/notifications/hooks/useNotificationsPage.js b/src/features/notifications/hooks/useNotificationsPage.js new file mode 100644 index 0000000..c37697e --- /dev/null +++ b/src/features/notifications/hooks/useNotificationsPage.js @@ -0,0 +1,62 @@ +import { useCallback } from "react"; +import { useAdminQuery } from "@/shared/hooks/useAdminQuery.js"; +import { useConfirm } from "@/shared/ui/ConfirmProvider.jsx"; +import { useToast } from "@/shared/ui/ToastProvider.jsx"; +import { + deleteNotification, + listNotifications, + markAllNotificationsRead, + markNotificationRead +} from "@/features/notifications/api"; +import { useNotificationAbilities } from "@/features/notifications/permissions.js"; + +const emptyData = { items: [], unread: 0 }; + +export function useNotificationsPage() { + const abilities = useNotificationAbilities(); + const confirm = useConfirm(); + const { showToast } = useToast(); + + const queryFn = useCallback(() => { + return listNotifications(); + }, []); + const { data, error, loading, reload } = useAdminQuery(queryFn, { + errorMessage: "加载通知失败", + initialData: emptyData, + keepPreviousData: true, + queryKey: ["notifications"] + }); + + const read = async (id) => { + await markNotificationRead(id); + showToast("通知已标记为已读", "success"); + reload(); + }; + + const readAll = async () => { + await markAllNotificationsRead(); + showToast("通知已全部标记为已读", "success"); + reload(); + }; + + const remove = async (item) => { + const ok = await confirm({ confirmText: "删除", message: item.title, title: "删除通知", tone: "danger" }); + if (!ok) { + return; + } + await deleteNotification(item.id); + showToast("通知已删除", "success"); + reload(); + }; + + return { + abilities, + data: data || emptyData, + error, + loading, + read, + readAll, + reload, + remove + }; +} diff --git a/src/features/notifications/notifications.css b/src/features/notifications/notifications.css new file mode 100644 index 0000000..5e740be --- /dev/null +++ b/src/features/notifications/notifications.css @@ -0,0 +1,25 @@ +.notification-list { + display: grid; +} + +.notification-item { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: center; + gap: var(--space-3); + min-height: 86px; + padding: var(--space-4) var(--space-5); + border-bottom: 1px solid var(--row-border); +} + +.notification-title { + color: var(--text-primary); + font-size: var(--admin-font-size); + font-weight: 700; +} + +.notification-content { + margin: var(--space-2) 0; + color: var(--text-secondary); + font-size: var(--admin-font-size); +} diff --git a/src/features/notifications/pages/NotificationsPage.jsx b/src/features/notifications/pages/NotificationsPage.jsx new file mode 100644 index 0000000..7ee1927 --- /dev/null +++ b/src/features/notifications/pages/NotificationsPage.jsx @@ -0,0 +1,17 @@ +import { DataState } from "@/shared/ui/DataState.jsx"; +import { NotificationList } from "@/features/notifications/components/NotificationList.jsx"; +import { NotificationToolbar } from "@/features/notifications/components/NotificationToolbar.jsx"; +import { useNotificationsPage } from "@/features/notifications/hooks/useNotificationsPage.js"; + +export function NotificationsPage() { + const page = useNotificationsPage(); + + return ( + <> + + + + + + ); +} diff --git a/src/features/notifications/permissions.js b/src/features/notifications/permissions.js new file mode 100644 index 0000000..58316ef --- /dev/null +++ b/src/features/notifications/permissions.js @@ -0,0 +1,12 @@ +import { useAuth } from "@/app/auth/AuthProvider.jsx"; +import { PERMISSIONS } from "@/app/permissions"; + +export function useNotificationAbilities() { + const { can } = useAuth(); + + return { + canDelete: can(PERMISSIONS.notificationDelete), + canRead: can(PERMISSIONS.notificationRead), + canReadAll: can(PERMISSIONS.notificationReadAll) + }; +} diff --git a/src/features/notifications/routes.js b/src/features/notifications/routes.js new file mode 100644 index 0000000..e79c24f --- /dev/null +++ b/src/features/notifications/routes.js @@ -0,0 +1,12 @@ +import { MENU_CODES, PERMISSIONS } from "@/app/permissions"; + +export const notificationsRoutes = [ + { + label: "通知中心", + loader: () => import("./pages/NotificationsPage.jsx").then((module) => module.NotificationsPage), + menuCode: MENU_CODES.notifications, + pageKey: "notifications", + path: "/notifications", + permission: PERMISSIONS.notificationView + } +]; diff --git a/src/features/resources/api.test.ts b/src/features/resources/api.test.ts new file mode 100644 index 0000000..ff286f7 --- /dev/null +++ b/src/features/resources/api.test.ts @@ -0,0 +1,204 @@ +import { afterEach, expect, test, vi } from "vitest"; +import { setAccessToken } from "@/shared/api/request"; +import { + createGift, + createResource, + createResourceGroup, + disableGift, + disableResource, + disableResourceGroup, + enableGift, + enableResource, + enableResourceGroup, + grantResource, + grantResourceGroup, + listResourceGrants, + listGifts, + listResources, + updateGift, + updateResource, + updateResourceGroup, +} from "./api"; + +afterEach(() => { + setAccessToken(""); + vi.unstubAllGlobals(); +}); + +test("resource APIs use generated admin paths", async () => { + vi.stubGlobal( + "fetch", + vi.fn( + async () => new Response(JSON.stringify({ code: 0, data: { items: [], page: 1, pageSize: 10, total: 0 } })), + ), + ); + + await listResources({ page: 1, page_size: 10, resource_type: "gift", status: "active" }); + await createResource({ + amount: 0, + assetUrl: "https://media.haiyihy.com/resource/material.png", + name: "Rose", + previewUrl: "https://media.haiyihy.com/resource/cover.png", + resourceCode: "gift_rose_test", + resourceType: "gift", + sortOrder: 0, + status: "active", + }); + await updateResource(11, { + amount: 0, + assetUrl: "https://media.haiyihy.com/resource/material-updated.png", + name: "Rose Updated", + previewUrl: "https://media.haiyihy.com/resource/cover-updated.png", + resourceCode: "gift_rose_test", + resourceType: "gift", + sortOrder: 0, + status: "active", + }); + await listGifts({ page: 1, page_size: 10, region_id: 1001 }); + await createGift({ + coinPrice: 10, + effectiveAtMs: 1777766400000, + giftId: "rose", + giftPointAmount: 1, + heatValue: 1, + name: "Rose", + presentationJson: "{}", + priceVersion: "default", + regionIds: [0], + resourceId: 11, + sortOrder: 0, + status: "active", + }); + await enableResource(11); + await disableResource(11); + await createResourceGroup({ + description: "", + groupCode: "starter_pack_test", + items: [ + { + durationDays: 7, + itemType: "resource", + resourceId: 11, + sortOrder: 0, + }, + { + amount: 1000, + assetType: "DIAMOND", + itemType: "wallet_asset", + sortOrder: 1, + }, + ], + name: "Starter Pack", + sortOrder: 0, + status: "active", + }); + await updateResourceGroup(22, { + description: "updated", + groupCode: "starter_pack_test", + items: [], + name: "Starter Pack Updated", + sortOrder: 1, + status: "disabled", + }); + await enableResourceGroup(22); + await disableResourceGroup(22); + await updateGift("rose", { + coinPrice: 10, + effectiveAtMs: 1777766400000, + giftId: "rose", + giftPointAmount: 1, + heatValue: 1, + name: "Rose", + presentationJson: "{}", + priceVersion: "default", + regionIds: [0], + resourceId: 11, + sortOrder: 0, + status: "active", + }); + await enableGift("rose"); + await disableGift("rose"); + await listResourceGrants({ page: 1, page_size: 10, target_user_id: 1001, status: "succeeded" }); + await grantResource({ + commandId: "resource-grant-test", + durationMs: 0, + quantity: 1, + reason: "manual", + resourceId: 11, + targetUserId: 1001, + }); + await grantResourceGroup({ + commandId: "resource-group-grant-test", + groupId: 22, + reason: "manual", + targetUserId: 1001, + }); + + const [listUrl, listInit] = vi.mocked(fetch).mock.calls[0]; + const [createUrl, createInit] = vi.mocked(fetch).mock.calls[1]; + const [updateUrl, updateInit] = vi.mocked(fetch).mock.calls[2]; + const [giftListUrl, giftListInit] = vi.mocked(fetch).mock.calls[3]; + const [createGiftUrl, createGiftInit] = vi.mocked(fetch).mock.calls[4]; + const [enableUrl, enableInit] = vi.mocked(fetch).mock.calls[5]; + const [disableUrl, disableInit] = vi.mocked(fetch).mock.calls[6]; + const [createGroupUrl, createGroupInit] = vi.mocked(fetch).mock.calls[7]; + const [updateGroupUrl, updateGroupInit] = vi.mocked(fetch).mock.calls[8]; + const [enableGroupUrl, enableGroupInit] = vi.mocked(fetch).mock.calls[9]; + const [disableGroupUrl, disableGroupInit] = vi.mocked(fetch).mock.calls[10]; + const [updateGiftUrl, updateGiftInit] = vi.mocked(fetch).mock.calls[11]; + const [enableGiftUrl, enableGiftInit] = vi.mocked(fetch).mock.calls[12]; + const [disableGiftUrl, disableGiftInit] = vi.mocked(fetch).mock.calls[13]; + const [grantListUrl, grantListInit] = vi.mocked(fetch).mock.calls[14]; + const [grantResourceUrl, grantResourceInit] = vi.mocked(fetch).mock.calls[15]; + const [grantGroupUrl, grantGroupInit] = vi.mocked(fetch).mock.calls[16]; + + expect(String(listUrl)).toContain("/api/v1/admin/resources?"); + expect(String(listUrl)).toContain("resource_type=gift"); + expect(String(listUrl)).toContain("status=active"); + expect(listInit?.method).toBe("GET"); + expect(String(createUrl)).toContain("/api/v1/admin/resources"); + expect(createInit?.method).toBe("POST"); + expect(JSON.parse(String(createInit?.body))).toMatchObject({ resourceCode: "gift_rose_test", status: "active" }); + expect(String(updateUrl)).toContain("/api/v1/admin/resources/11"); + expect(updateInit?.method).toBe("PUT"); + expect(String(giftListUrl)).toContain("/api/v1/admin/gifts?"); + expect(String(giftListUrl)).toContain("region_id=1001"); + expect(giftListInit?.method).toBe("GET"); + expect(String(createGiftUrl)).toContain("/api/v1/admin/gifts"); + expect(createGiftInit?.method).toBe("POST"); + expect(JSON.parse(String(createGiftInit?.body))).toMatchObject({ giftId: "rose", resourceId: 11 }); + expect(String(enableUrl)).toContain("/api/v1/admin/resources/11/enable"); + expect(enableInit?.method).toBe("POST"); + expect(String(disableUrl)).toContain("/api/v1/admin/resources/11/disable"); + expect(disableInit?.method).toBe("POST"); + expect(String(createGroupUrl)).toContain("/api/v1/admin/resource-groups"); + expect(createGroupInit?.method).toBe("POST"); + expect(JSON.parse(String(createGroupInit?.body))).toMatchObject({ + groupCode: "starter_pack_test", + items: [ + { durationDays: 7, itemType: "resource" }, + { amount: 1000, assetType: "DIAMOND" }, + ], + }); + expect(String(updateGroupUrl)).toContain("/api/v1/admin/resource-groups/22"); + expect(updateGroupInit?.method).toBe("PUT"); + expect(String(enableGroupUrl)).toContain("/api/v1/admin/resource-groups/22/enable"); + expect(enableGroupInit?.method).toBe("POST"); + expect(String(disableGroupUrl)).toContain("/api/v1/admin/resource-groups/22/disable"); + expect(disableGroupInit?.method).toBe("POST"); + expect(String(updateGiftUrl)).toContain("/api/v1/admin/gifts/rose"); + expect(updateGiftInit?.method).toBe("PUT"); + expect(String(enableGiftUrl)).toContain("/api/v1/admin/gifts/rose/enable"); + expect(enableGiftInit?.method).toBe("POST"); + expect(String(disableGiftUrl)).toContain("/api/v1/admin/gifts/rose/disable"); + expect(disableGiftInit?.method).toBe("POST"); + expect(String(grantListUrl)).toContain("/api/v1/admin/resource-grants?"); + expect(String(grantListUrl)).toContain("target_user_id=1001"); + expect(grantListInit?.method).toBe("GET"); + expect(String(grantResourceUrl)).toContain("/api/v1/admin/resource-grants/resource"); + expect(grantResourceInit?.method).toBe("POST"); + expect(JSON.parse(String(grantResourceInit?.body))).toMatchObject({ resourceId: 11, targetUserId: 1001 }); + expect(String(grantGroupUrl)).toContain("/api/v1/admin/resource-grants/group"); + expect(grantGroupInit?.method).toBe("POST"); + expect(JSON.parse(String(grantGroupInit?.body))).toMatchObject({ groupId: 22, targetUserId: 1001 }); +}); diff --git a/src/features/resources/api.ts b/src/features/resources/api.ts new file mode 100644 index 0000000..840c2a7 --- /dev/null +++ b/src/features/resources/api.ts @@ -0,0 +1,300 @@ +import { apiRequest } from "@/shared/api/request"; +import { API_ENDPOINTS, API_OPERATIONS, apiEndpointPath } from "@/shared/api/generated/endpoints"; +import type { ApiPage, EntityId, PageQuery } from "@/shared/api/types"; + +export interface ResourceDto { + appCode?: string; + resourceId: number; + resourceCode?: string; + resourceType?: string; + name?: string; + status?: string; + grantable?: boolean; + grantStrategy?: string; + walletAssetType?: string; + walletAssetAmount?: number; + usageScopes?: string[]; + assetUrl?: string; + previewUrl?: string; + animationUrl?: string; + metadataJson?: string; + sortOrder?: number; + createdAtMs?: number; + updatedAtMs?: number; +} + +export interface ResourcePayload { + amount: number; + assetUrl: string; + name: string; + previewUrl: string; + resourceCode: string; + resourceType: string; + sortOrder: number; + status: string; +} + +export interface ResourceGroupItemDto { + groupItemId: number; + itemType?: string; + resourceId: number; + resource?: ResourceDto; + walletAssetType?: string; + walletAssetAmount?: number; + quantity?: number; + durationMs?: number; + sortOrder?: number; +} + +export interface ResourceGroupDto { + appCode?: string; + groupId: number; + groupCode?: string; + name?: string; + status?: string; + description?: string; + sortOrder?: number; + items?: ResourceGroupItemDto[]; + createdAtMs?: number; + updatedAtMs?: number; +} + +export interface GiftDto { + appCode?: string; + giftId: string; + resourceId: number; + resource?: ResourceDto; + status?: string; + name?: string; + sortOrder?: number; + presentationJson?: string; + priceVersion?: string; + coinPrice?: number; + giftPointAmount?: number; + heatValue?: number; + createdAtMs?: number; + updatedAtMs?: number; + regionIds?: number[]; +} + +export interface GiftPayload { + giftId: string; + resourceId: number; + status: string; + name: string; + sortOrder: number; + presentationJson: string; + priceVersion: string; + coinPrice: number; + giftPointAmount: number; + heatValue: number; + effectiveAtMs: number; + regionIds: number[]; +} + +export interface ResourceGrantItemDto { + createdAtMs?: number; + durationMs?: number; + entitlementId?: string; + grantId?: string; + grantItemId?: number; + quantity?: number; + resourceId?: number; + resourceSnapshotJson?: string; + resultType?: string; + walletTransactionId?: string; +} + +export interface ResourceGrantDto { + appCode?: string; + commandId?: string; + createdAtMs?: number; + grantId: string; + grantSource?: string; + grantSubjectId?: string; + grantSubjectType?: string; + items?: ResourceGrantItemDto[]; + operatorUserId?: number; + reason?: string; + status?: string; + targetUserId?: number; + updatedAtMs?: number; +} + +export interface GrantResourcePayload { + commandId: string; + durationMs: number; + quantity: number; + reason: string; + resourceId: number; + targetUserId: number; +} + +export interface GrantResourceGroupPayload { + commandId: string; + groupId: number; + reason: string; + targetUserId: number; +} + +export interface ResourceGroupItemPayload { + itemType: string; + amount?: number; + assetType?: string; + durationDays?: number; + resourceId?: number; + sortOrder: number; +} + +export interface ResourceGroupPayload { + groupCode: string; + name: string; + status: string; + description: string; + sortOrder: number; + items: ResourceGroupItemPayload[]; +} + +export function listResources(query: PageQuery = {}): Promise> { + const endpoint = API_ENDPOINTS.listResources; + return apiRequest>(apiEndpointPath(API_OPERATIONS.listResources), { + method: endpoint.method, + query, + }); +} + +export function createResource(payload: ResourcePayload): Promise { + const endpoint = API_ENDPOINTS.createResource; + return apiRequest(apiEndpointPath(API_OPERATIONS.createResource), { + body: payload, + method: endpoint.method, + }); +} + +export function updateResource(resourceId: EntityId, payload: ResourcePayload): Promise { + const endpoint = API_ENDPOINTS.updateResource; + return apiRequest( + apiEndpointPath(API_OPERATIONS.updateResource, { resource_id: resourceId }), + { + body: payload, + method: endpoint.method, + }, + ); +} + +export function enableResource(resourceId: EntityId): Promise { + const endpoint = API_ENDPOINTS.enableResource; + return apiRequest(apiEndpointPath(API_OPERATIONS.enableResource, { resource_id: resourceId }), { + method: endpoint.method, + }); +} + +export function disableResource(resourceId: EntityId): Promise { + const endpoint = API_ENDPOINTS.disableResource; + return apiRequest(apiEndpointPath(API_OPERATIONS.disableResource, { resource_id: resourceId }), { + method: endpoint.method, + }); +} + +export function listResourceGroups(query: PageQuery = {}): Promise> { + const endpoint = API_ENDPOINTS.listResourceGroups; + return apiRequest>(apiEndpointPath(API_OPERATIONS.listResourceGroups), { + method: endpoint.method, + query, + }); +} + +export function createResourceGroup(payload: ResourceGroupPayload): Promise { + const endpoint = API_ENDPOINTS.createResourceGroup; + return apiRequest(apiEndpointPath(API_OPERATIONS.createResourceGroup), { + body: payload, + method: endpoint.method, + }); +} + +export function updateResourceGroup(groupId: EntityId, payload: ResourceGroupPayload): Promise { + const endpoint = API_ENDPOINTS.updateResourceGroup; + return apiRequest( + apiEndpointPath(API_OPERATIONS.updateResourceGroup, { group_id: groupId }), + { + body: payload, + method: endpoint.method, + }, + ); +} + +export function enableResourceGroup(groupId: EntityId): Promise { + const endpoint = API_ENDPOINTS.enableResourceGroup; + return apiRequest(apiEndpointPath(API_OPERATIONS.enableResourceGroup, { group_id: groupId }), { + method: endpoint.method, + }); +} + +export function disableResourceGroup(groupId: EntityId): Promise { + const endpoint = API_ENDPOINTS.disableResourceGroup; + return apiRequest(apiEndpointPath(API_OPERATIONS.disableResourceGroup, { group_id: groupId }), { + method: endpoint.method, + }); +} + +export function listGifts(query: PageQuery = {}): Promise> { + const endpoint = API_ENDPOINTS.listGifts; + return apiRequest>(apiEndpointPath(API_OPERATIONS.listGifts), { + method: endpoint.method, + query, + }); +} + +export function createGift(payload: GiftPayload): Promise { + const endpoint = API_ENDPOINTS.createGift; + return apiRequest(apiEndpointPath(API_OPERATIONS.createGift), { + body: payload, + method: endpoint.method, + }); +} + +export function updateGift(giftId: string, payload: GiftPayload): Promise { + const endpoint = API_ENDPOINTS.updateGift; + return apiRequest(apiEndpointPath(API_OPERATIONS.updateGift, { gift_id: giftId }), { + body: payload, + method: endpoint.method, + }); +} + +export function enableGift(giftId: string): Promise { + const endpoint = API_ENDPOINTS.enableGift; + return apiRequest(apiEndpointPath(API_OPERATIONS.enableGift, { gift_id: giftId }), { + method: endpoint.method, + }); +} + +export function disableGift(giftId: string): Promise { + const endpoint = API_ENDPOINTS.disableGift; + return apiRequest(apiEndpointPath(API_OPERATIONS.disableGift, { gift_id: giftId }), { + method: endpoint.method, + }); +} + +export function listResourceGrants(query: PageQuery = {}): Promise> { + const endpoint = API_ENDPOINTS.listResourceGrants; + return apiRequest>(apiEndpointPath(API_OPERATIONS.listResourceGrants), { + method: endpoint.method, + query, + }); +} + +export function grantResource(payload: GrantResourcePayload): Promise { + const endpoint = API_ENDPOINTS.grantResource; + return apiRequest(apiEndpointPath(API_OPERATIONS.grantResource), { + body: payload, + method: endpoint.method, + }); +} + +export function grantResourceGroup(payload: GrantResourceGroupPayload): Promise { + const endpoint = API_ENDPOINTS.grantResourceGroup; + return apiRequest(apiEndpointPath(API_OPERATIONS.grantResourceGroup), { + body: payload, + method: endpoint.method, + }); +} diff --git a/src/features/resources/constants.js b/src/features/resources/constants.js new file mode 100644 index 0000000..9b841a4 --- /dev/null +++ b/src/features/resources/constants.js @@ -0,0 +1,52 @@ +export const resourceStatusFilters = [ + ["", "全部状态"], + ["active", "启用"], + ["disabled", "禁用"], +]; + +export const resourceStatusLabels = { + active: "启用", + disabled: "禁用", +}; + +export const resourceGrantStatusFilters = [ + ["", "全部状态"], + ["succeeded", "成功"], +]; + +export const resourceGrantStatusLabels = { + succeeded: "成功", +}; + +export const resourceGrantSubjectLabels = { + resource: "资源", + resource_group: "资源组", +}; + +export const resourceTypeFilters = [ + ["", "全部类型"], + ["avatar_frame", "头像框"], + ["coin", "金币"], + ["vehicle", "座驾"], + ["chat_bubble", "气泡"], + ["badge", "徽章"], + ["floating_screen", "飘屏"], + ["gift", "礼物"], +]; + +export const resourceTypeLabels = Object.fromEntries(resourceTypeFilters.filter(([value]) => value)); + +export const resourceGroupAssetOptions = [ + ["COIN", "金币"], + ["DIAMOND", "钻石"], +]; + +export const resourceGroupAssetLabels = Object.fromEntries(resourceGroupAssetOptions); + +export const grantStrategyLabels = { + extend_expiry: "延长有效期", + increase_quantity: "增加数量", + new_entitlement: "新增权益", + set_active_flag: "激活标记", + wallet_credit: "钱包入账", +}; diff --git a/src/features/resources/hooks/useResourcePages.js b/src/features/resources/hooks/useResourcePages.js new file mode 100644 index 0000000..13081b2 --- /dev/null +++ b/src/features/resources/hooks/useResourcePages.js @@ -0,0 +1,811 @@ +import { useEffect, useMemo, useState } from "react"; +import { parseForm } from "@/shared/forms/validation"; +import { useRegionOptions } from "@/shared/hooks/useRegionOptions.js"; +import { usePaginatedQuery } from "@/shared/hooks/usePaginatedQuery.js"; +import { useToast } from "@/shared/ui/ToastProvider.jsx"; +import { + createGift, + createResource, + createResourceGroup, + disableGift, + disableResource, + disableResourceGroup, + enableGift, + enableResource, + enableResourceGroup, + grantResource, + grantResourceGroup, + listGifts, + listResourceGrants, + listResourceGroups, + listResources, + updateGift, + updateResource, + updateResourceGroup, +} from "@/features/resources/api"; +import { useResourceAbilities } from "@/features/resources/permissions.js"; +import { + giftFormSchema, + resourceFormSchema, + resourceGrantFormSchema, + resourceGroupCreateFormSchema, +} from "@/features/resources/schema.js"; + +const pageSize = 10; +const dayMillis = 24 * 60 * 60 * 1000; +const emptyData = { items: [], page: 1, pageSize, total: 0 }; +const emptyResourceForm = (resource = {}) => ({ + assetUrl: resource.assetUrl || "", + enabled: resource.status ? resource.status === "active" : true, + name: resource.name || "", + previewUrl: resource.previewUrl || "", + resourceCode: resource.resourceCode || "", + resourceType: resource.resourceType || "avatar_frame", + walletAssetAmount: resource.walletAssetAmount ? String(resource.walletAssetAmount) : "", +}); +const emptyResourceGroupForm = (group = {}) => ({ + description: group.description || "", + enabled: group.status ? group.status === "active" : true, + groupCode: group.groupCode || "", + items: group.items?.length ? group.items.map(groupItemToForm) : [emptyGroupResourceItem()], + name: group.name || "", + sortOrder: group.sortOrder === 0 || group.sortOrder ? String(group.sortOrder) : "0", +}); +const emptyGroupResourceItem = () => ({ + durationDays: "1", + itemType: "resource", + resourceId: "", + sortOrder: "0", + walletAssetAmount: "", + walletAssetType: "", +}); +const emptyGroupWalletAssetItem = (walletAssetType) => ({ + durationDays: "", + itemType: "wallet_asset", + resourceId: "", + sortOrder: "0", + walletAssetAmount: "", + walletAssetType, +}); +const emptyGiftForm = (gift = {}) => ({ + coinPrice: gift.coinPrice === 0 || gift.coinPrice ? String(gift.coinPrice) : "", + enabled: gift.status ? gift.status === "active" : true, + giftId: gift.giftId || "", + giftPointAmount: gift.giftPointAmount === 0 || gift.giftPointAmount ? String(gift.giftPointAmount) : "0", + heatValue: gift.heatValue === 0 || gift.heatValue ? String(gift.heatValue) : "0", + name: gift.name || "", + presentationJson: gift.presentationJson || "{}", + priceVersion: gift.priceVersion || "default", + regionIds: (gift.regionIds?.length ? gift.regionIds : [0]).map(String), + resourceId: gift.resourceId ? String(gift.resourceId) : "", + sortOrder: gift.sortOrder === 0 || gift.sortOrder ? String(gift.sortOrder) : "0", +}); +const emptyGrantForm = () => ({ + durationDays: "0", + groupId: "", + quantity: "1", + reason: "", + resourceId: "", + subjectType: "resource", + targetUserId: "", +}); + +function groupItemToForm(item) { + const sortOrder = item.sortOrder === 0 || item.sortOrder ? String(item.sortOrder) : "0"; + if (item.itemType === "wallet_asset") { + return { + durationDays: "", + itemType: "wallet_asset", + resourceId: "", + sortOrder, + walletAssetAmount: + item.walletAssetAmount === 0 || item.walletAssetAmount ? String(item.walletAssetAmount) : "", + walletAssetType: item.walletAssetType || "", + }; + } + + return { + durationDays: item.durationMs ? formatDurationDays(item.durationMs) : "1", + itemType: "resource", + resourceId: item.resourceId ? String(item.resourceId) : "", + sortOrder, + walletAssetAmount: "", + walletAssetType: "", + }; +} + +export function useResourceListPage() { + const abilities = useResourceAbilities(); + const { showToast } = useToast(); + const [query, setQuery] = useState(""); + const [status, setStatus] = useState(""); + const [resourceType, setResourceType] = useState(""); + const [page, setPage] = useState(1); + const [activeAction, setActiveAction] = useState(""); + const [form, setForm] = useState(emptyResourceForm); + const [selectedResource, setSelectedResource] = useState(null); + const [loadingAction, setLoadingAction] = useState(""); + const filters = useMemo( + () => ({ + keyword: query, + resource_type: resourceType, + status, + }), + [query, resourceType, status], + ); + const result = usePaginatedQuery({ + errorMessage: "加载资源列表失败", + fetcher: listResources, + filters, + page, + pageSize, + queryKey: ["resources", filters, page], + }); + + const openCreateResource = () => { + setForm(emptyResourceForm()); + setSelectedResource(null); + setActiveAction("create"); + }; + + const openEditResource = (resource) => { + if (!resource?.resourceId) { + return; + } + setForm(emptyResourceForm(resource)); + setSelectedResource(resource); + setActiveAction("edit"); + }; + + const closeAction = () => { + setActiveAction(""); + setSelectedResource(null); + }; + + const submitResource = async (event) => { + event.preventDefault(); + const editing = activeAction === "edit"; + await runAction( + editing ? "resource-edit" : "resource-create", + editing ? "资源已更新" : "资源已创建", + async () => { + const payload = buildResourcePayload(parseForm(resourceFormSchema, form)); + if (editing) { + await updateResource(selectedResource.resourceId, payload); + } else { + await createResource(payload); + } + setForm(emptyResourceForm()); + closeAction(); + await result.reload(); + }, + ); + }; + + const toggleResource = async (resource, nextEnabled = resource.status !== "active") => { + if (!abilities.canUpdate || !resource?.resourceId) { + return; + } + const nextStatus = nextEnabled ? "active" : "disabled"; + if ((resource.status === "active") === nextEnabled) { + return; + } + await runAction( + `resource-status-${resource.resourceId}`, + nextEnabled ? "资源已启用" : "资源已禁用", + async () => { + if (nextStatus === "active") { + await enableResource(resource.resourceId); + } else { + await disableResource(resource.resourceId); + } + await result.reload(); + }, + ); + }; + + const runAction = async (action, successMessage, submitter) => { + setLoadingAction(action); + try { + await submitter(); + showToast(successMessage, "success"); + } catch (err) { + showToast(err.message || "操作失败", "error"); + } finally { + setLoadingAction(""); + } + }; + + return { + ...sharedPageState({ page, query, result, setPage, setQuery }), + abilities, + activeAction, + changeResourceType: resetSetter(setResourceType, setPage), + changeStatus: resetSetter(setStatus, setPage), + closeAction, + form, + loadingAction, + openCreateResource, + openEditResource, + resourceType, + selectedResource, + setForm, + status, + submitResource, + toggleResource, + }; +} + +export function useResourceGroupListPage() { + const abilities = useResourceAbilities(); + const { showToast } = useToast(); + const [query, setQuery] = useState(""); + const [status, setStatus] = useState(""); + const [page, setPage] = useState(1); + const [activeAction, setActiveAction] = useState(""); + const [form, setForm] = useState(emptyResourceGroupForm); + const [selectedGroup, setSelectedGroup] = useState(null); + const [loadingAction, setLoadingAction] = useState(""); + const [resourceOptions, setResourceOptions] = useState([]); + const [resourceOptionsLoading, setResourceOptionsLoading] = useState(false); + const filters = useMemo( + () => ({ + keyword: query, + status, + }), + [query, status], + ); + const result = usePaginatedQuery({ + errorMessage: "加载资源组列表失败", + fetcher: listResourceGroups, + filters, + page, + pageSize, + queryKey: ["resource-groups", filters, page], + }); + + useEffect(() => { + if (activeAction !== "create" && activeAction !== "edit") { + return undefined; + } + let ignore = false; + setResourceOptionsLoading(true); + listResources({ page: 1, page_size: 100, status: "active" }) + .then((data) => { + if (!ignore) { + setResourceOptions(toGroupResourceOptions(data.items || [], selectedGroup)); + } + }) + .catch((err) => { + if (!ignore) { + showToast(err.message || "加载资源选项失败", "error"); + } + }) + .finally(() => { + if (!ignore) { + setResourceOptionsLoading(false); + } + }); + return () => { + ignore = true; + }; + }, [activeAction, selectedGroup, showToast]); + + const openCreateGroup = () => { + setForm(emptyResourceGroupForm()); + setSelectedGroup(null); + setActiveAction("create"); + }; + + const openEditGroup = (group) => { + if (!group?.groupId) { + return; + } + setForm(emptyResourceGroupForm(group)); + setSelectedGroup(group); + setActiveAction("edit"); + }; + + const closeAction = () => { + setActiveAction(""); + setSelectedGroup(null); + }; + + const addResourceItem = () => { + setForm((current) => ({ ...current, items: [...current.items, emptyGroupResourceItem()] })); + }; + + const addWalletAssetItem = (walletAssetType) => { + setForm((current) => ({ ...current, items: [...current.items, emptyGroupWalletAssetItem(walletAssetType)] })); + }; + + const updateGroupItem = (index, patch) => { + setForm((current) => ({ + ...current, + items: current.items.map((item, itemIndex) => (itemIndex === index ? { ...item, ...patch } : item)), + })); + }; + + const removeGroupItem = (index) => { + setForm((current) => ({ + ...current, + items: current.items.filter((_, itemIndex) => itemIndex !== index), + })); + }; + + const submitGroup = async (event) => { + event.preventDefault(); + const editing = activeAction === "edit"; + const action = editing ? "group-edit" : "group-create"; + const successMessage = editing ? "资源组已更新" : "资源组已创建"; + setLoadingAction(action); + try { + const payload = buildResourceGroupPayload(parseForm(resourceGroupCreateFormSchema, form)); + if (editing) { + await updateResourceGroup(selectedGroup.groupId, payload); + } else { + await createResourceGroup(payload); + } + showToast(successMessage, "success"); + setForm(emptyResourceGroupForm()); + closeAction(); + await result.reload(); + } catch (err) { + showToast(err.message || "操作失败", "error"); + } finally { + setLoadingAction(""); + } + }; + + const toggleGroup = async (group, nextEnabled = group.status !== "active") => { + if (!abilities.canUpdateGroup || !group?.groupId) { + return; + } + if ((group.status === "active") === nextEnabled) { + return; + } + setLoadingAction(`group-status-${group.groupId}`); + try { + if (nextEnabled) { + await enableResourceGroup(group.groupId); + } else { + await disableResourceGroup(group.groupId); + } + showToast(nextEnabled ? "资源组已启用" : "资源组已禁用", "success"); + await result.reload(); + } catch (err) { + showToast(err.message || "操作失败", "error"); + } finally { + setLoadingAction(""); + } + }; + + return { + ...sharedPageState({ page, query, result, setPage, setQuery }), + abilities, + activeAction, + addResourceItem, + addWalletAssetItem, + changeStatus: resetSetter(setStatus, setPage), + closeAction, + form, + loadingAction, + openCreateGroup, + openEditGroup, + removeGroupItem, + resourceOptions, + resourceOptionsLoading, + selectedGroup, + setForm, + status, + submitGroup, + toggleGroup, + updateGroupItem, + }; +} + +export function useGiftListPage() { + const abilities = useResourceAbilities(); + const { showToast } = useToast(); + const [query, setQuery] = useState(""); + const [status, setStatus] = useState(""); + const [regionId, setRegionId] = useState(""); + const [page, setPage] = useState(1); + const [activeAction, setActiveAction] = useState(""); + const [form, setForm] = useState(emptyGiftForm); + const [selectedGift, setSelectedGift] = useState(null); + const [loadingAction, setLoadingAction] = useState(""); + const [resourceOptions, setResourceOptions] = useState([]); + const [resourceOptionsLoading, setResourceOptionsLoading] = useState(false); + const { loadingRegions, regionOptions } = useRegionOptions(); + const filters = useMemo( + () => ({ + keyword: query, + region_id: regionId, + status, + }), + [query, regionId, status], + ); + const result = usePaginatedQuery({ + errorMessage: "加载礼物列表失败", + fetcher: listGifts, + filters, + page, + pageSize, + queryKey: ["gifts", filters, page], + }); + + useEffect(() => { + if (activeAction !== "create" && activeAction !== "edit") { + return undefined; + } + let ignore = false; + setResourceOptionsLoading(true); + listResources({ page: 1, page_size: 100, resource_type: "gift" }) + .then((data) => { + if (!ignore) { + setResourceOptions(toGiftResourceOptions(data.items || [], selectedGift)); + } + }) + .catch((err) => { + if (!ignore) { + showToast(err.message || "加载礼物资源失败", "error"); + } + }) + .finally(() => { + if (!ignore) { + setResourceOptionsLoading(false); + } + }); + return () => { + ignore = true; + }; + }, [activeAction, selectedGift, showToast]); + + const openCreateGift = () => { + setForm(emptyGiftForm()); + setSelectedGift(null); + setActiveAction("create"); + }; + + const openEditGift = (gift) => { + if (!gift?.giftId) { + return; + } + setForm(emptyGiftForm(gift)); + setSelectedGift(gift); + setActiveAction("edit"); + }; + + const closeAction = () => { + setActiveAction(""); + setSelectedGift(null); + }; + + const submitGift = async (event) => { + event.preventDefault(); + const editing = activeAction === "edit"; + if (editing && !selectedGift?.giftId) { + return; + } + setLoadingAction(editing ? "gift-edit" : "gift-create"); + try { + const payload = buildGiftPayload(parseForm(giftFormSchema, form)); + if (editing) { + await updateGift(selectedGift.giftId, payload); + } else { + await createGift(payload); + } + showToast(editing ? "礼物已更新" : "礼物已创建", "success"); + closeAction(); + await result.reload(); + } catch (err) { + showToast(err.message || "操作失败", "error"); + } finally { + setLoadingAction(""); + } + }; + + const toggleGift = async (gift, nextEnabled = gift.status !== "active") => { + if (!abilities.canStatusGift || !gift?.giftId) { + return; + } + if ((gift.status === "active") === nextEnabled) { + return; + } + setLoadingAction(`gift-status-${gift.giftId}`); + try { + if (nextEnabled) { + await enableGift(gift.giftId); + } else { + await disableGift(gift.giftId); + } + showToast(nextEnabled ? "礼物已启用" : "礼物已禁用", "success"); + await result.reload(); + } catch (err) { + showToast(err.message || "操作失败", "error"); + } finally { + setLoadingAction(""); + } + }; + + return { + ...sharedPageState({ page, query, result, setPage, setQuery }), + abilities, + activeAction, + changeRegionId: resetSetter(setRegionId, setPage), + changeStatus: resetSetter(setStatus, setPage), + closeAction, + form, + loadingAction, + loadingRegions, + openCreateGift, + openEditGift, + regionId, + regionOptions, + resourceOptions, + resourceOptionsLoading, + selectedGift, + setForm, + status, + submitGift, + toggleGift, + }; +} + +export function useResourceGrantListPage() { + const abilities = useResourceAbilities(); + const { showToast } = useToast(); + const [query, setQuery] = useState(""); + const [status, setStatus] = useState(""); + const [page, setPage] = useState(1); + const [activeAction, setActiveAction] = useState(""); + const [form, setForm] = useState(emptyGrantForm); + const [resourceOptions, setResourceOptions] = useState([]); + const [groupOptions, setGroupOptions] = useState([]); + const [optionsLoading, setOptionsLoading] = useState(false); + const [loadingAction, setLoadingAction] = useState(""); + const filters = useMemo( + () => ({ + status, + target_user_id: query.trim(), + }), + [query, status], + ); + const result = usePaginatedQuery({ + errorMessage: "加载资源赠送记录失败", + fetcher: listResourceGrants, + filters, + page, + pageSize, + queryKey: ["resource-grants", filters, page], + }); + + useEffect(() => { + if (activeAction !== "create") { + return undefined; + } + let ignore = false; + setOptionsLoading(true); + Promise.all([ + listResources({ page: 1, page_size: 100, status: "active" }), + listResourceGroups({ page: 1, page_size: 100, status: "active" }), + ]) + .then(([resources, groups]) => { + if (!ignore) { + setResourceOptions((resources.items || []).filter((resource) => resource.grantable !== false)); + setGroupOptions(groups.items || []); + } + }) + .catch((err) => { + if (!ignore) { + showToast(err.message || "加载资源选项失败", "error"); + } + }) + .finally(() => { + if (!ignore) { + setOptionsLoading(false); + } + }); + return () => { + ignore = true; + }; + }, [activeAction, showToast]); + + const openCreateGrant = () => { + setForm(emptyGrantForm()); + setActiveAction("create"); + }; + + const closeAction = () => { + setActiveAction(""); + }; + + const submitGrant = async (event) => { + event.preventDefault(); + if (!abilities.canCreateGrant) { + return; + } + setLoadingAction("grant-create"); + try { + const payload = buildGrantPayload(parseForm(resourceGrantFormSchema, form)); + if (payload.subjectType === "resource") { + await grantResource(payload.resource); + } else { + await grantResourceGroup(payload.group); + } + showToast("资源已赠送", "success"); + closeAction(); + setForm(emptyGrantForm()); + await result.reload(); + } catch (err) { + showToast(err.message || "操作失败", "error"); + } finally { + setLoadingAction(""); + } + }; + + return { + ...sharedPageState({ page, query, result, setPage, setQuery }), + abilities, + activeAction, + changeStatus: resetSetter(setStatus, setPage), + closeAction, + form, + groupOptions, + loadingAction, + openCreateGrant, + optionsLoading, + resourceOptions, + setForm, + status, + submitGrant, + }; +} + +function sharedPageState({ page, query, result, setPage, setQuery }) { + return { + data: result.data || emptyData, + error: result.error, + loading: result.loading, + page, + query, + reload: result.reload, + setPage, + changeQuery: resetSetter(setQuery, setPage), + }; +} + +function resetSetter(setValue, setPage) { + return (value) => { + setValue(value); + setPage(1); + }; +} + +function buildResourcePayload(form) { + const resourceType = form.resourceType; + const isCoin = resourceType === "coin"; + return { + amount: isCoin ? Number(form.walletAssetAmount) : 0, + assetUrl: form.assetUrl.trim(), + name: form.name.trim(), + previewUrl: form.previewUrl.trim(), + resourceCode: form.resourceCode.trim(), + resourceType, + sortOrder: 0, + status: form.enabled ? "active" : "disabled", + }; +} + +function buildResourceGroupPayload(form) { + return { + description: (form.description || "").trim(), + groupCode: form.groupCode.trim(), + items: form.items.map((item, index) => { + const sortOrder = Number(item.sortOrder || index); + if (item.itemType === "wallet_asset") { + return { + amount: Number(item.walletAssetAmount), + assetType: item.walletAssetType, + itemType: "wallet_asset", + sortOrder: Number.isInteger(sortOrder) ? sortOrder : index, + }; + } + return { + durationDays: Number(item.durationDays), + itemType: "resource", + resourceId: Number(item.resourceId), + sortOrder: Number.isInteger(sortOrder) ? sortOrder : index, + }; + }), + name: form.name.trim(), + sortOrder: Number(form.sortOrder || 0), + status: form.enabled ? "active" : "disabled", + }; +} + +function buildGiftPayload(form) { + return { + coinPrice: Number(form.coinPrice), + effectiveAtMs: Date.now(), + giftId: form.giftId.trim(), + giftPointAmount: Number(form.giftPointAmount || 0), + heatValue: Number(form.heatValue || 0), + name: form.name.trim(), + presentationJson: form.presentationJson?.trim() || "{}", + priceVersion: form.priceVersion.trim(), + regionIds: form.regionIds.map(Number), + resourceId: Number(form.resourceId), + sortOrder: Number(form.sortOrder || 0), + status: form.enabled ? "active" : "disabled", + }; +} + +function buildGrantPayload(form) { + const targetUserId = Number(form.targetUserId); + const reason = form.reason.trim(); + const commandId = makeCommandId(form.subjectType === "resource" ? "resource-grant" : "resource-group-grant"); + if (form.subjectType === "resource") { + return { + resource: { + commandId, + durationMs: Math.round(Number(form.durationDays || 0) * dayMillis), + quantity: Number(form.quantity || 1), + reason, + resourceId: Number(form.resourceId), + targetUserId, + }, + subjectType: "resource", + }; + } + return { + group: { + commandId, + groupId: Number(form.groupId), + reason, + targetUserId, + }, + subjectType: "resource_group", + }; +} + +function makeCommandId(prefix) { + return `${prefix}-${Date.now()}`; +} + +function toGroupResourceOptions(items, selectedGroup) { + const selectedResources = (selectedGroup?.items || []) + .map((item) => item.resource) + .filter((resource) => resource?.resourceId); + return uniqueResources([...items, ...selectedResources]).filter( + (resource) => resource.resourceType !== "coin" && resource.status === "active" && resource.grantable !== false, + ); +} + +function toGiftResourceOptions(items, selectedGift) { + const selectedResource = selectedGift?.resource?.resourceId + ? [selectedGift.resource] + : selectedGift?.resourceId + ? [{ name: String(selectedGift.resourceId), resourceId: selectedGift.resourceId, resourceType: "gift" }] + : []; + return uniqueResources([...items, ...selectedResource]).filter( + (resource) => resource.resourceType === "gift" || resource.resourceId === selectedGift?.resourceId, + ); +} + +function uniqueResources(resources) { + const seen = new Set(); + return resources.filter((resource) => { + if (!resource?.resourceId || seen.has(resource.resourceId)) { + return false; + } + seen.add(resource.resourceId); + return true; + }); +} + +function formatDurationDays(durationMs) { + const days = Number(durationMs || 0) / dayMillis; + return Number.isInteger(days) ? String(days) : String(days.toFixed(1)); +} diff --git a/src/features/resources/pages/GiftListPage.jsx b/src/features/resources/pages/GiftListPage.jsx new file mode 100644 index 0000000..66f2702 --- /dev/null +++ b/src/features/resources/pages/GiftListPage.jsx @@ -0,0 +1,382 @@ +import Add from "@mui/icons-material/Add"; +import CardGiftcardOutlined from "@mui/icons-material/CardGiftcardOutlined"; +import EditOutlined from "@mui/icons-material/EditOutlined"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import MenuItem from "@mui/material/MenuItem"; +import Switch from "@mui/material/Switch"; +import TextField from "@mui/material/TextField"; +import { AdminFormDialog } from "@/shared/ui/AdminFormDialog.jsx"; +import { DataState } from "@/shared/ui/DataState.jsx"; +import { DataTable } from "@/shared/ui/DataTable.jsx"; +import { RegionSelect } from "@/shared/ui/RegionSelect.jsx"; +import { + AdminActionIconButton, + AdminFilterSelect, + AdminListBody, + AdminListPage, + AdminRowActions, + AdminListToolbar, + AdminSearchBox +} from "@/shared/ui/AdminListLayout.jsx"; +import { PaginationBar } from "@/shared/ui/PaginationBar.jsx"; +import { formatMillis } from "@/shared/utils/time.js"; +import { resourceStatusFilters } from "@/features/resources/constants.js"; +import { useGiftListPage } from "@/features/resources/hooks/useResourcePages.js"; +import styles from "@/features/resources/resources.module.css"; + +const baseColumns = [ + { + key: "gift", + label: "礼物", + width: "minmax(260px, 1.4fr)", + render: (gift) => + }, + { + key: "resource", + label: "资源", + width: "minmax(220px, 1.1fr)", + render: (gift) => + }, + { + key: "price", + label: "价格", + width: "minmax(120px, 0.65fr)", + render: (gift) => formatNumber(gift.coinPrice) + }, + { + key: "income", + label: "积分 / 热度", + width: "minmax(140px, 0.8fr)", + render: (gift) => ( +
+ {formatNumber(gift.giftPointAmount)} + {formatNumber(gift.heatValue)} +
+ ) + }, + { + key: "regions", + label: "区域", + width: "minmax(180px, 0.95fr)" + }, + { + key: "status", + label: "状态", + width: "minmax(92px, 0.55fr)" + }, + { + key: "time", + label: "更新时间", + width: "minmax(170px, 0.9fr)", + render: (gift) => formatMillis(gift.updatedAtMs || gift.createdAtMs) + }, + { + key: "actions", + label: "操作", + width: "minmax(76px, 0.4fr)" + } +]; + +export function GiftListPage() { + const page = useGiftListPage(); + const items = page.data.items || []; + const total = page.data.total || 0; + const isEditing = page.activeAction === "edit"; + const giftRegionOptions = [{ label: "全局 · 0", value: "0" }, ...page.regionOptions]; + const regionLabelById = buildRegionLabelMap(giftRegionOptions); + const tableColumns = baseColumns.map((column) => { + if (column.key === "regions") { + return { + ...column, + render: (gift) => + }; + } + if (column.key === "status") { + return { + ...column, + render: (gift) => + }; + } + if (column.key === "actions") { + return { + ...column, + render: (gift) => + }; + } + return column; + }); + + return ( + + + + + + + } + actions={ + page.abilities.canCreateGift ? ( + + + + ) : null + } + /> + + + gift.giftId} /> + {total > 0 ? ( + + ) : null} + + + + + ); +} + +function GiftRowActions({ gift, page }) { + return ( + + page.openEditGift(gift)} + > + + + + ); +} + +function GiftStatusSwitch({ gift, page }) { + const checked = gift.status === "active"; + return ( + page.toggleGift(gift, event.target.checked)} + /> + ); +} + +function GiftFormDialog({ disabled, form, loading, mode, onClose, onSubmit, open, page, regionOptions }) { + const submitDisabled = disabled || loading || page.resourceOptionsLoading || page.loadingRegions; + return ( + + page.setForm({ ...form, name: event.target.value })} + /> + page.setForm({ ...form, giftId: event.target.value })} + /> + page.setForm({ ...form, resourceId: event.target.value })} + > + {page.resourceOptions.map((resource) => ( + + {resource.name || resource.resourceCode || resource.resourceId} + + ))} + + page.setForm({ ...form, coinPrice: event.target.value })} + /> + page.setForm({ ...form, giftPointAmount: event.target.value })} + /> + page.setForm({ ...form, heatValue: event.target.value })} + /> + page.setForm({ ...form, sortOrder: event.target.value })} + /> + page.setForm({ ...form, priceVersion: event.target.value })} + /> + page.setForm({ ...form, regionIds })} + /> + page.setForm({ ...form, presentationJson: event.target.value })} + /> + page.setForm({ ...form, enabled: event.target.checked })} + /> + } + label={form.enabled ? "启用" : "禁用"} + /> + + ); +} + +function GiftRegionSelect({ disabled, labels, onChange, options, value }) { + const selected = value || []; + const mergedOptions = [...new Set([...options.map((option) => option.value), ...selected])].sort( + (left, right) => Number(left) - Number(right) + ); + return ( + + selectedValue.map((regionId) => labels[String(regionId)] || regionId).join("、") + }} + value={selected} + onChange={(event) => { + const nextValue = event.target.value; + onChange(typeof nextValue === "string" ? nextValue.split(",") : nextValue); + }} + > + {mergedOptions.map((regionId) => ( + + {labels[String(regionId)] || regionId} + + ))} + + ); +} + +function RegionTags({ regionIds = [], regionLabelById }) { + const items = [...new Set(regionIds)].sort((left, right) => Number(left) - Number(right)); + if (!items.length) { + return "-"; + } + return ( +
+ {items.map((regionId) => ( + {regionLabelById[String(regionId)] || regionId} + ))} +
+ ); +} + +function GiftIdentity({ gift }) { + const preview = imageURL(gift.resource?.previewUrl || gift.resource?.assetUrl); + return ( +
+ + {preview ? : } + +
+ {gift.name || "-"} + {gift.giftId} +
+
+ ); +} + +function ResourceRef({ gift }) { + const resource = gift.resource || {}; + return ( +
+ {resource.name || "-"} + {resource.resourceCode || gift.resourceId || "-"} +
+ ); +} + +function imageURL(value) { + const url = String(value || "").trim(); + if (!url) { + return ""; + } + return /\.(avif|gif|jpe?g|png|webp)(\?|#|$)/i.test(url) ? url : ""; +} + +function formatNumber(value) { + return Number(value || 0).toLocaleString("zh-CN"); +} + +function buildRegionLabelMap(options = []) { + return options.reduce((acc, option) => { + acc[option.value] = shortRegionLabel(option.label); + return acc; + }, {}); +} + +function shortRegionLabel(label) { + return String(label || "").split(" · ")[0] || String(label || ""); +} diff --git a/src/features/resources/pages/ResourceGrantListPage.jsx b/src/features/resources/pages/ResourceGrantListPage.jsx new file mode 100644 index 0000000..7718756 --- /dev/null +++ b/src/features/resources/pages/ResourceGrantListPage.jsx @@ -0,0 +1,263 @@ +import Add from "@mui/icons-material/Add"; +import MenuItem from "@mui/material/MenuItem"; +import TextField from "@mui/material/TextField"; +import { AdminFormDialog } from "@/shared/ui/AdminFormDialog.jsx"; +import { DataState } from "@/shared/ui/DataState.jsx"; +import { DataTable } from "@/shared/ui/DataTable.jsx"; +import { + AdminActionIconButton, + AdminFilterSelect, + AdminListBody, + AdminListPage, + AdminListToolbar, + AdminSearchBox, +} from "@/shared/ui/AdminListLayout.jsx"; +import { PaginationBar } from "@/shared/ui/PaginationBar.jsx"; +import { formatMillis } from "@/shared/utils/time.js"; +import { + resourceGrantStatusFilters, + resourceGrantStatusLabels, + resourceGrantSubjectLabels, +} from "@/features/resources/constants.js"; +import { useResourceGrantListPage } from "@/features/resources/hooks/useResourcePages.js"; +import styles from "@/features/resources/resources.module.css"; + +const grantColumns = [ + { + key: "grant", + label: "赠送记录", + width: "minmax(240px, 1.2fr)", + render: (grant) => ( +
+ {grant.grantId || "-"} + {grant.commandId || "-"} +
+ ), + }, + { + key: "target", + label: "用户 ID", + width: "minmax(120px, 0.7fr)", + render: (grant) => grant.targetUserId || "-", + }, + { + key: "subject", + label: "对象", + width: "minmax(160px, 0.8fr)", + render: (grant) => ( +
+ {resourceGrantSubjectLabels[grant.grantSubjectType] || grant.grantSubjectType || "-"} + {grant.grantSubjectId || "-"} +
+ ), + }, + { + key: "items", + label: "发放内容", + width: "minmax(180px, 1fr)", + render: (grant) => , + }, + { + key: "status", + label: "状态", + width: "minmax(100px, 0.55fr)", + render: (grant) => ( + + + {resourceGrantStatusLabels[grant.status] || grant.status || "-"} + + ), + }, + { + key: "reason", + label: "原因", + width: "minmax(160px, 1fr)", + render: (grant) => grant.reason || "-", + }, + { + key: "time", + label: "创建时间", + width: "minmax(170px, 0.9fr)", + render: (grant) => formatMillis(grant.createdAtMs), + }, +]; + +export function ResourceGrantListPage() { + const page = useResourceGrantListPage(); + const items = page.data.items || []; + const total = page.data.total || 0; + + return ( + + + + + + } + actions={ + page.abilities.canCreateGrant ? ( + + + + ) : null + } + /> + + + grant.grantId} /> + {total > 0 ? ( + + ) : null} + + + + + ); +} + +function ResourceGrantDialog({ + disabled, + form, + groupOptions, + loading, + onClose, + onSubmit, + open, + optionsLoading, + resourceOptions, + setForm, +}) { + const submitDisabled = disabled || loading || optionsLoading; + return ( + + setForm({ ...form, targetUserId: event.target.value })} + /> + setForm({ ...form, groupId: "", resourceId: "", subjectType: event.target.value })} + > + 资源 + 资源组 + + {form.subjectType === "resource" ? ( + <> + setForm({ ...form, resourceId: event.target.value })} + > + {resourceOptions.map((resource) => ( + + {resource.name || resource.resourceCode || resource.resourceId} + + ))} + + setForm({ ...form, quantity: event.target.value })} + /> + setForm({ ...form, durationDays: event.target.value })} + /> + + ) : ( + setForm({ ...form, groupId: event.target.value })} + > + {groupOptions.map((group) => ( + + {group.name || group.groupCode || group.groupId} + + ))} + + )} + setForm({ ...form, reason: event.target.value })} + /> + + ); +} + +function GrantItems({ grant }) { + const items = grant.items || []; + if (!items.length) { + return "-"; + } + return ( +
+ {items.slice(0, 2).map((item) => ( + + {item.resourceId || "-"} / {item.quantity || 0} + {item.durationMs ? ` / ${formatDuration(item.durationMs)}` : ""} + + ))} + {items.length > 2 ? +{items.length - 2} : null} +
+ ); +} + +function formatDuration(durationMs) { + const days = Number(durationMs || 0) / (24 * 60 * 60 * 1000); + return Number.isInteger(days) ? `${days}天` : `${days.toFixed(1)}天`; +} diff --git a/src/features/resources/pages/ResourceGroupListPage.jsx b/src/features/resources/pages/ResourceGroupListPage.jsx new file mode 100644 index 0000000..cbc4007 --- /dev/null +++ b/src/features/resources/pages/ResourceGroupListPage.jsx @@ -0,0 +1,380 @@ +import Add from "@mui/icons-material/Add"; +import CategoryOutlined from "@mui/icons-material/CategoryOutlined"; +import DeleteOutlineOutlined from "@mui/icons-material/DeleteOutlineOutlined"; +import DiamondOutlined from "@mui/icons-material/DiamondOutlined"; +import EditOutlined from "@mui/icons-material/EditOutlined"; +import Inventory2Outlined from "@mui/icons-material/Inventory2Outlined"; +import MonetizationOnOutlined from "@mui/icons-material/MonetizationOnOutlined"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import MenuItem from "@mui/material/MenuItem"; +import Switch from "@mui/material/Switch"; +import TextField from "@mui/material/TextField"; +import { + AdminFormDialog, + AdminFormFieldGrid, + AdminFormInlineActions, + AdminFormList, + AdminFormListRow, + AdminFormSection, +} from "@/shared/ui/AdminFormDialog.jsx"; +import { Button } from "@/shared/ui/Button.jsx"; +import { DataState } from "@/shared/ui/DataState.jsx"; +import { DataTable } from "@/shared/ui/DataTable.jsx"; +import { + AdminActionIconButton, + AdminFilterSelect, + AdminListBody, + AdminListPage, + AdminListToolbar, + AdminRowActions, + AdminSearchBox, +} from "@/shared/ui/AdminListLayout.jsx"; +import { PaginationBar } from "@/shared/ui/PaginationBar.jsx"; +import { formatMillis } from "@/shared/utils/time.js"; +import { + resourceGroupAssetLabels, + resourceStatusFilters, + resourceTypeLabels, +} from "@/features/resources/constants.js"; +import { useResourceGroupListPage } from "@/features/resources/hooks/useResourcePages.js"; +import styles from "@/features/resources/resources.module.css"; + +const dayMillis = 24 * 60 * 60 * 1000; + +const baseColumns = [ + { + key: "group", + label: "资源组", + width: "minmax(260px, 1.5fr)", + render: (group) => , + }, + { + key: "items", + label: "组内资源", + width: "minmax(320px, 1.7fr)", + render: (group) => , + }, + { + key: "sort", + label: "排序", + width: "minmax(80px, 0.45fr)", + render: (group) => group.sortOrder ?? 0, + }, + { + key: "status", + label: "状态", + width: "minmax(92px, 0.55fr)", + }, + { + key: "time", + label: "更新时间", + width: "minmax(170px, 0.9fr)", + render: (group) => formatMillis(group.updatedAtMs || group.createdAtMs), + }, + { + key: "actions", + label: "操作", + width: "minmax(76px, 0.4fr)", + }, +]; + +export function ResourceGroupListPage() { + const page = useResourceGroupListPage(); + const items = page.data.items || []; + const total = page.data.total || 0; + const columns = baseColumns.map((column) => + column.key === "status" + ? { + ...column, + render: (group) => , + } + : column.key === "actions" + ? { + ...column, + render: (group) => , + } + : column, + ); + + return ( + + + + + + } + actions={ + page.abilities.canCreateGroup ? ( + + + + ) : null + } + /> + + + group.groupId} /> + {total > 0 ? ( + + ) : null} + + + + + ); +} + +function ResourceGroupRowActions({ group, page }) { + return ( + + page.openEditGroup(group)} + > + + + + ); +} + +function ResourceGroupStatusSwitch({ group, page }) { + const checked = group.status === "active"; + return ( + page.toggleGroup(group, event.target.checked)} + /> + ); +} + +function ResourceGroupFormDialog({ disabled, form, loading, mode, onClose, onSubmit, open, page }) { + const submitDisabled = disabled || loading || page.resourceOptionsLoading; + return ( + + + page.setForm({ ...form, name: event.target.value })} + /> + page.setForm({ ...form, groupCode: event.target.value })} + /> + page.setForm({ ...form, sortOrder: event.target.value })} + /> + + page.setForm({ ...form, description: event.target.value })} + /> + page.setForm({ ...form, enabled: event.target.checked })} + /> + } + label={form.enabled ? "启用" : "禁用"} + /> + + + + + + } + > + + {form.items.map((item, index) => ( + + ))} + + + + ); +} + +function ResourceGroupItemEditor({ disabled, index, item, page }) { + if (item.itemType === "wallet_asset") { + const label = resourceGroupAssetLabels[item.walletAssetType] || "钱包资产"; + return ( + + + page.updateGroupItem(index, { walletAssetAmount: event.target.value })} + /> + page.updateGroupItem(index, { sortOrder: event.target.value })} + /> + page.removeGroupItem(index)}> + + + + ); + } + + return ( + + page.updateGroupItem(index, { resourceId: event.target.value })} + > + {page.resourceOptions.map((resource) => ( + + {resource.name || resource.resourceCode || resource.resourceId} + + ))} + + page.updateGroupItem(index, { durationDays: event.target.value })} + /> + page.updateGroupItem(index, { sortOrder: event.target.value })} + /> + page.removeGroupItem(index)}> + + + + ); +} + +function GroupIdentity({ group }) { + return ( +
+ + + +
+ {group.name || "-"} + {group.groupCode || group.groupId} +
+
+ ); +} + +function GroupItems({ group }) { + const items = group.items || []; + if (!items.length) { + return "-"; + } + return ( +
+ {items.slice(0, 4).map((item) => { + if (item.itemType === "wallet_asset") { + const label = resourceGroupAssetLabels[item.walletAssetType] || item.walletAssetType || "钱包资产"; + return ( + + {label} {formatNumber(item.walletAssetAmount)} + + ); + } + const resource = item.resource || {}; + const type = resourceTypeLabels[resource.resourceType] || resource.resourceType || "资源"; + return ( + + {resource.name || item.resourceId} · {formatDurationDays(item.durationMs)}天 · {type} + + ); + })} + {items.length > 4 ? +{items.length - 4} : null} +
+ ); +} + +function formatDurationDays(durationMs) { + const days = Number(durationMs || 0) / dayMillis; + return Number.isInteger(days) ? days : days.toFixed(1); +} + +function formatNumber(value) { + return Number(value || 0).toLocaleString("zh-CN"); +} diff --git a/src/features/resources/pages/ResourceListPage.jsx b/src/features/resources/pages/ResourceListPage.jsx new file mode 100644 index 0000000..f73fb9a --- /dev/null +++ b/src/features/resources/pages/ResourceListPage.jsx @@ -0,0 +1,316 @@ +import Add from "@mui/icons-material/Add"; +import EditOutlined from "@mui/icons-material/EditOutlined"; +import Inventory2Outlined from "@mui/icons-material/Inventory2Outlined"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import MenuItem from "@mui/material/MenuItem"; +import Switch from "@mui/material/Switch"; +import TextField from "@mui/material/TextField"; +import { AdminFormDialog } from "@/shared/ui/AdminFormDialog.jsx"; +import { DataState } from "@/shared/ui/DataState.jsx"; +import { DataTable } from "@/shared/ui/DataTable.jsx"; +import { UploadField } from "@/shared/ui/UploadField.jsx"; +import { + AdminActionIconButton, + AdminFilterSelect, + AdminListBody, + AdminListPage, + AdminRowActions, + AdminListToolbar, + AdminSearchBox, +} from "@/shared/ui/AdminListLayout.jsx"; +import { PaginationBar } from "@/shared/ui/PaginationBar.jsx"; +import { formatMillis } from "@/shared/utils/time.js"; +import { + grantStrategyLabels, + resourceStatusFilters, + resourceTypeFilters, + resourceTypeLabels, +} from "@/features/resources/constants.js"; +import { useResourceListPage } from "@/features/resources/hooks/useResourcePages.js"; +import styles from "@/features/resources/resources.module.css"; + +const resourceTypeOptions = resourceTypeFilters.filter(([value]) => value); + +const baseColumns = [ + { + key: "resource", + label: "资源", + width: "minmax(260px, 1.5fr)", + render: (resource) => , + }, + { + key: "type", + label: "类型", + width: "minmax(120px, 0.7fr)", + render: (resource) => resourceTypeLabels[resource.resourceType] || resource.resourceType || "-", + }, + { + key: "grant", + label: "发放规则", + width: "minmax(170px, 1fr)", + render: (resource) => , + }, + { + key: "scopes", + label: "使用范围", + width: "minmax(180px, 1fr)", + render: (resource) => , + }, + { + key: "status", + label: "状态", + width: "minmax(92px, 0.55fr)", + }, + { + key: "time", + label: "更新时间", + width: "minmax(170px, 0.9fr)", + render: (resource) => formatMillis(resource.updatedAtMs || resource.createdAtMs), + }, + { + key: "actions", + label: "操作", + width: "minmax(76px, 0.4fr)", + }, +]; + +export function ResourceListPage() { + const page = useResourceListPage(); + const items = page.data.items || []; + const total = page.data.total || 0; + const createDisabled = !page.abilities.canCreate || !page.abilities.canUpload; + const columns = baseColumns.map((column) => + column.key === "status" + ? { + ...column, + render: (resource) => , + } + : column.key === "actions" + ? { + ...column, + render: (resource) => , + } + : column, + ); + + return ( + + + + + + + } + actions={ + page.abilities.canCreate ? ( + + + + ) : null + } + /> + + + resource.resourceId} + /> + {total > 0 ? ( + + ) : null} + + + + + ); +} + +function ResourceRowActions({ page, resource }) { + return ( + + page.openEditResource(resource)} + > + + + + ); +} + +function ResourceFormDialog({ disabled, form, loading, mode, onClose, onSubmit, open, setForm, uploadDisabled }) { + const submitDisabled = disabled || loading; + return ( + + setForm({ ...form, name: event.target.value })} + /> + setForm({ ...form, resourceCode: event.target.value })} + /> + setForm({ ...form, resourceType: event.target.value, walletAssetAmount: "" })} + > + {resourceTypeOptions.map(([value, label]) => ( + + {label} + + ))} + + {form.resourceType === "coin" ? ( + setForm({ ...form, walletAssetAmount: event.target.value })} + /> + ) : null} + setForm({ ...form, previewUrl })} + /> + setForm({ ...form, assetUrl })} + /> + setForm({ ...form, enabled: event.target.checked })} + /> + } + label={form.enabled ? "启用" : "禁用"} + /> + + ); +} + +function ResourceIdentity({ resource }) { + const preview = imageURL(resource.previewUrl || resource.assetUrl); + return ( +
+ + {preview ? : } + +
+ {resource.name || "-"} + {resource.resourceCode || resource.resourceId} +
+
+ ); +} + +function GrantInfo({ resource }) { + const strategy = grantStrategyLabels[resource.grantStrategy] || resource.grantStrategy || "-"; + if (resource.walletAssetType || resource.walletAssetAmount) { + return ( +
+ {strategy} + + {resource.walletAssetType || "-"} {formatNumber(resource.walletAssetAmount)} + +
+ ); + } + return strategy; +} + +function ResourceStatusSwitch({ page, resource }) { + const checked = resource.status === "active"; + return ( + page.toggleResource(resource, event.target.checked)} + /> + ); +} + +function TagList({ values = [] }) { + const items = values.filter(Boolean); + if (!items.length) { + return "-"; + } + return ( +
+ {items.map((value) => ( + + {value} + + ))} +
+ ); +} + +function imageURL(value) { + const url = String(value || "").trim(); + if (!url) { + return ""; + } + return /\.(avif|gif|jpe?g|png|webp)(\?|#|$)/i.test(url) ? url : ""; +} + +function formatNumber(value) { + return Number(value || 0).toLocaleString("zh-CN"); +} diff --git a/src/features/resources/permissions.js b/src/features/resources/permissions.js new file mode 100644 index 0000000..8c925da --- /dev/null +++ b/src/features/resources/permissions.js @@ -0,0 +1,20 @@ +import { useAuth } from "@/app/auth/AuthProvider.jsx"; +import { PERMISSIONS } from "@/app/permissions"; + +export function useResourceAbilities() { + const { can } = useAuth(); + + return { + canCreate: can(PERMISSIONS.resourceCreate), + canCreateGift: can(PERMISSIONS.giftCreate), + canCreateGrant: can(PERMISSIONS.resourceGrantCreate), + canCreateGroup: can(PERMISSIONS.resourceGroupCreate), + canStatusGift: can(PERMISSIONS.giftStatus), + canUpdateGroup: can(PERMISSIONS.resourceGroupUpdate), + canUpdateGift: can(PERMISSIONS.giftUpdate), + canUpdate: can(PERMISSIONS.resourceUpdate), + canUpload: can(PERMISSIONS.uploadCreate), + canView: can(PERMISSIONS.resourceView), + canViewGrant: can(PERMISSIONS.resourceGrantView) + }; +} diff --git a/src/features/resources/resources.module.css b/src/features/resources/resources.module.css new file mode 100644 index 0000000..9e549f3 --- /dev/null +++ b/src/features/resources/resources.module.css @@ -0,0 +1,72 @@ +.identity { + display: flex; + min-width: 0; + align-items: center; + gap: var(--space-3); +} + +.thumb { + display: inline-flex; + width: 36px; + height: 36px; + flex: 0 0 auto; + align-items: center; + justify-content: center; + overflow: hidden; + border: 1px solid var(--border); + border-radius: var(--radius-sm); + background: var(--bg-card-strong); + color: var(--text-secondary); +} + +.thumb img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.identityText, +.stack { + display: grid; + min-width: 0; + gap: 2px; +} + +.name { + overflow: hidden; + color: var(--text-primary); + font-weight: 650; + text-overflow: ellipsis; + white-space: nowrap; +} + +.meta { + overflow: hidden; + color: var(--text-tertiary); + font-size: var(--admin-font-size); + text-overflow: ellipsis; + white-space: nowrap; +} + +.tagList { + display: flex; + min-width: 0; + flex-wrap: wrap; + gap: var(--space-2); +} + +.tag { + display: inline-flex; + max-width: 100%; + align-items: center; + overflow: hidden; + border: 1px solid var(--border); + border-radius: 999px; + background: var(--neutral-surface); + color: var(--text-secondary); + font-size: var(--admin-font-size); + line-height: 1; + padding: 5px 8px; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/src/features/resources/routes.js b/src/features/resources/routes.js new file mode 100644 index 0000000..eb6f077 --- /dev/null +++ b/src/features/resources/routes.js @@ -0,0 +1,36 @@ +import { MENU_CODES, PERMISSIONS } from "@/app/permissions"; + +export const resourceRoutes = [ + { + label: "资源列表", + loader: () => import("./pages/ResourceListPage.jsx").then((module) => module.ResourceListPage), + menuCode: MENU_CODES.resourceList, + pageKey: "resource-list", + path: "/resources", + permission: PERMISSIONS.resourceView + }, + { + label: "资源组列表", + loader: () => import("./pages/ResourceGroupListPage.jsx").then((module) => module.ResourceGroupListPage), + menuCode: MENU_CODES.resourceGroupList, + pageKey: "resource-group-list", + path: "/resource-groups", + permission: PERMISSIONS.resourceGroupView + }, + { + label: "礼物列表", + loader: () => import("./pages/GiftListPage.jsx").then((module) => module.GiftListPage), + menuCode: MENU_CODES.giftList, + pageKey: "gift-list", + path: "/gifts", + permission: PERMISSIONS.giftView + }, + { + label: "资源赠送", + loader: () => import("./pages/ResourceGrantListPage.jsx").then((module) => module.ResourceGrantListPage), + menuCode: MENU_CODES.resourceGrantList, + pageKey: "resource-grant-list", + path: "/resource-grants", + permission: PERMISSIONS.resourceGrantView + } +]; diff --git a/src/features/resources/schema.js b/src/features/resources/schema.js new file mode 100644 index 0000000..9041298 --- /dev/null +++ b/src/features/resources/schema.js @@ -0,0 +1,235 @@ +import { z } from "zod"; + +const resourceTypes = ["avatar_frame", "coin", "vehicle", "chat_bubble", "badge", "floating_screen", "gift"]; +const walletAssetTypes = ["COIN", "DIAMOND"]; +const optionalWalletAssetTypeSchema = z + .preprocess((value) => { + if (typeof value !== "string") { + return value; + } + const normalized = value.trim().toUpperCase(); + return normalized || undefined; + }, z.string().optional()) + .refine((value) => !value || walletAssetTypes.includes(value), "请选择金币或钻石"); + +export const resourceCreateFormSchema = z + .object({ + assetUrl: z.string().trim().min(1, "请上传资源素材").max(512, "资源素材不能超过 512 个字符"), + enabled: z.boolean(), + name: z.string().trim().min(1, "请输入资源名称").max(128, "资源名称不能超过 128 个字符"), + previewUrl: z.string().trim().min(1, "请上传资源封面").max(512, "资源封面不能超过 512 个字符"), + resourceCode: z.string().trim().min(1, "请输入资源编码").max(96, "资源编码不能超过 96 个字符"), + resourceType: z.enum(resourceTypes, "请选择资源类型"), + walletAssetAmount: z.union([z.string(), z.number()]).optional(), + }) + .superRefine((value, context) => { + if (value.resourceType !== "coin") { + return; + } + const amount = Number(value.walletAssetAmount); + if (!Number.isInteger(amount) || amount <= 0) { + context.addIssue({ + code: "custom", + message: "请输入金币数量", + path: ["walletAssetAmount"], + }); + } + }); + +export const resourceFormSchema = resourceCreateFormSchema; + +const groupItemSchema = z.object({ + durationDays: z.union([z.string(), z.number()]).optional(), + itemType: z.enum(["resource", "wallet_asset"]), + resourceId: z.union([z.string(), z.number()]).optional(), + sortOrder: z.union([z.string(), z.number()]).optional(), + walletAssetAmount: z.union([z.string(), z.number()]).optional(), + walletAssetType: optionalWalletAssetTypeSchema, +}); + +export const resourceGroupCreateFormSchema = z + .object({ + description: z.string().trim().max(512, "资源组说明不能超过 512 个字符").optional(), + enabled: z.boolean(), + groupCode: z.string().trim().min(1, "请输入资源组编码").max(96, "资源组编码不能超过 96 个字符"), + items: z.array(groupItemSchema).min(1, "请至少配置一个资源组条目"), + name: z.string().trim().min(1, "请输入资源组名称").max(128, "资源组名称不能超过 128 个字符"), + sortOrder: z.union([z.string(), z.number()]).optional(), + }) + .superRefine((value, context) => { + const resourceIds = new Set(); + const walletAssets = new Set(); + value.items.forEach((item, index) => { + if (item.itemType === "wallet_asset") { + const amount = Number(item.walletAssetAmount); + if (!item.walletAssetType || !Number.isInteger(amount) || amount <= 0) { + context.addIssue({ + code: "custom", + message: "请输入金币或钻石数量", + path: ["items", index, "walletAssetAmount"], + }); + } + if (item.walletAssetType) { + if (walletAssets.has(item.walletAssetType)) { + context.addIssue({ + code: "custom", + message: "同一种钱包资产不能重复配置", + path: ["items", index, "walletAssetType"], + }); + } + walletAssets.add(item.walletAssetType); + } + return; + } + + const resourceId = Number(item.resourceId); + const durationDays = Number(item.durationDays); + if (!Number.isInteger(resourceId) || resourceId <= 0) { + context.addIssue({ + code: "custom", + message: "请选择资源", + path: ["items", index, "resourceId"], + }); + } + if (!Number.isInteger(durationDays) || durationDays <= 0) { + context.addIssue({ + code: "custom", + message: "请输入有效天数", + path: ["items", index, "durationDays"], + }); + } + if (resourceIds.has(resourceId)) { + context.addIssue({ + code: "custom", + message: "同一个资源不能重复配置", + path: ["items", index, "resourceId"], + }); + } + resourceIds.add(resourceId); + }); + }); + +export const giftFormSchema = z + .object({ + coinPrice: z.union([z.string(), z.number()]), + enabled: z.boolean(), + giftId: z.string().trim().min(1, "请输入礼物 ID").max(96, "礼物 ID 不能超过 96 个字符"), + giftPointAmount: z.union([z.string(), z.number()]).optional(), + heatValue: z.union([z.string(), z.number()]).optional(), + name: z.string().trim().min(1, "请输入礼物名称").max(128, "礼物名称不能超过 128 个字符"), + presentationJson: z.string().trim().optional(), + priceVersion: z.string().trim().min(1, "请输入价格版本").max(64, "价格版本不能超过 64 个字符"), + regionIds: z.array(z.union([z.string(), z.number()])).min(1, "请选择区域"), + resourceId: z.union([z.string(), z.number()]), + sortOrder: z.union([z.string(), z.number()]).optional(), + }) + .superRefine((value, context) => { + const resourceId = Number(value.resourceId); + const coinPrice = Number(value.coinPrice); + const giftPointAmount = Number(value.giftPointAmount || 0); + const heatValue = Number(value.heatValue || 0); + + if (!Number.isInteger(resourceId) || resourceId <= 0) { + context.addIssue({ + code: "custom", + message: "请选择礼物资源", + path: ["resourceId"], + }); + } + if (!Number.isInteger(coinPrice) || coinPrice <= 0) { + context.addIssue({ + code: "custom", + message: "请输入价格", + path: ["coinPrice"], + }); + } + if (!Number.isInteger(giftPointAmount) || giftPointAmount < 0) { + context.addIssue({ + code: "custom", + message: "请输入有效积分", + path: ["giftPointAmount"], + }); + } + if (!Number.isInteger(heatValue) || heatValue < 0) { + context.addIssue({ + code: "custom", + message: "请输入有效热度", + path: ["heatValue"], + }); + } + value.regionIds.forEach((regionId, index) => { + const normalizedRegionId = Number(regionId); + if (!Number.isInteger(normalizedRegionId) || normalizedRegionId < 0) { + context.addIssue({ + code: "custom", + message: "请选择区域", + path: ["regionIds", index], + }); + } + }); + if (value.presentationJson) { + try { + JSON.parse(value.presentationJson); + } catch { + context.addIssue({ + code: "custom", + message: "展示配置必须是合法 JSON", + path: ["presentationJson"], + }); + } + } + }); + +export const resourceGrantFormSchema = z + .object({ + durationDays: z.union([z.string(), z.number()]).optional(), + groupId: z.union([z.string(), z.number()]).optional(), + quantity: z.union([z.string(), z.number()]).optional(), + reason: z.string().trim().min(1, "请输入原因").max(240, "原因不能超过 240 个字符"), + resourceId: z.union([z.string(), z.number()]).optional(), + subjectType: z.enum(["resource", "resource_group"]), + targetUserId: z.union([z.string(), z.number()]), + }) + .superRefine((value, context) => { + const targetUserId = Number(value.targetUserId); + const resourceId = Number(value.resourceId || 0); + const groupId = Number(value.groupId || 0); + const quantity = Number(value.quantity || 1); + const durationDays = Number(value.durationDays || 0); + + if (!Number.isInteger(targetUserId) || targetUserId <= 0) { + context.addIssue({ + code: "custom", + message: "请输入用户 ID", + path: ["targetUserId"], + }); + } + if (value.subjectType === "resource" && (!Number.isInteger(resourceId) || resourceId <= 0)) { + context.addIssue({ + code: "custom", + message: "请选择资源", + path: ["resourceId"], + }); + } + if (value.subjectType === "resource_group" && (!Number.isInteger(groupId) || groupId <= 0)) { + context.addIssue({ + code: "custom", + message: "请选择资源组", + path: ["groupId"], + }); + } + if (!Number.isInteger(quantity) || quantity <= 0) { + context.addIssue({ + code: "custom", + message: "请输入数量", + path: ["quantity"], + }); + } + if (!Number.isFinite(durationDays) || durationDays < 0) { + context.addIssue({ + code: "custom", + message: "请输入有效天数", + path: ["durationDays"], + }); + } + }); diff --git a/src/features/resources/schema.test.ts b/src/features/resources/schema.test.ts new file mode 100644 index 0000000..97cfc80 --- /dev/null +++ b/src/features/resources/schema.test.ts @@ -0,0 +1,80 @@ +import { describe, expect, test } from "vitest"; +import { FormValidationError, parseForm } from "@/shared/forms/validation"; +import { giftFormSchema, resourceGrantFormSchema, resourceGroupCreateFormSchema } from "./schema.js"; + +describe("resource form schema", () => { + test("allows resource group resource items without wallet asset type", () => { + const payload = parseForm(resourceGroupCreateFormSchema, { + description: "", + enabled: true, + groupCode: "starter_pack", + items: [ + { + durationDays: "7", + itemType: "resource", + resourceId: "11", + sortOrder: "0", + walletAssetAmount: "", + walletAssetType: "" + } + ], + name: "Starter Pack", + sortOrder: "0" + }); + + expect(payload.items[0].walletAssetType).toBeUndefined(); + }); + + test("rejects invalid wallet asset type with Chinese message", () => { + expect(() => + parseForm(resourceGroupCreateFormSchema, { + description: "", + enabled: true, + groupCode: "starter_pack", + items: [ + { + durationDays: "", + itemType: "wallet_asset", + resourceId: "", + sortOrder: "0", + walletAssetAmount: "100", + walletAssetType: "gold" + } + ], + name: "Starter Pack", + sortOrder: "0" + }) + ).toThrow(FormValidationError); + }); + + test("validates editable gift form fields", () => { + const payload = parseForm(giftFormSchema, { + coinPrice: "10", + enabled: true, + giftId: "rose", + giftPointAmount: "1", + heatValue: "2", + name: "Rose", + presentationJson: "{}", + priceVersion: "default", + regionIds: ["0", "1001"], + resourceId: "11", + sortOrder: "0" + }); + + expect(payload.regionIds).toEqual(["0", "1001"]); + }); + + test("validates resource grant form fields", () => { + const payload = parseForm(resourceGrantFormSchema, { + durationDays: "7", + quantity: "1", + reason: "manual", + resourceId: "11", + subjectType: "resource", + targetUserId: "1001" + }); + + expect(payload.subjectType).toBe("resource"); + }); +}); diff --git a/src/features/roles/api.ts b/src/features/roles/api.ts new file mode 100644 index 0000000..0d991f3 --- /dev/null +++ b/src/features/roles/api.ts @@ -0,0 +1,69 @@ +import { apiRequest } from "@/shared/api/request"; +import { API_ENDPOINTS, API_OPERATIONS, apiEndpointPath } from "@/shared/api/generated/endpoints"; +import type { EntityId, PermissionDto, PermissionPayload, RoleDto, RolePayload } from "@/shared/api/types"; + +export function listRoles(): Promise { + return apiRequest(apiEndpointPath(API_OPERATIONS.listRoles)); +} + +export function createRole(payload: RolePayload): Promise { + const endpoint = API_ENDPOINTS.createRole; + return apiRequest(apiEndpointPath(API_OPERATIONS.createRole), { + body: payload, + method: endpoint.method + }); +} + +export function updateRole(id: EntityId, payload: RolePayload): Promise { + const endpoint = API_ENDPOINTS.updateRole; + return apiRequest(apiEndpointPath(API_OPERATIONS.updateRole, { id }), { + body: payload, + method: endpoint.method + }); +} + +export function deleteRole(id: EntityId): Promise { + const endpoint = API_ENDPOINTS.deleteRole; + return apiRequest(apiEndpointPath(API_OPERATIONS.deleteRole, { id }), { method: endpoint.method }); +} + +export function replaceRolePermissions(id: EntityId, permissionIds: number[]): Promise { + const endpoint = API_ENDPOINTS.replaceRolePermissions; + return apiRequest(apiEndpointPath(API_OPERATIONS.replaceRolePermissions, { id }), { + body: { permissionIds }, + method: endpoint.method + }); +} + +export function listPermissions(): Promise { + return apiRequest(apiEndpointPath(API_OPERATIONS.listPermissions)); +} + +export function createPermission(payload: PermissionPayload): Promise { + const endpoint = API_ENDPOINTS.createPermission; + return apiRequest(apiEndpointPath(API_OPERATIONS.createPermission), { + body: payload, + method: endpoint.method + }); +} + +export function updatePermission(id: EntityId, payload: PermissionPayload): Promise { + const endpoint = API_ENDPOINTS.updatePermission; + return apiRequest(apiEndpointPath(API_OPERATIONS.updatePermission, { id }), { + body: payload, + method: endpoint.method + }); +} + +export function deletePermission(id: EntityId): Promise { + const endpoint = API_ENDPOINTS.deletePermission; + return apiRequest(apiEndpointPath(API_OPERATIONS.deletePermission, { id }), { method: endpoint.method }); +} + +export function syncPermissions(): Promise { + const endpoint = API_ENDPOINTS.syncPermissions; + return apiRequest>(apiEndpointPath(API_OPERATIONS.syncPermissions), { + body: {}, + method: endpoint.method + }); +} diff --git a/src/features/roles/components/RoleFormDrawer.jsx b/src/features/roles/components/RoleFormDrawer.jsx new file mode 100644 index 0000000..13e933e --- /dev/null +++ b/src/features/roles/components/RoleFormDrawer.jsx @@ -0,0 +1,87 @@ +import Checkbox from "@mui/material/Checkbox"; +import Drawer from "@mui/material/Drawer"; +import TextField from "@mui/material/TextField"; +import { Button } from "@/shared/ui/Button.jsx"; + +export function RoleFormDrawer({ + abilities, + editingRole, + form, + onClose, + onSubmit, + onTogglePermissionGroup, + onTogglePermission, + open, + permissionGroups, + setForm +}) { + return ( + +
+

{editingRole ? "编辑角色" : "新增角色"}

+ setForm({ ...form, name: event.target.value })} /> + setForm({ ...form, code: event.target.value })} /> + setForm({ ...form, description: event.target.value })} /> + {abilities.canLoadPermissions ? ( +
+ {permissionGroups.map((group) => ( + + ))} +
+ ) : null} +
+ + +
+ +
+ ); +} + +function PermissionMenuGroup({ abilities, group, onTogglePermission, onTogglePermissionGroup, permissionIds }) { + const groupPermissionIds = group.items.map((permission) => permission.id); + const selectedCount = groupPermissionIds.filter((permissionId) => permissionIds.includes(permissionId)).length; + const allSelected = selectedCount === groupPermissionIds.length; + const partiallySelected = selectedCount > 0 && !allSelected; + + return ( +
+
+ {group.title} +
+ {selectedCount}/{groupPermissionIds.length} + +
+
+
+ {group.items.map((permission) => ( + + ))} +
+
+ ); +} diff --git a/src/features/roles/components/RoleTable.jsx b/src/features/roles/components/RoleTable.jsx new file mode 100644 index 0000000..41a127c --- /dev/null +++ b/src/features/roles/components/RoleTable.jsx @@ -0,0 +1,41 @@ +import DeleteOutlineOutlined from "@mui/icons-material/DeleteOutlineOutlined"; +import EditOutlined from "@mui/icons-material/EditOutlined"; +import { AdminActionIconButton, AdminRowActions } from "@/shared/ui/AdminListLayout.jsx"; +import { DataTable } from "@/shared/ui/DataTable.jsx"; + +export function RoleTable({ abilities, onEdit, onRemove, roles }) { + const columns = [ + { key: "name", label: "角色", width: "minmax(160px, 1fr)" }, + { key: "code", label: "编码", width: "minmax(180px, 1.2fr)", render: (role) => {role.code} }, + { key: "permissionCount", label: "权限数量", width: "110px", render: (role) => role.permissions?.length || 0 }, + { + key: "description", + label: "说明", + width: "minmax(240px, 1.6fr)", + render: (role) => {role.description || "-"} + }, + { + key: "actions", + label: "操作", + width: "120px", + render: (role) => ( + + {abilities.canUpdate || abilities.canGrant ? ( + onEdit(role)}> + + + ) : null} + {abilities.canDelete ? ( + onRemove(role)}> + + + ) : null} + + ) + } + ]; + + return ( + role.id} /> + ); +} diff --git a/src/features/roles/components/RoleToolbar.jsx b/src/features/roles/components/RoleToolbar.jsx new file mode 100644 index 0000000..99a0fce --- /dev/null +++ b/src/features/roles/components/RoleToolbar.jsx @@ -0,0 +1,16 @@ +import Add from "@mui/icons-material/Add"; +import { Button } from "@/shared/ui/Button.jsx"; +import { PageHead } from "@/shared/ui/PageHead.jsx"; + +export function RoleToolbar({ canCreate, onCreate }) { + return ( + + {canCreate ? ( + + ) : null} + + ); +} diff --git a/src/features/roles/hooks/useRolesPage.js b/src/features/roles/hooks/useRolesPage.js new file mode 100644 index 0000000..8b09c2e --- /dev/null +++ b/src/features/roles/hooks/useRolesPage.js @@ -0,0 +1,236 @@ +import { useCallback, useMemo, useState } from "react"; +import { parseForm } from "@/shared/forms/validation"; +import { useAdminQuery } from "@/shared/hooks/useAdminQuery.js"; +import { useConfirm } from "@/shared/ui/ConfirmProvider.jsx"; +import { useToast } from "@/shared/ui/ToastProvider.jsx"; +import { + createRole, + deleteRole, + listPermissions, + listRoles, + replaceRolePermissions, + updateRole +} from "@/features/roles/api"; +import { useRoleAbilities } from "@/features/roles/permissions.js"; +import { roleFormSchema } from "@/features/roles/schema"; + +const emptyRole = { code: "", description: "", name: "", permissionIds: [] }; +const emptyData = { permissions: [], roles: [] }; +const permissionGroupDefinitions = [ + { key: "overview", prefixes: ["overview"], title: "总览" }, + { key: "users", prefixes: ["user"], title: "用户管理" }, + { key: "roles", prefixes: ["role"], title: "角色配置" }, + { key: "menus", prefixes: ["menu", "permission"], title: "菜单权限" }, + { key: "app-users", prefixes: ["app-user"], title: "App 用户" }, + { key: "rooms", prefixes: ["room"], title: "房间管理" }, + { key: "countries", prefixes: ["country"], title: "国家管理" }, + { key: "regions", prefixes: ["region"], title: "区域管理" }, + { key: "agencies", prefixes: ["agency"], title: "Agency 管理" }, + { key: "bds", prefixes: ["bd"], title: "BD 管理" }, + { key: "hosts", prefixes: ["host"], title: "主播管理" }, + { key: "logs", prefixes: ["log"], title: "日志审计" }, + { key: "notifications", prefixes: ["notification"], title: "通知中心" }, + { key: "jobs", prefixes: ["job", "export"], title: "任务中心" }, + { key: "common", prefixes: ["upload"], title: "通用能力" } +]; +const fallbackPermissionGroup = { key: "other", title: "其他权限" }; +const permissionKindOrder = { menu: 1, button: 2, api: 3 }; + +export function useRolesPage() { + const [query, setQuery] = useState(""); + const [drawerOpen, setDrawerOpen] = useState(false); + const [editingRole, setEditingRole] = useState(null); + const [form, setForm] = useState(emptyRole); + const abilities = useRoleAbilities(); + const confirm = useConfirm(); + const { showToast } = useToast(); + + const queryFn = useCallback(async () => { + const roleData = await listRoles(); + const permissionData = abilities.canLoadPermissions ? await listPermissions() : []; + return { permissions: permissionData || [], roles: roleData || [] }; + }, [abilities.canLoadPermissions]); + + const { data, error, loading, reload } = useAdminQuery(queryFn, { + errorMessage: "加载角色失败", + initialData: emptyData, + keepPreviousData: true, + queryKey: ["roles", abilities.canLoadPermissions] + }); + + const permissionGroups = useMemo(() => { + return groupPermissionsByMenu(data.permissions || []); + }, [data.permissions]); + + const roles = useMemo(() => { + const keyword = query.trim().toLowerCase(); + const items = data.roles || []; + if (!keyword) { + return items; + } + return items.filter((role) => { + return [role.name, role.code, role.description].some((value) => String(value || "").toLowerCase().includes(keyword)); + }); + }, [data.roles, query]); + + const changeQuery = (value) => { + setQuery(value); + }; + + const openCreate = () => { + setEditingRole(null); + setForm(emptyRole); + setDrawerOpen(true); + }; + + const openEdit = (role) => { + setEditingRole(role); + setForm({ + code: role.code, + description: role.description || "", + name: role.name, + permissionIds: role.permissions?.map((permission) => permission.id) || [] + }); + setDrawerOpen(true); + }; + + const closeDrawer = () => { + setDrawerOpen(false); + }; + + const submit = async (event) => { + event.preventDefault(); + try { + const payload = parseForm(roleFormSchema, form); + if (editingRole) { + if (abilities.canUpdate) { + await updateRole(editingRole.id, { + code: payload.code, + description: payload.description, + name: payload.name + }); + } + if (abilities.canGrant) { + await replaceRolePermissions(editingRole.id, payload.permissionIds); + } + showToast("角色已更新", "success"); + } else { + await createRole({ + code: payload.code, + description: payload.description, + name: payload.name, + permissionIds: abilities.canGrant ? payload.permissionIds : [] + }); + showToast("角色已创建", "success"); + } + setDrawerOpen(false); + reload(); + } catch (err) { + showToast(err.message || "保存角色失败", "error"); + } + }; + + const remove = async (role) => { + const ok = await confirm({ + confirmText: "删除", + message: `${role.name} 删除前需要先解绑用户。`, + title: "删除角色", + tone: "danger" + }); + if (!ok) { + return; + } + try { + await deleteRole(role.id); + showToast("角色已删除", "success"); + reload(); + } catch (err) { + showToast(err.message || "删除角色失败", "error"); + } + }; + + const togglePermission = (permissionId, checked) => { + if (!abilities.canGrant) { + return; + } + setForm((current) => ({ + ...current, + permissionIds: checked + ? Array.from(new Set([...current.permissionIds, permissionId])) + : current.permissionIds.filter((id) => id !== permissionId) + })); + }; + + const togglePermissionGroup = (permissionIds, checked) => { + if (!abilities.canGrant) { + return; + } + setForm((current) => { + const targetIds = new Set(permissionIds); + + return { + ...current, + permissionIds: checked + ? Array.from(new Set([...current.permissionIds, ...permissionIds])) + : current.permissionIds.filter((id) => !targetIds.has(id)) + }; + }); + }; + + return { + abilities, + changeQuery, + closeDrawer, + drawerOpen, + editingRole, + error, + form, + loading, + openCreate, + openEdit, + permissionGroups, + permissions: data.permissions || [], + query, + reload, + remove, + roles, + setForm, + submit, + togglePermission, + togglePermissionGroup + }; +} + +function groupPermissionsByMenu(permissions) { + const groups = new Map(permissionGroupDefinitions.map((definition) => [definition.key, { ...definition, items: [] }])); + const prefixToGroup = new Map( + permissionGroupDefinitions.flatMap((definition) => definition.prefixes.map((prefix) => [prefix, definition.key])) + ); + const fallbackGroup = { ...fallbackPermissionGroup, items: [] }; + + permissions.forEach((permission) => { + const prefix = getPermissionPrefix(permission.code); + const groupKey = prefixToGroup.get(prefix); + const group = groupKey ? groups.get(groupKey) : fallbackGroup; + group.items.push(permission); + }); + + return [...groups.values(), fallbackGroup] + .filter((group) => group.items.length > 0) + .map((group) => ({ + ...group, + items: [...group.items].sort(comparePermissions) + })); +} + +function getPermissionPrefix(code = "") { + return code.split(":")[0] || "other"; +} + +function comparePermissions(left, right) { + const kindDiff = (permissionKindOrder[left.kind] || 9) - (permissionKindOrder[right.kind] || 9); + if (kindDiff !== 0) { + return kindDiff; + } + return left.code.localeCompare(right.code); +} diff --git a/src/features/roles/pages/RoleManagementPage.jsx b/src/features/roles/pages/RoleManagementPage.jsx new file mode 100644 index 0000000..eeca37e --- /dev/null +++ b/src/features/roles/pages/RoleManagementPage.jsx @@ -0,0 +1,53 @@ +import Add from "@mui/icons-material/Add"; +import { DataState } from "@/shared/ui/DataState.jsx"; +import { + AdminActionIconButton, + AdminListBody, + AdminListPage, + AdminListToolbar, + AdminSearchBox +} from "@/shared/ui/AdminListLayout.jsx"; +import { RoleFormDrawer } from "@/features/roles/components/RoleFormDrawer.jsx"; +import { RoleTable } from "@/features/roles/components/RoleTable.jsx"; +import { useRolesPage } from "@/features/roles/hooks/useRolesPage.js"; + +export function RoleManagementPage() { + const page = useRolesPage(); + + return ( + <> + + } + actions={page.abilities.canCreate ? ( + + + + ) : null} + /> + + + + +
+ {page.roles.length} 条 +
+
+
+
+ + + + ); +} diff --git a/src/features/roles/permissions.js b/src/features/roles/permissions.js new file mode 100644 index 0000000..b215ef6 --- /dev/null +++ b/src/features/roles/permissions.js @@ -0,0 +1,15 @@ +import { useAuth } from "@/app/auth/AuthProvider.jsx"; +import { PERMISSIONS } from "@/app/permissions"; + +export function useRoleAbilities() { + const { can } = useAuth(); + const canGrant = can(PERMISSIONS.rolePermission) || can(PERMISSIONS.roleManage); + + return { + canCreate: can(PERMISSIONS.roleCreate) || can(PERMISSIONS.roleManage), + canDelete: can(PERMISSIONS.roleDelete) || can(PERMISSIONS.roleManage), + canGrant, + canLoadPermissions: can(PERMISSIONS.permissionView) || canGrant, + canUpdate: can(PERMISSIONS.roleUpdate) || can(PERMISSIONS.roleManage) + }; +} diff --git a/src/features/roles/routes.js b/src/features/roles/routes.js new file mode 100644 index 0000000..7ec6f3f --- /dev/null +++ b/src/features/roles/routes.js @@ -0,0 +1,12 @@ +import { MENU_CODES, PERMISSIONS } from "@/app/permissions"; + +export const rolesRoutes = [ + { + label: "角色配置", + loader: () => import("./pages/RoleManagementPage.jsx").then((module) => module.RoleManagementPage), + menuCode: MENU_CODES.systemRoles, + pageKey: "roles", + path: "/system/roles", + permission: PERMISSIONS.roleView + } +]; diff --git a/src/features/roles/schema.ts b/src/features/roles/schema.ts new file mode 100644 index 0000000..ca671fc --- /dev/null +++ b/src/features/roles/schema.ts @@ -0,0 +1,10 @@ +import { z } from "zod"; + +export const roleFormSchema = z.object({ + code: z.string().trim().min(1, "请输入角色编码").max(60, "角色编码不能超过 60 个字符"), + description: z.string().trim().max(200, "描述不能超过 200 个字符").optional().default(""), + name: z.string().trim().min(1, "请输入角色名称").max(60, "角色名称不能超过 60 个字符"), + permissionIds: z.array(z.coerce.number().int().positive()).default([]) +}); + +export type RoleForm = z.infer; diff --git a/src/features/rooms/api.ts b/src/features/rooms/api.ts new file mode 100644 index 0000000..0e55875 --- /dev/null +++ b/src/features/rooms/api.ts @@ -0,0 +1,32 @@ +import { apiRequest } from "@/shared/api/request"; +import { API_ENDPOINTS, API_OPERATIONS, apiEndpointPath } from "@/shared/api/generated/endpoints"; +import type { + ApiPage, + EntityId, + PageQuery, + RoomDto, + RoomUpdatePayload +} from "@/shared/api/types"; + +export function listRooms(query: PageQuery = {}): Promise> { + const endpoint = API_ENDPOINTS.listRooms; + return apiRequest>(apiEndpointPath(API_OPERATIONS.listRooms), { + method: endpoint.method, + query + }); +} + +export function updateRoom(roomId: EntityId, payload: RoomUpdatePayload): Promise { + const endpoint = API_ENDPOINTS.updateRoom; + return apiRequest(apiEndpointPath(API_OPERATIONS.updateRoom, { room_id: roomId }), { + body: payload, + method: endpoint.method + }); +} + +export function deleteRoom(roomId: EntityId): Promise<{ deleted: boolean }> { + const endpoint = API_ENDPOINTS.deleteRoom; + return apiRequest<{ deleted: boolean }>(apiEndpointPath(API_OPERATIONS.deleteRoom, { room_id: roomId }), { + method: endpoint.method + }); +} diff --git a/src/features/rooms/constants.js b/src/features/rooms/constants.js new file mode 100644 index 0000000..c91de70 --- /dev/null +++ b/src/features/rooms/constants.js @@ -0,0 +1,10 @@ +export const roomStatusFilters = [ + ["", "全部"], + ["active", "正常"], + ["closed", "关闭"] +]; + +export const roomStatusLabels = { + active: "正常", + closed: "关闭" +}; diff --git a/src/features/rooms/hooks/useRoomsPage.js b/src/features/rooms/hooks/useRoomsPage.js new file mode 100644 index 0000000..72abd08 --- /dev/null +++ b/src/features/rooms/hooks/useRoomsPage.js @@ -0,0 +1,176 @@ +import { useMemo, useState } from "react"; +import { parseForm } from "@/shared/forms/validation"; +import { useRegionOptions } from "@/shared/hooks/useRegionOptions.js"; +import { usePaginatedQuery } from "@/shared/hooks/usePaginatedQuery.js"; +import { useConfirm } from "@/shared/ui/ConfirmProvider.jsx"; +import { useToast } from "@/shared/ui/ToastProvider.jsx"; +import { deleteRoom, listRooms, updateRoom } from "@/features/rooms/api"; +import { useRoomAbilities } from "@/features/rooms/permissions.js"; +import { roomStatusSchema, roomUpdateSchema } from "@/features/rooms/schema"; + +const pageSize = 10; +const emptyData = { items: [], page: 1, pageSize, total: 0 }; +const emptyForm = () => ({ + coverUrl: "", + description: "", + title: "", + visibleRegionId: 0 +}); +const emptyStatusForm = () => ({ status: "active" }); + +export function useRoomsPage() { + const abilities = useRoomAbilities(); + const confirm = useConfirm(); + const { showToast } = useToast(); + const { loadingRegions, regionOptions } = useRegionOptions(); + const [query, setQuery] = useState(""); + const [status, setStatus] = useState(""); + const [regionId, setRegionId] = useState(""); + const [page, setPage] = useState(1); + const [activeAction, setActiveAction] = useState(""); + const [activeRoom, setActiveRoom] = useState(null); + const [form, setForm] = useState(emptyForm); + const [statusForm, setStatusForm] = useState(emptyStatusForm); + const [loadingAction, setLoadingAction] = useState(""); + const filters = useMemo( + () => ({ + keyword: query, + region_id: regionId, + status + }), + [query, regionId, status] + ); + const { data = emptyData, error, loading, reload } = usePaginatedQuery({ + errorMessage: "加载房间列表失败", + fetcher: listRooms, + filters, + page, + pageSize, + queryKey: ["rooms", filters, page] + }); + + const changeQuery = (value) => { + setQuery(value); + setPage(1); + }; + + const changeStatus = (value) => { + setStatus(value); + setPage(1); + }; + + const changeRegionId = (value) => { + setRegionId(value); + setPage(1); + }; + + const openEdit = (room) => { + setActiveRoom(room); + setForm({ + coverUrl: room.coverUrl || "", + description: "", + title: room.title || "", + visibleRegionId: room.visibleRegionId || 0 + }); + setActiveAction("edit"); + }; + + const openStatus = (room) => { + setActiveRoom(room); + setStatusForm({ status: room.status === "closed" ? "closed" : "active" }); + setActiveAction("status"); + }; + + const closeAction = () => { + setActiveAction(""); + setActiveRoom(null); + }; + + const submitEdit = async (event) => { + event.preventDefault(); + if (!activeRoom) { + return; + } + await runAction("edit", "房间已更新", async () => { + const payload = parseForm(roomUpdateSchema, form); + if (!String(form.description || "").trim()) { + delete payload.description; + } + await updateRoom(activeRoom.roomId, payload); + closeAction(); + await reload(); + }); + }; + + const submitStatus = async (event) => { + event.preventDefault(); + if (!activeRoom) { + return; + } + await runAction("status", "房间状态已更新", async () => { + const payload = parseForm(roomStatusSchema, statusForm); + await updateRoom(activeRoom.roomId, payload); + closeAction(); + await reload(); + }); + }; + + const removeRoom = async (room) => { + const ok = await confirm({ + confirmText: "删除", + message: `${room.title || room.roomId} 会从房间列表和房间状态中删除。`, + title: "删除房间", + tone: "danger" + }); + if (!ok) { + return; + } + await runAction(`delete-${room.roomId}`, "房间已删除", async () => { + await deleteRoom(room.roomId); + await reload(); + }); + }; + + const runAction = async (action, successMessage, submitter) => { + setLoadingAction(action); + try { + await submitter(); + showToast(successMessage, "success"); + } catch (err) { + showToast(err.message || "操作失败", "error"); + } finally { + setLoadingAction(""); + } + }; + + return { + abilities, + activeAction, + activeRoom, + changeQuery, + changeRegionId, + changeStatus, + closeAction, + data, + error, + form, + loading, + loadingAction, + loadingRegions, + openEdit, + openStatus, + page, + query, + regionId, + regionOptions, + reload, + removeRoom, + setForm, + setPage, + setStatusForm, + status, + statusForm, + submitStatus, + submitEdit + }; +} diff --git a/src/features/rooms/pages/RoomListPage.jsx b/src/features/rooms/pages/RoomListPage.jsx new file mode 100644 index 0000000..25c8c0a --- /dev/null +++ b/src/features/rooms/pages/RoomListPage.jsx @@ -0,0 +1,240 @@ +import DeleteOutlineOutlined from "@mui/icons-material/DeleteOutlineOutlined"; +import EditOutlined from "@mui/icons-material/EditOutlined"; +import MeetingRoomOutlined from "@mui/icons-material/MeetingRoomOutlined"; +import PersonOutlineOutlined from "@mui/icons-material/PersonOutlineOutlined"; +import MenuItem from "@mui/material/MenuItem"; +import TextField from "@mui/material/TextField"; +import Tooltip from "@mui/material/Tooltip"; +import { AdminFormDialog } from "@/shared/ui/AdminFormDialog.jsx"; +import { DataState } from "@/shared/ui/DataState.jsx"; +import { DataTable } from "@/shared/ui/DataTable.jsx"; +import { IconButton } from "@/shared/ui/IconButton.jsx"; +import { PaginationBar } from "@/shared/ui/PaginationBar.jsx"; +import { RegionSelect } from "@/shared/ui/RegionSelect.jsx"; +import { SearchBox } from "@/shared/ui/SearchBox.jsx"; +import { StatusSelect } from "@/shared/ui/StatusSelect.jsx"; +import { UploadField } from "@/shared/ui/UploadField.jsx"; +import { formatMillis } from "@/shared/utils/time.js"; +import { roomStatusFilters, roomStatusLabels } from "@/features/rooms/constants.js"; +import { useRoomsPage } from "@/features/rooms/hooks/useRoomsPage.js"; +import styles from "@/features/rooms/rooms.module.css"; + +const columns = [ + { + key: "identity", + label: "房间", + width: "minmax(240px, 1.5fr)", + render: (room) => + }, + { + key: "owner", + label: "房主", + width: "minmax(210px, 1.2fr)", + render: (room) => + }, + { + key: "region", + label: "区域", + width: "minmax(140px, 0.8fr)", + render: (room) => room.regionName || (room.visibleRegionId ? `区域 ${room.visibleRegionId}` : "-") + }, + { + key: "onlineCount", + label: "房间人数", + width: "minmax(110px, 0.7fr)", + render: (room) => `${formatNumber(room.onlineCount)} 人` + }, + { + key: "status", + label: "状态", + width: "minmax(100px, 0.7fr)", + render: (room) => + }, + { + key: "time", + label: "创建时间", + width: "minmax(170px, 1fr)", + render: (room) => formatMillis(room.createdAtMs) + } +]; + +export function RoomListPage() { + const page = useRoomsPage(); + const items = page.data.items || []; + const total = page.data.total || 0; + const tableColumns = [ + ...columns.map((column) => + column.key === "status" + ? { + ...column, + render: (room) => ( + page.openStatus(room)} /> + ) + } + : column + ), + { + key: "actions", + label: "操作", + width: "minmax(104px, 0.6fr)", + render: (room) => + } + ]; + + return ( +
+
+
+
+ + + +
+
+ + +
+ room.roomId} /> + {total > 0 ? : null} +
+
+
+ + + page.setForm({ ...page.form, title: event.target.value })} /> + page.setForm({ ...page.form, coverUrl })} /> + page.setForm({ ...page.form, visibleRegionId })} /> + page.setForm({ ...page.form, description: event.target.value })} /> + + + + page.setStatusForm({ status: event.target.value })}> + 正常 + 关闭 + + +
+ ); +} + +function RoomIdentity({ room }) { + return ( +
+ + {room.coverUrl ? : } + +
+
{room.title || "-"}
+
{room.roomId}
+
+
+ ); +} + +function OwnerIdentity({ room }) { + const owner = room.owner || {}; + const ownerName = owner.username || "-"; + const ownerDisplayId = owner.displayUserId || room.ownerUserId || "-"; + + return ( +
+ + {owner.avatar ? : } + +
+
{ownerName}
+
{ownerDisplayId}
+
+
+ ); +} + +function RoomActions({ page, room }) { + return ( +
+ {page.abilities.canUpdate ? ( + + + page.openEdit(room)}> + + + + + ) : null} + {page.abilities.canDelete ? ( + + + page.removeRoom(room)}> + + + + + ) : null} +
+ ); +} + +function RoomStatus({ disabled, onClick, status }) { + const normalizedStatus = status === "active" ? "active" : "closed"; + const tone = normalizedStatus === "active" ? "running" : "stopped"; + const badge = ( + + + {roomStatusLabels[normalizedStatus]} + + ); + if (disabled) { + return badge; + } + return ( + + ); +} + +function ActionModal({ children, disabled, loading, onClose, onSubmit, open, title }) { + return ( + + {children} + + ); +} + +function formatNumber(value) { + return Number(value || 0).toLocaleString("zh-CN"); +} diff --git a/src/features/rooms/permissions.js b/src/features/rooms/permissions.js new file mode 100644 index 0000000..f0da236 --- /dev/null +++ b/src/features/rooms/permissions.js @@ -0,0 +1,13 @@ +import { useAuth } from "@/app/auth/AuthProvider.jsx"; +import { PERMISSIONS } from "@/app/permissions"; + +export function useRoomAbilities() { + const { can } = useAuth(); + + return { + canDelete: can(PERMISSIONS.roomDelete), + canUpdate: can(PERMISSIONS.roomUpdate), + canUpload: can(PERMISSIONS.uploadCreate), + canView: can(PERMISSIONS.roomView) + }; +} diff --git a/src/features/rooms/rooms.module.css b/src/features/rooms/rooms.module.css new file mode 100644 index 0000000..9ea2468 --- /dev/null +++ b/src/features/rooms/rooms.module.css @@ -0,0 +1,186 @@ +.root { + display: flex; + height: calc(100vh - var(--header-height) - var(--space-8) - var(--space-4)); + height: calc(100dvh - var(--header-height) - var(--space-8) - var(--space-4)); + min-height: 0; + flex-direction: column; +} + +.contentPanel { + display: flex; + flex: 1; + flex-direction: column; + min-height: 0; + overflow: hidden; + border: 1px solid var(--border); + border-radius: var(--radius-card); + background: var(--bg-card); +} + +.contentPanel :global(.data-state), +.contentPanel :global(.table-frame) { + flex: 1; + min-height: 0; + border: 0; + border-radius: 0; + background: transparent; +} + +.contentPanel :global(.table-scroll) { + overflow: auto; +} + +.toolbar { + display: flex; + align-items: center; + gap: var(--space-4); + padding: var(--space-4) var(--space-5); + border-bottom: 1px solid var(--border); +} + +.filters { + display: flex; + min-width: 0; + flex: 1; + align-items: center; + flex-wrap: wrap; + gap: var(--space-3); +} + +.search { + flex: 0 1 320px; +} + +.statusSelect { + flex: 0 0 150px; +} + +.regionInput { + flex: 0 0 150px; +} + +.listBlock { + display: flex; + flex: 1; + min-height: 0; + flex-direction: column; + background: var(--bg-card); +} + +.listBlock :global(.pagination-bar) { + margin: var(--space-3) var(--space-5) var(--space-4); +} + +.identity { + display: flex; + min-width: 0; + align-items: center; + gap: var(--space-3); +} + +.cover { + display: inline-flex; + width: 36px; + height: 36px; + flex: 0 0 auto; + align-items: center; + justify-content: center; + overflow: hidden; + border: 1px solid var(--border); + border-radius: var(--radius-control); + background: var(--bg-card-strong); + color: var(--text-secondary); +} + +.cover img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.ownerAvatar { + display: inline-flex; + width: 36px; + height: 36px; + flex: 0 0 auto; + align-items: center; + justify-content: center; + overflow: hidden; + border: 1px solid var(--border); + border-radius: 50%; + background: var(--bg-card-strong); + color: var(--text-secondary); +} + +.ownerAvatar img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.identityText { + min-width: 0; +} + +.name { + overflow: hidden; + color: var(--text-primary); + font-weight: 650; + text-overflow: ellipsis; + white-space: nowrap; +} + +.meta { + color: var(--text-tertiary); + font-size: var(--admin-font-size); +} + +.stack { + display: grid; + gap: 2px; +} + +.primaryText { + color: var(--text-primary); + font-weight: 650; +} + +.rowActions { + display: inline-flex; + align-items: center; + gap: var(--space-2); +} + +.statusButton { + display: inline-flex; + align-items: center; + padding: 0; + border: 0; + background: transparent; + cursor: pointer; +} + +.statusButton:focus-visible { + border-radius: 999px; + outline: 2px solid var(--primary); + outline-offset: 2px; +} + +.statusButton:hover :global(.status-badge) { + border-color: var(--primary-border); +} + +@media (max-width: 760px) { + .toolbar, + .filters { + align-items: stretch; + flex-direction: column; + } + + .search, + .statusSelect, + .regionInput { + flex: 1 1 auto; + width: 100%; + } +} diff --git a/src/features/rooms/routes.js b/src/features/rooms/routes.js new file mode 100644 index 0000000..64cd24f --- /dev/null +++ b/src/features/rooms/routes.js @@ -0,0 +1,12 @@ +import { MENU_CODES, PERMISSIONS } from "@/app/permissions"; + +export const roomRoutes = [ + { + label: "房间列表", + loader: () => import("./pages/RoomListPage.jsx").then((module) => module.RoomListPage), + menuCode: MENU_CODES.roomList, + pageKey: "room-list", + path: "/rooms", + permission: PERMISSIONS.roomView + } +]; diff --git a/src/features/rooms/schema.ts b/src/features/rooms/schema.ts new file mode 100644 index 0000000..7da664a --- /dev/null +++ b/src/features/rooms/schema.ts @@ -0,0 +1,17 @@ +import { z } from "zod"; + +const optionalText = (max: number, message: string) => z.string().trim().max(max, message).optional().default(""); + +export const roomUpdateSchema = z.object({ + coverUrl: optionalText(512, "房间头像不能超过 512 个字符"), + description: optionalText(512, "房间简介不能超过 512 个字符"), + title: z.string().trim().min(1, "请输入房间名称").max(128, "房间名称不能超过 128 个字符"), + visibleRegionId: z.coerce.number().int().min(0, "区域 ID 不正确").default(0) +}); + +export const roomStatusSchema = z.object({ + status: z.string().trim().refine((value) => value === "active" || value === "closed", "房间状态不正确") +}); + +export type RoomUpdateForm = z.infer; +export type RoomStatusForm = z.infer; diff --git a/src/features/search/api.ts b/src/features/search/api.ts new file mode 100644 index 0000000..0d981ac --- /dev/null +++ b/src/features/search/api.ts @@ -0,0 +1,7 @@ +import { apiRequest } from "@/shared/api/request"; +import { API_OPERATIONS, apiEndpointPath } from "@/shared/api/generated/endpoints"; +import type { SearchResultDto } from "@/shared/api/types"; + +export function searchGlobal(keyword: string): Promise { + return apiRequest(apiEndpointPath(API_OPERATIONS.search), { query: { keyword } }); +} diff --git a/src/components/search/SearchDialog.jsx b/src/features/search/components/SearchDialog.jsx similarity index 91% rename from src/components/search/SearchDialog.jsx rename to src/features/search/components/SearchDialog.jsx index 6afad7c..9e77e53 100644 --- a/src/components/search/SearchDialog.jsx +++ b/src/features/search/components/SearchDialog.jsx @@ -3,8 +3,8 @@ import List from "@mui/material/List"; import ListItemButton from "@mui/material/ListItemButton"; import ListItemText from "@mui/material/ListItemText"; import { useEffect, useState } from "react"; -import { searchGlobal } from "../../api/search.js"; -import { SearchBox } from "../base/SearchBox.jsx"; +import { searchGlobal } from "@/features/search/api"; +import { SearchBox } from "@/shared/ui/SearchBox.jsx"; export function SearchDialog({ onClose, onSelect, open }) { const [keyword, setKeyword] = useState(""); @@ -53,7 +53,7 @@ export function SearchDialog({ onClose, onSelect, open }) { slotProps={{ paper: { className: "search-dialog" } }} >
- +
diff --git a/src/features/users/api.test.ts b/src/features/users/api.test.ts new file mode 100644 index 0000000..52c7d2f --- /dev/null +++ b/src/features/users/api.test.ts @@ -0,0 +1,39 @@ +import { afterEach, expect, test, vi } from "vitest"; +import { setAccessToken } from "@/shared/api/request"; +import { listUsers, updateUserStatus } from "./api"; + +afterEach(() => { + setAccessToken(""); + vi.unstubAllGlobals(); +}); + +test("listUsers uses generated endpoint path and query", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async () => new Response(JSON.stringify({ code: 0, data: { items: [], page: 1, pageSize: 10, total: 0 } }))) + ); + + const result = await listUsers({ page: 1, page_size: 10, status: "active" }); + const [url, init] = vi.mocked(fetch).mock.calls[0]; + + expect(result.items).toEqual([]); + expect(String(url)).toContain("/api/v1/users?"); + expect(String(url)).toContain("page=1"); + expect(String(url)).toContain("page_size=10"); + expect(String(url)).toContain("status=active"); + expect(init?.method).toBe("GET"); +}); + +test("updateUserStatus sends generated endpoint path and payload", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async () => new Response(JSON.stringify({ code: 0, data: { id: 7, name: "张三", account: "zhangsan", status: "locked" } }))) + ); + + await updateUserStatus(7, "locked"); + const [url, init] = vi.mocked(fetch).mock.calls[0]; + + expect(String(url)).toContain("/api/v1/users/7/status"); + expect(init?.method).toBe("PATCH"); + expect(JSON.parse(String(init?.body))).toEqual({ status: "locked" }); +}); diff --git a/src/features/users/api.ts b/src/features/users/api.ts new file mode 100644 index 0000000..d38b734 --- /dev/null +++ b/src/features/users/api.ts @@ -0,0 +1,65 @@ +import { apiRequest } from "@/shared/api/request"; +import { API_ENDPOINTS, API_OPERATIONS, apiEndpointPath } from "@/shared/api/generated/endpoints"; +import type { + AdminUserDto, + ApiPage, + CreateUserResultDto, + EntityId, + PageQuery, + ResetPasswordResultDto, + UserFormPayload, + UserStatus, + UserUpdatePayload +} from "@/shared/api/types"; + +export function listUsers(query?: PageQuery): Promise> { + return apiRequest>(apiEndpointPath(API_OPERATIONS.listUsers), { query }); +} + +export function getUser(id: EntityId): Promise { + return apiRequest(apiEndpointPath(API_OPERATIONS.getUser, { id })); +} + +export function createUser(payload: UserFormPayload): Promise { + const endpoint = API_ENDPOINTS.createUser; + return apiRequest(apiEndpointPath(API_OPERATIONS.createUser), { + body: payload, + method: endpoint.method + }); +} + +export function updateUser(id: EntityId, payload: UserUpdatePayload): Promise { + const endpoint = API_ENDPOINTS.updateUser; + return apiRequest(apiEndpointPath(API_OPERATIONS.updateUser, { id }), { + body: payload, + method: endpoint.method + }); +} + +export function updateUserStatus(id: EntityId, status: UserStatus): Promise { + const endpoint = API_ENDPOINTS.updateUserStatus; + return apiRequest(apiEndpointPath(API_OPERATIONS.updateUserStatus, { id }), { + body: { status }, + method: endpoint.method + }); +} + +export function resetUserPassword(id: EntityId, password?: string): Promise { + const endpoint = API_ENDPOINTS.resetUserPassword; + return apiRequest(apiEndpointPath(API_OPERATIONS.resetUserPassword, { id }), { + body: { password }, + method: endpoint.method + }); +} + +export function batchUpdateUserStatus(ids: EntityId[], status: UserStatus): Promise { + const endpoint = API_ENDPOINTS.batchUpdateUserStatus; + return apiRequest(apiEndpointPath(API_OPERATIONS.batchUpdateUserStatus), { + body: { ids, status }, + method: endpoint.method + }); +} + +export function exportUsers(query?: PageQuery): Promise { + return apiRequest(apiEndpointPath(API_OPERATIONS.exportUsers), { query, raw: true }); +} diff --git a/src/features/users/components/UserFilters.jsx b/src/features/users/components/UserFilters.jsx new file mode 100644 index 0000000..cb06db2 --- /dev/null +++ b/src/features/users/components/UserFilters.jsx @@ -0,0 +1,44 @@ +import { Button } from "@/shared/ui/Button.jsx"; +import { FilterChip } from "@/shared/ui/FilterChip.jsx"; +import { SearchBox } from "@/shared/ui/SearchBox.jsx"; + +const statusFilters = [ + ["all", "全部"], + ["active", "启用"], + ["locked", "锁定"], + ["disabled", "停用"] +]; + +export function UserFilters({ + canBatchStatus, + onBatchStatus, + onQueryChange, + onRoleFilterChange, + onStatusFilterChange, + query, + roleFilter, + roleFilters, + statusFilter +}) { + return ( +
+ +
+ {roleFilters.map(([value, label]) => ( + onRoleFilterChange(value)} /> + ))} +
+
+ {statusFilters.map(([value, label]) => ( + onStatusFilterChange(value)} /> + ))} +
+ {canBatchStatus ? ( +
+ + +
+ ) : null} +
+ ); +} diff --git a/src/features/users/components/UserFormDrawer.jsx b/src/features/users/components/UserFormDrawer.jsx new file mode 100644 index 0000000..22b6dd2 --- /dev/null +++ b/src/features/users/components/UserFormDrawer.jsx @@ -0,0 +1,57 @@ +import Drawer from "@mui/material/Drawer"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import Switch from "@mui/material/Switch"; +import TextField from "@mui/material/TextField"; +import { Button } from "@/shared/ui/Button.jsx"; + +export function UserFormDrawer({ + editingUser, + form, + onClose, + onRoleChecked, + onSubmit, + open, + roles, + setForm +}) { + return ( + +
+

{editingUser ? "编辑用户" : "新增用户"}

+ setForm({ ...form, username: event.target.value })} + /> + setForm({ ...form, name: event.target.value })} /> + setForm({ ...form, team: event.target.value })} /> + {!editingUser ? ( + setForm({ ...form, password: event.target.value })} /> + ) : null} +
角色
+
+ {roles.map((role) => ( + + ))} +
+ setForm({ ...form, mfaEnabled: event.target.checked })} />} + label={form.mfaEnabled ? "MFA 已开启" : "MFA 未开启"} + /> +
+ + +
+ +
+ ); +} diff --git a/src/features/users/components/UserProfileDrawer.jsx b/src/features/users/components/UserProfileDrawer.jsx new file mode 100644 index 0000000..ff97fd9 --- /dev/null +++ b/src/features/users/components/UserProfileDrawer.jsx @@ -0,0 +1,50 @@ +import Person from "@mui/icons-material/Person"; +import { SideDrawer } from "@/shared/ui/SideDrawer.jsx"; +import { UserStatusBadge } from "./UserStatusBadge.jsx"; + +export function UserProfileDrawer({ onClose, open, user }) { + if (!user) { + return null; + } + + return ( + +
+
+
+ +
+
+ {user.name || "-"} + {user.account || "-"} +
+
+ +
+ + + + } /> + + +
+
+
+ ); +} + +function ProfileRow({ label, value }) { + return ( +
+ {label} +
{value === 0 || value ? value : "-"}
+
+ ); +} + +function formatMfa(user) { + if (user.mfa) { + return user.mfa; + } + return user.mfaEnabled ? "已开启" : "未开启"; +} diff --git a/src/features/users/components/UserStats.jsx b/src/features/users/components/UserStats.jsx new file mode 100644 index 0000000..9823fbb --- /dev/null +++ b/src/features/users/components/UserStats.jsx @@ -0,0 +1,17 @@ +import BlockOutlined from "@mui/icons-material/BlockOutlined"; +import ManageAccountsOutlined from "@mui/icons-material/ManageAccountsOutlined"; +import ShieldOutlined from "@mui/icons-material/ShieldOutlined"; +import VerifiedUserOutlined from "@mui/icons-material/VerifiedUserOutlined"; +import { KpiCard } from "@/shared/ui/KpiCard.jsx"; +import { userStatusMeta } from "@/features/users/constants.js"; + +export function UserStats({ stats, total }) { + return ( +
+ {stats.active} 个启用账户} /> + 当前页统计} /> + 可登录控制台} /> + 需管理员处理} /> +
+ ); +} diff --git a/src/components/user/UserStatusBadge.jsx b/src/features/users/components/UserStatusBadge.jsx similarity index 84% rename from src/components/user/UserStatusBadge.jsx rename to src/features/users/components/UserStatusBadge.jsx index e78449e..0d97c46 100644 --- a/src/components/user/UserStatusBadge.jsx +++ b/src/features/users/components/UserStatusBadge.jsx @@ -1,5 +1,5 @@ import Chip from "@mui/material/Chip"; -import { userStatusMeta } from "../../data/users.js"; +import { userStatusMeta } from "@/features/users/constants.js"; export function UserStatusBadge({ status }) { const meta = userStatusMeta[status]; diff --git a/src/features/users/components/UserTable.jsx b/src/features/users/components/UserTable.jsx new file mode 100644 index 0000000..41bc7cb --- /dev/null +++ b/src/features/users/components/UserTable.jsx @@ -0,0 +1,146 @@ +import EditOutlined from "@mui/icons-material/EditOutlined"; +import LockOutlined from "@mui/icons-material/LockOutlined"; +import LockOpenOutlined from "@mui/icons-material/LockOpenOutlined"; +import PasswordOutlined from "@mui/icons-material/PasswordOutlined"; +import Person from "@mui/icons-material/Person"; +import PersonRemoveOutlined from "@mui/icons-material/PersonRemoveOutlined"; +import VerifiedUserOutlined from "@mui/icons-material/VerifiedUserOutlined"; +import Checkbox from "@mui/material/Checkbox"; +import { AdminActionIconButton, AdminRowActions } from "@/shared/ui/AdminListLayout.jsx"; +import { DataTable } from "@/shared/ui/DataTable.jsx"; +import { UserStatusBadge } from "./UserStatusBadge.jsx"; + +export function UserTable({ + canEdit, + canResetPassword, + canStatus, + onEdit, + onOpenProfile = () => {}, + onResetPassword, + onToggleDisabled, + onToggleLocked, + selectedIds, + setSelectedIds, + users +}) { + const allChecked = users.length > 0 && users.every((user) => selectedIds.includes(user.id)); + + const toggleAll = (checked) => { + if (!checked) { + setSelectedIds([]); + return; + } + setSelectedIds(users.map((user) => user.id)); + }; + const columns = [ + { + key: "select", + header: toggleAll(event.target.checked)} size="small" />, + width: "48px", + render: (user) => ( + { + setSelectedIds((current) => (event.target.checked ? [...current, user.id] : current.filter((id) => id !== user.id))); + }} + size="small" + /> + ) + }, + { + key: "user", + label: "用户", + width: "minmax(180px, 1.4fr)", + render: (user) => ( +
+ +
+
{user.name}
+
{user.account}
+
+
+ ) + }, + { key: "role", label: "角色", width: "minmax(120px, 1fr)", render: (user) => user.role || "-" }, + { key: "team", label: "团队", width: "minmax(120px, 1fr)", render: (user) => user.team || "-" }, + { key: "status", label: "状态", width: "minmax(96px, 0.72fr)", render: (user) => }, + { key: "mfa", label: "MFA", width: "minmax(80px, 0.72fr)" }, + { key: "lastLogin", label: "最后登录", width: "minmax(120px, 0.9fr)", className: "muted" }, + { + key: "actions", + label: "操作", + width: "minmax(180px, 1.2fr)", + render: (user) => ( + + ) + } + ]; + + return ( + user.id} + /> + ); +} + +function UserRowActions({ + canEdit, + canResetPassword, + canStatus, + onEdit, + onResetPassword, + onToggleDisabled, + onToggleLocked, + user +}) { + const isDisabled = user.status === "disabled"; + const isLocked = user.status === "locked"; + + return ( + + onEdit(user)}> + + + onResetPassword(user)}> + + + onToggleLocked(user)} + tone={isLocked ? "success" : undefined} + > + {isLocked ? : } + + onToggleDisabled(user)} + tone={isDisabled ? "success" : "danger"} + > + {isDisabled ? : } + + + ); +} diff --git a/src/features/users/components/UserTable.test.jsx b/src/features/users/components/UserTable.test.jsx new file mode 100644 index 0000000..7abeb72 --- /dev/null +++ b/src/features/users/components/UserTable.test.jsx @@ -0,0 +1,40 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { expect, test, vi } from "vitest"; +import { UserTable } from "./UserTable.jsx"; + +const baseUser = { + account: "alice", + id: 1, + lastLogin: "2026-05-01 10:00", + mfa: "已开启", + name: "Alice", + role: "管理员", + status: "active", + team: "平台" +}; + +test("opens user profile from avatar", async () => { + const onOpenProfile = vi.fn(); + const user = userEvent.setup(); + + render( + + ); + + await user.click(screen.getByRole("button", { name: "查看 Alice 信息" })); + + expect(onOpenProfile).toHaveBeenCalledWith(baseUser); +}); diff --git a/src/features/users/components/UserToolbar.jsx b/src/features/users/components/UserToolbar.jsx new file mode 100644 index 0000000..1293c43 --- /dev/null +++ b/src/features/users/components/UserToolbar.jsx @@ -0,0 +1,30 @@ +import Add from "@mui/icons-material/Add"; +import DownloadOutlined from "@mui/icons-material/DownloadOutlined"; +import ShieldOutlined from "@mui/icons-material/ShieldOutlined"; +import { Button } from "@/shared/ui/Button.jsx"; +import { PageHead } from "@/shared/ui/PageHead.jsx"; + +export function UserToolbar({ abilities, onCreate, onExport, onOpenRoles }) { + return ( + + {abilities.canViewRoles ? ( + + ) : null} + {abilities.canExport ? ( + + ) : null} + {abilities.canCreate ? ( + + ) : null} + + ); +} diff --git a/src/features/users/components/UserToolbar.test.jsx b/src/features/users/components/UserToolbar.test.jsx new file mode 100644 index 0000000..61ef12d --- /dev/null +++ b/src/features/users/components/UserToolbar.test.jsx @@ -0,0 +1,35 @@ +import { render, screen } from "@testing-library/react"; +import { expect, test, vi } from "vitest"; +import { UserToolbar } from "./UserToolbar.jsx"; + +const handlers = { + onCreate: vi.fn(), + onExport: vi.fn(), + onOpenRoles: vi.fn() +}; + +test("hides protected user actions without abilities", () => { + render( + + ); + + expect(screen.queryByRole("button", { name: /新增用户/ })).not.toBeInTheDocument(); + expect(screen.queryByRole("button", { name: /导出/ })).not.toBeInTheDocument(); + expect(screen.queryByRole("button", { name: /角色配置/ })).not.toBeInTheDocument(); +}); + +test("shows user actions with matching abilities", () => { + render( + + ); + + expect(screen.getByRole("button", { name: /新增用户/ })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: /导出/ })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: /角色配置/ })).toBeInTheDocument(); +}); diff --git a/src/data/users.js b/src/features/users/constants.js similarity index 89% rename from src/data/users.js rename to src/features/users/constants.js index 6954d53..af1a271 100644 --- a/src/data/users.js +++ b/src/features/users/constants.js @@ -4,6 +4,13 @@ export const userStatusMeta = { disabled: { label: "停用", tone: "stopped" } }; +export const userStatusFilters = [ + ["", "全部状态"], + ["active", "启用"], + ["locked", "锁定"], + ["disabled", "停用"] +]; + export const initialUsers = [ { id: "u-001", diff --git a/src/features/users/hooks/useUsersPage.js b/src/features/users/hooks/useUsersPage.js new file mode 100644 index 0000000..636fcd4 --- /dev/null +++ b/src/features/users/hooks/useUsersPage.js @@ -0,0 +1,268 @@ +import { useCallback, useMemo, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { downloadCsv } from "@/shared/api/download"; +import { toPageQuery } from "@/shared/api/query"; +import { parseForm } from "@/shared/forms/validation"; +import { useAdminQuery } from "@/shared/hooks/useAdminQuery.js"; +import { useConfirm } from "@/shared/ui/ConfirmProvider.jsx"; +import { useToast } from "@/shared/ui/ToastProvider.jsx"; +import { listRoles } from "@/features/roles/api"; +import { + batchUpdateUserStatus, + createUser, + exportUsers, + listUsers, + resetUserPassword, + updateUser, + updateUserStatus +} from "@/features/users/api"; +import { useUserAbilities } from "@/features/users/permissions.js"; +import { userCreateFormSchema, userUpdateFormSchema } from "@/features/users/schema"; + +const pageSize = 10; +const emptyUsers = { items: [], total: 0, page: 1, pageSize }; +const emptyData = { roles: [], users: emptyUsers }; + +const emptyForm = { + mfaEnabled: false, + name: "", + password: "", + roleIds: [], + status: "active", + team: "", + username: "" +}; + +export function useUsersPage() { + const [query, setQuery] = useState(""); + const [roleFilter, setRoleFilter] = useState(""); + const [statusFilter, setStatusFilter] = useState(""); + const [page, setPage] = useState(1); + const [selectedIds, setSelectedIds] = useState([]); + const [formOpen, setFormOpen] = useState(false); + const [editingUser, setEditingUser] = useState(null); + const [profileUser, setProfileUser] = useState(null); + const [form, setForm] = useState(emptyForm); + const abilities = useUserAbilities(); + const confirm = useConfirm(); + const { showToast } = useToast(); + const navigate = useNavigate(); + + const requestQuery = useMemo( + () => toPageQuery({ keyword: query, page, pageSize, role: roleFilter, status: statusFilter }), + [page, query, roleFilter, statusFilter] + ); + + const queryFn = useCallback(async () => { + const [userData, roleData] = await Promise.all([ + listUsers(requestQuery), + abilities.canViewRoles ? listRoles() : [] + ]); + setSelectedIds([]); + return { roles: roleData || [], users: userData || emptyUsers }; + }, [abilities.canViewRoles, requestQuery]); + + const { data, error, loading, reload } = useAdminQuery(queryFn, { + errorMessage: "加载用户失败", + initialData: emptyData, + keepPreviousData: true, + queryKey: ["users", requestQuery, abilities.canViewRoles] + }); + + const users = data?.users || emptyUsers; + const roles = useMemo(() => data?.roles || [], [data?.roles]); + + const roleFilters = useMemo(() => [["", "全部角色"], ...roles.map((role) => [role.name, role.name])], [roles]); + + const stats = useMemo(() => { + const items = users.items || []; + const active = items.filter((user) => user.status === "active").length; + const locked = items.filter((user) => user.status === "locked").length; + const admin = items.filter((user) => String(user.role).includes("管理员")).length; + return { active, admin, locked }; + }, [users.items]); + + const changeQuery = (value) => { + setQuery(value); + setPage(1); + }; + + const changeRoleFilter = (value) => { + setRoleFilter(value); + setPage(1); + }; + + const changeStatusFilter = (value) => { + setStatusFilter(value); + setPage(1); + }; + + const openCreate = () => { + setEditingUser(null); + setForm(emptyForm); + setFormOpen(true); + }; + + const openEdit = (user) => { + setEditingUser(user); + setForm({ + mfaEnabled: user.mfaEnabled, + name: user.name, + password: "", + roleIds: user.roleIds || [], + status: user.status, + team: user.team || "", + username: user.account + }); + setFormOpen(true); + }; + + const closeForm = () => { + setFormOpen(false); + }; + + const openProfile = (user) => { + setProfileUser(user); + }; + + const closeProfile = () => { + setProfileUser(null); + }; + + const submitUser = async (event) => { + event.preventDefault(); + try { + if (editingUser) { + const payload = parseForm(userUpdateFormSchema, form); + await updateUser(editingUser.id, payload); + showToast("用户已更新", "success"); + } else { + const payload = parseForm(userCreateFormSchema, form); + const result = await createUser(payload); + showToast(`用户已创建,初始密码:${result.initialPassword}`, "success"); + } + setFormOpen(false); + reload(); + } catch (err) { + showToast(err.message || "保存用户失败", "error"); + } + }; + + const toggleLocked = async (user) => { + const nextStatus = user.status === "locked" ? "active" : "locked"; + const ok = await confirm({ + confirmText: user.status === "locked" ? "解锁" : "锁定", + message: `${user.name} 的登录状态会立即变更。`, + title: user.status === "locked" ? "解锁用户" : "锁定用户", + tone: user.status === "locked" ? "primary" : "danger" + }); + if (!ok) { + return; + } + await updateUserStatus(user.id, nextStatus); + showToast("用户状态已更新", "success"); + reload(); + }; + + const toggleDisabled = async (user) => { + const nextStatus = user.status === "disabled" ? "active" : "disabled"; + const ok = await confirm({ + confirmText: user.status === "disabled" ? "启用" : "停用", + message: `${user.name} 的账号状态会立即变更。`, + title: user.status === "disabled" ? "启用用户" : "停用用户", + tone: user.status === "disabled" ? "primary" : "danger" + }); + if (!ok) { + return; + } + await updateUserStatus(user.id, nextStatus); + showToast("用户状态已更新", "success"); + reload(); + }; + + const resetPassword = async (user) => { + const ok = await confirm({ + confirmText: "重置", + message: `${user.name} 的密码会重置为系统生成的初始密码。`, + title: "重置密码", + tone: "danger" + }); + if (!ok) { + return; + } + const result = await resetUserPassword(user.id); + showToast(`已重置,初始密码:${result.initialPassword}`, "success"); + }; + + const batchStatus = async (status) => { + if (!selectedIds.length) { + showToast("请先选择用户", "warning"); + return; + } + const ok = await confirm({ + confirmText: "确认", + message: `将更新 ${selectedIds.length} 个用户状态。`, + title: "批量更新状态", + tone: status === "active" ? "primary" : "danger" + }); + if (!ok) { + return; + } + await batchUpdateUserStatus(selectedIds, status); + showToast("批量状态已更新", "success"); + reload(); + }; + + const downloadUsers = () => { + return downloadCsv( + () => exportUsers({ keyword: query, role: roleFilter, status: statusFilter }), + "hyapp-users.csv" + ); + }; + + const setRoleChecked = (roleId, checked) => { + setForm((current) => ({ + ...current, + roleIds: checked ? [...current.roleIds, roleId] : current.roleIds.filter((id) => id !== roleId) + })); + }; + + return { + abilities, + batchStatus, + changeQuery, + changeRoleFilter, + changeStatusFilter, + closeForm, + closeProfile, + downloadUsers, + editingUser, + error, + form, + formOpen, + loading, + navigate, + openCreate, + openEdit, + openProfile, + page, + profileUser, + query, + reload, + resetPassword, + roleFilter, + roleFilters, + roles, + selectedIds, + setForm, + setPage, + setRoleChecked, + setSelectedIds, + stats, + statusFilter, + submitUser, + toggleDisabled, + toggleLocked, + users + }; +} diff --git a/src/features/users/pages/UserManagementPage.jsx b/src/features/users/pages/UserManagementPage.jsx new file mode 100644 index 0000000..871e34b --- /dev/null +++ b/src/features/users/pages/UserManagementPage.jsx @@ -0,0 +1,104 @@ +import Add from "@mui/icons-material/Add"; +import DownloadOutlined from "@mui/icons-material/DownloadOutlined"; +import PersonRemoveOutlined from "@mui/icons-material/PersonRemoveOutlined"; +import ShieldOutlined from "@mui/icons-material/ShieldOutlined"; +import VerifiedUserOutlined from "@mui/icons-material/VerifiedUserOutlined"; +import { DataState } from "@/shared/ui/DataState.jsx"; +import { PaginationBar } from "@/shared/ui/PaginationBar.jsx"; +import { + AdminActionIconButton, + AdminFilterSelect, + AdminListBody, + AdminListPage, + AdminListToolbar, + AdminSearchBox +} from "@/shared/ui/AdminListLayout.jsx"; +import { UserFormDrawer } from "@/features/users/components/UserFormDrawer.jsx"; +import { UserProfileDrawer } from "@/features/users/components/UserProfileDrawer.jsx"; +import { UserTable } from "@/features/users/components/UserTable.jsx"; +import { useUsersPage } from "@/features/users/hooks/useUsersPage.js"; +import { userStatusFilters } from "@/features/users/constants.js"; + +export function UserManagementPage() { + const page = useUsersPage(); + + return ( + <> + + + + + + + )} + actions={( + <> + {page.abilities.canViewRoles ? ( + page.navigate("/system/roles")}> + + + ) : null} + {page.abilities.canStatus ? ( + <> + page.batchStatus("active")}> + + + page.batchStatus("disabled")}> + + + + ) : null} + {page.abilities.canExport ? ( + + + + ) : null} + {page.abilities.canCreate ? ( + + + + ) : null} + + )} + /> + + + + + + + + + + + + + ); +} diff --git a/src/features/users/permissions.js b/src/features/users/permissions.js new file mode 100644 index 0000000..c194461 --- /dev/null +++ b/src/features/users/permissions.js @@ -0,0 +1,15 @@ +import { useAuth } from "@/app/auth/AuthProvider.jsx"; +import { PERMISSIONS } from "@/app/permissions"; + +export function useUserAbilities() { + const { can } = useAuth(); + + return { + canCreate: can(PERMISSIONS.userCreate), + canEdit: can(PERMISSIONS.userUpdate), + canExport: can(PERMISSIONS.userExport), + canResetPassword: can(PERMISSIONS.userResetPassword), + canStatus: can(PERMISSIONS.userStatus), + canViewRoles: can(PERMISSIONS.roleView) + }; +} diff --git a/src/features/users/routes.js b/src/features/users/routes.js new file mode 100644 index 0000000..1a56fdd --- /dev/null +++ b/src/features/users/routes.js @@ -0,0 +1,12 @@ +import { MENU_CODES, PERMISSIONS } from "@/app/permissions"; + +export const usersRoutes = [ + { + label: "用户管理", + loader: () => import("./pages/UserManagementPage.jsx").then((module) => module.UserManagementPage), + menuCode: MENU_CODES.systemUsers, + pageKey: "users", + path: "/system/users", + permission: PERMISSIONS.userView + } +]; diff --git a/src/features/users/schema.test.ts b/src/features/users/schema.test.ts new file mode 100644 index 0000000..38b30a5 --- /dev/null +++ b/src/features/users/schema.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, test } from "vitest"; +import { FormValidationError, parseForm } from "@/shared/forms/validation"; +import { userCreateFormSchema, userUpdateFormSchema } from "./schema"; + +describe("user form schema", () => { + test("normalizes create payload", () => { + const payload = parseForm(userCreateFormSchema, { + mfaEnabled: true, + name: " 张三 ", + password: "", + roleIds: ["1", 2], + status: "active", + team: " 平台组 ", + username: " admin " + }); + + expect(payload).toEqual({ + mfaEnabled: true, + name: "张三", + password: "", + roleIds: [1, 2], + status: "active", + team: "平台组", + username: "admin" + }); + }); + + test("rejects missing required fields", () => { + expect(() => + parseForm(userUpdateFormSchema, { + mfaEnabled: false, + name: "", + roleIds: [], + status: "active", + team: "" + }) + ).toThrow(FormValidationError); + }); +}); diff --git a/src/features/users/schema.ts b/src/features/users/schema.ts new file mode 100644 index 0000000..f5970b9 --- /dev/null +++ b/src/features/users/schema.ts @@ -0,0 +1,22 @@ +import { z } from "zod"; + +const userStatusSchema = z.enum(["active", "disabled", "locked"]); +const roleIdsSchema = z.array(z.coerce.number().int().positive()).default([]); + +const userBaseSchema = z.object({ + mfaEnabled: z.boolean().default(false), + name: z.string().trim().min(1, "请输入姓名").max(50, "姓名不能超过 50 个字符"), + roleIds: roleIdsSchema, + status: userStatusSchema, + team: z.string().trim().max(80, "团队不能超过 80 个字符").optional().default("") +}); + +export const userCreateFormSchema = userBaseSchema.extend({ + password: z.string().trim().max(72, "密码不能超过 72 个字符").optional().default(""), + username: z.string().trim().min(1, "请输入账号").max(50, "账号不能超过 50 个字符") +}); + +export const userUpdateFormSchema = userBaseSchema; + +export type UserCreateForm = z.infer; +export type UserUpdateForm = z.infer; diff --git a/src/features/users/users.css b/src/features/users/users.css new file mode 100644 index 0000000..543d642 --- /dev/null +++ b/src/features/users/users.css @@ -0,0 +1,121 @@ +.user-kpi-row { + display: grid; + grid-template-columns: repeat(4, minmax(170px, 1fr)); + gap: var(--space-4); +} + +.user-filters-panel { + flex-wrap: wrap; +} + +.user-filters-panel .search-box { + flex: 0 0 320px; +} + +.user-main { + display: flex; + min-width: 0; + align-items: center; + gap: var(--space-3); +} + +.user-avatar { + display: grid; + flex: 0 0 auto; + width: 34px; + height: 34px; + place-items: center; + border-radius: var(--radius-control); + background: var(--primary-surface); + color: var(--primary); + transition: + background var(--motion-base) var(--ease-standard), + transform var(--motion-base) var(--ease-emphasized); +} + +.user-avatar-button { + display: grid; + flex: 0 0 auto; + width: 34px; + height: 34px; + padding: 0; + border: 0; + border-radius: var(--radius-control); + background: transparent; + color: inherit; + cursor: pointer; +} + +.user-avatar-button:focus-visible { + outline: 2px solid var(--primary-border-strong); + outline-offset: 2px; +} + +.admin-row:hover .user-avatar { + background: var(--primary-surface-strong); + transform: scale(1.08); +} + +.user-profile { + display: grid; + gap: var(--space-5); +} + +.user-profile__head { + display: flex; + align-items: center; + gap: var(--space-3); +} + +.user-profile__avatar { + display: grid; + width: 42px; + height: 42px; + place-items: center; + border-radius: var(--radius-control); + background: var(--primary-surface); + color: var(--primary); +} + +.user-profile__identity { + display: grid; + min-width: 0; + gap: var(--space-1); +} + +.user-profile__identity strong { + color: var(--text-primary); + font-weight: 800; +} + +.user-profile__identity span { + overflow: hidden; + color: var(--text-secondary); + text-overflow: ellipsis; + white-space: nowrap; +} + +.user-profile__rows { + display: grid; + border-top: 1px solid var(--border); +} + +.user-profile__row { + display: grid; + grid-template-columns: minmax(88px, 0.45fr) minmax(0, 1fr); + gap: var(--space-3); + align-items: center; + min-height: 44px; + border-bottom: 1px solid var(--border); +} + +.user-profile__row span { + color: var(--text-tertiary); +} + +.user-profile__value { + min-width: 0; + color: var(--text-primary); + font-weight: 700; + overflow-wrap: anywhere; +} diff --git a/src/main.jsx b/src/main.jsx index c1df0b9..8e53b3b 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -1,28 +1,21 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; -import { ThemeProvider } from "@mui/material/styles"; import { BrowserRouter } from "react-router-dom"; -import { AuthProvider } from "./auth/AuthProvider.jsx"; -import { ConfirmProvider } from "./components/base/ConfirmProvider.jsx"; -import { ToastProvider } from "./components/base/ToastProvider.jsx"; -import App from "./App.jsx"; -import { theme } from "./theme.js"; +import App from "@/app/App.jsx"; +import { ErrorBoundary } from "@/app/ErrorBoundary.jsx"; +import { AppProviders } from "@/app/providers.jsx"; import "./styles/tokens.css"; import "./styles/base.css"; import "./styles/app.css"; createRoot(document.getElementById("app")).render( - - - - - - - - - - - + + + + + + + ); diff --git a/src/pages/LogsPage.jsx b/src/pages/LogsPage.jsx deleted file mode 100644 index e37ec6d..0000000 --- a/src/pages/LogsPage.jsx +++ /dev/null @@ -1,107 +0,0 @@ -import { useEffect, useState } from "react"; -import { useOutletContext } from "react-router-dom"; -import { listLoginLogs, listOperationLogs } from "../api/logs.js"; -import { Card } from "../components/base/Card.jsx"; -import { DataState } from "../components/base/DataState.jsx"; -import { PaginationBar } from "../components/base/PaginationBar.jsx"; -import { SearchBox } from "../components/base/SearchBox.jsx"; -import { PageHead } from "../components/layout/PageHead.jsx"; - -export function LogsPage({ type }) { - const [query, setQuery] = useState(""); - const [page, setPage] = useState(1); - const [data, setData] = useState({ items: [], total: 0, page: 1, pageSize: 10 }); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(""); - const { refreshTick } = useOutletContext(); - const isLogin = type === "login"; - - const load = async () => { - setLoading(true); - setError(""); - try { - const loader = isLogin ? listLoginLogs : listOperationLogs; - const result = await loader({ page, page_size: 10, keyword: query }); - setData(result); - } catch (err) { - setError(err.message || "加载日志失败"); - } finally { - setLoading(false); - } - }; - - useEffect(() => { - load(); - }, [page, query, refreshTick, type]); - - return ( - <> - -
- { setQuery(value); setPage(1); }} placeholder="搜索用户、IP、动作、资源..." /> -
- - - -
-
{isLogin ? "登录记录" : "操作记录"}
-
共 {data.total} 条
-
-
-
- {isLogin ? ( - <> -
用户
-
IP
-
状态
-
消息
-
时间
- - ) : ( - <> -
用户
-
动作
-
资源
-
状态
-
详情
-
时间
- - )} -
- {(data.items || []).map((item) => ( -
- {isLogin ? ( - <> -
{item.username || "-"}
-
{item.ip || "-"}
-
{item.status}
-
{item.message || "-"}
-
{formatDate(item.createdAt)}
- - ) : ( - <> -
{item.username || "-"}
-
{item.action}
-
{item.resource || "-"}
-
{item.status}
-
{item.detail || "-"}
-
{formatDate(item.createdAt)}
- - )} -
- ))} - {!data.items?.length ?
暂无日志
: null} -
-
- -
- - ); -} - -function formatDate(value) { - if (!value) { - return "-"; - } - return new Date(value).toLocaleString("zh-CN", { hour12: false }); -} diff --git a/src/pages/MenuManagementPage.jsx b/src/pages/MenuManagementPage.jsx deleted file mode 100644 index 904a354..0000000 --- a/src/pages/MenuManagementPage.jsx +++ /dev/null @@ -1,81 +0,0 @@ -import MenuOpenOutlined from "@mui/icons-material/MenuOpenOutlined"; -import ShieldOutlined from "@mui/icons-material/ShieldOutlined"; -import { useEffect, useMemo, useState } from "react"; -import { useOutletContext } from "react-router-dom"; -import { getMenus } from "../api/navigation.js"; -import { listPermissions } from "../api/roles.js"; -import { Card } from "../components/base/Card.jsx"; -import { DataState } from "../components/base/DataState.jsx"; -import { KpiCard } from "../components/base/KpiCard.jsx"; -import { PageHead } from "../components/layout/PageHead.jsx"; - -export function MenuManagementPage() { - const [menus, setMenus] = useState([]); - const [permissions, setPermissions] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(""); - const { refreshTick } = useOutletContext(); - - const flatMenus = useMemo(() => flattenMenus(menus), [menus]); - - const load = async () => { - setLoading(true); - setError(""); - try { - const [menuData, permissionData] = await Promise.all([getMenus(), listPermissions()]); - setMenus(menuData || []); - setPermissions(permissionData || []); - } catch (err) { - setError(err.message || "加载菜单权限失败"); - } finally { - setLoading(false); - } - }; - - useEffect(() => { - load(); - }, [refreshTick]); - - return ( - <> - - - -
- 来自后端菜单树} /> - 菜单 / 按钮 / 接口} /> -
- - -
-
菜单树
-
共 {flatMenus.length} 项
-
-
-
-
菜单
-
路由
-
权限码
-
图标
-
- {flatMenus.map((menu) => ( -
-
{menu.label}
-
{menu.path || "-"}
-
{menu.permissionCode || "-"}
-
{menu.icon || "-"}
-
- ))} -
-
-
- - ); -} - -function flattenMenus(menus, depth = 0) { - return menus.flatMap((menu) => [ - { ...menu, depth }, - ...flattenMenus(menu.children || [], depth + 1) - ]); -} diff --git a/src/pages/NotificationsPage.jsx b/src/pages/NotificationsPage.jsx deleted file mode 100644 index 2ce1a31..0000000 --- a/src/pages/NotificationsPage.jsx +++ /dev/null @@ -1,76 +0,0 @@ -import DoneAllOutlined from "@mui/icons-material/DoneAllOutlined"; -import { useEffect, useState } from "react"; -import { useOutletContext } from "react-router-dom"; -import { listNotifications, markNotificationRead } from "../api/notifications.js"; -import { Button } from "../components/base/Button.jsx"; -import { Card } from "../components/base/Card.jsx"; -import { DataState } from "../components/base/DataState.jsx"; -import { useToast } from "../components/base/ToastProvider.jsx"; -import { PageHead } from "../components/layout/PageHead.jsx"; - -export function NotificationsPage() { - const [data, setData] = useState({ items: [], unread: 0 }); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(""); - const { refreshTick } = useOutletContext(); - const { showToast } = useToast(); - - const load = async () => { - setLoading(true); - setError(""); - try { - setData(await listNotifications()); - } catch (err) { - setError(err.message || "加载通知失败"); - } finally { - setLoading(false); - } - }; - - useEffect(() => { - load(); - }, [refreshTick]); - - const read = async (id) => { - await markNotificationRead(id); - showToast("通知已标记为已读", "success"); - load(); - }; - - return ( - <> - - - -
- {(data.items || []).map((item) => ( -
-
-
{item.title}
-
{item.content}
-
{formatDate(item.createdAt)}
-
- {!item.readAt ? ( - - ) : ( - 已读 - )} -
- ))} - {!data.items?.length ?
暂无通知
: null} -
-
-
- - ); -} - -function formatDate(value) { - if (!value) { - return "-"; - } - return new Date(value).toLocaleString("zh-CN", { hour12: false }); -} diff --git a/src/pages/OverviewPage.jsx b/src/pages/OverviewPage.jsx deleted file mode 100644 index 7f36ea9..0000000 --- a/src/pages/OverviewPage.jsx +++ /dev/null @@ -1,173 +0,0 @@ -import Add from "@mui/icons-material/Add"; -import DeveloperBoardOutlined from "@mui/icons-material/DeveloperBoardOutlined"; -import DnsOutlined from "@mui/icons-material/DnsOutlined"; -import Inventory2Outlined from "@mui/icons-material/Inventory2Outlined"; -import Refresh from "@mui/icons-material/Refresh"; -import SecurityOutlined from "@mui/icons-material/SecurityOutlined"; -import StorageOutlined from "@mui/icons-material/StorageOutlined"; -import { useEffect, useMemo, useState } from "react"; -import { useOutletContext } from "react-router-dom"; -import { getDashboardOverview } from "../api/dashboard.js"; -import { listServices, restartService as restartServiceApi, updateServiceStatus } from "../api/services.js"; -import { useAuth } from "../auth/AuthProvider.jsx"; -import { Button } from "../components/base/Button.jsx"; -import { Card } from "../components/base/Card.jsx"; -import { DataState } from "../components/base/DataState.jsx"; -import { KpiCard } from "../components/base/KpiCard.jsx"; -import { TimeRangeSelect } from "../components/base/TimeRangeSelect.jsx"; -import { useConfirm } from "../components/base/ConfirmProvider.jsx"; -import { useToast } from "../components/base/ToastProvider.jsx"; -import { BarChart } from "../components/charts/BarChart.jsx"; -import { ChartCard } from "../components/charts/ChartCard.jsx"; -import { StatusDonut } from "../components/charts/StatusDonut.jsx"; -import { PageHead } from "../components/layout/PageHead.jsx"; -import { ServerRail } from "../components/service/ServerRail.jsx"; -import { ServiceTable } from "../components/service/ServiceTable.jsx"; - -export function OverviewPage() { - const [range, setRange] = useState("近 1 小时"); - const [overview, setOverview] = useState(null); - const [services, setServices] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(""); - const { refreshTick } = useOutletContext(); - const { can } = useAuth(); - const confirm = useConfirm(); - const { showToast } = useToast(); - - const load = async () => { - setLoading(true); - setError(""); - try { - const [overviewData, serviceData] = await Promise.all([ - getDashboardOverview(), - listServices({ page: 1, page_size: 8 }) - ]); - setOverview(overviewData); - setServices(serviceData.items || []); - } catch (err) { - setError(err.message || "加载总览失败"); - } finally { - setLoading(false); - } - }; - - useEffect(() => { - load(); - }, [refreshTick]); - - const stats = useMemo(() => { - if (!overview) { - return { running: 0, stopped: 0, warning: 0, error: 0, avgCpu: 0, avgMemory: 0 }; - } - return { - running: overview.running, - stopped: overview.stopped, - warning: overview.warning, - error: overview.error, - avgCpu: overview.avgCpu, - avgMemory: overview.avgMemory - }; - }, [overview]); - - const setServiceStatus = async (id, status) => { - if (status === "stopped") { - const ok = await confirm({ - title: "停止服务", - message: "停止后该服务会立即进入不可用状态。", - confirmText: "停止", - tone: "danger" - }); - if (!ok) { - return; - } - } - await updateServiceStatus(id, status); - showToast("服务状态已更新", "success"); - load(); - }; - - const restartService = async (id) => { - const ok = await confirm({ - title: "重启服务", - message: "重启会写入操作日志并刷新服务状态。", - confirmText: "重启" - }); - if (!ok) { - return; - } - await restartServiceApi(id); - showToast("服务已重启", "success"); - load(); - }; - - return ( - <> - - - - {can("service:create") ? ( - - ) : null} - - - -
- 在线 {overview?.serversOnline || 0}} /> - 运行中 {stats.running} / 已停止 {stats.stopped}} /> - 来自服务资产} /> - 来自服务资产} /> - 严重 {stats.error} / 警告 {stats.warning}} /> -
- -
- -
-
服务器状态分布
-
- -
- - - -
-
告警趋势
-
- -
-
- -
- {}} /> - {}} - restartService={restartService} - setServiceStatus={setServiceStatus} - title="服务列表" - /> -
- -
- {(overview?.alerts || []).map((alert) => ( -
- -
-
{alert.name}
-
{alert.desc}
-
-
{alert.time}
-
- ))} -
-
- - ); -} diff --git a/src/pages/RoleManagementPage.jsx b/src/pages/RoleManagementPage.jsx deleted file mode 100644 index bb2ad99..0000000 --- a/src/pages/RoleManagementPage.jsx +++ /dev/null @@ -1,194 +0,0 @@ -import Add from "@mui/icons-material/Add"; -import DeleteOutlineOutlined from "@mui/icons-material/DeleteOutlineOutlined"; -import EditOutlined from "@mui/icons-material/EditOutlined"; -import Drawer from "@mui/material/Drawer"; -import TextField from "@mui/material/TextField"; -import { useEffect, useMemo, useState } from "react"; -import { useOutletContext } from "react-router-dom"; -import { createRole, deleteRole, listPermissions, listRoles, updateRole } from "../api/roles.js"; -import { useAuth } from "../auth/AuthProvider.jsx"; -import { Button } from "../components/base/Button.jsx"; -import { Card } from "../components/base/Card.jsx"; -import { DataState } from "../components/base/DataState.jsx"; -import { IconButton } from "../components/base/IconButton.jsx"; -import { useConfirm } from "../components/base/ConfirmProvider.jsx"; -import { useToast } from "../components/base/ToastProvider.jsx"; -import { PageHead } from "../components/layout/PageHead.jsx"; - -const emptyRole = { name: "", code: "", description: "", permissionIds: [] }; - -export function RoleManagementPage() { - const [roles, setRoles] = useState([]); - const [permissions, setPermissions] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(""); - const [drawerOpen, setDrawerOpen] = useState(false); - const [editingRole, setEditingRole] = useState(null); - const [form, setForm] = useState(emptyRole); - const { refreshTick } = useOutletContext(); - const { can } = useAuth(); - const confirm = useConfirm(); - const { showToast } = useToast(); - - const groupedPermissions = useMemo(() => { - return permissions.reduce((groups, permission) => { - const key = permission.kind || "other"; - groups[key] = groups[key] || []; - groups[key].push(permission); - return groups; - }, {}); - }, [permissions]); - - const load = async () => { - setLoading(true); - setError(""); - try { - const [roleData, permissionData] = await Promise.all([listRoles(), listPermissions()]); - setRoles(roleData || []); - setPermissions(permissionData || []); - } catch (err) { - setError(err.message || "加载角色失败"); - } finally { - setLoading(false); - } - }; - - useEffect(() => { - load(); - }, [refreshTick]); - - const openCreate = () => { - setEditingRole(null); - setForm(emptyRole); - setDrawerOpen(true); - }; - - const openEdit = (role) => { - setEditingRole(role); - setForm({ - name: role.name, - code: role.code, - description: role.description || "", - permissionIds: role.permissions?.map((permission) => permission.id) || [] - }); - setDrawerOpen(true); - }; - - const submit = async (event) => { - event.preventDefault(); - try { - if (editingRole) { - await updateRole(editingRole.id, form); - showToast("角色已更新", "success"); - } else { - await createRole(form); - showToast("角色已创建", "success"); - } - setDrawerOpen(false); - load(); - } catch (err) { - showToast(err.message || "保存角色失败", "error"); - } - }; - - const remove = async (role) => { - const ok = await confirm({ - title: "删除角色", - message: `${role.name} 删除后会影响已绑定用户的权限。`, - confirmText: "删除", - tone: "danger" - }); - if (!ok) { - return; - } - await deleteRole(role.id); - showToast("角色已删除", "success"); - load(); - }; - - const togglePermission = (permissionId, checked) => { - setForm((current) => ({ - ...current, - permissionIds: checked - ? [...current.permissionIds, permissionId] - : current.permissionIds.filter((id) => id !== permissionId) - })); - }; - - return ( - <> - - {can("role:manage") ? ( - - ) : null} - - - - -
-
角色清单
-
共 {roles.length} 条
-
-
-
-
角色
-
编码
-
权限数量
-
说明
-
操作
-
- {roles.map((role) => ( -
-
{role.name}
-
{role.code}
-
{role.permissions?.length || 0}
-
{role.description || "-"}
-
- openEdit(role)}> - - - remove(role)} tone="danger"> - - -
-
- ))} -
-
-
- - setDrawerOpen(false)}> -
-

{editingRole ? "编辑角色" : "新增角色"}

- setForm({ ...form, name: event.target.value })} /> - setForm({ ...form, code: event.target.value })} /> - setForm({ ...form, description: event.target.value })} /> - {Object.entries(groupedPermissions).map(([kind, items]) => ( -
-
{kind}
-
- {items.map((permission) => ( - - ))} -
-
- ))} -
- - -
- -
- - ); -} diff --git a/src/pages/ServicesPage.jsx b/src/pages/ServicesPage.jsx deleted file mode 100644 index 4d0f76f..0000000 --- a/src/pages/ServicesPage.jsx +++ /dev/null @@ -1,232 +0,0 @@ -import Add from "@mui/icons-material/Add"; -import Inventory2Outlined from "@mui/icons-material/Inventory2Outlined"; -import PlayCircleOutlined from "@mui/icons-material/PlayCircleOutlined"; -import StopCircleOutlined from "@mui/icons-material/StopCircleOutlined"; -import WarningAmberOutlined from "@mui/icons-material/WarningAmberOutlined"; -import Drawer from "@mui/material/Drawer"; -import TextField from "@mui/material/TextField"; -import { useEffect, useMemo, useState } from "react"; -import { useOutletContext, useSearchParams } from "react-router-dom"; -import { - createService, - listServices, - restartService as restartServiceApi, - updateServiceStatus -} from "../api/services.js"; -import { useAuth } from "../auth/AuthProvider.jsx"; -import { Button } from "../components/base/Button.jsx"; -import { DataState } from "../components/base/DataState.jsx"; -import { FilterChip } from "../components/base/FilterChip.jsx"; -import { KpiCard } from "../components/base/KpiCard.jsx"; -import { PaginationBar } from "../components/base/PaginationBar.jsx"; -import { SearchBox } from "../components/base/SearchBox.jsx"; -import { useConfirm } from "../components/base/ConfirmProvider.jsx"; -import { useToast } from "../components/base/ToastProvider.jsx"; -import { PageHead } from "../components/layout/PageHead.jsx"; -import { DetailDrawer, InlineInspector } from "../components/service/DetailPanel.jsx"; -import { ServiceTable } from "../components/service/ServiceTable.jsx"; -import { detailTabs } from "../constants/status.js"; - -const filterItems = [ - ["all", "全部状态"], - ["running", "运行中"], - ["warning", "警告"], - ["error", "异常"], - ["stopped", "已停止"] -]; - -const emptyForm = { - name: "", - desc: "", - deploy: "prod-a", - version: "v1.0.0", - port: "8080", - replicas: "1/1", - response: "-", - cpu: 0, - memory: 0, - status: "running" -}; - -export function ServicesPage() { - const [query, setQuery] = useState(""); - const [statusFilter, setStatusFilter] = useState("all"); - const [page, setPage] = useState(1); - const [data, setData] = useState({ items: [], total: 0, page: 1, pageSize: 10 }); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(""); - const [selectedId, setSelectedId] = useState(null); - const [drawerOpen, setDrawerOpen] = useState(false); - const [activeTab, setActiveTab] = useState(detailTabs[0]); - const [formOpen, setFormOpen] = useState(false); - const [form, setForm] = useState(emptyForm); - const { refreshTick } = useOutletContext(); - const [searchParams] = useSearchParams(); - const { can } = useAuth(); - const confirm = useConfirm(); - const { showToast } = useToast(); - - const load = async () => { - setLoading(true); - setError(""); - try { - const result = await listServices({ page, page_size: 10, keyword: query, status: statusFilter }); - setData(result); - if (!selectedId && result.items?.length) { - setSelectedId(Number(searchParams.get("service")) || result.items[0].id); - } - } catch (err) { - setError(err.message || "加载服务失败"); - } finally { - setLoading(false); - } - }; - - useEffect(() => { - load(); - }, [page, query, statusFilter, refreshTick]); - - const stats = useMemo(() => { - const items = data.items || []; - return { - running: items.filter((service) => service.status === "running").length, - stopped: items.filter((service) => service.status === "stopped").length, - error: items.filter((service) => service.status === "error").length - }; - }, [data.items]); - - const selectedService = (data.items || []).find((service) => service.id === selectedId) || data.items?.[0]; - - const openDetail = (id) => { - setSelectedId(id); - setActiveTab(detailTabs[0]); - setDrawerOpen(true); - }; - - const setServiceStatus = async (id, status) => { - if (status === "stopped") { - const ok = await confirm({ - title: "停止服务", - message: "停止服务会写入操作日志,并让该服务进入不可用状态。", - confirmText: "停止", - tone: "danger" - }); - if (!ok) { - return; - } - } - await updateServiceStatus(id, status); - showToast("服务状态已更新", "success"); - load(); - }; - - const restartService = async (id) => { - const ok = await confirm({ title: "重启服务", message: "重启会刷新服务状态并记录操作日志。", confirmText: "重启" }); - if (!ok) { - return; - } - await restartServiceApi(id); - showToast("服务已重启", "success"); - load(); - }; - - const submitService = async (event) => { - event.preventDefault(); - try { - await createService({ ...form, cpu: Number(form.cpu), memory: Number(form.memory) }); - showToast("服务已创建", "success"); - setFormOpen(false); - setForm(emptyForm); - load(); - } catch (err) { - showToast(err.message || "创建服务失败", "error"); - } - }; - - return ( - <> - - {can("service:create") ? ( - - ) : null} - - -
- 来自后端资产} /> - 当前页统计} /> - 当前页统计} /> - 当前页统计} /> -
- -
- { setQuery(value); setPage(1); }} placeholder="搜索服务名称、IP、端口..." /> -
- {filterItems.map(([value, label]) => ( - { - setStatusFilter(value); - setPage(1); - }} - /> - ))} -
-
- - -
-
- - -
- -
-
- - setDrawerOpen(false)} - restartService={restartService} - service={selectedService} - setActiveTab={setActiveTab} - setServiceStatus={setServiceStatus} - /> - - setFormOpen(false)}> -
-

创建服务

- setForm({ ...form, name: event.target.value })} /> - setForm({ ...form, desc: event.target.value })} /> - setForm({ ...form, deploy: event.target.value })} /> - setForm({ ...form, version: event.target.value })} /> - setForm({ ...form, port: event.target.value })} /> - setForm({ ...form, cpu: event.target.value })} /> - setForm({ ...form, memory: event.target.value })} /> -
- - -
- -
- - ); -} diff --git a/src/pages/UserManagementPage.jsx b/src/pages/UserManagementPage.jsx deleted file mode 100644 index 5cea24b..0000000 --- a/src/pages/UserManagementPage.jsx +++ /dev/null @@ -1,335 +0,0 @@ -import Add from "@mui/icons-material/Add"; -import BlockOutlined from "@mui/icons-material/BlockOutlined"; -import DownloadOutlined from "@mui/icons-material/DownloadOutlined"; -import ManageAccountsOutlined from "@mui/icons-material/ManageAccountsOutlined"; -import ShieldOutlined from "@mui/icons-material/ShieldOutlined"; -import VerifiedUserOutlined from "@mui/icons-material/VerifiedUserOutlined"; -import Drawer from "@mui/material/Drawer"; -import FormControlLabel from "@mui/material/FormControlLabel"; -import Switch from "@mui/material/Switch"; -import TextField from "@mui/material/TextField"; -import { useEffect, useMemo, useState } from "react"; -import { useNavigate, useOutletContext } from "react-router-dom"; -import { listRoles } from "../api/roles.js"; -import { - batchUpdateUserStatus, - createUser, - exportUsers, - listUsers, - resetUserPassword, - updateUser, - updateUserStatus -} from "../api/users.js"; -import { useAuth } from "../auth/AuthProvider.jsx"; -import { Button } from "../components/base/Button.jsx"; -import { DataState } from "../components/base/DataState.jsx"; -import { FilterChip } from "../components/base/FilterChip.jsx"; -import { KpiCard } from "../components/base/KpiCard.jsx"; -import { PaginationBar } from "../components/base/PaginationBar.jsx"; -import { SearchBox } from "../components/base/SearchBox.jsx"; -import { useConfirm } from "../components/base/ConfirmProvider.jsx"; -import { useToast } from "../components/base/ToastProvider.jsx"; -import { PageHead } from "../components/layout/PageHead.jsx"; -import { UserTable } from "../components/user/UserTable.jsx"; -import { userStatusMeta } from "../data/users.js"; - -const statusFilters = [ - ["all", "全部状态"], - ["active", "启用"], - ["locked", "锁定"], - ["disabled", "停用"] -]; - -const emptyForm = { - username: "", - name: "", - team: "", - password: "", - status: "active", - mfaEnabled: false, - roleIds: [] -}; - -export function UserManagementPage() { - const [users, setUsers] = useState({ items: [], total: 0, page: 1, pageSize: 10 }); - const [roles, setRoles] = useState([]); - const [query, setQuery] = useState(""); - const [roleFilter, setRoleFilter] = useState("all"); - const [statusFilter, setStatusFilter] = useState("all"); - const [page, setPage] = useState(1); - const [selectedIds, setSelectedIds] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(""); - const [formOpen, setFormOpen] = useState(false); - const [editingUser, setEditingUser] = useState(null); - const [form, setForm] = useState(emptyForm); - const { refreshTick } = useOutletContext(); - const { can } = useAuth(); - const navigate = useNavigate(); - const confirm = useConfirm(); - const { showToast } = useToast(); - - const roleFilters = useMemo(() => [["all", "全部角色"], ...roles.map((role) => [role.name, role.name])], [roles]); - - const load = async () => { - setLoading(true); - setError(""); - try { - const [userData, roleData] = await Promise.all([ - listUsers({ page, page_size: 10, keyword: query, role: roleFilter, status: statusFilter }), - listRoles() - ]); - setUsers(userData); - setRoles(roleData || []); - setSelectedIds([]); - } catch (err) { - setError(err.message || "加载用户失败"); - } finally { - setLoading(false); - } - }; - - useEffect(() => { - load(); - }, [page, query, roleFilter, statusFilter, refreshTick]); - - const stats = useMemo(() => { - const items = users.items || []; - const active = items.filter((user) => user.status === "active").length; - const locked = items.filter((user) => user.status === "locked").length; - const admin = items.filter((user) => String(user.role).includes("管理员")).length; - return { active, admin, locked }; - }, [users.items]); - - const openCreate = () => { - setEditingUser(null); - setForm(emptyForm); - setFormOpen(true); - }; - - const openEdit = (user) => { - setEditingUser(user); - setForm({ - username: user.account, - name: user.name, - team: user.team || "", - password: "", - status: user.status, - mfaEnabled: user.mfaEnabled, - roleIds: user.roleIds || [] - }); - setFormOpen(true); - }; - - const submitUser = async (event) => { - event.preventDefault(); - try { - if (editingUser) { - await updateUser(editingUser.id, { - name: form.name, - team: form.team, - status: form.status, - mfaEnabled: form.mfaEnabled, - roleIds: form.roleIds - }); - showToast("用户已更新", "success"); - } else { - const result = await createUser(form); - showToast(`用户已创建,初始密码:${result.initialPassword}`, "success"); - } - setFormOpen(false); - load(); - } catch (err) { - showToast(err.message || "保存用户失败", "error"); - } - }; - - const toggleLocked = async (user) => { - const nextStatus = user.status === "locked" ? "active" : "locked"; - const ok = await confirm({ - title: user.status === "locked" ? "解锁用户" : "锁定用户", - message: `${user.name} 的登录状态会立即变更。`, - confirmText: user.status === "locked" ? "解锁" : "锁定", - tone: user.status === "locked" ? "primary" : "danger" - }); - if (!ok) { - return; - } - await updateUserStatus(user.id, nextStatus); - showToast("用户状态已更新", "success"); - load(); - }; - - const toggleDisabled = async (user) => { - const nextStatus = user.status === "disabled" ? "active" : "disabled"; - const ok = await confirm({ - title: user.status === "disabled" ? "启用用户" : "停用用户", - message: `${user.name} 的账号状态会立即变更。`, - confirmText: user.status === "disabled" ? "启用" : "停用", - tone: user.status === "disabled" ? "primary" : "danger" - }); - if (!ok) { - return; - } - await updateUserStatus(user.id, nextStatus); - showToast("用户状态已更新", "success"); - load(); - }; - - const resetPassword = async (user) => { - const ok = await confirm({ - title: "重置密码", - message: `${user.name} 的密码会重置为系统生成的初始密码。`, - confirmText: "重置", - tone: "danger" - }); - if (!ok) { - return; - } - const result = await resetUserPassword(user.id); - showToast(`已重置,初始密码:${result.initialPassword}`, "success"); - }; - - const batchStatus = async (status) => { - if (!selectedIds.length) { - showToast("请先选择用户", "warning"); - return; - } - const ok = await confirm({ - title: "批量更新状态", - message: `将更新 ${selectedIds.length} 个用户状态。`, - confirmText: "确认", - tone: status === "active" ? "primary" : "danger" - }); - if (!ok) { - return; - } - await batchUpdateUserStatus(selectedIds, status); - showToast("批量状态已更新", "success"); - load(); - }; - - const downloadCSV = async () => { - const response = await exportUsers({ keyword: query, role: roleFilter, status: statusFilter }); - const blob = await response.blob(); - const url = window.URL.createObjectURL(blob); - const link = document.createElement("a"); - link.href = url; - link.download = "hyapp-users.csv"; - link.click(); - window.URL.revokeObjectURL(url); - }; - - const setRoleChecked = (roleId, checked) => { - setForm((current) => ({ - ...current, - roleIds: checked ? [...current.roleIds, roleId] : current.roleIds.filter((id) => id !== roleId) - })); - }; - - return ( - <> - - {can("role:view") ? ( - - ) : null} - - {can("user:create") ? ( - - ) : null} - - -
- {stats.active} 个启用账户} /> - 当前页统计} /> - 可登录控制台} /> - 需管理员处理} /> -
- -
- { setQuery(value); setPage(1); }} placeholder="搜索用户、账号、角色、团队..." /> -
- {roleFilters.map(([value, label]) => ( - { setRoleFilter(value); setPage(1); }} /> - ))} -
-
- {statusFilters.map(([value, label]) => ( - { setStatusFilter(value); setPage(1); }} /> - ))} -
- {can("user:status") ? ( -
- - -
- ) : null} -
- - - - - - - setFormOpen(false)}> -
-

{editingUser ? "编辑用户" : "新增用户"}

- setForm({ ...form, username: event.target.value })} - /> - setForm({ ...form, name: event.target.value })} /> - setForm({ ...form, team: event.target.value })} /> - {!editingUser ? ( - setForm({ ...form, password: event.target.value })} /> - ) : null} -
角色
-
- {roles.map((role) => ( - - ))} -
- setForm({ ...form, mfaEnabled: event.target.checked })} />} - label={form.mfaEnabled ? "MFA 已开启" : "MFA 未开启"} - /> -
- - -
- -
- - ); -} diff --git a/src/shared/api/download.ts b/src/shared/api/download.ts new file mode 100644 index 0000000..fe7832f --- /dev/null +++ b/src/shared/api/download.ts @@ -0,0 +1,14 @@ +export async function downloadResponse(response: Response, filename: string) { + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = filename; + link.click(); + window.URL.revokeObjectURL(url); +} + +export async function downloadCsv(request: () => Promise, filename: string) { + const response = await request(); + await downloadResponse(response, filename); +} diff --git a/src/shared/api/generated/endpoints.ts b/src/shared/api/generated/endpoints.ts new file mode 100644 index 0000000..2d9f083 --- /dev/null +++ b/src/shared/api/generated/endpoints.ts @@ -0,0 +1,899 @@ +/* eslint-disable */ +// Generated by scripts/generate-api-client.mjs. Do not edit manually. +import type { PermissionCode } from "@/app/permissions"; + +export type ApiHttpMethod = "DELETE" | "GET" | "PATCH" | "POST" | "PUT"; + +export interface ApiEndpoint { + method: ApiHttpMethod; + operationId: string; + path: string; + permission?: PermissionCode; + permissions?: PermissionCode[]; +} + +export const API_OPERATIONS = { + appBanUser: "appBanUser", + appGetUser: "appGetUser", + appListUsers: "appListUsers", + appSetPassword: "appSetPassword", + appUnbanUser: "appUnbanUser", + appUpdateUser: "appUpdateUser", + batchUpdateUserStatus: "batchUpdateUserStatus", + cancelJob: "cancelJob", + changePassword: "changePassword", + closeAgency: "closeAgency", + createAgency: "createAgency", + createBD: "createBD", + createBDLeader: "createBDLeader", + createCoinSeller: "createCoinSeller", + createCountry: "createCountry", + createGift: "createGift", + createMenu: "createMenu", + createPermission: "createPermission", + createRegion: "createRegion", + createResource: "createResource", + createResourceGroup: "createResourceGroup", + createRole: "createRole", + createUser: "createUser", + createUserExportJob: "createUserExportJob", + dashboardOverview: "dashboardOverview", + deleteCountry: "deleteCountry", + deleteMenu: "deleteMenu", + deleteNotification: "deleteNotification", + deletePermission: "deletePermission", + deleteRole: "deleteRole", + deleteRoom: "deleteRoom", + disableCountry: "disableCountry", + disableGift: "disableGift", + disableRegion: "disableRegion", + disableResource: "disableResource", + disableResourceGroup: "disableResourceGroup", + downloadJobArtifact: "downloadJobArtifact", + enableCountry: "enableCountry", + enableGift: "enableGift", + enableRegion: "enableRegion", + enableResource: "enableResource", + enableResourceGroup: "enableResourceGroup", + exportLoginLogs: "exportLoginLogs", + exportOperationLogs: "exportOperationLogs", + exportUsers: "exportUsers", + getCountry: "getCountry", + getRegion: "getRegion", + getResource: "getResource", + getResourceGroup: "getResourceGroup", + getRoleDataScopes: "getRoleDataScopes", + getUser: "getUser", + grantResource: "grantResource", + grantResourceGroup: "grantResourceGroup", + listAgencies: "listAgencies", + listApps: "listApps", + listBDLeaders: "listBDLeaders", + listBDs: "listBDs", + listCoinSellers: "listCoinSellers", + listCountries: "listCountries", + listGifts: "listGifts", + listH5Links: "listH5Links", + listHosts: "listHosts", + listJobs: "listJobs", + listLoginLogs: "listLoginLogs", + listNotifications: "listNotifications", + listOperationLogs: "listOperationLogs", + listPermissions: "listPermissions", + listRegions: "listRegions", + listResourceGrants: "listResourceGrants", + listResourceGroups: "listResourceGroups", + listResources: "listResources", + listRoles: "listRoles", + listRooms: "listRooms", + listSystemMenus: "listSystemMenus", + listUsers: "listUsers", + login: "login", + logout: "logout", + markAllNotificationsRead: "markAllNotificationsRead", + markNotificationRead: "markNotificationRead", + me: "me", + navigationMenus: "navigationMenus", + refresh: "refresh", + replaceRegionCountries: "replaceRegionCountries", + replaceRoleDataScopes: "replaceRoleDataScopes", + replaceRolePermissions: "replaceRolePermissions", + resetUserPassword: "resetUserPassword", + search: "search", + setAgencyJoinEnabled: "setAgencyJoinEnabled", + setBDLeaderStatus: "setBDLeaderStatus", + setBDStatus: "setBDStatus", + setCoinSellerStatus: "setCoinSellerStatus", + sortMenus: "sortMenus", + syncPermissions: "syncPermissions", + updateCountry: "updateCountry", + updateGift: "updateGift", + updateH5Links: "updateH5Links", + updateMenu: "updateMenu", + updateMenuVisible: "updateMenuVisible", + updatePermission: "updatePermission", + updateRegion: "updateRegion", + updateResource: "updateResource", + updateResourceGroup: "updateResourceGroup", + updateResourceGroupItems: "updateResourceGroupItems", + updateRole: "updateRole", + updateRoom: "updateRoom", + updateUser: "updateUser", + updateUserStatus: "updateUserStatus", + uploadFile: "uploadFile", + uploadImage: "uploadImage" +} as const; + +export type ApiOperationId = (typeof API_OPERATIONS)[keyof typeof API_OPERATIONS]; + +export const API_ENDPOINTS: Record = { + appBanUser: { + method: "POST", + operationId: API_OPERATIONS.appBanUser, + path: "/v1/app/users/{id}/ban", + permission: "app-user:status", + permissions: ["app-user:status"] + }, + appGetUser: { + method: "GET", + operationId: API_OPERATIONS.appGetUser, + path: "/v1/app/users/{id}", + permission: "app-user:view", + permissions: ["app-user:view"] + }, + appListUsers: { + method: "GET", + operationId: API_OPERATIONS.appListUsers, + path: "/v1/app/users", + permission: "app-user:view", + permissions: ["app-user:view"] + }, + appSetPassword: { + method: "POST", + operationId: API_OPERATIONS.appSetPassword, + path: "/v1/app/users/{id}/password", + permission: "app-user:password", + permissions: ["app-user:password"] + }, + appUnbanUser: { + method: "POST", + operationId: API_OPERATIONS.appUnbanUser, + path: "/v1/app/users/{id}/unban", + permission: "app-user:status", + permissions: ["app-user:status"] + }, + appUpdateUser: { + method: "PATCH", + operationId: API_OPERATIONS.appUpdateUser, + path: "/v1/app/users/{id}", + permission: "app-user:update", + permissions: ["app-user:update"] + }, + batchUpdateUserStatus: { + method: "POST", + operationId: API_OPERATIONS.batchUpdateUserStatus, + path: "/v1/users/batch/status", + permission: "user:status", + permissions: ["user:status"] + }, + cancelJob: { + method: "POST", + operationId: API_OPERATIONS.cancelJob, + path: "/v1/jobs/{id}/cancel", + permission: "job:cancel", + permissions: ["job:cancel"] + }, + changePassword: { + method: "POST", + operationId: API_OPERATIONS.changePassword, + path: "/v1/auth/change-password" + }, + closeAgency: { + method: "POST", + operationId: API_OPERATIONS.closeAgency, + path: "/v1/admin/agencies/{agency_id}/close", + permission: "agency:status", + permissions: ["agency:status"] + }, + createAgency: { + method: "POST", + operationId: API_OPERATIONS.createAgency, + path: "/v1/admin/agencies", + permission: "agency:create", + permissions: ["agency:create"] + }, + createBD: { + method: "POST", + operationId: API_OPERATIONS.createBD, + path: "/v1/admin/bds", + permission: "bd:create", + permissions: ["bd:create"] + }, + createBDLeader: { + method: "POST", + operationId: API_OPERATIONS.createBDLeader, + path: "/v1/admin/bd-leaders", + permission: "bd:create", + permissions: ["bd:create"] + }, + createCoinSeller: { + method: "POST", + operationId: API_OPERATIONS.createCoinSeller, + path: "/v1/admin/coin-sellers", + permission: "coin-seller:create", + permissions: ["coin-seller:create"] + }, + createCountry: { + method: "POST", + operationId: API_OPERATIONS.createCountry, + path: "/v1/admin/countries", + permission: "country:create", + permissions: ["country:create"] + }, + createGift: { + method: "POST", + operationId: API_OPERATIONS.createGift, + path: "/v1/admin/gifts", + permission: "gift:create", + permissions: ["gift:create"] + }, + createMenu: { + method: "POST", + operationId: API_OPERATIONS.createMenu, + path: "/v1/system/menus", + permission: "menu:create", + permissions: ["menu:create"] + }, + createPermission: { + method: "POST", + operationId: API_OPERATIONS.createPermission, + path: "/v1/permissions", + permission: "permission:create", + permissions: ["permission:create"] + }, + createRegion: { + method: "POST", + operationId: API_OPERATIONS.createRegion, + path: "/v1/admin/regions", + permission: "region:create", + permissions: ["region:create"] + }, + createResource: { + method: "POST", + operationId: API_OPERATIONS.createResource, + path: "/v1/admin/resources", + permission: "resource:create", + permissions: ["resource:create"] + }, + createResourceGroup: { + method: "POST", + operationId: API_OPERATIONS.createResourceGroup, + path: "/v1/admin/resource-groups", + permission: "resource-group:create", + permissions: ["resource-group:create"] + }, + createRole: { + method: "POST", + operationId: API_OPERATIONS.createRole, + path: "/v1/roles", + permission: "role:create", + permissions: ["role:create","role:manage"] + }, + createUser: { + method: "POST", + operationId: API_OPERATIONS.createUser, + path: "/v1/users", + permission: "user:create", + permissions: ["user:create"] + }, + createUserExportJob: { + method: "POST", + operationId: API_OPERATIONS.createUserExportJob, + path: "/v1/exports/users", + permission: "export:create", + permissions: ["export:create"] + }, + dashboardOverview: { + method: "GET", + operationId: API_OPERATIONS.dashboardOverview, + path: "/v1/dashboard/overview", + permission: "overview:view", + permissions: ["overview:view"] + }, + deleteCountry: { + method: "DELETE", + operationId: API_OPERATIONS.deleteCountry, + path: "/v1/admin/countries/{country_id}", + permission: "country:status", + permissions: ["country:status"] + }, + deleteMenu: { + method: "DELETE", + operationId: API_OPERATIONS.deleteMenu, + path: "/v1/system/menus/{id}", + permission: "menu:delete", + permissions: ["menu:delete"] + }, + deleteNotification: { + method: "DELETE", + operationId: API_OPERATIONS.deleteNotification, + path: "/v1/notifications/{id}", + permission: "notification:delete", + permissions: ["notification:delete"] + }, + deletePermission: { + method: "DELETE", + operationId: API_OPERATIONS.deletePermission, + path: "/v1/permissions/{id}", + permission: "permission:delete", + permissions: ["permission:delete"] + }, + deleteRole: { + method: "DELETE", + operationId: API_OPERATIONS.deleteRole, + path: "/v1/roles/{id}", + permission: "role:delete", + permissions: ["role:delete","role:manage"] + }, + deleteRoom: { + method: "DELETE", + operationId: API_OPERATIONS.deleteRoom, + path: "/v1/admin/rooms/{room_id}", + permission: "room:delete", + permissions: ["room:delete"] + }, + disableCountry: { + method: "POST", + operationId: API_OPERATIONS.disableCountry, + path: "/v1/admin/countries/{country_id}/disable", + permission: "country:status", + permissions: ["country:status"] + }, + disableGift: { + method: "POST", + operationId: API_OPERATIONS.disableGift, + path: "/v1/admin/gifts/{gift_id}/disable", + permission: "gift:status", + permissions: ["gift:status"] + }, + disableRegion: { + method: "DELETE", + operationId: API_OPERATIONS.disableRegion, + path: "/v1/admin/regions/{region_id}", + permission: "region:status", + permissions: ["region:status"] + }, + disableResource: { + method: "POST", + operationId: API_OPERATIONS.disableResource, + path: "/v1/admin/resources/{resource_id}/disable", + permission: "resource:update", + permissions: ["resource:update"] + }, + disableResourceGroup: { + method: "POST", + operationId: API_OPERATIONS.disableResourceGroup, + path: "/v1/admin/resource-groups/{group_id}/disable", + permission: "resource-group:update", + permissions: ["resource-group:update"] + }, + downloadJobArtifact: { + method: "GET", + operationId: API_OPERATIONS.downloadJobArtifact, + path: "/v1/jobs/{id}/artifact", + permission: "job:view", + permissions: ["job:view"] + }, + enableCountry: { + method: "POST", + operationId: API_OPERATIONS.enableCountry, + path: "/v1/admin/countries/{country_id}/enable", + permission: "country:status", + permissions: ["country:status"] + }, + enableGift: { + method: "POST", + operationId: API_OPERATIONS.enableGift, + path: "/v1/admin/gifts/{gift_id}/enable", + permission: "gift:status", + permissions: ["gift:status"] + }, + enableRegion: { + method: "POST", + operationId: API_OPERATIONS.enableRegion, + path: "/v1/admin/regions/{region_id}/enable", + permission: "region:status", + permissions: ["region:status"] + }, + enableResource: { + method: "POST", + operationId: API_OPERATIONS.enableResource, + path: "/v1/admin/resources/{resource_id}/enable", + permission: "resource:update", + permissions: ["resource:update"] + }, + enableResourceGroup: { + method: "POST", + operationId: API_OPERATIONS.enableResourceGroup, + path: "/v1/admin/resource-groups/{group_id}/enable", + permission: "resource-group:update", + permissions: ["resource-group:update"] + }, + exportLoginLogs: { + method: "GET", + operationId: API_OPERATIONS.exportLoginLogs, + path: "/v1/logs/login/export", + permission: "log:export", + permissions: ["log:export"] + }, + exportOperationLogs: { + method: "GET", + operationId: API_OPERATIONS.exportOperationLogs, + path: "/v1/logs/operations/export", + permission: "log:export", + permissions: ["log:export"] + }, + exportUsers: { + method: "GET", + operationId: API_OPERATIONS.exportUsers, + path: "/v1/users/export", + permission: "user:export", + permissions: ["user:export"] + }, + getCountry: { + method: "GET", + operationId: API_OPERATIONS.getCountry, + path: "/v1/admin/countries/{country_id}", + permission: "country:view", + permissions: ["country:view"] + }, + getRegion: { + method: "GET", + operationId: API_OPERATIONS.getRegion, + path: "/v1/admin/regions/{region_id}", + permission: "region:view", + permissions: ["region:view"] + }, + getResource: { + method: "GET", + operationId: API_OPERATIONS.getResource, + path: "/v1/admin/resources/{resource_id}", + permission: "resource:view", + permissions: ["resource:view"] + }, + getResourceGroup: { + method: "GET", + operationId: API_OPERATIONS.getResourceGroup, + path: "/v1/admin/resource-groups/{group_id}", + permission: "resource-group:view", + permissions: ["resource-group:view"] + }, + getRoleDataScopes: { + method: "GET", + operationId: API_OPERATIONS.getRoleDataScopes, + path: "/v1/roles/{id}/data-scopes", + permission: "role:data-scope", + permissions: ["role:data-scope"] + }, + getUser: { + method: "GET", + operationId: API_OPERATIONS.getUser, + path: "/v1/users/{id}", + permission: "user:view", + permissions: ["user:view"] + }, + grantResource: { + method: "POST", + operationId: API_OPERATIONS.grantResource, + path: "/v1/admin/resource-grants/resource", + permission: "resource-grant:create", + permissions: ["resource-grant:create"] + }, + grantResourceGroup: { + method: "POST", + operationId: API_OPERATIONS.grantResourceGroup, + path: "/v1/admin/resource-grants/group", + permission: "resource-grant:create", + permissions: ["resource-grant:create"] + }, + listAgencies: { + method: "GET", + operationId: API_OPERATIONS.listAgencies, + path: "/v1/admin/agencies", + permission: "agency:view", + permissions: ["agency:view"] + }, + listApps: { + method: "GET", + operationId: API_OPERATIONS.listApps, + path: "/v1/admin/apps" + }, + listBDLeaders: { + method: "GET", + operationId: API_OPERATIONS.listBDLeaders, + path: "/v1/admin/bd-leaders", + permission: "bd:view", + permissions: ["bd:view"] + }, + listBDs: { + method: "GET", + operationId: API_OPERATIONS.listBDs, + path: "/v1/admin/bds", + permission: "bd:view", + permissions: ["bd:view"] + }, + listCoinSellers: { + method: "GET", + operationId: API_OPERATIONS.listCoinSellers, + path: "/v1/admin/coin-sellers", + permission: "coin-seller:view", + permissions: ["coin-seller:view"] + }, + listCountries: { + method: "GET", + operationId: API_OPERATIONS.listCountries, + path: "/v1/admin/countries", + permission: "country:view", + permissions: ["country:view"] + }, + listGifts: { + method: "GET", + operationId: API_OPERATIONS.listGifts, + path: "/v1/admin/gifts", + permission: "gift:view", + permissions: ["gift:view"] + }, + listH5Links: { + method: "GET", + operationId: API_OPERATIONS.listH5Links, + path: "/v1/admin/app-config/h5-links", + permission: "app-config:view", + permissions: ["app-config:view"] + }, + listHosts: { + method: "GET", + operationId: API_OPERATIONS.listHosts, + path: "/v1/admin/hosts", + permission: "host:view", + permissions: ["host:view"] + }, + listJobs: { + method: "GET", + operationId: API_OPERATIONS.listJobs, + path: "/v1/jobs", + permission: "job:view", + permissions: ["job:view"] + }, + listLoginLogs: { + method: "GET", + operationId: API_OPERATIONS.listLoginLogs, + path: "/v1/logs/login", + permission: "log:view", + permissions: ["log:view"] + }, + listNotifications: { + method: "GET", + operationId: API_OPERATIONS.listNotifications, + path: "/v1/notifications", + permission: "notification:view", + permissions: ["notification:view"] + }, + listOperationLogs: { + method: "GET", + operationId: API_OPERATIONS.listOperationLogs, + path: "/v1/logs/operations", + permission: "log:view", + permissions: ["log:view"] + }, + listPermissions: { + method: "GET", + operationId: API_OPERATIONS.listPermissions, + path: "/v1/permissions", + permission: "permission:view", + permissions: ["permission:view","role:permission","role:manage"] + }, + listRegions: { + method: "GET", + operationId: API_OPERATIONS.listRegions, + path: "/v1/admin/regions", + permission: "region:view", + permissions: ["region:view"] + }, + listResourceGrants: { + method: "GET", + operationId: API_OPERATIONS.listResourceGrants, + path: "/v1/admin/resource-grants", + permission: "resource-grant:view", + permissions: ["resource-grant:view"] + }, + listResourceGroups: { + method: "GET", + operationId: API_OPERATIONS.listResourceGroups, + path: "/v1/admin/resource-groups", + permission: "resource-group:view", + permissions: ["resource-group:view"] + }, + listResources: { + method: "GET", + operationId: API_OPERATIONS.listResources, + path: "/v1/admin/resources", + permission: "resource:view", + permissions: ["resource:view"] + }, + listRoles: { + method: "GET", + operationId: API_OPERATIONS.listRoles, + path: "/v1/roles", + permission: "role:view", + permissions: ["role:view"] + }, + listRooms: { + method: "GET", + operationId: API_OPERATIONS.listRooms, + path: "/v1/admin/rooms", + permission: "room:view", + permissions: ["room:view"] + }, + listSystemMenus: { + method: "GET", + operationId: API_OPERATIONS.listSystemMenus, + path: "/v1/system/menus", + permission: "menu:view", + permissions: ["menu:view"] + }, + listUsers: { + method: "GET", + operationId: API_OPERATIONS.listUsers, + path: "/v1/users", + permission: "user:view", + permissions: ["user:view"] + }, + login: { + method: "POST", + operationId: API_OPERATIONS.login, + path: "/v1/auth/login" + }, + logout: { + method: "POST", + operationId: API_OPERATIONS.logout, + path: "/v1/auth/logout" + }, + markAllNotificationsRead: { + method: "PATCH", + operationId: API_OPERATIONS.markAllNotificationsRead, + path: "/v1/notifications/read-all", + permission: "notification:read-all", + permissions: ["notification:read-all"] + }, + markNotificationRead: { + method: "PATCH", + operationId: API_OPERATIONS.markNotificationRead, + path: "/v1/notifications/{id}/read", + permission: "notification:read", + permissions: ["notification:read"] + }, + me: { + method: "GET", + operationId: API_OPERATIONS.me, + path: "/v1/auth/me" + }, + navigationMenus: { + method: "GET", + operationId: API_OPERATIONS.navigationMenus, + path: "/v1/navigation/menus" + }, + refresh: { + method: "POST", + operationId: API_OPERATIONS.refresh, + path: "/v1/auth/refresh" + }, + replaceRegionCountries: { + method: "PUT", + operationId: API_OPERATIONS.replaceRegionCountries, + path: "/v1/admin/regions/{region_id}/countries", + permission: "region:update", + permissions: ["region:update"] + }, + replaceRoleDataScopes: { + method: "PUT", + operationId: API_OPERATIONS.replaceRoleDataScopes, + path: "/v1/roles/{id}/data-scopes", + permission: "role:data-scope", + permissions: ["role:data-scope"] + }, + replaceRolePermissions: { + method: "PUT", + operationId: API_OPERATIONS.replaceRolePermissions, + path: "/v1/roles/{id}/permissions", + permission: "role:permission", + permissions: ["role:permission","role:manage"] + }, + resetUserPassword: { + method: "POST", + operationId: API_OPERATIONS.resetUserPassword, + path: "/v1/users/{id}/reset-password", + permission: "user:reset-password", + permissions: ["user:reset-password"] + }, + search: { + method: "GET", + operationId: API_OPERATIONS.search, + path: "/v1/search" + }, + setAgencyJoinEnabled: { + method: "POST", + operationId: API_OPERATIONS.setAgencyJoinEnabled, + path: "/v1/admin/agencies/{agency_id}/join-enabled", + permission: "agency:status", + permissions: ["agency:status"] + }, + setBDLeaderStatus: { + method: "PATCH", + operationId: API_OPERATIONS.setBDLeaderStatus, + path: "/v1/admin/bd-leaders/{user_id}/status", + permission: "bd:update", + permissions: ["bd:update"] + }, + setBDStatus: { + method: "PATCH", + operationId: API_OPERATIONS.setBDStatus, + path: "/v1/admin/bds/{user_id}/status", + permission: "bd:update", + permissions: ["bd:update"] + }, + setCoinSellerStatus: { + method: "PATCH", + operationId: API_OPERATIONS.setCoinSellerStatus, + path: "/v1/admin/coin-sellers/{user_id}/status", + permission: "coin-seller:update", + permissions: ["coin-seller:update"] + }, + sortMenus: { + method: "PUT", + operationId: API_OPERATIONS.sortMenus, + path: "/v1/system/menus/sort", + permission: "menu:sort", + permissions: ["menu:sort"] + }, + syncPermissions: { + method: "POST", + operationId: API_OPERATIONS.syncPermissions, + path: "/v1/permissions/sync", + permission: "permission:sync", + permissions: ["permission:sync"] + }, + updateCountry: { + method: "PATCH", + operationId: API_OPERATIONS.updateCountry, + path: "/v1/admin/countries/{country_id}", + permission: "country:update", + permissions: ["country:update"] + }, + updateGift: { + method: "PUT", + operationId: API_OPERATIONS.updateGift, + path: "/v1/admin/gifts/{gift_id}", + permission: "gift:update", + permissions: ["gift:update"] + }, + updateH5Links: { + method: "PUT", + operationId: API_OPERATIONS.updateH5Links, + path: "/v1/admin/app-config/h5-links", + permission: "app-config:update", + permissions: ["app-config:update"] + }, + updateMenu: { + method: "PATCH", + operationId: API_OPERATIONS.updateMenu, + path: "/v1/system/menus/{id}", + permission: "menu:update", + permissions: ["menu:update"] + }, + updateMenuVisible: { + method: "PATCH", + operationId: API_OPERATIONS.updateMenuVisible, + path: "/v1/system/menus/{id}/visible", + permission: "menu:visible", + permissions: ["menu:visible"] + }, + updatePermission: { + method: "PATCH", + operationId: API_OPERATIONS.updatePermission, + path: "/v1/permissions/{id}", + permission: "permission:update", + permissions: ["permission:update"] + }, + updateRegion: { + method: "PATCH", + operationId: API_OPERATIONS.updateRegion, + path: "/v1/admin/regions/{region_id}", + permission: "region:update", + permissions: ["region:update"] + }, + updateResource: { + method: "PUT", + operationId: API_OPERATIONS.updateResource, + path: "/v1/admin/resources/{resource_id}", + permission: "resource:update", + permissions: ["resource:update"] + }, + updateResourceGroup: { + method: "PUT", + operationId: API_OPERATIONS.updateResourceGroup, + path: "/v1/admin/resource-groups/{group_id}", + permission: "resource-group:update", + permissions: ["resource-group:update"] + }, + updateResourceGroupItems: { + method: "PUT", + operationId: API_OPERATIONS.updateResourceGroupItems, + path: "/v1/admin/resource-groups/{group_id}/items", + permission: "resource-group:update", + permissions: ["resource-group:update"] + }, + updateRole: { + method: "PATCH", + operationId: API_OPERATIONS.updateRole, + path: "/v1/roles/{id}", + permission: "role:update", + permissions: ["role:update","role:manage"] + }, + updateRoom: { + method: "PATCH", + operationId: API_OPERATIONS.updateRoom, + path: "/v1/admin/rooms/{room_id}", + permission: "room:update", + permissions: ["room:update"] + }, + updateUser: { + method: "PATCH", + operationId: API_OPERATIONS.updateUser, + path: "/v1/users/{id}", + permission: "user:update", + permissions: ["user:update"] + }, + updateUserStatus: { + method: "PATCH", + operationId: API_OPERATIONS.updateUserStatus, + path: "/v1/users/{id}/status", + permission: "user:status", + permissions: ["user:status"] + }, + uploadFile: { + method: "POST", + operationId: API_OPERATIONS.uploadFile, + path: "/v1/admin/files/upload", + permission: "upload:create", + permissions: ["upload:create"] + }, + uploadImage: { + method: "POST", + operationId: API_OPERATIONS.uploadImage, + path: "/v1/admin/files/image/upload", + permission: "upload:create", + permissions: ["upload:create"] + } +}; + +export const API_OPERATION_IDS = Object.values(API_OPERATIONS); + +export const API_PERMISSION_CODES = Array.from( + new Set( + Object.values(API_ENDPOINTS) + .flatMap((endpoint) => endpoint.permissions || (endpoint.permission ? [endpoint.permission] : [])) + .filter((permission): permission is PermissionCode => Boolean(permission)) + ) +); + +export type ApiPathParams = Record; + +export function apiEndpointPath(operationId: ApiOperationId, params: ApiPathParams = {}) { + let endpointPath = API_ENDPOINTS[operationId].path; + + for (const [key, value] of Object.entries(params)) { + endpointPath = endpointPath.replace(`{${key}}`, encodeURIComponent(String(value))); + } + + return endpointPath; +} diff --git a/src/shared/api/generated/schema.d.ts b/src/shared/api/generated/schema.d.ts new file mode 100644 index 0000000..d33546d --- /dev/null +++ b/src/shared/api/generated/schema.d.ts @@ -0,0 +1,3257 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/admin/agencies": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listAgencies"]; + put?: never; + post: operations["createAgency"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/agencies/{agency_id}/close": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["closeAgency"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/agencies/{agency_id}/join-enabled": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["setAgencyJoinEnabled"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/app-config/h5-links": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listH5Links"]; + put: operations["updateH5Links"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/apps": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listApps"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/bd-leaders": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listBDLeaders"]; + put?: never; + post: operations["createBDLeader"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/bd-leaders/{user_id}/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch: operations["setBDLeaderStatus"]; + trace?: never; + }; + "/admin/bds": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listBDs"]; + put?: never; + post: operations["createBD"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/bds/{user_id}/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch: operations["setBDStatus"]; + trace?: never; + }; + "/admin/coin-sellers": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listCoinSellers"]; + put?: never; + post: operations["createCoinSeller"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/coin-sellers/{user_id}/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch: operations["setCoinSellerStatus"]; + trace?: never; + }; + "/admin/countries": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listCountries"]; + put?: never; + post: operations["createCountry"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/countries/{country_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getCountry"]; + put?: never; + post?: never; + delete: operations["deleteCountry"]; + options?: never; + head?: never; + patch: operations["updateCountry"]; + trace?: never; + }; + "/admin/countries/{country_id}/disable": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["disableCountry"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/countries/{country_id}/enable": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["enableCountry"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/files/image/upload": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["uploadImage"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/files/upload": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["uploadFile"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/gifts": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listGifts"]; + put?: never; + post: operations["createGift"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/gifts/{gift_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put: operations["updateGift"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/gifts/{gift_id}/disable": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["disableGift"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/gifts/{gift_id}/enable": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["enableGift"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/hosts": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listHosts"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/regions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listRegions"]; + put?: never; + post: operations["createRegion"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/regions/{region_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getRegion"]; + put?: never; + post?: never; + delete: operations["disableRegion"]; + options?: never; + head?: never; + patch: operations["updateRegion"]; + trace?: never; + }; + "/admin/regions/{region_id}/countries": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put: operations["replaceRegionCountries"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/regions/{region_id}/enable": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["enableRegion"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/resource-grants": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listResourceGrants"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/resource-grants/group": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["grantResourceGroup"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/resource-grants/resource": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["grantResource"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/resource-groups": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listResourceGroups"]; + put?: never; + post: operations["createResourceGroup"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/resource-groups/{group_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getResourceGroup"]; + put: operations["updateResourceGroup"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/resource-groups/{group_id}/disable": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["disableResourceGroup"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/resource-groups/{group_id}/enable": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["enableResourceGroup"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/resource-groups/{group_id}/items": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put: operations["updateResourceGroupItems"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/resources": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listResources"]; + put?: never; + post: operations["createResource"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/resources/{resource_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getResource"]; + put: operations["updateResource"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/resources/{resource_id}/disable": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["disableResource"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/resources/{resource_id}/enable": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["enableResource"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/rooms": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listRooms"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/rooms/{room_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete: operations["deleteRoom"]; + options?: never; + head?: never; + patch: operations["updateRoom"]; + trace?: never; + }; + "/app/users": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["appListUsers"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/app/users/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["appGetUser"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch: operations["appUpdateUser"]; + trace?: never; + }; + "/app/users/{id}/ban": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["appBanUser"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/app/users/{id}/password": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["appSetPassword"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/app/users/{id}/unban": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["appUnbanUser"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/change-password": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["changePassword"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/login": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["login"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/logout": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["logout"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/me": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["me"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/refresh": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["refresh"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/dashboard/overview": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["dashboardOverview"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/exports/users": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["createUserExportJob"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/jobs": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listJobs"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/jobs/{id}/artifact": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["downloadJobArtifact"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/jobs/{id}/cancel": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["cancelJob"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/logs/login": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listLoginLogs"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/logs/login/export": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["exportLoginLogs"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/logs/operations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listOperationLogs"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/logs/operations/export": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["exportOperationLogs"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/navigation/menus": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["navigationMenus"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/notifications": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listNotifications"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/notifications/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete: operations["deleteNotification"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/notifications/{id}/read": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch: operations["markNotificationRead"]; + trace?: never; + }; + "/notifications/read-all": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch: operations["markAllNotificationsRead"]; + trace?: never; + }; + "/permissions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listPermissions"]; + put?: never; + post: operations["createPermission"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/permissions/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete: operations["deletePermission"]; + options?: never; + head?: never; + patch: operations["updatePermission"]; + trace?: never; + }; + "/permissions/sync": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["syncPermissions"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/roles": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listRoles"]; + put?: never; + post: operations["createRole"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/roles/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete: operations["deleteRole"]; + options?: never; + head?: never; + patch: operations["updateRole"]; + trace?: never; + }; + "/roles/{id}/data-scopes": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getRoleDataScopes"]; + put: operations["replaceRoleDataScopes"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/roles/{id}/permissions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put: operations["replaceRolePermissions"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/search": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["search"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/system/menus": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listSystemMenus"]; + put?: never; + post: operations["createMenu"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/system/menus/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete: operations["deleteMenu"]; + options?: never; + head?: never; + patch: operations["updateMenu"]; + trace?: never; + }; + "/system/menus/{id}/visible": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch: operations["updateMenuVisible"]; + trace?: never; + }; + "/system/menus/sort": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put: operations["sortMenus"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/users": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listUsers"]; + put?: never; + post: operations["createUser"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/users/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["getUser"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch: operations["updateUser"]; + trace?: never; + }; + "/users/{id}/reset-password": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["resetUserPassword"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/users/{id}/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch: operations["updateUserStatus"]; + trace?: never; + }; + "/users/batch/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["batchUpdateUserStatus"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/users/export": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["exportUsers"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + ApiPageAgency: { + items: components["schemas"]["Agency"][]; + page: number; + pageSize: number; + total: number; + }; + ApiPageBDProfile: { + items: components["schemas"]["BDProfile"][]; + page: number; + pageSize: number; + total: number; + }; + ApiPageHostProfile: { + items: components["schemas"]["HostProfile"][]; + page: number; + pageSize: number; + total: number; + }; + ApiPageLog: { + items: components["schemas"]["Log"][]; + page: number; + pageSize: number; + total: number; + }; + ApiPageUser: { + items: components["schemas"]["AdminUser"][]; + page: number; + pageSize: number; + total: number; + }; + ApiResponseCreateUserResult: components["schemas"]["Envelope"] & { + data?: components["schemas"]["CreateUserResult"]; + }; + ApiResponseAgencyPage: components["schemas"]["Envelope"] & { + data?: components["schemas"]["ApiPageAgency"]; + }; + ApiResponseBDProfilePage: components["schemas"]["Envelope"] & { + data?: components["schemas"]["ApiPageBDProfile"]; + }; + ApiResponseDashboardOverview: components["schemas"]["Envelope"] & { + data?: components["schemas"]["DashboardOverview"]; + }; + ApiResponseEmpty: components["schemas"]["Envelope"]; + ApiResponseHostProfilePage: components["schemas"]["Envelope"] & { + data?: components["schemas"]["ApiPageHostProfile"]; + }; + ApiResponseLogPage: components["schemas"]["Envelope"] & { + data?: components["schemas"]["ApiPageLog"]; + }; + ApiResponseMenu: components["schemas"]["Envelope"] & { + data?: components["schemas"]["Menu"]; + }; + ApiResponseMenuList: components["schemas"]["Envelope"] & { + data?: components["schemas"]["Menu"][]; + }; + ApiResponseNotification: components["schemas"]["Envelope"] & { + data?: components["schemas"]["Notification"]; + }; + ApiResponseNotificationList: components["schemas"]["Envelope"] & { + data?: components["schemas"]["NotificationList"]; + }; + ApiResponsePermission: components["schemas"]["Envelope"] & { + data?: components["schemas"]["Permission"]; + }; + ApiResponsePermissionList: components["schemas"]["Envelope"] & { + data?: components["schemas"]["Permission"][]; + }; + ApiResponseResetPasswordResult: components["schemas"]["Envelope"] & { + data?: components["schemas"]["ResetPasswordResult"]; + }; + ApiResponseRole: components["schemas"]["Envelope"] & { + data?: components["schemas"]["Role"]; + }; + ApiResponseRoleList: components["schemas"]["Envelope"] & { + data?: components["schemas"]["Role"][]; + }; + ApiResponseSearchList: components["schemas"]["Envelope"] & { + data?: components["schemas"]["SearchResult"][]; + }; + ApiResponseSession: components["schemas"]["Envelope"] & { + data?: components["schemas"]["Session"]; + }; + ApiResponseUser: components["schemas"]["Envelope"] & { + data?: components["schemas"]["AdminUser"]; + }; + ApiResponseUserPage: components["schemas"]["Envelope"] & { + data?: components["schemas"]["ApiPageUser"]; + }; + AdminUser: { + account: string; + id: number; + lastLogin?: string; + mfa?: string; + mfaEnabled?: boolean; + name: string; + role?: string; + roleIds?: number[]; + status: string; + team?: string; + }; + Agency: { + agencyId: number; + createdAtMs?: number; + createdByUserId?: number; + joinEnabled?: boolean; + maxHosts?: number; + name?: string; + ownerUserId?: number; + parentBdUserId?: number; + regionId?: number; + status?: string; + updatedAtMs?: number; + }; + BDProfile: { + createdAtMs?: number; + createdByUserId?: number; + parentLeaderUserId?: number; + regionId?: number; + role?: string; + status?: string; + updatedAtMs?: number; + userId: number; + }; + CreateUserResult: { + initialPassword?: string; + }; + DashboardOverview: { + [key: string]: unknown; + }; + Envelope: { + code?: number; + data?: unknown; + message?: string; + }; + HostProfile: { + createdAtMs?: number; + currentAgencyId?: number; + currentMembershipId?: number; + firstBecameHostAtMs?: number; + regionId?: number; + source?: string; + status?: string; + updatedAtMs?: number; + userId: number; + }; + Log: { + [key: string]: unknown; + }; + Menu: { + [key: string]: unknown; + }; + MenuInput: { + [key: string]: unknown; + }; + Notification: { + [key: string]: unknown; + }; + NotificationList: { + items: components["schemas"]["Notification"][]; + unread: number; + }; + Permission: { + [key: string]: unknown; + }; + PermissionInput: { + [key: string]: unknown; + }; + ResetPasswordResult: { + initialPassword?: string; + }; + Role: { + [key: string]: unknown; + }; + RoleInput: { + [key: string]: unknown; + }; + SearchResult: { + [key: string]: unknown; + }; + Session: { + [key: string]: unknown; + }; + UserInput: { + [key: string]: unknown; + }; + }; + responses: { + /** @description OK */ + AgencyPageResponse: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponseAgencyPage"]; + }; + }; + /** @description OK */ + BDProfilePageResponse: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponseBDProfilePage"]; + }; + }; + /** @description OK */ + CreateUserResponse: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponseCreateUserResult"]; + }; + }; + /** @description OK */ + DashboardOverviewResponse: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponseDashboardOverview"]; + }; + }; + /** @description OK */ + EmptyResponse: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponseEmpty"]; + }; + }; + /** @description OK */ + HostProfilePageResponse: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponseHostProfilePage"]; + }; + }; + /** @description OK */ + LogPageResponse: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponseLogPage"]; + }; + }; + /** @description OK */ + MenuListResponse: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponseMenuList"]; + }; + }; + /** @description OK */ + MenuResponse: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponseMenu"]; + }; + }; + /** @description OK */ + NotificationListResponse: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponseNotificationList"]; + }; + }; + /** @description OK */ + NotificationResponse: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponseNotification"]; + }; + }; + /** @description OK */ + PermissionListResponse: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponsePermissionList"]; + }; + }; + /** @description OK */ + PermissionResponse: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponsePermission"]; + }; + }; + /** @description OK */ + ResetPasswordResponse: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponseResetPasswordResult"]; + }; + }; + /** @description OK */ + RoleListResponse: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponseRoleList"]; + }; + }; + /** @description OK */ + RoleResponse: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponseRole"]; + }; + }; + /** @description OK */ + SearchResponse: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponseSearchList"]; + }; + }; + /** @description OK */ + SessionResponse: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponseSession"]; + }; + }; + /** @description OK */ + UserPageResponse: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponseUserPage"]; + }; + }; + /** @description OK */ + UserResponse: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponseUser"]; + }; + }; + }; + parameters: { + AgencyId: number; + Id: number; + Keyword: string; + Page: number; + PageSize: number; + ParentBdUserId: number; + ParentLeaderUserId: number; + RegionId: number; + Status: string; + }; + requestBodies: { + BatchStatusRequest: { + content: { + "application/json": { + ids: number[]; + status: string; + }; + }; + }; + ChangePasswordRequest: { + content: { + "application/json": { + oldPassword: string; + newPassword: string; + }; + }; + }; + LoginRequest: { + content: { + "application/json": { + username: string; + password: string; + }; + }; + }; + MenuRequest: { + content: { + "application/json": components["schemas"]["MenuInput"]; + }; + }; + MenuSortRequest: { + content: { + "application/json": { + items: { + id: number; + sort: number; + }[]; + }; + }; + }; + MenuVisibleRequest: { + content: { + "application/json": { + visible: boolean; + }; + }; + }; + PermissionRequest: { + content: { + "application/json": components["schemas"]["PermissionInput"]; + }; + }; + RolePermissionRequest: { + content: { + "application/json": { + permissionIds: number[]; + }; + }; + }; + RoleRequest: { + content: { + "application/json": components["schemas"]["RoleInput"]; + }; + }; + StatusRequest: { + content: { + "application/json": { + status: string; + }; + }; + }; + UserRequest: { + content: { + "application/json": components["schemas"]["UserInput"]; + }; + }; + }; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + listAgencies: { + parameters: { + query?: { + page?: components["parameters"]["Page"]; + page_size?: components["parameters"]["PageSize"]; + keyword?: components["parameters"]["Keyword"]; + status?: components["parameters"]["Status"]; + region_id?: components["parameters"]["RegionId"]; + parent_bd_user_id?: components["parameters"]["ParentBdUserId"]; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["AgencyPageResponse"]; + }; + }; + createAgency: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + closeAgency: { + parameters: { + query?: never; + header?: never; + path: { + agency_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + setAgencyJoinEnabled: { + parameters: { + query?: never; + header?: never; + path: { + agency_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + listH5Links: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + updateH5Links: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + listApps: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + listBDLeaders: { + parameters: { + query?: { + page?: components["parameters"]["Page"]; + page_size?: components["parameters"]["PageSize"]; + keyword?: components["parameters"]["Keyword"]; + status?: components["parameters"]["Status"]; + region_id?: components["parameters"]["RegionId"]; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["BDProfilePageResponse"]; + }; + }; + createBDLeader: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + setBDLeaderStatus: { + parameters: { + query?: never; + header?: never; + path: { + user_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + listBDs: { + parameters: { + query?: { + page?: components["parameters"]["Page"]; + page_size?: components["parameters"]["PageSize"]; + keyword?: components["parameters"]["Keyword"]; + status?: components["parameters"]["Status"]; + region_id?: components["parameters"]["RegionId"]; + parent_leader_user_id?: components["parameters"]["ParentLeaderUserId"]; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["BDProfilePageResponse"]; + }; + }; + createBD: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + setBDStatus: { + parameters: { + query?: never; + header?: never; + path: { + user_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + listCoinSellers: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + createCoinSeller: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + setCoinSellerStatus: { + parameters: { + query?: never; + header?: never; + path: { + user_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + listCountries: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + createCountry: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + getCountry: { + parameters: { + query?: never; + header?: never; + path: { + country_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + deleteCountry: { + parameters: { + query?: never; + header?: never; + path: { + country_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + updateCountry: { + parameters: { + query?: never; + header?: never; + path: { + country_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + disableCountry: { + parameters: { + query?: never; + header?: never; + path: { + country_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + enableCountry: { + parameters: { + query?: never; + header?: never; + path: { + country_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + uploadImage: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + uploadFile: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + listGifts: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + createGift: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + updateGift: { + parameters: { + query?: never; + header?: never; + path: { + gift_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + disableGift: { + parameters: { + query?: never; + header?: never; + path: { + gift_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + enableGift: { + parameters: { + query?: never; + header?: never; + path: { + gift_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + listHosts: { + parameters: { + query?: { + page?: components["parameters"]["Page"]; + page_size?: components["parameters"]["PageSize"]; + keyword?: components["parameters"]["Keyword"]; + status?: components["parameters"]["Status"]; + region_id?: components["parameters"]["RegionId"]; + agency_id?: components["parameters"]["AgencyId"]; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["HostProfilePageResponse"]; + }; + }; + listRegions: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + createRegion: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + getRegion: { + parameters: { + query?: never; + header?: never; + path: { + region_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + disableRegion: { + parameters: { + query?: never; + header?: never; + path: { + region_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + updateRegion: { + parameters: { + query?: never; + header?: never; + path: { + region_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + replaceRegionCountries: { + parameters: { + query?: never; + header?: never; + path: { + region_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + enableRegion: { + parameters: { + query?: never; + header?: never; + path: { + region_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + listResourceGrants: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + grantResourceGroup: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + grantResource: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + listResourceGroups: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + createResourceGroup: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + getResourceGroup: { + parameters: { + query?: never; + header?: never; + path: { + group_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + updateResourceGroup: { + parameters: { + query?: never; + header?: never; + path: { + group_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + disableResourceGroup: { + parameters: { + query?: never; + header?: never; + path: { + group_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + enableResourceGroup: { + parameters: { + query?: never; + header?: never; + path: { + group_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + updateResourceGroupItems: { + parameters: { + query?: never; + header?: never; + path: { + group_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + listResources: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + createResource: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + getResource: { + parameters: { + query?: never; + header?: never; + path: { + resource_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + updateResource: { + parameters: { + query?: never; + header?: never; + path: { + resource_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + disableResource: { + parameters: { + query?: never; + header?: never; + path: { + resource_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + enableResource: { + parameters: { + query?: never; + header?: never; + path: { + resource_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + listRooms: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + deleteRoom: { + parameters: { + query?: never; + header?: never; + path: { + room_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + updateRoom: { + parameters: { + query?: never; + header?: never; + path: { + room_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + appListUsers: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + appGetUser: { + parameters: { + query?: never; + header?: never; + path: { + id: components["parameters"]["Id"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + appUpdateUser: { + parameters: { + query?: never; + header?: never; + path: { + id: components["parameters"]["Id"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + appBanUser: { + parameters: { + query?: never; + header?: never; + path: { + id: components["parameters"]["Id"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + appSetPassword: { + parameters: { + query?: never; + header?: never; + path: { + id: components["parameters"]["Id"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + appUnbanUser: { + parameters: { + query?: never; + header?: never; + path: { + id: components["parameters"]["Id"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + changePassword: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: components["requestBodies"]["ChangePasswordRequest"]; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + login: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: components["requestBodies"]["LoginRequest"]; + responses: { + 200: components["responses"]["SessionResponse"]; + }; + }; + logout: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + me: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["SessionResponse"]; + }; + }; + refresh: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["SessionResponse"]; + }; + }; + dashboardOverview: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["DashboardOverviewResponse"]; + }; + }; + createUserExportJob: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + listJobs: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + downloadJobArtifact: { + parameters: { + query?: never; + header?: never; + path: { + id: components["parameters"]["Id"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + cancelJob: { + parameters: { + query?: never; + header?: never; + path: { + id: components["parameters"]["Id"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + listLoginLogs: { + parameters: { + query?: { + page?: components["parameters"]["Page"]; + page_size?: components["parameters"]["PageSize"]; + keyword?: components["parameters"]["Keyword"]; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["LogPageResponse"]; + }; + }; + exportLoginLogs: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description CSV */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + listOperationLogs: { + parameters: { + query?: { + page?: components["parameters"]["Page"]; + page_size?: components["parameters"]["PageSize"]; + keyword?: components["parameters"]["Keyword"]; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["LogPageResponse"]; + }; + }; + exportOperationLogs: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description CSV */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + navigationMenus: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["MenuListResponse"]; + }; + }; + listNotifications: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["NotificationListResponse"]; + }; + }; + deleteNotification: { + parameters: { + query?: never; + header?: never; + path: { + id: components["parameters"]["Id"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + markNotificationRead: { + parameters: { + query?: never; + header?: never; + path: { + id: components["parameters"]["Id"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["NotificationResponse"]; + }; + }; + markAllNotificationsRead: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["NotificationListResponse"]; + }; + }; + listPermissions: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["PermissionListResponse"]; + }; + }; + createPermission: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: components["requestBodies"]["PermissionRequest"]; + responses: { + 200: components["responses"]["PermissionResponse"]; + }; + }; + deletePermission: { + parameters: { + query?: never; + header?: never; + path: { + id: components["parameters"]["Id"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + updatePermission: { + parameters: { + query?: never; + header?: never; + path: { + id: components["parameters"]["Id"]; + }; + cookie?: never; + }; + requestBody?: components["requestBodies"]["PermissionRequest"]; + responses: { + 200: components["responses"]["PermissionResponse"]; + }; + }; + syncPermissions: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["PermissionListResponse"]; + }; + }; + listRoles: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["RoleListResponse"]; + }; + }; + createRole: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: components["requestBodies"]["RoleRequest"]; + responses: { + 200: components["responses"]["RoleResponse"]; + }; + }; + deleteRole: { + parameters: { + query?: never; + header?: never; + path: { + id: components["parameters"]["Id"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + updateRole: { + parameters: { + query?: never; + header?: never; + path: { + id: components["parameters"]["Id"]; + }; + cookie?: never; + }; + requestBody?: components["requestBodies"]["RoleRequest"]; + responses: { + 200: components["responses"]["RoleResponse"]; + }; + }; + getRoleDataScopes: { + parameters: { + query?: never; + header?: never; + path: { + id: components["parameters"]["Id"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + replaceRoleDataScopes: { + parameters: { + query?: never; + header?: never; + path: { + id: components["parameters"]["Id"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + replaceRolePermissions: { + parameters: { + query?: never; + header?: never; + path: { + id: components["parameters"]["Id"]; + }; + cookie?: never; + }; + requestBody?: components["requestBodies"]["RolePermissionRequest"]; + responses: { + 200: components["responses"]["RoleResponse"]; + }; + }; + search: { + parameters: { + query?: { + keyword?: components["parameters"]["Keyword"]; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["SearchResponse"]; + }; + }; + listSystemMenus: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["MenuListResponse"]; + }; + }; + createMenu: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: components["requestBodies"]["MenuRequest"]; + responses: { + 200: components["responses"]["MenuResponse"]; + }; + }; + deleteMenu: { + parameters: { + query?: never; + header?: never; + path: { + id: components["parameters"]["Id"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + updateMenu: { + parameters: { + query?: never; + header?: never; + path: { + id: components["parameters"]["Id"]; + }; + cookie?: never; + }; + requestBody?: components["requestBodies"]["MenuRequest"]; + responses: { + 200: components["responses"]["MenuResponse"]; + }; + }; + updateMenuVisible: { + parameters: { + query?: never; + header?: never; + path: { + id: components["parameters"]["Id"]; + }; + cookie?: never; + }; + requestBody?: components["requestBodies"]["MenuVisibleRequest"]; + responses: { + 200: components["responses"]["MenuResponse"]; + }; + }; + sortMenus: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: components["requestBodies"]["MenuSortRequest"]; + responses: { + 200: components["responses"]["MenuListResponse"]; + }; + }; + listUsers: { + parameters: { + query?: { + page?: components["parameters"]["Page"]; + page_size?: components["parameters"]["PageSize"]; + keyword?: components["parameters"]["Keyword"]; + role?: string; + status?: components["parameters"]["Status"]; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["UserPageResponse"]; + }; + }; + createUser: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: components["requestBodies"]["UserRequest"]; + responses: { + 200: components["responses"]["CreateUserResponse"]; + }; + }; + getUser: { + parameters: { + query?: never; + header?: never; + path: { + id: components["parameters"]["Id"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["UserResponse"]; + }; + }; + updateUser: { + parameters: { + query?: never; + header?: never; + path: { + id: components["parameters"]["Id"]; + }; + cookie?: never; + }; + requestBody?: components["requestBodies"]["UserRequest"]; + responses: { + 200: components["responses"]["UserResponse"]; + }; + }; + resetUserPassword: { + parameters: { + query?: never; + header?: never; + path: { + id: components["parameters"]["Id"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["ResetPasswordResponse"]; + }; + }; + updateUserStatus: { + parameters: { + query?: never; + header?: never; + path: { + id: components["parameters"]["Id"]; + }; + cookie?: never; + }; + requestBody?: components["requestBodies"]["StatusRequest"]; + responses: { + 200: components["responses"]["UserResponse"]; + }; + }; + batchUpdateUserStatus: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: components["requestBodies"]["BatchStatusRequest"]; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + exportUsers: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description CSV */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; +} diff --git a/src/shared/api/query.ts b/src/shared/api/query.ts new file mode 100644 index 0000000..6c4e229 --- /dev/null +++ b/src/shared/api/query.ts @@ -0,0 +1,15 @@ +import type { QueryParams } from "./request"; + +export function cleanQuery(query: QueryParams = {}) { + return Object.fromEntries( + Object.entries(query).filter(([, value]) => value !== undefined && value !== null && value !== "") + ); +} + +export function toPageQuery({ page, pageSize, ...query }: QueryParams & { page: number; pageSize: number }) { + return cleanQuery({ + ...query, + page, + page_size: pageSize + }); +} diff --git a/src/shared/api/regions.ts b/src/shared/api/regions.ts new file mode 100644 index 0000000..907ee17 --- /dev/null +++ b/src/shared/api/regions.ts @@ -0,0 +1,11 @@ +import { apiRequest } from "@/shared/api/request"; +import { API_ENDPOINTS, API_OPERATIONS, apiEndpointPath } from "@/shared/api/generated/endpoints"; +import type { ApiList, PageQuery, RegionDto } from "@/shared/api/types"; + +export function listRegions(query: PageQuery = {}): Promise> { + const endpoint = API_ENDPOINTS.listRegions; + return apiRequest>(apiEndpointPath(API_OPERATIONS.listRegions), { + method: endpoint.method, + query + }); +} diff --git a/src/shared/api/request.test.ts b/src/shared/api/request.test.ts new file mode 100644 index 0000000..e4b74ee --- /dev/null +++ b/src/shared/api/request.test.ts @@ -0,0 +1,32 @@ +import { afterEach, expect, test, vi } from "vitest"; +import { apiRequest, setAccessToken, setRefreshHandler, setUnauthorizedHandler } from "./request"; + +afterEach(() => { + setAccessToken(""); + setRefreshHandler(null); + setUnauthorizedHandler(null); + vi.unstubAllGlobals(); +}); + +test("retries once after refreshing a stale permission token on 403", async () => { + setAccessToken("old-token"); + setRefreshHandler(async () => { + setAccessToken("new-token"); + return true; + }); + vi.stubGlobal( + "fetch", + vi.fn(async () => { + if (vi.mocked(fetch).mock.calls.length === 1) { + return new Response(JSON.stringify({ code: 403, message: "没有操作权限" }), { status: 403 }); + } + return new Response(JSON.stringify({ code: 0, data: { ok: true } })); + }) + ); + + await expect(apiRequest("/v1/admin/countries", { body: {}, method: "POST" })).resolves.toEqual({ ok: true }); + + expect(fetch).toHaveBeenCalledTimes(2); + expect(vi.mocked(fetch).mock.calls[0][1]?.headers).toMatchObject({ Authorization: "Bearer old-token" }); + expect(vi.mocked(fetch).mock.calls[1][1]?.headers).toMatchObject({ Authorization: "Bearer new-token" }); +}); diff --git a/src/shared/api/request.ts b/src/shared/api/request.ts new file mode 100644 index 0000000..187f98e --- /dev/null +++ b/src/shared/api/request.ts @@ -0,0 +1,170 @@ +import { API_BASE_URL } from "@/shared/config/env.js"; + +const TOKEN_KEY = "hyapp-admin.access-token"; +const APP_CODE_KEY = "hyapp-admin.app-code"; +const APP_CODE_HEADER = "X-App-Code"; + +let accessToken = window.localStorage.getItem(TOKEN_KEY) || ""; +let selectedAppCode = normalizeAppCode(window.localStorage.getItem(APP_CODE_KEY) || "lalu"); +let refreshHandler: (() => Promise) | null = null; +let unauthorizedHandler: (() => void) | null = null; + +export type QueryValue = string | number | boolean | null | undefined; +export type QueryParams = Record; +export type HttpMethod = "DELETE" | "GET" | "PATCH" | "POST" | "PUT"; + +export interface ApiRequestOptions { + body?: TBody; + headers?: Record; + method?: HttpMethod; + query?: QueryParams; + raw?: boolean; + retry?: boolean; + skipAuth?: boolean; +} + +interface ApiEnvelope { + code?: number; + data?: TData; + message?: string; +} + +export function getAccessToken() { + return accessToken; +} + +export function setAccessToken(token: string) { + accessToken = token || ""; + if (accessToken) { + window.localStorage.setItem(TOKEN_KEY, accessToken); + } else { + window.localStorage.removeItem(TOKEN_KEY); + } +} + +export function getSelectedAppCode() { + return selectedAppCode; +} + +export function setSelectedAppCode(appCode: string) { + selectedAppCode = normalizeAppCode(appCode); + if (selectedAppCode) { + window.localStorage.setItem(APP_CODE_KEY, selectedAppCode); + } else { + window.localStorage.removeItem(APP_CODE_KEY); + } +} + +export function setRefreshHandler(handler: (() => Promise) | null) { + refreshHandler = handler; +} + +export function setUnauthorizedHandler(handler: (() => void) | null) { + unauthorizedHandler = handler; +} + +export async function apiRequest( + path: string, + options: ApiRequestOptions & { raw: true } +): Promise; +export async function apiRequest( + path: string, + options?: ApiRequestOptions & { raw?: false } +): Promise; +export async function apiRequest( + path: string, + options?: ApiRequestOptions +): Promise; +export async function apiRequest( + path: string, + options: ApiRequestOptions = {} +): Promise { + const { + body, + headers = {}, + method = body ? "POST" : "GET", + query, + retry = true, + raw = false, + skipAuth = false + } = options; + const url = buildURL(path, query); + const requestHeaders = { ...headers }; + + if (body !== undefined && !(body instanceof FormData)) { + requestHeaders["Content-Type"] = "application/json"; + } + if (accessToken && !skipAuth) { + requestHeaders.Authorization = `Bearer ${accessToken}`; + } + if (selectedAppCode && !requestHeaders[APP_CODE_HEADER]) { + requestHeaders[APP_CODE_HEADER] = selectedAppCode; + } + + const response = await fetch(url, { + method, + headers: requestHeaders, + body: body === undefined ? undefined : body instanceof FormData ? body : JSON.stringify(body), + credentials: "include" + }); + + if ((response.status === 401 || response.status === 403) && retry && refreshHandler && !skipAuth) { + const refreshed = await refreshHandler(); + if (refreshed) { + return apiRequest(path, { ...options, retry: false }); + } + } + + if (response.status === 401 && unauthorizedHandler && !skipAuth) { + unauthorizedHandler(); + } + + if (raw) { + if (!response.ok) { + throw new Error(response.statusText || "请求失败"); + } + return response; + } + + const payload = await readJSON(response); + if (!response.ok || payload.code !== 0) { + throw new Error(resolveErrorMessage(response, payload.message)); + } + + return payload.data as TData; +} + +function resolveErrorMessage(response: Response, message?: string) { + if (response.status === 404 && (!message || message.includes("404 page not found"))) { + return "接口不存在或后端服务未更新"; + } + return message || response.statusText || "请求失败"; +} + +function buildURL(path: string, query?: QueryParams) { + const url = new URL(`${API_BASE_URL}${path}`, window.location.origin); + if (query) { + Object.entries(query).forEach(([key, value]) => { + if (value !== undefined && value !== null && value !== "") { + url.searchParams.set(key, String(value)); + } + }); + } + return url.toString(); +} + +async function readJSON(response: Response): Promise> { + const text = await response.text(); + if (!text) { + return {}; + } + try { + return JSON.parse(text) as ApiEnvelope; + } catch { + return { code: response.ok ? 0 : response.status, message: text, data: text as TData }; + } +} + +function normalizeAppCode(value: string) { + return value.trim().toLowerCase(); +} diff --git a/src/shared/api/types.ts b/src/shared/api/types.ts new file mode 100644 index 0000000..f2eccf4 --- /dev/null +++ b/src/shared/api/types.ts @@ -0,0 +1,470 @@ +export interface ApiPage { + items: TItem[]; + page: number; + pageSize: number; + total: number; +} + +export interface ApiList { + items: TItem[]; + total: number; +} + +export type EntityId = number | string; + +export interface PageQuery { + [key: string]: boolean | number | string | null | undefined; + agency_id?: EntityId; + agencyId?: EntityId; + enabled?: boolean; + keyword?: string; + page?: number; + parent_bd_user_id?: EntityId; + parent_leader_user_id?: EntityId; + parentBdUserId?: EntityId; + parentLeaderUserId?: EntityId; + page_size?: number; + pageSize?: number; + region_id?: EntityId; + regionId?: EntityId; + role?: string; + status?: string; +} + +export interface IdName { + id: number; + name: string; +} + +export type StatusKind = "active" | "disabled" | "error" | "locked" | "running" | "stopped" | "warning"; +export type UserStatus = "active" | "disabled" | "locked"; + +export interface AdminUserDto { + account: string; + id: EntityId; + lastLogin?: string; + mfa?: string; + mfaEnabled?: boolean; + name: string; + role?: string; + roleIds?: number[]; + status: UserStatus; + team?: string; +} + +export interface UserFormPayload { + mfaEnabled?: boolean; + name: string; + password?: string; + roleIds: number[]; + status: UserStatus; + team?: string; + username?: string; +} + +export type UserUpdatePayload = Omit; + +export interface ResetPasswordResultDto { + initialPassword?: string; +} + +export interface CreateUserResultDto { + initialPassword?: string; +} + +export interface RoleDto { + code: string; + description?: string; + id: number; + name: string; + permissions?: PermissionDto[]; +} + +export interface RolePayload { + code: string; + description?: string; + name: string; + permissionIds?: number[]; +} + +export interface PermissionDto { + code: string; + description?: string; + id: number; + kind: "api" | "button" | "menu"; + name: string; +} + +export interface PermissionPayload { + code: string; + description?: string; + kind: PermissionDto["kind"]; + name: string; +} + +export interface MenuDto { + children?: MenuDto[]; + code: string; + icon?: string; + id: number; + label: string; + parentId?: number | null; + path?: string; + permissionCode?: string; + sort?: number; + visible?: boolean; +} + +export interface MenuPayload { + code: string; + icon?: string; + label: string; + parentId?: number | null; + path?: string; + permissionCode?: string; + sort?: number; + visible?: boolean; +} + +export interface MenuSortItem { + id: number; + sort?: number; +} + +export interface DashboardOverviewDto { + alerts?: Array<{ desc: string; name: string; time: string; type: string }>; + activeUsers: number; + disabledUsers: number; + lockedUsers: number; + logsToday: number; + menuTotal: number; + rolesTotal: number; + series?: { + notifications?: number[]; + operations?: number[]; + users?: number[]; + }; + unreadNotifications: number; + updatedAt?: string; + usersTotal: number; +} + +export interface LogDto { + action?: string; + createdAt?: string; + detail?: string; + id: number; + ip?: string; + message?: string; + resource?: string; + status: string; + username?: string; +} + +export interface NotificationDto { + content: string; + createdAt?: string; + id: number; + level: string; + readAt?: string | null; + title: string; +} + +export interface NotificationListDto { + items: NotificationDto[]; + unread: number; +} + +export interface SessionDto { + accessToken?: string; + permissions?: string[]; + user?: { + account?: string; + id?: number; + name?: string; + }; +} + +export interface LoginPayload { + password: string; + username: string; +} + +export interface ChangePasswordPayload { + newPassword: string; + oldPassword: string; +} + +export interface BDProfileDto { + createdAtMs?: number; + createdByUserId?: number; + parentLeaderUserId?: number; + regionId?: number; + role?: string; + status?: string; + updatedAtMs?: number; + userId: number; +} + +export interface AgencyDto { + agencyId: number; + createdAtMs?: number; + createdByUserId?: number; + joinEnabled?: boolean; + maxHosts?: number; + name?: string; + ownerUserId?: number; + parentBdUserId?: number; + regionId?: number; + status?: string; + updatedAtMs?: number; +} + +export interface HostProfileDto { + createdAtMs?: number; + currentAgencyId?: number; + currentMembershipId?: number; + firstBecameHostAtMs?: number; + regionId?: number; + source?: string; + status?: string; + updatedAtMs?: number; + userId: number; +} + +export interface CoinSellerDto { + avatar?: string; + createdAtMs?: number; + createdByUserId?: number; + displayUserId?: string; + merchantAssetType?: string; + merchantBalance?: number; + regionId?: number; + regionName?: string; + status?: string; + updatedAtMs?: number; + userId: number; + username?: string; +} + +export interface AppUserDto { + avatar?: string; + coin?: number; + country?: string; + countryDisplayName?: string; + countryName?: string; + createdAtMs?: number; + diamond?: number; + displayUserId?: string; + gender?: string; + lastActiveAtMs?: number; + regionId?: number; + regionName?: string; + status?: string; + updatedAtMs?: number; + userId: string; + username?: string; +} + +export interface AppUserUpdatePayload { + avatar?: string; + country?: string; + gender?: string; + username?: string; +} + +export interface AppUserPasswordPayload { + password: string; +} + +export interface RoomDto { + coverUrl?: string; + createdAtMs?: number; + heat?: number; + hostUserId?: string; + mode?: string; + occupiedSeatCount?: number; + onlineCount?: number; + owner?: RoomOwnerDto; + ownerUserId?: string; + regionName?: string; + roomId: string; + seatCount?: number; + sortScore?: number; + status?: string; + title?: string; + updatedAtMs?: number; + visibleRegionId?: number; +} + +export interface RoomOwnerDto { + avatar?: string; + displayUserId?: string; + userId?: string; + username?: string; +} + +export interface RoomUpdatePayload { + coverUrl?: string; + description?: string; + mode?: string; + status?: string; + title?: string; + visibleRegionId?: number; +} + +export interface UploadResultDto { + content_type?: string; + contentType?: string; + object_key?: string; + objectKey?: string; + size_bytes?: number; + sizeBytes?: number; + url: string; +} + +export interface AdminAppDto { + appCode: string; + appId: number; + appName: string; + createdAtMs?: number; + packageName?: string; + platform?: string; + status?: string; + updatedAtMs?: number; +} + +export interface H5LinkConfigDto { + key: string; + label: string; + updatedAtMs?: number; + url?: string; +} + +export interface H5LinkConfigPayload { + key: string; + url?: string; +} + +export interface H5LinkConfigUpdatePayload { + items: H5LinkConfigPayload[]; +} + +export interface AgencyMembershipDto { + agencyId?: number; + createdAtMs?: number; + endedAtMs?: number; + endedByUserId?: number; + endedReason?: string; + hostUserId?: number; + joinedAtMs?: number; + membershipId: number; + membershipType?: string; + regionId?: number; + status?: string; + updatedAtMs?: number; +} + +export interface CreateAgencyResultDto { + agency?: AgencyDto; + hostProfile?: HostProfileDto; + membership?: AgencyMembershipDto; +} + +export interface HostCommandPayload { + commandId: string; + reason?: string; +} + +export interface CreateBDLeaderPayload extends HostCommandPayload { + regionId: number; + targetUserId: number; +} + +export interface CreateBDPayload extends HostCommandPayload { + parentLeaderUserId: number; + targetUserId: number; +} + +export interface BDStatusPayload extends HostCommandPayload { + status: string; +} + +export interface CreateCoinSellerPayload extends HostCommandPayload { + targetUserId: number; +} + +export type CoinSellerStatusPayload = BDStatusPayload; + +export interface CreateAgencyPayload extends HostCommandPayload { + joinEnabled: boolean; + maxHosts?: number; + name: string; + ownerUserId: number; + parentBdUserId: number; +} + +export interface AgencyJoinEnabledPayload extends HostCommandPayload { + joinEnabled: boolean; +} + +export interface CountryDto { + countryCode: string; + countryDisplayName?: string; + countryId: number; + countryName?: string; + createdAtMs?: number; + enabled?: boolean; + flag?: string; + isoAlpha3?: string; + isoNumeric?: string; + phoneCountryCode?: string; + sortOrder?: number; + updatedAtMs?: number; +} + +export interface CountryPayload { + countryCode?: string; + countryDisplayName: string; + countryName: string; + enabled?: boolean; + flag?: string; + isoAlpha3?: string; + isoNumeric?: string; + phoneCountryCode?: string; + sortOrder?: number; +} + +export type CountryUpdatePayload = Omit; + +export interface RegionDto { + countries?: string[]; + createdAtMs?: number; + name: string; + regionCode: string; + regionId: number; + sortOrder?: number; + status?: string; + updatedAtMs?: number; +} + +export interface RegionPayload { + countries?: string[]; + name: string; + regionCode: string; + sortOrder?: number; +} + +export type RegionUpdatePayload = Omit; + +export interface RegionCountriesPayload { + countries: string[]; +} + +export interface SearchResultDto { + id: EntityId; + subtitle?: string; + target?: string; + title: string; + type: string; +} diff --git a/src/shared/api/upload.test.ts b/src/shared/api/upload.test.ts new file mode 100644 index 0000000..c0b70cc --- /dev/null +++ b/src/shared/api/upload.test.ts @@ -0,0 +1,39 @@ +import { afterEach, expect, test, vi } from "vitest"; +import { setAccessToken } from "@/shared/api/request"; +import { uploadFile, uploadImage } from "@/shared/api/upload"; + +afterEach(() => { + setAccessToken(""); + vi.unstubAllGlobals(); +}); + +test("uploadImage sends multipart data to generated image endpoint", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async () => new Response(JSON.stringify({ code: 0, data: { url: "https://media.haiyihy.com/admin/images/a.png" } }))) + ); + + const result = await uploadImage(new File(["image"], "avatar.png", { type: "image/png" })); + const [url, init] = vi.mocked(fetch).mock.calls[0]; + + expect(result.url).toContain("/admin/images/"); + expect(String(url)).toContain("/api/v1/admin/files/image/upload"); + expect(init?.method).toBe("POST"); + expect(init?.body).toBeInstanceOf(FormData); + expect(init?.headers).not.toHaveProperty("Content-Type"); +}); + +test("uploadFile sends multipart data to generated file endpoint", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async () => new Response(JSON.stringify({ code: 0, data: { url: "https://media.haiyihy.com/admin/files/a.pdf" } }))) + ); + + await uploadFile(new File(["file"], "doc.pdf", { type: "application/pdf" })); + const [url, init] = vi.mocked(fetch).mock.calls[0]; + + expect(String(url)).toContain("/api/v1/admin/files/upload"); + expect(init?.method).toBe("POST"); + expect(init?.body).toBeInstanceOf(FormData); + expect(init?.headers).not.toHaveProperty("Content-Type"); +}); diff --git a/src/shared/api/upload.ts b/src/shared/api/upload.ts new file mode 100644 index 0000000..7f4e70c --- /dev/null +++ b/src/shared/api/upload.ts @@ -0,0 +1,25 @@ +import { apiRequest } from "@/shared/api/request"; +import { API_ENDPOINTS, API_OPERATIONS, apiEndpointPath } from "@/shared/api/generated/endpoints"; +import type { UploadResultDto } from "@/shared/api/types"; + +export function uploadFile(file: File): Promise { + const endpoint = API_ENDPOINTS.uploadFile; + const formData = new FormData(); + formData.append("file", file); + + return apiRequest(apiEndpointPath(API_OPERATIONS.uploadFile), { + body: formData, + method: endpoint.method + }); +} + +export function uploadImage(file: File): Promise { + const endpoint = API_ENDPOINTS.uploadImage; + const formData = new FormData(); + formData.append("image", file); + + return apiRequest(apiEndpointPath(API_OPERATIONS.uploadImage), { + body: formData, + method: endpoint.method + }); +} diff --git a/src/components/charts/BarChart.jsx b/src/shared/charts/BarChart.jsx similarity index 54% rename from src/components/charts/BarChart.jsx rename to src/shared/charts/BarChart.jsx index 505175b..2af85b9 100644 --- a/src/components/charts/BarChart.jsx +++ b/src/shared/charts/BarChart.jsx @@ -1,18 +1,26 @@ import { EChart } from "./EChart.jsx"; +import { readCssToken } from "@/shared/utils/cssTokens.js"; export function BarChart({ series }) { return ; } function createBarOption(series) { + const tooltipBg = readCssToken("--chart-tooltip-bg"); + const tooltipBorder = readCssToken("--chart-tooltip-border"); + const tooltipText = readCssToken("--chart-tooltip-text"); + const gridColor = readCssToken("--chart-grid"); + const danger = readCssToken("--chart-red"); + const warning = readCssToken("--chart-orange"); + return { animationDuration: 450, grid: { left: 8, right: 8, top: 16, bottom: 10, containLabel: false }, tooltip: { trigger: "axis", - backgroundColor: "#ffffff", - borderColor: "#d8e0ec", - textStyle: { color: "#0f172a" } + backgroundColor: tooltipBg, + borderColor: tooltipBorder, + textStyle: { color: tooltipText } }, xAxis: { type: "category", @@ -22,7 +30,7 @@ function createBarOption(series) { yAxis: { type: "value", show: false, - splitLine: { show: true, lineStyle: { color: "rgba(100, 116, 139, 0.14)" } } + splitLine: { show: true, lineStyle: { color: gridColor } } }, series: [ { @@ -30,14 +38,14 @@ function createBarOption(series) { type: "bar", data: series.map((value) => Math.max(1, Math.round(value * 0.45))), barWidth: 8, - itemStyle: { color: "#dc2626", borderRadius: [4, 4, 0, 0] } + itemStyle: { color: danger, borderRadius: [4, 4, 0, 0] } }, { name: "警告", type: "bar", data: series, barWidth: 8, - itemStyle: { color: "#d97706", borderRadius: [4, 4, 0, 0] } + itemStyle: { color: warning, borderRadius: [4, 4, 0, 0] } } ] }; diff --git a/src/components/charts/ChartCard.jsx b/src/shared/charts/ChartCard.jsx similarity index 52% rename from src/components/charts/ChartCard.jsx rename to src/shared/charts/ChartCard.jsx index b04ff9b..19bcb8c 100644 --- a/src/components/charts/ChartCard.jsx +++ b/src/shared/charts/ChartCard.jsx @@ -1,15 +1,15 @@ -import { Card } from "../base/Card.jsx"; -import { SegmentControl } from "../base/SegmentControl.jsx"; +import { Card } from "@/shared/ui/Card.jsx"; +import { SegmentControl } from "@/shared/ui/SegmentControl.jsx"; import { LineChart } from "./LineChart.jsx"; -export function ChartCard({ color, compact = false, series, title }) { +export function ChartCard({ colorToken, compact = false, series, title }) { return (
{title}
- +
); } diff --git a/src/components/charts/EChart.jsx b/src/shared/charts/EChart.jsx similarity index 100% rename from src/components/charts/EChart.jsx rename to src/shared/charts/EChart.jsx diff --git a/src/components/charts/LineChart.jsx b/src/shared/charts/LineChart.jsx similarity index 57% rename from src/components/charts/LineChart.jsx rename to src/shared/charts/LineChart.jsx index 08288d8..a957001 100644 --- a/src/components/charts/LineChart.jsx +++ b/src/shared/charts/LineChart.jsx @@ -1,18 +1,25 @@ import { EChart } from "./EChart.jsx"; +import { readCssToken } from "@/shared/utils/cssTokens.js"; -export function LineChart({ color, series }) { +export function LineChart({ colorToken = "--chart-blue", series }) { + const color = readCssToken(colorToken); return ; } function createLineOption(color, series) { + const tooltipBg = readCssToken("--chart-tooltip-bg"); + const tooltipBorder = readCssToken("--chart-tooltip-border"); + const tooltipText = readCssToken("--chart-tooltip-text"); + const gridColor = readCssToken("--chart-grid"); + return { animationDuration: 450, grid: { left: 8, right: 8, top: 14, bottom: 8, containLabel: false }, tooltip: { trigger: "axis", - backgroundColor: "#ffffff", - borderColor: "#d8e0ec", - textStyle: { color: "#0f172a" } + backgroundColor: tooltipBg, + borderColor: tooltipBorder, + textStyle: { color: tooltipText } }, xAxis: { type: "category", @@ -25,7 +32,7 @@ function createLineOption(color, series) { min: 0, max: 100, show: false, - splitLine: { show: true, lineStyle: { color: "rgba(100, 116, 139, 0.16)" } } + splitLine: { show: true, lineStyle: { color: gridColor } } }, series: [ { diff --git a/src/components/charts/StatusDonut.jsx b/src/shared/charts/StatusDonut.jsx similarity index 69% rename from src/components/charts/StatusDonut.jsx rename to src/shared/charts/StatusDonut.jsx index e4aaf46..8644b5d 100644 --- a/src/components/charts/StatusDonut.jsx +++ b/src/shared/charts/StatusDonut.jsx @@ -1,4 +1,5 @@ import { EChart } from "./EChart.jsx"; +import { readCssToken } from "@/shared/utils/cssTokens.js"; export function StatusDonut({ stats }) { return ( @@ -14,13 +15,20 @@ export function StatusDonut({ stats }) { } function createPieOption(stats) { + const tooltipBg = readCssToken("--chart-tooltip-bg"); + const tooltipBorder = readCssToken("--chart-tooltip-border"); + const tooltipText = readCssToken("--chart-tooltip-text"); + const success = readCssToken("--chart-green"); + const danger = readCssToken("--chart-red"); + const stopped = readCssToken("--stopped"); + return { animationDuration: 450, tooltip: { trigger: "item", - backgroundColor: "#ffffff", - borderColor: "#d8e0ec", - textStyle: { color: "#0f172a" } + backgroundColor: tooltipBg, + borderColor: tooltipBorder, + textStyle: { color: tooltipText } }, series: [ { @@ -31,9 +39,9 @@ function createPieOption(stats) { label: { show: false }, labelLine: { show: false }, data: [ - { name: "运行中", value: stats.running, itemStyle: { color: "#16a34a" } }, - { name: "异常/警告", value: stats.error + stats.warning, itemStyle: { color: "#dc2626" } }, - { name: "已停止", value: stats.stopped, itemStyle: { color: "#64748b" } } + { name: "运行中", value: stats.running, itemStyle: { color: success } }, + { name: "异常/警告", value: stats.error + stats.warning, itemStyle: { color: danger } }, + { name: "已停止", value: stats.stopped, itemStyle: { color: stopped } } ] } ] diff --git a/src/config/env.js b/src/shared/config/env.js similarity index 100% rename from src/config/env.js rename to src/shared/config/env.js diff --git a/src/shared/forms/validation.ts b/src/shared/forms/validation.ts new file mode 100644 index 0000000..e95da19 --- /dev/null +++ b/src/shared/forms/validation.ts @@ -0,0 +1,27 @@ +import type { ZodIssue, ZodType } from "zod"; + +export class FormValidationError extends Error { + fieldErrors: Record; + issues: ZodIssue[]; + + constructor(issues: ZodIssue[]) { + super(issues[0]?.message || "表单校验失败"); + this.name = "FormValidationError"; + this.issues = issues; + this.fieldErrors = issues.reduce>((errors, issue) => { + const field = issue.path.join("."); + if (field && !errors[field]) { + errors[field] = issue.message; + } + return errors; + }, {}); + } +} + +export function parseForm(schema: ZodType, value: unknown): TValue { + const result = schema.safeParse(value); + if (result.success) { + return result.data; + } + throw new FormValidationError(result.error.issues); +} diff --git a/src/shared/hooks/useAdminMutation.js b/src/shared/hooks/useAdminMutation.js new file mode 100644 index 0000000..92298bb --- /dev/null +++ b/src/shared/hooks/useAdminMutation.js @@ -0,0 +1,25 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +export function useAdminMutation(mutationFn, options = {}) { + const queryClient = useQueryClient(); + const mutation = useMutation({ + mutationFn: (args) => mutationFn(...args), + onSuccess: async (data, variables, context) => { + if (options.invalidateQueries) { + const keys = Array.isArray(options.invalidateQueries[0]) + ? options.invalidateQueries + : [options.invalidateQueries]; + await Promise.all(keys.map((queryKey) => queryClient.invalidateQueries({ queryKey }))); + } + if (options.onSuccess) { + await options.onSuccess(data, variables, context); + } + } + }); + + return { + error: mutation.error ? mutation.error.message || "操作失败" : "", + loading: mutation.isPending, + mutate: (...args) => mutation.mutateAsync(args) + }; +} diff --git a/src/shared/hooks/useAdminQuery.js b/src/shared/hooks/useAdminQuery.js new file mode 100644 index 0000000..a3821b1 --- /dev/null +++ b/src/shared/hooks/useAdminQuery.js @@ -0,0 +1,47 @@ +import { useCallback, useId } from "react"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; + +export function useAdminQuery(queryFn, options = {}) { + const defaultQueryId = useId(); + + const { + enabled = true, + errorMessage = "请求失败", + keepPreviousData = false, + initialData = null, + queryKey = ["admin-query", defaultQueryId], + refetchInterval, + staleTime + } = options; + const queryClient = useQueryClient(); + const query = useQuery({ + enabled, + placeholderData: keepPreviousData ? (previousData) => previousData ?? initialData : initialData, + queryFn, + queryKey, + refetchInterval, + staleTime + }); + + const reload = useCallback(async () => { + const result = await query.refetch(); + return result.data ?? null; + }, [query]); + + const setData = useCallback( + (updater) => { + queryClient.setQueryData(queryKey, updater); + }, + [queryClient, queryKey] + ); + + const error = query.error ? query.error.message || errorMessage : ""; + + return { + data: query.data, + error, + loading: query.isLoading || query.isFetching, + reload, + setData + }; +} diff --git a/src/shared/hooks/useAdminQuery.test.jsx b/src/shared/hooks/useAdminQuery.test.jsx new file mode 100644 index 0000000..a70079a --- /dev/null +++ b/src/shared/hooks/useAdminQuery.test.jsx @@ -0,0 +1,26 @@ +import { QueryClientProvider } from "@tanstack/react-query"; +import { renderHook, waitFor } from "@testing-library/react"; +import { expect, test, vi } from "vitest"; +import { createAdminQueryClient } from "@/shared/query/client"; +import { useAdminQuery } from "./useAdminQuery.js"; + +test("reuses cached query data by query key", async () => { + const queryClient = createAdminQueryClient(); + const fetcher = vi.fn(async () => ({ value: 1 })); + const wrapper = ({ children }) => {children}; + + const queryOptions = { initialData: null, queryKey: ["cache-test"], staleTime: 60000 }; + const first = renderHook(() => useAdminQuery(fetcher, queryOptions), { wrapper }); + + await waitFor(() => { + expect(first.result.current.data).toEqual({ value: 1 }); + }); + + renderHook(() => useAdminQuery(fetcher, queryOptions), { wrapper }); + + await waitFor(() => { + expect(fetcher).toHaveBeenCalledTimes(1); + }); + + queryClient.clear(); +}); diff --git a/src/shared/hooks/usePaginatedQuery.js b/src/shared/hooks/usePaginatedQuery.js new file mode 100644 index 0000000..046d433 --- /dev/null +++ b/src/shared/hooks/usePaginatedQuery.js @@ -0,0 +1,20 @@ +import { useCallback, useMemo } from "react"; +import { toPageQuery } from "@/shared/api/query"; +import { useAdminQuery } from "./useAdminQuery.js"; + +const emptyPage = { items: [], total: 0, page: 1, pageSize: 10 }; + +export function usePaginatedQuery({ errorMessage, fetcher, filters = {}, page, pageSize = 10, queryKey }) { + const requestQuery = useMemo( + () => toPageQuery({ ...filters, page, pageSize }), + [filters, page, pageSize] + ); + const queryFn = useCallback(() => fetcher(requestQuery), [fetcher, requestQuery]); + + return useAdminQuery(queryFn, { + errorMessage, + initialData: emptyPage, + keepPreviousData: true, + queryKey: queryKey || ["paginated", requestQuery] + }); +} diff --git a/src/shared/hooks/useRefreshSignal.js b/src/shared/hooks/useRefreshSignal.js new file mode 100644 index 0000000..584be94 --- /dev/null +++ b/src/shared/hooks/useRefreshSignal.js @@ -0,0 +1,28 @@ +import { createContext, createElement, useCallback, useContext, useMemo, useState } from "react"; +import { useQueryClient } from "@tanstack/react-query"; + +const RefreshContext = createContext(null); + +export function RefreshProvider({ children }) { + const [refreshKey, setRefreshKey] = useState(0); + const queryClient = useQueryClient(); + const refresh = useCallback(() => { + setRefreshKey((current) => current + 1); + queryClient.invalidateQueries(); + }, [queryClient]); + const value = useMemo(() => ({ refresh, refreshKey }), [refresh, refreshKey]); + + return createElement(RefreshContext.Provider, { value }, children); +} + +export function useRefreshSignal() { + const context = useContext(RefreshContext); + if (!context) { + throw new Error("useRefreshSignal must be used inside RefreshProvider"); + } + return context; +} + +export function useRefreshKey() { + return useRefreshSignal().refreshKey; +} diff --git a/src/shared/hooks/useRegionOptions.js b/src/shared/hooks/useRegionOptions.js new file mode 100644 index 0000000..d8f4661 --- /dev/null +++ b/src/shared/hooks/useRegionOptions.js @@ -0,0 +1,31 @@ +import { useCallback, useMemo } from "react"; +import { listRegions } from "@/shared/api/regions"; +import { useAdminQuery } from "@/shared/hooks/useAdminQuery.js"; + +const emptyData = { items: [], total: 0 }; + +export function useRegionOptions() { + const queryFn = useCallback(() => listRegions(), []); + const { data = emptyData, loading: loadingRegions } = useAdminQuery(queryFn, { + errorMessage: "加载区域选项失败", + initialData: emptyData, + queryKey: ["regions", "options"], + staleTime: 5 * 60 * 1000 + }); + + const regionOptions = useMemo(() => { + return (data?.items || []).map((region) => ({ + label: formatRegionLabel(region), + name: region.name, + regionCode: region.regionCode, + regionId: region.regionId, + value: String(region.regionId) + })); + }, [data?.items]); + + return { loadingRegions, regionOptions }; +} + +function formatRegionLabel(region) { + return [region.name, region.regionCode, region.regionId].filter(Boolean).join(" · "); +} diff --git a/src/shared/query/QueryProvider.jsx b/src/shared/query/QueryProvider.jsx new file mode 100644 index 0000000..a8a6e87 --- /dev/null +++ b/src/shared/query/QueryProvider.jsx @@ -0,0 +1,6 @@ +import { QueryClientProvider } from "@tanstack/react-query"; +import { queryClient } from "./client"; + +export function QueryProvider({ children }) { + return {children}; +} diff --git a/src/shared/query/client.ts b/src/shared/query/client.ts new file mode 100644 index 0000000..6a0ea6f --- /dev/null +++ b/src/shared/query/client.ts @@ -0,0 +1,19 @@ +import { QueryClient } from "@tanstack/react-query"; + +const defaultOptions = { + queries: { + gcTime: 10 * 60 * 1000, + refetchOnWindowFocus: false, + retry: 1, + staleTime: 30 * 1000 + }, + mutations: { + retry: 0 + } +}; + +export function createAdminQueryClient() { + return new QueryClient({ defaultOptions }); +} + +export const queryClient = createAdminQueryClient(); diff --git a/src/shared/ui/AdminFormDialog.jsx b/src/shared/ui/AdminFormDialog.jsx new file mode 100644 index 0000000..0314038 --- /dev/null +++ b/src/shared/ui/AdminFormDialog.jsx @@ -0,0 +1,83 @@ +import CircularProgress from "@mui/material/CircularProgress"; +import Dialog from "@mui/material/Dialog"; +import DialogActions from "@mui/material/DialogActions"; +import DialogContent from "@mui/material/DialogContent"; +import DialogTitle from "@mui/material/DialogTitle"; +import { Button } from "@/shared/ui/Button.jsx"; +import styles from "@/shared/ui/AdminFormDialog.module.css"; + +export function AdminFormDialog({ + children, + disabled = false, + loading = false, + onClose, + onSubmit, + open, + size = "standard", + submitDisabled, + submitLabel = "提交", + submitVariant = "primary", + title +}) { + const isSubmitDisabled = submitDisabled ?? (disabled || loading); + + return ( + +
+ {title} + {children} + + + + +
+
+ ); +} + +export function AdminFormSection({ actions, children, title }) { + return ( +
+ {title || actions ? ( +
+ {title ?
{title}
: } + {actions ?
{actions}
: null} +
+ ) : null} + {children} +
+ ); +} + +export function AdminFormFieldGrid({ children, columns }) { + return ( +
+ {children} +
+ ); +} + +export function AdminFormInlineActions({ children }) { + return
{children}
; +} + +export function AdminFormList({ children }) { + return
{children}
; +} + +export function AdminFormListRow({ children, columns }) { + return ( +
+ {children} +
+ ); +} diff --git a/src/shared/ui/AdminFormDialog.module.css b/src/shared/ui/AdminFormDialog.module.css new file mode 100644 index 0000000..2211a46 --- /dev/null +++ b/src/shared/ui/AdminFormDialog.module.css @@ -0,0 +1,161 @@ +.dialog { + --admin-form-dialog-width: 600px; + --admin-form-content-x: var(--space-6); + --admin-form-field-width: 240px; + --admin-form-wide-width: calc(var(--admin-form-dialog-width) - var(--admin-form-content-x) - var(--admin-form-content-x)); +} + +.paper { + width: min(var(--admin-form-dialog-width), calc(100vw - 32px)) !important; + max-width: min(var(--admin-form-dialog-width), calc(100vw - 32px)) !important; + border-radius: var(--radius-card); +} + +.compact { + --admin-form-dialog-width: 520px; + --admin-form-field-width: 220px; +} + +.wide { + --admin-form-dialog-width: 680px; + --admin-form-field-width: 220px; +} + +.form { + display: flex; + max-height: calc(100vh - 64px); + flex-direction: column; +} + +.title { + padding: var(--space-5) var(--admin-form-content-x) var(--space-3); + color: var(--text-primary); + font-size: var(--admin-font-size); + font-weight: 760; + line-height: var(--admin-line-height); +} + +.content { + display: flex; + flex: 0 1 auto; + flex-wrap: wrap; + overflow: auto; + align-items: start; + gap: var(--space-3); + padding: var(--space-2) var(--admin-form-content-x) var(--space-4) !important; +} + +.content > :global(.MuiTextField-root), +.content > :global(.MuiFormControl-root) { + flex: 0 0 min(100%, var(--admin-form-field-width)); + width: min(100%, var(--admin-form-field-width)); + max-width: 100%; +} + +.content > :global(.MuiTextField-root:has(.MuiInputBase-multiline)), +.content > :global(.MuiFormControl-root:has(.MuiInputBase-multiline)), +.content > :global(.MuiAutocomplete-root), +.content > :global(.MuiFormControlLabel-root), +.content > :not(:global(.MuiTextField-root)):not(:global(.MuiFormControl-root)):not(:global(.MuiAutocomplete-root)):not(:global(.MuiFormControlLabel-root)) { + flex: 0 0 min(100%, var(--admin-form-wide-width)); + width: min(100%, var(--admin-form-wide-width)); + max-width: 100%; +} + +.section { + display: grid; + gap: var(--space-3); +} + +.sectionHeader { + display: flex; + min-height: var(--control-height); + align-items: center; + justify-content: flex-start; + flex-wrap: wrap; + gap: var(--space-3); +} + +.sectionTitle { + color: var(--text-primary); + font-size: var(--admin-font-size); + font-weight: 700; +} + +.sectionActions, +.inlineActions { + display: flex; + flex-wrap: wrap; + gap: var(--space-2); +} + +.fieldGrid { + display: grid; + grid-template-columns: var(--admin-form-grid-columns, repeat(auto-fit, minmax(180px, 1fr))); + gap: var(--space-3); + width: min(100%, var(--admin-form-wide-width)); + max-width: 100%; +} + +.fieldGrid > :global(.MuiTextField-root), +.fieldGrid > :global(.MuiFormControl-root), +.fieldGrid > :global(.MuiAutocomplete-root) { + width: 100%; +} + +.list { + display: grid; + gap: var(--space-2); +} + +.listRow { + display: grid; + grid-template-columns: var(--admin-form-list-columns, minmax(180px, 1fr) minmax(112px, 0.55fr) minmax(92px, 0.4fr) auto); + align-items: start; + gap: var(--space-2); + padding: var(--space-3); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + background: var(--bg-card-strong); +} + +.listRow > :global(.MuiTextField-root), +.listRow > :global(.MuiFormControl-root) { + width: 100%; +} + +.actions { + justify-content: flex-end; + gap: var(--space-2); + padding: var(--space-3) var(--admin-form-content-x) var(--space-5); +} + +@media (max-width: 760px) { + .dialog { + --admin-form-content-x: var(--space-4); + --admin-form-wide-width: calc(100vw - 24px - var(--admin-form-content-x) - var(--admin-form-content-x)); + } + + .paper { + width: calc(100vw - 24px) !important; + max-width: calc(100vw - 24px) !important; + } + + .content { + padding-right: var(--admin-form-content-x) !important; + padding-left: var(--admin-form-content-x) !important; + } + + .content > :global(.MuiTextField-root), + .content > :global(.MuiFormControl-root) { + width: 100%; + } + + .listRow { + grid-template-columns: 1fr; + } + + .fieldGrid { + grid-template-columns: 1fr !important; + } +} diff --git a/src/shared/ui/AdminListLayout.jsx b/src/shared/ui/AdminListLayout.jsx new file mode 100644 index 0000000..087adef --- /dev/null +++ b/src/shared/ui/AdminListLayout.jsx @@ -0,0 +1,70 @@ +import Tooltip from "@mui/material/Tooltip"; +import { IconButton } from "@/shared/ui/IconButton.jsx"; +import { SearchBox } from "@/shared/ui/SearchBox.jsx"; +import { StatusSelect } from "@/shared/ui/StatusSelect.jsx"; +import styles from "@/shared/ui/AdminListLayout.module.css"; + +export function AdminListPage({ children }) { + return ( +
+
{children}
+
+ ); +} + +export function AdminListToolbar({ actions, filters }) { + return ( +
+
+
{filters}
+
+ {actions ?
{actions}
: null} +
+ ); +} + +export function AdminListBody({ children }) { + return
{children}
; +} + +export function AdminRowActions({ children }) { + return
{children}
; +} + +export function AdminSearchBox(props) { + return ; +} + +export function AdminFilterSelect(props) { + return ; +} + +export function AdminActionIconButton({ children, label, primary = false, tooltip = label, ...props }) { + return ( + + + + {children} + + + + ); +} + +export const adminListClasses = styles; + +const primaryActionSx = { + borderColor: "var(--primary)", + backgroundColor: "var(--primary)", + color: "var(--active-contrast)", + "&:hover": { + borderColor: "var(--primary-strong)", + backgroundColor: "var(--primary-strong)", + color: "var(--active-contrast)" + } +}; diff --git a/src/shared/ui/AdminListLayout.module.css b/src/shared/ui/AdminListLayout.module.css new file mode 100644 index 0000000..5e39a2e --- /dev/null +++ b/src/shared/ui/AdminListLayout.module.css @@ -0,0 +1,135 @@ +.root { + display: flex; + height: calc(100vh - var(--header-height) - var(--space-8) - var(--space-4)); + height: calc(100dvh - var(--header-height) - var(--space-8) - var(--space-4)); + min-height: 0; + flex-direction: column; +} + +.contentPanel { + display: flex; + flex: 1; + flex-direction: column; + min-height: 0; + overflow: hidden; + border: 1px solid var(--border); + border-radius: var(--radius-card); + background: var(--bg-card); +} + +.contentPanel :global(.data-state), +.contentPanel :global(.table-frame) { + flex: 1; + min-height: 0; + border: 0; + border-radius: 0; + background: transparent; +} + +.contentPanel :global(.table-scroll) { + overflow: auto; +} + +.contentPanel :global(.admin-row--head) { + position: sticky; + top: 0; + z-index: 1; +} + +.toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-4); + padding: var(--space-4) var(--space-5); + border-bottom: 1px solid var(--border); +} + +.toolbarLeft { + display: flex; + min-width: 0; + flex: 1; + align-items: center; + gap: var(--space-3); +} + +.toolbarActions { + display: flex; + flex: 0 0 auto; + align-items: center; + gap: var(--space-2); +} + +.filters { + display: flex; + min-width: 0; + flex: 1; + align-items: center; + flex-wrap: wrap; + gap: var(--space-3); +} + +.search { + flex: 0 1 300px; +} + +.statusSelect { + flex: 0 0 150px; +} + +.listBlock { + display: flex; + flex: 1; + min-height: 0; + flex-direction: column; + background: var(--bg-card); +} + +.listBlock :global(.pagination-bar) { + margin: var(--space-3) var(--space-5) var(--space-4); +} + +.rowActions { + display: inline-flex; + align-items: center; + gap: var(--space-2); +} + +.dragRow { + cursor: grab; +} + +.dragRow:active { + cursor: grabbing; +} + +.dragRowActive { + background: var(--primary-hover); +} + +.dragHandle { + display: inline-flex; + align-items: center; + justify-content: center; + color: var(--text-tertiary); + cursor: grab; +} + +@media (max-width: 760px) { + .toolbar, + .toolbarLeft, + .filters { + align-items: stretch; + flex-direction: column; + } + + .toolbarActions { + align-self: flex-end; + } + + .search, + .statusSelect { + flex: 1 1 auto; + width: 100%; + } +} diff --git a/src/components/base/Button.jsx b/src/shared/ui/Button.jsx similarity index 71% rename from src/components/base/Button.jsx rename to src/shared/ui/Button.jsx index 5a2bcea..5dd0ceb 100644 --- a/src/components/base/Button.jsx +++ b/src/shared/ui/Button.jsx @@ -12,7 +12,7 @@ export function Button({ children, className = "", variant, ...props }) { className={["button", variantClass, className].filter(Boolean).join(" ")} color={color} disableElevation - sx={{ ...toneSx, ...sx }} + sx={{ height: "var(--control-height)", minHeight: "var(--control-height)", ...toneSx, ...sx }} type="button" variant={muiVariant} {...buttonProps} @@ -26,10 +26,10 @@ function getToneSx(variant) { if (variant === "success") { return { color: "var(--success)", - borderColor: "rgba(46, 219, 95, 0.38)", + borderColor: "var(--success-border)", "&:hover": { - borderColor: "rgba(46, 219, 95, 0.72)", - backgroundColor: "rgba(46, 219, 95, 0.1)" + borderColor: "var(--success-border-strong)", + backgroundColor: "var(--success-surface)" } }; } @@ -37,10 +37,10 @@ function getToneSx(variant) { if (variant === "danger") { return { color: "var(--danger)", - borderColor: "rgba(255, 77, 79, 0.38)", + borderColor: "var(--danger-border)", "&:hover": { - borderColor: "rgba(255, 77, 79, 0.72)", - backgroundColor: "rgba(255, 77, 79, 0.1)" + borderColor: "var(--danger-border-strong)", + backgroundColor: "var(--danger-surface)" } }; } diff --git a/src/components/base/Card.jsx b/src/shared/ui/Card.jsx similarity index 100% rename from src/components/base/Card.jsx rename to src/shared/ui/Card.jsx diff --git a/src/components/base/ConfirmProvider.jsx b/src/shared/ui/ConfirmProvider.jsx similarity index 100% rename from src/components/base/ConfirmProvider.jsx rename to src/shared/ui/ConfirmProvider.jsx diff --git a/src/components/base/DataState.jsx b/src/shared/ui/DataState.jsx similarity index 100% rename from src/components/base/DataState.jsx rename to src/shared/ui/DataState.jsx diff --git a/src/shared/ui/DataTable.jsx b/src/shared/ui/DataTable.jsx new file mode 100644 index 0000000..6908796 --- /dev/null +++ b/src/shared/ui/DataTable.jsx @@ -0,0 +1,64 @@ +export function DataTable({ + className = "", + columns, + context, + emptyLabel = "当前无数据", + getRowProps, + items, + minWidth = "980px", + rowKey, + tableClassName = "", + title, + total +}) { + const gridTemplate = columns.map((column) => column.width || "minmax(120px, 1fr)").join(" "); + + return ( +
+ {title || total !== undefined ? ( +
+
{title}
+ {total !== undefined ?
共 {total} 条
: null} +
+ ) : null} +
+
+
+ {columns.map((column) => ( +
+ {column.header || column.label} +
+ ))} +
+ {items.length ? ( + items.map((item, index) => { + const rowProps = getRowProps ? getRowProps(item, index) : {}; + const { className: rowClassName = "", ...restRowProps } = rowProps; + + return ( +
+ {columns.map((column) => ( +
+ {column.render ? column.render(item, index, context) : displayValue(item[column.key])} +
+ ))} +
+ ); + }) + ) : ( +
+
{emptyLabel}
+
+ )} +
+
+
+ ); +} + +function displayValue(value) { + return value === 0 || value === false || value ? value : "-"; +} diff --git a/src/components/base/FilterChip.jsx b/src/shared/ui/FilterChip.jsx similarity index 54% rename from src/components/base/FilterChip.jsx rename to src/shared/ui/FilterChip.jsx index a9491fd..1576cf6 100644 --- a/src/components/base/FilterChip.jsx +++ b/src/shared/ui/FilterChip.jsx @@ -9,14 +9,14 @@ export function FilterChip({ active, label, onClick }) { onClick={onClick} size="small" sx={{ - height: 30, - borderColor: active ? "rgba(37, 99, 235, 0.7)" : "var(--border)", - backgroundColor: active ? "rgba(37, 99, 235, 0.1)" : "transparent", + height: "var(--status-height)", + borderColor: active ? "var(--primary-border-strong)" : "var(--border)", + backgroundColor: active ? "var(--primary-surface)" : "transparent", color: active ? "var(--text-primary)" : "var(--text-tertiary)", - fontSize: 12, + fontSize: "var(--admin-font-size)", "&:hover": { - borderColor: "rgba(37, 99, 235, 0.7)", - backgroundColor: active ? "rgba(37, 99, 235, 0.14)" : "rgba(37, 99, 235, 0.08)" + borderColor: "var(--primary-border-strong)", + backgroundColor: active ? "var(--primary-surface-strong)" : "var(--primary-hover)" } }} variant="outlined" diff --git a/src/components/base/IconButton.jsx b/src/shared/ui/IconButton.jsx similarity index 64% rename from src/components/base/IconButton.jsx rename to src/shared/ui/IconButton.jsx index b0dc05b..a2647fd 100644 --- a/src/components/base/IconButton.jsx +++ b/src/shared/ui/IconButton.jsx @@ -8,11 +8,11 @@ export function IconButton({ children, className = "", label, notice, tone, ...p className={["icon-button", notice ? "icon-button--notice" : "", className].filter(Boolean).join(" ")} aria-label={label} sx={{ - width: className.includes("action-button") ? 32 : 38, - height: className.includes("action-button") ? 32 : 38, + width: "var(--control-height)", + height: "var(--control-height)", padding: 0, border: "1px solid var(--border)", - backgroundColor: className.includes("action-button") ? "var(--bg-card-strong)" : "var(--bg-card)", + backgroundColor: "var(--bg-card)", ...getToneSx(tone), ...sx }} @@ -28,14 +28,14 @@ function getToneSx(tone) { if (tone === "success") { return { color: "var(--success)", - "&:hover": { borderColor: "rgba(46, 219, 95, 0.72)", backgroundColor: "rgba(46, 219, 95, 0.1)" } + "&:hover": { borderColor: "var(--success-border-strong)", backgroundColor: "var(--success-surface)" } }; } if (tone === "danger") { return { color: "var(--danger)", - "&:hover": { borderColor: "rgba(255, 77, 79, 0.72)", backgroundColor: "rgba(255, 77, 79, 0.1)" } + "&:hover": { borderColor: "var(--danger-border-strong)", backgroundColor: "var(--danger-surface)" } }; } diff --git a/src/components/base/KpiCard.jsx b/src/shared/ui/KpiCard.jsx similarity index 100% rename from src/components/base/KpiCard.jsx rename to src/shared/ui/KpiCard.jsx diff --git a/src/components/layout/PageHead.jsx b/src/shared/ui/PageHead.jsx similarity index 100% rename from src/components/layout/PageHead.jsx rename to src/shared/ui/PageHead.jsx diff --git a/src/components/layout/PageSkeleton.jsx b/src/shared/ui/PageSkeleton.jsx similarity index 100% rename from src/components/layout/PageSkeleton.jsx rename to src/shared/ui/PageSkeleton.jsx diff --git a/src/components/base/PaginationBar.jsx b/src/shared/ui/PaginationBar.jsx similarity index 100% rename from src/components/base/PaginationBar.jsx rename to src/shared/ui/PaginationBar.jsx diff --git a/src/shared/ui/RegionSelect.jsx b/src/shared/ui/RegionSelect.jsx new file mode 100644 index 0000000..675f2e8 --- /dev/null +++ b/src/shared/ui/RegionSelect.jsx @@ -0,0 +1,54 @@ +import MenuItem from "@mui/material/MenuItem"; +import TextField from "@mui/material/TextField"; +import { restorePlaceholderAfterAllSelected } from "@/shared/ui/selectAllBehavior.js"; + +export function RegionSelect({ + className, + disabled = false, + emptyLabel, + label = "区域", + loading = false, + onChange, + options = [], + required = false, + size = "small", + value +}) { + const isEmpty = !loading && options.length === 0; + const selectedValue = value === undefined || value === null ? "" : String(value); + const hasSelectedOption = !selectedValue || options.some((option) => option.value === selectedValue); + + const handleChange = (event) => { + const nextValue = event.target.value; + const selectedOption = nextValue === "" ? { label: emptyLabel } : options.find((option) => option.value === nextValue); + onChange(nextValue); + restorePlaceholderAfterAllSelected(selectedOption?.label); + }; + const rootClassName = ["region-select", className].filter(Boolean).join(" "); + + return ( + + {emptyLabel ? {emptyLabel} : null} + {!hasSelectedOption ? {selectedValue} : null} + {isEmpty ? ( + + 暂无区域 + + ) : null} + {options.map((option) => ( + + {option.label} + + ))} + + ); +} diff --git a/src/components/base/SearchBox.jsx b/src/shared/ui/SearchBox.jsx similarity index 85% rename from src/components/base/SearchBox.jsx rename to src/shared/ui/SearchBox.jsx index 9d4ceea..5667c76 100644 --- a/src/components/base/SearchBox.jsx +++ b/src/shared/ui/SearchBox.jsx @@ -11,7 +11,7 @@ export function SearchBox({ autoFocus = false, className = "", onChange, placeho onChange={(event) => onChange(event.currentTarget.value)} placeholder={placeholder} type="search" - sx={{ color: "var(--text-primary)", flex: 1, fontSize: 13 }} + sx={{ color: "var(--text-primary)", flex: 1, fontSize: "var(--admin-font-size)" }} /> ); diff --git a/src/components/base/SegmentControl.jsx b/src/shared/ui/SegmentControl.jsx similarity index 79% rename from src/components/base/SegmentControl.jsx rename to src/shared/ui/SegmentControl.jsx index 13d7744..fcdedad 100644 --- a/src/components/base/SegmentControl.jsx +++ b/src/shared/ui/SegmentControl.jsx @@ -37,17 +37,17 @@ export function SegmentControl({ value = "avg" }) { const segmentSx = { minWidth: 52, - height: 24, - padding: "0 10px", - borderRadius: "6px !important", + height: "var(--status-height)", + padding: "0 var(--space-3)", + borderRadius: "var(--radius-control) !important", color: "var(--text-tertiary)", - fontSize: 12, - lineHeight: 1, + fontSize: "var(--admin-font-size)", + lineHeight: "var(--admin-line-height)", "&.Mui-selected": { - backgroundColor: "rgba(37, 99, 235, 0.1)", + backgroundColor: "var(--primary-surface)", color: "var(--text-primary)" }, "&.Mui-selected:hover": { - backgroundColor: "rgba(37, 99, 235, 0.14)" + backgroundColor: "var(--primary-surface-strong)" } }; diff --git a/src/shared/ui/SideDrawer.jsx b/src/shared/ui/SideDrawer.jsx new file mode 100644 index 0000000..eaa7aa2 --- /dev/null +++ b/src/shared/ui/SideDrawer.jsx @@ -0,0 +1,41 @@ +import CloseOutlined from "@mui/icons-material/CloseOutlined"; +import Drawer from "@mui/material/Drawer"; +import { IconButton } from "@/shared/ui/IconButton.jsx"; + +export function SideDrawer({ + actions, + as: Component = "section", + children, + className = "", + contentClassName = "", + onClose, + open, + title, + width = "default", + ...props +}) { + const drawerClassName = [ + "side-drawer", + width === "wide" ? "side-drawer--wide" : "", + className + ].filter(Boolean).join(" "); + + return ( + + +
+ {title ?

{title}

: } + {onClose ? ( + + + + ) : null} +
+
+ {children} +
+ {actions ?
{actions}
: null} +
+
+ ); +} diff --git a/src/shared/ui/StatusSelect.jsx b/src/shared/ui/StatusSelect.jsx new file mode 100644 index 0000000..679713b --- /dev/null +++ b/src/shared/ui/StatusSelect.jsx @@ -0,0 +1,29 @@ +import MenuItem from "@mui/material/MenuItem"; +import TextField from "@mui/material/TextField"; +import { restorePlaceholderAfterAllSelected } from "@/shared/ui/selectAllBehavior.js"; + +export function StatusSelect({ className, label = "状态", options = [], size = "small", value, onChange }) { + const handleChange = (event) => { + const nextValue = event.target.value; + const selectedOption = options.find(([optionValue]) => String(optionValue) === String(nextValue)); + onChange(nextValue); + restorePlaceholderAfterAllSelected(selectedOption?.[1]); + }; + + return ( + + {options.map(([optionValue, optionLabel]) => ( + + {optionLabel} + + ))} + + ); +} diff --git a/src/components/base/TimeRangeSelect.jsx b/src/shared/ui/TimeRangeSelect.jsx similarity index 88% rename from src/components/base/TimeRangeSelect.jsx rename to src/shared/ui/TimeRangeSelect.jsx index b297cb5..d13a0e2 100644 --- a/src/components/base/TimeRangeSelect.jsx +++ b/src/shared/ui/TimeRangeSelect.jsx @@ -10,7 +10,7 @@ export function TimeRangeSelect({ className = "", onChange, value }) { value={value} onChange={(event) => onChange(event.target.value)} size="small" - sx={{ width: 116, height: 38, fontSize: 13 }} + sx={{ width: 116, height: 36, fontSize: "var(--admin-font-size)" }} variant="outlined" > {ranges.map((range) => ( diff --git a/src/components/base/ToastProvider.jsx b/src/shared/ui/ToastProvider.jsx similarity index 100% rename from src/components/base/ToastProvider.jsx rename to src/shared/ui/ToastProvider.jsx diff --git a/src/shared/ui/UploadField.jsx b/src/shared/ui/UploadField.jsx new file mode 100644 index 0000000..e653d95 --- /dev/null +++ b/src/shared/ui/UploadField.jsx @@ -0,0 +1,369 @@ +import DeleteOutlineOutlined from "@mui/icons-material/DeleteOutlineOutlined"; +import FileUploadOutlined from "@mui/icons-material/FileUploadOutlined"; +import ImageOutlined from "@mui/icons-material/ImageOutlined"; +import InsertDriveFileOutlined from "@mui/icons-material/InsertDriveFileOutlined"; +import CircularProgress from "@mui/material/CircularProgress"; +import Tooltip from "@mui/material/Tooltip"; +import { useEffect, useId, useRef, useState } from "react"; +import { uploadFile, uploadImage } from "@/shared/api/upload"; +import { useToast } from "@/shared/ui/ToastProvider.jsx"; +import styles from "./UploadField.module.css"; + +const imageAccept = "image/jpeg,image/png,image/webp,.jpg,.jpeg,.png,.webp,.svga,.pag"; +const pagWasmURL = "/vendor/libpag.wasm"; +let pagModulePromise; + +export function UploadField({ + accept, + disabled = false, + kind = "image", + label, + onChange, + value = "" +}) { + const inputId = useId(); + const inputRef = useRef(null); + const { showToast } = useToast(); + const [uploading, setUploading] = useState(false); + const [localPreview, setLocalPreview] = useState(null); + const isImage = kind === "image"; + const source = localPreview?.url || value; + const assetKind = localPreview?.assetKind || getAssetKind(value, kind); + const displayName = localPreview?.name || getDisplayValue(value); + const inputAccept = accept || (isImage ? imageAccept : undefined); + + useEffect(() => { + return () => { + if (localPreview?.url) { + URL.revokeObjectURL(localPreview.url); + } + }; + }, [localPreview]); + + const openPicker = () => { + if (!disabled && !uploading) { + inputRef.current?.click(); + } + }; + + const handleFileChange = async (event) => { + const file = event.target.files?.[0]; + event.target.value = ""; + if (!file) { + return; + } + + setLocalPreview((previous) => { + if (previous?.url) { + URL.revokeObjectURL(previous.url); + } + return { + assetKind: getAssetKind(file.name, kind, file.type), + name: file.name, + url: URL.createObjectURL(file) + }; + }); + setUploading(true); + try { + const result = isImage ? await uploadImage(file) : await uploadFile(file); + onChange(result.url); + setLocalPreview(null); + showToast("上传成功", "success"); + } catch (err) { + setLocalPreview(null); + showToast(err.message || "上传失败", "error"); + } finally { + setUploading(false); + } + }; + + const clearValue = () => { + setLocalPreview(null); + onChange(""); + }; + + return ( +
+
+ {label} + {assetKindLabel(assetKind, isImage)} +
+
+ +
+ {displayName || "未上传"} + + + + + + + {source ? ( + + + + + + ) : null} + +
+
+ +
+ ); +} + +function AssetPreview({ assetKind, isImage, src }) { + if (!src) { + return ( + + {isImage ? : } + + ); + } + if (assetKind === "svga") { + return ; + } + if (assetKind === "pag") { + return ; + } + if (isImage) { + return ; + } + return ( + + + + ); +} + +function RasterAssetPreview({ src }) { + const [failed, setFailed] = useState(false); + + useEffect(() => { + setFailed(false); + }, [src]); + + if (failed) { + return ( + + + + ); + } + + return setFailed(true)} />; +} + +function SVGAAssetPreview({ src }) { + const containerRef = useRef(null); + const [failed, setFailed] = useState(false); + + useEffect(() => { + let cancelled = false; + const container = containerRef.current; + let player; + setFailed(false); + + async function load() { + try { + const module = await import("svgaplayerweb"); + const SVGA = module.default || module; + if (cancelled || !container) { + return; + } + container.innerHTML = ""; + player = new SVGA.Player(container); + player.loops = 0; + player.clearsAfterStop = false; + player.setContentMode("AspectFit"); + const parser = new SVGA.Parser(container); + parser.load( + src, + (videoItem) => { + if (cancelled) { + return; + } + player.setVideoItem(videoItem); + player.startAnimation(); + }, + () => { + if (!cancelled) { + setFailed(true); + } + } + ); + } catch { + if (!cancelled) { + setFailed(true); + } + } + } + + load(); + + return () => { + cancelled = true; + if (player) { + try { + player.stopAnimation(true); + player.clear(); + } catch { + // Ignore player cleanup errors from partially loaded assets. + } + } + if (container) { + container.innerHTML = ""; + } + }; + }, [src]); + + return ( + + + {failed ? SVGA 预览失败 : null} + + ); +} + +function PAGAssetPreview({ src }) { + const canvasRef = useRef(null); + const [failed, setFailed] = useState(false); + + useEffect(() => { + let cancelled = false; + let pagFile; + let pagView; + setFailed(false); + + async function load() { + try { + const PAG = await getPAGModule(); + const buffer = await fetch(src).then((response) => response.arrayBuffer()); + pagFile = await PAG.PAGFile.load(buffer); + if (cancelled || !canvasRef.current) { + return; + } + canvasRef.current.width = pagFile.width(); + canvasRef.current.height = pagFile.height(); + pagView = await PAG.PAGView.init(pagFile, canvasRef.current, { useScale: true }); + if (!pagView) { + throw new Error("PAGView init failed"); + } + pagView.setRepeatCount(0); + await pagView.play(); + } catch { + if (!cancelled) { + setFailed(true); + } + } + } + + load(); + + return () => { + cancelled = true; + if (pagView) { + pagView.destroy(); + } + if (pagFile?.destroy) { + pagFile.destroy(); + } + }; + }, [src]); + + return ( + + + {failed ? PAG 预览失败 : null} + + ); +} + +async function getPAGModule() { + if (!pagModulePromise) { + pagModulePromise = import("libpag").then(({ PAGInit }) => PAGInit({ locateFile: () => pagWasmURL })); + } + return pagModulePromise; +} + +function getDisplayValue(value) { + if (!value) { + return ""; + } + const raw = String(value); + try { + const url = new URL(raw); + const name = url.pathname.split("/").filter(Boolean).pop(); + return name ? decodeURIComponent(name) : raw; + } catch { + return raw; + } +} + +function getAssetKind(value, kind, contentType = "") { + const extension = getExtension(value); + if (extension === "svga") { + return "svga"; + } + if (extension === "pag") { + return "pag"; + } + const normalizedType = String(contentType || "").toLowerCase(); + if (normalizedType.includes("svga")) { + return "svga"; + } + if (normalizedType.includes("pag")) { + return "pag"; + } + return kind === "image" ? "image" : "file"; +} + +function getExtension(value) { + if (!value) { + return ""; + } + const raw = String(value); + const pathname = safePathname(raw); + const name = pathname.split("/").filter(Boolean).pop() || raw; + const cleanName = name.split("?")[0].split("#")[0]; + const dotIndex = cleanName.lastIndexOf("."); + return dotIndex >= 0 ? cleanName.slice(dotIndex + 1).toLowerCase() : ""; +} + +function safePathname(value) { + try { + return new URL(value).pathname; + } catch { + return value; + } +} + +function assetKindLabel(assetKind, isImage) { + if (assetKind === "svga") { + return "SVGA"; + } + if (assetKind === "pag") { + return "PAG"; + } + return isImage ? "图片" : "文件"; +} diff --git a/src/shared/ui/UploadField.module.css b/src/shared/ui/UploadField.module.css new file mode 100644 index 0000000..c8a4511 --- /dev/null +++ b/src/shared/ui/UploadField.module.css @@ -0,0 +1,176 @@ +.root { + display: grid; + width: 100%; + gap: var(--space-2); +} + +.disabled { + opacity: 0.72; +} + +.input { + display: none; +} + +.header { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-3); +} + +.label { + color: var(--text-secondary); + font-weight: 650; +} + +.type { + color: var(--text-tertiary); + font-size: var(--admin-font-size); +} + +.panel { + overflow: hidden; + border: 1px solid var(--border); + border-radius: var(--radius-control); + background: var(--bg-input); + backdrop-filter: var(--glass-blur); + -webkit-backdrop-filter: var(--glass-blur); +} + +.preview { + position: relative; + display: flex; + width: 100%; + height: 168px; + align-items: center; + justify-content: center; + overflow: hidden; + border: 0; + border-bottom: 1px solid var(--border); + background: + linear-gradient(45deg, rgba(100, 116, 139, 0.08) 25%, transparent 25%) 0 0 / 16px 16px, + linear-gradient(45deg, transparent 75%, rgba(100, 116, 139, 0.08) 75%) 0 0 / 16px 16px, + linear-gradient(45deg, transparent 75%, rgba(100, 116, 139, 0.08) 75%) 8px 8px / 16px 16px, + linear-gradient(45deg, rgba(100, 116, 139, 0.08) 25%, transparent 25%) 8px 8px / 16px 16px, + var(--bg-card-strong); + color: var(--text-tertiary); + cursor: pointer; +} + +.preview:disabled { + cursor: not-allowed; +} + +.image { + width: 100%; + height: 100%; + object-fit: contain; +} + +.empty { + display: inline-flex; + min-width: 0; + align-items: center; + justify-content: center; + gap: var(--space-2); + color: var(--text-tertiary); + font-weight: 650; +} + +.empty svg { + width: 28px; + height: 28px; +} + +.overlay { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + background: rgba(255, 255, 255, 0.68); + color: var(--primary); + backdrop-filter: blur(6px); + -webkit-backdrop-filter: blur(6px); +} + +.player { + position: relative; + display: flex; + width: 100%; + height: 100%; + align-items: center; + justify-content: center; +} + +.playerSurface { + width: 100%; + height: 100%; +} + +.playerSurface canvas, +.pagCanvas { + width: 100% !important; + height: 100% !important; +} + +.pagCanvas { + display: block; +} + +.footer { + display: flex; + min-height: 52px; + align-items: center; + justify-content: space-between; + gap: var(--space-3); + padding: var(--space-2) var(--space-3); +} + +.name { + min-width: 0; + overflow: hidden; + color: var(--text-secondary); + text-overflow: ellipsis; + white-space: nowrap; +} + +.actions { + display: inline-flex; + flex: 0 0 auto; + align-items: center; + gap: var(--space-2); +} + +.action { + display: inline-flex; + height: var(--control-height); + min-width: var(--control-height); + align-items: center; + justify-content: center; + gap: var(--space-1); + padding: 0 var(--space-3); + border: 1px solid var(--border); + border-radius: var(--radius-control); + background: var(--bg-input-strong); + color: var(--text-secondary); + cursor: pointer; + font-size: var(--admin-font-size); + line-height: var(--admin-line-height); + transition: + border-color var(--motion-fast) var(--ease-standard), + color var(--motion-fast) var(--ease-standard), + background var(--motion-fast) var(--ease-standard); +} + +.action:hover:not(:disabled) { + border-color: var(--primary-border); + background: var(--primary-hover); + color: var(--primary); +} + +.action:disabled { + cursor: not-allowed; + opacity: 0.48; +} diff --git a/src/shared/ui/selectAllBehavior.js b/src/shared/ui/selectAllBehavior.js new file mode 100644 index 0000000..11b04dc --- /dev/null +++ b/src/shared/ui/selectAllBehavior.js @@ -0,0 +1,16 @@ +export function restorePlaceholderAfterAllSelected(label) { + if (!isAllLabel(label)) { + return; + } + + globalThis.requestAnimationFrame?.(() => { + const activeElement = globalThis.document?.activeElement; + if (activeElement && typeof activeElement.blur === "function") { + activeElement.blur(); + } + }); +} + +export function isAllLabel(label) { + return String(label || "").trim().startsWith("全部"); +} diff --git a/src/shared/utils/cssTokens.js b/src/shared/utils/cssTokens.js new file mode 100644 index 0000000..98dd5f3 --- /dev/null +++ b/src/shared/utils/cssTokens.js @@ -0,0 +1,7 @@ +export function readCssToken(name, fallback = "") { + if (typeof window === "undefined") { + return fallback; + } + + return getComputedStyle(document.documentElement).getPropertyValue(name).trim() || fallback; +} diff --git a/src/shared/utils/time.js b/src/shared/utils/time.js new file mode 100644 index 0000000..3ea9cd3 --- /dev/null +++ b/src/shared/utils/time.js @@ -0,0 +1,22 @@ +export function formatTime(date = new Date()) { + return date.toLocaleTimeString("zh-CN", { + hour12: false, + hour: "2-digit", + minute: "2-digit", + second: "2-digit" + }); +} + +export function formatDateTime(value) { + if (!value) { + return "-"; + } + return new Date(value).toLocaleString("zh-CN", { hour12: false }); +} + +export function formatMillis(value) { + if (!value) { + return "-"; + } + return formatDateTime(Number(value)); +} diff --git a/src/styles/app.css b/src/styles/app.css index dabc836..95619b1 100644 --- a/src/styles/app.css +++ b/src/styles/app.css @@ -1,2333 +1,32 @@ -.app-shell { - display: grid; - grid-template-rows: var(--header-height) 1fr; - min-height: 100vh; - background: var(--bg-page); -} - -@keyframes page-enter { - from { - opacity: 0; - transform: translateY(10px) scale(0.995); - } - to { - opacity: 1; - transform: translateY(0) scale(1); - } -} - -@keyframes panel-enter { - from { - opacity: 0; - transform: translateY(12px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -@keyframes row-enter { - from { - opacity: 0; - transform: translateX(-8px); - } - to { - opacity: 1; - transform: translateX(0); - } -} - -@keyframes shimmer { - from { - background-position: 120% 0; - } - to { - background-position: -120% 0; - } -} - -@keyframes notice-pop { - 0%, - 100% { - transform: scale(1); - } - 45% { - transform: scale(1.18); - } -} - -@keyframes progress-fill { - from { - transform: scaleX(0); - } - to { - transform: scaleX(1); - } -} - -.header { - display: grid; - grid-template-columns: 248px 1fr; - height: var(--header-height); - border-bottom: 1px solid var(--border); - background: var(--bg-header); - transition: grid-template-columns var(--motion-slow) var(--ease-emphasized); -} - -.brand { - display: flex; - align-items: center; - gap: 14px; - height: var(--header-height); - padding: 0 22px; - border-right: 1px solid var(--border); - background: var(--bg-sidebar); -} - -.brand-mark { - display: grid; - width: 38px; - height: 38px; - place-items: center; - border-radius: 8px; - background: linear-gradient(135deg, var(--primary), var(--brand-secondary)); - color: var(--active-contrast); - transition: - transform var(--motion-base) var(--ease-emphasized), - box-shadow var(--motion-base) var(--ease-standard); -} - -.brand:hover .brand-mark { - box-shadow: 0 10px 24px rgba(37, 99, 235, 0.22); - transform: rotate(-6deg) scale(1.05); -} - -.brand-title { - display: block; - color: var(--text-primary); - font-size: 16px; - font-weight: 650; -} - -.brand-meta { - display: block; - margin-top: 2px; - color: var(--text-tertiary); - font-size: 12px; -} - -.header-tools { - display: flex; - min-width: 0; - align-items: center; - justify-content: space-between; - gap: 16px; - padding: 0 24px; -} - -.header-left-tools, -.header-right-tools { - display: flex; - min-width: 0; - align-items: center; - gap: 12px; -} - -.header-right-tools { - margin-left: auto; - justify-content: flex-end; -} - -.header-location { - display: inline-flex; - min-width: 74px; - height: 36px; - align-items: center; - justify-content: center; - padding: 0 16px; - border: 1px solid rgba(37, 99, 235, 0.24); - border-radius: 8px; - background: rgba(37, 99, 235, 0.08); - box-shadow: - var(--shadow-soft), - inset 0 -1px 0 rgba(37, 99, 235, 0.22); - color: var(--text-primary); - font-size: 15px; - font-weight: 650; - white-space: nowrap; -} - -.search-box { - display: flex; - flex: 1 1 420px; - min-width: 180px; - max-width: 520px; - height: 38px; - align-items: center; - gap: 10px; - padding: 0 12px; - border: 1px solid var(--border); - border-radius: 8px; - background: var(--bg-input); - color: var(--text-tertiary); - transition: - border-color var(--motion-base) var(--ease-standard), - background var(--motion-base) var(--ease-standard), - box-shadow var(--motion-base) var(--ease-standard), - transform var(--motion-base) var(--ease-emphasized); -} - -.search-box:focus-within { - border-color: rgba(37, 99, 235, 0.78); - background: var(--bg-input-strong); - box-shadow: 0 0 0 3px var(--focus-ring); - transform: translateY(-1px); -} - -.search-box svg { - transition: - color var(--motion-base) var(--ease-standard), - transform var(--motion-base) var(--ease-emphasized); -} - -.search-box:focus-within svg { - color: var(--primary); - transform: scale(1.08); -} - -.search-trigger { - display: flex; - width: min(360px, 34vw); - min-width: 260px; - height: 38px; - align-items: center; - gap: 10px; - padding: 0 12px; - border: 1px solid var(--border); - border-radius: 8px; - background: var(--bg-input); - color: var(--text-tertiary); - cursor: pointer; - text-align: left; - transition: - border-color var(--motion-base) var(--ease-standard), - background var(--motion-base) var(--ease-standard), - box-shadow var(--motion-base) var(--ease-standard), - color var(--motion-base) var(--ease-standard), - transform var(--motion-base) var(--ease-emphasized); -} - -.search-trigger span { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.search-trigger:hover, -.search-trigger:focus-visible { - border-color: rgba(37, 99, 235, 0.78); - background: var(--bg-input-strong); - box-shadow: 0 0 0 3px var(--focus-ring); - color: var(--text-primary); - transform: translateY(-1px); -} - -.search-box input { - width: 100%; - min-width: 0; - border: 0; - background: transparent; - color: var(--text-primary); - outline: 0; -} - -.search-box input::placeholder { - color: var(--text-tertiary); -} - -.time-select { - width: 116px; - height: 38px; - border: 1px solid var(--border); - border-radius: 8px; - background: var(--bg-card); - color: var(--text-secondary); -} - -.icon-button { - display: inline-grid; - width: 38px; - height: 38px; - place-items: center; - border: 1px solid var(--border); - border-radius: 8px; - background: var(--bg-card); - color: var(--text-secondary); - cursor: pointer; - transition: - background var(--motion-fast) var(--ease-standard), - color var(--motion-fast) var(--ease-standard), - border-color var(--motion-fast) var(--ease-standard), - box-shadow var(--motion-fast) var(--ease-standard), - transform var(--motion-base) var(--ease-emphasized); -} - -.icon-button:hover { - border-color: rgba(37, 99, 235, 0.7); - background: var(--bg-hover); - box-shadow: var(--shadow-soft); - color: var(--text-primary); - transform: translateY(-1px); -} - -.icon-button:active { - box-shadow: none; - transform: translateY(0) scale(0.96); -} - -.icon-button--notice { - position: relative; -} - -.icon-button--notice span { - position: absolute; - top: -5px; - right: -5px; - display: grid; - min-width: 17px; - height: 17px; - place-items: center; - border-radius: 999px; - background: var(--danger); - color: var(--active-contrast); - font-size: 10px; - font-weight: 700; - animation: notice-pop 1500ms var(--ease-emphasized) infinite; -} - -.user-pill { - display: flex; - height: 38px; - align-items: center; - gap: 8px; - padding: 0 12px; - border: 1px solid var(--border); - border-radius: 8px; - background: transparent; - color: var(--text-secondary); - cursor: pointer; - font-size: 13px; - transition: - border-color var(--motion-base) var(--ease-standard), - background var(--motion-base) var(--ease-standard), - color var(--motion-base) var(--ease-standard); -} - -.user-pill:hover { - border-color: rgba(37, 99, 235, 0.52); - background: rgba(37, 99, 235, 0.08); - color: var(--text-primary); -} - -.user-pill:active { - transform: scale(0.98); -} - -.user-menu .MuiPaper-root { - min-width: 132px; - margin-top: 8px; -} - -.user-menu__item { - display: flex; - gap: 8px; - min-height: 38px; - color: var(--text-secondary); - font-size: 13px; -} - -.user-menu__item:hover { - color: var(--text-primary); -} - -.body-grid { - display: grid; - grid-template-columns: 248px 1fr; - min-height: 0; - transition: grid-template-columns var(--motion-slow) var(--ease-emphasized); -} - -.sidebar { - position: relative; - z-index: 25; - min-height: 0; - overflow: visible; - padding: 16px 12px; - border-right: 1px solid var(--border); - background: var(--bg-sidebar); -} - -.app-shell--sidebar-collapsed .header, -.app-shell--sidebar-collapsed .body-grid { - grid-template-columns: 84px 1fr; -} - -.app-shell--sidebar-collapsed .brand { - gap: 0; - justify-content: center; - padding: 0; -} - -.brand-text, -.nav-label { - display: inline-grid; - min-width: 0; - max-width: 160px; - opacity: 1; - overflow: hidden; - transform: translateX(0); - transition: - max-width var(--motion-slow) var(--ease-emphasized), - opacity var(--motion-base) var(--ease-standard), - transform var(--motion-base) var(--ease-emphasized); -} - -.app-shell--sidebar-collapsed .brand-text, -.sidebar--collapsed .nav-label { - max-width: 0; - opacity: 0; - pointer-events: none; - transform: translateX(-6px); -} - -.sidebar--collapsed { - padding: 16px 10px; -} - -.sidebar--collapsed .nav-item { - width: 44px; - justify-content: center; - justify-self: center; - padding: 0; -} - -.nav-list { - display: grid; - gap: 6px; -} - -.nav-group { - display: grid; - position: relative; - gap: 4px; -} - -.nav-item { - position: relative; - display: flex; - width: 100%; - height: 44px; - align-items: center; - gap: 10px; - padding: 0 12px; - border-radius: 8px; - background: transparent; - color: var(--text-secondary); - cursor: pointer; - overflow: hidden; - transition: - background var(--motion-base) var(--ease-standard), - color var(--motion-base) var(--ease-standard), - gap var(--motion-slow) var(--ease-emphasized), - padding var(--motion-slow) var(--ease-emphasized), - width var(--motion-slow) var(--ease-emphasized), - transform var(--motion-base) var(--ease-emphasized); -} - -.nav-item--parent { - padding-right: 10px; -} - -.nav-item--open:not(.nav-item--active) { - background: rgba(37, 99, 235, 0.06); - color: var(--text-primary); -} - -.nav-expand { - margin-left: auto; - color: var(--text-tertiary); - transition: - color var(--motion-base) var(--ease-standard), - transform var(--motion-base) var(--ease-emphasized); -} - -.nav-item--open .nav-expand, -.nav-item:hover .nav-expand { - color: currentColor; -} - -.nav-item--expanded .nav-expand { - transform: rotate(180deg); -} - -.nav-children { - display: grid; - grid-template-rows: 0fr; - opacity: 0; - overflow: hidden; - padding-left: 20px; - pointer-events: none; - transform: translateY(-4px); - transition: - grid-template-rows var(--motion-slow) var(--ease-emphasized), - opacity var(--motion-base) var(--ease-standard), - transform var(--motion-slow) var(--ease-emphasized); -} - -.nav-children--open { - grid-template-rows: 1fr; - opacity: 1; - pointer-events: auto; - transform: translateY(0); -} - -.nav-children__inner { - display: grid; - gap: 4px; - min-height: 0; - overflow: hidden; -} - -.nav-item--child { - height: 38px; - padding-left: 12px; - font-size: 13px; -} - -.nav-item--child svg { - font-size: 18px; -} - -.sidebar--collapsed .nav-children, -.sidebar--collapsed .nav-item--parent .nav-expand { - display: none !important; -} - -.sidebar--collapsed .nav-item--open, -.sidebar--collapsed .nav-item--open:hover, -.sidebar--collapsed .nav-item--flyout-open, -.sidebar--collapsed .nav-item--flyout-open:hover { - background: linear-gradient(90deg, rgba(37, 99, 235, 0.96), rgba(14, 165, 233, 0.34)); - box-shadow: inset 0 0 0 1px rgba(14, 165, 233, 0.2); - color: var(--active-contrast); -} - -.nav-flyout { - position: absolute; - top: 0; - left: calc(100% + 12px); - z-index: 30; - display: grid; - width: 168px; - gap: 4px; - padding: 8px; - border: 1px solid rgba(37, 99, 235, 0.22); - border-radius: 8px; - background: var(--bg-flyout); - box-shadow: var(--shadow-panel); - animation: panel-enter var(--motion-base) var(--ease-emphasized) both; -} - -.nav-flyout::before { - position: absolute; - top: 16px; - left: -6px; - width: 10px; - height: 10px; - border-bottom: 1px solid rgba(37, 99, 235, 0.22); - border-left: 1px solid rgba(37, 99, 235, 0.22); - background: var(--bg-flyout); - content: ""; - transform: rotate(45deg); -} - -.nav-flyout__title { - padding: 4px 8px 6px; - color: var(--text-tertiary); - font-size: 12px; - font-weight: 650; -} - -.nav-flyout__item { - display: flex; - height: 38px; - align-items: center; - gap: 10px; - padding: 0 10px; - border-radius: 8px; - background: transparent; - color: var(--text-secondary); - cursor: pointer; - font-size: 13px; - text-align: left; - transition: - background var(--motion-fast) var(--ease-standard), - color var(--motion-fast) var(--ease-standard), - transform var(--motion-base) var(--ease-emphasized); -} - -.nav-flyout__item:hover, -.nav-flyout__item--active { - background: rgba(37, 99, 235, 0.1); - color: var(--text-primary); - transform: translateX(2px); -} - -.nav-item::before { - position: absolute; - inset: 8px auto 8px 0; - width: 3px; - border-radius: 0 999px 999px 0; - background: var(--primary); - content: ""; - opacity: 0; - transform: translateX(-4px); - transition: - opacity var(--motion-base) var(--ease-standard), - transform var(--motion-base) var(--ease-emphasized); -} - -.nav-item:hover { - background: var(--bg-hover); - color: var(--text-primary); - transform: translateX(2px); -} - -.nav-item--active, -.nav-item--active:hover { - background: linear-gradient(90deg, rgba(37, 99, 235, 0.96), rgba(14, 165, 233, 0.34)); - box-shadow: inset 0 0 0 1px rgba(14, 165, 233, 0.2); - color: var(--active-contrast); -} - -.nav-item:hover::before, -.nav-item--active::before { - opacity: 1; - transform: translateX(0); -} - -.nav-label { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.workspace { - position: relative; - min-width: 0; - min-height: 0; - overflow: auto; - padding: 24px; -} - -.page-transition { - animation: page-enter var(--motion-slow) var(--ease-emphasized) both; -} - -.page-skeleton { - display: grid; - height: calc(100vh - var(--header-height) - 48px); - height: calc(100dvh - var(--header-height) - 48px); - grid-template-rows: 38px minmax(96px, 0.7fr) minmax(160px, 1fr) minmax(200px, 1.2fr); - gap: 16px; - animation: page-enter var(--motion-slow) var(--ease-emphasized) both; -} - -.page-skeleton__head { - display: flex; - align-items: center; - justify-content: space-between; - gap: 16px; -} - -.page-skeleton__title { - width: min(220px, 36%); - height: 30px; -} - -.page-skeleton__actions { - display: flex; - gap: 10px; -} - -.page-skeleton__actions .page-skeleton__block { - width: 96px; - height: 36px; -} - -.page-skeleton__kpis { - display: grid; - grid-template-columns: repeat(5, minmax(0, 1fr)); - gap: 16px; -} - -.page-skeleton__charts { - display: grid; - grid-template-columns: minmax(280px, 1.15fr) repeat(2, minmax(260px, 1fr)) minmax(260px, 0.95fr); - gap: 16px; - min-height: 0; -} - -.page-skeleton__content { - display: grid; - grid-template-columns: 280px minmax(0, 1fr); - gap: 16px; - min-height: 0; -} - -.page-skeleton__block { - position: relative; - width: 100%; - height: 100%; - overflow: hidden; - border: 1px solid var(--border); - border-radius: 10px; - background-color: var(--skeleton-surface); - opacity: 1; - transform: translateZ(0); - animation: panel-enter var(--motion-slow) var(--ease-emphasized) both; -} - -.page-skeleton__block::after { - background: linear-gradient(90deg, transparent, var(--skeleton-shine), transparent); -} - -.page-skeleton__kpi:nth-child(2), -.page-skeleton__charts .page-skeleton__block:nth-child(2), -.page-skeleton__content .page-skeleton__block:nth-child(2) { - animation-delay: 40ms; -} - -.page-skeleton__kpi:nth-child(3), -.page-skeleton__charts .page-skeleton__block:nth-child(3) { - animation-delay: 80ms; -} - -.page-skeleton__kpi:nth-child(4), -.page-skeleton__charts .page-skeleton__block:nth-child(4) { - animation-delay: 120ms; -} - -.page-skeleton__kpi:nth-child(5) { - animation-delay: 160ms; -} - -.search-dialog { - overflow: hidden; - border: 1px solid var(--border); - border-radius: 12px; - background: var(--bg-card); - background-image: none; - box-shadow: var(--shadow-dialog); - animation: panel-enter var(--motion-slow) var(--ease-emphasized) both; -} - -.search-dialog__head { - padding: 18px; - border-bottom: 1px solid var(--border); - background: var(--bg-card-strong); -} - -.search-dialog__input { - max-width: none; - width: 100%; - height: 44px; -} - -.search-dialog__list { - display: grid; - gap: 6px; - max-height: min(56vh, 520px); - overflow: auto; - padding: 10px; -} - -.search-dialog__item { - min-height: 64px; - border: 1px solid transparent; - border-radius: 10px; - color: var(--text-primary); - transition: - background var(--motion-base) var(--ease-standard), - border-color var(--motion-base) var(--ease-standard), - transform var(--motion-base) var(--ease-emphasized); -} - -.search-dialog__item:hover { - border-color: rgba(37, 99, 235, 0.34); - background: rgba(37, 99, 235, 0.08); - transform: translateX(3px); -} - -.search-dialog__primary { - color: var(--text-primary); - font-size: 14px; - font-weight: 650; -} - -.search-dialog__secondary { - color: var(--text-tertiary); - font-size: 12px; -} - -.search-dialog__empty { - display: grid; - min-height: 120px; - place-items: center; - color: var(--text-tertiary); - font-size: 13px; -} - -.page-head { - display: flex; - align-items: flex-end; - justify-content: space-between; - gap: 16px; - margin-bottom: 18px; -} - -.page-title { - margin: 0; - font-size: 20px; - font-weight: 700; -} - -.page-meta { - margin: 6px 0 0; - color: var(--text-tertiary); - font-size: 12px; -} - -.page-actions { - display: flex; - align-items: center; - gap: 10px; -} - -.button { - display: inline-flex; - height: 36px; - align-items: center; - justify-content: center; - gap: 8px; - padding: 0 12px; - border: 1px solid var(--border); - border-radius: 8px; - background: var(--bg-card); - color: var(--text-secondary); - cursor: pointer; - font-size: 13px; - white-space: nowrap; - transition: - background var(--motion-fast) var(--ease-standard), - border-color var(--motion-fast) var(--ease-standard), - box-shadow var(--motion-fast) var(--ease-standard), - color var(--motion-fast) var(--ease-standard), - transform var(--motion-base) var(--ease-emphasized); -} - -.button:hover { - border-color: rgba(37, 99, 235, 0.7); - background: var(--bg-hover); - box-shadow: var(--shadow-soft); - color: var(--text-primary); - transform: translateY(-1px); -} - -.button:active { - box-shadow: none; - transform: translateY(0) scale(0.985); -} - -.button:disabled, -.action-button:disabled { - cursor: not-allowed; - opacity: 0.42; -} - -.button--primary { - border-color: var(--primary); - background: var(--primary); - color: var(--active-contrast); -} - -.button--success { - color: var(--success); -} - -.button--danger { - color: var(--danger); -} - -.kpi-grid { - display: grid; - grid-template-columns: repeat(5, minmax(0, 1fr)); - gap: 16px; -} - -.card { - border: 1px solid var(--border); - border-radius: 10px; - background: var(--bg-card); -} - -.kpi-card { - min-height: 132px; - padding: 20px; - animation: panel-enter var(--motion-slow) var(--ease-emphasized) both; - transition: - border-color var(--motion-base) var(--ease-standard), - box-shadow var(--motion-base) var(--ease-standard), - transform var(--motion-base) var(--ease-emphasized); -} - -.kpi-grid .kpi-card:nth-child(2), -.service-kpi-row .kpi-card:nth-child(2) { - animation-delay: 40ms; -} - -.kpi-grid .kpi-card:nth-child(3), -.service-kpi-row .kpi-card:nth-child(3) { - animation-delay: 80ms; -} - -.kpi-grid .kpi-card:nth-child(4), -.service-kpi-row .kpi-card:nth-child(4) { - animation-delay: 120ms; -} - -.kpi-grid .kpi-card:nth-child(5), -.service-kpi-row .chart-card { - animation-delay: 160ms; -} - -.kpi-card:hover, -.chart-card:hover, -.table-card:hover, -.server-rail:hover, -.inline-inspector:hover { - border-color: rgba(37, 99, 235, 0.34); - box-shadow: var(--shadow-panel); - transform: translateY(-2px); -} - -.kpi-card__top { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; -} - -.kpi-card__label, -.card-title { - color: var(--text-secondary); - font-size: 14px; - font-weight: 650; -} - -.metric-icon { - display: grid; - width: 34px; - height: 34px; - place-items: center; - border-radius: 8px; - background: rgba(37, 99, 235, 0.1); - color: var(--primary); - transition: - background var(--motion-base) var(--ease-standard), - transform var(--motion-base) var(--ease-emphasized); -} - -.kpi-card:hover .metric-icon { - transform: scale(1.08) rotate(-4deg); -} - -.metric-icon--success { - background: rgba(22, 163, 74, 0.1); - color: var(--success); -} - -.metric-icon--warning { - background: rgba(217, 119, 6, 0.1); - color: var(--warning); -} - -.metric-icon--info { - background: rgba(2, 132, 199, 0.1); - color: var(--info); -} - -.kpi-value { - margin-top: 18px; - color: var(--text-primary); - font-size: 32px; - font-weight: 760; - line-height: 1; -} - -.kpi-value span { - margin-left: 5px; - font-size: 14px; - font-weight: 600; -} - -.kpi-sub { - margin-top: 12px; - color: var(--text-tertiary); - font-size: 12px; -} - -.status-ok { - color: var(--success); -} - -.status-warn { - color: var(--warning); -} - -.status-danger { - color: var(--danger); -} - -.panel-grid { - display: grid; - grid-template-columns: minmax(0, 1.35fr) minmax(340px, 0.65fr); - gap: 16px; - margin-top: 16px; -} - -.overview-charts { - display: grid; - grid-template-columns: minmax(280px, 1.15fr) repeat(2, minmax(260px, 1fr)) minmax(260px, 0.95fr); - gap: 16px; - margin-top: 16px; -} - -.chart-card { - min-height: 304px; - padding: 20px; - animation: panel-enter var(--motion-slow) var(--ease-emphasized) both; - transition: - border-color var(--motion-base) var(--ease-standard), - box-shadow var(--motion-base) var(--ease-standard), - transform var(--motion-base) var(--ease-emphasized); -} - -.chart-card--compact { - min-height: 150px; -} - -.chart-card--compact .line-chart { - height: 78px; -} - -.chart-card--compact .card-head { - margin-bottom: 6px; -} - -.chart-card--compact .segmented { - display: none; -} - -.card-head { - display: flex; - align-items: center; - justify-content: space-between; - gap: 14px; - margin-bottom: 18px; -} - -.segmented { - display: inline-flex; - height: 30px; - padding: 2px; - border: 1px solid var(--border); - border-radius: 8px; - background: var(--bg-card-strong); -} - -.segment { - min-width: 52px; - padding: 0 10px; - border-radius: 6px; - background: transparent; - color: var(--text-tertiary); - cursor: pointer; - font-size: 12px; - transition: - background var(--motion-fast) var(--ease-standard), - color var(--motion-fast) var(--ease-standard), - transform var(--motion-base) var(--ease-emphasized); -} - -.segment:hover { - transform: translateY(-1px); -} - -.segment--active { - background: rgba(37, 99, 235, 0.1); - color: var(--text-primary); -} - -.line-chart { - width: 100%; - height: 216px; - opacity: 0; - animation: panel-enter 420ms var(--ease-emphasized) 120ms both; -} - -.echart { - width: 100%; - min-width: 0; -} - -.line-chart__grid { - stroke: rgba(100, 116, 139, 0.16); - stroke-width: 1; -} - -.line-chart__area { - opacity: 0.18; -} - -.line-chart__line { - fill: none; - stroke-width: 2; - stroke-linecap: round; - stroke-linejoin: round; -} - -.donut-wrap { - display: grid; - grid-template-columns: 140px 1fr; - align-items: center; - gap: 22px; - min-height: 216px; -} - -.donut-echart { - width: 140px; - height: 140px; - opacity: 0; - animation: panel-enter 420ms var(--ease-emphasized) 120ms both; -} - -.donut { - display: grid; - width: 140px; - height: 140px; - place-items: center; - border-radius: 50%; -} - -.donut-core { - display: grid; - width: 78px; - height: 78px; - place-items: center; - border-radius: 50%; - background: var(--bg-card); - color: var(--text-secondary); - font-size: 12px; -} - -.donut-core strong { - display: block; - color: var(--text-primary); - font-size: 24px; - line-height: 1; -} - -.donut-legend { - display: grid; - gap: 14px; -} - -.legend-row { - display: grid; - grid-template-columns: 8px 1fr auto; - align-items: center; - gap: 10px; - color: var(--text-secondary); - font-size: 13px; -} - -.legend-row strong { - color: var(--text-primary); -} - -.legend-dot { - width: 8px; - height: 8px; - border-radius: 50%; -} - -.legend-dot--success { - background: var(--success); -} - -.legend-dot--danger { - background: var(--danger); -} - -.legend-dot--stopped { - background: var(--stopped); -} - -.bar-chart { - display: block; - width: 100%; - height: 216px; - opacity: 0; - animation: panel-enter 420ms var(--ease-emphasized) 120ms both; -} - -.alert-list { - display: grid; - gap: 12px; -} - -.alert-item { - display: grid; - grid-template-columns: 8px 1fr auto; - align-items: center; - gap: 12px; - min-height: 42px; - animation: row-enter var(--motion-slow) var(--ease-emphasized) both; - transition: - background var(--motion-base) var(--ease-standard), - transform var(--motion-base) var(--ease-emphasized); -} - -.alert-item:nth-child(2) { - animation-delay: 50ms; -} - -.alert-item:nth-child(3) { - animation-delay: 100ms; -} - -.alert-item:hover { - transform: translateX(2px); -} - -.alert-dot { - width: 8px; - height: 8px; - border-radius: 50%; -} - -.alert-dot--danger { - background: var(--danger); -} - -.alert-dot--warning { - background: var(--warning); -} - -.alert-dot--info { - background: var(--info); -} - -.alert-name { - color: var(--text-primary); - font-size: 13px; - font-weight: 620; -} - -.alert-desc, -.alert-time { - color: var(--text-tertiary); - font-size: 12px; -} - -.table-card { - margin-top: 16px; - overflow: hidden; - animation: panel-enter var(--motion-slow) var(--ease-emphasized) both; - transition: - border-color var(--motion-base) var(--ease-standard), - box-shadow var(--motion-base) var(--ease-standard), - transform var(--motion-base) var(--ease-emphasized); -} - -.split-grid { - display: grid; - grid-template-columns: 280px minmax(0, 1fr); - gap: 16px; - margin-top: 16px; -} - -.server-rail { - overflow: hidden; - animation: panel-enter var(--motion-slow) var(--ease-emphasized) both; - transition: - border-color var(--motion-base) var(--ease-standard), - box-shadow var(--motion-base) var(--ease-standard), - transform var(--motion-base) var(--ease-emphasized); -} - -.server-list { - display: grid; - gap: 0; - padding: 8px; -} - -.server-item { - display: grid; - grid-template-columns: 8px 1fr auto; - align-items: center; - gap: 12px; - width: 100%; - min-height: 64px; - padding: 10px 12px; - border-radius: 8px; - background: transparent; - color: var(--text-primary); - cursor: pointer; - text-align: left; - animation: row-enter var(--motion-slow) var(--ease-emphasized) both; - transition: - background var(--motion-base) var(--ease-standard), - transform var(--motion-base) var(--ease-emphasized); -} - -.server-item:hover { - background: var(--bg-hover); - transform: translateX(3px); -} - -.server-item:nth-child(2) { - animation-delay: 40ms; -} - -.server-item:nth-child(3) { - animation-delay: 80ms; -} - -.server-item:nth-child(4) { - animation-delay: 120ms; -} - -.server-item strong, -.server-item small { - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.server-item small { - margin-top: 4px; - color: var(--text-tertiary); - font-size: 12px; -} - -.server-status { - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--stopped); -} - -.server-status--running { - background: var(--success); -} - -.server-status--warning { - background: var(--warning); -} - -.server-status--error { - background: var(--danger); -} - -.link-button { - background: transparent; - color: var(--primary); - cursor: pointer; - font-size: 12px; -} - -.alert-strip { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 16px; - margin-top: 16px; - padding-bottom: 8px; -} - -.table-toolbar { - display: flex; - align-items: center; - justify-content: space-between; - gap: 14px; - padding: 18px 20px; - border-bottom: 1px solid var(--border); -} - -.filter-row { - display: flex; - align-items: center; - gap: 8px; -} - -.filter-chip { - height: 30px; - padding: 0 10px; - border: 1px solid var(--border); - border-radius: 999px; - background: transparent; - color: var(--text-tertiary); - cursor: pointer; - font-size: 12px; - transition: - background var(--motion-fast) var(--ease-standard), - border-color var(--motion-fast) var(--ease-standard), - box-shadow var(--motion-fast) var(--ease-standard), - color var(--motion-fast) var(--ease-standard), - transform var(--motion-base) var(--ease-emphasized); -} - -.filter-chip:hover { - box-shadow: var(--shadow-soft); - transform: translateY(-1px); -} - -.filter-chip--active { - border-color: rgba(37, 99, 235, 0.7); - background: rgba(37, 99, 235, 0.1); - color: var(--text-primary); -} - -.service-kpi-row { - display: grid; - grid-template-columns: repeat(4, minmax(170px, 1fr)); - gap: 16px; -} - -.service-kpi-row .kpi-card { - min-height: 150px; -} - -.filters-panel { - display: flex; - align-items: center; - gap: 14px; - margin-top: 16px; -} - -.filters-panel .search-box { - flex: 0 0 280px; -} - -.service-page-grid { - display: grid; - grid-template-columns: minmax(0, 1fr) 320px; - gap: 16px; - margin-top: 12px; -} - -.service-page-grid .table-card { - margin-top: 0; -} - -.user-kpi-row { - display: grid; - grid-template-columns: repeat(4, minmax(170px, 1fr)); - gap: 16px; -} - -.user-filters-panel { - flex-wrap: wrap; -} - -.user-filters-panel .search-box { - flex: 0 0 320px; -} - -.user-table-card { - margin-top: 16px; -} - -.user-table { - min-width: 1040px; -} - -.user-row { - display: grid; - grid-template-columns: 1.4fr 1fr 1fr 0.72fr 0.72fr 0.9fr 0.72fr; - align-items: center; - min-height: 64px; - padding: 0 20px; - border-bottom: 1px solid var(--row-border); - color: var(--text-secondary); - font-size: 13px; - animation: row-enter var(--motion-slow) var(--ease-emphasized) both; - transition: - background var(--motion-base) var(--ease-standard), - border-color var(--motion-base) var(--ease-standard), - transform var(--motion-base) var(--ease-emphasized); -} - -.user-table--selectable { - min-width: 1180px; -} - -.user-table--selectable .user-row { - grid-template-columns: 48px 1.4fr 1fr 1fr 0.72fr 0.72fr 0.9fr 1.2fr; -} - -.user-row--head { - min-height: 44px; - background: var(--bg-card-strong); - color: var(--text-tertiary); - font-size: 12px; - font-weight: 650; -} - -.user-row:not(.user-row--head):hover { - background: rgba(37, 99, 235, 0.06); - border-color: rgba(37, 99, 235, 0.2); - transform: translateX(3px); -} - -.user-row:nth-child(3) { - animation-delay: 35ms; -} - -.user-row:nth-child(4) { - animation-delay: 70ms; -} - -.user-row:nth-child(5) { - animation-delay: 105ms; -} - -.user-row:nth-child(6) { - animation-delay: 140ms; -} - -.user-cell { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.user-main { - display: flex; - min-width: 0; - align-items: center; - gap: 12px; -} - -.user-avatar { - display: grid; - flex: 0 0 auto; - width: 34px; - height: 34px; - place-items: center; - border-radius: 8px; - background: rgba(37, 99, 235, 0.1); - color: var(--primary); - transition: - background var(--motion-base) var(--ease-standard), - transform var(--motion-base) var(--ease-emphasized); -} - -.user-row:hover .user-avatar { - background: rgba(37, 99, 235, 0.14); - transform: scale(1.08); -} - -.service-table { - min-width: 980px; -} - -.service-row { - display: grid; - grid-template-columns: 2.1fr 0.85fr 1fr 1fr 0.9fr 1.35fr; - align-items: center; - min-height: 64px; - padding: 0 20px; - border-bottom: 1px solid var(--row-border); - font-size: 13px; - animation: row-enter var(--motion-slow) var(--ease-emphasized) both; - transition: - background var(--motion-base) var(--ease-standard), - border-color var(--motion-base) var(--ease-standard), - transform var(--motion-base) var(--ease-emphasized); -} - -.service-row--head { - min-height: 44px; - background: var(--bg-card-strong); - color: var(--text-tertiary); - font-size: 12px; - font-weight: 650; -} - -.service-row:not(.service-row--head) { - cursor: pointer; -} - -.service-row:not(.service-row--head):hover { - background: rgba(37, 99, 235, 0.06); - border-color: rgba(37, 99, 235, 0.2); - transform: translateX(3px); -} - -.service-row:nth-child(3) { - animation-delay: 35ms; -} - -.service-row:nth-child(4) { - animation-delay: 70ms; -} - -.service-row:nth-child(5) { - animation-delay: 105ms; -} - -.service-row:nth-child(6) { - animation-delay: 140ms; -} - -.service-row:nth-child(7) { - animation-delay: 175ms; -} - -.service-row:nth-child(8) { - animation-delay: 210ms; -} - -.service-cell { - min-width: 0; -} - -.service-main { - display: flex; - min-width: 0; - align-items: center; - gap: 12px; -} - -.service-icon { - display: grid; - flex: 0 0 auto; - width: 34px; - height: 34px; - place-items: center; - border-radius: 8px; - background: rgba(2, 132, 199, 0.1); - color: var(--info); - transition: - background var(--motion-base) var(--ease-standard), - transform var(--motion-base) var(--ease-emphasized); -} - -.service-row:hover .service-icon { - background: rgba(2, 132, 199, 0.14); - transform: scale(1.08); -} - -.service-name { - overflow: hidden; - color: var(--text-primary); - font-weight: 650; - text-overflow: ellipsis; - white-space: nowrap; -} - -.service-desc { - overflow: hidden; - margin-top: 4px; - color: var(--text-tertiary); - font-size: 12px; - text-overflow: ellipsis; - white-space: nowrap; -} - -.status-badge { - display: inline-flex; - height: 24px; - align-items: center; - gap: 7px; - padding: 0 10px; - border-radius: 999px; - background: rgba(100, 116, 139, 0.1); - color: var(--text-secondary); - font-size: 12px; - transition: - background var(--motion-base) var(--ease-standard), - border-color var(--motion-base) var(--ease-standard), - color var(--motion-base) var(--ease-standard), - transform var(--motion-base) var(--ease-emphasized); -} - -.status-badge:hover { - transform: translateY(-1px); -} - -.status-point { - width: 8px; - height: 8px; - border-radius: 50%; - transition: - background var(--motion-base) var(--ease-standard), - box-shadow var(--motion-base) var(--ease-standard), - transform var(--motion-base) var(--ease-emphasized); -} - -.status-badge:hover .status-point { - transform: scale(1.18); -} - -.status-badge--running .status-point { - background: var(--success); - box-shadow: 0 0 12px rgba(22, 163, 74, 0.28); -} - -.status-badge--running { - color: var(--success); -} - -.status-badge--warning .status-point { - background: var(--warning); - box-shadow: 0 0 12px rgba(217, 119, 6, 0.28); -} - -.status-badge--warning { - color: var(--warning); -} - -.status-badge--error .status-point { - background: var(--danger); - box-shadow: 0 0 12px rgba(220, 38, 38, 0.28); -} - -.status-badge--error { - color: var(--danger); -} - -.status-badge--stopped .status-point { - background: var(--stopped); -} - -.resource-cell { - display: grid; - gap: 7px; - width: min(144px, 92%); -} - -.resource-line { - display: flex; - justify-content: space-between; - color: var(--text-secondary); - font-size: 12px; -} - -.progress { - height: 6px; - overflow: hidden; - border-radius: 999px; - background: rgba(100, 116, 139, 0.16); -} - -.progress span { - display: block; - height: 100%; - border-radius: inherit; - background: var(--primary); - transform-origin: left center; - animation: progress-fill 650ms var(--ease-emphasized) both; - transition: - background var(--motion-base) var(--ease-standard), - width var(--motion-slow) var(--ease-emphasized); -} - -.progress--memory span { - background: var(--info); -} - -.deploy-cell { - color: var(--text-secondary); -} - -.muted { - color: var(--text-tertiary); -} - -.row-actions { - display: flex; - align-items: center; - gap: 8px; -} - -.action-button { - display: inline-grid; - width: 32px; - height: 32px; - place-items: center; - border: 1px solid var(--border); - border-radius: 8px; - background: var(--bg-card-strong); - color: var(--text-secondary); - cursor: pointer; - transition: - background var(--motion-fast) var(--ease-standard), - color var(--motion-fast) var(--ease-standard), - border-color var(--motion-fast) var(--ease-standard), - box-shadow var(--motion-fast) var(--ease-standard), - transform var(--motion-base) var(--ease-emphasized); -} - -.action-button:hover { - border-color: rgba(37, 99, 235, 0.7); - background: var(--bg-hover); - box-shadow: var(--shadow-soft); - color: var(--text-primary); - transform: translateY(-1px); -} - -.action-button:active { - box-shadow: none; - transform: translateY(0) scale(0.94); -} - -.action-button--success { - color: var(--success); -} - -.action-button--danger { - color: var(--danger); -} - -.drawer-scrim { - position: fixed; - inset: var(--header-height) 0 0; - z-index: 19; - display: block; - background: rgba(15, 23, 42, 0); - opacity: 0; - transition: - background var(--motion-slow) var(--ease-standard), - opacity var(--motion-slow) var(--ease-standard); -} - -.drawer-scrim--open { - background: var(--overlay-scrim); - opacity: 1; -} - -.drawer { - position: fixed; - top: var(--header-height); - right: 0; - bottom: 0; - z-index: 20; - display: grid; - width: 360px; - grid-template-rows: auto auto 1fr auto; - border-left: 1px solid var(--border); - background: var(--bg-card); - box-shadow: var(--shadow-drawer); - transform: translateX(100%); - transition: - opacity 280ms var(--ease-standard), - transform 280ms var(--ease-emphasized); - opacity: 0.86; -} - -.drawer--open { - transform: translateX(0); - opacity: 1; -} - -.drawer-head { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - padding: 18px 18px 14px; - border-bottom: 1px solid var(--border); -} - -.panel-title-line { - display: flex; - min-width: 0; - align-items: center; - gap: 10px; -} - -.drawer-title { - margin: 0; - color: var(--text-primary); - font-size: 16px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.drawer-tabs { - display: grid; - grid-template-columns: repeat(4, 1fr); - border-bottom: 1px solid var(--border); -} - -.drawer-tab { - height: 42px; - background: transparent; - color: var(--text-tertiary); - cursor: pointer; - font-size: 12px; - transition: - background var(--motion-fast) var(--ease-standard), - color var(--motion-fast) var(--ease-standard), - box-shadow var(--motion-base) var(--ease-standard); -} - -.drawer-tab:hover { - background: rgba(37, 99, 235, 0.08); - color: var(--text-primary); -} - -.drawer-tab--active { - color: var(--primary); - box-shadow: inset 0 -2px 0 var(--primary); -} - -.drawer-body { - min-height: 0; - overflow: auto; - padding: 18px; -} - -.detail-grid { - display: grid; - gap: 12px; -} - -.detail-row { - display: flex; - justify-content: space-between; - gap: 16px; - padding-bottom: 12px; - border-bottom: 1px solid var(--detail-border); - color: var(--text-secondary); - font-size: 13px; - animation: row-enter var(--motion-slow) var(--ease-emphasized) both; -} - -.detail-row span:first-child { - color: var(--text-tertiary); -} - -.log-list { - display: grid; - gap: 10px; - margin: 0; - padding: 0; - list-style: none; -} - -.log-list li { - padding-bottom: 10px; - border-bottom: 1px solid var(--detail-border); - color: var(--text-secondary); - font-size: 12px; - line-height: 1.6; - animation: row-enter var(--motion-slow) var(--ease-emphasized) both; -} - -.drawer-actions { - display: flex; - justify-content: flex-end; - gap: 10px; - padding: 14px 18px; - border-top: 1px solid var(--border); -} - -.inline-inspector { - display: grid; - min-height: 420px; - grid-template-rows: auto auto 1fr auto; - overflow: hidden; - animation: panel-enter var(--motion-slow) var(--ease-emphasized) both; - transition: - border-color var(--motion-base) var(--ease-standard), - box-shadow var(--motion-base) var(--ease-standard), - transform var(--motion-base) var(--ease-emphasized); -} - -.inline-inspector .drawer-body { - max-height: none; -} - -.empty-state { - padding: 34px 20px; - color: var(--text-tertiary); - text-align: center; -} - -.login-page { - display: grid; - min-height: 100vh; - place-items: center; - padding: 24px; - background: - linear-gradient(180deg, rgba(37, 99, 235, 0.08), rgba(37, 99, 235, 0)), - var(--bg-page); -} - -.login-panel { - display: grid; - width: min(420px, 100%); - gap: 24px; - padding: 28px; - border: 1px solid var(--border); - border-radius: 10px; - background: var(--bg-card); - box-shadow: var(--shadow-panel); - animation: panel-enter var(--motion-slow) var(--ease-emphasized) both; -} - -.login-brand { - display: flex; - align-items: center; - gap: 14px; -} - -.login-brand h1 { - margin: 0; - color: var(--text-primary); - font-size: 20px; -} - -.login-brand p { - margin: 4px 0 0; - color: var(--text-tertiary); - font-size: 13px; -} - -.login-form, -.form-drawer { - display: grid; - gap: 16px; -} - -.form-drawer { - width: min(420px, 100vw); - padding: 22px; -} - -.form-drawer--wide { - width: min(560px, 100vw); -} - -.form-drawer h2 { - margin: 0 0 4px; - color: var(--text-primary); - font-size: 18px; -} - -.form-drawer__actions { - display: flex; - justify-content: flex-end; - gap: 10px; - padding-top: 8px; -} - -.form-section-title { - color: var(--text-secondary); - font-size: 13px; - font-weight: 700; -} - -.permission-section { - display: grid; - gap: 10px; -} - -.permission-grid { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 8px; -} - -.permission-check { - display: flex; - align-items: center; - gap: 8px; - min-height: 34px; - padding: 0 10px; - border: 1px solid var(--border); - border-radius: 8px; - background: var(--bg-card-strong); - color: var(--text-secondary); - font-size: 12px; -} - -.batch-actions { - display: flex; - gap: 8px; -} - -.pagination-bar { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - margin-top: 12px; - color: var(--text-tertiary); - font-size: 12px; -} - -.pagination-bar__actions { - display: flex; - align-items: center; - gap: 8px; -} - -.data-state { - display: grid; - min-height: 260px; - place-items: center; - gap: 14px; - border: 1px solid var(--border); - border-radius: 10px; - background: var(--bg-card); - color: var(--text-tertiary); -} - -.data-state--loading { - padding: 16px; -} - -.data-state--loading .page-skeleton__block { - min-height: 220px; -} - -.data-state__title { - color: var(--text-secondary); - font-size: 14px; - font-weight: 650; -} - -.admin-table { - min-width: 900px; -} - -.admin-row { - display: grid; - align-items: center; - min-height: 58px; - padding: 0 20px; - border-bottom: 1px solid var(--row-border); - color: var(--text-secondary); - font-size: 13px; - animation: row-enter var(--motion-slow) var(--ease-emphasized) both; -} - -.admin-row--head { - min-height: 44px; - background: var(--bg-card-strong); - color: var(--text-tertiary); - font-size: 12px; - font-weight: 650; -} - -.admin-table--roles .admin-row { - grid-template-columns: 1fr 1fr 0.7fr 1.6fr 0.8fr; -} - -.admin-table--menus .admin-row { - grid-template-columns: 1.2fr 1.4fr 1.2fr 0.8fr; -} - -.admin-table--login-logs .admin-row { - grid-template-columns: 1fr 1fr 0.7fr 1.6fr 1.2fr; -} - -.admin-table--operation-logs .admin-row { - grid-template-columns: 0.9fr 1fr 1fr 0.7fr 1.6fr 1.2fr; -} - -.notification-list { - display: grid; -} - -.notification-item { - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - align-items: center; - gap: 14px; - min-height: 86px; - padding: 16px 20px; - border-bottom: 1px solid var(--row-border); -} - -.notification-title { - color: var(--text-primary); - font-size: 14px; - font-weight: 700; -} - -.notification-content { - margin: 6px 0; - color: var(--text-secondary); - font-size: 13px; -} - -@media (max-width: 1080px) { - body { - overflow: auto; - } - - .app-shell, - .body-grid { - min-height: auto; - } - - .workspace { - overflow: visible; - } - - .kpi-grid, - .panel-grid, - .overview-charts, - .service-kpi-row, - .user-kpi-row, - .page-skeleton__kpis, - .page-skeleton__charts { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - - .table-scroll { - overflow-x: auto; - } - - .user-pill { - display: none; - } - - .split-grid, - .service-page-grid, - .page-skeleton__content { - grid-template-columns: 1fr; - } - - .inline-inspector { - display: none; - } -} - -@media (max-width: 760px) { - .header-tools { - gap: 8px; - padding: 0 12px; - } - - .header-location { - min-width: auto; - padding: 0 12px; - } - - .search-trigger { - min-width: 0; - width: 42px; - justify-content: center; - padding: 0; - } - - .search-trigger span { - display: none; - } - - .workspace { - padding: 16px; - } - - .page-skeleton { - height: calc(100vh - var(--header-height) - 32px); - height: calc(100dvh - var(--header-height) - 32px); - grid-template-rows: 80px minmax(96px, 0.55fr) minmax(132px, 0.75fr) minmax(180px, 1fr); - } - - .page-skeleton__head { - align-items: flex-start; - flex-direction: column; - } - - .page-skeleton__title { - width: 58%; - } - - .page-head, - .table-toolbar { - align-items: flex-start; - flex-direction: column; - } - - .kpi-grid, - .panel-grid, - .overview-charts, - .service-kpi-row, - .user-kpi-row, - .alert-strip, - .page-skeleton__kpis, - .page-skeleton__charts { - grid-template-columns: 1fr; - } - - .page-skeleton__kpis .page-skeleton__kpi:nth-child(n + 3), - .page-skeleton__charts .page-skeleton__block:nth-child(n + 2), - .page-skeleton__rail { - display: none; - } - - .page-skeleton__kpis { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - - .filters-panel { - align-items: stretch; - flex-direction: column; - } - - .filters-panel .search-box { - flex-basis: auto; - width: 100%; - } - - .filter-row { - flex-wrap: wrap; - } - - .drawer { - width: min(360px, 100vw); - } -} - -@media (prefers-reduced-motion: reduce) { - *, - *::before, - *::after { - animation-duration: 1ms !important; - animation-iteration-count: 1 !important; - scroll-behavior: auto !important; - transition-duration: 1ms !important; - } +@import "./layout.css"; +@import "./shared-ui.css"; +@import "../features/dashboard/dashboard.css"; +@import "../features/users/users.css"; +@import "../features/menus/menus.css"; +@import "../features/notifications/notifications.css"; +@import "./responsive.css"; + +#app, +.MuiAutocomplete-popper, +.MuiDialog-root, +.MuiMenu-root, +.MuiModal-root, +.MuiPopover-root, +.MuiPopper-root, +.MuiSnackbar-root, +.MuiTooltip-popper { + font-size: var(--admin-font-size); + line-height: var(--admin-line-height); +} + +#app :is(h1, h2, h3, h4, h5, h6, p, span, small, strong, em, label, button, input, textarea, select, option, a, li, dt, dd, th, td, div, pre, code), +.MuiAutocomplete-popper :is(h1, h2, h3, h4, h5, h6, p, span, small, strong, em, label, button, input, textarea, select, option, a, li, dt, dd, th, td, div, pre, code), +.MuiDialog-root :is(h1, h2, h3, h4, h5, h6, p, span, small, strong, em, label, button, input, textarea, select, option, a, li, dt, dd, th, td, div, pre, code), +.MuiMenu-root :is(h1, h2, h3, h4, h5, h6, p, span, small, strong, em, label, button, input, textarea, select, option, a, li, dt, dd, th, td, div, pre, code), +.MuiModal-root :is(h1, h2, h3, h4, h5, h6, p, span, small, strong, em, label, button, input, textarea, select, option, a, li, dt, dd, th, td, div, pre, code), +.MuiPopover-root :is(h1, h2, h3, h4, h5, h6, p, span, small, strong, em, label, button, input, textarea, select, option, a, li, dt, dd, th, td, div, pre, code), +.MuiPopper-root :is(h1, h2, h3, h4, h5, h6, p, span, small, strong, em, label, button, input, textarea, select, option, a, li, dt, dd, th, td, div, pre, code), +.MuiSnackbar-root :is(h1, h2, h3, h4, h5, h6, p, span, small, strong, em, label, button, input, textarea, select, option, a, li, dt, dd, th, td, div, pre, code), +.MuiTooltip-popper :is(h1, h2, h3, h4, h5, h6, p, span, small, strong, em, label, button, input, textarea, select, option, a, li, dt, dd, th, td, div, pre, code) { + font-size: var(--admin-font-size); } diff --git a/src/styles/base.css b/src/styles/base.css index f0898fb..2d59317 100644 --- a/src/styles/base.css +++ b/src/styles/base.css @@ -14,7 +14,9 @@ body { overflow: hidden; background: var(--bg-page); color: var(--text-primary); + font-size: var(--admin-font-size); font-family: var(--font-system); + line-height: var(--admin-line-height); letter-spacing: 0; } @@ -31,7 +33,7 @@ button { button:focus-visible, input:focus-visible, select:focus-visible { - outline: 2px solid rgba(37, 99, 235, 0.7); + outline: 2px solid var(--primary-border-strong); outline-offset: 2px; } diff --git a/src/styles/layout.css b/src/styles/layout.css new file mode 100644 index 0000000..e852eca --- /dev/null +++ b/src/styles/layout.css @@ -0,0 +1,867 @@ +.app-shell { + display: grid; + grid-template-rows: var(--header-height) 1fr; + min-height: 100vh; + background: var(--bg-page); +} + +@keyframes page-enter { + from { + opacity: 0; + transform: translateY(10px) scale(0.995); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +@keyframes panel-enter { + from { + opacity: 0; + transform: translateY(12px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes row-enter { + from { + opacity: 0; + transform: translateX(-8px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes shimmer { + from { + background-position: 120% 0; + } + to { + background-position: -120% 0; + } +} + +@keyframes notice-pop { + 0%, + 100% { + transform: scale(1); + } + 45% { + transform: scale(1.18); + } +} + +@keyframes progress-fill { + from { + transform: scaleX(0); + } + to { + transform: scaleX(1); + } +} + +@keyframes autocomplete-pop-enter { + from { + opacity: 0; + transform: translateY(-6px) scale(0.98); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +@keyframes autocomplete-pop-enter-up { + from { + opacity: 0; + transform: translateY(6px) scale(0.98); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +.header { + display: grid; + grid-template-columns: 248px 1fr; + height: var(--header-height); + border-bottom: 1px solid var(--border); + background: var(--bg-header); + transition: grid-template-columns var(--motion-slow) var(--ease-emphasized); +} + +.brand { + display: flex; + align-items: center; + gap: var(--space-3); + height: var(--header-height); + padding: 0 var(--space-6); + border-right: 1px solid var(--border); + background: var(--bg-sidebar); +} + +.brand-mark { + display: grid; + width: 38px; + height: 38px; + place-items: center; + border-radius: var(--radius-control); + background: var(--brand-gradient); + color: var(--active-contrast); + transition: + transform var(--motion-base) var(--ease-emphasized), + box-shadow var(--motion-base) var(--ease-standard); +} + +.brand:hover .brand-mark { + box-shadow: var(--brand-shadow); + transform: rotate(-6deg) scale(1.05); +} + +.brand-title { + display: block; + color: var(--text-primary); + font-size: var(--admin-font-size); + font-weight: 650; +} + +.brand-meta { + display: block; + margin-top: calc(var(--space-1) / 2); + color: var(--text-tertiary); + font-size: var(--admin-font-size); +} + +.header-tools { + display: flex; + min-width: 0; + align-items: center; + justify-content: space-between; + gap: var(--space-4); + padding: 0 var(--space-6); +} + +.header-left-tools, +.header-right-tools { + display: flex; + min-width: 0; + align-items: center; + gap: var(--space-3); +} + +.header-right-tools { + margin-left: auto; + justify-content: flex-end; +} + +.app-switch { + flex: 0 0 156px; +} + +.app-switch .MuiOutlinedInput-root { + background: var(--bg-input); +} + +.app-switch .MuiSelect-select { + font-weight: 650; +} + +.header-location { + display: inline-flex; + min-width: 74px; + height: var(--control-height); + align-items: center; + justify-content: center; + padding: 0 var(--space-4); + border: 1px solid var(--primary-border); + border-radius: var(--radius-control); + background: var(--primary-hover); + box-shadow: + var(--shadow-soft), + var(--active-inset); + color: var(--text-primary); + font-size: var(--admin-font-size); + font-weight: 650; + white-space: nowrap; +} + +.search-box { + display: flex; + flex: 1 1 420px; + min-width: 180px; + max-width: 520px; + height: var(--control-height); + align-items: center; + gap: var(--space-3); + padding: 0 var(--space-3); + border: 1px solid var(--border); + border-radius: var(--radius-control); + background: var(--bg-input); + backdrop-filter: var(--glass-blur); + color: var(--text-tertiary); + -webkit-backdrop-filter: var(--glass-blur); + transition: + border-color var(--motion-base) var(--ease-standard), + background var(--motion-base) var(--ease-standard), + transform var(--motion-base) var(--ease-emphasized); +} + +.search-box:focus-within { + border-color: var(--primary-border-active); + background: var(--bg-input-strong); + transform: translateY(-1px); +} + +.search-box svg { + transition: + color var(--motion-base) var(--ease-standard), + transform var(--motion-base) var(--ease-emphasized); +} + +.search-box:focus-within svg { + color: var(--primary); + transform: scale(1.08); +} + +.search-trigger { + display: flex; + width: min(360px, 34vw); + min-width: 260px; + height: var(--control-height); + align-items: center; + gap: var(--space-3); + padding: 0 var(--space-3); + border: 1px solid var(--border); + border-radius: var(--radius-control); + background: var(--bg-input); + backdrop-filter: var(--glass-blur); + color: var(--text-tertiary); + cursor: pointer; + text-align: left; + -webkit-backdrop-filter: var(--glass-blur); + transition: + border-color var(--motion-base) var(--ease-standard), + background var(--motion-base) var(--ease-standard), + color var(--motion-base) var(--ease-standard), + transform var(--motion-base) var(--ease-emphasized); +} + +.search-trigger span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.search-trigger:hover, +.search-trigger:focus-visible { + border-color: var(--primary-border-active); + background: var(--bg-input-strong); + color: var(--text-primary); + transform: translateY(-1px); +} + +.search-box input { + width: 100%; + height: 20px; + min-width: 0; + padding: 0; + border: 0; + background: transparent; + color: var(--text-primary); + font-size: var(--admin-font-size); + line-height: 20px; + outline: 0; +} + +.search-box input::placeholder { + color: var(--text-tertiary); +} + +.time-select { + width: 116px; + height: var(--control-height); + border: 1px solid var(--border); + border-radius: var(--radius-control); + background: var(--bg-input); + backdrop-filter: var(--glass-blur); + color: var(--text-secondary); + -webkit-backdrop-filter: var(--glass-blur); +} + +.icon-button { + display: inline-grid; + width: var(--control-height); + height: var(--control-height); + place-items: center; + border: 1px solid var(--border); + border-radius: var(--radius-control); + background: var(--bg-card); + color: var(--text-secondary); + cursor: pointer; + transition: + background var(--motion-fast) var(--ease-standard), + color var(--motion-fast) var(--ease-standard), + border-color var(--motion-fast) var(--ease-standard), + box-shadow var(--motion-fast) var(--ease-standard), + transform var(--motion-base) var(--ease-emphasized); +} + +.icon-button:hover { + border-color: var(--primary-border-strong); + background: var(--bg-hover); + box-shadow: var(--shadow-soft); + color: var(--text-primary); + transform: translateY(-1px); +} + +.icon-button:active { + box-shadow: none; + transform: translateY(0) scale(0.96); +} + +.icon-button--notice { + position: relative; +} + +.icon-button--notice span { + position: absolute; + top: -5px; + right: -5px; + display: grid; + min-width: 17px; + height: 17px; + place-items: center; + border-radius: var(--radius-pill); + background: var(--danger); + color: var(--active-contrast); + font-size: var(--admin-font-size); + font-weight: 700; + animation: notice-pop 1500ms var(--ease-emphasized) infinite; +} + +.user-pill { + display: flex; + height: var(--control-height); + align-items: center; + gap: var(--space-2); + padding: 0 var(--space-3); + border: 1px solid var(--border); + border-radius: var(--radius-control); + background: transparent; + color: var(--text-secondary); + cursor: pointer; + font-size: var(--admin-font-size); + transition: + border-color var(--motion-base) var(--ease-standard), + background var(--motion-base) var(--ease-standard), + color var(--motion-base) var(--ease-standard); +} + +.user-pill:hover { + border-color: var(--primary-border); + background: var(--primary-hover); + color: var(--text-primary); +} + +.user-pill:active { + transform: scale(0.98); +} + +.user-menu .MuiPaper-root { + min-width: 132px; + margin-top: var(--space-2); +} + +.user-menu__item { + display: flex; + gap: var(--space-2); + min-height: var(--control-height); + color: var(--text-secondary); + font-size: var(--admin-font-size); +} + +.user-menu__item:hover { + color: var(--text-primary); +} + +.body-grid { + display: grid; + grid-template-columns: 248px 1fr; + min-height: 0; + transition: grid-template-columns var(--motion-slow) var(--ease-emphasized); +} + +.sidebar { + position: relative; + z-index: var(--z-sidebar); + min-height: 0; + overflow: visible; + padding: var(--space-4) var(--space-3); + border-right: 1px solid var(--border); + background: var(--bg-sidebar); +} + +.app-shell--sidebar-collapsed .header, +.app-shell--sidebar-collapsed .body-grid { + grid-template-columns: 84px 1fr; +} + +.app-shell--sidebar-collapsed .brand { + gap: 0; + justify-content: center; + padding: 0; +} + +.brand-text, +.nav-label { + display: inline-grid; + min-width: 0; + max-width: 160px; + opacity: 1; + overflow: hidden; + transform: translateX(0); + transition: + max-width var(--motion-slow) var(--ease-emphasized), + opacity var(--motion-base) var(--ease-standard), + transform var(--motion-base) var(--ease-emphasized); +} + +.app-shell--sidebar-collapsed .brand-text, +.sidebar--collapsed .nav-label { + max-width: 0; + opacity: 0; + pointer-events: none; + transform: translateX(-6px); +} + +.sidebar--collapsed { + padding: var(--space-4) var(--space-3); +} + +.sidebar--collapsed .nav-item { + width: 44px; + justify-content: center; + justify-self: center; + padding: 0; +} + +.nav-list { + display: grid; + gap: var(--space-3); +} + +.nav-group { + display: grid; + position: relative; + gap: 0; +} + +.nav-item { + position: relative; + display: flex; + width: 100%; + height: 40px; + align-items: center; + gap: 10px; + padding: 0 var(--space-3); + border-radius: var(--radius-control); + background: transparent; + color: var(--text-secondary); + cursor: pointer; + overflow: hidden; + transition: + background var(--motion-base) var(--ease-standard), + color var(--motion-base) var(--ease-standard), + gap var(--motion-slow) var(--ease-emphasized), + padding var(--motion-slow) var(--ease-emphasized), + width var(--motion-slow) var(--ease-emphasized), + transform var(--motion-base) var(--ease-emphasized); +} + +.nav-item svg { + flex: 0 0 auto; + color: currentColor; + font-size: 20px; +} + +.nav-item--parent { + padding-right: var(--space-3); + font-weight: 650; +} + +.nav-item--open:not(.nav-item--active) { + background: var(--primary-surface); + color: var(--text-secondary); +} + +.nav-expand { + margin-left: auto; + color: currentColor; + transition: + color var(--motion-base) var(--ease-standard), + transform var(--motion-base) var(--ease-emphasized); +} + +.nav-item--open .nav-expand, +.nav-item:hover .nav-expand { + color: currentColor; +} + +.nav-item--expanded .nav-expand { + transform: rotate(180deg); +} + +.nav-children { + display: grid; + position: relative; + grid-template-rows: 0fr; + margin: 0; + opacity: 0; + overflow: hidden; + padding-left: var(--space-7); + pointer-events: none; + transform: translateY(-4px); + transition: + grid-template-rows var(--motion-slow) var(--ease-emphasized), + opacity var(--motion-base) var(--ease-standard), + transform var(--motion-slow) var(--ease-emphasized); +} + +.nav-children::before { + position: absolute; + top: var(--space-1); + bottom: var(--space-1); + left: var(--space-4); + width: 1px; + background: var(--border-soft); + content: ""; +} + +.nav-children--open { + grid-template-rows: 1fr; + margin-top: var(--space-2); + opacity: 1; + pointer-events: auto; + transform: translateY(0); +} + +.nav-children__inner { + display: grid; + gap: var(--space-1); + min-height: 0; + overflow: hidden; +} + +.nav-item--child { + height: 34px; + padding: 0 var(--space-3); + font-size: var(--admin-font-size); + font-weight: 600; +} + +.nav-item--child svg { + font-size: 16px; +} + +.nav-item--child::before { + display: none; +} + +.sidebar--collapsed .nav-children, +.sidebar--collapsed .nav-item--parent .nav-expand { + display: none !important; +} + +.sidebar--collapsed .nav-item--open, +.sidebar--collapsed .nav-item--open:hover, +.sidebar--collapsed .nav-item--flyout-open, +.sidebar--collapsed .nav-item--flyout-open:hover { + background: var(--active-gradient); + box-shadow: var(--active-inset); + color: var(--active-contrast); +} + +.nav-flyout { + position: absolute; + top: 0; + left: calc(100% + var(--space-3)); + z-index: var(--z-popover); + display: grid; + width: 168px; + gap: var(--space-1); + padding: var(--space-2); + border: 1px solid var(--primary-border); + border-radius: var(--radius-control); + background: var(--bg-flyout); + box-shadow: var(--shadow-panel); + animation: panel-enter var(--motion-base) var(--ease-emphasized) both; +} + +.nav-flyout::before { + position: absolute; + top: 16px; + left: -6px; + width: 10px; + height: 10px; + border-bottom: 1px solid var(--primary-border); + border-left: 1px solid var(--primary-border); + background: var(--bg-flyout); + content: ""; + transform: rotate(45deg); +} + +.nav-flyout__title { + padding: var(--space-1) var(--space-2) var(--space-2); + color: var(--text-tertiary); + font-size: var(--admin-font-size); + font-weight: 650; +} + +.nav-flyout__item { + display: flex; + height: var(--control-height); + align-items: center; + gap: var(--space-3); + padding: 0 var(--space-3); + border-radius: var(--radius-control); + background: transparent; + color: var(--text-secondary); + cursor: pointer; + font-size: var(--admin-font-size); + text-align: left; + transition: + background var(--motion-fast) var(--ease-standard), + color var(--motion-fast) var(--ease-standard), + transform var(--motion-base) var(--ease-emphasized); +} + +.nav-flyout__item:hover, +.nav-flyout__item--active { + background: var(--primary-surface); + color: var(--text-primary); + transform: translateX(2px); +} + +.nav-item::before { + position: absolute; + inset: 8px auto 8px 0; + width: 3px; + border-radius: 0 var(--radius-pill) var(--radius-pill) 0; + background: var(--primary); + content: ""; + opacity: 0; + transform: translateX(-4px); + transition: + opacity var(--motion-base) var(--ease-standard), + transform var(--motion-base) var(--ease-emphasized); +} + +.nav-item:hover { + background: var(--bg-hover); + color: var(--text-secondary); + transform: translateX(2px); +} + +.nav-item--child:hover { + transform: none; +} + +.nav-item--active, +.nav-item--active:hover { + background: var(--active-gradient); + box-shadow: var(--active-inset); + color: var(--active-contrast); +} + +.nav-item--child.nav-item--active, +.nav-item--child.nav-item--active:hover { + background: var(--primary); + box-shadow: 0 8px 18px rgba(37, 99, 235, 0.2); + color: var(--active-contrast); + transform: none; +} + +.nav-item:hover::before, +.nav-item--active::before { + opacity: 1; + transform: translateX(0); +} + +.nav-label { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.workspace { + position: relative; + min-width: 0; + min-height: 0; + overflow: auto; + padding: var(--space-6); +} + +.page-transition { + animation: page-enter var(--motion-slow) var(--ease-emphasized) both; +} + +.page-skeleton { + display: grid; + height: calc(100vh - var(--header-height) - var(--space-8) - var(--space-4)); + height: calc(100dvh - var(--header-height) - var(--space-8) - var(--space-4)); + grid-template-rows: 38px minmax(96px, 0.7fr) minmax(160px, 1fr) minmax(200px, 1.2fr); + gap: var(--space-4); + animation: page-enter var(--motion-slow) var(--ease-emphasized) both; +} + +.page-skeleton__head { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-4); +} + +.page-skeleton__title { + width: min(220px, 36%); + height: 30px; +} + +.page-skeleton__actions { + display: flex; + gap: var(--space-3); +} + +.page-skeleton__actions .page-skeleton__block { + width: 96px; + height: var(--control-height); +} + +.page-skeleton__kpis { + display: grid; + grid-template-columns: repeat(5, minmax(0, 1fr)); + gap: var(--space-4); +} + +.page-skeleton__charts { + display: grid; + grid-template-columns: minmax(280px, 1.15fr) repeat(2, minmax(260px, 1fr)) minmax(260px, 0.95fr); + gap: var(--space-4); + min-height: 0; +} + +.page-skeleton__content { + display: grid; + grid-template-columns: 280px minmax(0, 1fr); + gap: var(--space-4); + min-height: 0; +} + +.page-skeleton__block { + position: relative; + width: 100%; + height: 100%; + overflow: hidden; + border: 1px solid var(--border); + border-radius: var(--radius-card); + background-color: var(--skeleton-surface); + opacity: 1; + transform: translateZ(0); + animation: panel-enter var(--motion-slow) var(--ease-emphasized) both; +} + +.page-skeleton__block::after { + background: var(--skeleton-gradient); +} + +.page-skeleton__kpi:nth-child(2), +.page-skeleton__charts .page-skeleton__block:nth-child(2), +.page-skeleton__content .page-skeleton__block:nth-child(2) { + animation-delay: 40ms; +} + +.page-skeleton__kpi:nth-child(3), +.page-skeleton__charts .page-skeleton__block:nth-child(3) { + animation-delay: 80ms; +} + +.page-skeleton__kpi:nth-child(4), +.page-skeleton__charts .page-skeleton__block:nth-child(4) { + animation-delay: 120ms; +} + +.page-skeleton__kpi:nth-child(5) { + animation-delay: 160ms; +} + +.search-dialog { + overflow: hidden; + border: 1px solid var(--border); + border-radius: var(--radius-dialog); + background: var(--bg-card); + background-image: none; + box-shadow: var(--shadow-dialog); + animation: panel-enter var(--motion-slow) var(--ease-emphasized) both; +} + +.search-dialog__head { + padding: var(--space-5); + border-bottom: 1px solid var(--border); + background: var(--bg-card-strong); +} + +.search-dialog__input { + max-width: none; + width: 100%; + height: 44px; +} + +.search-dialog__list { + display: grid; + gap: var(--space-2); + max-height: min(56vh, 520px); + overflow: auto; + padding: var(--space-3); +} + +.search-dialog__item { + min-height: 64px; + border: 1px solid transparent; + border-radius: var(--radius-card); + color: var(--text-primary); + transition: + background var(--motion-base) var(--ease-standard), + border-color var(--motion-base) var(--ease-standard), + transform var(--motion-base) var(--ease-emphasized); +} + +.search-dialog__item:hover { + border-color: var(--primary-border-strong); + background: var(--primary-hover); + transform: translateX(3px); +} + +.search-dialog__primary { + color: var(--text-primary); + font-size: var(--admin-font-size); + font-weight: 650; +} + +.search-dialog__secondary { + color: var(--text-tertiary); + font-size: var(--admin-font-size); +} + +.search-dialog__empty { + display: grid; + min-height: 120px; + place-items: center; + color: var(--text-tertiary); + font-size: var(--admin-font-size); +} diff --git a/src/styles/modules.d.ts b/src/styles/modules.d.ts new file mode 100644 index 0000000..6178fd4 --- /dev/null +++ b/src/styles/modules.d.ts @@ -0,0 +1,4 @@ +declare module "*.module.css" { + const styles: Record; + export default styles; +} diff --git a/src/styles/responsive.css b/src/styles/responsive.css new file mode 100644 index 0000000..7ee4ece --- /dev/null +++ b/src/styles/responsive.css @@ -0,0 +1,137 @@ +@media (max-width: 1080px) { + body { + overflow: auto; + } + + .app-shell, + .body-grid { + min-height: auto; + } + + .workspace { + overflow: visible; + } + + .kpi-grid, + .panel-grid, + .overview-charts, + .user-kpi-row, + .page-skeleton__kpis, + .page-skeleton__charts { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .table-scroll { + overflow-x: auto; + } + + .user-pill { + display: none; + } + + .split-grid, + .page-skeleton__content { + grid-template-columns: 1fr; + } + + .inline-inspector { + display: none; + } +} + +@media (max-width: 760px) { + .header-tools { + gap: 8px; + padding: 0 12px; + } + + .header-location { + min-width: auto; + padding: 0 12px; + } + + .search-trigger { + min-width: 0; + width: 42px; + justify-content: center; + padding: 0; + } + + .search-trigger span { + display: none; + } + + .workspace { + padding: 16px; + } + + .page-skeleton { + height: calc(100vh - var(--header-height) - 32px); + height: calc(100dvh - var(--header-height) - 32px); + grid-template-rows: 80px minmax(96px, 0.55fr) minmax(132px, 0.75fr) minmax(180px, 1fr); + } + + .page-skeleton__head { + align-items: flex-start; + flex-direction: column; + } + + .page-skeleton__title { + width: 58%; + } + + .page-head, + .table-toolbar { + align-items: flex-start; + flex-direction: column; + } + + .kpi-grid, + .panel-grid, + .overview-charts, + .user-kpi-row, + .alert-strip, + .page-skeleton__kpis, + .page-skeleton__charts { + grid-template-columns: 1fr; + } + + .page-skeleton__kpis .page-skeleton__kpi:nth-child(n + 3), + .page-skeleton__charts .page-skeleton__block:nth-child(n + 2), + .page-skeleton__rail { + display: none; + } + + .page-skeleton__kpis { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .filters-panel { + align-items: stretch; + flex-direction: column; + } + + .filters-panel .search-box { + flex-basis: auto; + width: 100%; + } + + .filter-row { + flex-wrap: wrap; + } + + .drawer { + width: min(360px, 100vw); + } +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 1ms !important; + animation-iteration-count: 1 !important; + scroll-behavior: auto !important; + transition-duration: 1ms !important; + } +} diff --git a/src/styles/shared-ui.css b/src/styles/shared-ui.css new file mode 100644 index 0000000..0c197e5 --- /dev/null +++ b/src/styles/shared-ui.css @@ -0,0 +1,944 @@ +.page-head { + display: flex; + align-items: flex-end; + justify-content: space-between; + gap: var(--space-4); + margin-bottom: var(--space-5); +} + +.page-title { + margin: 0; + font-size: var(--admin-font-size); + font-weight: 700; +} + +.page-meta { + margin: var(--space-2) 0 0; + color: var(--text-tertiary); + font-size: var(--admin-font-size); +} + +.page-actions { + display: flex; + align-items: center; + gap: var(--space-3); +} + +.button { + display: inline-flex; + height: var(--control-height); + min-height: var(--control-height); + align-items: center; + justify-content: center; + gap: var(--space-2); + padding: 0 var(--space-3); + border: 1px solid var(--border); + border-radius: var(--radius-control); + background: var(--bg-card); + color: var(--text-secondary); + cursor: pointer; + font-size: var(--admin-font-size); + line-height: var(--admin-line-height); + white-space: nowrap; + transition: + background var(--motion-fast) var(--ease-standard), + border-color var(--motion-fast) var(--ease-standard), + box-shadow var(--motion-fast) var(--ease-standard), + color var(--motion-fast) var(--ease-standard), + transform var(--motion-base) var(--ease-emphasized); +} + +.button:hover { + border-color: var(--primary-border-strong); + background: var(--bg-hover); + box-shadow: var(--shadow-soft); + color: var(--text-primary); + transform: translateY(-1px); +} + +.button:active { + box-shadow: none; + transform: translateY(0) scale(0.985); +} + +.button:disabled, +.icon-button:disabled { + cursor: not-allowed; + opacity: 0.42; +} + +.button--primary { + border-color: var(--primary); + background: var(--primary); + color: var(--active-contrast); +} + +.button--success { + color: var(--success); +} + +.button--danger { + color: var(--danger); +} + +.button.MuiButton-root { + height: var(--control-height); + min-height: var(--control-height); + padding: 0 var(--space-3); + border-radius: var(--radius-control); + font-size: var(--admin-font-size); + line-height: var(--admin-line-height); + text-transform: none; +} + +.kpi-grid { + display: grid; + grid-template-columns: repeat(5, minmax(0, 1fr)); + gap: var(--space-4); +} + +.card { + border: 1px solid var(--border); + border-radius: var(--radius-card); + background: var(--bg-card); +} + +.MuiTextField-root, +.MuiFormControl-root { + --admin-control-height: var(--control-height); + --admin-control-font-size: var(--admin-font-size); + --admin-control-line-height: var(--admin-line-height); +} + +.region-select.MuiTextField-root, +.region-select.MuiFormControl-root { + width: 180px; + min-width: 180px; + flex: 0 0 180px; +} + +.MuiInputBase-root.MuiOutlinedInput-root:not(.MuiInputBase-multiline) { + height: var(--control-height); + min-height: var(--control-height); +} + +.MuiInputBase-root.MuiOutlinedInput-root { + background: var(--bg-input); + backdrop-filter: var(--glass-blur); + -webkit-backdrop-filter: var(--glass-blur); +} + +.MuiInputBase-root.MuiOutlinedInput-root:not(.MuiInputBase-multiline) .MuiInputBase-input { + box-sizing: border-box; + height: var(--control-height); + padding-top: 0; + padding-bottom: 0; + font-size: var(--admin-font-size); + line-height: var(--admin-line-height); +} + +.MuiInputBase-root .MuiInputBase-input::placeholder { + font-size: var(--admin-font-size); + line-height: var(--admin-line-height); +} + +.MuiInputBase-input:focus { + outline: 0; +} + +.MuiTextField-root .MuiOutlinedInput-root, +.MuiFormControl-root .MuiOutlinedInput-root { + border-radius: var(--radius-control); + background: var(--bg-input); + backdrop-filter: var(--glass-blur); + color: var(--text-primary); + font-size: var(--admin-control-font-size); + line-height: var(--admin-control-line-height); + -webkit-backdrop-filter: var(--glass-blur); +} + +.MuiTextField-root .MuiOutlinedInput-root:not(.MuiInputBase-multiline), +.MuiFormControl-root .MuiOutlinedInput-root:not(.MuiInputBase-multiline) { + height: var(--admin-control-height); + min-height: var(--admin-control-height); +} + +.MuiTextField-root .MuiOutlinedInput-notchedOutline, +.MuiFormControl-root .MuiOutlinedInput-notchedOutline { + border-color: var(--border); +} + +.MuiTextField-root .MuiOutlinedInput-root:hover .MuiOutlinedInput-notchedOutline, +.MuiFormControl-root .MuiOutlinedInput-root:hover .MuiOutlinedInput-notchedOutline { + border-color: var(--primary-border); +} + +.MuiTextField-root .MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline, +.MuiFormControl-root .MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline { + border-color: var(--primary); + border-width: 1px; +} + +.MuiInputBase-root.Mui-focused { + box-shadow: none; +} + +.MuiTextField-root .MuiOutlinedInput-input, +.MuiFormControl-root .MuiOutlinedInput-input { + box-sizing: border-box; + height: var(--admin-control-height); + padding: 0 var(--space-3); + color: var(--text-primary); + font-size: var(--admin-control-font-size); + line-height: var(--admin-control-line-height); +} + +.MuiTextField-root .MuiOutlinedInput-input::placeholder, +.MuiFormControl-root .MuiOutlinedInput-input::placeholder { + color: var(--text-tertiary); + font-size: var(--admin-control-font-size); + line-height: var(--admin-control-line-height); + opacity: 1; +} + +.MuiTextField-root .MuiInputLabel-outlined, +.MuiFormControl-root .MuiInputLabel-outlined { + color: var(--text-tertiary); + font-size: var(--admin-control-font-size); + line-height: var(--admin-control-line-height); + transform: translate(14px, 8px) scale(1); +} + +.MuiTextField-root .MuiInputLabel-outlined.MuiInputLabel-shrink, +.MuiFormControl-root .MuiInputLabel-outlined.MuiInputLabel-shrink { + transform: translate(14px, -8px) scale(0.75); +} + +.MuiTextField-root .MuiFormLabel-root.Mui-focused, +.MuiFormControl-root .MuiFormLabel-root.Mui-focused { + color: var(--primary); +} + +.MuiTextField-root .MuiSelect-select, +.MuiFormControl-root .MuiSelect-select { + display: flex; + box-sizing: border-box; + height: var(--admin-control-height); + min-height: var(--admin-control-height) !important; + align-items: center; + padding: 0 var(--space-8) 0 var(--space-3) !important; + font-size: var(--admin-control-font-size); + line-height: var(--admin-control-line-height); +} + +.MuiTextField-root .MuiSelect-icon, +.MuiFormControl-root .MuiSelect-icon { + top: 50%; + color: var(--text-tertiary); + transform: translateY(-50%); +} + +.MuiAutocomplete-root { + --admin-autocomplete-chip-height: 28px; + --admin-autocomplete-input-height: 24px; +} + +.MuiAutocomplete-root .MuiOutlinedInput-root.MuiAutocomplete-inputRoot:not(.MuiInputBase-multiline) { + height: auto; + min-height: var(--control-height); + align-items: flex-start; + flex-wrap: wrap; + gap: var(--space-1); + padding: 5px 40px 5px var(--space-2); +} + +.MuiAutocomplete-root .MuiOutlinedInput-root.MuiAutocomplete-inputRoot .MuiAutocomplete-input { + flex: 1 0 96px; + width: auto !important; + min-width: 80px; + height: var(--admin-autocomplete-input-height); + padding: 0 !important; + line-height: var(--admin-autocomplete-input-height); +} + +.MuiAutocomplete-root .MuiOutlinedInput-root.MuiAutocomplete-inputRoot .MuiAutocomplete-endAdornment { + top: 50%; + right: var(--space-2); + transform: translateY(-50%); +} + +.MuiAutocomplete-root .MuiAutocomplete-tag { + max-width: calc(100% - var(--space-2)); + height: var(--admin-autocomplete-chip-height); + margin: 0; + border-radius: var(--radius-pill); + background: var(--neutral-surface-strong); + color: var(--text-primary); + font-size: var(--admin-font-size); +} + +.MuiAutocomplete-root .MuiAutocomplete-tag .MuiChip-label { + padding: 0 var(--space-2); +} + +.MuiAutocomplete-root .MuiAutocomplete-tag .MuiChip-deleteIcon { + width: 18px; + height: 18px; + margin-right: var(--space-1); + color: var(--text-tertiary); +} + +.MuiAutocomplete-popper .MuiAutocomplete-paper { + overflow: hidden; + border: 1px solid var(--border); + border-radius: var(--radius-control); + background: var(--bg-flyout); + box-shadow: var(--shadow-panel); + transform-origin: top center; + animation: autocomplete-pop-enter var(--motion-base) var(--ease-emphasized) both; +} + +.MuiAutocomplete-popper[data-popper-placement*="top"] .MuiAutocomplete-paper { + transform-origin: bottom center; + animation-name: autocomplete-pop-enter-up; +} + +.MuiAutocomplete-popper .MuiAutocomplete-listbox { + max-height: 280px; + padding: var(--space-1); +} + +.MuiAutocomplete-popper .MuiAutocomplete-option { + min-height: var(--control-height); + border-radius: var(--radius-control); + color: var(--text-secondary); + font-size: var(--admin-font-size); + transition: + background var(--motion-fast) var(--ease-standard), + color var(--motion-fast) var(--ease-standard), + transform var(--motion-fast) var(--ease-emphasized); +} + +.MuiAutocomplete-popper .MuiAutocomplete-option.Mui-focused, +.MuiAutocomplete-popper .MuiAutocomplete-option[aria-selected="true"] { + background: var(--primary-hover); + color: var(--text-primary); + transform: translateX(2px); +} + +.MuiTextField-root .MuiOutlinedInput-root.MuiInputBase-multiline, +.MuiFormControl-root .MuiOutlinedInput-root.MuiInputBase-multiline { + align-items: flex-start; + padding: var(--space-2) var(--space-3); +} + +.MuiTextField-root .MuiOutlinedInput-root.MuiInputBase-multiline .MuiOutlinedInput-input, +.MuiFormControl-root .MuiOutlinedInput-root.MuiInputBase-multiline .MuiOutlinedInput-input { + height: auto; + min-height: calc(var(--admin-line-height) * 2); + padding: 0; + line-height: var(--admin-control-line-height); +} + +.MuiFormHelperText-root { + margin: var(--space-1) 0 0; + color: var(--text-tertiary); + font-size: var(--admin-font-size); + line-height: var(--admin-line-height); +} + +.MuiMenuItem-root { + min-height: var(--control-height); + font-size: var(--admin-font-size); +} + +@media (max-width: 760px) { + .region-select.MuiTextField-root, + .region-select.MuiFormControl-root { + width: 100%; + min-width: 0; + flex: 1 1 100%; + } + +} + +.kpi-card { + min-height: 132px; + padding: var(--space-5); + animation: panel-enter var(--motion-slow) var(--ease-emphasized) both; + transition: + border-color var(--motion-base) var(--ease-standard), + box-shadow var(--motion-base) var(--ease-standard), + transform var(--motion-base) var(--ease-emphasized); +} + +.kpi-grid .kpi-card:nth-child(2) { + animation-delay: 40ms; +} + +.kpi-grid .kpi-card:nth-child(3) { + animation-delay: 80ms; +} + +.kpi-grid .kpi-card:nth-child(4) { + animation-delay: 120ms; +} + +.kpi-grid .kpi-card:nth-child(5) { + animation-delay: 160ms; +} + +.kpi-card:hover, +.chart-card:hover, +.table-card:hover, +.server-rail:hover, +.inline-inspector:hover { + border-color: var(--primary-border-strong); + box-shadow: var(--shadow-panel); + transform: translateY(-2px); +} + +.kpi-card__top { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-3); +} + +.kpi-card__label, +.card-title { + color: var(--text-secondary); + font-size: var(--admin-font-size); + font-weight: 650; +} + +.metric-icon { + display: grid; + width: var(--control-height); + height: var(--control-height); + place-items: center; + border-radius: var(--radius-control); + background: var(--primary-surface); + color: var(--primary); + transition: + background var(--motion-base) var(--ease-standard), + transform var(--motion-base) var(--ease-emphasized); +} + +.kpi-card:hover .metric-icon { + transform: scale(1.08) rotate(-4deg); +} + +.metric-icon--success { + background: var(--success-surface); + color: var(--success); +} + +.metric-icon--warning { + background: var(--warning-surface); + color: var(--warning); +} + +.metric-icon--info { + background: var(--info-surface); + color: var(--info); +} + +.kpi-value { + margin-top: var(--space-5); + color: var(--text-primary); + font-size: var(--admin-font-size); + font-weight: 760; + line-height: 1; +} + +.kpi-value span { + margin-left: var(--space-1); + font-size: var(--admin-font-size); + font-weight: 600; +} + +.kpi-sub { + margin-top: var(--space-3); + color: var(--text-tertiary); + font-size: var(--admin-font-size); +} + +.status-ok { + color: var(--success); +} + +.status-warn { + color: var(--warning); +} + +.status-danger { + color: var(--danger); +} + +.status-badge { + display: inline-flex; + height: var(--status-height); + align-items: center; + gap: var(--space-2); + padding: 0 var(--space-3); + border: 1px solid var(--border) !important; + border-radius: var(--radius-pill) !important; + background: var(--neutral-surface) !important; + color: var(--text-secondary) !important; + font-size: var(--admin-font-size) !important; +} + +.status-point { + width: var(--space-2); + height: var(--space-2); + border-radius: var(--radius-pill); + background: var(--stopped); +} + +.status-badge--running, +.status-badge--succeeded { + border-color: var(--success-border) !important; + background: var(--success-surface) !important; + color: var(--success) !important; +} + +.status-badge--running .status-point, +.status-badge--succeeded .status-point { + background: var(--success); + box-shadow: 0 0 var(--space-3) var(--success-glow); +} + +.status-badge--warning { + border-color: var(--warning-border) !important; + background: var(--warning-surface) !important; + color: var(--warning) !important; +} + +.status-badge--warning .status-point { + background: var(--warning); + box-shadow: 0 0 var(--space-3) var(--warning-glow); +} + +.status-badge--error, +.status-badge--danger { + border-color: var(--danger-border) !important; + background: var(--danger-surface) !important; + color: var(--danger) !important; +} + +.status-badge--error .status-point, +.status-badge--danger .status-point { + background: var(--danger); + box-shadow: 0 0 var(--space-3) var(--danger-glow); +} + +.status-badge--stopped { + background: var(--neutral-surface) !important; + color: var(--stopped) !important; +} + +.panel-grid { + display: grid; + grid-template-columns: minmax(0, 1.35fr) minmax(340px, 0.65fr); + gap: var(--space-4); + margin-top: var(--space-4); +} + +.table-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-4); + padding: var(--space-4) var(--space-5); + border-bottom: 1px solid var(--border); +} + +.filter-row { + display: flex; + align-items: center; + gap: var(--space-2); +} + +.filter-chip { + height: var(--status-height); + padding: 0 var(--space-3); + border: 1px solid var(--border); + border-radius: var(--radius-pill); + background: transparent; + color: var(--text-tertiary); + cursor: pointer; + font-size: var(--admin-font-size); + transition: + background var(--motion-fast) var(--ease-standard), + border-color var(--motion-fast) var(--ease-standard), + box-shadow var(--motion-fast) var(--ease-standard), + color var(--motion-fast) var(--ease-standard), + transform var(--motion-base) var(--ease-emphasized); +} + +.filter-chip:hover { + box-shadow: var(--shadow-soft); + transform: translateY(-1px); +} + +.filter-chip--active { + border-color: var(--primary-border-strong); + background: var(--primary-surface); + color: var(--text-primary); +} + +.muted { + color: var(--text-tertiary); +} + +.row-actions { + display: flex; + align-items: center; + gap: var(--space-2); +} + +.empty-state { + padding: var(--space-8) var(--space-5); + color: var(--text-tertiary); + text-align: center; +} + +.login-page { + display: grid; + min-height: 100vh; + place-items: center; + padding: var(--space-6); + background: + var(--login-gradient), + var(--bg-page); +} + +.login-panel { + display: grid; + width: min(420px, 100%); + gap: var(--space-6); + padding: var(--space-7); + border: 1px solid var(--border); + border-radius: var(--radius-card); + background: var(--bg-card); + box-shadow: var(--shadow-panel); + animation: panel-enter var(--motion-slow) var(--ease-emphasized) both; +} + +.login-brand { + display: flex; + align-items: center; + gap: var(--space-3); +} + +.login-brand h1 { + margin: 0; + color: var(--text-primary); + font-size: var(--admin-font-size); +} + +.login-brand p { + margin: var(--space-1) 0 0; + color: var(--text-tertiary); + font-size: var(--admin-font-size); +} + +.login-form, +.form-drawer { + display: grid; + gap: var(--space-4); +} + +.form-drawer { + width: min(420px, 100vw); + padding: var(--space-6); +} + +.form-drawer--wide { + width: min(560px, 100vw); +} + +.form-drawer h2 { + margin: 0 0 var(--space-1); + color: var(--text-primary); + font-size: var(--admin-font-size); +} + +.form-drawer__actions { + display: flex; + justify-content: flex-end; + gap: var(--space-3); + padding-top: var(--space-2); +} + +.side-drawer { + display: flex; + width: min(420px, 100vw); + min-height: 100%; + flex-direction: column; + gap: var(--space-4); + padding: var(--space-6); + background: var(--bg-card); +} + +.side-drawer--wide { + width: min(560px, 100vw); +} + +.side-drawer__header { + display: flex; + min-height: var(--control-height); + align-items: center; + justify-content: space-between; + gap: var(--space-3); +} + +.side-drawer__title { + margin: 0; + color: var(--text-primary); + font-size: var(--admin-font-size); + font-weight: 800; +} + +.side-drawer__body { + display: grid; + gap: var(--space-4); +} + +.side-drawer__actions { + display: flex; + justify-content: flex-end; + gap: var(--space-3); + margin-top: auto; + padding-top: var(--space-2); +} + +.form-section-title { + color: var(--text-secondary); + font-size: var(--admin-font-size); + font-weight: 700; +} + +.permission-menu-list { + display: grid; + gap: var(--space-3); +} + +.permission-menu-section { + display: grid; + gap: var(--space-2); + padding: var(--space-3); + border: 1px solid var(--border); + border-radius: var(--radius-card); + background: var(--bg-card); +} + +.permission-menu-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-3); + min-height: var(--control-height); +} + +.permission-menu-title { + color: var(--text-primary); + font-size: var(--admin-font-size); + font-weight: 700; +} + +.permission-menu-actions { + display: inline-flex; + align-items: center; + gap: var(--space-3); +} + +.permission-menu-toggle { + display: inline-flex; + align-items: center; + gap: var(--space-2); + color: var(--text-secondary); + font-size: var(--admin-font-size); + font-weight: 600; + white-space: nowrap; +} + +.permission-menu-toggle .MuiCheckbox-root, +.permission-check .MuiCheckbox-root { + padding: 0; +} + +.permission-menu-count { + color: var(--text-tertiary); + font-size: var(--admin-font-size); + white-space: nowrap; +} + +.permission-section { + display: grid; + gap: var(--space-3); +} + +.permission-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: var(--space-2); +} + +.permission-grid--compact { + gap: var(--space-2); +} + +.permission-check { + display: flex; + align-items: center; + gap: var(--space-2); + min-height: var(--control-height); + padding: 0 var(--space-3); + border: 1px solid var(--border); + border-radius: var(--radius-control); + background: var(--bg-card-strong); + color: var(--text-secondary); + font-size: var(--admin-font-size); +} + +.batch-actions { + display: flex; + gap: var(--space-2); +} + +.pagination-bar { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-3); + margin-top: var(--space-3); + color: var(--text-tertiary); + font-size: var(--admin-font-size); +} + +.pagination-bar__actions { + display: flex; + align-items: center; + gap: var(--space-2); +} + +.data-state { + display: grid; + min-height: 180px; + place-items: center; + gap: var(--space-3); + border: 1px solid var(--border); + border-radius: var(--radius-card); + background: var(--bg-card); + color: var(--text-tertiary); +} + +.data-state--loading { + padding: var(--space-4); +} + +.data-state--loading .page-skeleton__block { + min-height: 220px; +} + +.data-state__title { + color: var(--text-secondary); + font-size: var(--admin-font-size); + font-weight: 650; +} + +.admin-table { + display: flex; + flex-direction: column; + width: 100%; + min-height: 100%; + min-width: var(--admin-table-min-width, 900px); +} + +.admin-row { + display: grid; + flex: 0 0 auto; + align-items: center; + grid-template-columns: var(--admin-table-columns); + min-height: var(--table-row-height); + padding: 0 var(--space-5); + border-bottom: 1px solid var(--row-border); + color: var(--text-secondary); + column-gap: var(--space-4); + font-size: var(--admin-font-size); + animation: row-enter var(--motion-slow) var(--ease-emphasized) both; +} + +.admin-row[role="button"] { + cursor: pointer; +} + +.admin-row--head { + min-height: var(--table-head-height); + background: var(--bg-card-strong); + color: var(--text-tertiary); + font-size: var(--admin-font-size); + font-weight: 650; +} + +.admin-cell { + min-width: 0; + overflow-wrap: anywhere; +} + +.admin-table .empty-state--table { + flex: 1; +} + +.table-frame { + display: flex; + flex: 1; + min-height: 0; + flex-direction: column; + background: var(--bg-card); +} + +.table-frame .table-scroll { + flex: 1; + min-height: 0; + overflow: auto; +} + +.table-frame .admin-row--head { + position: sticky; + top: 0; + z-index: 1; +} + +.content-panel, +.table-frame, +.table-card { + overflow: hidden; + border: 1px solid var(--border); + border-radius: var(--radius-card); + background: var(--bg-card); +} + +.content-panel { + display: flex; + flex: 1; + flex-direction: column; + min-height: 520px; +} + +.empty-state--table { + min-height: 340px; + align-content: center; +} + +.empty-state__title { + color: var(--text-secondary); + font-weight: 650; +} + +.empty-state__action { + display: flex; + justify-content: center; +} diff --git a/src/styles/tokens.css b/src/styles/tokens.css index 05fa1df..c72efe4 100644 --- a/src/styles/tokens.css +++ b/src/styles/tokens.css @@ -1,12 +1,39 @@ :root { + --admin-font-size: 14px; + --admin-line-height: 20px; + --control-height: 36px; + --status-height: 24px; + --table-head-height: 44px; + --table-row-height: 58px; + --switch-width: 46px; + --switch-height: 26px; + --switch-thumb-size: 20px; + --switch-padding: 3px; + --switch-translate-x: 20px; + + --space-0: 0; + --space-1: 4px; + --space-2: 8px; + --space-3: 12px; + --space-4: 16px; + --space-5: 20px; + --space-6: 24px; + --space-7: 28px; + --space-8: 32px; + + --radius-control: 8px; + --radius-card: 10px; + --radius-dialog: 12px; + --radius-pill: 999px; + --bg-page: #f4f7fb; --bg-sidebar: #ffffff; --bg-header: #ffffff; --bg-card: #ffffff; --bg-card-strong: #f1f5f9; --bg-hover: rgba(37, 99, 235, 0.08); - --bg-input: rgba(248, 250, 252, 0.92); - --bg-input-strong: #ffffff; + --bg-input: rgba(255, 255, 255, 0.58); + --bg-input-strong: rgba(255, 255, 255, 0.74); --bg-flyout: rgba(255, 255, 255, 0.98); --border: #d8e0ec; @@ -16,18 +43,49 @@ --primary: #2563eb; --primary-strong: #1d4ed8; + --primary-hover: rgba(37, 99, 235, 0.08); + --primary-surface: rgba(37, 99, 235, 0.1); + --primary-surface-strong: rgba(37, 99, 235, 0.14); + --primary-border: rgba(37, 99, 235, 0.52); + --primary-border-strong: rgba(37, 99, 235, 0.7); + --primary-border-active: rgba(37, 99, 235, 0.78); --brand-secondary: #0ea5e9; + --brand-gradient: linear-gradient(135deg, var(--primary), var(--brand-secondary)); + --brand-shadow: 0 10px 24px rgba(37, 99, 235, 0.22); + --active-gradient: linear-gradient(90deg, rgba(37, 99, 235, 0.96), rgba(14, 165, 233, 0.34)); + --active-inset: inset 0 0 0 1px rgba(14, 165, 233, 0.2); --success: #16a34a; + --success-surface: rgba(22, 163, 74, 0.1); + --success-border: rgba(22, 163, 74, 0.32); + --success-border-strong: rgba(22, 163, 74, 0.72); + --success-glow: rgba(22, 163, 74, 0.28); --warning: #d97706; + --warning-surface: rgba(217, 119, 6, 0.1); + --warning-border: rgba(217, 119, 6, 0.32); + --warning-glow: rgba(217, 119, 6, 0.28); --danger: #dc2626; + --danger-surface: rgba(220, 38, 38, 0.1); + --danger-border: rgba(220, 38, 38, 0.28); + --danger-border-strong: rgba(220, 38, 38, 0.72); + --danger-glow: rgba(220, 38, 38, 0.28); --info: #0284c7; + --info-surface: rgba(2, 132, 199, 0.1); + --info-surface-strong: rgba(2, 132, 199, 0.14); + --info-border: rgba(2, 132, 199, 0.32); --stopped: #64748b; + --neutral-surface: rgba(100, 116, 139, 0.1); + --neutral-surface-strong: rgba(100, 116, 139, 0.16); + --neutral-border: rgba(100, 116, 139, 0.22); --chart-blue: #2563eb; --chart-green: #16a34a; --chart-red: #dc2626; --chart-orange: #d97706; + --chart-grid: rgba(100, 116, 139, 0.16); + --chart-tooltip-bg: #ffffff; + --chart-tooltip-border: #d8e0ec; + --chart-tooltip-text: #0f172a; --text-primary: #0f172a; --text-secondary: #475569; @@ -40,8 +98,16 @@ --shadow-drawer: 0 24px 60px rgba(15, 23, 42, 0.18); --overlay-scrim: rgba(15, 23, 42, 0.24); --focus-ring: rgba(37, 99, 235, 0.16); + --glass-blur: blur(14px) saturate(140%); + --login-gradient: linear-gradient(180deg, rgba(37, 99, 235, 0.08), rgba(37, 99, 235, 0)); --skeleton-surface: rgba(255, 255, 255, 0.86); --skeleton-shine: rgba(37, 99, 235, 0.1); + --skeleton-gradient: linear-gradient(90deg, transparent, var(--skeleton-shine), transparent); + --z-sidebar: 25; + --z-drawer-scrim: 19; + --z-drawer: 20; + --z-popover: 30; + --z-modal: 40; --motion-fast: 150ms; --motion-base: 220ms; --motion-slow: 320ms; diff --git a/src/theme.js b/src/theme.js index 124f791..83de685 100644 --- a/src/theme.js +++ b/src/theme.js @@ -1,35 +1,42 @@ import { createTheme } from "@mui/material/styles"; +const token = (name) => `var(--${name})`; + export const theme = createTheme({ palette: { mode: "light", primary: { - main: "#2563eb", - contrastText: "#ffffff" + main: "hsl(221, 83%, 53%)", + contrastText: "hsl(0, 0%, 100%)" }, success: { - main: "#16a34a" + main: "hsl(142, 76%, 36%)" }, warning: { - main: "#d97706" + main: "hsl(32, 95%, 44%)" }, error: { - main: "#dc2626" + main: "hsl(0, 72%, 51%)" }, background: { - default: "#f4f7fb", - paper: "#ffffff" + default: "hsl(215, 47%, 97%)", + paper: "hsl(0, 0%, 100%)" }, text: { - primary: "#0f172a", - secondary: "#475569", - disabled: "#94a3b8" + primary: "hsl(222, 47%, 11%)", + secondary: "hsl(215, 25%, 35%)", + disabled: "hsl(215, 16%, 47%)" }, - divider: "#d8e0ec" + divider: "hsl(216, 37%, 89%)" }, typography: { fontFamily: '-apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", sans-serif', + fontSize: 14, + allVariants: { + fontSize: 14 + }, button: { + fontSize: 14, textTransform: "none", fontWeight: 650 } @@ -45,27 +52,27 @@ export const theme = createTheme({ styleOverrides: { root: { minWidth: 0, - height: 36, - borderRadius: 8, - padding: "0 12px", - fontSize: 13, + height: token("control-height"), + borderRadius: token("radius-control"), + padding: `0 ${token("space-3")}`, + fontSize: 14, transition: "background-color 150ms cubic-bezier(0.2, 0, 0, 1), border-color 150ms cubic-bezier(0.2, 0, 0, 1), box-shadow 150ms cubic-bezier(0.2, 0, 0, 1), transform 220ms cubic-bezier(0.16, 1, 0.3, 1)" }, outlined: { - borderColor: "#d8e0ec", - color: "#475569", - backgroundColor: "#ffffff", + borderColor: token("border"), + color: token("text-secondary"), + backgroundColor: token("bg-card"), "&:hover": { - borderColor: "rgba(37, 99, 235, 0.7)", - backgroundColor: "rgba(37, 99, 235, 0.08)", - color: "#0f172a" + borderColor: token("primary-border-strong"), + backgroundColor: token("primary-hover"), + color: token("text-primary") } }, containedPrimary: { - backgroundColor: "#2563eb", + backgroundColor: token("primary"), "&:hover": { - backgroundColor: "#1d4ed8" + backgroundColor: token("primary-strong") } } } @@ -73,11 +80,11 @@ export const theme = createTheme({ MuiCard: { styleOverrides: { root: { - border: "1px solid #d8e0ec", - borderRadius: 10, - backgroundColor: "#ffffff", + border: `1px solid ${token("border")}`, + borderRadius: token("radius-card"), + backgroundColor: token("bg-card"), backgroundImage: "none", - color: "#0f172a", + color: token("text-primary"), transition: "border-color 220ms cubic-bezier(0.2, 0, 0, 1), box-shadow 220ms cubic-bezier(0.2, 0, 0, 1), transform 220ms cubic-bezier(0.16, 1, 0.3, 1)" } @@ -86,10 +93,10 @@ export const theme = createTheme({ MuiChip: { styleOverrides: { root: { - height: 24, - borderRadius: 999, - fontSize: 12, - backgroundColor: "rgba(100, 116, 139, 0.1)", + height: token("status-height"), + borderRadius: token("radius-pill"), + fontSize: 14, + backgroundColor: token("neutral-surface"), transition: "background-color 150ms cubic-bezier(0.2, 0, 0, 1), border-color 150ms cubic-bezier(0.2, 0, 0, 1), color 150ms cubic-bezier(0.2, 0, 0, 1), transform 220ms cubic-bezier(0.16, 1, 0.3, 1)" }, @@ -106,8 +113,8 @@ export const theme = createTheme({ MuiIconButton: { styleOverrides: { root: { - borderRadius: 8, - color: "#475569", + borderRadius: token("radius-control"), + color: token("text-secondary"), transition: "background-color 150ms cubic-bezier(0.2, 0, 0, 1), border-color 150ms cubic-bezier(0.2, 0, 0, 1), color 150ms cubic-bezier(0.2, 0, 0, 1), transform 220ms cubic-bezier(0.16, 1, 0.3, 1)" } @@ -115,9 +122,13 @@ export const theme = createTheme({ }, MuiInputBase: { styleOverrides: { + root: { + fontSize: 14 + }, input: { + fontSize: 14, "&::placeholder": { - color: "#94a3b8", + color: token("text-tertiary"), opacity: 1 } } @@ -126,9 +137,9 @@ export const theme = createTheme({ MuiMenu: { styleOverrides: { paper: { - border: "1px solid #d8e0ec", + border: `1px solid ${token("border")}`, backgroundImage: "none", - backgroundColor: "#ffffff", + backgroundColor: token("bg-card"), transformOrigin: "top right" } } @@ -136,11 +147,11 @@ export const theme = createTheme({ MuiSelect: { styleOverrides: { root: { - color: "#475569", - fontSize: 13 + color: token("text-secondary"), + fontSize: 14 }, icon: { - color: "#475569" + color: token("text-secondary") } } }, @@ -150,47 +161,47 @@ export const theme = createTheme({ }, styleOverrides: { root: { - width: 46, - height: 26, + width: token("switch-width"), + height: token("switch-height"), padding: 0, - margin: "0 8px", + margin: `0 ${token("space-2")}`, overflow: "visible" }, switchBase: { - padding: 3, - color: "#ffffff", + padding: token("switch-padding"), + color: token("active-contrast"), transition: "transform 220ms cubic-bezier(0.16, 1, 0.3, 1)", "&.Mui-checked": { - color: "#ffffff", - transform: "translateX(20px)", + color: token("active-contrast"), + transform: `translateX(${token("switch-translate-x")})`, "& + .MuiSwitch-track": { - borderColor: "rgba(37, 99, 235, 0.36)", - background: "linear-gradient(135deg, #2563eb, #0ea5e9)", - boxShadow: "0 8px 20px rgba(37, 99, 235, 0.22)", + borderColor: token("primary-border"), + background: token("brand-gradient"), + boxShadow: token("shadow-soft"), opacity: 1 }, "& .MuiSwitch-thumb": { - boxShadow: "0 4px 12px rgba(15, 23, 42, 0.18)" + boxShadow: token("shadow-soft") } }, "&.Mui-disabled": { - color: "#ffffff", + color: token("active-contrast"), "& + .MuiSwitch-track": { opacity: 0.5 } } }, thumb: { - width: 20, - height: 20, - backgroundColor: "#ffffff", - boxShadow: "0 3px 10px rgba(15, 23, 42, 0.18)" + width: token("switch-thumb-size"), + height: token("switch-thumb-size"), + backgroundColor: token("active-contrast"), + boxShadow: token("shadow-soft") }, track: { - border: "1px solid #d8e0ec", - borderRadius: 999, - backgroundColor: "#e2e8f0", - boxShadow: "inset 0 1px 2px rgba(15, 23, 42, 0.06)", + border: `1px solid ${token("border")}`, + borderRadius: token("radius-pill"), + backgroundColor: token("bg-card-strong"), + boxShadow: "none", opacity: 1, transition: "background 220ms cubic-bezier(0.2, 0, 0, 1), border-color 220ms cubic-bezier(0.2, 0, 0, 1), box-shadow 220ms cubic-bezier(0.2, 0, 0, 1)" @@ -200,24 +211,45 @@ export const theme = createTheme({ MuiOutlinedInput: { styleOverrides: { notchedOutline: { - borderColor: "#d8e0ec" + borderColor: token("border") }, root: { - backgroundColor: "#ffffff", + backgroundColor: token("bg-input"), + backdropFilter: token("glass-blur"), transition: - "background-color 150ms cubic-bezier(0.2, 0, 0, 1), box-shadow 220ms cubic-bezier(0.2, 0, 0, 1), transform 220ms cubic-bezier(0.16, 1, 0.3, 1)", + "background-color 150ms cubic-bezier(0.2, 0, 0, 1), border-color 220ms cubic-bezier(0.2, 0, 0, 1)", "&:hover .MuiOutlinedInput-notchedOutline": { - borderColor: "rgba(37, 99, 235, 0.7)" + borderColor: token("primary-border-strong") }, "&.Mui-focused .MuiOutlinedInput-notchedOutline": { - borderColor: "#2563eb" + borderColor: token("primary") }, "&.Mui-focused": { - boxShadow: "0 0 0 3px rgba(37, 99, 235, 0.16)", - transform: "translateY(-1px)" + boxShadow: "none" } } } + }, + MuiFormLabel: { + styleOverrides: { + root: { + fontSize: 14 + } + } + }, + MuiFormHelperText: { + styleOverrides: { + root: { + fontSize: 14 + } + } + }, + MuiMenuItem: { + styleOverrides: { + root: { + fontSize: 14 + } + } } } }); diff --git a/src/utils/time.js b/src/utils/time.js deleted file mode 100644 index 4e32e5c..0000000 --- a/src/utils/time.js +++ /dev/null @@ -1,8 +0,0 @@ -export function formatTime(date = new Date()) { - return date.toLocaleTimeString("zh-CN", { - hour12: false, - hour: "2-digit", - minute: "2-digit", - second: "2-digit" - }); -} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1c407b7 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": false, + "jsx": "react-jsx", + "module": "ESNext", + "moduleResolution": "Bundler", + "noEmit": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "target": "ES2022", + "types": ["node", "vitest/globals", "vite/client"], + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + }, + "include": ["src", "scripts", "contracts"] +} diff --git a/vite.config.js b/vite.config.js index 18d24d5..f3105d3 100644 --- a/vite.config.js +++ b/vite.config.js @@ -2,6 +2,17 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; export default defineConfig({ + css: { + modules: { + generateScopedName: "hy-[name]__[local]__[hash:base64:5]", + localsConvention: "camelCaseOnly" + } + }, + resolve: { + alias: { + "@": new URL("./src", import.meta.url).pathname + } + }, plugins: [react()], server: { port: 7001,