Sfoglia il codice sorgente

fix: update react version

周玉环 2 giorni fa
parent
commit
39cdd7fefd

+ 4 - 4
package.json

@@ -18,16 +18,16 @@
     "@emotion/styled": "^11.14.1",
     "axios": "^1.13.2",
     "crypto-js": "^4.2.0",
-    "react": "^19.2.0",
-    "react-dom": "^19.2.0",
+    "react": "^19.2.3",
+    "react-dom": "^19.2.3",
     "react-router-dom": "^7.9.6"
   },
   "devDependencies": {
     "@eslint/js": "^9.39.1",
     "@types/crypto-js": "^4.2.2",
     "@types/node": "^24.10.1",
-    "@types/react": "^18.3.12",
-    "@types/react-dom": "^18.3.1",
+    "@types/react": "^19.2.7",
+    "@types/react-dom": "^19.2.3",
     "@vitejs/plugin-react": "^5.1.1",
     "eslint": "^9.39.1",
     "eslint-plugin-react-hooks": "^7.0.1",

+ 106 - 99
pnpm-lock.yaml

@@ -10,7 +10,7 @@ importers:
     dependencies:
       '@contentful/f36-components':
         specifier: ^5.9.0
-        version: 5.9.0(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+        version: 5.9.0(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
       '@contentful/f36-icons':
         specifier: ^5.9.0
         version: 5.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -22,10 +22,10 @@ importers:
         version: 5.1.0
       '@emotion/react':
         specifier: ^11.14.0
-        version: 11.14.0(@types/react@18.3.27)(react@19.2.3)
+        version: 11.14.0(@types/react@19.2.7)(react@19.2.3)
       '@emotion/styled':
         specifier: ^11.14.1
-        version: 11.14.1(@emotion/react@11.14.0(@types/react@18.3.27)(react@19.2.3))(@types/react@18.3.27)(react@19.2.3)
+        version: 11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3)
       axios:
         specifier: ^1.13.2
         version: 1.13.2
@@ -33,10 +33,10 @@ importers:
         specifier: ^4.2.0
         version: 4.2.0
       react:
-        specifier: ^19.2.0
+        specifier: ^19.2.3
         version: 19.2.3
       react-dom:
-        specifier: ^19.2.0
+        specifier: ^19.2.3
         version: 19.2.3(react@19.2.3)
       react-router-dom:
         specifier: ^7.9.6
@@ -52,11 +52,11 @@ importers:
         specifier: ^24.10.1
         version: 24.10.1
       '@types/react':
-        specifier: ^18.3.12
-        version: 18.3.27
+        specifier: ^19.2.7
+        version: 19.2.7
       '@types/react-dom':
-        specifier: ^18.3.1
-        version: 18.3.7(@types/react@18.3.27)
+        specifier: ^19.2.3
+        version: 19.2.3(@types/react@19.2.7)
       '@vitejs/plugin-react':
         specifier: ^5.1.1
         version: 5.1.1(vite@7.2.6(@types/node@24.10.1))
@@ -1075,10 +1075,10 @@ packages:
   '@types/prop-types@15.7.15':
     resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==}
 
-  '@types/react-dom@18.3.7':
-    resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==}
+  '@types/react-dom@19.2.3':
+    resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
     peerDependencies:
-      '@types/react': ^18.0.0
+      '@types/react': ^19.2.0
 
   '@types/react-modal@3.16.3':
     resolution: {integrity: sha512-xXuGavyEGaFQDgBv4UVm8/ZsG+qxeQ7f77yNrW3n+1J6XAstUy5rYHeIHPh1KzsGc6IkCIdu6lQ2xWzu1jBTLg==}
@@ -1086,6 +1086,9 @@ packages:
   '@types/react@18.3.27':
     resolution: {integrity: sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==}
 
+  '@types/react@19.2.7':
+    resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==}
+
   '@typescript-eslint/eslint-plugin@8.48.0':
     resolution: {integrity: sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -2215,7 +2218,7 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@contentful/f36-components@5.9.0(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+  '@contentful/f36-components@5.9.0(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
     dependencies:
       '@contentful/f36-accordion': 5.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
       '@contentful/f36-asset': 5.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -2227,7 +2230,7 @@ snapshots:
       '@contentful/f36-collapse': 5.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
       '@contentful/f36-copybutton': 5.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
       '@contentful/f36-core': 5.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
-      '@contentful/f36-datepicker': 5.9.0(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+      '@contentful/f36-datepicker': 5.9.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
       '@contentful/f36-datetime': 5.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
       '@contentful/f36-drag-handle': 5.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
       '@contentful/f36-empty-state': 5.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -2241,7 +2244,7 @@ snapshots:
       '@contentful/f36-list': 5.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
       '@contentful/f36-menu': 5.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
       '@contentful/f36-modal': 5.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
-      '@contentful/f36-multiselect': 5.9.0(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+      '@contentful/f36-multiselect': 5.9.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
       '@contentful/f36-navlist': 5.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
       '@contentful/f36-note': 5.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
       '@contentful/f36-notification': 5.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -2252,7 +2255,7 @@ snapshots:
       '@contentful/f36-skeleton': 5.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
       '@contentful/f36-spinner': 5.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
       '@contentful/f36-table': 5.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
-      '@contentful/f36-tabs': 5.9.0(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+      '@contentful/f36-tabs': 5.9.0(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
       '@contentful/f36-text-link': 5.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
       '@contentful/f36-tokens': 5.1.0
       '@contentful/f36-tooltip': 5.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -2263,8 +2266,8 @@ snapshots:
       react: 19.2.3
       react-dom: 19.2.3(react@19.2.3)
     optionalDependencies:
-      '@types/react': 18.3.27
-      '@types/react-dom': 18.3.7(@types/react@18.3.27)
+      '@types/react': 19.2.7
+      '@types/react-dom': 19.2.3(@types/react@19.2.7)
     transitivePeerDependencies:
       - supports-color
 
@@ -2293,7 +2296,7 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@contentful/f36-datepicker@5.9.0(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+  '@contentful/f36-datepicker@5.9.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
     dependencies:
       '@contentful/f36-button': 5.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
       '@contentful/f36-core': 5.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -2307,7 +2310,7 @@ snapshots:
       react: 19.2.3
       react-day-picker: 8.10.1(date-fns@2.30.0)(react@19.2.3)
       react-dom: 19.2.3(react@19.2.3)
-      react-focus-lock: 2.13.7(@types/react@18.3.27)(react@19.2.3)
+      react-focus-lock: 2.13.7(@types/react@19.2.7)(react@19.2.3)
     transitivePeerDependencies:
       - '@types/react'
       - supports-color
@@ -2473,7 +2476,7 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@contentful/f36-multiselect@5.9.0(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+  '@contentful/f36-multiselect@5.9.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
     dependencies:
       '@babel/runtime': 7.28.4
       '@contentful/f36-button': 5.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -2489,7 +2492,7 @@ snapshots:
       emotion: 10.0.27
       react: 19.2.3
       react-dom: 19.2.3(react@19.2.3)
-      react-focus-lock: 2.13.7(@types/react@18.3.27)(react@19.2.3)
+      react-focus-lock: 2.13.7(@types/react@19.2.7)(react@19.2.3)
     transitivePeerDependencies:
       - '@types/react'
       - supports-color
@@ -2641,11 +2644,11 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@contentful/f36-tabs@5.9.0(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+  '@contentful/f36-tabs@5.9.0(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
     dependencies:
       '@contentful/f36-core': 5.9.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
       '@contentful/f36-tokens': 5.1.0
-      '@radix-ui/react-tabs': 1.1.13(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+      '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
       emotion: 10.0.27
       react: 19.2.3
       react-dom: 19.2.3(react@19.2.3)
@@ -2789,7 +2792,7 @@ snapshots:
 
   '@emotion/memoize@0.9.0': {}
 
-  '@emotion/react@11.14.0(@types/react@18.3.27)(react@19.2.3)':
+  '@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3)':
     dependencies:
       '@babel/runtime': 7.28.4
       '@emotion/babel-plugin': 11.13.5
@@ -2801,7 +2804,7 @@ snapshots:
       hoist-non-react-statics: 3.3.2
       react: 19.2.3
     optionalDependencies:
-      '@types/react': 18.3.27
+      '@types/react': 19.2.7
     transitivePeerDependencies:
       - supports-color
 
@@ -2825,18 +2828,18 @@ snapshots:
 
   '@emotion/sheet@1.4.0': {}
 
-  '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.27)(react@19.2.3))(@types/react@18.3.27)(react@19.2.3)':
+  '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3)':
     dependencies:
       '@babel/runtime': 7.28.4
       '@emotion/babel-plugin': 11.13.5
       '@emotion/is-prop-valid': 1.4.0
-      '@emotion/react': 11.14.0(@types/react@18.3.27)(react@19.2.3)
+      '@emotion/react': 11.14.0(@types/react@19.2.7)(react@19.2.3)
       '@emotion/serialize': 1.3.3
       '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.3)
       '@emotion/utils': 1.4.2
       react: 19.2.3
     optionalDependencies:
-      '@types/react': 18.3.27
+      '@types/react': 19.2.7
     transitivePeerDependencies:
       - supports-color
 
@@ -3021,128 +3024,128 @@ snapshots:
 
   '@radix-ui/primitive@1.1.3': {}
 
-  '@radix-ui/react-collection@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+  '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
     dependencies:
-      '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.3)
-      '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.3)
-      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
-      '@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@19.2.3)
+      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.3)
+      '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.3)
+      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+      '@radix-ui/react-slot': 1.2.3(@types/react@19.2.7)(react@19.2.3)
       react: 19.2.3
       react-dom: 19.2.3(react@19.2.3)
     optionalDependencies:
-      '@types/react': 18.3.27
-      '@types/react-dom': 18.3.7(@types/react@18.3.27)
+      '@types/react': 19.2.7
+      '@types/react-dom': 19.2.3(@types/react@19.2.7)
 
-  '@radix-ui/react-compose-refs@1.1.2(@types/react@18.3.27)(react@19.2.3)':
+  '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.7)(react@19.2.3)':
     dependencies:
       react: 19.2.3
     optionalDependencies:
-      '@types/react': 18.3.27
+      '@types/react': 19.2.7
 
-  '@radix-ui/react-context@1.1.2(@types/react@18.3.27)(react@19.2.3)':
+  '@radix-ui/react-context@1.1.2(@types/react@19.2.7)(react@19.2.3)':
     dependencies:
       react: 19.2.3
     optionalDependencies:
-      '@types/react': 18.3.27
+      '@types/react': 19.2.7
 
-  '@radix-ui/react-direction@1.1.1(@types/react@18.3.27)(react@19.2.3)':
+  '@radix-ui/react-direction@1.1.1(@types/react@19.2.7)(react@19.2.3)':
     dependencies:
       react: 19.2.3
     optionalDependencies:
-      '@types/react': 18.3.27
+      '@types/react': 19.2.7
 
-  '@radix-ui/react-id@1.1.1(@types/react@18.3.27)(react@19.2.3)':
+  '@radix-ui/react-id@1.1.1(@types/react@19.2.7)(react@19.2.3)':
     dependencies:
-      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@19.2.3)
+      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.3)
       react: 19.2.3
     optionalDependencies:
-      '@types/react': 18.3.27
+      '@types/react': 19.2.7
 
-  '@radix-ui/react-presence@1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+  '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
     dependencies:
-      '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.3)
-      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@19.2.3)
+      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.3)
+      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.3)
       react: 19.2.3
       react-dom: 19.2.3(react@19.2.3)
     optionalDependencies:
-      '@types/react': 18.3.27
-      '@types/react-dom': 18.3.7(@types/react@18.3.27)
+      '@types/react': 19.2.7
+      '@types/react-dom': 19.2.3(@types/react@19.2.7)
 
-  '@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+  '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
     dependencies:
-      '@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@19.2.3)
+      '@radix-ui/react-slot': 1.2.3(@types/react@19.2.7)(react@19.2.3)
       react: 19.2.3
       react-dom: 19.2.3(react@19.2.3)
     optionalDependencies:
-      '@types/react': 18.3.27
-      '@types/react-dom': 18.3.7(@types/react@18.3.27)
+      '@types/react': 19.2.7
+      '@types/react-dom': 19.2.3(@types/react@19.2.7)
 
-  '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+  '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
     dependencies:
       '@radix-ui/primitive': 1.1.3
-      '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
-      '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.3)
-      '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.3)
-      '@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@19.2.3)
-      '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@19.2.3)
-      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
-      '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@19.2.3)
-      '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.3)
+      '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.3)
+      '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.3)
+      '@radix-ui/react-direction': 1.1.1(@types/react@19.2.7)(react@19.2.3)
+      '@radix-ui/react-id': 1.1.1(@types/react@19.2.7)(react@19.2.3)
+      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+      '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.3)
+      '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.3)
       react: 19.2.3
       react-dom: 19.2.3(react@19.2.3)
     optionalDependencies:
-      '@types/react': 18.3.27
-      '@types/react-dom': 18.3.7(@types/react@18.3.27)
+      '@types/react': 19.2.7
+      '@types/react-dom': 19.2.3(@types/react@19.2.7)
 
-  '@radix-ui/react-slot@1.2.3(@types/react@18.3.27)(react@19.2.3)':
+  '@radix-ui/react-slot@1.2.3(@types/react@19.2.7)(react@19.2.3)':
     dependencies:
-      '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.3)
+      '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.3)
       react: 19.2.3
     optionalDependencies:
-      '@types/react': 18.3.27
+      '@types/react': 19.2.7
 
-  '@radix-ui/react-tabs@1.1.13(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+  '@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
     dependencies:
       '@radix-ui/primitive': 1.1.3
-      '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.3)
-      '@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@19.2.3)
-      '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@19.2.3)
-      '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
-      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
-      '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
-      '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.3)
+      '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.3)
+      '@radix-ui/react-direction': 1.1.1(@types/react@19.2.7)(react@19.2.3)
+      '@radix-ui/react-id': 1.1.1(@types/react@19.2.7)(react@19.2.3)
+      '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+      '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+      '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+      '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.3)
       react: 19.2.3
       react-dom: 19.2.3(react@19.2.3)
     optionalDependencies:
-      '@types/react': 18.3.27
-      '@types/react-dom': 18.3.7(@types/react@18.3.27)
+      '@types/react': 19.2.7
+      '@types/react-dom': 19.2.3(@types/react@19.2.7)
 
-  '@radix-ui/react-use-callback-ref@1.1.1(@types/react@18.3.27)(react@19.2.3)':
+  '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.7)(react@19.2.3)':
     dependencies:
       react: 19.2.3
     optionalDependencies:
-      '@types/react': 18.3.27
+      '@types/react': 19.2.7
 
-  '@radix-ui/react-use-controllable-state@1.2.2(@types/react@18.3.27)(react@19.2.3)':
+  '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.7)(react@19.2.3)':
     dependencies:
-      '@radix-ui/react-use-effect-event': 0.0.2(@types/react@18.3.27)(react@19.2.3)
-      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@19.2.3)
+      '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.7)(react@19.2.3)
+      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.3)
       react: 19.2.3
     optionalDependencies:
-      '@types/react': 18.3.27
+      '@types/react': 19.2.7
 
-  '@radix-ui/react-use-effect-event@0.0.2(@types/react@18.3.27)(react@19.2.3)':
+  '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.7)(react@19.2.3)':
     dependencies:
-      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@19.2.3)
+      '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.3)
       react: 19.2.3
     optionalDependencies:
-      '@types/react': 18.3.27
+      '@types/react': 19.2.7
 
-  '@radix-ui/react-use-layout-effect@1.1.1(@types/react@18.3.27)(react@19.2.3)':
+  '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.7)(react@19.2.3)':
     dependencies:
       react: 19.2.3
     optionalDependencies:
-      '@types/react': 18.3.27
+      '@types/react': 19.2.7
 
   '@rolldown/pluginutils@1.0.0-beta.47': {}
 
@@ -3247,9 +3250,9 @@ snapshots:
 
   '@types/prop-types@15.7.15': {}
 
-  '@types/react-dom@18.3.7(@types/react@18.3.27)':
+  '@types/react-dom@19.2.3(@types/react@19.2.7)':
     dependencies:
-      '@types/react': 18.3.27
+      '@types/react': 19.2.7
 
   '@types/react-modal@3.16.3':
     dependencies:
@@ -3260,6 +3263,10 @@ snapshots:
       '@types/prop-types': 15.7.15
       csstype: 3.2.3
 
+  '@types/react@19.2.7':
+    dependencies:
+      csstype: 3.2.3
+
   '@typescript-eslint/eslint-plugin@8.48.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3)':
     dependencies:
       '@eslint-community/regexpp': 4.12.2
@@ -3964,17 +3971,17 @@ snapshots:
 
   react-fast-compare@3.2.2: {}
 
-  react-focus-lock@2.13.7(@types/react@18.3.27)(react@19.2.3):
+  react-focus-lock@2.13.7(@types/react@19.2.7)(react@19.2.3):
     dependencies:
       '@babel/runtime': 7.28.4
       focus-lock: 1.3.6
       prop-types: 15.8.1
       react: 19.2.3
       react-clientside-effect: 1.2.8(react@19.2.3)
-      use-callback-ref: 1.3.3(@types/react@18.3.27)(react@19.2.3)
-      use-sidecar: 1.1.3(@types/react@18.3.27)(react@19.2.3)
+      use-callback-ref: 1.3.3(@types/react@19.2.7)(react@19.2.3)
+      use-sidecar: 1.1.3(@types/react@19.2.7)(react@19.2.3)
     optionalDependencies:
-      '@types/react': 18.3.27
+      '@types/react': 19.2.7
 
   react-is@16.13.1: {}
 
@@ -4123,20 +4130,20 @@ snapshots:
     dependencies:
       punycode: 2.3.1
 
-  use-callback-ref@1.3.3(@types/react@18.3.27)(react@19.2.3):
+  use-callback-ref@1.3.3(@types/react@19.2.7)(react@19.2.3):
     dependencies:
       react: 19.2.3
       tslib: 2.8.1
     optionalDependencies:
-      '@types/react': 18.3.27
+      '@types/react': 19.2.7
 
-  use-sidecar@1.1.3(@types/react@18.3.27)(react@19.2.3):
+  use-sidecar@1.1.3(@types/react@19.2.7)(react@19.2.3):
     dependencies:
       detect-node-es: 1.1.0
       react: 19.2.3
       tslib: 2.8.1
     optionalDependencies:
-      '@types/react': 18.3.27
+      '@types/react': 19.2.7
 
   vite@7.2.6(@types/node@24.10.1):
     dependencies:

+ 1 - 16
src/App.tsx

@@ -24,7 +24,7 @@ function AppRouter() {
     return createAppRouter(menus);
   }, [menus]);
 
-  if (loading) {
+  if (loading || !router) {
     return (
       <div
         style={{
@@ -39,21 +39,6 @@ function AppRouter() {
     );
   }
 
-  if (!router) {
-    return (
-      <div
-        style={{
-          display: "flex",
-          justifyContent: "center",
-          alignItems: "center",
-          height: "100vh",
-        }}
-      >
-        无可用菜单
-      </div>
-    );
-  }
-
   return <RouterProvider router={router} />;
 }
 

+ 70 - 0
src/config/systemMenus.ts

@@ -0,0 +1,70 @@
+/**
+ * 系统管理菜单配置
+ */
+import type { JeecgMenu } from "@/types/menu";
+
+// 系统管理菜单配置
+export const systemMenus: JeecgMenu[] = [
+  {
+    id: "system",
+    name: "system",
+    path: "/system",
+    component: "system/index",
+    meta: {
+      title: "系统管理",
+      icon: "SettingsIcon",
+      hideMenu: false,
+      keepAlive: false,
+    },
+    children: [
+      {
+        id: "system-menu",
+        name: "system-menu",
+        path: "/system/menu",
+        component: "system/menu/index",
+        meta: {
+          title: "菜单管理",
+          icon: "MenuIcon",
+          hideMenu: false,
+          keepAlive: true,
+        },
+      },
+      {
+        id: "system-user",
+        name: "system-user",
+        path: "/system/user",
+        component: "system/user/index",
+        meta: {
+          title: "用户管理",
+          icon: "PersonIcon",
+          hideMenu: false,
+          keepAlive: true,
+        },
+      },
+      {
+        id: "system-role",
+        name: "system-role",
+        path: "/system/role",
+        component: "system/role/index",
+        meta: {
+          title: "角色管理",
+          icon: "TeamIcon",
+          hideMenu: false,
+          keepAlive: true,
+        },
+      },
+      {
+        id: "system-log",
+        name: "system-log",
+        path: "/system/log",
+        component: "system/log/index",
+        meta: {
+          title: "日志管理",
+          icon: "DocumentIcon",
+          hideMenu: false,
+          keepAlive: true,
+        },
+      },
+    ],
+  },
+];

+ 7 - 0
src/contexts/AuthContext.tsx

@@ -3,6 +3,7 @@ import type { ReactNode } from "react";
 import type { JeecgMenu } from "@/types/menu";
 import { getUserPermissions } from "@/services/api";
 import { isPublicRoute } from "@/router/publicRoutes.tsx";
+// import { systemMenus } from "@/config/systemMenus";
 
 interface AuthContextType {
   menus: JeecgMenu[];
@@ -28,8 +29,14 @@ export function AuthProvider({ children }: { children: ReactNode }) {
         getUserPermissions(),
       ]);
       setMenus(menusData.menu);
+
+      // 合并系统菜单和用户菜单
+      // const allMenus = [...(menusData.menu || []), ...systemMenus];
+      // setMenus(allMenus);
     } catch (error) {
       console.error("加载权限数据失败:", error);
+      // 如果获取用户菜单失败,至少显示系统管理菜单
+      // setMenus(systemMenus);
     } finally {
       setLoading(false);
     }

+ 63 - 0
src/pages/system-test/index.tsx

@@ -0,0 +1,63 @@
+/**
+ * 系统管理测试页面
+ */
+import { Card, Stack, Text, Button, Flex } from "@contentful/f36-components";
+import { Link } from "react-router-dom";
+
+export default function SystemTest() {
+  return (
+    <Card>
+      <Stack spacing="spacingM">
+        <Text fontSize="fontSizeL" fontWeight="fontWeightMedium">
+          系统管理功能测试
+        </Text>
+        
+        <Text>
+          这是一个测试页面,用于验证系统管理功能是否正常工作。
+        </Text>
+
+        <Stack spacing="spacingS">
+          <Text fontWeight="fontWeightMedium">可用的系统管理功能:</Text>
+          
+          <Flex gap="spacingS" flexWrap="wrap">
+            <Button as={Link} to="/system/menu" variant="primary">
+              菜单管理
+            </Button>
+            <Button as={Link} to="/system/user" variant="primary">
+              用户管理
+            </Button>
+            <Button as={Link} to="/system/role" variant="primary">
+              角色管理
+            </Button>
+            <Button as={Link} to="/system/log" variant="primary">
+              日志管理
+            </Button>
+          </Flex>
+        </Stack>
+
+        <Stack spacing="spacingS">
+          <Text fontWeight="fontWeightMedium">功能特点:</Text>
+          <ul>
+            <li>使用 Contentful F36 组件库,保持一致的设计风格</li>
+            <li>完整的 CRUD 操作支持</li>
+            <li>分页、搜索、批量操作</li>
+            <li>权限管理和角色分配</li>
+            <li>操作日志记录和查看</li>
+            <li>响应式设计,适配不同屏幕尺寸</li>
+          </ul>
+        </Stack>
+
+        <Stack spacing="spacingS">
+          <Text fontWeight="fontWeightMedium">技术栈:</Text>
+          <ul>
+            <li>React 19 + TypeScript</li>
+            <li>Contentful F36 Components</li>
+            <li>React Router v7</li>
+            <li>Axios HTTP 客户端</li>
+            <li>Vite 构建工具</li>
+          </ul>
+        </Stack>
+      </Stack>
+    </Card>
+  );
+}

+ 8 - 0
src/pages/system/index.tsx

@@ -0,0 +1,8 @@
+/**
+ * 系统管理主页面
+ */
+import { Outlet } from "react-router-dom";
+
+export default function SystemManagement() {
+  return <Outlet />;
+}

+ 475 - 0
src/pages/system/log/index.tsx

@@ -0,0 +1,475 @@
+/**
+ * 日志管理页面
+ */
+import { useState, useEffect } from "react";
+import {
+  Card,
+  Table,
+  Button,
+  Stack,
+  Flex,
+  Text,
+  Badge,
+  Modal,
+  Form,
+  FormControl,
+  TextInput,
+  Select,
+  Pagination,
+} from "@contentful/f36-components";
+import { logApi } from "@/services";
+import type { SysLog, LogQueryParams } from "@/types/system";
+
+export default function LogManagement() {
+  const [loading, setLoading] = useState(false);
+  const [logList, setLogList] = useState<SysLog[]>([]);
+  const [selectedRows, setSelectedRows] = useState<string[]>([]);
+  const [detailModalVisible, setDetailModalVisible] = useState(false);
+  const [currentLog, setCurrentLog] = useState<SysLog | null>(null);
+
+  // 分页参数
+  const [pagination, setPagination] = useState({
+    current: 1,
+    pageSize: 10,
+    total: 0,
+  });
+
+  // 查询参数
+  const [queryParams, setQueryParams] = useState<LogQueryParams>({
+    pageNo: 1,
+    pageSize: 10,
+  });
+
+  // 获取日志列表
+  const fetchLogList = async () => {
+    setLoading(true);
+    try {
+      const response = await logApi.getLogList(queryParams);
+      setLogList(response.records || []);
+      setPagination({
+        current: response.current,
+        pageSize: response.size,
+        total: response.total,
+      });
+    } catch (error) {
+      console.error("获取日志列表失败:", error);
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  useEffect(() => {
+    fetchLogList();
+  }, [queryParams]);
+
+  // 处理搜索
+  const handleSearch = () => {
+    setQueryParams({ ...queryParams, pageNo: 1 });
+  };
+
+  // 处理重置
+  const handleReset = () => {
+    setQueryParams({
+      pageNo: 1,
+      pageSize: 10,
+    });
+  };
+
+  // 处理删除日志
+  const handleDelete = async (id: string) => {
+    try {
+      await logApi.deleteLog(id);
+      fetchLogList();
+    } catch (error) {
+      console.error("删除日志失败:", error);
+    }
+  };
+
+  // 处理批量删除
+  const handleBatchDelete = async () => {
+    if (selectedRows.length === 0) return;
+    try {
+      await logApi.deleteBatchLog(selectedRows);
+      setSelectedRows([]);
+      fetchLogList();
+    } catch (error) {
+      console.error("批量删除日志失败:", error);
+    }
+  };
+
+  // 处理清空日志
+  const handleDeleteAll = async () => {
+    try {
+      await logApi.deleteAllLog();
+      fetchLogList();
+    } catch (error) {
+      console.error("清空日志失败:", error);
+    }
+  };
+
+  // 处理查看详情
+  const handleViewDetail = (record: SysLog) => {
+    setCurrentLog(record);
+    setDetailModalVisible(true);
+  };
+
+  // 渲染日志类型
+  const renderLogType = (type?: string) => {
+    const typeMap = {
+      "1": { label: "登录日志", variant: "primary" as const },
+      "2": { label: "操作日志", variant: "secondary" as const },
+    };
+    const config = typeMap[type as keyof typeof typeMap];
+    return config ? (
+      <Badge variant={config.variant}>{config.label}</Badge>
+    ) : (
+      <Badge variant="secondary">未知</Badge>
+    );
+  };
+
+  // 渲染操作类型
+  const renderOperateType = (type?: string) => {
+    const typeMap = {
+      "1": { label: "添加", variant: "positive" as const },
+      "2": { label: "修改", variant: "warning" as const },
+      "3": { label: "删除", variant: "negative" as const },
+      "4": { label: "查询", variant: "secondary" as const },
+      "5": { label: "其他", variant: "secondary" as const },
+    };
+    const config = typeMap[type as keyof typeof typeMap];
+    return config ? (
+      <Badge variant={config.variant}>{config.label}</Badge>
+    ) : (
+      <Badge variant="secondary">未知</Badge>
+    );
+  };
+
+  // 表格列定义
+  const columns = [
+    {
+      header: "日志类型",
+      accessor: "logType" as keyof SysLog,
+      render: (value: any) => renderLogType(value),
+    },
+    {
+      header: "操作类型",
+      accessor: "operateType" as keyof SysLog,
+      render: (value: any) => renderOperateType(value),
+    },
+    {
+      header: "操作用户",
+      accessor: "username" as keyof SysLog,
+      render: (value: any) => value || "-",
+    },
+    {
+      header: "请求方法",
+      accessor: "method" as keyof SysLog,
+      render: (value: any) => (
+        <Badge variant="secondary">{value || "-"}</Badge>
+      ),
+    },
+    {
+      header: "请求URL",
+      accessor: "requestUrl" as keyof SysLog,
+      render: (value: any) => (
+        <Text fontColor="gray600" style={{ maxWidth: "200px", overflow: "hidden", textOverflow: "ellipsis" }}>
+          {value || "-"}
+        </Text>
+      ),
+    },
+    {
+      header: "IP地址",
+      accessor: "ip" as keyof SysLog,
+      render: (value: any) => value || "-",
+    },
+    {
+      header: "耗时(ms)",
+      accessor: "costTime" as keyof SysLog,
+      render: (value: any) => value?.toString() || "-",
+    },
+    {
+      header: "创建时间",
+      accessor: "createTime" as keyof SysLog,
+      render: (value: any) => value?.substring(0, 19) || "-",
+    },
+    {
+      header: "操作",
+      accessor: "id" as keyof SysLog,
+      render: (_: any, record: SysLog) => (
+        <Flex gap="spacingXs">
+          <Button
+            variant="secondary"
+            size="small"
+            onClick={() => handleViewDetail(record)}
+          >
+            详情
+          </Button>
+          <Button
+            variant="negative"
+            size="small"
+            onClick={() => handleDelete(record.id)}
+          >
+            删除
+          </Button>
+        </Flex>
+      ),
+    },
+  ];
+
+  return (
+    <Card>
+      <Stack spacing="spacingM">
+        {/* 页面标题 */}
+        <Flex justifyContent="space-between" alignItems="center">
+          <Text fontSize="fontSizeL" fontWeight="fontWeightMedium">
+            日志管理
+          </Text>
+        </Flex>
+
+        {/* 查询条件 */}
+        <Card>
+          <Form>
+            <Flex gap="spacingM" alignItems="end" flexWrap="wrap">
+              <FormControl>
+                <FormControl.Label>日志类型</FormControl.Label>
+                <Select
+                  value={queryParams.logType || ""}
+                  onChange={(e) =>
+                    setQueryParams({ ...queryParams, logType: e.target.value })
+                  }
+                >
+                  <Select.Option value="">全部</Select.Option>
+                  <Select.Option value="1">登录日志</Select.Option>
+                  <Select.Option value="2">操作日志</Select.Option>
+                </Select>
+              </FormControl>
+
+              <FormControl>
+                <FormControl.Label>操作类型</FormControl.Label>
+                <Select
+                  value={queryParams.operateType || ""}
+                  onChange={(e) =>
+                    setQueryParams({ ...queryParams, operateType: e.target.value })
+                  }
+                >
+                  <Select.Option value="">全部</Select.Option>
+                  <Select.Option value="1">添加</Select.Option>
+                  <Select.Option value="2">修改</Select.Option>
+                  <Select.Option value="3">删除</Select.Option>
+                  <Select.Option value="4">查询</Select.Option>
+                  <Select.Option value="5">其他</Select.Option>
+                </Select>
+              </FormControl>
+
+              <FormControl>
+                <FormControl.Label>操作用户</FormControl.Label>
+                <TextInput
+                  value={queryParams.username || ""}
+                  onChange={(e) =>
+                    setQueryParams({ ...queryParams, username: e.target.value })
+                  }
+                  placeholder="请输入操作用户"
+                />
+              </FormControl>
+
+              <FormControl>
+                <FormControl.Label>开始时间</FormControl.Label>
+                <TextInput
+                  type="date"
+                  value={queryParams.startTime || ""}
+                  onChange={(e) =>
+                    setQueryParams({ ...queryParams, startTime: e.target.value })
+                  }
+                />
+              </FormControl>
+
+              <FormControl>
+                <FormControl.Label>结束时间</FormControl.Label>
+                <TextInput
+                  type="date"
+                  value={queryParams.endTime || ""}
+                  onChange={(e) =>
+                    setQueryParams({ ...queryParams, endTime: e.target.value })
+                  }
+                />
+              </FormControl>
+
+              <Flex gap="spacingS">
+                <Button
+                  variant="primary"
+                  onClick={handleSearch}
+                >
+                  查询
+                </Button>
+                <Button variant="secondary" onClick={handleReset}>
+                  重置
+                </Button>
+              </Flex>
+            </Flex>
+          </Form>
+        </Card>
+
+        {/* 操作按钮 */}
+        <Flex gap="spacingS">
+          <Button
+            variant="secondary"
+            onClick={fetchLogList}
+            isLoading={loading}
+          >
+            刷新
+          </Button>
+          <Button
+            variant="negative"
+            onClick={handleBatchDelete}
+            isDisabled={selectedRows.length === 0}
+          >
+            批量删除
+          </Button>
+          <Button
+            variant="negative"
+            onClick={handleDeleteAll}
+          >
+            清空日志
+          </Button>
+        </Flex>
+
+        {/* 表格 */}
+        <Table>
+          <Table.Head>
+            <Table.Row>
+              <Table.Cell>
+                <input
+                  type="checkbox"
+                  onChange={(e) => {
+                    if (e.target.checked) {
+                      setSelectedRows(logList.map((item) => item.id));
+                    } else {
+                      setSelectedRows([]);
+                    }
+                  }}
+                />
+              </Table.Cell>
+              {columns.map((col) => (
+                <Table.Cell key={col.accessor}>{col.header}</Table.Cell>
+              ))}
+            </Table.Row>
+          </Table.Head>
+          <Table.Body>
+            {logList.map((item) => (
+              <Table.Row key={item.id}>
+                <Table.Cell>
+                  <input
+                    type="checkbox"
+                    checked={selectedRows.includes(item.id)}
+                    onChange={(e) => {
+                      if (e.target.checked) {
+                        setSelectedRows([...selectedRows, item.id]);
+                      } else {
+                        setSelectedRows(selectedRows.filter((id) => id !== item.id));
+                      }
+                    }}
+                  />
+                </Table.Cell>
+                {columns.map((col) => (
+                  <Table.Cell key={col.accessor}>
+                    {col.render
+                      ? col.render(item[col.accessor], item)
+                      : String(item[col.accessor] || "")}
+                  </Table.Cell>
+                ))}
+              </Table.Row>
+            ))}
+          </Table.Body>
+        </Table>
+
+        {/* 分页 */}
+        <Flex justifyContent="center">
+          <Pagination
+            activePage={pagination.current}
+            totalItems={pagination.total}
+            itemsPerPage={pagination.pageSize}
+            onPageChange={(page) =>
+              setQueryParams({ ...queryParams, pageNo: page })
+            }
+          />
+        </Flex>
+      </Stack>
+
+      {/* 日志详情弹窗 */}
+      <Modal isShown={detailModalVisible} onClose={() => setDetailModalVisible(false)}>
+        <Modal.Header
+          title="日志详情"
+          onClose={() => setDetailModalVisible(false)}
+        />
+        <Modal.Content>
+          {currentLog && (
+            <Stack spacing="spacingM">
+              <Flex gap="spacingM">
+                <Text fontWeight="fontWeightMedium">日志类型:</Text>
+                {renderLogType(currentLog.logType)}
+              </Flex>
+              
+              <Flex gap="spacingM">
+                <Text fontWeight="fontWeightMedium">操作类型:</Text>
+                {renderOperateType(currentLog.operateType)}
+              </Flex>
+
+              <Flex gap="spacingM">
+                <Text fontWeight="fontWeightMedium">操作用户:</Text>
+                <Text>{currentLog.username || "-"}</Text>
+              </Flex>
+
+              <Flex gap="spacingM">
+                <Text fontWeight="fontWeightMedium">请求方法:</Text>
+                <Badge variant="secondary">{currentLog.method || "-"}</Badge>
+              </Flex>
+
+              <Stack spacing="spacingXs">
+                <Text fontWeight="fontWeightMedium">请求URL:</Text>
+                <Text fontColor="gray600" style={{ wordBreak: "break-all" }}>
+                  {currentLog.requestUrl || "-"}
+                </Text>
+              </Stack>
+
+              <Stack spacing="spacingXs">
+                <Text fontWeight="fontWeightMedium">请求参数:</Text>
+                <Card style={{ backgroundColor: "#f8f9fa" }}>
+                  <Text fontSize="fontSizeS" style={{ whiteSpace: "pre-wrap", fontFamily: "monospace" }}>
+                    {currentLog.requestParam || "-"}
+                  </Text>
+                </Card>
+              </Stack>
+
+              <Stack spacing="spacingXs">
+                <Text fontWeight="fontWeightMedium">日志内容:</Text>
+                <Text fontColor="gray600" style={{ wordBreak: "break-all" }}>
+                  {currentLog.logContent || "-"}
+                </Text>
+              </Stack>
+
+              <Flex gap="spacingM">
+                <Text fontWeight="fontWeightMedium">IP地址:</Text>
+                <Text>{currentLog.ip || "-"}</Text>
+              </Flex>
+
+              <Flex gap="spacingM">
+                <Text fontWeight="fontWeightMedium">耗时:</Text>
+                <Text>{currentLog.costTime ? `${currentLog.costTime}ms` : "-"}</Text>
+              </Flex>
+
+              <Flex gap="spacingM">
+                <Text fontWeight="fontWeightMedium">创建时间:</Text>
+                <Text>{currentLog.createTime || "-"}</Text>
+              </Flex>
+            </Stack>
+          )}
+        </Modal.Content>
+        <Modal.Controls>
+          <Button variant="secondary" onClick={() => setDetailModalVisible(false)}>
+            关闭
+          </Button>
+        </Modal.Controls>
+      </Modal>
+    </Card>
+  );
+}

+ 128 - 0
src/pages/system/menu/MenuForm.tsx

@@ -0,0 +1,128 @@
+/**
+ * 菜单表单组件
+ */
+import {
+  Form,
+  FormControl,
+  TextInput,
+  Textarea,
+  Select,
+  Switch,
+  Stack,
+  Flex,
+} from "@contentful/f36-components";
+import type { SysMenu } from "@/types/system";
+import { MENU_TYPE_OPTIONS, FORM_FIELDS, SWITCH_FIELDS } from "./constants";
+
+interface MenuFormProps {
+  form: Partial<SysMenu>;
+  onChange: (form: Partial<SysMenu>) => void;
+}
+
+export default function MenuForm({ form, onChange }: MenuFormProps) {
+  const handleInputChange = (field: keyof SysMenu, value: any) => {
+    onChange({ ...form, [field]: value });
+  };
+
+  return (
+    <Form>
+      <Stack spacing="spacingM">
+        {/* 菜单名称 */}
+        <FormControl>
+          <FormControl.Label>{FORM_FIELDS.name.label}</FormControl.Label>
+          <TextInput
+            value={form.name || ""}
+            onChange={(e) => handleInputChange("name", e.target.value)}
+            placeholder={FORM_FIELDS.name.placeholder}
+          />
+        </FormControl>
+
+        {/* 菜单类型 */}
+        <FormControl>
+          <FormControl.Label>{FORM_FIELDS.menuType.label}</FormControl.Label>
+          <Select
+            value={form.menuType?.toString() || "0"}
+            onChange={(e) =>
+              handleInputChange("menuType", parseInt(e.target.value) as 0 | 1 | 2)
+            }
+          >
+            {MENU_TYPE_OPTIONS.map((option) => (
+              <Select.Option key={option.value} value={option.value}>
+                {option.label}
+              </Select.Option>
+            ))}
+          </Select>
+        </FormControl>
+
+        {/* 路由地址 */}
+        <FormControl>
+          <FormControl.Label>{FORM_FIELDS.url.label}</FormControl.Label>
+          <TextInput
+            value={form.url || ""}
+            onChange={(e) => handleInputChange("url", e.target.value)}
+            placeholder={FORM_FIELDS.url.placeholder}
+          />
+        </FormControl>
+
+        {/* 组件路径 */}
+        <FormControl>
+          <FormControl.Label>{FORM_FIELDS.component.label}</FormControl.Label>
+          <TextInput
+            value={form.component || ""}
+            onChange={(e) => handleInputChange("component", e.target.value)}
+            placeholder={FORM_FIELDS.component.placeholder}
+          />
+        </FormControl>
+
+        {/* 权限标识 */}
+        <FormControl>
+          <FormControl.Label>{FORM_FIELDS.perms.label}</FormControl.Label>
+          <TextInput
+            value={form.perms || ""}
+            onChange={(e) => handleInputChange("perms", e.target.value)}
+            placeholder={FORM_FIELDS.perms.placeholder}
+          />
+        </FormControl>
+
+        {/* 排序号 */}
+        <FormControl>
+          <FormControl.Label>{FORM_FIELDS.sortNo.label}</FormControl.Label>
+          <TextInput
+            type="number"
+            value={form.sortNo?.toString() || "1"}
+            onChange={(e) =>
+              handleInputChange("sortNo", parseInt(e.target.value) || 1)
+            }
+          />
+        </FormControl>
+
+        {/* 菜单描述 */}
+        <FormControl>
+          <FormControl.Label>{FORM_FIELDS.description.label}</FormControl.Label>
+          <Textarea
+            value={form.description || ""}
+            onChange={(e) => handleInputChange("description", e.target.value)}
+            placeholder={FORM_FIELDS.description.placeholder}
+          />
+        </FormControl>
+
+        {/* 开关字段 */}
+        <Flex gap="spacingM">
+          {SWITCH_FIELDS.map((field) => (
+            <FormControl key={field.key}>
+              <Switch
+                isChecked={field.getValue(form)}
+                onChange={(e) => {
+                  const updatedForm = field.setValue(form, e.target.checked);
+                  onChange(updatedForm as Partial<SysMenu>);
+                }}
+              >
+                {field.label}
+              </Switch>
+            </FormControl>
+          ))}
+        </Flex>
+      </Stack>
+    </Form>
+  );
+}

+ 93 - 0
src/pages/system/menu/README.md

@@ -0,0 +1,93 @@
+# 菜单管理模块
+
+## 文件结构
+
+```
+src/pages/system/menu/
+├── index.tsx           # 主页面组件
+├── useMenuManagement.ts # 自定义Hook,管理状态和业务逻辑
+├── MenuForm.tsx        # 菜单表单组件
+├── columns.tsx         # 表格列配置
+├── constants.ts        # 常量配置
+└── README.md          # 说明文档
+```
+
+## 模块说明
+
+### 1. `index.tsx` - 主页面组件
+- 负责页面布局和组件组合
+- 使用自定义Hook管理状态
+- 渲染表格和弹窗
+
+### 2. `useMenuManagement.ts` - 自定义Hook
+- 管理所有状态(loading、menuList、selectedRows等)
+- 封装所有业务逻辑(增删改查、选择等)
+- 提供统一的接口给组件使用
+
+### 3. `MenuForm.tsx` - 表单组件
+- 独立的表单组件,可复用
+- 接收form数据和onChange回调
+- 使用常量配置渲染表单字段
+
+### 4. `columns.tsx` - 表格列配置
+- 定义表格列的结构和渲染逻辑
+- 包含渲染函数(renderMenuType、renderStatus)
+- 支持传入回调函数处理操作
+
+### 5. `constants.ts` - 常量配置
+- 菜单类型配置(MENU_TYPE_CONFIG)
+- 菜单状态配置(MENU_STATUS_CONFIG)
+- 表单字段配置(FORM_FIELDS)
+- 开关字段配置(SWITCH_FIELDS)
+- 默认表单数据(DEFAULT_FORM_DATA)
+
+## 设计优势
+
+### 1. 关注点分离
+- 业务逻辑与UI分离
+- 数据配置与组件分离
+- 可复用组件独立封装
+
+### 2. 可维护性
+- 配置集中管理,易于修改
+- 组件职责单一,易于理解
+- 类型安全,减少错误
+
+### 3. 可扩展性
+- 新增字段只需修改constants.ts
+- 表格列可灵活配置
+- 表单组件可在其他地方复用
+
+### 4. 代码复用
+- MenuForm组件可在其他菜单相关页面使用
+- 渲染函数可在其他地方复用
+- Hook可以被其他组件使用
+
+## 使用示例
+
+```tsx
+// 在其他组件中使用菜单表单
+import MenuForm from './MenuForm';
+
+function AnotherComponent() {
+  const [form, setForm] = useState<Partial<SysMenu>>({});
+  
+  return (
+    <MenuForm form={form} onChange={setForm} />
+  );
+}
+```
+
+```tsx
+// 在其他地方使用渲染函数
+import { renderMenuType, renderStatus } from './columns';
+
+function MenuCard({ menu }: { menu: SysMenu }) {
+  return (
+    <div>
+      {renderMenuType(menu.menuType)}
+      {renderStatus(menu.status)}
+    </div>
+  );
+}
+```

+ 80 - 0
src/pages/system/menu/columns.tsx

@@ -0,0 +1,80 @@
+/**
+ * 菜单管理表格列配置
+ */
+import { Text, Badge, Flex, Button } from "@contentful/f36-components";
+import type { SysMenu } from "@/types/system";
+import { MENU_TYPE_CONFIG, MENU_STATUS_CONFIG } from "./constants";
+
+// 渲染菜单类型
+export const renderMenuType = (type: number) => {
+  const config = MENU_TYPE_CONFIG[type as keyof typeof MENU_TYPE_CONFIG];
+  return <Badge variant={config?.variant}>{config?.label}</Badge>;
+};
+
+// 渲染状态
+export const renderStatus = (status?: string) => {
+  const config = MENU_STATUS_CONFIG[status as keyof typeof MENU_STATUS_CONFIG];
+  return config ? (
+    <Badge variant={config.variant}>{config.label}</Badge>
+  ) : (
+    <Badge variant="secondary">未知</Badge>
+  );
+};
+
+// 表格列配置
+export const createColumns = (
+  onEdit: (record: SysMenu) => void,
+  onDelete: (id: string) => void
+) => [
+  {
+    header: "菜单名称",
+    accessor: "name" as keyof SysMenu,
+    render: (value: any) => <Text>{value}</Text>,
+  },
+  {
+    header: "菜单类型",
+    accessor: "menuType" as keyof SysMenu,
+    render: (value: any) => renderMenuType(value),
+  },
+  {
+    header: "权限标识",
+    accessor: "perms" as keyof SysMenu,
+    render: (value: any) => <Text fontColor="gray600">{value || "-"}</Text>,
+  },
+  {
+    header: "组件路径",
+    accessor: "component" as keyof SysMenu,
+    render: (value: any) => <Text fontColor="gray600">{value || "-"}</Text>,
+  },
+  {
+    header: "排序",
+    accessor: "sortNo" as keyof SysMenu,
+  },
+  {
+    header: "状态",
+    accessor: "status" as keyof SysMenu,
+    render: (value: any) => renderStatus(value),
+  },
+  {
+    header: "操作",
+    accessor: "id" as keyof SysMenu,
+    render: (_: any, record: SysMenu) => (
+      <Flex gap="spacingXs">
+        <Button
+          variant="secondary"
+          size="small"
+          onClick={() => onEdit(record)}
+        >
+          编辑
+        </Button>
+        <Button
+          variant="negative"
+          size="small"
+          onClick={() => onDelete(record.id)}
+        >
+          删除
+        </Button>
+      </Flex>
+    ),
+  },
+];

+ 99 - 0
src/pages/system/menu/constants.ts

@@ -0,0 +1,99 @@
+/**
+ * 菜单管理页面常量配置
+ */
+import type { SysMenu } from "@/types/system";
+
+// 菜单类型配置
+export const MENU_TYPE_CONFIG = {
+  0: { label: "一级菜单", variant: "primary" as const },
+  1: { label: "子菜单", variant: "secondary" as const },
+  2: { label: "按钮权限", variant: "warning" as const },
+} as const;
+
+// 菜单状态配置
+export const MENU_STATUS_CONFIG = {
+  "0": { label: "正常", variant: "positive" as const },
+  "1": { label: "禁用", variant: "negative" as const },
+} as const;
+
+// 菜单类型选项
+export const MENU_TYPE_OPTIONS = [
+  { value: "0", label: "一级菜单" },
+  { value: "1", label: "子菜单" },
+  { value: "2", label: "按钮权限" },
+] as const;
+
+// 默认表单数据
+export const DEFAULT_FORM_DATA: Partial<SysMenu> = {
+  menuType: 0,
+  sortNo: 1,
+  status: "0",
+  isRoute: true,
+  keepAlive: false,
+  hidden: false,
+};
+
+// 表单字段配置
+export const FORM_FIELDS = {
+  name: {
+    label: "菜单名称",
+    placeholder: "请输入菜单名称",
+    required: true,
+  },
+  menuType: {
+    label: "菜单类型",
+    required: true,
+  },
+  url: {
+    label: "路由地址",
+    placeholder: "请输入路由地址",
+  },
+  component: {
+    label: "组件路径",
+    placeholder: "请输入组件路径",
+  },
+  perms: {
+    label: "权限标识",
+    placeholder: "请输入权限标识",
+  },
+  sortNo: {
+    label: "排序号",
+    type: "number" as const,
+  },
+  description: {
+    label: "菜单描述",
+    placeholder: "请输入菜单描述",
+    type: "textarea" as const,
+  },
+} as const;
+
+// 开关字段配置
+export const SWITCH_FIELDS = [
+  {
+    key: "status" as keyof SysMenu,
+    label: "启用状态",
+    getValue: (form: Partial<SysMenu>) => form.status === "0",
+    setValue: (form: Partial<SysMenu>, checked: boolean) => ({
+      ...form,
+      status: checked ? "0" : "1",
+    }),
+  },
+  {
+    key: "hidden" as keyof SysMenu,
+    label: "菜单可见",
+    getValue: (form: Partial<SysMenu>) => form.hidden === false,
+    setValue: (form: Partial<SysMenu>, checked: boolean) => ({
+      ...form,
+      hidden: !checked,
+    }),
+  },
+  {
+    key: "keepAlive" as keyof SysMenu,
+    label: "缓存页面",
+    getValue: (form: Partial<SysMenu>) => form.keepAlive === true,
+    setValue: (form: Partial<SysMenu>, checked: boolean) => ({
+      ...form,
+      keepAlive: checked,
+    }),
+  },
+] as const;

+ 128 - 0
src/pages/system/menu/index.tsx

@@ -0,0 +1,128 @@
+/**
+ * 菜单管理页面
+ */
+import {
+  Card,
+  Table,
+  Button,
+  Stack,
+  Flex,
+  Text,
+  Modal,
+} from "@contentful/f36-components";
+import { useMenuManagement } from "./useMenuManagement";
+import { createColumns } from "./columns";
+import MenuForm from "./MenuForm";
+
+export default function MenuManagement() {
+  const {
+    loading,
+    menuList,
+    selectedRows,
+    modalVisible,
+    editingMenu,
+    form,
+    setForm,
+    fetchMenuList,
+    handleAdd,
+    handleEdit,
+    handleDelete,
+    handleBatchDelete,
+    handleSave,
+    handleCancel,
+    handleSelectAll,
+    handleSelectRow,
+  } = useMenuManagement();
+
+  // 创建表格列配置
+  const columns = createColumns(handleEdit, handleDelete);
+
+  return (
+    <Card>
+      <Stack spacing="spacingM" flexDirection="column">
+        {/* 页面标题和操作按钮 */}
+        <Flex
+          justifyContent="space-between"
+          alignItems="center"
+          style={{ width: "100%" }}
+        >
+          <Flex gap="spacingS">
+            <Button variant="primary" onClick={handleAdd}>
+              新增菜单
+            </Button>
+            <Button
+              variant="secondary"
+              onClick={fetchMenuList}
+              isLoading={loading}
+            >
+              刷新
+            </Button>
+            <Button
+              variant="negative"
+              onClick={handleBatchDelete}
+              isDisabled={selectedRows.length === 0}
+            >
+              批量删除
+            </Button>
+          </Flex>
+        </Flex>
+
+        {/* 表格 */}
+        <Table>
+          <Table.Head>
+            <Table.Row>
+              <Table.Cell>
+                <input
+                  type="checkbox"
+                  onChange={(e) => handleSelectAll(e.target.checked)}
+                />
+              </Table.Cell>
+              {columns.map((col) => (
+                <Table.Cell key={col.accessor}>{col.header}</Table.Cell>
+              ))}
+            </Table.Row>
+          </Table.Head>
+          <Table.Body>
+            {menuList.map((item) => (
+              <Table.Row key={item.id}>
+                <Table.Cell>
+                  <input
+                    type="checkbox"
+                    checked={selectedRows.includes(item.id)}
+                    onChange={(e) => handleSelectRow(item.id, e.target.checked)}
+                  />
+                </Table.Cell>
+                {columns.map((col) => (
+                  <Table.Cell key={col.accessor}>
+                    {col.render
+                      ? col.render(item[col.accessor], item)
+                      : String(item[col.accessor] || "")}
+                  </Table.Cell>
+                ))}
+              </Table.Row>
+            ))}
+          </Table.Body>
+        </Table>
+      </Stack>
+
+      {/* 添加/编辑弹窗 */}
+      <Modal isShown={modalVisible} onClose={handleCancel}>
+        <Modal.Header
+          title={editingMenu ? "编辑菜单" : "新增菜单"}
+          onClose={handleCancel}
+        />
+        <Modal.Content>
+          <MenuForm form={form} onChange={setForm} />
+        </Modal.Content>
+        <Modal.Controls>
+          <Button variant="secondary" onClick={handleCancel}>
+            取消
+          </Button>
+          <Button variant="primary" onClick={handleSave}>
+            保存
+          </Button>
+        </Modal.Controls>
+      </Modal>
+    </Card>
+  );
+}

+ 132 - 0
src/pages/system/menu/useMenuManagement.ts

@@ -0,0 +1,132 @@
+/**
+ * 菜单管理自定义Hook
+ */
+import { useState, useEffect } from "react";
+import { menuApi } from "@/services";
+import type { SysMenu } from "@/types/system";
+import { DEFAULT_FORM_DATA } from "./constants";
+
+export function useMenuManagement() {
+  const [loading, setLoading] = useState(false);
+  const [menuList, setMenuList] = useState<SysMenu[]>([]);
+  const [selectedRows, setSelectedRows] = useState<string[]>([]);
+  const [modalVisible, setModalVisible] = useState(false);
+  const [editingMenu, setEditingMenu] = useState<SysMenu | null>(null);
+  const [form, setForm] = useState<Partial<SysMenu>>({});
+
+  // 获取菜单列表
+  const fetchMenuList = async () => {
+    setLoading(true);
+    try {
+      const response = await menuApi.getMenuList();
+      setMenuList(response || []);
+    } catch (error) {
+      console.error("获取菜单列表失败:", error);
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  // 初始化加载
+  useEffect(() => {
+    fetchMenuList();
+  }, []);
+
+  // 处理添加菜单
+  const handleAdd = () => {
+    setEditingMenu(null);
+    setForm({ ...DEFAULT_FORM_DATA });
+    setModalVisible(true);
+  };
+
+  // 处理编辑菜单
+  const handleEdit = (record: SysMenu) => {
+    setEditingMenu(record);
+    setForm({ ...record });
+    setModalVisible(true);
+  };
+
+  // 处理删除菜单
+  const handleDelete = async (id: string) => {
+    try {
+      await menuApi.deleteMenu(id);
+      await fetchMenuList();
+    } catch (error) {
+      console.error("删除菜单失败:", error);
+    }
+  };
+
+  // 处理批量删除
+  const handleBatchDelete = async () => {
+    if (selectedRows.length === 0) return;
+    try {
+      await menuApi.deleteBatchMenu(selectedRows);
+      setSelectedRows([]);
+      await fetchMenuList();
+    } catch (error) {
+      console.error("批量删除菜单失败:", error);
+    }
+  };
+
+  // 处理保存
+  const handleSave = async () => {
+    try {
+      if (editingMenu) {
+        await menuApi.editMenu({ ...form, id: editingMenu.id });
+      } else {
+        await menuApi.addMenu(form);
+      }
+      setModalVisible(false);
+      await fetchMenuList();
+    } catch (error) {
+      console.error("保存菜单失败:", error);
+    }
+  };
+
+  // 处理取消
+  const handleCancel = () => {
+    setModalVisible(false);
+    setForm({});
+    setEditingMenu(null);
+  };
+
+  // 处理全选
+  const handleSelectAll = (checked: boolean) => {
+    if (checked) {
+      setSelectedRows(menuList.map((item) => item.id));
+    } else {
+      setSelectedRows([]);
+    }
+  };
+
+  // 处理单选
+  const handleSelectRow = (id: string, checked: boolean) => {
+    if (checked) {
+      setSelectedRows([...selectedRows, id]);
+    } else {
+      setSelectedRows(selectedRows.filter((rowId) => rowId !== id));
+    }
+  };
+
+  return {
+    // 状态
+    loading,
+    menuList,
+    selectedRows,
+    modalVisible,
+    editingMenu,
+    form,
+    
+    // 方法
+    setForm,
+    fetchMenuList,
+    handleAdd,
+    handleEdit,
+    handleDelete,
+    handleBatchDelete,
+    handleSave,
+    handleCancel,
+    handleSelectAll,
+    handleSelectRow,
+  };
+}

+ 439 - 0
src/pages/system/role/index.tsx

@@ -0,0 +1,439 @@
+/**
+ * 角色管理页面
+ */
+import { useState, useEffect } from "react";
+import {
+  Card,
+  Table,
+  Button,
+  Stack,
+  Flex,
+  Text,
+  Modal,
+  Form,
+  FormControl,
+  TextInput,
+  Textarea,
+  Pagination,
+} from "@contentful/f36-components";
+// 暂时移除图标导入,使用文本按钮
+import { roleApi, menuApi, permissionApi } from "@/services";
+import type { SysRole, RoleQueryParams } from "@/types/system";
+
+export default function RoleManagement() {
+  const [loading, setLoading] = useState(false);
+  const [roleList, setRoleList] = useState<SysRole[]>([]);
+  // const [menuTree] = useState<SysMenu[]>([]);
+  const [selectedRows, setSelectedRows] = useState<string[]>([]);
+  const [modalVisible, setModalVisible] = useState(false);
+  const [permissionModalVisible, setPermissionModalVisible] = useState(false);
+  const [editingRole, setEditingRole] = useState<SysRole | null>(null);
+  const [currentRole, setCurrentRole] = useState<SysRole | null>(null);
+  const [form, setForm] = useState<Partial<SysRole>>({});
+  const [selectedPermissions, setSelectedPermissions] = useState<string[]>([]);
+
+  // 分页参数
+  const [pagination, setPagination] = useState({
+    current: 1,
+    pageSize: 10,
+    total: 0,
+  });
+
+  // 查询参数
+  const [queryParams, setQueryParams] = useState<RoleQueryParams>({
+    pageNo: 1,
+    pageSize: 10,
+  });
+
+  // 获取角色列表
+  const fetchRoleList = async () => {
+    setLoading(true);
+    try {
+      const response = await roleApi.getRoleList(queryParams);
+      setRoleList(response.records || []);
+      setPagination({
+        current: response.current,
+        pageSize: response.size,
+        total: response.total,
+      });
+    } catch (error) {
+      console.error("获取角色列表失败:", error);
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  // 获取菜单树
+  const fetchMenuTree = async () => {
+    try {
+      await menuApi.getMenuTree();
+      // setMenuTree(response || []);
+    } catch (error) {
+      console.error("获取菜单树失败:", error);
+    }
+  };
+
+  useEffect(() => {
+    fetchRoleList();
+    fetchMenuTree();
+  }, [queryParams]);
+
+  // 处理搜索
+  const handleSearch = () => {
+    setQueryParams({ ...queryParams, pageNo: 1 });
+  };
+
+  // 处理重置
+  const handleReset = () => {
+    setQueryParams({
+      pageNo: 1,
+      pageSize: 10,
+    });
+  };
+
+  // 处理添加角色
+  const handleAdd = () => {
+    setEditingRole(null);
+    setForm({});
+    setModalVisible(true);
+  };
+
+  // 处理编辑角色
+  const handleEdit = (record: SysRole) => {
+    setEditingRole(record);
+    setForm({ ...record });
+    setModalVisible(true);
+  };
+
+  // 处理删除角色
+  const handleDelete = async (id: string) => {
+    try {
+      await roleApi.deleteRole(id);
+      fetchRoleList();
+    } catch (error) {
+      console.error("删除角色失败:", error);
+    }
+  };
+
+  // 处理批量删除
+  const handleBatchDelete = async () => {
+    if (selectedRows.length === 0) return;
+    try {
+      await roleApi.deleteBatchRole(selectedRows);
+      setSelectedRows([]);
+      fetchRoleList();
+    } catch (error) {
+      console.error("批量删除角色失败:", error);
+    }
+  };
+
+  // 处理保存角色
+  const handleSave = async () => {
+    try {
+      if (editingRole) {
+        await roleApi.editRole({ ...form, id: editingRole.id });
+      } else {
+        await roleApi.addRole(form);
+      }
+      setModalVisible(false);
+      fetchRoleList();
+    } catch (error) {
+      console.error("保存角色失败:", error);
+    }
+  };
+
+  // 处理分配权限
+  const handleAssignPermission = async (role: SysRole) => {
+    setCurrentRole(role);
+    try {
+      const permissions = await permissionApi.queryRolePermission(role.id);
+      setSelectedPermissions(permissions || []);
+      setPermissionModalVisible(true);
+    } catch (error) {
+      console.error("获取角色权限失败:", error);
+    }
+  };
+
+  // 处理保存权限
+  const handleSavePermission = async () => {
+    if (!currentRole) return;
+    try {
+      await permissionApi.saveRolePermission(currentRole.id, selectedPermissions);
+      setPermissionModalVisible(false);
+    } catch (error) {
+      console.error("保存角色权限失败:", error);
+    }
+  };
+
+  // 渲染菜单树
+  // const renderMenuTree = (menus: SysMenu[]): any[] => {
+  //   return menus.map((menu) => ({
+  //     id: menu.id,
+  //     title: menu.name,
+  //     children: menu.children ? renderMenuTree(menu.children) : undefined,
+  //   }));
+  // };
+
+  // 表格列定义
+  const columns = [
+    {
+      header: "角色名称",
+      accessor: "roleName" as keyof SysRole,
+    },
+    {
+      header: "角色编码",
+      accessor: "roleCode" as keyof SysRole,
+    },
+    {
+      header: "描述",
+      accessor: "description" as keyof SysRole,
+      render: (value?: string) => value || "-",
+    },
+    {
+      header: "创建时间",
+      accessor: "createTime" as keyof SysRole,
+      render: (value?: string) => value?.substring(0, 10) || "-",
+    },
+    {
+      header: "操作",
+      accessor: "id" as keyof SysRole,
+      render: (_: string, record: SysRole) => (
+        <Flex gap="spacingXs">
+          <Button
+            variant="secondary"
+            size="small"
+            onClick={() => handleEdit(record)}
+          >
+            编辑
+          </Button>
+          <Button
+            variant="secondary"
+            size="small"
+            onClick={() => handleAssignPermission(record)}
+          >
+            权限
+          </Button>
+          <Button
+            variant="negative"
+            size="small"
+            onClick={() => handleDelete(record.id)}
+          >
+            删除
+          </Button>
+        </Flex>
+      ),
+    },
+  ];
+
+  return (
+    <Card>
+      <Stack spacing="spacingM">
+        {/* 页面标题 */}
+        <Flex justifyContent="space-between" alignItems="center">
+          <Text fontSize="fontSizeL" fontWeight="fontWeightMedium">
+            角色管理
+          </Text>
+        </Flex>
+
+        {/* 查询条件 */}
+        <Card>
+          <Form>
+            <Flex gap="spacingM" alignItems="end" flexWrap="wrap">
+              <FormControl>
+                <FormControl.Label>角色名称</FormControl.Label>
+                <TextInput
+                  value={queryParams.roleName || ""}
+                  onChange={(e) =>
+                    setQueryParams({ ...queryParams, roleName: e.target.value })
+                  }
+                  placeholder="请输入角色名称"
+                />
+              </FormControl>
+
+              <FormControl>
+                <FormControl.Label>角色编码</FormControl.Label>
+                <TextInput
+                  value={queryParams.roleCode || ""}
+                  onChange={(e) =>
+                    setQueryParams({ ...queryParams, roleCode: e.target.value })
+                  }
+                  placeholder="请输入角色编码"
+                />
+              </FormControl>
+
+              <Flex gap="spacingS">
+                <Button
+                  variant="primary"
+                  onClick={handleSearch}
+                >
+                  查询
+                </Button>
+                <Button variant="secondary" onClick={handleReset}>
+                  重置
+                </Button>
+              </Flex>
+            </Flex>
+          </Form>
+        </Card>
+
+        {/* 操作按钮 */}
+        <Flex gap="spacingS">
+          <Button
+            variant="secondary"
+            onClick={fetchRoleList}
+            isLoading={loading}
+          >
+            刷新
+          </Button>
+          <Button
+            variant="negative"
+            onClick={handleBatchDelete}
+            isDisabled={selectedRows.length === 0}
+          >
+            批量删除
+          </Button>
+          <Button
+            variant="primary"
+            onClick={handleAdd}
+          >
+            新增角色
+          </Button>
+        </Flex>
+
+        {/* 表格 */}
+        <Table>
+          <Table.Head>
+            <Table.Row>
+              <Table.Cell>
+                <input
+                  type="checkbox"
+                  onChange={(e) => {
+                    if (e.target.checked) {
+                      setSelectedRows(roleList.map((item) => item.id));
+                    } else {
+                      setSelectedRows([]);
+                    }
+                  }}
+                />
+              </Table.Cell>
+              {columns.map((col) => (
+                <Table.Cell key={col.accessor}>{col.header}</Table.Cell>
+              ))}
+            </Table.Row>
+          </Table.Head>
+          <Table.Body>
+            {roleList.map((item) => (
+              <Table.Row key={item.id}>
+                <Table.Cell>
+                  <input
+                    type="checkbox"
+                    checked={selectedRows.includes(item.id)}
+                    onChange={(e) => {
+                      if (e.target.checked) {
+                        setSelectedRows([...selectedRows, item.id]);
+                      } else {
+                        setSelectedRows(selectedRows.filter((id) => id !== item.id));
+                      }
+                    }}
+                  />
+                </Table.Cell>
+                {columns.map((col) => (
+                  <Table.Cell key={col.accessor}>
+                    {col.render
+                      ? col.render(item[col.accessor] as any, item)
+                      : (item[col.accessor] as string)}
+                  </Table.Cell>
+                ))}
+              </Table.Row>
+            ))}
+          </Table.Body>
+        </Table>
+
+        {/* 分页 */}
+        <Flex justifyContent="center">
+          <Pagination
+            activePage={pagination.current}
+            totalItems={pagination.total}
+            itemsPerPage={pagination.pageSize}
+            onPageChange={(page) =>
+              setQueryParams({ ...queryParams, pageNo: page })
+            }
+          />
+        </Flex>
+      </Stack>
+
+      {/* 添加/编辑角色弹窗 */}
+      <Modal isShown={modalVisible} onClose={() => setModalVisible(false)}>
+        <Modal.Header
+          title={editingRole ? "编辑角色" : "新增角色"}
+          onClose={() => setModalVisible(false)}
+        />
+        <Modal.Content>
+          <Form>
+            <Stack spacing="spacingM">
+              <FormControl>
+                <FormControl.Label>角色名称</FormControl.Label>
+                <TextInput
+                  value={form.roleName || ""}
+                  onChange={(e) => setForm({ ...form, roleName: e.target.value })}
+                  placeholder="请输入角色名称"
+                />
+              </FormControl>
+
+              <FormControl>
+                <FormControl.Label>角色编码</FormControl.Label>
+                <TextInput
+                  value={form.roleCode || ""}
+                  onChange={(e) => setForm({ ...form, roleCode: e.target.value })}
+                  placeholder="请输入角色编码"
+                />
+              </FormControl>
+
+              <FormControl>
+                <FormControl.Label>角色描述</FormControl.Label>
+                <Textarea
+                  value={form.description || ""}
+                  onChange={(e) => setForm({ ...form, description: e.target.value })}
+                  placeholder="请输入角色描述"
+                />
+              </FormControl>
+            </Stack>
+          </Form>
+        </Modal.Content>
+        <Modal.Controls>
+          <Button variant="secondary" onClick={() => setModalVisible(false)}>
+            取消
+          </Button>
+          <Button variant="primary" onClick={handleSave}>
+            保存
+          </Button>
+        </Modal.Controls>
+      </Modal>
+
+      {/* 分配权限弹窗 */}
+      <Modal isShown={permissionModalVisible} onClose={() => setPermissionModalVisible(false)}>
+        <Modal.Header
+          title="分配权限"
+          onClose={() => setPermissionModalVisible(false)}
+        />
+        <Modal.Content>
+          <Stack spacing="spacingM">
+            <Text>为角色 "{currentRole?.roleName}" 分配权限:</Text>
+            <div style={{ maxHeight: "400px", overflow: "auto" }}>
+              <Text>权限树组件开发中...</Text>
+              {/* TODO: 实现权限树组件 */}
+            </div>
+          </Stack>
+        </Modal.Content>
+        <Modal.Controls>
+          <Button variant="secondary" onClick={() => setPermissionModalVisible(false)}>
+            取消
+          </Button>
+          <Button variant="primary" onClick={handleSavePermission}>
+            保存
+          </Button>
+        </Modal.Controls>
+      </Modal>
+    </Card>
+  );
+}

+ 618 - 0
src/pages/system/user/index.tsx

@@ -0,0 +1,618 @@
+/**
+ * 用户管理页面
+ */
+import { useState, useEffect } from "react";
+import {
+  Card,
+  Table,
+  Button,
+  Stack,
+  Flex,
+  Text,
+  Badge,
+  Modal,
+  Form,
+  FormControl,
+  TextInput,
+  Select,
+  Switch,
+  Avatar,
+  Pagination,
+} from "@contentful/f36-components";
+// 暂时移除图标导入,使用文本按钮
+import { userApi, roleApi } from "@/services";
+import type { SysUser, SysRole, UserQueryParams } from "@/types/system";
+
+export default function UserManagement() {
+  const [loading, setLoading] = useState(false);
+  const [userList, setUserList] = useState<SysUser[]>([]);
+  const [roleList, setRoleList] = useState<SysRole[]>([]);
+  const [selectedRows, setSelectedRows] = useState<string[]>([]);
+  const [modalVisible, setModalVisible] = useState(false);
+  const [roleModalVisible, setRoleModalVisible] = useState(false);
+  const [editingUser, setEditingUser] = useState<SysUser | null>(null);
+  const [form, setForm] = useState<Partial<SysUser>>({});
+  const [userRoles, setUserRoles] = useState<string[]>([]);
+  const [currentUser, setCurrentUser] = useState<SysUser | null>(null);
+
+  // 分页参数
+  const [pagination, setPagination] = useState({
+    current: 1,
+    pageSize: 10,
+    total: 0,
+  });
+
+  // 查询参数
+  const [queryParams, setQueryParams] = useState<UserQueryParams>({
+    pageNo: 1,
+    pageSize: 10,
+  });
+
+  // 获取用户列表
+  const fetchUserList = async () => {
+    setLoading(true);
+    try {
+      const response = await userApi.getUserList(queryParams);
+      setUserList(response.records || []);
+      setPagination({
+        current: response.current,
+        pageSize: response.size,
+        total: response.total,
+      });
+    } catch (error) {
+      console.error("获取用户列表失败:", error);
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  // 获取角色列表
+  const fetchRoleList = async () => {
+    try {
+      const response = await roleApi.getAllRoles();
+      setRoleList(response || []);
+    } catch (error) {
+      console.error("获取角色列表失败:", error);
+    }
+  };
+
+  useEffect(() => {
+    fetchUserList();
+    fetchRoleList();
+  }, [queryParams]);
+
+  // 处理搜索
+  const handleSearch = () => {
+    setQueryParams({ ...queryParams, pageNo: 1 });
+  };
+
+  // 处理重置
+  const handleReset = () => {
+    setQueryParams({
+      pageNo: 1,
+      pageSize: 10,
+    });
+  };
+
+  // 处理添加用户
+  const handleAdd = () => {
+    setEditingUser(null);
+    setForm({
+      sex: "1",
+      status: "1",
+      userIdentity: "1",
+    });
+    setModalVisible(true);
+  };
+
+  // 处理编辑用户
+  const handleEdit = (record: SysUser) => {
+    setEditingUser(record);
+    setForm({ ...record });
+    setModalVisible(true);
+  };
+
+  // 处理删除用户
+  const handleDelete = async (id: string) => {
+    try {
+      await userApi.deleteUser(id);
+      fetchUserList();
+    } catch (error) {
+      console.error("删除用户失败:", error);
+    }
+  };
+
+  // 处理批量删除
+  const handleBatchDelete = async () => {
+    if (selectedRows.length === 0) return;
+    try {
+      await userApi.deleteBatchUser(selectedRows);
+      setSelectedRows([]);
+      fetchUserList();
+    } catch (error) {
+      console.error("批量删除用户失败:", error);
+    }
+  };
+
+  // 处理重置密码
+  const handleResetPassword = async (id: string) => {
+    try {
+      await userApi.resetPassword(id);
+      // 显示成功消息
+    } catch (error) {
+      console.error("重置密码失败:", error);
+    }
+  };
+
+  // 处理冻结/解冻用户
+  const handleFrozen = async (ids: string[], status: '1' | '2') => {
+    try {
+      await userApi.frozenBatch(ids, status);
+      fetchUserList();
+    } catch (error) {
+      console.error("操作失败:", error);
+    }
+  };
+
+  // 处理保存用户
+  const handleSave = async () => {
+    try {
+      if (editingUser) {
+        await userApi.editUser({ ...form, id: editingUser.id });
+      } else {
+        await userApi.addUser(form);
+      }
+      setModalVisible(false);
+      fetchUserList();
+    } catch (error) {
+      console.error("保存用户失败:", error);
+    }
+  };
+
+  // 处理分配角色
+  const handleAssignRole = async (user: SysUser) => {
+    setCurrentUser(user);
+    try {
+      const roles = await userApi.getUserRoles(user.id);
+      setUserRoles(roles || []);
+      setRoleModalVisible(true);
+    } catch (error) {
+      console.error("获取用户角色失败:", error);
+    }
+  };
+
+  // 处理保存用户角色
+  const handleSaveUserRole = async () => {
+    if (!currentUser) return;
+    try {
+      await userApi.editUserRole(currentUser.id, userRoles);
+      setRoleModalVisible(false);
+    } catch (error) {
+      console.error("保存用户角色失败:", error);
+    }
+  };
+
+  // 渲染性别
+  const renderSex = (sex?: string) => {
+    return sex === "1" ? "男" : sex === "2" ? "女" : "-";
+  };
+
+  // 渲染状态
+  const renderStatus = (status?: string) => {
+    return status === "1" ? (
+      <Badge variant="positive">正常</Badge>
+    ) : (
+      <Badge variant="negative">冻结</Badge>
+    );
+  };
+
+  // 表格列定义
+  const columns = [
+    {
+      header: "用户头像",
+      accessor: "avatar" as keyof SysUser,
+      render: (value?: string) => (
+        <Avatar
+          src={value}
+          size="small"
+        />
+      ),
+    },
+    {
+      header: "用户账号",
+      accessor: "username" as keyof SysUser,
+    },
+    {
+      header: "用户姓名",
+      accessor: "realname" as keyof SysUser,
+    },
+    {
+      header: "性别",
+      accessor: "sex" as keyof SysUser,
+      render: (value?: string) => renderSex(value),
+    },
+    {
+      header: "手机号码",
+      accessor: "phone" as keyof SysUser,
+      render: (value?: string) => value || "-",
+    },
+    {
+      header: "邮箱",
+      accessor: "email" as keyof SysUser,
+      render: (value?: string) => value || "-",
+    },
+    {
+      header: "状态",
+      accessor: "status" as keyof SysUser,
+      render: (value?: string) => renderStatus(value),
+    },
+    {
+      header: "创建时间",
+      accessor: "createTime" as keyof SysUser,
+      render: (value?: string) => value?.substring(0, 10) || "-",
+    },
+    {
+      header: "操作",
+      accessor: "id" as keyof SysUser,
+      render: (_: string, record: SysUser) => (
+        <Flex gap="spacingXs">
+          <Button
+            variant="secondary"
+            size="small"
+            onClick={() => handleEdit(record)}
+          >
+            编辑
+          </Button>
+          <Button
+            variant="secondary"
+            size="small"
+            onClick={() => handleAssignRole(record)}
+          >
+            角色
+          </Button>
+          <Button
+            variant="secondary"
+            size="small"
+            onClick={() => handleResetPassword(record.id)}
+          >
+            重置
+          </Button>
+          <Button
+            variant="negative"
+            size="small"
+            onClick={() => handleDelete(record.id)}
+          >
+            删除
+          </Button>
+        </Flex>
+      ),
+    },
+  ];
+
+  return (
+    <Card>
+      <Stack spacing="spacingM">
+        {/* 页面标题 */}
+        <Flex justifyContent="space-between" alignItems="center">
+          <Text fontSize="fontSizeL" fontWeight="fontWeightMedium">
+            用户管理
+          </Text>
+        </Flex>
+
+        {/* 查询条件 */}
+        <Card>
+          <Form>
+            <Flex gap="spacingM" alignItems="end" flexWrap="wrap">
+              <FormControl>
+                <FormControl.Label>用户账号</FormControl.Label>
+                <TextInput
+                  value={queryParams.username || ""}
+                  onChange={(e) =>
+                    setQueryParams({ ...queryParams, username: e.target.value })
+                  }
+                  placeholder="请输入用户账号"
+                />
+              </FormControl>
+
+              <FormControl>
+                <FormControl.Label>用户姓名</FormControl.Label>
+                <TextInput
+                  value={queryParams.realname || ""}
+                  onChange={(e) =>
+                    setQueryParams({ ...queryParams, realname: e.target.value })
+                  }
+                  placeholder="请输入用户姓名"
+                />
+              </FormControl>
+
+              <FormControl>
+                <FormControl.Label>状态</FormControl.Label>
+                <Select
+                  value={queryParams.status || ""}
+                  onChange={(e) =>
+                    setQueryParams({ ...queryParams, status: e.target.value })
+                  }
+                >
+                  <Select.Option value="">全部</Select.Option>
+                  <Select.Option value="1">正常</Select.Option>
+                  <Select.Option value="2">冻结</Select.Option>
+                </Select>
+              </FormControl>
+
+              <Flex gap="spacingS">
+                <Button
+                  variant="primary"
+                  onClick={handleSearch}
+                >
+                  查询
+                </Button>
+                <Button variant="secondary" onClick={handleReset}>
+                  重置
+                </Button>
+              </Flex>
+            </Flex>
+          </Form>
+        </Card>
+
+        {/* 操作按钮 */}
+        <Flex gap="spacingS">
+          <Button
+            variant="secondary"
+            onClick={fetchUserList}
+            isLoading={loading}
+          >
+            刷新
+          </Button>
+          <Button
+            variant="negative"
+            onClick={handleBatchDelete}
+            isDisabled={selectedRows.length === 0}
+          >
+            批量删除
+          </Button>
+          <Button
+            variant="secondary"
+            onClick={() => handleFrozen(selectedRows, "2")}
+            isDisabled={selectedRows.length === 0}
+          >
+            批量冻结
+          </Button>
+          <Button
+            variant="secondary"
+            onClick={() => handleFrozen(selectedRows, "1")}
+            isDisabled={selectedRows.length === 0}
+          >
+            批量解冻
+          </Button>
+          <Button
+            variant="primary"
+            onClick={handleAdd}
+          >
+            新增用户
+          </Button>
+        </Flex>
+
+        {/* 表格 */}
+        <Table>
+          <Table.Head>
+            <Table.Row>
+              <Table.Cell>
+                <input
+                  type="checkbox"
+                  onChange={(e) => {
+                    if (e.target.checked) {
+                      setSelectedRows(userList.map((item) => item.id));
+                    } else {
+                      setSelectedRows([]);
+                    }
+                  }}
+                />
+              </Table.Cell>
+              {columns.map((col) => (
+                <Table.Cell key={col.accessor}>{col.header}</Table.Cell>
+              ))}
+            </Table.Row>
+          </Table.Head>
+          <Table.Body>
+            {userList.map((item) => (
+              <Table.Row key={item.id}>
+                <Table.Cell>
+                  <input
+                    type="checkbox"
+                    checked={selectedRows.includes(item.id)}
+                    onChange={(e) => {
+                      if (e.target.checked) {
+                        setSelectedRows([...selectedRows, item.id]);
+                      } else {
+                        setSelectedRows(selectedRows.filter((id) => id !== item.id));
+                      }
+                    }}
+                  />
+                </Table.Cell>
+                {columns.map((col) => (
+                  <Table.Cell key={col.accessor}>
+                    {col.render
+                      ? col.render(item[col.accessor] as any, item)
+                      : (item[col.accessor] as string)}
+                  </Table.Cell>
+                ))}
+              </Table.Row>
+            ))}
+          </Table.Body>
+        </Table>
+
+        {/* 分页 */}
+        <Flex justifyContent="center">
+          <Pagination
+            activePage={pagination.current}
+            totalItems={pagination.total}
+            itemsPerPage={pagination.pageSize}
+            onPageChange={(page) =>
+              setQueryParams({ ...queryParams, pageNo: page })
+            }
+          />
+        </Flex>
+      </Stack>
+
+      {/* 添加/编辑用户弹窗 */}
+      <Modal isShown={modalVisible} onClose={() => setModalVisible(false)}>
+        <Modal.Header
+          title={editingUser ? "编辑用户" : "新增用户"}
+          onClose={() => setModalVisible(false)}
+        />
+        <Modal.Content>
+          <Form>
+            <Stack spacing="spacingM">
+              <Flex gap="spacingM">
+                <FormControl>
+                  <FormControl.Label>用户账号</FormControl.Label>
+                  <TextInput
+                    value={form.username || ""}
+                    onChange={(e) => setForm({ ...form, username: e.target.value })}
+                    placeholder="请输入用户账号"
+                    isDisabled={!!editingUser}
+                  />
+                </FormControl>
+
+                <FormControl>
+                  <FormControl.Label>用户姓名</FormControl.Label>
+                  <TextInput
+                    value={form.realname || ""}
+                    onChange={(e) => setForm({ ...form, realname: e.target.value })}
+                    placeholder="请输入用户姓名"
+                  />
+                </FormControl>
+              </Flex>
+
+              {!editingUser && (
+                <FormControl>
+                  <FormControl.Label>登录密码</FormControl.Label>
+                  <TextInput
+                    type="password"
+                    value={form.password || ""}
+                    onChange={(e) => setForm({ ...form, password: e.target.value })}
+                    placeholder="请输入登录密码"
+                  />
+                </FormControl>
+              )}
+
+              <Flex gap="spacingM">
+                <FormControl>
+                  <FormControl.Label>性别</FormControl.Label>
+                  <Select
+                    value={form.sex || "1"}
+                    onChange={(e) => setForm({ ...form, sex: e.target.value as "1" | "2" })}
+                  >
+                    <Select.Option value="1">男</Select.Option>
+                    <Select.Option value="2">女</Select.Option>
+                  </Select>
+                </FormControl>
+
+                <FormControl>
+                  <FormControl.Label>状态</FormControl.Label>
+                  <Select
+                    value={form.status || "1"}
+                    onChange={(e) =>
+                      setForm({ ...form, status: e.target.value as "1" | "2" })
+                    }
+                  >
+                    <Select.Option value="1">正常</Select.Option>
+                    <Select.Option value="2">冻结</Select.Option>
+                  </Select>
+                </FormControl>
+              </Flex>
+
+              <Flex gap="spacingM">
+                <FormControl>
+                  <FormControl.Label>手机号码</FormControl.Label>
+                  <TextInput
+                    value={form.phone || ""}
+                    onChange={(e) => setForm({ ...form, phone: e.target.value })}
+                    placeholder="请输入手机号码"
+                  />
+                </FormControl>
+
+                <FormControl>
+                  <FormControl.Label>邮箱</FormControl.Label>
+                  <TextInput
+                    type="email"
+                    value={form.email || ""}
+                    onChange={(e) => setForm({ ...form, email: e.target.value })}
+                    placeholder="请输入邮箱"
+                  />
+                </FormControl>
+              </Flex>
+
+              <Flex gap="spacingM">
+                <FormControl>
+                  <FormControl.Label>工号</FormControl.Label>
+                  <TextInput
+                    value={form.workNo || ""}
+                    onChange={(e) => setForm({ ...form, workNo: e.target.value })}
+                    placeholder="请输入工号"
+                  />
+                </FormControl>
+
+                <FormControl>
+                  <FormControl.Label>职务</FormControl.Label>
+                  <TextInput
+                    value={form.post || ""}
+                    onChange={(e) => setForm({ ...form, post: e.target.value })}
+                    placeholder="请输入职务"
+                  />
+                </FormControl>
+              </Flex>
+            </Stack>
+          </Form>
+        </Modal.Content>
+        <Modal.Controls>
+          <Button variant="secondary" onClick={() => setModalVisible(false)}>
+            取消
+          </Button>
+          <Button variant="primary" onClick={handleSave}>
+            保存
+          </Button>
+        </Modal.Controls>
+      </Modal>
+
+      {/* 分配角色弹窗 */}
+      <Modal isShown={roleModalVisible} onClose={() => setRoleModalVisible(false)}>
+        <Modal.Header
+          title="分配角色"
+          onClose={() => setRoleModalVisible(false)}
+        />
+        <Modal.Content>
+          <Stack spacing="spacingM">
+            <Text>为用户 "{currentUser?.realname}" 分配角色:</Text>
+            <Stack spacing="spacingS">
+              {roleList.map((role) => (
+                <FormControl key={role.id}>
+                  <Switch
+                    isChecked={userRoles.includes(role.id)}
+                    onChange={(checked) => {
+                      if (checked) {
+                        setUserRoles([...userRoles, role.id]);
+                      } else {
+                        setUserRoles(userRoles.filter((id) => id !== role.id));
+                      }
+                    }}
+                  >
+                    {role.roleName}
+                  </Switch>
+                </FormControl>
+              ))}
+            </Stack>
+          </Stack>
+        </Modal.Content>
+        <Modal.Controls>
+          <Button variant="secondary" onClick={() => setRoleModalVisible(false)}>
+            取消
+          </Button>
+          <Button variant="primary" onClick={handleSaveUserRole}>
+            保存
+          </Button>
+        </Modal.Controls>
+      </Modal>
+    </Card>
+  );
+}

+ 3 - 0
src/router/index.tsx

@@ -11,6 +11,7 @@ import {
 } from "react-router-dom";
 import MainLayout from "@/layouts/MainLayout/index";
 import { publicRoutes } from "./publicRoutes.tsx";
+// import { systemRoutes } from "./systemRoutes.tsx";
 
 import type { JeecgMenu } from "@/types/menu";
 
@@ -183,6 +184,8 @@ function generateProtectedRoutes(menus: JeecgMenu[]): RouteObject {
         element: <Navigate to={renderMenus[0]?.path || "/content-model"} replace />,
       },
       ...routes,
+      // 添加系统管理路由
+      // systemRoutes,
     ],
   };
 }

+ 50 - 0
src/router/systemRoutes.tsx

@@ -0,0 +1,50 @@
+/**
+ * 系统管理路由配置
+ */
+import { lazy, Suspense } from "react";
+import type { RouteObject } from "react-router-dom";
+
+// 加载组件
+const SystemManagement = lazy(() => import("@/pages/system"));
+const MenuManagement = lazy(() => import("@/pages/system/menu"));
+const UserManagement = lazy(() => import("@/pages/system/user"));
+const RoleManagement = lazy(() => import("@/pages/system/role"));
+const LogManagement = lazy(() => import("@/pages/system/log"));
+
+// 加载中组件
+function LoadingPage() {
+  return <div style={{ padding: "20px" }}>加载中...</div>;
+}
+
+// 包装组件以支持 Suspense
+function withSuspense(Component: React.ComponentType) {
+  return (
+    <Suspense fallback={<LoadingPage />}>
+      <Component />
+    </Suspense>
+  );
+}
+
+// 系统管理路由配置
+export const systemRoutes: RouteObject = {
+  path: "system",
+  element: withSuspense(SystemManagement),
+  children: [
+    {
+      path: "menu",
+      element: withSuspense(MenuManagement),
+    },
+    {
+      path: "user",
+      element: withSuspense(UserManagement),
+    },
+    {
+      path: "role",
+      element: withSuspense(RoleManagement),
+    },
+    {
+      path: "log",
+      element: withSuspense(LogManagement),
+    },
+  ],
+};

+ 12 - 1
src/services/api.ts

@@ -6,7 +6,18 @@
 import { defHttp } from "@/http";
 
 // 导出系统管理相关 API
-export { authApi, permissionApi, dictApi, uploadApi, sysApi } from "./modules/system";
+export { 
+  authApi, 
+  permissionApi, 
+  dictApi, 
+  uploadApi, 
+  sysApi,
+  menuApi,
+  userApi,
+  roleApi,
+  logApi,
+  dictManageApi
+} from "./modules/system";
 
 // 兼容旧的导出方式
 export { permissionApi as permissionService } from "./modules/system";

+ 5 - 0
src/services/index.ts

@@ -11,4 +11,9 @@ export {
   dictApi,
   uploadApi,
   sysApi,
+  menuApi,
+  userApi,
+  roleApi,
+  logApi,
+  dictManageApi,
 } from "./modules/system";

+ 301 - 0
src/services/modules/system.ts

@@ -4,6 +4,19 @@
 
 import { defHttp } from "@/http";
 import type { JeecgUserPermissions } from "@/types/menu";
+import type {
+  SysMenu,
+  SysUser,
+  SysRole,
+  SysLog,
+  SysDict,
+  SysDictItem,
+  PageResult,
+  MenuQueryParams,
+  UserQueryParams,
+  RoleQueryParams,
+  LogQueryParams,
+} from "@/types/system";
 
 const http = defHttp;
 
@@ -116,3 +129,291 @@ export const sysApi = {
   ...authApi,
   ...permissionApi,
 };
+
+/**
+ * 菜单管理相关
+ */
+export const menuApi = {
+  // 获取菜单列表
+  getMenuList: (params?: MenuQueryParams) =>
+    http.get<SysMenu[]>({
+      url: "/sys/permission/list",
+      params,
+    }),
+
+  // 获取菜单树
+  getMenuTree: () =>
+    http.get<{treeList: SysMenu[], ids: string[]}>({
+      url: "/sys/permission/queryTreeList",
+    }),
+
+  // 获取菜单详情
+  getMenuDetail: (id: string) =>
+    http.get<SysMenu>({
+      url: `/sys/permission/queryById`,
+      params: { id },
+    }),
+
+  // 添加菜单
+  addMenu: (data: Partial<SysMenu>) =>
+    http.post<{ success: boolean; message: string }>({
+      url: "/sys/permission/add",
+      data,
+    }),
+
+  // 编辑菜单
+  editMenu: (data: Partial<SysMenu>) =>
+    http.put<{ success: boolean; message: string }>({
+      url: "/sys/permission/edit",
+      data,
+    }),
+
+  // 删除菜单
+  deleteMenu: (id: string) =>
+    http.delete<{ success: boolean; message: string }>({
+      url: `/sys/permission/delete`,
+      params: { id },
+    }),
+
+  // 批量删除菜单
+  deleteBatchMenu: (ids: string[]) =>
+    http.delete<{ success: boolean; message: string }>({
+      url: "/sys/permission/deleteBatch",
+      params: { ids: ids.join(",") },
+    }),
+};
+
+/**
+ * 用户管理相关
+ */
+export const userApi = {
+  // 获取用户列表
+  getUserList: (params?: UserQueryParams) =>
+    http.get<PageResult<SysUser>>({
+      url: "/sys/user/list",
+      params,
+    }),
+
+  // 获取用户详情
+  getUserDetail: (id: string) =>
+    http.get<SysUser>({
+      url: `/sys/user/queryById`,
+      params: { id },
+    }),
+
+  // 添加用户
+  addUser: (data: Partial<SysUser>) =>
+    http.post<{ success: boolean; message: string }>({
+      url: "/sys/user/add",
+      data,
+    }),
+
+  // 编辑用户
+  editUser: (data: Partial<SysUser>) =>
+    http.put<{ success: boolean; message: string }>({
+      url: "/sys/user/edit",
+      data,
+    }),
+
+  // 删除用户
+  deleteUser: (id: string) =>
+    http.delete<{ success: boolean; message: string }>({
+      url: `/sys/user/delete`,
+      params: { id },
+    }),
+
+  // 批量删除用户
+  deleteBatchUser: (ids: string[]) =>
+    http.delete<{ success: boolean; message: string }>({
+      url: "/sys/user/deleteBatch",
+      params: { ids: ids.join(",") },
+    }),
+
+  // 重置密码
+  resetPassword: (id: string) =>
+    http.put<{ success: boolean; message: string }>({
+      url: "/sys/user/resetPassword",
+      data: { id },
+    }),
+
+  // 冻结/解冻用户
+  frozenBatch: (ids: string[], status: '1' | '2') =>
+    http.put<{ success: boolean; message: string }>({
+      url: "/sys/user/frozenBatch",
+      data: { ids: ids.join(","), status },
+    }),
+
+  // 获取用户角色
+  getUserRoles: (userId: string) =>
+    http.get<string[]>({
+      url: "/sys/user/queryUserRole",
+      params: { userid: userId },
+    }),
+
+  // 编辑用户角色
+  editUserRole: (userId: string, roleIds: string[]) =>
+    http.post<{ success: boolean; message: string }>({
+      url: "/sys/user/editUserRole",
+      data: { userid: userId, roleIds: roleIds.join(",") },
+    }),
+};
+
+/**
+ * 角色管理相关
+ */
+export const roleApi = {
+  // 获取角色列表
+  getRoleList: (params?: RoleQueryParams) =>
+    http.get<PageResult<SysRole>>({
+      url: "/sys/role/list",
+      params,
+    }),
+
+  // 获取所有角色
+  getAllRoles: () =>
+    http.get<SysRole[]>({
+      url: "/sys/role/queryall",
+    }),
+
+  // 获取角色详情
+  getRoleDetail: (id: string) =>
+    http.get<SysRole>({
+      url: `/sys/role/queryById`,
+      params: { id },
+    }),
+
+  // 添加角色
+  addRole: (data: Partial<SysRole>) =>
+    http.post<{ success: boolean; message: string }>({
+      url: "/sys/role/add",
+      data,
+    }),
+
+  // 编辑角色
+  editRole: (data: Partial<SysRole>) =>
+    http.put<{ success: boolean; message: string }>({
+      url: "/sys/role/edit",
+      data,
+    }),
+
+  // 删除角色
+  deleteRole: (id: string) =>
+    http.delete<{ success: boolean; message: string }>({
+      url: `/sys/role/delete`,
+      params: { id },
+    }),
+
+  // 批量删除角色
+  deleteBatchRole: (ids: string[]) =>
+    http.delete<{ success: boolean; message: string }>({
+      url: "/sys/role/deleteBatch",
+      params: { ids: ids.join(",") },
+    }),
+
+  // 检查角色编码是否存在
+  checkRoleCode: (id: string, roleCode: string) =>
+    http.get<{ success: boolean; message: string }>({
+      url: "/sys/role/checkRoleCode",
+      params: { id, roleCode },
+    }),
+};
+
+/**
+ * 日志管理相关
+ */
+export const logApi = {
+  // 获取日志列表
+  getLogList: (params?: LogQueryParams) =>
+    http.get<PageResult<SysLog>>({
+      url: "/sys/log/list",
+      params,
+    }),
+
+  // 删除日志
+  deleteLog: (id: string) =>
+    http.delete<{ success: boolean; message: string }>({
+      url: `/sys/log/delete`,
+      params: { id },
+    }),
+
+  // 批量删除日志
+  deleteBatchLog: (ids: string[]) =>
+    http.delete<{ success: boolean; message: string }>({
+      url: "/sys/log/deleteBatch",
+      params: { ids: ids.join(",") },
+    }),
+
+  // 清空日志
+  deleteAllLog: () =>
+    http.delete<{ success: boolean; message: string }>({
+      url: "/sys/log/deleteAll",
+    }),
+};
+
+/**
+ * 数据字典管理相关
+ */
+export const dictManageApi = {
+  // 获取字典列表
+  getDictList: (params?: any) =>
+    http.get<PageResult<SysDict>>({
+      url: "/sys/dict/list",
+      params,
+    }),
+
+  // 获取字典详情
+  getDictDetail: (id: string) =>
+    http.get<SysDict>({
+      url: `/sys/dict/queryById`,
+      params: { id },
+    }),
+
+  // 添加字典
+  addDict: (data: Partial<SysDict>) =>
+    http.post<{ success: boolean; message: string }>({
+      url: "/sys/dict/add",
+      data,
+    }),
+
+  // 编辑字典
+  editDict: (data: Partial<SysDict>) =>
+    http.put<{ success: boolean; message: string }>({
+      url: "/sys/dict/edit",
+      data,
+    }),
+
+  // 删除字典
+  deleteDict: (id: string) =>
+    http.delete<{ success: boolean; message: string }>({
+      url: `/sys/dict/delete`,
+      params: { id },
+    }),
+
+  // 获取字典项列表
+  getDictItemList: (dictId: string, params?: any) =>
+    http.get<PageResult<SysDictItem>>({
+      url: "/sys/dictItem/list",
+      params: { ...params, dictId },
+    }),
+
+  // 添加字典项
+  addDictItem: (data: Partial<SysDictItem>) =>
+    http.post<{ success: boolean; message: string }>({
+      url: "/sys/dictItem/add",
+      data,
+    }),
+
+  // 编辑字典项
+  editDictItem: (data: Partial<SysDictItem>) =>
+    http.put<{ success: boolean; message: string }>({
+      url: "/sys/dictItem/edit",
+      data,
+    }),
+
+  // 删除字典项
+  deleteDictItem: (id: string) =>
+    http.delete<{ success: boolean; message: string }>({
+      url: `/sys/dictItem/delete`,
+      params: { id },
+    }),
+};

+ 176 - 0
src/types/system.ts

@@ -0,0 +1,176 @@
+/**
+ * 系统管理相关类型定义
+ */
+
+// 基础分页参数
+export interface PageParams {
+  pageNo: number;
+  pageSize: number;
+}
+
+// 分页响应
+export interface PageResult<T> {
+  records: T[];
+  total: number;
+  size: number;
+  current: number;
+  pages: number;
+}
+
+// 菜单管理
+export interface SysMenu {
+  id: string;
+  parentId?: string;
+  name: string;
+  url?: string;
+  component?: string;
+  redirect?: string;
+  menuType: 0 | 1 | 2; // 0:一级菜单 1:子菜单 2:按钮权限
+  perms?: string;
+  permsType?: '0' | '1'; // 0:可见/可访问 1:可编辑
+  sortNo: number;
+  alwaysShow?: boolean;
+  icon?: string;
+  isRoute?: boolean;
+  isLeaf?: boolean;
+  keepAlive?: boolean;
+  hidden?: boolean;
+  hideTab?: boolean;
+  description?: string;
+  status?: '0' | '1'; // 0:正常 1:禁用
+  delFlag?: '0' | '1'; // 0:正常 1:删除
+  ruleflag?: '0' | '1';
+  createBy?: string;
+  createTime?: string;
+  updateBy?: string;
+  updateTime?: string;
+  children?: SysMenu[];
+}
+
+// 用户管理
+export interface SysUser {
+  id: string;
+  username: string;
+  realname: string;
+  password?: string;
+  salt?: string;
+  avatar?: string;
+  birthday?: string;
+  sex?: '1' | '2'; // 1:男 2:女
+  email?: string;
+  phone?: string;
+  orgCode?: string;
+  status?: '1' | '2'; // 1:正常 2:冻结
+  delFlag?: '0' | '1'; // 0:正常 1:删除
+  workNo?: string;
+  post?: string;
+  telephone?: string;
+  createBy?: string;
+  createTime?: string;
+  updateBy?: string;
+  updateTime?: string;
+  userIdentity?: '1' | '2'; // 1:普通用户 2:上级
+  departIds?: string;
+  relTenantIds?: string;
+  clientId?: string;
+}
+
+// 角色管理
+export interface SysRole {
+  id: string;
+  roleName: string;
+  roleCode: string;
+  description?: string;
+  createBy?: string;
+  createTime?: string;
+  updateBy?: string;
+  updateTime?: string;
+}
+
+// 用户角色关联
+export interface SysUserRole {
+  id: string;
+  userId: string;
+  roleId: string;
+}
+
+// 角色权限关联
+export interface SysRolePermission {
+  id: string;
+  roleId: string;
+  permissionId: string;
+}
+
+// 系统日志
+export interface SysLog {
+  id: string;
+  logType?: '1' | '2'; // 1:登录日志 2:操作日志
+  logContent?: string;
+  operateType?: '1' | '2' | '3' | '4' | '5'; // 1:添加 2:修改 3:删除 4:查询 5:其他
+  userid?: string;
+  username?: string;
+  ip?: string;
+  method?: string;
+  requestUrl?: string;
+  requestParam?: string;
+  requestType?: string;
+  costTime?: number;
+  createBy?: string;
+  createTime?: string;
+  updateBy?: string;
+  updateTime?: string;
+}
+
+// 数据字典
+export interface SysDict {
+  id: string;
+  dictName: string;
+  dictCode: string;
+  description?: string;
+  delFlag?: '0' | '1';
+  createBy?: string;
+  createTime?: string;
+  updateBy?: string;
+  updateTime?: string;
+  type?: '0' | '1'; // 0:字符 1:数字
+}
+
+// 数据字典项
+export interface SysDictItem {
+  id: string;
+  dictId: string;
+  itemText: string;
+  itemValue: string;
+  description?: string;
+  sortOrder?: number;
+  status?: '0' | '1'; // 0:正常 1:禁用
+  createBy?: string;
+  createTime?: string;
+  updateBy?: string;
+  updateTime?: string;
+}
+
+// 查询参数
+export interface MenuQueryParams extends PageParams {
+  menuName?: string;
+  status?: string;
+}
+
+export interface UserQueryParams extends PageParams {
+  username?: string;
+  realname?: string;
+  status?: string;
+}
+
+export interface RoleQueryParams extends PageParams {
+  roleName?: string;
+  roleCode?: string;
+}
+
+export interface LogQueryParams extends PageParams {
+  logType?: string;
+  operateType?: string;
+  username?: string;
+  startTime?: string;
+  endTime?: string;
+}