소스 검색

完成功能:1.登录页 2.路由权限配置 3.菜单 4.用户管理页 5.设置功能汉化

linyuanjie 4 달 전
부모
커밋
a5e5d40b5b
41개의 변경된 파일3078개의 추가작업 그리고 1753개의 파일을 삭제
  1. 3 2
      index.html
  2. 4 3
      package.json
  3. 418 0
      pnpm-lock.yaml
  4. 0 2
      src/api/request/index.ts
  5. 24 0
      src/api/services/dept/data.d.ts
  6. 64 0
      src/api/services/dept/index.ts
  7. 0 1
      src/api/services/login/index.ts
  8. 32 0
      src/api/services/post/data.d.ts
  9. 70 0
      src/api/services/post/index.ts
  10. 40 0
      src/api/services/role/data.d.ts
  11. 63 0
      src/api/services/role/index.ts
  12. BIN
      src/assets/images/logo.png
  13. 35 37
      src/components/icon/svg-icon.tsx
  14. 10 18
      src/components/logo/index.tsx
  15. 1 1
      src/components/verifition/Verify.tsx
  16. 32 24
      src/components/verifition/Verify/VerifySlide/index.tsx
  17. 20 30
      src/components/verifition/Verify/VerifySlide/style.js
  18. 12 17
      src/components/verifition/style.js
  19. 79 87
      src/layouts/components/account-dropdown.tsx
  20. 454 447
      src/layouts/components/setting-button.tsx
  21. 9 9
      src/layouts/dashboard/header.tsx
  22. 36 31
      src/layouts/dashboard/nav/nav-logo.tsx
  23. 4 7
      src/layouts/dashboard/nav/nav-vertical.tsx
  24. 29 32
      src/locales/i18n.ts
  25. 38 39
      src/locales/use-locale.ts
  26. 2 3
      src/pages/sys/login/Login.tsx
  27. 0 11
      src/pages/sys/login/LoginForm.tsx
  28. 676 656
      src/pages/sys/login/components/UserAgreementModal.tsx
  29. 5 0
      src/pages/system/auth/menu/index.tsx
  30. 235 0
      src/pages/system/auth/user/components/AddModal.tsx
  31. 149 0
      src/pages/system/auth/user/components/DeptTreeSelect.tsx
  32. 252 4
      src/pages/system/auth/user/index.tsx
  33. 0 1
      src/router/components/protected-route.tsx
  34. 15 18
      src/router/hooks/use-permission-routes.tsx
  35. 52 52
      src/router/hooks/use-route-to-menu.tsx
  36. 2 9
      src/router/index.tsx
  37. 0 6
      src/router/routes/modules/dashboard.tsx
  38. 30 31
      src/router/utils.ts
  39. 54 52
      src/store/userStore.ts
  40. 123 123
      src/theme/tokens/color.ts
  41. 6 0
      types/enum.ts

+ 3 - 2
index.html

@@ -1,13 +1,14 @@
-<!DOCTYPE html>
+<!doctype html>
 <html lang="en">
   <head>
     <meta charset="UTF-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>Slash Admin</title>
+    <title>要易业务管理系统</title>
   </head>
   <body>
     <noscript>You need to enable JavaScript to run this app.</noscript>
     <div id="root"></div>
     <script type="module" src="/src/main.tsx"></script>
+    <script src="//at.alicdn.com/t/c/font_2786994_xnpdvvnci2.js"></script>
   </body>
 </html>

+ 4 - 3
package.json

@@ -14,6 +14,7 @@
   "dependencies": {
     "@ant-design/cssinjs": "^1.17.2",
     "@ant-design/icons": "^5.2.6",
+    "@ant-design/pro-components": "^2.8.5",
     "@dnd-kit/core": "^6.3.1",
     "@dnd-kit/sortable": "^10.0.0",
     "@dnd-kit/utilities": "^3.2.2",
@@ -50,6 +51,7 @@
     "i18next-browser-languagedetector": "^7.1.0",
     "nprogress": "^0.2.0",
     "numeral": "^2.0.6",
+    "qs": "^6.11.0",
     "ramda": "^0.29.1",
     "react": "18.2.0",
     "react-apexcharts": "^1.4.1",
@@ -73,8 +75,7 @@
     "sonner": "^1.7.0",
     "styled-components": "^6.0.9",
     "tailwind-merge": "^2.5.4",
-    "zustand": "^4.4.3",
-    "qs": "^6.11.0"
+    "zustand": "^4.4.3"
   },
   "devDependencies": {
     "@commitlint/cli": "^17.7.2",
@@ -85,12 +86,12 @@
     "@types/color": "^3.0.4",
     "@types/nprogress": "^0.2.1",
     "@types/numeral": "^2.0.3",
+    "@types/qs": "^6.9.7",
     "@types/ramda": "^0.29.6",
     "@types/react": "^18.2.28",
     "@types/react-dom": "^18.2.13",
     "@types/react-transition-group": "^4.4.12",
     "@types/styled-components": "^5.1.28",
-    "@types/qs": "^6.9.7",
     "@typescript-eslint/eslint-plugin": "^8.23.0",
     "@typescript-eslint/parser": "^8.23.0",
     "autoprefixer": "^10.4.16",

+ 418 - 0
pnpm-lock.yaml

@@ -14,6 +14,9 @@ importers:
       '@ant-design/icons':
         specifier: ^5.2.6
         version: 5.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-components':
+        specifier: ^2.8.5
+        version: 2.8.5(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(rc-field-form@2.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
       '@dnd-kit/core':
         specifier: ^6.3.1
         version: 6.3.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -86,6 +89,9 @@ importers:
       axios:
         specifier: ^1.5.1
         version: 1.7.7
+      bignumber.js:
+        specifier: ^9.1.1
+        version: 9.1.2
       classnames:
         specifier: ^2.3.2
         version: 2.5.1
@@ -346,6 +352,82 @@ packages:
       react: '>=16.0.0'
       react-dom: '>=16.0.0'
 
+  '@ant-design/pro-card@2.9.5':
+    resolution: {integrity: sha512-2ioOI6G4/zkC+YgnvRjAMgKKz2VNuGCaqYrmpDZDCl7ptWiwDoE13uQxR7+5x7STGzMwQLSf09Z45Qh1IUVJiQ==}
+    peerDependencies:
+      antd: ^4.24.15 || ^5.11.2
+      react: '>=17.0.0'
+
+  '@ant-design/pro-components@2.8.5':
+    resolution: {integrity: sha512-wHPuoPXN6qVBAE6L6GKGPfMG0dUZWnsGAA9i66jIQYibATOY3ggw9VcBYVUhZKbFa6VlJ5axFMrSvYYgha6HQA==}
+    peerDependencies:
+      antd: ^4.24.15 || ^5.11.2
+      react: '>=17.0.0'
+      react-dom: '>=17.0.0'
+
+  '@ant-design/pro-descriptions@2.6.5':
+    resolution: {integrity: sha512-aj6pUCKpOcwYykSc6zAG8GJf5AJoNf1K8u2QfOB0qKdgK9wGFITUphfM+rFMxa1CrpDGbtApsmOmDLtYlkH2sg==}
+    peerDependencies:
+      antd: ^4.24.15 || ^5.11.2
+      react: '>=17.0.0'
+
+  '@ant-design/pro-field@3.0.2':
+    resolution: {integrity: sha512-eqJxMUeMLfBFs0AXH7geAsmCLEiYrRJw+tmyHO8bioUUgw8O2abG2jwdMRVOMkhhIKszbpLDNYOmCB2h/36eEQ==}
+    peerDependencies:
+      antd: ^4.24.15 || ^5.11.2
+      react: '>=17.0.0'
+
+  '@ant-design/pro-form@2.31.5':
+    resolution: {integrity: sha512-LTFx29c2LvO5m1PWsiI9C0VH78/b9fzC4EldD8OH6hy9dcH1bmKZdrVZogQKD1Y8m4yU43kDNzoPbizow6SdKQ==}
+    peerDependencies:
+      antd: ^4.24.15 || ^5.11.2
+      rc-field-form: '>=1.22.0'
+      react: '>=17.0.0'
+      react-dom: '>=17.0.0'
+
+  '@ant-design/pro-layout@7.22.2':
+    resolution: {integrity: sha512-RlXqN+EVnF1Sup84O0IjS/vMMgwFnbBZwvR+GVnmZg/+cIa4/BDTXyhbb1KRwUqzn1ctDzj7JfbWOWqmGMw6yA==}
+    peerDependencies:
+      antd: ^4.24.15 || ^5.11.2
+      react: '>=17.0.0'
+      react-dom: '>=17.0.0'
+
+  '@ant-design/pro-list@2.6.5':
+    resolution: {integrity: sha512-Mc8bgxaf4Ro3PUtfTAd6BrvHhNhi8Vi1CzHFJVqwBQo11Qly+T9eLCKIaKnFwm4wfatOL8f5ANCANW3Te/YDYQ==}
+    peerDependencies:
+      antd: ^4.24.15 || ^5.11.2
+      react: '>=17.0.0'
+      react-dom: '>=17.0.0'
+
+  '@ant-design/pro-provider@2.15.3':
+    resolution: {integrity: sha512-jUBCuRrhAXNMumSZ++704/zEg/7U1k2N3jMVBgtirvVaCAk5O9iZQKK4W3O3LRFc+D8yO16sXjsxhawvdGL4cA==}
+    peerDependencies:
+      antd: ^4.24.15 || ^5.11.2
+      react: '>=17.0.0'
+      react-dom: '>=17.0.0'
+
+  '@ant-design/pro-skeleton@2.2.1':
+    resolution: {integrity: sha512-3M2jNOZQZWEDR8pheY00OkHREfb0rquvFZLCa6DypGmiksiuuYuR9Y4iA82ZF+mva2FmpHekdwbje/GpbxqBeg==}
+    peerDependencies:
+      antd: ^4.24.15 || ^5.11.2
+      react: '>=17.0.0'
+      react-dom: '>=17.0.0'
+
+  '@ant-design/pro-table@3.18.5':
+    resolution: {integrity: sha512-LejqPfHnTL8aKIbh8uOY8BcKAP7OZZlD0UwxPU631OYixuEB5J++ayhZh6Yj3Yye62t3A7lXN9+Tc8pab9Golw==}
+    peerDependencies:
+      antd: ^4.24.15 || ^5.11.2
+      rc-field-form: '>=1.22.0'
+      react: '>=17.0.0'
+      react-dom: '>=17.0.0'
+
+  '@ant-design/pro-utils@2.16.3':
+    resolution: {integrity: sha512-uNjKh51v/SUlCJbWfhg2lRQB/TB0MyNMCQkFZ8mZBQ2rk3Ew47Sly6VssVVWMjIWBLE+g9fOgPg0C1IVeilIXA==}
+    peerDependencies:
+      antd: ^4.24.15 || ^5.11.2
+      react: '>=17.0.0'
+      react-dom: '>=17.0.0'
+
   '@ant-design/react-slick@1.1.2':
     resolution: {integrity: sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==}
     peerDependencies:
@@ -477,6 +559,11 @@ packages:
   '@bundled-es-modules/tough-cookie@0.1.6':
     resolution: {integrity: sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==}
 
+  '@chenshuai2144/sketch-color@1.0.9':
+    resolution: {integrity: sha512-obzSy26cb7Pm7OprWyVpgMpIlrZpZ0B7vbrU0RMbvRg0YAI890S5Xy02Aj1Nhl4+KTbi1lVYHt6HQP8Hm9s+1w==}
+    peerDependencies:
+      react: '>=16.12.0'
+
   '@commitlint/cli@17.8.1':
     resolution: {integrity: sha512-ay+WbzQesE0Rv4EQKfNbSMiJJ12KdKTDzIt0tcK4k11FdsWmtwP0Kp1NWMOUswfIWo6Eb7p7Ln721Nx9FLNBjg==}
     engines: {node: '>=v14'}
@@ -571,12 +658,24 @@ packages:
       react: '>=16.8.0'
       react-dom: '>=16.8.0'
 
+  '@dnd-kit/modifiers@6.0.1':
+    resolution: {integrity: sha512-rbxcsg3HhzlcMHVHWDuh9LCjpOVAgqbV78wLGI8tziXY3+qcMQ61qVXIvNKQFuhj75dSfD+o+PYZQ/NUk2A23A==}
+    peerDependencies:
+      '@dnd-kit/core': ^6.0.6
+      react: '>=16.8.0'
+
   '@dnd-kit/sortable@10.0.0':
     resolution: {integrity: sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==}
     peerDependencies:
       '@dnd-kit/core': ^6.3.0
       react: '>=16.8.0'
 
+  '@dnd-kit/sortable@7.0.2':
+    resolution: {integrity: sha512-wDkBHHf9iCi1veM834Gbk1429bd4lHX4RpAwT0y2cHLf246GAvU2sVw/oxWNpPKQNQRQaeGXhAVgrOl1IT+iyA==}
+    peerDependencies:
+      '@dnd-kit/core': ^6.0.7
+      react: '>=16.8.0'
+
   '@dnd-kit/utilities@3.2.2':
     resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==}
     peerDependencies:
@@ -1415,6 +1514,14 @@ packages:
     resolution: {integrity: sha512-oWWhcWDLwDfu++BGTZcmXWqpwtkwb5o7fxUIGksMQQDSdPW9prsSnfIOZMlsj4vBOSrcnjIUZMiIjODgGosFhQ==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
+  '@umijs/route-utils@4.0.1':
+    resolution: {integrity: sha512-+1ixf1BTOLuH+ORb4x8vYMPeIt38n9q0fJDwhv9nSxrV46mxbLF0nmELIo9CKQB2gHfuC4+hww6xejJ6VYnBHQ==}
+
+  '@umijs/use-params@1.0.9':
+    resolution: {integrity: sha512-QlN0RJSBVQBwLRNxbxjQ5qzqYIGn+K7USppMoIOVlf7fxXHsnQZ2bEsa6Pm74bt6DVQxpUE8HqvdStn6Y9FV1w==}
+    peerDependencies:
+      react: '*'
+
   '@ungap/structured-clone@1.3.0':
     resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
 
@@ -1491,6 +1598,9 @@ packages:
     engines: {node: '>=0.4.0'}
     hasBin: true
 
+  add-dom-event-listener@1.1.0:
+    resolution: {integrity: sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw==}
+
   ajv@6.12.6:
     resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
 
@@ -1670,6 +1780,9 @@ packages:
   big.js@5.2.2:
     resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==}
 
+  bignumber.js@9.1.2:
+    resolution: {integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==}
+
   binary-extensions@2.3.0:
     resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
     engines: {node: '>=8'}
@@ -3717,6 +3830,10 @@ packages:
   path-to-regexp@6.3.0:
     resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==}
 
+  path-to-regexp@8.2.0:
+    resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==}
+    engines: {node: '>=16'}
+
   path-type@4.0.0:
     resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
     engines: {node: '>=8'}
@@ -4021,6 +4138,12 @@ packages:
       react: '>=16.9.0'
       react-dom: '>=16.9.0'
 
+  rc-resize-observer@0.2.6:
+    resolution: {integrity: sha512-YX6nYnd6fk7zbuvT6oSDMKiZjyngjHoy+fz+vL3Tez38d/G5iGdaDJa2yE7345G6sc4Mm1IGRUIwclvltddhmA==}
+    peerDependencies:
+      react: '>=16.9.0'
+      react-dom: '>=16.9.0'
+
   rc-resize-observer@1.4.0:
     resolution: {integrity: sha512-PnMVyRid9JLxFavTjeDXEXo65HCRqbmLBw9xX9gfC4BZiSzbLXKzW3jPz+J0P71pLbD5tBMTT+mkstV5gD0c9Q==}
     peerDependencies:
@@ -4105,6 +4228,9 @@ packages:
       react: '>=16.9.0'
       react-dom: '>=16.9.0'
 
+  rc-util@4.21.1:
+    resolution: {integrity: sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg==}
+
   rc-util@5.43.0:
     resolution: {integrity: sha512-AzC7KKOXFqAdIBqdGWepL9Xn7cm3vnAmjlHqUnoQaTMZYhM4VlXGLkkHHxj/BZ7Td0+SOPKB4RGPboBVKT9htw==}
     peerDependencies:
@@ -4166,6 +4292,9 @@ packages:
   react-is@18.3.1:
     resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
 
+  react-lifecycles-compat@3.0.4:
+    resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==}
+
   react-markdown@8.0.7:
     resolution: {integrity: sha512-bvWbzG4MtOU62XqBx3Xx+zB2raaFFsq4mYiAzfjXJMEz2sixgeAfraA3tvzULF02ZdOMUOKTBFFaZJDDrq+BJQ==}
     peerDependencies:
@@ -4221,6 +4350,11 @@ packages:
     resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
     engines: {node: '>=0.10.0'}
 
+  reactcss@1.2.3:
+    resolution: {integrity: sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==}
+    peerDependencies:
+      react: '*'
+
   read-cache@1.0.0:
     resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
 
@@ -4402,6 +4536,10 @@ packages:
   safe-regex@1.1.0:
     resolution: {integrity: sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==}
 
+  safe-stable-stringify@2.5.0:
+    resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
+    engines: {node: '>=10'}
+
   sass@1.81.0:
     resolution: {integrity: sha512-Q4fOxRfhmv3sqCLoGfvrC9pRV8btc0UtqL9mN6Yrv6Qi9ScL55CVH1vlPP863ISLEEMNLLuu9P+enCeGHlnzhA==}
     engines: {node: '>=14.0.0'}
@@ -4756,6 +4894,11 @@ packages:
     engines: {node: '>=10.13.0'}
     hasBin: true
 
+  swr@2.3.2:
+    resolution: {integrity: sha512-RosxFpiabojs75IwQ316DGoDRmOqtiAj0tg8wCcbEu4CiLZBs/a9QNtHV7TUfDXmmlgqij/NqzKq/eLelyv9xA==}
+    peerDependencies:
+      react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
   synckit@0.9.2:
     resolution: {integrity: sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==}
     engines: {node: ^14.18.0 || >=16.0.0}
@@ -4801,6 +4944,9 @@ packages:
   through@2.3.8:
     resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
 
+  tinycolor2@1.6.0:
+    resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
+
   to-fast-properties@2.0.0:
     resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
     engines: {node: '>=4'}
@@ -5039,6 +5185,11 @@ packages:
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
 
+  use-sync-external-store@1.4.0:
+    resolution: {integrity: sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==}
+    peerDependencies:
+      react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
   use@3.1.1:
     resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==}
     engines: {node: '>=0.10.0'}
@@ -5129,6 +5280,9 @@ packages:
     resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
     engines: {node: '>=0.10.0'}
 
+  warning@4.0.3:
+    resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==}
+
   web-namespaces@2.0.1:
     resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
 
@@ -5289,6 +5443,194 @@ snapshots:
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
 
+  '@ant-design/pro-card@2.9.5(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
+    dependencies:
+      '@ant-design/cssinjs': 1.22.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/icons': 5.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-provider': 2.15.3(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-utils': 2.16.3(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@babel/runtime': 7.26.0
+      antd: 5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      classnames: 2.5.1
+      rc-resize-observer: 1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      rc-util: 5.43.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      react: 18.2.0
+    transitivePeerDependencies:
+      - react-dom
+
+  '@ant-design/pro-components@2.8.5(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(rc-field-form@2.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
+    dependencies:
+      '@ant-design/pro-card': 2.9.5(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-descriptions': 2.6.5(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(rc-field-form@2.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-field': 3.0.2(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-form': 2.31.5(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(rc-field-form@2.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-layout': 7.22.2(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-list': 2.6.5(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(rc-field-form@2.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-provider': 2.15.3(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-skeleton': 2.2.1(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-table': 3.18.5(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(rc-field-form@2.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-utils': 2.16.3(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@babel/runtime': 7.26.0
+      antd: 5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
+    transitivePeerDependencies:
+      - rc-field-form
+
+  '@ant-design/pro-descriptions@2.6.5(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(rc-field-form@2.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
+    dependencies:
+      '@ant-design/pro-field': 3.0.2(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-form': 2.31.5(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(rc-field-form@2.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-provider': 2.15.3(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-skeleton': 2.2.1(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-utils': 2.16.3(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@babel/runtime': 7.26.0
+      antd: 5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      rc-resize-observer: 0.2.6(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      rc-util: 5.43.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      react: 18.2.0
+    transitivePeerDependencies:
+      - rc-field-form
+      - react-dom
+
+  '@ant-design/pro-field@3.0.2(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
+    dependencies:
+      '@ant-design/icons': 5.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-provider': 2.15.3(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-utils': 2.16.3(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@babel/runtime': 7.26.0
+      '@chenshuai2144/sketch-color': 1.0.9(react@18.2.0)
+      antd: 5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      classnames: 2.5.1
+      dayjs: 1.11.13
+      lodash: 4.17.21
+      lodash-es: 4.17.21
+      rc-util: 5.43.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      react: 18.2.0
+      swr: 2.3.2(react@18.2.0)
+    transitivePeerDependencies:
+      - react-dom
+
+  '@ant-design/pro-form@2.31.5(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(rc-field-form@2.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
+    dependencies:
+      '@ant-design/icons': 5.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-field': 3.0.2(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-provider': 2.15.3(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-utils': 2.16.3(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@babel/runtime': 7.26.0
+      '@chenshuai2144/sketch-color': 1.0.9(react@18.2.0)
+      '@umijs/use-params': 1.0.9(react@18.2.0)
+      antd: 5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      classnames: 2.5.1
+      dayjs: 1.11.13
+      lodash: 4.17.21
+      lodash-es: 4.17.21
+      rc-field-form: 2.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      rc-resize-observer: 1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      rc-util: 5.43.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
+
+  '@ant-design/pro-layout@7.22.2(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
+    dependencies:
+      '@ant-design/cssinjs': 1.22.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/icons': 5.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-provider': 2.15.3(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-utils': 2.16.3(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@babel/runtime': 7.26.0
+      '@umijs/route-utils': 4.0.1
+      '@umijs/use-params': 1.0.9(react@18.2.0)
+      antd: 5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      classnames: 2.5.1
+      lodash: 4.17.21
+      lodash-es: 4.17.21
+      path-to-regexp: 8.2.0
+      rc-resize-observer: 1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      rc-util: 5.43.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
+      swr: 2.3.2(react@18.2.0)
+      warning: 4.0.3
+
+  '@ant-design/pro-list@2.6.5(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(rc-field-form@2.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
+    dependencies:
+      '@ant-design/cssinjs': 1.22.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/icons': 5.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-card': 2.9.5(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-field': 3.0.2(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-table': 3.18.5(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(rc-field-form@2.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-utils': 2.16.3(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@babel/runtime': 7.26.0
+      antd: 5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      classnames: 2.5.1
+      dayjs: 1.11.13
+      rc-resize-observer: 1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      rc-util: 4.21.1
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
+    transitivePeerDependencies:
+      - rc-field-form
+
+  '@ant-design/pro-provider@2.15.3(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
+    dependencies:
+      '@ant-design/cssinjs': 1.22.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@babel/runtime': 7.26.0
+      '@ctrl/tinycolor': 3.6.1
+      antd: 5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      dayjs: 1.11.13
+      rc-util: 5.43.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
+      swr: 2.3.2(react@18.2.0)
+
+  '@ant-design/pro-skeleton@2.2.1(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
+    dependencies:
+      '@babel/runtime': 7.26.0
+      antd: 5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
+
+  '@ant-design/pro-table@3.18.5(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(rc-field-form@2.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
+    dependencies:
+      '@ant-design/cssinjs': 1.22.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/icons': 5.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-card': 2.9.5(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-field': 3.0.2(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-form': 2.31.5(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(rc-field-form@2.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-provider': 2.15.3(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-utils': 2.16.3(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@babel/runtime': 7.26.0
+      '@dnd-kit/core': 6.3.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@dnd-kit/modifiers': 6.0.1(@dnd-kit/core@6.3.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0)
+      '@dnd-kit/sortable': 7.0.2(@dnd-kit/core@6.3.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0)
+      '@dnd-kit/utilities': 3.2.2(react@18.2.0)
+      antd: 5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      classnames: 2.5.1
+      dayjs: 1.11.13
+      lodash: 4.17.21
+      lodash-es: 4.17.21
+      rc-field-form: 2.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      rc-resize-observer: 1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      rc-util: 5.43.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
+
+  '@ant-design/pro-utils@2.16.3(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
+    dependencies:
+      '@ant-design/icons': 5.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@ant-design/pro-provider': 2.15.3(antd@5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@babel/runtime': 7.26.0
+      antd: 5.22.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      classnames: 2.5.1
+      dayjs: 1.11.13
+      lodash: 4.17.21
+      lodash-es: 4.17.21
+      rc-util: 5.43.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
+      safe-stable-stringify: 2.5.0
+      swr: 2.3.2(react@18.2.0)
+
   '@ant-design/react-slick@1.1.2(react@18.2.0)':
     dependencies:
       '@babel/runtime': 7.26.0
@@ -5473,6 +5815,12 @@ snapshots:
       '@types/tough-cookie': 4.0.5
       tough-cookie: 4.1.4
 
+  '@chenshuai2144/sketch-color@1.0.9(react@18.2.0)':
+    dependencies:
+      react: 18.2.0
+      reactcss: 1.2.3(react@18.2.0)
+      tinycolor2: 1.6.0
+
   '@commitlint/cli@17.8.1':
     dependencies:
       '@commitlint/format': 17.8.1
@@ -5612,6 +5960,13 @@ snapshots:
       react-dom: 18.2.0(react@18.2.0)
       tslib: 2.8.1
 
+  '@dnd-kit/modifiers@6.0.1(@dnd-kit/core@6.3.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0)':
+    dependencies:
+      '@dnd-kit/core': 6.3.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@dnd-kit/utilities': 3.2.2(react@18.2.0)
+      react: 18.2.0
+      tslib: 2.8.1
+
   '@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0)':
     dependencies:
       '@dnd-kit/core': 6.3.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -5619,6 +5974,13 @@ snapshots:
       react: 18.2.0
       tslib: 2.8.1
 
+  '@dnd-kit/sortable@7.0.2(@dnd-kit/core@6.3.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0)':
+    dependencies:
+      '@dnd-kit/core': 6.3.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@dnd-kit/utilities': 3.2.2(react@18.2.0)
+      react: 18.2.0
+      tslib: 2.8.1
+
   '@dnd-kit/utilities@3.2.2(react@18.2.0)':
     dependencies:
       react: 18.2.0
@@ -6398,6 +6760,12 @@ snapshots:
       '@typescript-eslint/types': 8.23.0
       eslint-visitor-keys: 4.2.0
 
+  '@umijs/route-utils@4.0.1': {}
+
+  '@umijs/use-params@1.0.9(react@18.2.0)':
+    dependencies:
+      react: 18.2.0
+
   '@ungap/structured-clone@1.3.0': {}
 
   '@vanilla-extract/babel-plugin-debug-ids@1.2.0':
@@ -6501,6 +6869,10 @@ snapshots:
 
   acorn@8.14.0: {}
 
+  add-dom-event-listener@1.1.0:
+    dependencies:
+      object-assign: 4.1.1
+
   ajv@6.12.6:
     dependencies:
       fast-deep-equal: 3.1.3
@@ -6774,6 +7146,8 @@ snapshots:
 
   big.js@5.2.2: {}
 
+  bignumber.js@9.1.2: {}
+
   binary-extensions@2.3.0: {}
 
   bluebird@3.7.2: {}
@@ -9251,6 +9625,8 @@ snapshots:
 
   path-to-regexp@6.3.0: {}
 
+  path-to-regexp@8.2.0: {}
+
   path-type@4.0.0: {}
 
   pathe@0.2.0: {}
@@ -9596,6 +9972,15 @@ snapshots:
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
 
+  rc-resize-observer@0.2.6(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
+    dependencies:
+      '@babel/runtime': 7.26.0
+      classnames: 2.5.1
+      rc-util: 5.43.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
+      resize-observer-polyfill: 1.5.1
+
   rc-resize-observer@1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
     dependencies:
       '@babel/runtime': 7.26.0
@@ -9719,6 +10104,14 @@ snapshots:
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
 
+  rc-util@4.21.1:
+    dependencies:
+      add-dom-event-listener: 1.1.0
+      prop-types: 15.8.1
+      react-is: 16.13.1
+      react-lifecycles-compat: 3.0.4
+      shallowequal: 1.1.0
+
   rc-util@5.43.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
     dependencies:
       '@babel/runtime': 7.26.0
@@ -9778,6 +10171,8 @@ snapshots:
 
   react-is@18.3.1: {}
 
+  react-lifecycles-compat@3.0.4: {}
+
   react-markdown@8.0.7(@types/react@18.3.12)(react@18.2.0):
     dependencies:
       '@types/hast': 2.3.10
@@ -9865,6 +10260,11 @@ snapshots:
     dependencies:
       loose-envify: 1.4.0
 
+  reactcss@1.2.3(react@18.2.0):
+    dependencies:
+      lodash: 4.17.21
+      react: 18.2.0
+
   read-cache@1.0.0:
     dependencies:
       pify: 2.3.0
@@ -10098,6 +10498,8 @@ snapshots:
     dependencies:
       ret: 0.1.15
 
+  safe-stable-stringify@2.5.0: {}
+
   sass@1.81.0:
     dependencies:
       chokidar: 4.0.1
@@ -10527,6 +10929,12 @@ snapshots:
       picocolors: 1.1.1
       stable: 0.1.8
 
+  swr@2.3.2(react@18.2.0):
+    dependencies:
+      dequal: 2.0.3
+      react: 18.2.0
+      use-sync-external-store: 1.4.0(react@18.2.0)
+
   synckit@0.9.2:
     dependencies:
       '@pkgr/core': 0.1.1
@@ -10591,6 +10999,8 @@ snapshots:
 
   through@2.3.8: {}
 
+  tinycolor2@1.6.0: {}
+
   to-fast-properties@2.0.0: {}
 
   to-object-path@0.3.0:
@@ -10870,6 +11280,10 @@ snapshots:
     dependencies:
       react: 18.2.0
 
+  use-sync-external-store@1.4.0(react@18.2.0):
+    dependencies:
+      react: 18.2.0
+
   use@3.1.1: {}
 
   util-deprecate@1.0.2: {}
@@ -10971,6 +11385,10 @@ snapshots:
 
   void-elements@3.1.0: {}
 
+  warning@4.0.3:
+    dependencies:
+      loose-envify: 1.4.0
+
   web-namespaces@2.0.1: {}
 
   which-boxed-primitive@1.0.2:

+ 0 - 2
src/api/request/index.ts

@@ -117,7 +117,6 @@ axiosInstance.interceptors.response.use(
   (res) => {
     if (!res.data) throw new Error(t('sys.api.apiRequestFailed'))
     const { data, status, statusText } = res
-    console.log(res)
 
     // 业务请求成功
     const hasSuccess = data && status === ResultEnum.SUCCESS
@@ -129,7 +128,6 @@ axiosInstance.interceptors.response.use(
     throw new Error(statusText || t('sys.api.apiRequestFailed'))
   },
   (err) => {
-    console.log(err)
     handleNetworkError(err.response)
     if (err.response) {
       // 请求已发出, 服务器用状态码响应

+ 24 - 0
src/api/services/dept/data.d.ts

@@ -0,0 +1,24 @@
+declare namespace API {
+  type DeptTreeItem = {
+    children?: DeptTreeItem[]
+    id: string
+    parentId: string
+    weight: number
+    name: string
+    isLock: boolean
+    createdTime: string
+  }
+
+  type DeptTreeResult = DeptTreeItem[]
+
+  /** 部门参数 */
+  type DeptParams = {
+    parentId: number | string
+    deptId: number
+    deptName: string
+    sortOrder: number
+  }
+
+  /** 新增部门入参 */
+  type AddDeptParams = Pick<DeptParams, 'deptName' | 'parentId' | 'sortOrder'>
+}

+ 64 - 0
src/api/services/dept/index.ts

@@ -0,0 +1,64 @@
+import Request from '../..'
+
+enum Api {
+  DeptTree = '/workspace/dept/tree',
+  DeptCreate = '/workspace/dept/create',
+  DeptUpdate = '/workspace/dept/update',
+  DeptDetail = '/workspace/dept/details',
+  DeptDelete = '/workspace/dept/del',
+  AllDeptUser = '/workspace/dept/org',
+  SearchUser = '/workspace/dept/org/user/search'
+}
+
+export function fetchTree(deptName?: string) {
+  return Request.get<API.DeptTreeResult>({
+    url: Api.DeptTree,
+    params: {
+      deptName,
+      parentId: -1
+    }
+  })
+}
+
+export function addObj(data: API.AddDeptParams) {
+  return Request.post({
+    url: Api.DeptCreate,
+    data
+  })
+}
+
+export function putObj(data: API.DeptParams) {
+  return Request.post({
+    url: Api.DeptUpdate,
+    data
+  })
+}
+
+export function getObj(id: number | string) {
+  return Request.get<API.DeptTreeItem>({
+    url: Api.DeptDetail + `/${id}`
+  })
+}
+
+export function delObj(id: number | string) {
+  return Request.post({
+    url: Api.DeptDelete,
+    data: { deptId: id, deleteType: 'SOFT' }
+  })
+}
+
+// 查询全部部门(包含用户)
+export function getAllDeptAndUser(parentDeptId: number | string = -1, type: string) {
+  return Request.get<API.AllDeptUser>({
+    url: Api.AllDeptUser,
+    params: { parentDeptId, type }
+  })
+}
+
+// 模糊搜索用户
+export function userSearch(query: { username: string }) {
+  return Request.get<API.Employee[]>({
+    url: Api.SearchUser,
+    params: query
+  })
+}

+ 0 - 1
src/api/services/login/index.ts

@@ -47,7 +47,6 @@ export const loginByUsername = (
 ) => {
   const grant_type = 'password'
   const dataObj = qs.stringify({ username: username, password: password })
-  console.log(randomStr)
   return Request.post<LoginResult, false>({
     url: Api.Login,
     headers: {

+ 32 - 0
src/api/services/post/data.d.ts

@@ -0,0 +1,32 @@
+declare namespace API {
+  type PostListItem = {
+    postId: string
+    postCode: string
+    postName: string
+    postSort: number
+    remark: string
+    delFlag: string
+    createBy: string
+    updateBy: string
+    createTime: string
+    updateTime: string
+  }
+
+  /** 岗位列表结果 */
+  type PostListResult = PostListItem[]
+
+  /** 岗位分页结果 */
+  type PostPageResult = PostListItem[]
+
+  /** 编辑岗位入参 */
+  type UpdatePostParams = {
+    postId: number
+    postCode: string
+    postName: string
+    postSort: number
+    remarks?: string
+  }
+
+  /** 新增岗位入参 */
+  type AddPostParams = Omit<UpdatePostParams, 'postId'>
+}

+ 70 - 0
src/api/services/post/index.ts

@@ -0,0 +1,70 @@
+import Request from '../..'
+
+enum Api {
+  RolesList = '/workspace/role/list',
+  PostPage = '/workspace/post/page',
+  PostList = '/workspace/post/list',
+  PostDel = '/workspace/post/del',
+  PostAdd = '/workspace/post/create',
+  PostUpdate = '/workspace/post/update',
+  RolesBase = '/workspace/role',
+  RolesMenuUpdate = '/workspace/role/menu/update',
+  RolesMenuTree = '/workspace/menu/role/list'
+}
+
+export function deptRoleList() {
+  return Request.get<API.RoleListResult>({
+    url: Api.RolesList
+  })
+}
+
+export function fetchList(query: API.PageParams) {
+  return Request.get<API.TableListResult<API.PostPageResult>>({
+    url: Api.PostPage,
+    params: query
+  })
+}
+
+export function fetchPostList() {
+  return Request.get<API.PostListResult>({
+    url: Api.PostList
+  })
+}
+
+export function addObj(data: API.AddPostParams) {
+  return Request.post({
+    url: Api.PostAdd,
+    data
+  })
+}
+
+export function putObj(data: API.UpdatePostParams) {
+  return Request.post({
+    url: Api.PostUpdate,
+    data
+  })
+}
+
+export function delObj(id: string) {
+  return Request.post({
+    url: Api.PostDel,
+    data: { postIds: [id], deleteType: 'SOFT' }
+  })
+}
+
+export function permissionUpd(roleId: number, menuIds: string) {
+  return Request.post({
+    url: Api.RolesMenuUpdate,
+    data: {
+      roleId,
+      menuIds
+    }
+  })
+}
+
+export function fetchRoleTree(roleId: number) {
+  return Request.get<number[]>({
+    url: Api.RolesMenuTree,
+    params: { roleId }
+  })
+}

+ 40 - 0
src/api/services/role/data.d.ts

@@ -0,0 +1,40 @@
+declare namespace API {
+  type RoleListItem = {
+    roleId: number
+    roleName: string
+    roleCode: string
+    roleDesc: string
+    dataScopeType: string
+    scopeIds?: any
+    delFlag: string
+    createdBy: string
+    modifiedBy: string
+    createdTime: string
+    modifiedTime: string
+  }
+
+  /** 角色列表结果 */
+  type RoleListResult = RoleListItem[]
+
+  /** 角色分页结果 */
+  type RolesPageResult = RoleListItem[]
+
+  /** 编辑角色入参 */
+  type UpdateRolesParams = {
+    createdBy: string
+    createdTime: string
+    dsScope: string
+    dataScopeType: string
+    id: number
+    modifiedBy: string
+    modifiedTime: string
+    optFlag: string
+    roleCode: string
+    roleDesc: string
+    roleName: string
+    scopeIds?: any
+  }
+
+  /** 新增角色入参 */
+  type AddRolesParams = Pick<UpdateRolesParams, 'dsType' | 'roleCode' | 'roleDesc' | 'roleName'>
+}

+ 63 - 0
src/api/services/role/index.ts

@@ -0,0 +1,63 @@
+import Request from '../..'
+
+enum Api {
+  RolesList = '/workspace/role/list',
+  RolesPage = '/workspace/role/page',
+  RolesBase = '/workspace/role',
+  RoleCreate = '/workspace/role/create',
+  RoleUpdate = '/workspace/role/update',
+  RoleDel = '/workspace/role/del',
+  RolesMenuUpdate = '/workspace/role/menu/update',
+  RolesMenuTree = '/workspace/menu/role/list'
+}
+
+export function deptRoleList() {
+  return Request.get<API.RoleListResult>({
+    url: Api.RolesList
+  })
+}
+
+export function fetchList(query: API.PageParams) {
+  return Request.get<API.TableListResult<API.RolesPageResult>>({
+    url: Api.RolesPage,
+    params: query
+  })
+}
+
+export function addObj(data: API.AddRolesParams) {
+  return Request.post({
+    url: Api.RoleCreate,
+    data
+  })
+}
+
+export function putObj(data: API.UpdateRolesParams) {
+  return Request.post({
+    url: Api.RoleUpdate,
+    data
+  })
+}
+
+export function delObj(id: number) {
+  return Request.post({
+    url: Api.RoleDel,
+    data: { roleIds: [id], deleteType: 'SOFT' }
+  })
+}
+
+export function permissionUpd(roleId: number, menuIds: string[]) {
+  return Request.post({
+    url: Api.RolesMenuUpdate,
+    data: {
+      roleId,
+      menuIds
+    }
+  })
+}
+
+export function fetchRoleTree(roleId: number) {
+  return Request.get<string[]>({
+    url: Api.RolesMenuTree,
+    params: { roleId }
+  })
+}

BIN
src/assets/images/logo.png


+ 35 - 37
src/components/icon/svg-icon.tsx

@@ -1,44 +1,42 @@
-import { cn } from '@/utils';
-import type { CSSProperties } from 'react';
+import type { CSSProperties } from 'react'
+import { cn } from '@/utils'
 
 interface SvgIconProps {
-	prefix?: string;
-	icon: string;
-	color?: string;
-	size?: string | number;
-	className?: string;
-	style?: CSSProperties;
+  prefix?: string
+  icon: string
+  color?: string
+  size?: string | number
+  className?: string
+  style?: CSSProperties
 }
 
 export default function SvgIcon({
-	icon,
-	prefix = 'icon',
-	color = 'currentColor',
-	size = '1em',
-	className = '',
-	style = {}
+  icon,
+  prefix = 'icon',
+  color = 'currentColor',
+  size = '1em',
+  className = '',
+  style = {}
 }: SvgIconProps) {
-	const symbolId = `#${prefix}-${icon}`;
-	const svgStyle: CSSProperties = {
-		verticalAlign: 'middle',
-		width: size,
-		height: size,
-		color,
-		...style
-	};
-	return (
-		<svg
-			xmlns="http://www.w3.org/2000/svg"
-			viewBox="0 0 100 100"
-			className={cn(
-				'anticon fill-current inline-block h-[1em] w-[1em] overflow-hidden outline-none',
-				className
-			)}
-			style={svgStyle}
-			aria-label={icon}
-		>
-			<title>{icon}</title>
-			<use xlinkHref={symbolId} fill="currentColor" />
-		</svg>
-	);
+  const symbolId = `#${prefix}-${icon}`
+  const svgStyle: CSSProperties = {
+    width: size,
+    height: size,
+    color,
+    ...style
+  }
+  return (
+    <svg
+      xmlns='http://www.w3.org/2000/svg'
+      viewBox='0 0 100 100'
+      className={cn(
+        'anticon fill-current inline-block h-[1em] w-[1em] overflow-hidden outline-none',
+        className
+      )}
+      style={svgStyle}
+      aria-label={icon}>
+      <title>{icon}</title>
+      <use xlinkHref={symbolId} fill='currentColor' />
+    </svg>
+  )
 }

+ 10 - 18
src/components/logo/index.tsx

@@ -1,20 +1,12 @@
-import { NavLink } from 'react-router';
-
-import { useTheme } from '@/theme/hooks';
-
-import { Iconify } from '../icon';
-
-interface Props {
-	size?: number | string;
-}
-function Logo({ size = 50 }: Props) {
-	const { themeTokens } = useTheme();
-
-	return (
-		<NavLink to="/">
-			<Iconify icon="solar:code-square-bold" color={themeTokens.color.palette.primary.default} size={size} />
-		</NavLink>
-	);
+import { NavLink } from 'react-router'
+import logo from '@/assets/images/logo.png'
+
+function Logo() {
+  return (
+    <NavLink to='/'>
+      <img src={logo} alt='' className='w-[42px] h-[42px]' />
+    </NavLink>
+  )
 }
 
-export default Logo;
+export default Logo

+ 1 - 1
src/components/verifition/Verify.tsx

@@ -128,7 +128,7 @@ const Verify = memo(
               style={{ maxWidth: parseInt(imgSize.width) + 30 + 'px' }}>
               {mode === 'pop' && (
                 <div className='verifybox-top'>
-                  请完成安全验证
+                  安全验证
                   <span className='verifybox-close' onClick={closeBox}>
                     <CloseOutlined className='icon-close' />
                   </span>

+ 32 - 24
src/components/verifition/Verify/VerifySlide/index.tsx

@@ -5,7 +5,8 @@ import { reqCheck, reqGet } from '../../api/index'
 import { aesEncrypt } from '../../utils/ase'
 import { resetSize } from '../../utils/util'
 import { useVerifyContext } from '@/pages/sys/login/providers/VerifyProvider'
-import { CheckOutlined, CloseOutlined, ReloadOutlined, RightOutlined } from '@ant-design/icons'
+import { ReloadOutlined } from '@ant-design/icons'
+import { SvgIcon } from '@/components/icon'
 import { VerifySlideWrapper } from './style'
 
 export interface VerifySlideRef {
@@ -33,7 +34,7 @@ const VerifySlide = memo(
         captchaType,
         type = '1',
         vSpace = 5,
-        explain = '向右滑块完成验证',
+        explain = '拖动箭头填充拼图',
         barSize = { width: '310px', height: '40px' },
         blockSize = { width: '50px', height: '50px' },
         setClickShowFn,
@@ -79,11 +80,11 @@ const VerifySlide = memo(
       const [moveBlockLeft, setMoveBlockLeft] = useState<string | undefined>(undefined)
       const [leftBarWidth, setLeftBarWidth] = useState<string | number | undefined>(undefined)
       // 移动中样式
-      const [moveBlockBackgroundColor, setMoveBlockBackgroundColor] = useState<string | undefined>(
-        undefined
-      )
+      // const [moveBlockBackgroundColor, setMoveBlockBackgroundColor] = useState<string | undefined>(
+      //   undefined
+      // )
       const [leftBarBorderColor, setLeftBarBorderColor] = useState('#ddd')
-      const [iconColor, setIconColor] = useState<string | undefined>(undefined)
+      // const [iconColor, setIconColor] = useState<string | undefined>(undefined)
       const [iconClass, setIconClass] = useState('icon-right')
       const [status, setStatus] = useState(false) // 鼠标状态
       const [isEnd, setIsEnd] = useState(false)
@@ -103,8 +104,8 @@ const VerifySlide = memo(
         setTransitionWidth('width .3s')
 
         setLeftBarBorderColor('#ddd')
-        setMoveBlockBackgroundColor('#fff')
-        setIconColor('#000')
+        // setMoveBlockBackgroundColor('#fff')
+        // setIconColor('#000')
         setIconClass('icon-right')
         setIsEnd(false)
 
@@ -147,9 +148,9 @@ const VerifySlide = memo(
         setStartMoveTime(+new Date()) // 开始滑动的时间
         if (isEnd == false) {
           setText('')
-          setMoveBlockBackgroundColor('#337ab7')
-          setLeftBarBorderColor('#337AB7')
-          setIconColor('#fff')
+          // setMoveBlockBackgroundColor('#1a1a1a')
+          setLeftBarBorderColor('#1a1a1a')
+          // setIconColor('#fff')
           e.stopPropagation()
           setStatus(true)
         }
@@ -237,9 +238,9 @@ const VerifySlide = memo(
           reqCheck(data).then((response) => {
             const res = response.data
             if (res?.repCode == '0000') {
-              setMoveBlockBackgroundColor('#5cb85c')
+              // setMoveBlockBackgroundColor('#5cb85c')
               setLeftBarBorderColor('#5cb85c')
-              setIconColor('#fff')
+              // setIconColor('#fff')
               setIconClass('icon-check')
               setShowRefresh(false)
               setIsEnd(true)
@@ -271,9 +272,9 @@ const VerifySlide = memo(
                 success({ captchaVerification })
               }, 1000)
             } else {
-              setMoveBlockBackgroundColor('#d9534f')
+              // setMoveBlockBackgroundColor('#d9534f')
               setLeftBarBorderColor('#d9534f')
-              setIconColor('#fff')
+              // setIconColor('#fff')
               setIconClass('icon-close')
               setPassFlag(false)
               setTimeout(function () {
@@ -340,7 +341,7 @@ const VerifySlide = memo(
         <VerifySlideWrapper ref={vmRef} style={{ position: 'relative' }}>
           {props.type === '2' && (
             <div
-              className='verify-img-out'
+              className='verify-img-out rounded-[14px] overflow-hidden'
               style={{
                 height: parseInt(setSize.imgHeight) + props.vSpace + 'px'
               }}>
@@ -382,15 +383,22 @@ const VerifySlide = memo(
             style={{
               width: setSize.imgWidth,
               height: props.barSize.height,
-              lineHeight: props.barSize.height
+              lineHeight: props.barSize.height,
+              borderRadius: barSize.height
             }}>
             <span className='verify-msg'>{text}</span>
             <div
               className='verify-left-bar'
               style={{
-                width: leftBarWidth !== undefined ? leftBarWidth : props.barSize.height,
+                width:
+                  leftBarWidth !== undefined
+                    ? parseFloat((leftBarWidth as string).slice(0, -2)) +
+                      parseFloat(barSize.height.slice(0, -2)) +
+                      'px'
+                    : props.barSize.height,
                 height: props.barSize.height,
                 borderColor: leftBarBorderColor,
+                borderRadius: barSize.height,
                 transition: transitionWidth
               }}>
               <span className='verify-msg'>{finishText}</span>
@@ -399,20 +407,20 @@ const VerifySlide = memo(
                 style={{
                   width: props.barSize.height,
                   height: props.barSize.height,
-                  backgroundColor: moveBlockBackgroundColor,
                   left: moveBlockLeft,
-                  transition: transitionLeft
+                  transition: transitionLeft,
+                  borderRadius: barSize.height
                 }}
                 onTouchStart={start}
                 onMouseDown={start}>
                 {iconClass === 'icon-right' && (
-                  <RightOutlined className='verify-icon' style={{ color: iconColor }} />
+                  <SvgIcon icon='yanzhenghuakuai' className='!w-full !h-full' />
                 )}
                 {iconClass === 'icon-close' && (
-                  <CloseOutlined className='verify-icon' style={{ color: iconColor }} />
+                  <SvgIcon icon='yanzhengshibai' className='!w-full !h-full' />
                 )}
                 {iconClass === 'icon-check' && (
-                  <CheckOutlined className='verify-icon' style={{ color: iconColor }} />
+                  <SvgIcon icon='yanzhengchenggong' className='!w-full !h-full' />
                 )}
                 {props.type === '2' && (
                   <div
@@ -420,7 +428,7 @@ const VerifySlide = memo(
                     style={{
                       width: Math.floor((parseInt(setSize.imgWidth) * 47) / 310) + 'px',
                       height: setSize.imgHeight,
-                      top: '-' + (parseInt(setSize.imgHeight) + props.vSpace) + 'px',
+                      top: '-' + (parseInt(setSize.imgHeight) + props.vSpace + 10) + 'px',
                       backgroundSize: setSize.imgWidth + ' ' + setSize.imgHeight
                     }}>
                     <img

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 20 - 30
src/components/verifition/Verify/VerifySlide/style.js


+ 12 - 17
src/components/verifition/style.js

@@ -1,31 +1,30 @@
-import styled from 'styled-components';
+import styled from 'styled-components'
 
 export const VerifyWrapper = styled.div`
   .verifybox {
     position: relative;
     box-sizing: border-box;
-    border-radius: 2px;
+    border-radius: 16px;
     border: 1px solid #e4e7eb;
     background-color: #fff;
-    box-shadow: 0 0 10px rgba(0,0,0,.3);
+    box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
     left: 50%;
-    top:50%;
-    transform: translate(-50%,-50%);
+    top: 50%;
+    transform: translate(-50%, -50%);
   }
 
   .verifybox-top {
     padding: 0 15px;
     height: 50px;
     line-height: 50px;
-    text-align: left;
-    font-size: 16px;
-    color: #45494c;
-    border-bottom: 1px solid #e4e7eb;
+    text-align: center;
+    font-size: 18px;
+    color: #1f2329;
     box-sizing: border-box;
   }
 
   .verifybox-bottom {
-    padding: 15px;
+    padding: 10px;
     box-sizing: border-box;
   }
 
@@ -35,23 +34,19 @@ export const VerifyWrapper = styled.div`
     right: 9px;
     width: 24px;
     height: 24px;
-    line-height: 24px;
     text-align: center;
     cursor: pointer;
-    .icon-close {
-      font-size: 18px;
-    }
   }
 
   &.mask {
     position: fixed;
     top: 0;
-    left:0;
+    left: 0;
     z-index: 1001;
     width: 100%;
     height: 100vh;
-    background: rgba(0,0,0,.3);
+    background: rgba(0, 0, 0, 0.3);
     /* display: none; */
-    transition: all .5s;
+    transition: all 0.5s;
   }
 `

+ 79 - 87
src/layouts/components/account-dropdown.tsx

@@ -1,99 +1,91 @@
-import { Divider, type MenuProps } from 'antd';
-import Dropdown, { type DropdownProps } from 'antd/es/dropdown/dropdown';
-import React from 'react';
-import { useTranslation } from 'react-i18next';
-import { NavLink } from 'react-router';
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+import { NavLink } from 'react-router'
+import { useUserActions, useUserInfo } from '@/store/userStore'
+import { useRouter } from '@/router/hooks'
+// import { useLoginStateContext } from '@/pages/sys/login/providers/LoginStateProvider'
+import { Divider, type MenuProps } from 'antd'
+import Dropdown, { type DropdownProps } from 'antd/es/dropdown/dropdown'
+import { useTheme } from '@/theme/hooks'
+import { IconButton } from '@/components/icon'
 
-import { IconButton } from '@/components/icon';
-import { useLoginStateContext } from '@/pages/sys/login/providers/LoginStateProvider';
-import { useRouter } from '@/router/hooks';
-import { useUserActions, useUserInfo } from '@/store/userStore';
-import { useTheme } from '@/theme/hooks';
-
-const { VITE_APP_HOMEPAGE: HOMEPAGE } = import.meta.env;
+const nameColorList = ['#71BC78', '#5BB4EB', '#F28CAE', '#ED9848', '#DB93DB']
+const randomIndex = Math.floor(Math.random() * 5)
 
 /**
  * Account Dropdown
  */
 export default function AccountDropdown() {
-	const { replace } = useRouter();
-	const { username, email, avatar } = useUserInfo();
-	const { clearUserInfoAndToken } = useUserActions();
-	const { backToLogin } = useLoginStateContext();
-	const { t } = useTranslation();
-	const logout = () => {
-		try {
-			clearUserInfoAndToken();
-			backToLogin();
-		} catch (error) {
-			console.log(error);
-		} finally {
-			replace('/login');
-		}
-	};
-	const {
-		themeVars: { colors, borderRadius, shadows }
-	} = useTheme();
+  const { replace } = useRouter()
+  const { username, phone } = useUserInfo()
+  const { clearUserInfoAndToken } = useUserActions()
+  // const { backToLogin } = useLoginStateContext()
+  const { t } = useTranslation()
+  const logout = () => {
+    try {
+      clearUserInfoAndToken()
+      // backToLogin();
+    } catch (error) {
+      console.log(error)
+    } finally {
+      replace('/login')
+    }
+  }
+  const {
+    themeVars: { colors, borderRadius, shadows }
+  } = useTheme()
 
-	const contentStyle: React.CSSProperties = {
-		backgroundColor: colors.background.default,
-		borderRadius: borderRadius.lg,
-		boxShadow: shadows.dropdown
-	};
+  const contentStyle: React.CSSProperties = {
+    backgroundColor: colors.background.default,
+    borderRadius: borderRadius.lg,
+    boxShadow: shadows.dropdown
+  }
 
-	const menuStyle: React.CSSProperties = {
-		boxShadow: 'none'
-	};
+  const menuStyle: React.CSSProperties = {
+    boxShadow: 'none'
+  }
 
-	const dropdownRender: DropdownProps['dropdownRender'] = (menu) => (
-		<div style={contentStyle}>
-			<div className="flex flex-col items-start p-4">
-				<div>{username}</div>
-				<div className="text-gray">{email}</div>
-			</div>
-			<Divider style={{ margin: 0 }} />
-			{React.cloneElement(menu as React.ReactElement, { style: menuStyle })}
-		</div>
-	);
+  const dropdownRender: DropdownProps['dropdownRender'] = (menu) => (
+    <div style={contentStyle}>
+      <div className='flex flex-col items-start p-4'>
+        <div>{username}</div>
+        <div className='text-gray'>{phone}</div>
+      </div>
+      <Divider style={{ margin: 0 }} />
+      {React.cloneElement(menu as React.ReactElement, { style: menuStyle })}
+    </div>
+  )
 
-	const items: MenuProps['items'] = [
-		{
-			label: (
-				<NavLink to="https://docs-admin.slashspaces.com/" target="_blank">
-					{t('sys.docs')}
-				</NavLink>
-			),
-			key: '0'
-		},
-		{
-			label: <NavLink to={HOMEPAGE}>{t('sys.menu.dashboard')}</NavLink>,
-			key: '1'
-		},
-		{
-			label: <NavLink to="/management/user/profile">{t('sys.menu.user.profile')}</NavLink>,
-			key: '2'
-		},
-		{
-			label: <NavLink to="/management/user/account">{t('sys.menu.user.account')}</NavLink>,
-			key: '3'
-		},
-		{ type: 'divider' },
-		{
-			label: (
-				<button className="font-bold text-warning" type="button">
-					{t('sys.login.logout')}
-				</button>
-			),
-			key: '4',
-			onClick: logout
-		}
-	];
+  const items: MenuProps['items'] = [
+    {
+      label: <NavLink to='/management/user/profile'>{t('sys.menu.user.profile')}</NavLink>,
+      key: '0'
+    },
+    {
+      label: <NavLink to='/management/user/account'>{t('sys.menu.user.account')}</NavLink>,
+      key: '1'
+    },
+    { type: 'divider' },
+    {
+      label: (
+        <button className='font-bold text-warning' type='button'>
+          {t('sys.login.logout')}
+        </button>
+      ),
+      key: '2',
+      onClick: logout
+    }
+  ]
 
-	return (
-		<Dropdown menu={{ items }} trigger={['click']} dropdownRender={dropdownRender}>
-			<IconButton className="h-10 w-10 transform-none px-0 hover:scale-105">
-				<img className="h-8 w-8 rounded-full" src={avatar} alt="" />
-			</IconButton>
-		</Dropdown>
-	);
+  return (
+    <Dropdown menu={{ items }} trigger={['click']} dropdownRender={dropdownRender}>
+      <IconButton className='h-10 w-10 transform-none px-0 hover:scale-105'>
+        <div
+          className='inline-block w-[32px] h-[32px] rounded-full text-[#fff] text-center leading-[32px] text-[16px]'
+          style={{ backgroundColor: nameColorList[randomIndex] }}>
+          <span>{username?.slice(0, 1)?.toUpperCase() || '-'}</span>
+        </div>
+      </IconButton>
+    </Dropdown>
+  )
 }

+ 454 - 447
src/layouts/components/setting-button.tsx

@@ -1,475 +1,482 @@
-import { CloseOutlined, LeftOutlined, QuestionCircleOutlined, RightOutlined } from '@ant-design/icons';
-import { Button, Card, Drawer, Slider, Switch, Tooltip } from 'antd';
-import { m } from 'framer-motion';
-import { type CSSProperties, useState } from 'react';
-import { MdCircle } from 'react-icons/md';
-import screenfull from 'screenfull';
-
-import CyanBlur from '@/assets/images/background/cyan-blur.png';
-import RedBlur from '@/assets/images/background/red-blur.png';
-import { varHover } from '@/components/animate/variants/action';
-import { IconButton, SvgIcon } from '@/components/icon';
-import { useSettingActions, useSettings } from '@/store/settingStore';
-import { presetsColors } from '@/theme/tokens/color';
-
-import { themeVars } from '@/theme/theme.css';
-import { FontFamilyPreset } from '@/theme/tokens/typography';
-import { cn } from '@/utils';
-import { type ThemeColorPresets, ThemeLayout, ThemeMode } from '#/enum';
+import { type CSSProperties, useState } from 'react'
+import { MdCircle } from 'react-icons/md'
+import { useSettingActions, useSettings } from '@/store/settingStore'
+import { type ThemeColorPresets, ThemeLayout, ThemeMode } from '#/enum'
+import CyanBlur from '@/assets/images/background/cyan-blur.png'
+import RedBlur from '@/assets/images/background/red-blur.png'
+import { cn } from '@/utils'
+import { m } from 'framer-motion'
+import screenfull from 'screenfull'
+import { Button, Card, Drawer, Slider, Switch, Tooltip } from 'antd'
+import {
+  CloseOutlined,
+  LeftOutlined,
+  QuestionCircleOutlined,
+  RightOutlined
+} from '@ant-design/icons'
+import { themeVars } from '@/theme/theme.css'
+import { presetsColors } from '@/theme/tokens/color'
+import { FontFamilyPreset } from '@/theme/tokens/typography'
+import { varHover } from '@/components/animate/variants/action'
+import { IconButton, SvgIcon } from '@/components/icon'
 
 /**
  * App Setting
  */
 export default function SettingButton() {
-	const [drawerOpen, setDrawerOpen] = useState(false);
+  const [drawerOpen, setDrawerOpen] = useState(false)
 
-	const settings = useSettings();
-	const {
-		themeMode,
-		themeColorPresets,
-		themeLayout,
-		themeStretch,
-		breadCrumb,
-		multiTab,
-		darkSidebar,
-		fontSize,
-		fontFamily
-	} = settings;
-	const { setSettings } = useSettingActions();
+  const settings = useSettings()
+  const {
+    themeMode,
+    themeColorPresets,
+    themeLayout,
+    themeStretch,
+    breadCrumb,
+    multiTab,
+    darkSidebar,
+    fontSize,
+    fontFamily
+  } = settings
+  const { setSettings } = useSettingActions()
 
-	const setThemeMode = (themeMode: ThemeMode) => {
-		setSettings({
-			...settings,
-			themeMode
-		});
-	};
+  const setThemeMode = (themeMode: ThemeMode) => {
+    setSettings({
+      ...settings,
+      themeMode
+    })
+  }
 
-	const setThemeColorPresets = (themeColorPresets: ThemeColorPresets) => {
-		setSettings({
-			...settings,
-			themeColorPresets
-		});
-	};
+  const setThemeColorPresets = (themeColorPresets: ThemeColorPresets) => {
+    setSettings({
+      ...settings,
+      themeColorPresets
+    })
+  }
 
-	const setThemeLayout = (themeLayout: ThemeLayout) => {
-		setSettings({
-			...settings,
-			themeLayout
-		});
-	};
+  const setThemeLayout = (themeLayout: ThemeLayout) => {
+    setSettings({
+      ...settings,
+      themeLayout
+    })
+  }
 
-	const setThemeStretch = (themeStretch: boolean) => {
-		setSettings({
-			...settings,
-			themeStretch
-		});
-	};
+  const setThemeStretch = (themeStretch: boolean) => {
+    setSettings({
+      ...settings,
+      themeStretch
+    })
+  }
 
-	const setBreadCrumn = (checked: boolean) => {
-		setSettings({
-			...settings,
-			breadCrumb: checked
-		});
-	};
+  const setBreadCrumn = (checked: boolean) => {
+    setSettings({
+      ...settings,
+      breadCrumb: checked
+    })
+  }
 
-	const setMultiTab = (checked: boolean) => {
-		setSettings({
-			...settings,
-			multiTab: checked
-		});
-	};
+  const setMultiTab = (checked: boolean) => {
+    setSettings({
+      ...settings,
+      multiTab: checked
+    })
+  }
 
-	const setDarkSidebar = (checked: boolean) => {
-		setSettings({
-			...settings,
-			darkSidebar: checked
-		});
-	};
+  const setDarkSidebar = (checked: boolean) => {
+    setSettings({
+      ...settings,
+      darkSidebar: checked
+    })
+  }
 
-	const setFontFamily = (fontFamily: string) => {
-		setSettings({
-			...settings,
-			fontFamily
-		});
-	};
+  const setFontFamily = (fontFamily: string) => {
+    setSettings({
+      ...settings,
+      fontFamily
+    })
+  }
 
-	const setFontSize = (fontSize: number) => {
-		setSettings({
-			...settings,
-			fontSize
-		});
-	};
+  const setFontSize = (fontSize: number) => {
+    setSettings({
+      ...settings,
+      fontSize
+    })
+  }
 
-	const style: CSSProperties = {
-		backdropFilter: 'blur(20px)',
-		backgroundImage: `url("${CyanBlur}"), url("${RedBlur}")`,
-		backgroundRepeat: 'no-repeat, no-repeat',
-		backgroundColor: `rgba(${themeVars.colors.background.paperChannel}, 0.9)`,
-		backgroundPosition: 'right top, left bottom',
-		backgroundSize: '50, 50%'
-	};
+  const style: CSSProperties = {
+    backdropFilter: 'blur(20px)',
+    backgroundImage: `url("${CyanBlur}"), url("${RedBlur}")`,
+    backgroundRepeat: 'no-repeat, no-repeat',
+    backgroundColor: `rgba(${themeVars.colors.background.paperChannel}, 0.9)`,
+    backgroundPosition: 'right top, left bottom',
+    backgroundSize: '50, 50%'
+  }
 
-	const [isFullscreen, setIsFullscreen] = useState(screenfull.isFullscreen);
-	const toggleFullScreen = () => {
-		if (screenfull.isEnabled) {
-			screenfull.toggle();
-			setIsFullscreen(!isFullscreen);
-		}
-	};
+  const [isFullscreen, setIsFullscreen] = useState(screenfull.isFullscreen)
+  const toggleFullScreen = () => {
+    if (screenfull.isEnabled) {
+      screenfull.toggle()
+      setIsFullscreen(!isFullscreen)
+    }
+  }
 
-	const layoutBackground = (layout: ThemeLayout) =>
-		themeLayout === layout
-			? `linear-gradient(135deg, ${themeVars.colors.background.neutral} 0%, ${themeVars.colors.palette.primary.default} 100%)`
-			: themeVars.colors.palette.gray[500];
+  const layoutBackground = (layout: ThemeLayout) =>
+    themeLayout === layout
+      ? `linear-gradient(135deg, ${themeVars.colors.background.neutral} 0%, ${themeVars.colors.palette.primary.default} 100%)`
+      : themeVars.colors.palette.gray[500]
 
-	return (
-		<>
-			<div className="flex items-center justify-center overflow-hidden">
-				<m.div
-					animate={{
-						rotate: [0, drawerOpen ? 0 : 360]
-					}}
-					transition={{
-						duration: 12,
-						ease: 'linear',
-						repeat: Number.POSITIVE_INFINITY
-					}}
-					whileTap="tap"
-					whileHover="hover"
-					variants={varHover(1.05)}
-					onClick={() => setDrawerOpen(true)}
-				>
-					<IconButton className="h-10 w-10">
-						<SvgIcon icon="ic-setting" size="24" />
-					</IconButton>
-				</m.div>
-			</div>
-			<Drawer
-				placement="right"
-				title="Settings"
-				onClose={() => setDrawerOpen(false)}
-				open={drawerOpen}
-				closable={false}
-				styles={{
-					body: { padding: 0 },
-					mask: { backgroundColor: 'transparent' }
-				}}
-				style={style}
-				extra={
-					<IconButton onClick={() => setDrawerOpen(false)} className="h-9 w-9 hover:scale-105">
-						<CloseOutlined className="text-gray-400" />
-					</IconButton>
-				}
-				footer={
-					<Button type="dashed" block size="large" onClick={toggleFullScreen}>
-						<div className="flex items-center justify-center">
-							{isFullscreen ? (
-								<>
-									<SvgIcon
-										icon="ic-settings-exit-fullscreen"
-										color={themeVars.colors.palette.primary.default}
-										className="!m-0"
-									/>
-									<span className="ml-2">Exit FullScreen</span>
-								</>
-							) : (
-								<>
-									<SvgIcon icon="ic-settings-fullscreen" className="!m-0" />
-									<span className="ml-2 text-gray">FullScreen</span>
-								</>
-							)}
-						</div>
-					</Button>
-				}
-			>
-				<div className="flex flex-col gap-6 p-6">
-					{/* theme mode */}
-					<div>
-						<div className="mb-3 text-base font-semibold text-text-secondary">Mode</div>
-						<div className="flex flex-row gap-4">
-							<Card
-								onClick={() => setThemeMode(ThemeMode.Light)}
-								className="flex h-20 w-full cursor-pointer items-center justify-center"
-							>
-								<SvgIcon
-									icon="ic-settings-mode-sun"
-									size="24"
-									color={themeMode === ThemeMode.Light ? themeVars.colors.palette.primary.default : ''}
-								/>
-							</Card>
-							<Card
-								onClick={() => setThemeMode(ThemeMode.Dark)}
-								className="flex h-20 w-full cursor-pointer items-center justify-center"
-							>
-								<SvgIcon
-									icon="ic-settings-mode-moon"
-									size="24"
-									color={themeMode === ThemeMode.Dark ? themeVars.colors.palette.primary.default : ''}
-								/>
-							</Card>
-						</div>
-					</div>
+  return (
+    <>
+      <div className='flex items-center justify-center overflow-hidden'>
+        <m.div
+          animate={{
+            rotate: [0, drawerOpen ? 0 : 360]
+          }}
+          transition={{
+            duration: 12,
+            ease: 'linear',
+            repeat: Number.POSITIVE_INFINITY
+          }}
+          whileTap='tap'
+          whileHover='hover'
+          variants={varHover(1.05)}
+          onClick={() => setDrawerOpen(true)}>
+          <IconButton className='h-10 w-10'>
+            <SvgIcon icon='ic-setting' size='24' />
+          </IconButton>
+        </m.div>
+      </div>
+      <Drawer
+        placement='right'
+        title='设置'
+        onClose={() => setDrawerOpen(false)}
+        open={drawerOpen}
+        closable={false}
+        styles={{
+          body: { padding: 0 },
+          mask: { backgroundColor: 'transparent' }
+        }}
+        style={style}
+        extra={
+          <IconButton onClick={() => setDrawerOpen(false)} className='h-9 w-9 hover:scale-105'>
+            <CloseOutlined className='text-gray-400' />
+          </IconButton>
+        }
+        footer={
+          <Button type='dashed' block size='large' onClick={toggleFullScreen}>
+            <div className='flex items-center justify-center'>
+              {isFullscreen ? (
+                <>
+                  <SvgIcon
+                    icon='ic-settings-exit-fullscreen'
+                    color={themeVars.colors.palette.primary.default}
+                    className='!m-0'
+                  />
+                  <span className='ml-2'>退出全屏</span>
+                </>
+              ) : (
+                <>
+                  <SvgIcon icon='ic-settings-fullscreen' className='!m-0' />
+                  <span className='ml-2 text-gray'>全屏</span>
+                </>
+              )}
+            </div>
+          </Button>
+        }>
+        <div className='flex flex-col gap-6 p-6'>
+          {/* theme mode */}
+          <div>
+            <div className='mb-3 text-base font-semibold text-text-secondary'>模式</div>
+            <div className='flex flex-row gap-4'>
+              <Card
+                onClick={() => setThemeMode(ThemeMode.Light)}
+                className='flex h-20 w-full cursor-pointer items-center justify-center'>
+                <SvgIcon
+                  icon='ic-settings-mode-sun'
+                  size='24'
+                  color={
+                    themeMode === ThemeMode.Light ? themeVars.colors.palette.primary.default : ''
+                  }
+                />
+              </Card>
+              <Card
+                onClick={() => setThemeMode(ThemeMode.Dark)}
+                className='flex h-20 w-full cursor-pointer items-center justify-center'>
+                <SvgIcon
+                  icon='ic-settings-mode-moon'
+                  size='24'
+                  color={
+                    themeMode === ThemeMode.Dark ? themeVars.colors.palette.primary.default : ''
+                  }
+                />
+              </Card>
+            </div>
+          </div>
 
-					{/* theme layout */}
-					<div>
-						<div className="mb-3 text-base font-semibold text-text-secondary">Layout</div>
-						<div className="grid grid-cols-3 gap-4">
-							<Card
-								onClick={() => setThemeLayout(ThemeLayout.Vertical)}
-								className="h-16 cursor-pointer"
-								style={{ flexGrow: 1, flexShrink: 0 }}
-								styles={{
-									body: {
-										padding: 0,
-										display: 'flex',
-										justifyContent: 'center',
-										alignItems: 'center',
-										height: '100%'
-									}
-								}}
-							>
-								<div className="flex h-full w-7 flex-shrink-0 flex-col gap-1 p-1">
-									<div
-										className="h-2 w-2 flex-shrink-0 rounded"
-										style={{
-											background: layoutBackground(ThemeLayout.Vertical)
-										}}
-									/>
-									<div
-										className="h-1 w-full flex-shrink-0 rounded opacity-50"
-										style={{
-											background: layoutBackground(ThemeLayout.Vertical)
-										}}
-									/>
-									<div
-										className="h-1 max-w-[12px] flex-shrink-0 rounded opacity-20"
-										style={{
-											background: layoutBackground(ThemeLayout.Vertical)
-										}}
-									/>
-								</div>
-								<div className="h-full w-full flex-1 flex-grow p-1">
-									<div
-										className="h-full w-full rounded opacity-20"
-										style={{
-											background: layoutBackground(ThemeLayout.Vertical)
-										}}
-									/>
-								</div>
-							</Card>
-							<Card
-								onClick={() => setThemeLayout(ThemeLayout.Horizontal)}
-								className="h-16 cursor-pointer"
-								style={{ flexGrow: 1, flexShrink: 0 }}
-								styles={{
-									body: {
-										padding: 0,
-										display: 'flex',
-										flexDirection: 'column',
-										justifyContent: 'center',
-										alignItems: 'center',
-										height: '100%'
-									}
-								}}
-							>
-								<div className="flex h-4 w-full items-center gap-1  p-1">
-									<div
-										className="h-2 w-2 flex-shrink-0 rounded"
-										style={{
-											background: layoutBackground(ThemeLayout.Horizontal)
-										}}
-									/>
-									<div
-										className="h-1 w-4 flex-shrink-0 rounded opacity-50"
-										style={{
-											background: layoutBackground(ThemeLayout.Horizontal)
-										}}
-									/>
-									<div
-										className="h-1 w-3 flex-shrink-0 rounded opacity-20"
-										style={{
-											background: layoutBackground(ThemeLayout.Horizontal)
-										}}
-									/>
-								</div>
-								<div className="h-full w-full flex-1 flex-grow p-1">
-									<div
-										className="h-full w-full rounded opacity-20"
-										style={{
-											background: layoutBackground(ThemeLayout.Horizontal)
-										}}
-									/>
-								</div>
-							</Card>
-							<Card
-								onClick={() => setThemeLayout(ThemeLayout.Mini)}
-								className="h-16 cursor-pointer"
-								style={{ flexGrow: 1, flexShrink: 0 }}
-								styles={{
-									body: {
-										padding: 0,
-										display: 'flex',
-										justifyContent: 'center',
-										alignItems: 'center',
-										height: '100%'
-									}
-								}}
-							>
-								<div className="flex h-full flex-shrink-0 flex-col gap-1 p-1">
-									<div
-										className="h-2 w-2 flex-shrink-0 rounded"
-										style={{ background: layoutBackground(ThemeLayout.Mini) }}
-									/>
-									<div
-										className="h-1 w-full flex-shrink-0 rounded opacity-50"
-										style={{ background: layoutBackground(ThemeLayout.Mini) }}
-									/>
-									<div
-										className="h-1 max-w-[12px] flex-shrink-0 rounded opacity-20"
-										style={{ background: layoutBackground(ThemeLayout.Mini) }}
-									/>
-								</div>
-								<div className="h-full w-full flex-1 flex-grow p-1">
-									<div
-										className="h-full w-full rounded opacity-20"
-										style={{ background: layoutBackground(ThemeLayout.Mini) }}
-									/>
-								</div>
-							</Card>
-						</div>
-					</div>
+          {/* theme layout */}
+          <div>
+            <div className='mb-3 text-base font-semibold text-text-secondary'>布局</div>
+            <div className='grid grid-cols-3 gap-4'>
+              <Card
+                onClick={() => setThemeLayout(ThemeLayout.Vertical)}
+                className='h-16 cursor-pointer'
+                style={{ flexGrow: 1, flexShrink: 0 }}
+                styles={{
+                  body: {
+                    padding: 0,
+                    display: 'flex',
+                    justifyContent: 'center',
+                    alignItems: 'center',
+                    height: '100%'
+                  }
+                }}>
+                <div className='flex h-full w-7 flex-shrink-0 flex-col gap-1 p-1'>
+                  <div
+                    className='h-2 w-2 flex-shrink-0 rounded'
+                    style={{
+                      background: layoutBackground(ThemeLayout.Vertical)
+                    }}
+                  />
+                  <div
+                    className='h-1 w-full flex-shrink-0 rounded opacity-50'
+                    style={{
+                      background: layoutBackground(ThemeLayout.Vertical)
+                    }}
+                  />
+                  <div
+                    className='h-1 max-w-[12px] flex-shrink-0 rounded opacity-20'
+                    style={{
+                      background: layoutBackground(ThemeLayout.Vertical)
+                    }}
+                  />
+                </div>
+                <div className='h-full w-full flex-1 flex-grow p-1'>
+                  <div
+                    className='h-full w-full rounded opacity-20'
+                    style={{
+                      background: layoutBackground(ThemeLayout.Vertical)
+                    }}
+                  />
+                </div>
+              </Card>
+              <Card
+                onClick={() => setThemeLayout(ThemeLayout.Horizontal)}
+                className='h-16 cursor-pointer'
+                style={{ flexGrow: 1, flexShrink: 0 }}
+                styles={{
+                  body: {
+                    padding: 0,
+                    display: 'flex',
+                    flexDirection: 'column',
+                    justifyContent: 'center',
+                    alignItems: 'center',
+                    height: '100%'
+                  }
+                }}>
+                <div className='flex h-4 w-full items-center gap-1  p-1'>
+                  <div
+                    className='h-2 w-2 flex-shrink-0 rounded'
+                    style={{
+                      background: layoutBackground(ThemeLayout.Horizontal)
+                    }}
+                  />
+                  <div
+                    className='h-1 w-4 flex-shrink-0 rounded opacity-50'
+                    style={{
+                      background: layoutBackground(ThemeLayout.Horizontal)
+                    }}
+                  />
+                  <div
+                    className='h-1 w-3 flex-shrink-0 rounded opacity-20'
+                    style={{
+                      background: layoutBackground(ThemeLayout.Horizontal)
+                    }}
+                  />
+                </div>
+                <div className='h-full w-full flex-1 flex-grow p-1'>
+                  <div
+                    className='h-full w-full rounded opacity-20'
+                    style={{
+                      background: layoutBackground(ThemeLayout.Horizontal)
+                    }}
+                  />
+                </div>
+              </Card>
+              <Card
+                onClick={() => setThemeLayout(ThemeLayout.Mini)}
+                className='h-16 cursor-pointer'
+                style={{ flexGrow: 1, flexShrink: 0 }}
+                styles={{
+                  body: {
+                    padding: 0,
+                    display: 'flex',
+                    justifyContent: 'center',
+                    alignItems: 'center',
+                    height: '100%'
+                  }
+                }}>
+                <div className='flex h-full flex-shrink-0 flex-col gap-1 p-1'>
+                  <div
+                    className='h-2 w-2 flex-shrink-0 rounded'
+                    style={{ background: layoutBackground(ThemeLayout.Mini) }}
+                  />
+                  <div
+                    className='h-1 w-full flex-shrink-0 rounded opacity-50'
+                    style={{ background: layoutBackground(ThemeLayout.Mini) }}
+                  />
+                  <div
+                    className='h-1 max-w-[12px] flex-shrink-0 rounded opacity-20'
+                    style={{ background: layoutBackground(ThemeLayout.Mini) }}
+                  />
+                </div>
+                <div className='h-full w-full flex-1 flex-grow p-1'>
+                  <div
+                    className='h-full w-full rounded opacity-20'
+                    style={{ background: layoutBackground(ThemeLayout.Mini) }}
+                  />
+                </div>
+              </Card>
+            </div>
+          </div>
 
-					{/* theme stretch */}
-					<div>
-						<div className="mb-3 text-base font-semibold text-text-secondary">
-							<span className="mr-2">Stretch</span>
-							<Tooltip title="Only available at large resolutions > 1600px (xl)">
-								<QuestionCircleOutlined />
-							</Tooltip>
-						</div>
+          {/* theme stretch */}
+          <div>
+            <div className='mb-3 text-base font-semibold text-text-secondary'>
+              <span className='mr-2'>伸缩</span>
+              <Tooltip title='Only available at large resolutions > 1600px (xl)'>
+                <QuestionCircleOutlined />
+              </Tooltip>
+            </div>
 
-						<Card
-							onClick={() => setThemeStretch(!themeStretch)}
-							className="flex h-20 w-full cursor-pointer items-center justify-center"
-							styles={{
-								body: {
-									width: '50%',
-									padding: 0,
-									display: 'flex',
-									justifyContent: 'center',
-									alignItems: 'center'
-								}
-							}}
-						>
-							{themeStretch ? (
-								<div
-									className="flex w-full items-center justify-between"
-									style={{
-										color: themeVars.colors.palette.primary.default,
-										transition: 'width 300ms 0ms'
-									}}
-								>
-									<LeftOutlined />
-									<div className="flex flex-grow border-b border-dashed border-border" />
-									<RightOutlined />
-								</div>
-							) : (
-								<div
-									className="flex w-1/2 items-center justify-between"
-									style={{
-										transition: 'width 300ms 0ms'
-									}}
-								>
-									<RightOutlined />
-									<div className="flex-grow border-b border-dashed border-border" />
-									<LeftOutlined />
-								</div>
-							)}
-						</Card>
-					</div>
+            <Card
+              onClick={() => setThemeStretch(!themeStretch)}
+              className='flex h-20 w-full cursor-pointer items-center justify-center'
+              styles={{
+                body: {
+                  width: '50%',
+                  padding: 0,
+                  display: 'flex',
+                  justifyContent: 'center',
+                  alignItems: 'center'
+                }
+              }}>
+              {themeStretch ? (
+                <div
+                  className='flex w-full items-center justify-between'
+                  style={{
+                    color: themeVars.colors.palette.primary.default,
+                    transition: 'width 300ms 0ms'
+                  }}>
+                  <LeftOutlined />
+                  <div className='flex flex-grow border-b border-dashed border-border' />
+                  <RightOutlined />
+                </div>
+              ) : (
+                <div
+                  className='flex w-1/2 items-center justify-between'
+                  style={{
+                    transition: 'width 300ms 0ms'
+                  }}>
+                  <RightOutlined />
+                  <div className='flex-grow border-b border-dashed border-border' />
+                  <LeftOutlined />
+                </div>
+              )}
+            </Card>
+          </div>
 
-					{/* theme presets */}
-					<div>
-						<div className="mb-3 text-base font-semibold text-text-secondary">Presets</div>
-						<div className="grid grid-cols-3 gap-x-4 gap-y-3">
-							{Object.entries(presetsColors).map(([preset, color]) => (
-								<Card
-									key={preset}
-									className="flex h-12 w-full cursor-pointer items-center justify-center"
-									style={{
-										backgroundColor: themeColorPresets === preset ? `${color}14` : ''
-									}}
-									onClick={() => setThemeColorPresets(preset as ThemeColorPresets)}
-								>
-									<div style={{ color: color.default }}>
-										<MdCircle
-											style={{
-												fontSize: themeColorPresets === preset ? 24 : 12
-											}}
-										/>
-									</div>
-								</Card>
-							))}
-						</div>
-					</div>
+          {/* theme presets */}
+          <div>
+            <div className='mb-3 text-base font-semibold text-text-secondary'>预设</div>
+            <div className='grid grid-cols-3 gap-x-4 gap-y-3'>
+              {Object.entries(presetsColors).map(([preset, color]) => (
+                <Card
+                  key={preset}
+                  className='flex h-12 w-full cursor-pointer items-center justify-center'
+                  style={{
+                    backgroundColor: themeColorPresets === preset ? `${color}14` : ''
+                  }}
+                  onClick={() => setThemeColorPresets(preset as ThemeColorPresets)}>
+                  <div style={{ color: color.default }}>
+                    <MdCircle
+                      style={{
+                        fontSize: themeColorPresets === preset ? 24 : 12
+                      }}
+                    />
+                  </div>
+                </Card>
+              ))}
+            </div>
+          </div>
 
-					{/* font */}
-					<div>
-						<div className="mb-3 text-base font-semibold text-text-secondary">Font </div>
+          {/* font */}
+          <div>
+            <div className='mb-3 text-base font-semibold text-text-secondary'>文字</div>
 
-						<div className="my-3 text-sm font-semibold text-text-disabled">Family</div>
-						<div className="flex flex-row gap-3">
-							{Object.entries(FontFamilyPreset).map(([font, family]) => (
-								<Card
-									key={font}
-									className="flex h-20 w-full cursor-pointer items-center justify-center"
-									onClick={() => setFontFamily(family)}
-								>
-									<div
-										className={cn(
-											fontFamily === family ? 'text-primary font-medium' : 'text-text-disabled',
-											'text-center text-lg'
-										)}
-									>
-										<span>A</span>
-										<span className="opacity-50 ml-0.5">a</span>
-									</div>
-									<span
-										className={cn(
-											fontFamily === family ? 'text-text-primary font-medium' : 'text-text-disabled',
-											'text-sm'
-										)}
-									>
-										{family.replace('Variable', '')}
-									</span>
-								</Card>
-							))}
-						</div>
+            <div className='my-3 text-sm font-semibold text-text-disabled'>字体</div>
+            <div className='flex flex-row gap-3'>
+              {Object.entries(FontFamilyPreset).map(([font, family]) => (
+                <Card
+                  key={font}
+                  className='flex h-20 w-full cursor-pointer items-center justify-center'
+                  onClick={() => setFontFamily(family)}>
+                  <div
+                    className={cn(
+                      fontFamily === family ? 'text-primary font-medium' : 'text-text-disabled',
+                      'text-center text-lg'
+                    )}>
+                    <span>A</span>
+                    <span className='opacity-50 ml-0.5'>a</span>
+                  </div>
+                  <span
+                    className={cn(
+                      fontFamily === family
+                        ? 'text-text-primary font-medium'
+                        : 'text-text-disabled',
+                      'text-sm'
+                    )}>
+                    {family.replace('Variable', '')}
+                  </span>
+                </Card>
+              ))}
+            </div>
 
-						<div className="my-3 text-sm font-semibold text-text-disabled">Size</div>
-						<Slider min={12} max={20} defaultValue={fontSize} onChange={setFontSize} />
-					</div>
+            <div className='my-3 text-sm font-semibold text-text-disabled'>尺寸</div>
+            <Slider min={12} max={20} defaultValue={fontSize} onChange={setFontSize} />
+          </div>
 
-					{/* Page config */}
-					<div>
-						<div className="mb-3 text-base font-semibold text-text-secondary">Page</div>
-						<div className="flex flex-col gap-2">
-							<div className="flex items-center justify-between text-sm text-text-disabled">
-								<div>BreadCrumb</div>
-								<Switch size="small" checked={breadCrumb} onChange={(checked) => setBreadCrumn(checked)} />
-							</div>
-							<div className="flex items-center justify-between text-sm text-text-disabled">
-								<div>Multi Tab</div>
-								<Switch size="small" checked={multiTab} onChange={(checked) => setMultiTab(checked)} />
-							</div>
-							<div className="flex items-center justify-between text-sm text-text-disabled">
-								<div>Dark Sidebar</div>
-								<Switch size="small" checked={darkSidebar} onChange={(checked) => setDarkSidebar(checked)} />
-							</div>
-						</div>
-					</div>
-				</div>
-			</Drawer>
-		</>
-	);
+          {/* Page config */}
+          <div>
+            <div className='mb-3 text-base font-semibold text-text-secondary'>页面</div>
+            <div className='flex flex-col gap-2'>
+              <div className='flex items-center justify-between text-sm text-text-disabled'>
+                <div>面包屑</div>
+                <Switch
+                  size='small'
+                  checked={breadCrumb}
+                  onChange={(checked) => setBreadCrumn(checked)}
+                />
+              </div>
+              <div className='flex items-center justify-between text-sm text-text-disabled'>
+                <div>多标签</div>
+                <Switch
+                  size='small'
+                  checked={multiTab}
+                  onChange={(checked) => setMultiTab(checked)}
+                />
+              </div>
+              <div className='flex items-center justify-between text-sm text-text-disabled'>
+                <div>暗黑 侧边栏</div>
+                <Switch
+                  size='small'
+                  checked={darkSidebar}
+                  onChange={(checked) => setDarkSidebar(checked)}
+                />
+              </div>
+            </div>
+          </div>
+        </div>
+      </Drawer>
+    </>
+  )
 }

+ 9 - 9
src/layouts/dashboard/header.tsx

@@ -1,5 +1,6 @@
-import { type CSSProperties, useState } from 'react'
+import { type CSSProperties, useEffect, useState } from 'react'
 import { useSettings } from '@/store/settingStore'
+import { useUserActions } from '@/store/userStore'
 import { ThemeLayout } from '#/enum'
 import { cn } from '@/utils'
 import { rgbAlpha } from '@/utils/theme'
@@ -10,13 +11,18 @@ import BreadCrumb from '../components/bread-crumb'
 import NoticeButton from '../components/notice'
 import SearchBar from '../components/search-bar'
 import SettingButton from '../components/setting-button'
-import { IconButton, Iconify, SvgIcon } from '@/components/icon'
+import { IconButton, SvgIcon } from '@/components/icon'
 import LocalePicker from '@/components/locale-picker'
 import Logo from '@/components/logo'
 import { HEADER_HEIGHT, NAV_COLLAPSED_WIDTH, NAV_WIDTH } from './config'
 import NavVertical from './nav/nav-vertical'
 
 export default function Header() {
+  const { getUserInfo } = useUserActions()
+  useEffect(() => {
+    getUserInfo()
+  }, [])
+
   const [drawerOpen, setDrawerOpen] = useState(false)
   const { themeLayout, breadCrumb } = useSettings()
 
@@ -54,14 +60,8 @@ export default function Header() {
           </div>
 
           <div className='flex'>
-            {/* <SearchBar /> */}
+            <SearchBar />
             <LocalePicker />
-            <IconButton onClick={() => window.open('https://github.com/d3george/slash-admin')}>
-              <Iconify icon='mdi:github' size={24} />
-            </IconButton>
-            <IconButton onClick={() => window.open('https://discord.gg/fXemAXVNDa')}>
-              <Iconify icon='carbon:logo-discord' size={24} />
-            </IconButton>
             <NoticeButton />
             <SettingButton />
             <AccountDropdown />

+ 36 - 31
src/layouts/dashboard/nav/nav-logo.tsx

@@ -1,36 +1,41 @@
-import Logo from '@/components/logo';
-import { useSettings } from '@/store/settingStore';
-import { cn } from '@/utils';
-import { LeftOutlined, RightOutlined } from '@ant-design/icons';
-import { ThemeLayout } from '#/enum';
-import { HEADER_HEIGHT } from '../config';
+import { useSettings } from '@/store/settingStore'
+import { ThemeLayout } from '#/enum'
+import { cn } from '@/utils'
+import { LeftOutlined, RightOutlined } from '@ant-design/icons'
+import Logo from '@/components/logo'
+import { HEADER_HEIGHT } from '../config'
+
+const { VITE_APP_TITLE: APPTITLE } = import.meta.env
 
 type Props = {
-	collapsed: boolean;
-	onToggle: () => void;
-};
+  collapsed: boolean
+  onToggle: () => void
+}
 export default function NavLogo({ collapsed, onToggle }: Props) {
-	const { themeLayout } = useSettings();
+  const { themeLayout } = useSettings()
 
-	return (
-		<div style={{ height: `${HEADER_HEIGHT}px` }} className="relative flex items-center justify-center py-4">
-			<div className="flex items-center">
-				<Logo />
-				{themeLayout !== ThemeLayout.Mini && <span className="ml-2 text-xl font-bold text-primary">Slash Admin</span>}
-			</div>
-			<div
-				onClick={onToggle}
-				onKeyDown={onToggle}
-				className={cn(
-					'absolute right-0 top-7 z-50 hidden h-6 w-6 translate-x-1/2 cursor-pointer select-none items-center justify-center rounded-full text-center md:flex border border-dashed border-border text-sm bg-bg-paper'
-				)}
-			>
-				{collapsed ? (
-					<RightOutlined className="text-xs text-text-disabled" />
-				) : (
-					<LeftOutlined className="text-xs text-text-disabled" />
-				)}
-			</div>
-		</div>
-	);
+  return (
+    <div
+      style={{ height: `${HEADER_HEIGHT}px` }}
+      className='relative flex items-center justify-center py-4'>
+      <div className='flex items-center'>
+        <Logo />
+        {themeLayout !== ThemeLayout.Mini && (
+          <span className='ml-2 text-xl font-bold text-primary'>{APPTITLE}</span>
+        )}
+      </div>
+      <div
+        onClick={onToggle}
+        onKeyDown={onToggle}
+        className={cn(
+          'absolute right-0 top-7 z-50 hidden h-6 w-6 translate-x-1/2 cursor-pointer select-none items-center justify-center rounded-full text-center md:flex border border-dashed border-border text-sm bg-bg-paper'
+        )}>
+        {collapsed ? (
+          <RightOutlined className='text-xs text-text-disabled' />
+        ) : (
+          <LeftOutlined className='text-xs text-text-disabled' />
+        )}
+      </div>
+    </div>
+  )
 }

+ 4 - 7
src/layouts/dashboard/nav/nav-vertical.tsx

@@ -14,25 +14,21 @@ import Scrollbar from '@/components/scrollbar'
 import { NAV_WIDTH } from '../config'
 import NavLogo from './nav-logo'
 
+const { VITE_APP_HOMEPAGE: HOMEPAGE } = import.meta.env
+
 const { Sider } = Layout
 
 type Props = {
   closeSideBarDrawer?: () => void
 }
-// 导出一个默认的NavVertical函数,接收Props参数
 export default function NavVertical(props: Props) {
-  // 使用useNavigate钩子函数获取导航对象
   const navigate = useNavigate()
-  // 使用useMatches钩子函数获取匹配的路由
   const matches = useMatches()
-  // 使用usePathname钩子函数获取当前路径名
   const pathname = usePathname()
 
-  // 使用useSettings钩子函数获取设置
   const settings = useSettings()
   // 从设置中获取布局、模式和暗色侧边栏
   const { themeLayout, themeMode, darkSidebar } = settings
-  // 使用useSettingActions钩子函数获取设置操作
   const { setSettings } = useSettingActions()
 
   // 使用useRouteToMenuFn钩子函数获取路由到菜单的函数
@@ -47,7 +43,8 @@ export default function NavVertical(props: Props) {
 
   // 根据权限路由和路由到菜单的函数生成菜单列表
   const menuList = useMemo(() => {
-    const menuRoutes = menuFilter(permissionRoutes)
+    const withOutDashboard = permissionRoutes.filter((e) => e.path !== HOMEPAGE)
+    const menuRoutes = menuFilter(withOutDashboard)
     return routeToMenuFn(menuRoutes)
   }, [routeToMenuFn, permissionRoutes])
 

+ 29 - 32
src/locales/i18n.ts

@@ -1,35 +1,32 @@
-import i18n from 'i18next';
-import LanguageDetector from 'i18next-browser-languagedetector';
-import { initReactI18next } from 'react-i18next';
+import { initReactI18next } from 'react-i18next'
+import { LocalEnum, StorageEnum } from '#/enum'
+import i18n from 'i18next'
+import LanguageDetector from 'i18next-browser-languagedetector'
+import { getStringItem } from '@/utils/storage'
+import en_US from './lang/en_US'
+import zh_CN from './lang/zh_CN'
 
-import { getStringItem } from '@/utils/storage';
-
-import en_US from './lang/en_US';
-import zh_CN from './lang/zh_CN';
-
-import { LocalEnum, StorageEnum } from '#/enum';
-
-const defaultLng = getStringItem(StorageEnum.I18N) || (LocalEnum.en_US as string);
+const defaultLng = getStringItem(StorageEnum.I18N) || (LocalEnum.zh_CN as string)
 i18n
-	// detect user language
-	// learn more: https://github.com/i18next/i18next-browser-languageDetector
-	.use(LanguageDetector)
-	// pass the i18n instance to react-i18next.
-	.use(initReactI18next)
-	// init i18next
-	// for all options read: https://www.i18next.com/overview/configuration-options
-	.init({
-		debug: true,
-		lng: defaultLng, // localstorage -> i18nextLng: en_US
-		fallbackLng: LocalEnum.en_US,
-		interpolation: {
-			escapeValue: false // not needed for react as it escapes by default
-		},
-		resources: {
-			en_US: { translation: en_US },
-			zh_CN: { translation: zh_CN }
-		}
-	});
+  // detect user language
+  // learn more: https://github.com/i18next/i18next-browser-languageDetector
+  .use(LanguageDetector)
+  // pass the i18n instance to react-i18next.
+  .use(initReactI18next)
+  // init i18next
+  // for all options read: https://www.i18next.com/overview/configuration-options
+  .init({
+    debug: false,
+    lng: defaultLng, // localstorage -> i18nextLng: en_US
+    fallbackLng: LocalEnum.en_US,
+    interpolation: {
+      escapeValue: false // not needed for react as it escapes by default
+    },
+    resources: {
+      en_US: { translation: en_US },
+      zh_CN: { translation: zh_CN }
+    }
+  })
 
-export default i18n;
-export const { t } = i18n;
+export default i18n
+export const { t } = i18n

+ 38 - 39
src/locales/use-locale.ts

@@ -1,50 +1,49 @@
-import en_US from 'antd/locale/en_US';
-import zh_CN from 'antd/locale/zh_CN';
-import { useTranslation } from 'react-i18next';
+import { useTranslation } from 'react-i18next'
+import { LocalEnum } from '#/enum'
+import type { Locale as AntdLocal } from 'antd/es/locale'
+import en_US from 'antd/locale/en_US'
+import zh_CN from 'antd/locale/zh_CN'
 
-import type { Locale as AntdLocal } from 'antd/es/locale';
-import { LocalEnum } from '#/enum';
-
-type Locale = keyof typeof LocalEnum;
+type Locale = keyof typeof LocalEnum
 type Language = {
-	locale: keyof typeof LocalEnum;
-	icon: string;
-	label: string;
-	antdLocal: AntdLocal;
-};
+  locale: keyof typeof LocalEnum
+  icon: string
+  label: string
+  antdLocal: AntdLocal
+}
 
 export const LANGUAGE_MAP: Record<Locale, Language> = {
-	[LocalEnum.zh_CN]: {
-		locale: LocalEnum.zh_CN,
-		label: 'Chinese',
-		icon: 'ic-locale_zh_CN',
-		antdLocal: zh_CN
-	},
-	[LocalEnum.en_US]: {
-		locale: LocalEnum.en_US,
-		label: 'English',
-		icon: 'ic-locale_en_US',
-		antdLocal: en_US
-	}
-};
+  [LocalEnum.zh_CN]: {
+    locale: LocalEnum.zh_CN,
+    label: '中文',
+    icon: 'ic-locale_zh_CN',
+    antdLocal: zh_CN
+  },
+  [LocalEnum.en_US]: {
+    locale: LocalEnum.en_US,
+    label: 'English',
+    icon: 'ic-locale_en_US',
+    antdLocal: en_US
+  }
+}
 
 export default function useLocale() {
-	const { i18n } = useTranslation();
+  const { i18n } = useTranslation()
 
-	/**
-	 * localstorage -> i18nextLng change
-	 */
-	const setLocale = (locale: Locale) => {
-		i18n.changeLanguage(locale);
-	};
+  /**
+   * localstorage -> i18nextLng change
+   */
+  const setLocale = (locale: Locale) => {
+    i18n.changeLanguage(locale)
+  }
 
-	const locale = (i18n.resolvedLanguage || LocalEnum.en_US) as Locale;
+  const locale = (i18n.resolvedLanguage || LocalEnum.zh_CN) as Locale
 
-	const language = LANGUAGE_MAP[locale];
+  const language = LANGUAGE_MAP[locale]
 
-	return {
-		locale,
-		language,
-		setLocale
-	};
+  return {
+    locale,
+    language,
+    setLocale
+  }
 }

+ 2 - 3
src/pages/sys/login/Login.tsx

@@ -14,6 +14,7 @@ import RegisterForm from './RegisterForm'
 import ResetForm from './ResetForm'
 
 // const { VITE_APP_HOMEPAGE: HOMEPAGE } = import.meta.env
+const { VITE_APP_TITLE: APPTITLE } = import.meta.env
 
 function Login() {
   const { t } = useTranslation()
@@ -35,9 +36,7 @@ function Login() {
         style={{
           background: bg
         }}>
-        <div className='text-2xl font-bold leading-normal lg:text-3xl xl:text-4xl'>
-          要易业务管理系统
-        </div>
+        <div className='text-2xl font-bold leading-normal lg:text-3xl xl:text-4xl'>{APPTITLE}</div>
         <img className='max-w-[480px] xl:max-w-[560px]' src={DashboardImg} alt='' />
         <Typography.Text className='flex flex-row gap-[16px] text-2xl'>
           {t('sys.login.signInSecondTitle')}

+ 0 - 11
src/pages/sys/login/LoginForm.tsx

@@ -1,6 +1,5 @@
 import { useRef, useState } from 'react'
 import { useTranslation } from 'react-i18next'
-// import { useNavigate } from 'react-router'
 import useUserStore from '@/store/userStore'
 import { useLoginByUsername } from '@/store/userStore'
 import { DEFAULT_USER } from '@/_mock/assets'
@@ -31,7 +30,6 @@ function LoginForm() {
   const loginFn = useLoginByUsername()
 
   // if (loginState !== LoginStateEnum.LOGIN) return null
-  // console.log(useUserStore())
   const { actions } = useUserStore()
   const loginForm = useRef<ILoginForm>({
     username: '',
@@ -42,13 +40,11 @@ function LoginForm() {
     randomStr: 'blockPuzzle'
   })
   const handleFinish = async (values: ILoginForm) => {
-    console.log(values)
     if (values.tenant_name) {
       setLoading(true)
       loginForm.current.username = values.username
       loginForm.current.password = values.password
       getTenantByName({ identifier: values.tenant_name! }).then((res) => {
-        console.log(res)
         setLoading(false)
         if (!res?.data?.tenantId) {
           toast.warning('未查询到租户!', { position: 'top-center' })
@@ -65,18 +61,11 @@ function LoginForm() {
   }
   // 滑块验证
   const verifyRef = useRef<VerifyRef>(null)
-  // const navigate = useNavigate()
   const verifySuccess = function (params: { captchaVerification: string }) {
     loginForm.current.code = params.captchaVerification
     const submitData = { ...loginForm.current }
     delete submitData.tenant_name
     loginFn(submitData)
-      .then(() => {
-        toast.success('登录成功!', { position: 'top-center' })
-      })
-      .catch((err) => {
-        toast.error(err || '登录失败!', { position: 'top-center' })
-      })
   }
 
   const [userAgreementModalProps, setUserAgreementModalProps] = useState<UserAgreementModalProps>({

+ 676 - 656
src/pages/sys/login/components/UserAgreementModal.tsx

@@ -1,666 +1,686 @@
-import { Button, Modal } from 'antd';
-import styled from 'styled-components';
+import styled from 'styled-components'
+import { Button, Modal } from 'antd'
 
 export type UserAgreementModalProps = {
-  title: string;
-  show: boolean;
-  onCancel: VoidFunction;
-};
+  title: string
+  show: boolean
+  onCancel: VoidFunction
+}
 
-export default function UserAgreementModal({
-	title,
-	show,
-	onCancel
-}: UserAgreementModalProps) {
-	return (
-		<Modal title={title} open={show} onCancel={onCancel} footer={[
-      <Button key="back" onClick={onCancel}>
-        关 闭
-      </Button>
-    ]}>
-			<AgreementWrapper className="h-[500px] overflow-y-auto">
-      <p className="title mb-[20px] bold-text text-center">要易云平台用户协议</p>
-      <p className="text-right">第1.0版</p>
-      <p className="underline-bold">
-        《平台用户协议》系由您(或称“用户”)与深圳要易云科技服务有限公司(或称“要易云”)之间就您使用要易云平台的(以下或简称“本平台”)服务而签订。本平台依据《平台用户协议》的规定提供服务,该协议一旦生效对用户及要易云均产生法律效力。您在用户注册时,请认真阅读本协议,审阅并选择同意或不同意本协议。
-      </p>
-      <p className="underline-bold">
-        在使用本平台服务之前,请您务必审慎阅读、充分理解本协议各项条款,特别是限制或免除责任条款、隐私保护政策、账号规则、法律适用和争议解决条款(包括管辖条款),以及其它以加粗加黑和/或加下划线等显著形式提示您注意的重要条款,请务必重点查阅。
-      </p>
-      <p className="underline-bold">
-        要易云与您将通过数据电文形式订立本协议,若您不同意本协议,则您有充分且完全的权利立即停止成为本平台用户的注册程序。您通过网络页面点击“同意”的按钮并且该注册和/或实际使用平台服务的行为即视为您已阅读、充分理解并同意接受本协议的全部条款含义及相应的法律后果,不可撤销地同意与要易云以数据电文形式订立本协议并受本协议条款及其后续修订版本的全部约束,本协议在您点击“同意”并实际使用平台服务时成立生效。如果您对本协议有任何的疑问、投诉、意见和建议,欢迎您通过本协议所附联系方式与要易云沟通反馈。
-      </p>
-      <p className="bold-text">第1条 基础约定事项</p>
-      <p>
-        1.1
-        协议范围:考虑到互联网服务以及商品频繁迭代更新等行业特点,为了更全面的界定您与本平台之间的权利义务,
-        <span className="underline-bold">
-          本协议包括要易云根据法律法规规定制定的其他政策、规则、公告声明等(除非特有所指,以上合称为“本协议”)
-        </span>
-        ,您应当加以遵守。
-      </p>
-      <p>
-        1.2
-        服务范围:要易云可能通过不断丰富的功能界面向您提供本协议项下的平台服务,包括但不限于PC端网站以及其他形式。具体以要易云实时发布的服务功能界面范围为准。
-      </p>
-      <p>
-        1.3 主体地位:请您在接受本协议或者以其他要易云允许的方式实际使用本服务之前,
-        <span className="underline-bold">
-          请确保您是适格的合同主体,并在清楚理解本协议内容及其法律后果后自行决定是否同意签署,而且就您所知,您不存在任何可能使得本协议无效或者对本协议项下义务的履行产生重大不利影响之情形。
-        </span>
-      </p>
-      <p className="bold-text">第2条 用户资格</p>
-      <p>2.1 只有符合下列条件的机构才能申请成为本平台用户,可以使用本平台的服务:</p>
-      <p>
-        2.1.1
-        <span className="underline-bold">非自然人用户:</span>
-      </p>
-      <p className="underline-bold">
-        (1)根据中国法律、法规、行政规章合法设立并有效存续的机关、企事业单位、社团组织和其他组织;
-      </p>
-      <p className="underline-bold">
-        (2)未被国家企业信用信息公示系统(www.gsxt.gov.cn)列入严重违法失信企业名录;
-      </p>
-      <p className="underline-bold">(3)未被责令停业或处于破产状态的;</p>
-      <p className="underline-bold">(4)财产及其股权未处于重组、接管、查封、扣押或冻结状态;</p>
-      <p className="underline-bold">(5)近三年内不涉及重大诉讼、仲裁,不属于失信被执行人的;</p>
-      <p className="underline-bold">(6)不存在违反法律法规规定的其他情形。</p>
-      <p>
-        2.2
-        <span className="underline-bold">
-          不符合以上条件的法人或其他组织,将不被允许注册及认证成为本平台用户,且不得使用本平台相关服务,本平台一经发现,有权立即终止对该用户的服务,并追究其使用本平台服务的一切法律责任。
-        </span>
-      </p>
-      <p>
-        2.3
-        <span className="underline-bold">非自然人用户需通过授权自然人为管理员后进行操作及管理。</span>
-      </p>
-      <p>2.4 用户需要提供真实有效的联系电话,并提供真实信息。</p>
-      <p>
-        2.5
-        本平台用户须承诺遵守法律法规、社会主义制度、国家利益、公民合法权益、公共秩序、社会道德风尚和信息真实性。
-      </p>
-      <p className="bold-text">第3条 用户账号及资金账户规则</p>
-      <p>3.1 用户账号</p>
-      <p>
-        3.1.1
-        您通过平台或上级企业管理员获得要易云配置的用户账号,自首次使用该账号登录并使用平台服务之日起即成为平台用户(以下称“注册”),您在使用本平台服务时可能需要提供一些必要的信息,具体以本网站相关页面提示为准。您须保证所填写及所提供的资料真实、准确、完整,否则可能无法使用本平台服务,或在使用过程中受到限制,甚至影响您正常使用本平台特定功能。
-        <span className="underline-bold">
-          对于因您提供的信息不真实、不准确或不完整导致的责任和损失由您自行承担。
-        </span>
-      </p>
-      <p>
-        3.1.2
-        <span className="underline-bold">
-          您有责任自行负责保管账号的用户名和登录密码等信息,否则因该等事项引发的法律责任由用户自行承担(例如账户被盗用、转让等)。凡使用本平台服务登录账号和登录凭证的行为,本平台服务视为您本人的操作,操作所产生的电子信息记录均为本平台服务用户行为的有效凭据。
-        </span>
-      </p>
-      <p>3.2 账号锁定</p>
-      <p>
-        3.2.1 用户申请锁定情形
-        账号锁定指,在本平台执行某账户进行锁定后,该用户将不能登录该账户、进而不能使用平台项下全部功能。如果您需要锁定您的账号,您可以通过联系平台客服进行申请,经本平台审核同意后方可解除与本平台的协议关系并锁定您账号。当您的账户锁定后,您的个人信息和您在使用要易云服务期间提供或产生的信息我们将按照《要易云隐私政策》第三条第三款约定的存储期限进行保存。
-      </p>
-      <p>3.5.2 强制锁定情形</p>
-      <p className="underline-bold">要易云有权在您的账号符合如下情况时,锁定您的账号并停止服务:</p>
-      <p className="underline-bold">(1)连续180日未曾登录、使用本平台服务;</p>
-      <p className="underline-bold">
-        (2)在用户违反本协议及相关规则规定时,本平台有权终止向该用户提供服务。但该用户在被本平台终止提供服务后,再一次直接或间接或以他人名义注册为本平台用户的,一经发现,本平台有权再次单方面终止为该用户提供服务;
-      </p>
-      <p className="underline-bold">
-        (3)本平台发现用户注册资料中主要内容是虚假的,本平台有权随时终止为该用户提供服务;
-      </p>
-      <p className="underline-bold">(4)本协议终止或更新时,用户未确认新的协议的;</p>
-      <p className="underline-bold">
-        (5)其它本平台认为需终止服务的情况。
-        有以上强制锁定情形的,本平台可自行全权决定,在发出通知或不发出通知的情况下,随时停止或终止提供全部或部分服务。服务终止后,本平台没有义务为用户保留原账户中或与之相关的任何信息,或在出现强制锁定情形后转发任何未曾阅读或发送的信息给用户或第三方。
-      </p>
-      <p>3.5.3 账户锁定资金结算</p>
-      <p className="underline-bold">
-        要易云将在您与平台其他用户清理、结算账户资产及纠纷争议后,为您提供账号锁定服务。在锁定账号之后,要易云将停止为您提供任何服务。但因强制锁定的,要易云有权在资金及纠纷处理完毕且锁定该账户,使其无法正常使用任何功能直至相关纠纷处理完毕。
-      </p>
-      <p>3.5.4 不论用户账户通过何种方式锁定,本平台仍保留下列权利:</p>
-      <p>
-        本平台有权在法律、法规、行政规章规定的时间内保留该用户的资料,包括但不限于以前的用户资料、操作记录等;保留期限详见《要易云隐私政策》。
-      </p>
-      <p className="bold-text">第4条 平台交易规则</p>
-      <p>您承诺与本平台其他用户进行交易的过程中,应良好遵守如下基本交易规则:</p>
-      <p>4.1 了解服务信息</p>
-      <p>
-        您在通过本平台交易前,应详细了解并完全接受本平台服务内容及操作规则,方可进行操作或签署协议进行交易。
-      </p>
-      <p>4.2 数据信息保护</p>
-      <p className="underline-bold">
-        关于您在向合作的第三方服务商发起的准入信息申报邀请时所收集到的信息,需用仅用于您对于其准入需求,不可用于外部分享或其他用途。
-      </p>
-      <p>4.3 其他规则说明</p>
-      <p>
-        4.3.1
-        为交易达成以及用户体验提升等目的,本平台可能会为您提供更为友好完善的交易规则服务,并引入第三方服务。第三方服务主要为您提供企业管理所查询到的数据信息和发票管理对应的发票数据信息。
-      </p>
-      <p>
-        4.3.2
-        您同意并知悉,本平台中的具体内容、功能和形式由平台根据实际情况按“现状”实时提供,要易云有权自行确定本平台服务的具体内容、功能和形式,有权自行决定增加、变更、中断和停止本平台具体的内容、功能和形式。具体以本平台实时呈现的服务内容、功能和形式为准。
-      </p>
-      <p>
-        4.3.3
-        <span className="underline-bold">
-          由于众所周知的互联网技术因素等客观原因存在,本平台显示的信息可能会有一定的滞后性或差错,对此情形您知悉并理解。如因系统故障或平台重大误解导致显示信息明显不合理的,请勿进行下一步操作,如您明知显示信息明显不合理仍然提交交易文件的,将可能被视为恶意行为,本平台将有权取消本次交易;如果您在不知情的情况下进行了下一步操作,请第一时间联系本平台客服。
-        </span>
-      </p>
-      <p>
-        4.3.4
-        <span className="underline-bold">
-          您同意在使用本平台服务的过程中遵守诚实信用原则,您不得实施恶意行为扰乱本平台正常秩序。如本平台根据您的登录账号下的操作记录及其他相关信息,发现您有(或可能有)恶意行为的,则本平台有权单方拒绝您的需求,或单方取消您的操作且不予退还保证金(如有),且无须由此向您承担法律责任。
-        </span>
-      </p>
-      <p className="bold-text">第5条 用户的权利和义务</p>
-      <p>
-        5.1
-        用户有权根据本协议及本平台发布的相关规则,登录并利用本平台查询公开的企业相关信息、发票状态信息、收集合作第三方服务商准入信息等,并有权享受本平台提供的其他有关资讯及信息服务。
-      </p>
-      <p>
-        5.2
-        <span className="underline-bold">
-          用户有权对部分自身账号相关信息进行查询、更新。用户有权对自身账号对应的用户信息、操作信息、汇总信息等相关信息进行查询,用户可以按其权限进行操作处理,例如管理员可按权限创建账户、查询第三方企业公开信息、发票状态信息、收集合作第三方服务商准入信息等。
-        </span>
-      </p>
-      <p>
-        5.3
-        <span className="underline-bold">
-          用户须自行管理自己的用户账号和密码或者验证码,且须对在用户账号密码下发生的所有活动承担责任。用户有权根据需要更改登录密码。因用户的过错导致的任何损失由用户自行承担,该过错包括但不限于:不按照提示操作、不按照平台流程操作、遗忘或泄漏密码或者验证码、密码被他人破解及用户使用的计算机被他人侵入等情形。
-        </span>
-      </p>
-      <p>
-        5.4
-        用户应当向本平台提供真实准确的开通账户所需信息,包括但不限于真实姓名、手机号码(手机号码即为用户在本平台的账户)等。保证本平台可以通过上述联系方式与用户进行联系。同时,
-        <span className="underline-bold">
-          用户也应当在相关资料实际变更时及时更新有关用户资料,因未及时更新资料导致的任何损失由用户自行承担。
-        </span>
-      </p>
-      <p>
-        5.5
-        <span className="underline-bold">用户在本平台的账号名称及发布的内容,不得有下列情形:</span>
-      </p>
-      <p className="underline-bold">(1)违反中国宪法或法律法规规定的;</p>
-      <p className="underline-bold">(2)危害国家安全,泄露国家秘密,颠覆国家政权,破坏国家统一的;</p>
-      <p className="underline-bold">(3)损害国家荣誉和利益的,损害公共利益的;</p>
-      <p className="underline-bold">(4)煽动民族仇恨、民族歧视,破坏民族团结的;</p>
-      <p className="underline-bold">(5)破坏国家宗教政策,宣扬邪教和封建迷信的;</p>
-      <p className="underline-bold">(6)散布谣言,扰乱社会秩序,破坏社会稳定的;</p>
-      <p className="underline-bold">(7)散布淫秽、色情、赌博、暴力、凶杀、恐怖或者教唆犯罪的;</p>
-      <p className="underline-bold">(8)侮辱或者诽谤他人,侵害他人合法权益的;</p>
-      <p className="underline-bold">(9)含有法律、行政法规禁止的其他内容的。</p>
-      <p>
-        5.6
-        <span className="underline-bold">
-          用户在本平台上所实施行为,不得违反国家法律、法规、行政规章的规定,不得侵犯他人知识产权或其他合法权益的信息,不得违背社会公共利益或公共道德,不得违反本平台的相关规定。
-        </span>
-      </p>
-      <p>
-        5.7
-        <span className="underline-bold">
-          用户承诺自己在使用本平台实施的所有行为遵守法律、法规、行政规章和本平台的相关规定以及各种社会公共利益或公共道德。如有违反导致任何法律后果的发生,用户将以自己的名义独立承担相应的法律责任。
-        </span>
-      </p>
-      <p>
-        5.8
-        用户在本平台网上操作过程中如与其他用户因产生纠纷,可以请求本平台从中予以协调处理。用户如发现其他用户有违法或违反本协议的行为,可以向本平台举报。
-      </p>
-      <p>
-        5.9
-        <span className="bold-text">用户不得使用以下方式登录平台或破坏本平台所提供的服务:</span>
-      </p>
-      <p className="underline-bold">
-        (1)以任何机器人软件、蜘蛛软件、爬虫软件、刷屏软件或其它自动方式访问或登录本平台;
-      </p>
-      <p className="underline-bold">
-        (2)通过任何方式对本平台内部结构造成或可能造成不合理或不合比例的重大负荷的行为;
-      </p>
-      <p className="underline-bold">
-        (3)通过任何方式干扰或试图干扰本平台的正常工作或本平台上进行的任何活动。
-      </p>
-      <p>
-        5.10
-        <span className="underline-bold">
-          用户同意接收来自本平台的信息,包括但不限于平台内部弹窗、手机短信、手机消息通知等。
-        </span>
-      </p>
-      <p>
-        5.11
-        <span className="underline-bold">
-          用户如为非自然人用户的,其在本平台实名认证的管理人员的所有操作视为用户行为,用户须对其管理人员的行为承担一切责任。用户应要求其管理的人员遵守本协议及本平台相关规则等要求,如用户实名认证的管理人员违反本协议及/或本平台规则等要求的,视为用户违反上述要求,用户应按照本协议要求承担相应责任。
-        </span>
-      </p>
-      <p className="bold-text">第6条 本平台的权利和义务</p>
-      <p>6.1 本平台,是一个信息服务平台,为用户提供信息查询、信息采集服务。</p>
-      <p>
-        6.2
-        本平台有义务在现有技术水平的基础上努力确保网上交流平台的正常运行,尽力避免因平台原因导致服务中断,若因不可避免的原因中断服务,平台将把中断时间限制在最短时间内,保证用户网上交流活动的顺利进行。
-      </p>
-      <p>
-        6.3
-        用户因在本平台实施活动过程中与其他用户产生纠纷的,用户将纠纷告知本平台,或本平台知悉纠纷情况的,经审核后,本平台有权通过电子邮件及联系电话向纠纷双方了解纠纷情况,并将所了解的情况通过电子邮件互相通知对方;用户通过司法机关依照法定程序要求本平台提供相关资料,本平台将积极配合并提供有关资料。
-      </p>
-      <p>
-        6.4
-        <span className="underline-bold">
-          因网上信息平台的特殊性,本平台不承担对所有用户的操作行为进行事先审查的义务,但如发生以下情形,本平台有权无需征得用户的同意限制用户的活动、向用户核实有关资料、发出警告通知、暂时中止、无限期中止及拒绝向该用户提供服务:
-        </span>
-      </p>
-      <p className="underline-bold">(1)用户违反本协议的;</p>
-      <p className="underline-bold">
-        (2)存在用户或其他第三方通知本平台,认为某个用户或具体交易事项存在违法或不当行为,并提供相关证据,而本平台无法联系到该用户核证或验证该用户向本平台提供的任何资料的;
-      </p>
-      <p className="underline-bold">
-        (3)存在用户或其他第三方通知本平台,认为某个用户或具体事项存在违法或不当行为,并提供相关证据。本平台以普通非专业人员的知识水平标准对相关内容进行判别,可以认为这些内容或行为存在显著可能会对他方或本平台造成财务损失或承担法律责任的。
-      </p>
-      <p>
-        6.5
-        <span className="underline-bold">
-          根据国家法律、法规、行政规章规定、本协议的内容和本平台所掌握的事实依据,可以认定该用户存在违法或违反本协议行为或在本平台交易平台上存在其他不当行为,本平台有权无需征得用户的同意在本平台上以网络发布形式公布对该用户匿名化处理后的违法行为,并有权随时作出对该用户终止服务提供等处理。
-        </span>
-      </p>
-      <p>
-        6.6
-        <span className="underline-bold">
-          本平台有权在不通知用户的前提下,删除或采取其他限制性措施处理下列信息:包括但不限于违反法律法规、公共利益或可能严重损害本平台和其他用户合法利益的信息。
-        </span>
-      </p>
-      <p className="bold-text">第7条 免责声明</p>
-      <p>当用户接受该协议时,用户应当明确了解并同意:</p>
-      <p>
-        7.1
-        <span className="underline-bold">
-          要易云对于您自本平台而获得的所有要易云以外主体发布的信息、内容等(以下统称“信息”),除法律明确规定外,不保证真实、准确和完整性。如果任何单位或者个人通过上述“信息”而进行任何行为,您须自行甄别真伪和谨慎预防风险。无论何种原因,要易云不对任何非与要易云直接发生的交易或行为承担任何直接、间接、附带或衍生的损失和责任。
-        </span>
-      </p>
-      <p>
-        7.2
-        <span className="underline-bold">
-          本平台是在现有技术基础上提供的服务,本平台不能随时预见到任何技术上的问题或其他困难。该等困难可能会导致数据损失或其他服务中断。本平台不保证以下事项:
-        </span>
-      </p>
-      <p className="underline-bold">(1)本平台将符合所有用户的需求;</p>
-      <p className="underline-bold">(2)本平台不受干扰、能够随时响应、安全可靠或免于出错;</p>
-      <p className="underline-bold">(3)本服务使用权的取得结果是正确或可靠的。</p>
-      <p>
-        7.4
-        <span className="underline-bold">
-          是否经由本平台下载或取得任何非由本平台提供的资料,由用户自行考虑、衡量并且自负风险,因下载任何资料而导致用户电脑系统的任何损坏或资料流失,用户应负完全责任。
-        </span>
-      </p>
-      <p>
-        7.5
-        <span className="underline-bold">
-          用户经由本平台取得的建议和资讯,无论其形式或表现,均不构成要易云对用户通过本平台进行交易所作出的任何明示或暗示的保证或担保。
-        </span>
-      </p>
-      <p>
-        7.6
-        <span className="underline-bold">
-          基于以下原因而造成的利润、商誉、使用、资料损失或其它无形损失,本平台不承担任何直接、间接、附带、特别、衍生性或惩罚性赔偿:
-        </span>
-      </p>
-      <p className="underline-bold">(1)本平台的使用或无法使用;</p>
-      <p className="underline-bold">(2)用户的传输或资料遭到未获授权的存取或变更;</p>
-      <p className="underline-bold">(3)本平台中任何第三方之声明或行为;</p>
-      <p className="underline-bold">(4)本平台其它相关事宜。</p>
-      <p>
-        7.7
-        <span className="underline-bold">
-          本平台是为用户提供信息查询及收集服务的第三方平台,对于所查询及收集的信息的合法性、真实性等,本平台一律不负任何担保责任。
-        </span>
-      </p>
-      <p>
-        7.8
-        <span className="underline-bold">
-          本平台提供的与其它互联网平台或资源的链接,用户可能会因此链接至其它运营商经营的平台,但不表示本平台与这些运营商有任何关系。其它运营商经营的平台均由各经营者自行负责,不属于本平台控制及负责范围之内。对于存在或来源于此类平台或资源的任何内容、广告、物品或其它资料,本平台亦不予保证或负责。因使用或依赖任何此类平台或资源发布的或经由此类平台或资源获得的任何内容、物品或服务所产生的任何损害或损失,本平台不负任何直接或间接的责任。
-        </span>
-      </p>
-      <p>
-        7.9
-        <span className="underline-bold">就下列情形的发生,本平台不承担任何法律责任:</span>
-      </p>
-      <p className="underline-bold">
-        (1)由于您将账号、密码告知他人或与他人共享注册账户,由此导致的任何个人信息的泄漏,或其他非因本平台原因导致的个人信息的泄漏;
-      </p>
-      <p className="underline-bold">
-        (2)您在申请使用平台服务时因个人资料有任何变动,但未能及时更新,由此导致个人信息不真实而引起的问题及后果;
-      </p>
-      <p className="underline-bold">
-        (3)本平台根据法律规定、政策要求或行政/司法机关的要求对外提供您的个人信息或交易记录;
-      </p>
-      <p className="underline-bold">
-        (4)因不可抗力导致的任何后果。本用户协议中的“不可抗力”之定义,详见本协议第12.1条的相关表述。
-      </p>
-      <p className="bold-text">第8条 知识产权</p>
-      <p>
-        8.1
-        <span className="underline-bold">
-          本平台及本平台所使用的任何相关软件、程序、内容,包括但不限于作品、图片、档案、资料、平台构架、平台版面的安排、网页设计、经由本平台或广告商向用户呈现的广告或资讯,均由本平台或法定权利人依法享有相应的知识产权,包括但不限于著作权、商标权、专利权或其它专属权利等,受到相关法律的保护。未经本平台或权利人明示授权,用户不得擅自复制、传播、展示、镜像、上传、下载、使用、修改、出租、出借、出售、散布上述任何资料和资源,或根据上述资料和资源制作成任何种类产品,或者从事任何其它侵犯知识产权的行为,否则,平台有权追求侵权人的相关法律责任。
-        </span>
-      </p>
-      <p>
-        8.2
-        <span className="underline-bold">
-          本平台授予用户不可转移及非专属的使用权,使用户可以通过单独计算机使用本平台的目标代码(以下简称“软件”),但用户不得且不得允许任何第三方复制、修改、创作衍生作品、进行还原工程、反向组译,或以其它方式破译或试图破译源代码,或出售、转让“软件”或对“软件”进行再授权,或以其它方式移转“软件”之任何权利。用户同意不以任何方式修改“软件”,或使用修改后的“软件”。
-        </span>
-      </p>
-      <p>
-        8.3
-        您在使用要易云服务时利用本平台及服务所发表上传的文字、图片、视频等原创信息的知识产权归您所有(或由第三方内容提供方和您另行约定)。
-        <span className="underline-bold">
-          您在此明确授权:您上传到本平台服务器上的任何内容,您皆授予本平台在世界范围内基于商业或非商业目的,永久的、免费的以复制、翻译、发行、重新编排、节选等方式使用,或采用任何形式再传播或创作改变或衍生作品的权利。
-        </span>
-      </p>
-      <p>
-        8.4
-        <span className="underline-bold">
-          基于对数据的合法加工而获得的具有竞争性的数据权益,除法律法规另有规定外,要易云享有独立的使用权益而无须获得您的同意。
-        </span>
-      </p>
-      <p>
-        8.5
-        <span className="underline-bold">
-          在未得到著作权人的授权时,您不得将他人的作品全部或部分复制发表到本平台系统。如您上传的内容侵犯了第三方的著作权或其他权利,本平台将不承担任何法律责任,所有后果由您自行承担。如果第三方提出关于著作权的异议,本平台有权根据实际情况删除相关的内容,并有权追究您的法律责任。因您的侵权行为给本平台或任何第三方造成损失的,您应负责全部赔偿责任。
-        </span>
-      </p>
-      <p>
-        8.6
-        关于信息内容的投诉或举报。若您在使用平台过程中,不慎受到合法权益的侵犯,您有权通知要易云采取必要措施进行处置。若您在使用平台过程中,发现存在违法违规或违反本服务相关规则的情况,您也有权向要易云发起举报,要易云亦会及时采取必要措施(删除、屏蔽、断开链接或限制使用功能等)进行处置。
-      </p>
-      <p>
-        8.7
-        <span className="underline-bold">
-          内容维权授权。在法律法规允许的范围内,您同意并授权要易云就侵犯您合法权益的行为(包括但不限于私自复制、使用、编辑、抄袭等行为)采取任何形式的法律行动,包括但不限于投诉、诉讼等必要的维权措施。
-        </span>
-      </p>
-      <p>
-        8.8
-        <span className="underline-bold">用户不得经由非本平台所提供的界面使用本平台。</span>
-      </p>
-      <p className="bold-text">第9条 用户行为规范</p>
-      <p>
-        9.1
-        <span className="underline-bold">
-          您保证合理地使用本平台服务,并接受本协议和要易云适时制定并发布的其他政策、规则、公告声明。
-        </span>
-      </p>
-      <p>
-        9.2
-        <span className="underline-bold">
-          行为禁止:您可在本协议约定的范围内使用本平台及服务,您不得利用本平台从事以下行为:
-        </span>
-      </p>
-      <p className="underline-bold">(1)超出授权或恶意使用平台服务;</p>
-      <p className="underline-bold">
-        (2)利用本平台发表、传送、传播、储存危害国家安全、国家统一、社会稳定的内容,或侮辱诽谤、色情、暴力、引起他人不安及任何违反国家法律法规政策的内容,或者设置含有上述内容的网名、角色名,发布、传送、传播违法广告信息、营销信息及垃圾信息等;
-      </p>
-      <p className="underline-bold">
-        (3)利用本平台侵害他人知识产权、肖像权、隐私权、名誉权、个人信息等合法权利或权益;
-      </p>
-      <p className="underline-bold">(4)恶意虚构或协助虚构事实、评价等信息或数据;</p>
-      <p className="underline-bold">
-        (5)进行任何危害计算机网络安全的行为,包括但不限于:使用未经许可的数据或进入未经许可的服务器/账户;未经允许进入公众计算机网络或者他人计算机系统并删除、修改、增加存储信息;未经许可,企图探查、扫描、测试本“软件”系统或网络的弱点或其它实施破坏网络安全的行为;企图干涉、破坏本“软件”系统或网站的正常运行,故意传播恶意程序或病毒以及其他破坏干扰正常网络信息服务的行为;伪造TCP/IP数据包名称或部分名称;利用本平台及服务上传任何病毒、木马,或者蠕虫等危害网络健康的内容;
-      </p>
-      <p className="underline-bold">
-        (6)对本平台进行反向工程、反向编译或反向汇编或以其它方式企图发现平台的源代码和算法,未经许可修改、禁用软件的任何功能或创建基于软件的衍生作品。去除本平台或文档上的任何所有权声明或标签,或将其他软件与本平台合并;
-      </p>
-      <p className="underline-bold">
-        (7)进行任何破坏要易云提供服务公平性或者其他影响应用正常秩序的行为,如主动或被动刷分、合伙作弊、使用外挂或者其他的作弊软件、利用BUG(又叫“漏洞”或者“缺陷”)来获得不正当的非法利益,或者利用互联网或其他方式将外挂、作弊软件、BUG公之于众;
-      </p>
-      <p className="underline-bold">
-        (8)从事其他法律法规、政策及公序良俗、社会公德禁止的行为以及侵犯其他个人、公司、社会团体、组织的合法权益的行为。
-      </p>
-      <p className="underline-bold">
-        9.3
-        信息内容规范:为了营造良好网络生态,保障公民、法人和其他组织的合法权益,维护国家安全和公共利益,要易云将根据法律、行政法规,营造清朗的网络空间,并开展弘扬正能量、处置违法和不良信息等相关活动。
-      </p>
-      <p>
-        9.3.1
-        <span className="underline-bold">您不得制作、复制、发布含有下列内容的违法信息:</span>
-      </p>
-      <p className="underline-bold">(1)反对中国宪法所确定的基本原则的;</p>
-      <p className="underline-bold">(2)危害国家安全,泄露国家秘密,颠覆国家政权,破坏国家统一的;</p>
-      <p className="underline-bold">(3)损害国家荣誉和利益的;</p>
-      <p className="underline-bold">
-        (4)歪曲、丑化、亵渎、否定英雄烈士事迹和精神,以侮辱、诽谤或者其他方式侵害英雄烈士的姓名、肖像、名誉、荣誉的;
-      </p>
-      <p className="underline-bold">
-        (5)宣扬恐怖主义、极端主义或者煽动实施恐怖活动、极端主义活动的;
-      </p>
-      <p className="underline-bold">(6)煽动民族仇恨、民族歧视,破坏民族团结的;</p>
-      <p className="underline-bold">(7)破坏国家宗教政策,宣扬邪教和封建迷信的;</p>
-      <p className="underline-bold">(8)散布谣言,扰乱经济秩序和社会秩序的;</p>
-      <p className="underline-bold">(9)散布淫秽、色情、赌博、暴力、凶杀、恐怖或者教唆犯罪的;</p>
-      <p className="underline-bold">(10)侮辱或者诽谤他人,侵害他人名誉、隐私和其他合法权益的;</p>
-      <p className="underline-bold">(11)法律、行政法规禁止的其他内容。</p>
-      <p>
-        9.3.2
-        <span className="underline-bold">
-          要易云也将依法采取措施,防范和抵制制作、复制、发布含有下列内容的不良信息:
-        </span>
-      </p>
-      <p className="underline-bold">(1)使用夸张标题,内容与标题严重不符的;</p>
-      <p className="underline-bold">(2)炒作绯闻、丑闻、劣迹等的;</p>
-      <p className="underline-bold">(3)不当评述自然灾害、重大事故等灾难的;</p>
-      <p className="underline-bold">(4)带有性暗示、性挑逗等易使人产生性联想的;</p>
-      <p className="underline-bold">(5)展现血腥、惊悚、残忍等致人身心不适的;</p>
-      <p className="underline-bold">(6)煽动人群歧视、地域歧视等的;</p>
-      <p className="underline-bold">(7)宣扬低俗、庸俗、媚俗内容的;</p>
-      <p className="underline-bold">
-        (8)可能引发未成年人模仿不安全行为和违反社会公德行为、诱导未成年人不良嗜好等的;
-      </p>
-      <p className="underline-bold">(9)其他对网络生态造成不良影响的内容。</p>
-      <p>
-        9.4
-        <span className="underline-bold">信息内容的使用规范</span>
-      </p>
-      <p className="underline-bold">
-        未经要易云书面许可,您不得自行或授权、允许、协助任何第三人对本平台中信息内容进行如下行为:
-      </p>
-      <p className="underline-bold">(1)复制、读取、采用本平台的信息内容,用于任何形式的商业用途;</p>
-      <p className="underline-bold">
-        (2)擅自编辑、整理、编排平台及相关服务的信息内容后在平台及相关服务的源页面以外的渠道进行展示;
-      </p>
-      <p className="underline-bold">
-        (3)采用不正当方式,自行或协助第三人对本平台及相关服务的信息内容产生流量、阅读量或交易引导、转移、劫持等不利影响。
-      </p>
-      <p className="bold-text">第10条 个人信息保护与隐私政策</p>
-      <p>
-        10.1
-        尊重用户隐私并保护您的个人信息安全是要易云的一贯态度,平台将会采取合理的措施保护您的个人信息与隐私。
-      </p>
-      <p>10.2 信息使用</p>
-      <p className="underline-bold">
-        (1)要易云有权在遵守法律法规的前提下,以明示的方式获取、使用、储存和分享您的个人信息。要易云不会在未经您授权时,公开、编辑或透露您的个人信息及您保存在要易云的非公开内容。您同意并保证:本平台有权依法收集使用您的相关信息。
-      </p>
-      <p>(2)本平台不会向任何人出售或出借用户的个人或法人信息,除非事先得到用户得许可;</p>
-      <p className="underline-bold">
-        (3)本平台亦不允许任何第三方以任何手段收集、编辑、出售或者无偿传播用户的个人或法人信息。任何用户如从事上述活动,一经发现,本平台有权立即终止与该用户的服务协议,查封其账号。
-      </p>
-      <p className="underline-bold">
-        (4)您同意,要易云有权通过cookie等技术收集您的使用、行为信息,并在经过数据脱敏使之不再指向、关联到您个人的身份信息时,自由使用脱敏后的纯商业数据。当然,您也可根据自己的偏好删除Cookie,但如果您这么做,则需要在每一次访问要易云的网站时亲自更改用户设置。目前删除Cookie的一般路径是浏览器:“设置-清除数据”,或者将手机系统还原/清除。
-      </p>
-      <p>10.3 信息披露</p>
-      <p className="underline-bold">用户的信息将在下述情况下部分或全部被披露:</p>
-      <p className="underline-bold">(1)经用户同意,向第三方披露;</p>
-      <p className="underline-bold">
-        (2)根据法律的有关规定,或者行政、司法机关的要求,向第三方或者行政、司法机关披露;
-      </p>
-      <p className="underline-bold">
-        (3)若用户出现违反中国有关法律或者平台规定的情况,需要向第三方披露;
-      </p>
-      <p className="underline-bold">
-        (4)为保护您、本平台的其他用户或本平台的关联方的合法权益,本平台可能将您的个人信息用于预防、发现、调查以下事项:欺诈、危害安全、违法或违反与本平台或关联方协议、政策或规则的行为;
-      </p>
-      <p className="underline-bold">
-        (5)在遵循隐私权保护以及其他相应的保密安全措施的前提下,允许本平台将您的个人信息提供给相关合作方,让其根据本平台指令处理相关信息;
-      </p>
-      <p className="underline-bold">(6)其它本平台根据法律法规认为合适的披露。</p>
-      <p className="underline-bold">若您不同意以上内容,请立即停止使用要易云平台服务。</p>
-      <p>10.4 信息安全</p>
-      <p>
-        (1)要易云将运用各种安全技术和程序建立完善的管理制度来保护您的个人信息及隐私安全,以免遭受未经授权的访问、使用或披露。
-      </p>
-      <p>
-        (2)如果用户发现自己的个人或法人信息泄密,尤其是用户账户或密码发生泄露,请用户立即联络本平台客服,以便要易云采取相应措施。
-      </p>
-      <p>
-        10.5
-        <span className="underline-bold">
-          在遵守本协议项下特别约定的个人信息保护与隐私政策外,要易云希望您认真并完整阅读要易云特别针对平台而制定并适时发布的《隐私政策》,这将更有助于保障您的个人信息。
-        </span>
-      </p>
-      <p className="bold-text">第11条 信息或广告推送</p>
-      <p>
-        您同意在接受要易云提供服务的同时,允许要易云在遵守法律法规的前提下自行或由第三方广告商通过平台内弹窗、手机系统弹窗、手机短信、电子邮件等方式向您发送、展示广告、推广或宣传信息(包括商业与非商业信息)。如您对发送、推荐的广告或信息不感兴趣,您可以基于要易云提供的相关技术选项,控制系统向您展示或不展示/减少展示相关类型的广告或信息。
-      </p>
-      <p className="bold-text">第12条 不可抗力</p>
-      <p>
-        12.1
-        <span className="underline-bold">
-          鉴于互联网服务的特殊性,您理解并同意要易云在因不可抗力或者其他意外事件导致平台及服务障碍不能正常运作、服务中断或延迟等情形下,对您在本平台所遭受的损失(包括但不限于财产、收益、数据资料等方面的损失或其它无形损失)无需承担责任。同时,因不可抗力使得本协议的履行不可能、不必要或者无意义的,任何一方均可单方解除本协议且无需承担违约责任。
-        </span>
-      </p>
-      <p>
-        本协议所称之不可抗力意指不能预见、不能避免并不能克服的客观情况,包括但不限于战争、台风、水灾、火灾、雷击或地震、罢工、暴动、法定疾病、停电、计算机病毒、木马、其他恶意程序、黑客攻击、网络病毒、电信部门技术管制、网络运营公司技术调整或故障、系统维护、政府行为或任何其它自然或人为造成的灾难等客观情况。
-      </p>
-      <p>
-        12.2
-        若任何一方由于不可抗力无法履行协议,该方须及时通知另一方以便减轻另一方可能遭受的损失,并应在合理的时间内提供不可抗力的证明。
-      </p>
-      <p className="bold-text">第13条 保密</p>
-      <p className="underline-bold">
-        用户保证在使用本平台过程中所获悉的属于本平台、本平台其他用户及其他第三方的无法自公开渠道获得的文件及资料(包括但不限于商业秘密、公司计划、运营活动、财务信息、技术信息、经营信息及其他商业秘密)予以保密。未经该资料和文件的原提供方同意,用户不得向第三方泄露该商业秘密的全部或者部分内容。但法律、法规、行政规章另有规定或者双方另有约定的除外。
-      </p>
-      <p className="bold-text">第14条 违约责任</p>
-      <p>
-        14.1
-        <span className="underline-bold">
-          如果您在使用本平台服务的过程中违反本协议约定、本平台其他规则、协议,或其他可适用的法律法规、国家政策、合同约定、平台规则或公告的,本平台有权要求您改正或直接采取一切必须要的措施以减轻或消除您的不当行为所带来的负面影响,并可根据违规情形严重情况要求您承担违约责任,具体措施包括但不限于:
-        </span>
-      </p>
-      <p className="underline-bold">(1)警告;</p>
-      <p className="underline-bold">(2)违规情形的平台内公示;</p>
-      <p className="underline-bold">(3)修改或屏蔽违规内容,包括但不限于将违规信息移除;</p>
-      <p className="underline-bold">(4)限制信息发布,限制平台其他功能的使用;</p>
-      <p className="underline-bold">(5)部分或全部扣除相关费用;</p>
-      <p className="underline-bold">(6)部分或全部扣除保证金(如有);</p>
-      <p className="underline-bold">(7)锁定用户账号,中断向您提供本平台服务;</p>
-      <p className="underline-bold">
-        (8)锁定您的账号,终止向您提供服务,和/或拒绝或限制您再行注册或使用本平台服务;
-      </p>
-      <p className="underline-bold">(9)要求您承担损害赔偿责任。</p>
-      <p>
-        14.2
-        <span className="underline-bold">
-          若您的行为给我们造成损失的(包括但不限于直接损失、名誉损失、第三方的罚款、索赔等),我们有权全额向您追偿,如您在本平台中有保证金等财产权益的,我们有权冻结。
-        </span>
-      </p>
-      <p>
-        14.3
-        <span className="underline-bold">
-          本协议终止后,除法律有明确规定外,本平台无义务向您或您指定的第三方披露您账户中的任何信息。本协议终止后,本平台仍享有下列权利:根据法律规定,继续保存您留存于本平台的的各类信息,具体的存储期限,详见《隐私政策》的相关规定;对于您过往的违约行为,本平台仍可依据本协议向您追究违约责任。
-        </span>
-      </p>
-      <p className="underline-bold">
-        14.4
-        本协议及本平台其他规则、协议项下所述“损失”或“赔偿责任”,均包括但不限于自身损失、向第三方支付的违约金、赔偿金及因实现权利而产生的律师费、取证费、诉讼/仲裁费、财产保全费等。
-      </p>
-      <p className="bold-text">第15条 纠纷解决方式 15.1</p>
-      <p>
-        本协议及其规则的有效性、履行和与本协议及其规则效力有关的所有事宜,将受中华人民共和国法律管辖,任何争议仅适用中华人民共和国法律。
-      </p>
-      <p>
-        15.2
-        本平台有权受理并调处用户之间因承接服务产生的纠纷。因本平台非司法机关,您完全理解并承认,本平台对证据的鉴别能力及对纠纷的处理能力有限,调节纠纷完全是基于用户委托,不保证处理结果符合用户的期望,本平台有权决定是否参与争议的调处。
-      </p>
-      <p>
-        15.3
-        凡因履行本协议及其规则发生的纠纷以及因使用本平台服务而产生的所有与本平台的纠纷,双方应先协商解决,协商不成的,提交深圳国际仲裁院依据其现行有效的仲裁规则进行仲裁。
-      </p>
-      <p>
-        15.4
-        用户已知悉并确认:对于因用户使用本平台争议引起的纠纷,仲裁机构可以通过手机短信等现代通讯方式送达法律文书;指定接收法律文书的手机号码为用户登录时输入的手机号码;仲裁机构可采取一种或多种送达方式送达法律文书,送达时间以上述送达方式中最先送达的为准;上述送达方式适用于各个仲裁阶段;用户保证其手机号码与电子邮箱准确、有效,如果提供的送达信息不准确,或者不及时告知变更后的送达信息,使法律文书无法送达或未及时送达,自行承担由此可能产生的法律后果。
-      </p>
-      <p className="bold-text">第16条 完整协议</p>
-      <p>
-        16.1
-        本协议由本协议条款与本平台公示的各项规则组成,相关名词可互相引用参照,如有不同理解,以本协议条款为准。
-      </p>
-      <p>
-        16.2
-        用户一旦确认对本协议理解和认同,即对本协议所有组成部分的内容理解并认同,一旦用户使用本平台提供的服务,即受本协议所有组成部分的约束。
-      </p>
-      <p className="bold-text">第17条 协议修改及解释</p>
-      <p>
-        17.1
-        本协议内容包括本协议条款及平台不时发布或组织订立的各类协议、平台规则和公告。所有签署内容均为本协议不可分割的组成部分,与本协议正文具有同等法律效力。
-      </p>
-      <p>17.2 本协议所有条款的标题仅为阅读方便,本身并无实际涵义,不能作为本协议涵义解释的依据。</p>
-      <p>17.3 本协议条款无论因何种原因部分无效或不可执行,其余条款仍有效,对双方具有约束力。</p>
-      <p>
-        17.4 修改与更新:
-        <span className="underline-bold">
-          要易云有权根据法律法规变化、维护平台秩序、保护消费者合法权益的需要,在必要时修改本协议(包括适时制定并发布其他政策、规则、公告声明),当您登录本平台时会自动弹出变更后的协议,若您同意变更后的内容,请点击“同意”按钮并继续登陆本平台使用平台服务,若您不同意变更后的内容,您将无法使用本平台服务,同时应及时与平台客服取得联系,本平台将根据情况审核确认给予您一定的时限完成您账户下尚未完结的事项,在已有事项全部完结后锁定您的账户,此期间您将无法继续开展新的业务。
-        </span>
-      </p>
-      <p className="underline-bold">
-        17.5
-        您使用本平台即视为您已阅读并同意受本协议的约束。要易云有权在必要时修改本协议条款。您可以在本平台的最新版本中查阅相关协议条款。本协议条款变更后,如果您继续使用本凭条,即视为您已接受修改后的协议。如果您不接受修改后的协议,应当停止使用本平台。
-      </p>
-      <p className="bold-text">第18条 通知与送达</p>
-      <p>
-        18.1
-        <span className="bold-text">有效联系方式</span>
-      </p>
-      <p className="underline-bold">
-        您在成为本平台用户并接受本平台服务时,您应该向本平台提供真实有效的联系方式(包括您的手机号码等),对于联系方式发生变更的,您有义务及时更新有关信息,并保持可被联系的状态。您在成为本平台用户时生成的用于登录本平台接收站内信、系统消息的用户账号,也作为您的有效联系方式。
-      </p>
-      <p className="underline-bold">
-        本平台将向您的上述联系方式的其中之一或其中若干向您送达各类通知,而此类通知的内容可能对您的权利义务产生重大的有利或不利影响,请您务必及时关注。
-      </p>
-      <p className="underline-bold">
-        您有权通过您登录时用的手机号码获取您感兴趣的商品广告信息、促销优惠等商业性信息;您如果不愿意接收此类信息,您有权通过本平台提供的相应的退订功能进行退订。
-      </p>
-      <p>
-        18.2
-        <span className="bold-text">通知与送达</span>
-      </p>
-      <p className="underline-bold">
-        本平台通过上述联系方式向您发出通知,其中以电子方式发出的书面通知,包括但不限于在本平台公告,向您提供的联系电话发送手机短信,向您的账号发送系统消息以及站内信息,在发送成功后即视为送达;以纸质载体发出的书面通知,按照提供联系地址交邮后的第五个自然日即视为送达。
-      </p>
-      <p className="underline-bold">
-        对于与本平台产生的任何纠纷,您同意司法机关(包括但不限于人民法院)可以通过手机短信、电子邮件等现代通讯方式或邮寄方式向您送达法律文书(包括但不限于诉讼文书)。您指定接收法律文书的手机号码、电子邮箱等联系方式为您在平台注册、更新时提供的手机号码、电子邮箱,司法机关向上述联系方式发出法律文书即视为送达。您指定的邮寄地址为您的法定联系地址或您提供的有效联系地址。
-      </p>
-      <p className="underline-bold">
-        您同意司法机关可采取以上一种或多种送达方式向您达法律文书,司法机关采取多种方式向您送达法律文书,送达时间以上述送达方式中最先送达的为准。
-      </p>
-      <p className="underline-bold">您同意上述送达方式适用于各个司法程序阶段。</p>
-      <p className="underline-bold">
-        你应当保证所提供的联系方式是准确、有效的,并进行实时更新。如果因提供的联系方式不确切,或不及时告知变更后的联系方式,使法律文书无法送达或未及时送达,由您自行承担由此可能产生的法律后果。
-      </p>
-      <p className="bold-text">第19条 附则</p>
-      <p>
-        19.1
-        <span className="underline-bold">
-          用户在登录使用前应认真阅读本协议的全部内容,如对协议有任何疑问,可向要易云咨询。本协议对登录使用、激活成功之后的用户产生法律约束力,届时用户不得以未阅读本协议内容或未获得本平台对问题的解答等为由,主张本协议无效或要求撤销本协议。
-        </span>
-      </p>
-      <p>19.2 要易云对本协议保留一切解释和修改的权利。</p>
-      <p className="bold-text">第20条 联系要易云</p>
-      <p>如果您有任何的疑问、投诉、意见和建议,欢迎您与要易云沟通反馈。要易云的联系方式见下:</p>
-      <p>客服邮箱:changtianchen@yaoyi.net</p>
-      <p>联系地址:深圳市福田区莲花街道福中社区福中三路1006号诺德金融中心17A</p>
-      <p>(正文完)</p>
+export default function UserAgreementModal({ title, show, onCancel }: UserAgreementModalProps) {
+  return (
+    <Modal
+      title={title}
+      open={show}
+      onCancel={onCancel}
+      footer={[
+        <Button key='back' onClick={onCancel}>
+          关 闭
+        </Button>
+      ]}>
+      <AgreementWrapper className='h-[500px] overflow-y-auto'>
+        <p className='title mb-[20px] bold-text text-center'>要易云平台用户协议</p>
+        <p className='text-right'>第1.0版</p>
+        <p className='underline-bold'>
+          《平台用户协议》系由您(或称“用户”)与深圳要易云科技服务有限公司(或称“要易云”)之间就您使用要易云平台的(以下或简称“本平台”)服务而签订。本平台依据《平台用户协议》的规定提供服务,该协议一旦生效对用户及要易云均产生法律效力。您在用户注册时,请认真阅读本协议,审阅并选择同意或不同意本协议。
+        </p>
+        <p className='underline-bold'>
+          在使用本平台服务之前,请您务必审慎阅读、充分理解本协议各项条款,特别是限制或免除责任条款、隐私保护政策、账号规则、法律适用和争议解决条款(包括管辖条款),以及其它以加粗加黑和/或加下划线等显著形式提示您注意的重要条款,请务必重点查阅。
+        </p>
+        <p className='underline-bold'>
+          要易云与您将通过数据电文形式订立本协议,若您不同意本协议,则您有充分且完全的权利立即停止成为本平台用户的注册程序。您通过网络页面点击“同意”的按钮并且该注册和/或实际使用平台服务的行为即视为您已阅读、充分理解并同意接受本协议的全部条款含义及相应的法律后果,不可撤销地同意与要易云以数据电文形式订立本协议并受本协议条款及其后续修订版本的全部约束,本协议在您点击“同意”并实际使用平台服务时成立生效。如果您对本协议有任何的疑问、投诉、意见和建议,欢迎您通过本协议所附联系方式与要易云沟通反馈。
+        </p>
+        <p className='bold-text'>第1条 基础约定事项</p>
+        <p>
+          1.1
+          协议范围:考虑到互联网服务以及商品频繁迭代更新等行业特点,为了更全面的界定您与本平台之间的权利义务,
+          <span className='underline-bold'>
+            本协议包括要易云根据法律法规规定制定的其他政策、规则、公告声明等(除非特有所指,以上合称为“本协议”)
+          </span>
+          ,您应当加以遵守。
+        </p>
+        <p>
+          1.2
+          服务范围:要易云可能通过不断丰富的功能界面向您提供本协议项下的平台服务,包括但不限于PC端网站以及其他形式。具体以要易云实时发布的服务功能界面范围为准。
+        </p>
+        <p>
+          1.3 主体地位:请您在接受本协议或者以其他要易云允许的方式实际使用本服务之前,
+          <span className='underline-bold'>
+            请确保您是适格的合同主体,并在清楚理解本协议内容及其法律后果后自行决定是否同意签署,而且就您所知,您不存在任何可能使得本协议无效或者对本协议项下义务的履行产生重大不利影响之情形。
+          </span>
+        </p>
+        <p className='bold-text'>第2条 用户资格</p>
+        <p>2.1 只有符合下列条件的机构才能申请成为本平台用户,可以使用本平台的服务:</p>
+        <p>
+          2.1.1
+          <span className='underline-bold'>非自然人用户:</span>
+        </p>
+        <p className='underline-bold'>
+          (1)根据中国法律、法规、行政规章合法设立并有效存续的机关、企事业单位、社团组织和其他组织;
+        </p>
+        <p className='underline-bold'>
+          (2)未被国家企业信用信息公示系统(www.gsxt.gov.cn)列入严重违法失信企业名录;
+        </p>
+        <p className='underline-bold'>(3)未被责令停业或处于破产状态的;</p>
+        <p className='underline-bold'>(4)财产及其股权未处于重组、接管、查封、扣押或冻结状态;</p>
+        <p className='underline-bold'>(5)近三年内不涉及重大诉讼、仲裁,不属于失信被执行人的;</p>
+        <p className='underline-bold'>(6)不存在违反法律法规规定的其他情形。</p>
+        <p>
+          2.2
+          <span className='underline-bold'>
+            不符合以上条件的法人或其他组织,将不被允许注册及认证成为本平台用户,且不得使用本平台相关服务,本平台一经发现,有权立即终止对该用户的服务,并追究其使用本平台服务的一切法律责任。
+          </span>
+        </p>
+        <p>
+          2.3
+          <span className='underline-bold'>
+            非自然人用户需通过授权自然人为管理员后进行操作及管理。
+          </span>
+        </p>
+        <p>2.4 用户需要提供真实有效的联系电话,并提供真实信息。</p>
+        <p>
+          2.5
+          本平台用户须承诺遵守法律法规、社会主义制度、国家利益、公民合法权益、公共秩序、社会道德风尚和信息真实性。
+        </p>
+        <p className='bold-text'>第3条 用户账号及资金账户规则</p>
+        <p>3.1 用户账号</p>
+        <p>
+          3.1.1
+          您通过平台或上级企业管理员获得要易云配置的用户账号,自首次使用该账号登录并使用平台服务之日起即成为平台用户(以下称“注册”),您在使用本平台服务时可能需要提供一些必要的信息,具体以本网站相关页面提示为准。您须保证所填写及所提供的资料真实、准确、完整,否则可能无法使用本平台服务,或在使用过程中受到限制,甚至影响您正常使用本平台特定功能。
+          <span className='underline-bold'>
+            对于因您提供的信息不真实、不准确或不完整导致的责任和损失由您自行承担。
+          </span>
+        </p>
+        <p>
+          3.1.2
+          <span className='underline-bold'>
+            您有责任自行负责保管账号的用户名和登录密码等信息,否则因该等事项引发的法律责任由用户自行承担(例如账户被盗用、转让等)。凡使用本平台服务登录账号和登录凭证的行为,本平台服务视为您本人的操作,操作所产生的电子信息记录均为本平台服务用户行为的有效凭据。
+          </span>
+        </p>
+        <p>3.2 账号锁定</p>
+        <p>
+          3.2.1 用户申请锁定情形
+          账号锁定指,在本平台执行某账户进行锁定后,该用户将不能登录该账户、进而不能使用平台项下全部功能。如果您需要锁定您的账号,您可以通过联系平台客服进行申请,经本平台审核同意后方可解除与本平台的协议关系并锁定您账号。当您的账户锁定后,您的个人信息和您在使用要易云服务期间提供或产生的信息我们将按照《要易云隐私政策》第三条第三款约定的存储期限进行保存。
+        </p>
+        <p>3.5.2 强制锁定情形</p>
+        <p className='underline-bold'>
+          要易云有权在您的账号符合如下情况时,锁定您的账号并停止服务:
+        </p>
+        <p className='underline-bold'>(1)连续180日未曾登录、使用本平台服务;</p>
+        <p className='underline-bold'>
+          (2)在用户违反本协议及相关规则规定时,本平台有权终止向该用户提供服务。但该用户在被本平台终止提供服务后,再一次直接或间接或以他人名义注册为本平台用户的,一经发现,本平台有权再次单方面终止为该用户提供服务;
+        </p>
+        <p className='underline-bold'>
+          (3)本平台发现用户注册资料中主要内容是虚假的,本平台有权随时终止为该用户提供服务;
+        </p>
+        <p className='underline-bold'>(4)本协议终止或更新时,用户未确认新的协议的;</p>
+        <p className='underline-bold'>
+          (5)其它本平台认为需终止服务的情况。
+          有以上强制锁定情形的,本平台可自行全权决定,在发出通知或不发出通知的情况下,随时停止或终止提供全部或部分服务。服务终止后,本平台没有义务为用户保留原账户中或与之相关的任何信息,或在出现强制锁定情形后转发任何未曾阅读或发送的信息给用户或第三方。
+        </p>
+        <p>3.5.3 账户锁定资金结算</p>
+        <p className='underline-bold'>
+          要易云将在您与平台其他用户清理、结算账户资产及纠纷争议后,为您提供账号锁定服务。在锁定账号之后,要易云将停止为您提供任何服务。但因强制锁定的,要易云有权在资金及纠纷处理完毕且锁定该账户,使其无法正常使用任何功能直至相关纠纷处理完毕。
+        </p>
+        <p>3.5.4 不论用户账户通过何种方式锁定,本平台仍保留下列权利:</p>
+        <p>
+          本平台有权在法律、法规、行政规章规定的时间内保留该用户的资料,包括但不限于以前的用户资料、操作记录等;保留期限详见《要易云隐私政策》。
+        </p>
+        <p className='bold-text'>第4条 平台交易规则</p>
+        <p>您承诺与本平台其他用户进行交易的过程中,应良好遵守如下基本交易规则:</p>
+        <p>4.1 了解服务信息</p>
+        <p>
+          您在通过本平台交易前,应详细了解并完全接受本平台服务内容及操作规则,方可进行操作或签署协议进行交易。
+        </p>
+        <p>4.2 数据信息保护</p>
+        <p className='underline-bold'>
+          关于您在向合作的第三方服务商发起的准入信息申报邀请时所收集到的信息,需用仅用于您对于其准入需求,不可用于外部分享或其他用途。
+        </p>
+        <p>4.3 其他规则说明</p>
+        <p>
+          4.3.1
+          为交易达成以及用户体验提升等目的,本平台可能会为您提供更为友好完善的交易规则服务,并引入第三方服务。第三方服务主要为您提供企业管理所查询到的数据信息和发票管理对应的发票数据信息。
+        </p>
+        <p>
+          4.3.2
+          您同意并知悉,本平台中的具体内容、功能和形式由平台根据实际情况按“现状”实时提供,要易云有权自行确定本平台服务的具体内容、功能和形式,有权自行决定增加、变更、中断和停止本平台具体的内容、功能和形式。具体以本平台实时呈现的服务内容、功能和形式为准。
+        </p>
+        <p>
+          4.3.3
+          <span className='underline-bold'>
+            由于众所周知的互联网技术因素等客观原因存在,本平台显示的信息可能会有一定的滞后性或差错,对此情形您知悉并理解。如因系统故障或平台重大误解导致显示信息明显不合理的,请勿进行下一步操作,如您明知显示信息明显不合理仍然提交交易文件的,将可能被视为恶意行为,本平台将有权取消本次交易;如果您在不知情的情况下进行了下一步操作,请第一时间联系本平台客服。
+          </span>
+        </p>
+        <p>
+          4.3.4
+          <span className='underline-bold'>
+            您同意在使用本平台服务的过程中遵守诚实信用原则,您不得实施恶意行为扰乱本平台正常秩序。如本平台根据您的登录账号下的操作记录及其他相关信息,发现您有(或可能有)恶意行为的,则本平台有权单方拒绝您的需求,或单方取消您的操作且不予退还保证金(如有),且无须由此向您承担法律责任。
+          </span>
+        </p>
+        <p className='bold-text'>第5条 用户的权利和义务</p>
+        <p>
+          5.1
+          用户有权根据本协议及本平台发布的相关规则,登录并利用本平台查询公开的企业相关信息、发票状态信息、收集合作第三方服务商准入信息等,并有权享受本平台提供的其他有关资讯及信息服务。
+        </p>
+        <p>
+          5.2
+          <span className='underline-bold'>
+            用户有权对部分自身账号相关信息进行查询、更新。用户有权对自身账号对应的用户信息、操作信息、汇总信息等相关信息进行查询,用户可以按其权限进行操作处理,例如管理员可按权限创建账户、查询第三方企业公开信息、发票状态信息、收集合作第三方服务商准入信息等。
+          </span>
+        </p>
+        <p>
+          5.3
+          <span className='underline-bold'>
+            用户须自行管理自己的用户账号和密码或者验证码,且须对在用户账号密码下发生的所有活动承担责任。用户有权根据需要更改登录密码。因用户的过错导致的任何损失由用户自行承担,该过错包括但不限于:不按照提示操作、不按照平台流程操作、遗忘或泄漏密码或者验证码、密码被他人破解及用户使用的计算机被他人侵入等情形。
+          </span>
+        </p>
+        <p>
+          5.4
+          用户应当向本平台提供真实准确的开通账户所需信息,包括但不限于真实姓名、手机号码(手机号码即为用户在本平台的账户)等。保证本平台可以通过上述联系方式与用户进行联系。同时,
+          <span className='underline-bold'>
+            用户也应当在相关资料实际变更时及时更新有关用户资料,因未及时更新资料导致的任何损失由用户自行承担。
+          </span>
+        </p>
+        <p>
+          5.5
+          <span className='underline-bold'>
+            用户在本平台的账号名称及发布的内容,不得有下列情形:
+          </span>
+        </p>
+        <p className='underline-bold'>(1)违反中国宪法或法律法规规定的;</p>
+        <p className='underline-bold'>
+          (2)危害国家安全,泄露国家秘密,颠覆国家政权,破坏国家统一的;
+        </p>
+        <p className='underline-bold'>(3)损害国家荣誉和利益的,损害公共利益的;</p>
+        <p className='underline-bold'>(4)煽动民族仇恨、民族歧视,破坏民族团结的;</p>
+        <p className='underline-bold'>(5)破坏国家宗教政策,宣扬邪教和封建迷信的;</p>
+        <p className='underline-bold'>(6)散布谣言,扰乱社会秩序,破坏社会稳定的;</p>
+        <p className='underline-bold'>
+          (7)散布淫秽、色情、赌博、暴力、凶杀、恐怖或者教唆犯罪的;
+        </p>
+        <p className='underline-bold'>(8)侮辱或者诽谤他人,侵害他人合法权益的;</p>
+        <p className='underline-bold'>(9)含有法律、行政法规禁止的其他内容的。</p>
+        <p>
+          5.6
+          <span className='underline-bold'>
+            用户在本平台上所实施行为,不得违反国家法律、法规、行政规章的规定,不得侵犯他人知识产权或其他合法权益的信息,不得违背社会公共利益或公共道德,不得违反本平台的相关规定。
+          </span>
+        </p>
+        <p>
+          5.7
+          <span className='underline-bold'>
+            用户承诺自己在使用本平台实施的所有行为遵守法律、法规、行政规章和本平台的相关规定以及各种社会公共利益或公共道德。如有违反导致任何法律后果的发生,用户将以自己的名义独立承担相应的法律责任。
+          </span>
+        </p>
+        <p>
+          5.8
+          用户在本平台网上操作过程中如与其他用户因产生纠纷,可以请求本平台从中予以协调处理。用户如发现其他用户有违法或违反本协议的行为,可以向本平台举报。
+        </p>
+        <p>
+          5.9
+          <span className='bold-text'>用户不得使用以下方式登录平台或破坏本平台所提供的服务:</span>
+        </p>
+        <p className='underline-bold'>
+          (1)以任何机器人软件、蜘蛛软件、爬虫软件、刷屏软件或其它自动方式访问或登录本平台;
+        </p>
+        <p className='underline-bold'>
+          (2)通过任何方式对本平台内部结构造成或可能造成不合理或不合比例的重大负荷的行为;
+        </p>
+        <p className='underline-bold'>
+          (3)通过任何方式干扰或试图干扰本平台的正常工作或本平台上进行的任何活动。
+        </p>
+        <p>
+          5.10
+          <span className='underline-bold'>
+            用户同意接收来自本平台的信息,包括但不限于平台内部弹窗、手机短信、手机消息通知等。
+          </span>
+        </p>
+        <p>
+          5.11
+          <span className='underline-bold'>
+            用户如为非自然人用户的,其在本平台实名认证的管理人员的所有操作视为用户行为,用户须对其管理人员的行为承担一切责任。用户应要求其管理的人员遵守本协议及本平台相关规则等要求,如用户实名认证的管理人员违反本协议及/或本平台规则等要求的,视为用户违反上述要求,用户应按照本协议要求承担相应责任。
+          </span>
+        </p>
+        <p className='bold-text'>第6条 本平台的权利和义务</p>
+        <p>6.1 本平台,是一个信息服务平台,为用户提供信息查询、信息采集服务。</p>
+        <p>
+          6.2
+          本平台有义务在现有技术水平的基础上努力确保网上交流平台的正常运行,尽力避免因平台原因导致服务中断,若因不可避免的原因中断服务,平台将把中断时间限制在最短时间内,保证用户网上交流活动的顺利进行。
+        </p>
+        <p>
+          6.3
+          用户因在本平台实施活动过程中与其他用户产生纠纷的,用户将纠纷告知本平台,或本平台知悉纠纷情况的,经审核后,本平台有权通过电子邮件及联系电话向纠纷双方了解纠纷情况,并将所了解的情况通过电子邮件互相通知对方;用户通过司法机关依照法定程序要求本平台提供相关资料,本平台将积极配合并提供有关资料。
+        </p>
+        <p>
+          6.4
+          <span className='underline-bold'>
+            因网上信息平台的特殊性,本平台不承担对所有用户的操作行为进行事先审查的义务,但如发生以下情形,本平台有权无需征得用户的同意限制用户的活动、向用户核实有关资料、发出警告通知、暂时中止、无限期中止及拒绝向该用户提供服务:
+          </span>
+        </p>
+        <p className='underline-bold'>(1)用户违反本协议的;</p>
+        <p className='underline-bold'>
+          (2)存在用户或其他第三方通知本平台,认为某个用户或具体交易事项存在违法或不当行为,并提供相关证据,而本平台无法联系到该用户核证或验证该用户向本平台提供的任何资料的;
+        </p>
+        <p className='underline-bold'>
+          (3)存在用户或其他第三方通知本平台,认为某个用户或具体事项存在违法或不当行为,并提供相关证据。本平台以普通非专业人员的知识水平标准对相关内容进行判别,可以认为这些内容或行为存在显著可能会对他方或本平台造成财务损失或承担法律责任的。
+        </p>
+        <p>
+          6.5
+          <span className='underline-bold'>
+            根据国家法律、法规、行政规章规定、本协议的内容和本平台所掌握的事实依据,可以认定该用户存在违法或违反本协议行为或在本平台交易平台上存在其他不当行为,本平台有权无需征得用户的同意在本平台上以网络发布形式公布对该用户匿名化处理后的违法行为,并有权随时作出对该用户终止服务提供等处理。
+          </span>
+        </p>
+        <p>
+          6.6
+          <span className='underline-bold'>
+            本平台有权在不通知用户的前提下,删除或采取其他限制性措施处理下列信息:包括但不限于违反法律法规、公共利益或可能严重损害本平台和其他用户合法利益的信息。
+          </span>
+        </p>
+        <p className='bold-text'>第7条 免责声明</p>
+        <p>当用户接受该协议时,用户应当明确了解并同意:</p>
+        <p>
+          7.1
+          <span className='underline-bold'>
+            要易云对于您自本平台而获得的所有要易云以外主体发布的信息、内容等(以下统称“信息”),除法律明确规定外,不保证真实、准确和完整性。如果任何单位或者个人通过上述“信息”而进行任何行为,您须自行甄别真伪和谨慎预防风险。无论何种原因,要易云不对任何非与要易云直接发生的交易或行为承担任何直接、间接、附带或衍生的损失和责任。
+          </span>
+        </p>
+        <p>
+          7.2
+          <span className='underline-bold'>
+            本平台是在现有技术基础上提供的服务,本平台不能随时预见到任何技术上的问题或其他困难。该等困难可能会导致数据损失或其他服务中断。本平台不保证以下事项:
+          </span>
+        </p>
+        <p className='underline-bold'>(1)本平台将符合所有用户的需求;</p>
+        <p className='underline-bold'>(2)本平台不受干扰、能够随时响应、安全可靠或免于出错;</p>
+        <p className='underline-bold'>(3)本服务使用权的取得结果是正确或可靠的。</p>
+        <p>
+          7.4
+          <span className='underline-bold'>
+            是否经由本平台下载或取得任何非由本平台提供的资料,由用户自行考虑、衡量并且自负风险,因下载任何资料而导致用户电脑系统的任何损坏或资料流失,用户应负完全责任。
+          </span>
+        </p>
+        <p>
+          7.5
+          <span className='underline-bold'>
+            用户经由本平台取得的建议和资讯,无论其形式或表现,均不构成要易云对用户通过本平台进行交易所作出的任何明示或暗示的保证或担保。
+          </span>
+        </p>
+        <p>
+          7.6
+          <span className='underline-bold'>
+            基于以下原因而造成的利润、商誉、使用、资料损失或其它无形损失,本平台不承担任何直接、间接、附带、特别、衍生性或惩罚性赔偿:
+          </span>
+        </p>
+        <p className='underline-bold'>(1)本平台的使用或无法使用;</p>
+        <p className='underline-bold'>(2)用户的传输或资料遭到未获授权的存取或变更;</p>
+        <p className='underline-bold'>(3)本平台中任何第三方之声明或行为;</p>
+        <p className='underline-bold'>(4)本平台其它相关事宜。</p>
+        <p>
+          7.7
+          <span className='underline-bold'>
+            本平台是为用户提供信息查询及收集服务的第三方平台,对于所查询及收集的信息的合法性、真实性等,本平台一律不负任何担保责任。
+          </span>
+        </p>
+        <p>
+          7.8
+          <span className='underline-bold'>
+            本平台提供的与其它互联网平台或资源的链接,用户可能会因此链接至其它运营商经营的平台,但不表示本平台与这些运营商有任何关系。其它运营商经营的平台均由各经营者自行负责,不属于本平台控制及负责范围之内。对于存在或来源于此类平台或资源的任何内容、广告、物品或其它资料,本平台亦不予保证或负责。因使用或依赖任何此类平台或资源发布的或经由此类平台或资源获得的任何内容、物品或服务所产生的任何损害或损失,本平台不负任何直接或间接的责任。
+          </span>
+        </p>
+        <p>
+          7.9
+          <span className='underline-bold'>就下列情形的发生,本平台不承担任何法律责任:</span>
+        </p>
+        <p className='underline-bold'>
+          (1)由于您将账号、密码告知他人或与他人共享注册账户,由此导致的任何个人信息的泄漏,或其他非因本平台原因导致的个人信息的泄漏;
+        </p>
+        <p className='underline-bold'>
+          (2)您在申请使用平台服务时因个人资料有任何变动,但未能及时更新,由此导致个人信息不真实而引起的问题及后果;
+        </p>
+        <p className='underline-bold'>
+          (3)本平台根据法律规定、政策要求或行政/司法机关的要求对外提供您的个人信息或交易记录;
+        </p>
+        <p className='underline-bold'>
+          (4)因不可抗力导致的任何后果。本用户协议中的“不可抗力”之定义,详见本协议第12.1条的相关表述。
+        </p>
+        <p className='bold-text'>第8条 知识产权</p>
+        <p>
+          8.1
+          <span className='underline-bold'>
+            本平台及本平台所使用的任何相关软件、程序、内容,包括但不限于作品、图片、档案、资料、平台构架、平台版面的安排、网页设计、经由本平台或广告商向用户呈现的广告或资讯,均由本平台或法定权利人依法享有相应的知识产权,包括但不限于著作权、商标权、专利权或其它专属权利等,受到相关法律的保护。未经本平台或权利人明示授权,用户不得擅自复制、传播、展示、镜像、上传、下载、使用、修改、出租、出借、出售、散布上述任何资料和资源,或根据上述资料和资源制作成任何种类产品,或者从事任何其它侵犯知识产权的行为,否则,平台有权追求侵权人的相关法律责任。
+          </span>
+        </p>
+        <p>
+          8.2
+          <span className='underline-bold'>
+            本平台授予用户不可转移及非专属的使用权,使用户可以通过单独计算机使用本平台的目标代码(以下简称“软件”),但用户不得且不得允许任何第三方复制、修改、创作衍生作品、进行还原工程、反向组译,或以其它方式破译或试图破译源代码,或出售、转让“软件”或对“软件”进行再授权,或以其它方式移转“软件”之任何权利。用户同意不以任何方式修改“软件”,或使用修改后的“软件”。
+          </span>
+        </p>
+        <p>
+          8.3
+          您在使用要易云服务时利用本平台及服务所发表上传的文字、图片、视频等原创信息的知识产权归您所有(或由第三方内容提供方和您另行约定)。
+          <span className='underline-bold'>
+            您在此明确授权:您上传到本平台服务器上的任何内容,您皆授予本平台在世界范围内基于商业或非商业目的,永久的、免费的以复制、翻译、发行、重新编排、节选等方式使用,或采用任何形式再传播或创作改变或衍生作品的权利。
+          </span>
+        </p>
+        <p>
+          8.4
+          <span className='underline-bold'>
+            基于对数据的合法加工而获得的具有竞争性的数据权益,除法律法规另有规定外,要易云享有独立的使用权益而无须获得您的同意。
+          </span>
+        </p>
+        <p>
+          8.5
+          <span className='underline-bold'>
+            在未得到著作权人的授权时,您不得将他人的作品全部或部分复制发表到本平台系统。如您上传的内容侵犯了第三方的著作权或其他权利,本平台将不承担任何法律责任,所有后果由您自行承担。如果第三方提出关于著作权的异议,本平台有权根据实际情况删除相关的内容,并有权追究您的法律责任。因您的侵权行为给本平台或任何第三方造成损失的,您应负责全部赔偿责任。
+          </span>
+        </p>
+        <p>
+          8.6
+          关于信息内容的投诉或举报。若您在使用平台过程中,不慎受到合法权益的侵犯,您有权通知要易云采取必要措施进行处置。若您在使用平台过程中,发现存在违法违规或违反本服务相关规则的情况,您也有权向要易云发起举报,要易云亦会及时采取必要措施(删除、屏蔽、断开链接或限制使用功能等)进行处置。
+        </p>
+        <p>
+          8.7
+          <span className='underline-bold'>
+            内容维权授权。在法律法规允许的范围内,您同意并授权要易云就侵犯您合法权益的行为(包括但不限于私自复制、使用、编辑、抄袭等行为)采取任何形式的法律行动,包括但不限于投诉、诉讼等必要的维权措施。
+          </span>
+        </p>
+        <p>
+          8.8
+          <span className='underline-bold'>用户不得经由非本平台所提供的界面使用本平台。</span>
+        </p>
+        <p className='bold-text'>第9条 用户行为规范</p>
+        <p>
+          9.1
+          <span className='underline-bold'>
+            您保证合理地使用本平台服务,并接受本协议和要易云适时制定并发布的其他政策、规则、公告声明。
+          </span>
+        </p>
+        <p>
+          9.2
+          <span className='underline-bold'>
+            行为禁止:您可在本协议约定的范围内使用本平台及服务,您不得利用本平台从事以下行为:
+          </span>
+        </p>
+        <p className='underline-bold'>(1)超出授权或恶意使用平台服务;</p>
+        <p className='underline-bold'>
+          (2)利用本平台发表、传送、传播、储存危害国家安全、国家统一、社会稳定的内容,或侮辱诽谤、色情、暴力、引起他人不安及任何违反国家法律法规政策的内容,或者设置含有上述内容的网名、角色名,发布、传送、传播违法广告信息、营销信息及垃圾信息等;
+        </p>
+        <p className='underline-bold'>
+          (3)利用本平台侵害他人知识产权、肖像权、隐私权、名誉权、个人信息等合法权利或权益;
+        </p>
+        <p className='underline-bold'>(4)恶意虚构或协助虚构事实、评价等信息或数据;</p>
+        <p className='underline-bold'>
+          (5)进行任何危害计算机网络安全的行为,包括但不限于:使用未经许可的数据或进入未经许可的服务器/账户;未经允许进入公众计算机网络或者他人计算机系统并删除、修改、增加存储信息;未经许可,企图探查、扫描、测试本“软件”系统或网络的弱点或其它实施破坏网络安全的行为;企图干涉、破坏本“软件”系统或网站的正常运行,故意传播恶意程序或病毒以及其他破坏干扰正常网络信息服务的行为;伪造TCP/IP数据包名称或部分名称;利用本平台及服务上传任何病毒、木马,或者蠕虫等危害网络健康的内容;
+        </p>
+        <p className='underline-bold'>
+          (6)对本平台进行反向工程、反向编译或反向汇编或以其它方式企图发现平台的源代码和算法,未经许可修改、禁用软件的任何功能或创建基于软件的衍生作品。去除本平台或文档上的任何所有权声明或标签,或将其他软件与本平台合并;
+        </p>
+        <p className='underline-bold'>
+          (7)进行任何破坏要易云提供服务公平性或者其他影响应用正常秩序的行为,如主动或被动刷分、合伙作弊、使用外挂或者其他的作弊软件、利用BUG(又叫“漏洞”或者“缺陷”)来获得不正当的非法利益,或者利用互联网或其他方式将外挂、作弊软件、BUG公之于众;
+        </p>
+        <p className='underline-bold'>
+          (8)从事其他法律法规、政策及公序良俗、社会公德禁止的行为以及侵犯其他个人、公司、社会团体、组织的合法权益的行为。
+        </p>
+        <p className='underline-bold'>
+          9.3
+          信息内容规范:为了营造良好网络生态,保障公民、法人和其他组织的合法权益,维护国家安全和公共利益,要易云将根据法律、行政法规,营造清朗的网络空间,并开展弘扬正能量、处置违法和不良信息等相关活动。
+        </p>
+        <p>
+          9.3.1
+          <span className='underline-bold'>您不得制作、复制、发布含有下列内容的违法信息:</span>
+        </p>
+        <p className='underline-bold'>(1)反对中国宪法所确定的基本原则的;</p>
+        <p className='underline-bold'>
+          (2)危害国家安全,泄露国家秘密,颠覆国家政权,破坏国家统一的;
+        </p>
+        <p className='underline-bold'>(3)损害国家荣誉和利益的;</p>
+        <p className='underline-bold'>
+          (4)歪曲、丑化、亵渎、否定英雄烈士事迹和精神,以侮辱、诽谤或者其他方式侵害英雄烈士的姓名、肖像、名誉、荣誉的;
+        </p>
+        <p className='underline-bold'>
+          (5)宣扬恐怖主义、极端主义或者煽动实施恐怖活动、极端主义活动的;
+        </p>
+        <p className='underline-bold'>(6)煽动民族仇恨、民族歧视,破坏民族团结的;</p>
+        <p className='underline-bold'>(7)破坏国家宗教政策,宣扬邪教和封建迷信的;</p>
+        <p className='underline-bold'>(8)散布谣言,扰乱经济秩序和社会秩序的;</p>
+        <p className='underline-bold'>
+          (9)散布淫秽、色情、赌博、暴力、凶杀、恐怖或者教唆犯罪的;
+        </p>
+        <p className='underline-bold'>
+          (10)侮辱或者诽谤他人,侵害他人名誉、隐私和其他合法权益的;
+        </p>
+        <p className='underline-bold'>(11)法律、行政法规禁止的其他内容。</p>
+        <p>
+          9.3.2
+          <span className='underline-bold'>
+            要易云也将依法采取措施,防范和抵制制作、复制、发布含有下列内容的不良信息:
+          </span>
+        </p>
+        <p className='underline-bold'>(1)使用夸张标题,内容与标题严重不符的;</p>
+        <p className='underline-bold'>(2)炒作绯闻、丑闻、劣迹等的;</p>
+        <p className='underline-bold'>(3)不当评述自然灾害、重大事故等灾难的;</p>
+        <p className='underline-bold'>(4)带有性暗示、性挑逗等易使人产生性联想的;</p>
+        <p className='underline-bold'>(5)展现血腥、惊悚、残忍等致人身心不适的;</p>
+        <p className='underline-bold'>(6)煽动人群歧视、地域歧视等的;</p>
+        <p className='underline-bold'>(7)宣扬低俗、庸俗、媚俗内容的;</p>
+        <p className='underline-bold'>
+          (8)可能引发未成年人模仿不安全行为和违反社会公德行为、诱导未成年人不良嗜好等的;
+        </p>
+        <p className='underline-bold'>(9)其他对网络生态造成不良影响的内容。</p>
+        <p>
+          9.4
+          <span className='underline-bold'>信息内容的使用规范</span>
+        </p>
+        <p className='underline-bold'>
+          未经要易云书面许可,您不得自行或授权、允许、协助任何第三人对本平台中信息内容进行如下行为:
+        </p>
+        <p className='underline-bold'>
+          (1)复制、读取、采用本平台的信息内容,用于任何形式的商业用途;
+        </p>
+        <p className='underline-bold'>
+          (2)擅自编辑、整理、编排平台及相关服务的信息内容后在平台及相关服务的源页面以外的渠道进行展示;
+        </p>
+        <p className='underline-bold'>
+          (3)采用不正当方式,自行或协助第三人对本平台及相关服务的信息内容产生流量、阅读量或交易引导、转移、劫持等不利影响。
+        </p>
+        <p className='bold-text'>第10条 个人信息保护与隐私政策</p>
+        <p>
+          10.1
+          尊重用户隐私并保护您的个人信息安全是要易云的一贯态度,平台将会采取合理的措施保护您的个人信息与隐私。
+        </p>
+        <p>10.2 信息使用</p>
+        <p className='underline-bold'>
+          (1)要易云有权在遵守法律法规的前提下,以明示的方式获取、使用、储存和分享您的个人信息。要易云不会在未经您授权时,公开、编辑或透露您的个人信息及您保存在要易云的非公开内容。您同意并保证:本平台有权依法收集使用您的相关信息。
+        </p>
+        <p>(2)本平台不会向任何人出售或出借用户的个人或法人信息,除非事先得到用户得许可;</p>
+        <p className='underline-bold'>
+          (3)本平台亦不允许任何第三方以任何手段收集、编辑、出售或者无偿传播用户的个人或法人信息。任何用户如从事上述活动,一经发现,本平台有权立即终止与该用户的服务协议,查封其账号。
+        </p>
+        <p className='underline-bold'>
+          (4)您同意,要易云有权通过cookie等技术收集您的使用、行为信息,并在经过数据脱敏使之不再指向、关联到您个人的身份信息时,自由使用脱敏后的纯商业数据。当然,您也可根据自己的偏好删除Cookie,但如果您这么做,则需要在每一次访问要易云的网站时亲自更改用户设置。目前删除Cookie的一般路径是浏览器:“设置-清除数据”,或者将手机系统还原/清除。
+        </p>
+        <p>10.3 信息披露</p>
+        <p className='underline-bold'>用户的信息将在下述情况下部分或全部被披露:</p>
+        <p className='underline-bold'>(1)经用户同意,向第三方披露;</p>
+        <p className='underline-bold'>
+          (2)根据法律的有关规定,或者行政、司法机关的要求,向第三方或者行政、司法机关披露;
+        </p>
+        <p className='underline-bold'>
+          (3)若用户出现违反中国有关法律或者平台规定的情况,需要向第三方披露;
+        </p>
+        <p className='underline-bold'>
+          (4)为保护您、本平台的其他用户或本平台的关联方的合法权益,本平台可能将您的个人信息用于预防、发现、调查以下事项:欺诈、危害安全、违法或违反与本平台或关联方协议、政策或规则的行为;
+        </p>
+        <p className='underline-bold'>
+          (5)在遵循隐私权保护以及其他相应的保密安全措施的前提下,允许本平台将您的个人信息提供给相关合作方,让其根据本平台指令处理相关信息;
+        </p>
+        <p className='underline-bold'>(6)其它本平台根据法律法规认为合适的披露。</p>
+        <p className='underline-bold'>若您不同意以上内容,请立即停止使用要易云平台服务。</p>
+        <p>10.4 信息安全</p>
+        <p>
+          (1)要易云将运用各种安全技术和程序建立完善的管理制度来保护您的个人信息及隐私安全,以免遭受未经授权的访问、使用或披露。
+        </p>
+        <p>
+          (2)如果用户发现自己的个人或法人信息泄密,尤其是用户账户或密码发生泄露,请用户立即联络本平台客服,以便要易云采取相应措施。
+        </p>
+        <p>
+          10.5
+          <span className='underline-bold'>
+            在遵守本协议项下特别约定的个人信息保护与隐私政策外,要易云希望您认真并完整阅读要易云特别针对平台而制定并适时发布的《隐私政策》,这将更有助于保障您的个人信息。
+          </span>
+        </p>
+        <p className='bold-text'>第11条 信息或广告推送</p>
+        <p>
+          您同意在接受要易云提供服务的同时,允许要易云在遵守法律法规的前提下自行或由第三方广告商通过平台内弹窗、手机系统弹窗、手机短信、电子邮件等方式向您发送、展示广告、推广或宣传信息(包括商业与非商业信息)。如您对发送、推荐的广告或信息不感兴趣,您可以基于要易云提供的相关技术选项,控制系统向您展示或不展示/减少展示相关类型的广告或信息。
+        </p>
+        <p className='bold-text'>第12条 不可抗力</p>
+        <p>
+          12.1
+          <span className='underline-bold'>
+            鉴于互联网服务的特殊性,您理解并同意要易云在因不可抗力或者其他意外事件导致平台及服务障碍不能正常运作、服务中断或延迟等情形下,对您在本平台所遭受的损失(包括但不限于财产、收益、数据资料等方面的损失或其它无形损失)无需承担责任。同时,因不可抗力使得本协议的履行不可能、不必要或者无意义的,任何一方均可单方解除本协议且无需承担违约责任。
+          </span>
+        </p>
+        <p>
+          本协议所称之不可抗力意指不能预见、不能避免并不能克服的客观情况,包括但不限于战争、台风、水灾、火灾、雷击或地震、罢工、暴动、法定疾病、停电、计算机病毒、木马、其他恶意程序、黑客攻击、网络病毒、电信部门技术管制、网络运营公司技术调整或故障、系统维护、政府行为或任何其它自然或人为造成的灾难等客观情况。
+        </p>
+        <p>
+          12.2
+          若任何一方由于不可抗力无法履行协议,该方须及时通知另一方以便减轻另一方可能遭受的损失,并应在合理的时间内提供不可抗力的证明。
+        </p>
+        <p className='bold-text'>第13条 保密</p>
+        <p className='underline-bold'>
+          用户保证在使用本平台过程中所获悉的属于本平台、本平台其他用户及其他第三方的无法自公开渠道获得的文件及资料(包括但不限于商业秘密、公司计划、运营活动、财务信息、技术信息、经营信息及其他商业秘密)予以保密。未经该资料和文件的原提供方同意,用户不得向第三方泄露该商业秘密的全部或者部分内容。但法律、法规、行政规章另有规定或者双方另有约定的除外。
+        </p>
+        <p className='bold-text'>第14条 违约责任</p>
+        <p>
+          14.1
+          <span className='underline-bold'>
+            如果您在使用本平台服务的过程中违反本协议约定、本平台其他规则、协议,或其他可适用的法律法规、国家政策、合同约定、平台规则或公告的,本平台有权要求您改正或直接采取一切必须要的措施以减轻或消除您的不当行为所带来的负面影响,并可根据违规情形严重情况要求您承担违约责任,具体措施包括但不限于:
+          </span>
+        </p>
+        <p className='underline-bold'>(1)警告;</p>
+        <p className='underline-bold'>(2)违规情形的平台内公示;</p>
+        <p className='underline-bold'>(3)修改或屏蔽违规内容,包括但不限于将违规信息移除;</p>
+        <p className='underline-bold'>(4)限制信息发布,限制平台其他功能的使用;</p>
+        <p className='underline-bold'>(5)部分或全部扣除相关费用;</p>
+        <p className='underline-bold'>(6)部分或全部扣除保证金(如有);</p>
+        <p className='underline-bold'>(7)锁定用户账号,中断向您提供本平台服务;</p>
+        <p className='underline-bold'>
+          (8)锁定您的账号,终止向您提供服务,和/或拒绝或限制您再行注册或使用本平台服务;
+        </p>
+        <p className='underline-bold'>(9)要求您承担损害赔偿责任。</p>
+        <p>
+          14.2
+          <span className='underline-bold'>
+            若您的行为给我们造成损失的(包括但不限于直接损失、名誉损失、第三方的罚款、索赔等),我们有权全额向您追偿,如您在本平台中有保证金等财产权益的,我们有权冻结。
+          </span>
+        </p>
+        <p>
+          14.3
+          <span className='underline-bold'>
+            本协议终止后,除法律有明确规定外,本平台无义务向您或您指定的第三方披露您账户中的任何信息。本协议终止后,本平台仍享有下列权利:根据法律规定,继续保存您留存于本平台的的各类信息,具体的存储期限,详见《隐私政策》的相关规定;对于您过往的违约行为,本平台仍可依据本协议向您追究违约责任。
+          </span>
+        </p>
+        <p className='underline-bold'>
+          14.4
+          本协议及本平台其他规则、协议项下所述“损失”或“赔偿责任”,均包括但不限于自身损失、向第三方支付的违约金、赔偿金及因实现权利而产生的律师费、取证费、诉讼/仲裁费、财产保全费等。
+        </p>
+        <p className='bold-text'>第15条 纠纷解决方式 15.1</p>
+        <p>
+          本协议及其规则的有效性、履行和与本协议及其规则效力有关的所有事宜,将受中华人民共和国法律管辖,任何争议仅适用中华人民共和国法律。
+        </p>
+        <p>
+          15.2
+          本平台有权受理并调处用户之间因承接服务产生的纠纷。因本平台非司法机关,您完全理解并承认,本平台对证据的鉴别能力及对纠纷的处理能力有限,调节纠纷完全是基于用户委托,不保证处理结果符合用户的期望,本平台有权决定是否参与争议的调处。
+        </p>
+        <p>
+          15.3
+          凡因履行本协议及其规则发生的纠纷以及因使用本平台服务而产生的所有与本平台的纠纷,双方应先协商解决,协商不成的,提交深圳国际仲裁院依据其现行有效的仲裁规则进行仲裁。
+        </p>
+        <p>
+          15.4
+          用户已知悉并确认:对于因用户使用本平台争议引起的纠纷,仲裁机构可以通过手机短信等现代通讯方式送达法律文书;指定接收法律文书的手机号码为用户登录时输入的手机号码;仲裁机构可采取一种或多种送达方式送达法律文书,送达时间以上述送达方式中最先送达的为准;上述送达方式适用于各个仲裁阶段;用户保证其手机号码与电子邮箱准确、有效,如果提供的送达信息不准确,或者不及时告知变更后的送达信息,使法律文书无法送达或未及时送达,自行承担由此可能产生的法律后果。
+        </p>
+        <p className='bold-text'>第16条 完整协议</p>
+        <p>
+          16.1
+          本协议由本协议条款与本平台公示的各项规则组成,相关名词可互相引用参照,如有不同理解,以本协议条款为准。
+        </p>
+        <p>
+          16.2
+          用户一旦确认对本协议理解和认同,即对本协议所有组成部分的内容理解并认同,一旦用户使用本平台提供的服务,即受本协议所有组成部分的约束。
+        </p>
+        <p className='bold-text'>第17条 协议修改及解释</p>
+        <p>
+          17.1
+          本协议内容包括本协议条款及平台不时发布或组织订立的各类协议、平台规则和公告。所有签署内容均为本协议不可分割的组成部分,与本协议正文具有同等法律效力。
+        </p>
+        <p>
+          17.2 本协议所有条款的标题仅为阅读方便,本身并无实际涵义,不能作为本协议涵义解释的依据。
+        </p>
+        <p>17.3 本协议条款无论因何种原因部分无效或不可执行,其余条款仍有效,对双方具有约束力。</p>
+        <p>
+          17.4 修改与更新:
+          <span className='underline-bold'>
+            要易云有权根据法律法规变化、维护平台秩序、保护消费者合法权益的需要,在必要时修改本协议(包括适时制定并发布其他政策、规则、公告声明),当您登录本平台时会自动弹出变更后的协议,若您同意变更后的内容,请点击“同意”按钮并继续登陆本平台使用平台服务,若您不同意变更后的内容,您将无法使用本平台服务,同时应及时与平台客服取得联系,本平台将根据情况审核确认给予您一定的时限完成您账户下尚未完结的事项,在已有事项全部完结后锁定您的账户,此期间您将无法继续开展新的业务。
+          </span>
+        </p>
+        <p className='underline-bold'>
+          17.5
+          您使用本平台即视为您已阅读并同意受本协议的约束。要易云有权在必要时修改本协议条款。您可以在本平台的最新版本中查阅相关协议条款。本协议条款变更后,如果您继续使用本凭条,即视为您已接受修改后的协议。如果您不接受修改后的协议,应当停止使用本平台。
+        </p>
+        <p className='bold-text'>第18条 通知与送达</p>
+        <p>
+          18.1
+          <span className='bold-text'>有效联系方式</span>
+        </p>
+        <p className='underline-bold'>
+          您在成为本平台用户并接受本平台服务时,您应该向本平台提供真实有效的联系方式(包括您的手机号码等),对于联系方式发生变更的,您有义务及时更新有关信息,并保持可被联系的状态。您在成为本平台用户时生成的用于登录本平台接收站内信、系统消息的用户账号,也作为您的有效联系方式。
+        </p>
+        <p className='underline-bold'>
+          本平台将向您的上述联系方式的其中之一或其中若干向您送达各类通知,而此类通知的内容可能对您的权利义务产生重大的有利或不利影响,请您务必及时关注。
+        </p>
+        <p className='underline-bold'>
+          您有权通过您登录时用的手机号码获取您感兴趣的商品广告信息、促销优惠等商业性信息;您如果不愿意接收此类信息,您有权通过本平台提供的相应的退订功能进行退订。
+        </p>
+        <p>
+          18.2
+          <span className='bold-text'>通知与送达</span>
+        </p>
+        <p className='underline-bold'>
+          本平台通过上述联系方式向您发出通知,其中以电子方式发出的书面通知,包括但不限于在本平台公告,向您提供的联系电话发送手机短信,向您的账号发送系统消息以及站内信息,在发送成功后即视为送达;以纸质载体发出的书面通知,按照提供联系地址交邮后的第五个自然日即视为送达。
+        </p>
+        <p className='underline-bold'>
+          对于与本平台产生的任何纠纷,您同意司法机关(包括但不限于人民法院)可以通过手机短信、电子邮件等现代通讯方式或邮寄方式向您送达法律文书(包括但不限于诉讼文书)。您指定接收法律文书的手机号码、电子邮箱等联系方式为您在平台注册、更新时提供的手机号码、电子邮箱,司法机关向上述联系方式发出法律文书即视为送达。您指定的邮寄地址为您的法定联系地址或您提供的有效联系地址。
+        </p>
+        <p className='underline-bold'>
+          您同意司法机关可采取以上一种或多种送达方式向您达法律文书,司法机关采取多种方式向您送达法律文书,送达时间以上述送达方式中最先送达的为准。
+        </p>
+        <p className='underline-bold'>您同意上述送达方式适用于各个司法程序阶段。</p>
+        <p className='underline-bold'>
+          你应当保证所提供的联系方式是准确、有效的,并进行实时更新。如果因提供的联系方式不确切,或不及时告知变更后的联系方式,使法律文书无法送达或未及时送达,由您自行承担由此可能产生的法律后果。
+        </p>
+        <p className='bold-text'>第19条 附则</p>
+        <p>
+          19.1
+          <span className='underline-bold'>
+            用户在登录使用前应认真阅读本协议的全部内容,如对协议有任何疑问,可向要易云咨询。本协议对登录使用、激活成功之后的用户产生法律约束力,届时用户不得以未阅读本协议内容或未获得本平台对问题的解答等为由,主张本协议无效或要求撤销本协议。
+          </span>
+        </p>
+        <p>19.2 要易云对本协议保留一切解释和修改的权利。</p>
+        <p className='bold-text'>第20条 联系要易云</p>
+        <p>如果您有任何的疑问、投诉、意见和建议,欢迎您与要易云沟通反馈。要易云的联系方式见下:</p>
+        <p>客服邮箱:changtianchen@yaoyi.net</p>
+        <p>联系地址:深圳市福田区莲花街道福中社区福中三路1006号诺德金融中心17A</p>
+        <p>(正文完)</p>
       </AgreementWrapper>
-		</Modal>
-	);
+    </Modal>
+  )
 }
 
 const AgreementWrapper = styled.div`
-	p:not(.title) {
+  p:not(.title) {
     text-indent: 2em;
     line-height: 1.5em;
   }

+ 5 - 0
src/pages/system/auth/menu/index.tsx

@@ -0,0 +1,5 @@
+import React from 'react'
+
+export const index = () => {
+  return <div>index</div>
+}

+ 235 - 0
src/pages/system/auth/user/components/AddModal.tsx

@@ -0,0 +1,235 @@
+import { memo, useEffect, useState } from 'react'
+import { LockFlag } from '#/enum'
+import { toast } from 'sonner'
+import { fetchTree } from '@/api/services/dept'
+import { fetchPostList } from '@/api/services/post'
+import { deptRoleList } from '@/api/services/role'
+import { addUser } from '@/api/services/user'
+import { Col, Form, Input, Modal, Radio, Row, Select, TreeSelect } from 'antd'
+
+export type AddModalProps = {
+  show: boolean
+  onCancel: VoidFunction
+  getList: VoidFunction
+}
+
+const formData: Partial<API.AddUserParams> = {}
+
+export const AddModal = memo(({ show = false, onCancel, getList }: AddModalProps) => {
+  // 查询部门树
+  const [treeDeptData, setTreeDeptData] = useState<API.DeptTreeResult>([])
+  const getDeptTree = async () => {
+    const res = await fetchTree()
+    if (res?.code === '2000') {
+      setTreeDeptData(res.data)
+    }
+  }
+  // 查询岗位列表
+  const [postOptions, setPostOptions] = useState<API.PostListResult>([])
+  const getPostList = async () => {
+    const res = await fetchPostList()
+    if (res?.code === '2000') {
+      setPostOptions(res.data)
+    }
+  }
+  // 查询角色列表
+  const [rolesOptions, setRolesOptions] = useState<API.RoleListResult>([])
+  const getRoleList = async () => {
+    const res = await deptRoleList()
+    if (res?.code === '2000') {
+      setRolesOptions(res.data)
+    }
+  }
+  useEffect(() => {
+    getDeptTree()
+    getPostList()
+    getRoleList()
+  }, [])
+
+  const [form] = Form.useForm()
+  const onOkFn = () => {
+    form.validateFields().then((values) => {
+      console.log(values)
+      addUser(values as API.AddUserParams).then((res) => {
+        if (res) {
+          console.log(res)
+          getList()
+          onCancelFn()
+          toast.success('创建成功', {
+            position: 'top-center'
+          })
+        }
+      })
+    })
+  }
+  const onCancelFn = () => {
+    form.resetFields()
+    onCancel()
+  }
+  return (
+    <Modal title='新增' open={show} width={900} onOk={onOkFn} onCancel={onCancelFn}>
+      {/* 表单名称,会作为表单字段 id 前缀使用,不加可能会造成表单id冲突 */}
+      <Form name='addUserModalForm' initialValues={formData} form={form} layout='vertical'>
+        <Row gutter={16}>
+          <Col span={12}>
+            <Form.Item<API.AddUserParams>
+              label='用户名'
+              name='username'
+              rules={[
+                {
+                  required: true,
+                  message: '请输入用户名'
+                },
+                {
+                  min: 3,
+                  max: 20,
+                  message: '长度在 3 到 20 个字符'
+                }
+              ]}>
+              <Input />
+            </Form.Item>
+          </Col>
+          <Col span={12}>
+            <Form.Item<API.AddUserParams>
+              label='密码'
+              name='password'
+              rules={[
+                {
+                  required: true,
+                  message: '请输入密码'
+                }
+              ]}>
+              <Input />
+            </Form.Item>
+          </Col>
+        </Row>
+        <Row gutter={16}>
+          <Col span={12}>
+            <Form.Item<API.AddUserParams>
+              label='真实姓名'
+              name='realName'
+              rules={[
+                {
+                  required: true,
+                  message: '请输入真实姓名'
+                }
+              ]}>
+              <Input />
+            </Form.Item>
+          </Col>
+          <Col span={12}>
+            <Form.Item<API.AddUserParams>
+              label='所属部门'
+              name='deptId'
+              rules={[
+                {
+                  required: true,
+                  message: '请选择部门'
+                }
+              ]}>
+              <TreeSelect
+                fieldNames={{
+                  label: 'name',
+                  value: 'id',
+                  children: 'children'
+                }}
+                allowClear
+                treeData={treeDeptData}
+              />
+            </Form.Item>
+          </Col>
+        </Row>
+        <Row gutter={16}>
+          <Col span={12}>
+            <Form.Item<API.AddUserParams>
+              label='手机号'
+              name='phone'
+              rules={[
+                {
+                  required: true,
+                  message: '请输入手机号'
+                }
+              ]}>
+              <Input />
+            </Form.Item>
+          </Col>
+          <Col span={12}>
+            <Form.Item<API.AddUserParams>
+              label='邮箱'
+              name='email'
+              rules={[
+                {
+                  required: true,
+                  message: '请输入邮箱'
+                }
+              ]}>
+              <Input />
+            </Form.Item>
+          </Col>
+        </Row>
+        <Row gutter={16}>
+          <Col span={12}>
+            <Form.Item<API.AddUserParams>
+              label='岗位'
+              name='postIds'
+              rules={[
+                {
+                  required: true,
+                  message: '请选择岗位'
+                }
+              ]}>
+              <Select
+                mode='multiple'
+                fieldNames={{
+                  label: 'postName',
+                  value: 'postId'
+                }}
+                allowClear
+                options={postOptions}
+              />
+            </Form.Item>
+          </Col>
+          <Col span={12}>
+            <Form.Item<API.AddUserParams>
+              label='角色'
+              name='roleIds'
+              rules={[
+                {
+                  required: true,
+                  message: '请选择角色'
+                }
+              ]}>
+              <Select
+                mode='multiple'
+                fieldNames={{
+                  label: 'roleName',
+                  value: 'roleId'
+                }}
+                allowClear
+                options={rolesOptions}
+              />
+            </Form.Item>
+          </Col>
+        </Row>
+        <Row gutter={16}>
+          <Col span={12}>
+            <Form.Item<API.AddUserParams>
+              label='状态'
+              name='lockFlag'
+              rules={[
+                {
+                  required: true,
+                  message: '请选择状态'
+                }
+              ]}>
+              <Radio.Group optionType='button' buttonStyle='solid'>
+                <Radio value={LockFlag.OK}>有效</Radio>
+                <Radio value={LockFlag.LOCKED}>锁定</Radio>
+              </Radio.Group>
+            </Form.Item>
+          </Col>
+        </Row>
+      </Form>
+    </Modal>
+  )
+})

+ 149 - 0
src/pages/system/auth/user/components/DeptTreeSelect.tsx

@@ -0,0 +1,149 @@
+import React, { Key, memo, useEffect, useMemo, useState } from 'react'
+import { fetchTree } from '@/api/services/dept'
+import { Input, Tree } from 'antd'
+import type { TreeDataNode } from 'antd'
+
+interface DeptTreeSelectProps {
+  onSelect: (id: Key[]) => void
+}
+
+const { Search } = Input
+
+let defaultData: TreeDataNode[] = []
+
+const dataList: { key: React.Key; title: string }[] = []
+const generateList = (data: TreeDataNode[]) => {
+  for (let i = 0; i < data.length; i++) {
+    const node = data[i]
+    const { key, title } = node
+    dataList.push({ key, title: title as string })
+    if (node.children) {
+      generateList(node.children)
+    }
+  }
+}
+
+const getParentKey = (keys: React.Key[], tree: TreeDataNode[]): React.Key[] => {
+  const parentKey: React.Key[] = []
+  const getMatchParentKeyInTree = (key: React.Key, tree: TreeDataNode[]) => {
+    tree.forEach((treeNode) => {
+      if (treeNode.children && treeNode.children.length) {
+        if (treeNode.children.some((child) => child.key === key)) {
+          parentKey.push(treeNode.key)
+        }
+        getMatchParentKeyInTree(key, treeNode.children)
+      } else if (treeNode.key === key) {
+        parentKey.push(key)
+      }
+    })
+  }
+  keys.forEach((key) => {
+    getMatchParentKeyInTree(key, tree)
+  })
+  return parentKey
+}
+
+const initDeptData = (originDeptData: API.DeptTreeResult): TreeDataNode[] => {
+  return originDeptData.map((ele) => {
+    return {
+      ...ele,
+      key: ele.id,
+      title: ele.name,
+      children: (ele.children && ele.children.length && initDeptData(ele.children)) || []
+    }
+  })
+}
+
+export default memo(({ onSelect }: DeptTreeSelectProps) => {
+  const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([])
+  const [searchValue, setSearchValue] = useState('')
+  const [autoExpandParent, setAutoExpandParent] = useState(true)
+  const [isLoading, setIsLoading] = useState(false)
+  const [originDeptData, setOriginDeptData] = useState<API.DeptTreeResult>([])
+
+  const getDeptTree = () => {
+    setIsLoading(true)
+    // tableData.value = []
+    fetchTree().then((res) => {
+      if (res) {
+        const data = res.data || []
+        setOriginDeptData(data)
+        defaultData = initDeptData(data)
+        generateList(defaultData)
+      }
+      setIsLoading(false)
+    })
+  }
+
+  useEffect(() => {
+    getDeptTree()
+  }, [])
+
+  const onSelectFn = (ids: Key[]) => {
+    onSelect(ids)
+  }
+  const onExpand = (newExpandedKeys: React.Key[]) => {
+    setExpandedKeys(newExpandedKeys)
+    setAutoExpandParent(false)
+  }
+
+  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+    const { value } = e.target
+    let newExpandedKeys: React.Key[] = []
+    if (value.trim() === '') {
+      newExpandedKeys = []
+    } else {
+      // 获取匹配的节点的父节点, 设置为展开节点
+      const matchKey = dataList
+        .filter((item) => item.title.indexOf(value) > -1)
+        .map((ele) => ele.key)
+      newExpandedKeys = getParentKey(matchKey, defaultData)
+    }
+    setExpandedKeys(newExpandedKeys)
+    setSearchValue(value)
+    setAutoExpandParent(true)
+  }
+
+  const treeData = useMemo(() => {
+    const loop = (data: TreeDataNode[]): TreeDataNode[] =>
+      data.map((item) => {
+        const strTitle = item.title as string
+        const index = strTitle.indexOf(searchValue)
+        const beforeStr = strTitle.substring(0, index)
+        const afterStr = strTitle.slice(index + searchValue.length)
+        const title =
+          index > -1 ? (
+            <span key={item.key}>
+              {beforeStr}
+              <span className='site-tree-search-value text-[#f50]'>{searchValue}</span>
+              {afterStr}
+            </span>
+          ) : (
+            <span key={item.key}>{strTitle}</span>
+          )
+        if (item.children) {
+          return { title, key: item.key, children: loop(item.children) }
+        }
+
+        return {
+          title,
+          key: item.key
+        }
+      })
+
+    return loop(defaultData)
+  }, [searchValue, defaultData])
+
+  return (
+    <div>
+      <Search style={{ marginBottom: 8 }} placeholder='请输入部门名称' onChange={onChange} />
+      <Tree
+        onSelect={onSelectFn}
+        onExpand={onExpand}
+        expandedKeys={expandedKeys}
+        autoExpandParent={autoExpandParent}
+        treeData={treeData}
+      />
+    </div>
+  )
+})

+ 252 - 4
src/pages/system/auth/user/index.tsx

@@ -1,7 +1,255 @@
-import React, { Component } from 'react'
+import { Key, useEffect, useState } from 'react'
+import { LockFlag } from '#/enum'
+import { toast } from 'sonner'
+import { deleteUser, getUserListPage } from '@/api/services/user'
+import { Button, Card, Col, Popconfirm, Row, Tag } from 'antd'
+import { PlusOutlined } from '@ant-design/icons'
+import type { ProColumns } from '@ant-design/pro-components'
+import { ProTable } from '@ant-design/pro-components'
+import { AddModal, AddModalProps } from './components/AddModal'
+import DeptTreeSelect from './components/DeptTreeSelect'
+import { IconButton, Iconify } from '@/components/icon'
 
-export default class index extends Component {
-  render() {
-    return <div>用户管理</div>
+const nameColorList = ['#71BC78', '#5BB4EB', '#F28CAE', '#ED9848', '#DB93DB']
+
+export default function RolePage() {
+  const [searchForm, setSearchForm] = useState({})
+  const [page, setPage] = useState({ total: 0, currentPage: 1, pageSize: 20 })
+  const [tableData, setTableData] = useState<API.UserListPageResult>([])
+  const [isLoading, setIsLoading] = useState(false)
+
+  const onDeptTreeSelect = (values: Key[]) => {
+    setSearchForm((prev) => ({ ...prev, deptId: values[0] }))
+  }
+
+  const getList = async () => {
+    setTableData([])
+    setIsLoading(true)
+    const res = await getUserListPage(
+      Object.assign(
+        {
+          current: page.currentPage,
+          size: page.pageSize
+        },
+        searchForm
+      )
+    )
+    if (res) {
+      setTableData(res.data.records)
+      setPage((prev) => ({ ...prev, total: res.data.total }))
+    }
+    setIsLoading(false)
+  }
+  useEffect(() => {
+    getList()
+  }, [searchForm, page.currentPage, page.pageSize])
+
+  const [addUserModalProps, setAddUserModalProps] = useState<AddModalProps>({
+    show: false,
+    onCancel: () => {
+      setAddUserModalProps((prev) => ({ ...prev, show: false }))
+    },
+    getList: () => {
+      getList()
+    }
+  })
+
+  const editFn = (record: API.UserListPageResultItem) => {
+    console.log(record)
+  }
+  const deleteFn = async (row: API.UserListPageResultItem) => {
+    const res = await deleteUser(row.userId)
+    if (res) {
+      getList()
+      toast.success('删除成功', {
+        position: 'top-center'
+      })
+    }
   }
+
+  const columns: ProColumns<API.UserListPageResultItem>[] = [
+    {
+      title: '序号',
+      dataIndex: 'index',
+      valueType: 'indexBorder',
+      width: 50
+    },
+    {
+      title: '用户名',
+      dataIndex: 'username',
+      width: 160,
+      render: (_, record, index) => {
+        return (
+          <div className='flex'>
+            <div
+              className='w-[40px] h-[40px] rounded-full mr-[8px] text-[#fff] text-center leading-[40px] text-[16px]'
+              style={{ backgroundColor: nameColorList[index % 5] }}>
+              <span>{record?.username?.slice(0, 1)?.toUpperCase() || '-'}</span>
+            </div>
+            <div className='ml-2 flex flex-col'>
+              <span className='text-sm'>{record.username}</span>
+              <span className='text-xs text-text-secondary'>{record.phone}</span>
+            </div>
+          </div>
+        )
+      }
+    },
+    {
+      title: '姓名',
+      dataIndex: 'realName',
+      width: 100
+    },
+    {
+      title: '手机号',
+      dataIndex: 'phone',
+      width: 120,
+      hideInTable: true
+    },
+    {
+      title: '角色',
+      dataIndex: 'roleList',
+      width: 120,
+      search: false,
+      render: (_, record) => {
+        return record.roleList.length === 1 ? (
+          <Tag color='processing'>{record.roleList[0].roleName}</Tag>
+        ) : (
+          <div className='overflow-hidden text-ellipsis whitespace-nowrap'>
+            {record.roleList.map((item) => {
+              return (
+                <Tag color='processing' key={item.roleId}>
+                  {item.roleName}
+                </Tag>
+              )
+            })}
+          </div>
+        )
+      }
+    },
+    {
+      title: '状态',
+      dataIndex: 'lockFlag',
+      width: 80,
+      search: false,
+      render: (lockFlag) => (
+        <Tag
+          color={
+            lockFlag === LockFlag.OK
+              ? 'success'
+              : lockFlag === LockFlag.LOCKED
+                ? 'error'
+                : lockFlag === LockFlag.DELETED
+                  ? 'default'
+                  : ''
+          }>
+          {lockFlag === LockFlag.OK
+            ? '正常'
+            : lockFlag === LockFlag.LOCKED
+              ? '锁定'
+              : lockFlag === LockFlag.DELETED
+                ? '删除'
+                : ''}
+        </Tag>
+      )
+    },
+    {
+      title: '创建时间',
+      dataIndex: 'createdTime',
+      width: 120,
+      search: false
+    },
+    {
+      title: '操作',
+      key: 'operation',
+      align: 'center',
+      width: 100,
+      search: false,
+      render: (_, record) => (
+        <div className='flex w-full justify-center text-gray-500'>
+          <IconButton
+            onClick={() => {
+              editFn(record)
+            }}>
+            <Iconify icon='mdi:card-account-details' size={18} />
+          </IconButton>
+          <IconButton onClick={() => {}}>
+            <Iconify icon='solar:pen-bold-duotone' size={18} />
+          </IconButton>
+          <Popconfirm
+            title='确定删除?'
+            okText='确定'
+            cancelText='取消'
+            placement='left'
+            onConfirm={() => {
+              deleteFn(record)
+            }}>
+            <IconButton>
+              <Iconify icon='mingcute:delete-2-fill' size={18} className='text-error' />
+            </IconButton>
+          </Popconfirm>
+        </div>
+      )
+    }
+  ]
+
+  return (
+    <Card bordered={false}>
+      <Row gutter={16}>
+        <Col span={5}>
+          <DeptTreeSelect onSelect={onDeptTreeSelect} />
+        </Col>
+        <Col span={19}>
+          <ProTable<API.UserListPageResultItem>
+            headerTitle='用户管理'
+            cardBordered
+            rowKey='userId'
+            loading={isLoading}
+            columns={columns}
+            dataSource={tableData}
+            pagination={{
+              current: page.currentPage,
+              pageSize: page.pageSize,
+              total: page.total,
+              showSizeChanger: true,
+              showQuickJumper: true,
+              onChange: (page) => {
+                setPage((prev) => ({ ...prev, currentPage: page }))
+              },
+              onShowSizeChange: (_current, size) => {
+                setPage((prev) => ({ ...prev, pageSize: size }))
+              }
+            }}
+            search={{
+              labelWidth: 'auto'
+            }}
+            onSubmit={(params) => {
+              setSearchForm(() => ({ ...params }))
+            }}
+            toolBarRender={() => [
+              <Button
+                key='button'
+                icon={<PlusOutlined />}
+                onClick={() => {
+                  setAddUserModalProps((prev) => ({ ...prev, show: true }))
+                }}
+                type='primary'>
+                新增
+              </Button>
+            ]}
+            options={{
+              fullScreen: true,
+              reload: () => {
+                getList()
+              }
+            }}
+            columnsState={{
+              persistenceKey: 'system-auth-user-list-column',
+              persistenceType: 'localStorage'
+            }}
+          />
+        </Col>
+      </Row>
+      <AddModal {...addUserModalProps} />
+    </Card>
+  )
 }

+ 0 - 1
src/router/components/protected-route.tsx

@@ -8,7 +8,6 @@ type Props = {
   children: React.ReactNode
 }
 export default function ProtectedRoute({ children }: Props) {
-  console.log('ProtectedRoute render')
   const router = useRouter()
   const access_token = useAccessToken()
 

+ 15 - 18
src/router/hooks/use-permission-routes.tsx

@@ -5,25 +5,24 @@ import type { AppRouteObject } from '#/router'
 import { PermissionType } from '#/enum'
 import { isEmpty } from 'ramda'
 import { clone } from 'ramda'
-import { Tag } from 'antd'
-import { Iconify } from '@/components/icon'
+// import { Tag } from 'antd'
+// import { Iconify } from '@/components/icon'
 import { CircleLoading } from '@/components/loading'
 
 const ENTRY_PATH = '/src/pages'
 const PAGES = import.meta.glob('/src/pages/**/*.tsx')
 export const loadComponentFromPath = (path: string) => PAGES[`${ENTRY_PATH}${path}.tsx`]
 
-// Components
-function NewFeatureTag() {
-  return (
-    <Tag color='cyan' className='!ml-2'>
-      <div className='flex items-center gap-1'>
-        <Iconify icon='solar:bell-bing-bold-duotone' size={12} />
-        <span className='ms-1'>NEW</span>
-      </div>
-    </Tag>
-  )
-}
+// function NewFeatureTag() {
+//   return (
+//     <Tag color='cyan' className='!ml-2'>
+//       <div className='flex items-center gap-1'>
+//         <Iconify icon='solar:bell-bing-bold-duotone' size={12} />
+//         <span className='ms-1'>NEW</span>
+//       </div>
+//     </Tag>
+//   )
+// }
 
 // Route Transformers
 const createBaseRoute = (menuItem: API.MenuChildrenItem, completeRoute: string): AppRouteObject => {
@@ -118,18 +117,16 @@ const addTypeToMenu = (menu: API.MenuResult) => {
   })
 }
 
-// 导出一个函数usePermissionRoutes
 export function usePermissionRoutes() {
   const menu = useUserMenu()
 
   return useMemo(() => {
-    if (!menu) return []
+    if (!menu || !menu.length) return []
 
-    // 克隆menu
     const menuCopy = clone(menu)
     // 给menu的每一级添加type, type为0, 即为目录, type为1, 即为菜单, 只有最后一级才是菜单
     addTypeToMenu(menuCopy)
-    console.log('添加type后的菜单', menuCopy)
+    // console.log('添加type后的菜单', menuCopy)
     const constantMenu: API.MenuResult = [
       {
         id: 'dashboard',
@@ -150,7 +147,7 @@ export function usePermissionRoutes() {
     ]
     menuCopy.unshift(...constantMenu)
 
-    console.log('动态路由:', transformMenuToRoutes(menuCopy))
+    // console.log('动态路由:', transformMenuToRoutes(menuCopy))
     return transformMenuToRoutes(menuCopy)
   }, [menu])
 }

+ 52 - 52
src/router/hooks/use-route-to-menu.tsx

@@ -1,65 +1,65 @@
-import { useCallback } from 'react';
-import { useTranslation } from 'react-i18next';
+import { useCallback } from 'react'
+import { useTranslation } from 'react-i18next'
+import { useSettings } from '@/store/settingStore'
+import type { AppRouteObject } from '#/router'
+import { ThemeLayout } from '#/enum'
+import { cn } from '@/utils'
+import type { GetProp, MenuProps } from 'antd'
+import { SvgIcon } from '@/components/icon'
 
-import { Iconify, SvgIcon } from '@/components/icon';
+// import { Iconify, SvgIcon } from '@/components/icon'
+// Iconify图标源:https://icon-sets.iconify.design/
 
-import { useSettings } from '@/store/settingStore';
-import { cn } from '@/utils';
-import type { GetProp, MenuProps } from 'antd';
-import { ThemeLayout } from '#/enum';
-import type { AppRouteObject } from '#/router';
-
-type MenuItem = GetProp<MenuProps, 'items'>[number];
+type MenuItem = GetProp<MenuProps, 'items'>[number]
 
 const renderIcon = (icon: string | React.ReactNode): React.ReactNode => {
-	if (typeof icon !== 'string') return icon;
-
-	return icon.startsWith('ic') ? (
-		<SvgIcon icon={icon} size={24} className="ant-menu-item-icon" />
-	) : (
-		<Iconify icon={icon} size={24} className="ant-menu-item-icon" />
-	);
-};
+  if (typeof icon !== 'string') return icon
+  return <SvgIcon icon={icon} size={24} className='ant-menu-item-icon' />
+  // return <Iconify icon={'material-symbols:10k-outline'} size={24} className='ant-menu-item-icon' />
+  // TODO 这样判断会存在问题
+  // return icon.startsWith('ic') ? (
+  //   <SvgIcon icon={icon} size={24} className='ant-menu-item-icon' />
+  // ) : (
+  //   <Iconify icon={icon} size={24} className='ant-menu-item-icon' />
+  // )
+}
 
 /**
  *   routes -> menus
  */
 export function useRouteToMenuFn() {
-	const { t } = useTranslation();
-	const { themeLayout } = useSettings();
+  const { t } = useTranslation()
+  const { themeLayout } = useSettings()
 
-	const routeToMenuFn = useCallback(
-		(items: AppRouteObject[]): MenuItem[] => {
-			return items
-				.filter((item) => !item.meta?.hideMenu)
-				.map((item) => {
-					const { meta, children } = item;
-					if (!meta) return {} as MenuItem;
+  const routeToMenuFn = useCallback(
+    (items: AppRouteObject[]): MenuItem[] => {
+      return items
+        .filter((item) => !item.meta?.hideMenu)
+        .map((item) => {
+          const { meta, children } = item
+          if (!meta) return {} as MenuItem
 
-					const menuItem: Partial<MenuItem> = {
-						key: meta.key,
-						disabled: meta.disabled,
-						label: (
-							<div
-								className={cn(
-									'inline-flex items-center overflow-hidden',
-									themeLayout === ThemeLayout.Horizontal
-										? 'justify-start'
-										: 'justify-between'
-								)}
-							>
-								<div className="">{t(meta.label)}</div>
-								{meta.suffix}
-							</div>
-						),
-						...(meta.icon && { icon: renderIcon(meta.icon) }),
-						...(children && { children: routeToMenuFn(children) })
-					};
+          const menuItem: Partial<MenuItem> = {
+            key: meta.key,
+            disabled: meta.disabled,
+            label: (
+              <div
+                className={cn(
+                  'inline-flex items-center overflow-hidden',
+                  themeLayout === ThemeLayout.Horizontal ? 'justify-start' : 'justify-between'
+                )}>
+                <div className=''>{t(meta.label)}</div>
+                {meta.suffix}
+              </div>
+            ),
+            ...(meta.icon && { icon: renderIcon(meta.icon) }),
+            ...(children && { children: routeToMenuFn(children) })
+          }
 
-					return menuItem as MenuItem;
-				});
-		},
-		[t, themeLayout]
-	);
-	return routeToMenuFn;
+          return menuItem as MenuItem
+        })
+    },
+    [t, themeLayout]
+  )
+  return routeToMenuFn
 }

+ 2 - 9
src/router/index.tsx

@@ -5,7 +5,6 @@ import type { AppRouteObject } from '#/router'
 import ProtectedRoute from '@/router/components/protected-route'
 import { usePermissionRoutes } from '@/router/hooks'
 import { ERROR_ROUTE } from '@/router/routes/error-routes'
-// import HOME_ROUTE from '@/router/routes/modules/constantRoutes'
 import DashboardLayout from '@/layouts/dashboard'
 import PageError from '@/pages/sys/error/PageError'
 import Login from '@/pages/sys/login/Login'
@@ -27,7 +26,6 @@ const NO_MATCHED_ROUTE: AppRouteObject = {
 }
 
 export default function Router() {
-  console.log('Router render')
   const permissionRoutes = usePermissionRoutes()
 
   // 动态路由
@@ -38,18 +36,13 @@ export default function Router() {
         <DashboardLayout />
       </ProtectedRoute>
     ),
-    children: [
-      { index: true, element: <Navigate to={HOMEPAGE} replace /> },
-      // HOME_ROUTE,
-      ...permissionRoutes
-    ]
+    children: [{ index: true, element: <Navigate to={HOMEPAGE} replace /> }, ...permissionRoutes]
   }
 
   const routes = [PUBLIC_ROUTE, PROTECTED_ROUTE, ERROR_ROUTE, NO_MATCHED_ROUTE] as RouteObject[]
-  console.log(routes)
+  // console.log(routes)
 
   const router = createHashRouter(routes)
-  console.log(router)
 
   return <RouterProvider router={router} />
 }

+ 0 - 6
src/router/routes/modules/dashboard.tsx

@@ -5,7 +5,6 @@ import { SvgIcon } from '@/components/icon'
 import { CircleLoading } from '@/components/loading'
 
 const HomePage = lazy(() => import('@/pages/dashboard'))
-const Analysis = lazy(() => import('@/pages/dashboard/analysis'))
 
 const dashboard: AppRouteObject = {
   order: 1,
@@ -29,11 +28,6 @@ const dashboard: AppRouteObject = {
       path: 'workbench',
       element: <HomePage />,
       meta: { label: 'sys.menu.workbench', key: '/dashboard/workbench' }
-    },
-    {
-      path: 'analysis',
-      element: <Analysis />,
-      meta: { label: 'sys.menu.analysis', key: '/dashboard/analysis' }
     }
   ]
 }

+ 30 - 31
src/router/utils.ts

@@ -1,55 +1,54 @@
-import { ascend } from 'ramda';
-
-import type { AppRouteObject, RouteMeta } from '#/router';
+import type { AppRouteObject, RouteMeta } from '#/router'
+import { ascend } from 'ramda'
 
 /**
  * return menu routes
  */
 export const menuFilter = (items: AppRouteObject[]) => {
-	return items
-		.filter((item) => {
-			const show = item.meta?.key;
-			if (show && item.children) {
-				item.children = menuFilter(item.children);
-			}
-			return show;
-		})
-		.sort(ascend((item) => item.order || Number.POSITIVE_INFINITY));
-};
+  return items
+    .filter((item) => {
+      const show = item.meta?.key
+      if (show && item.children) {
+        item.children = menuFilter(item.children)
+      }
+      return show
+    })
+    .sort(ascend((item) => item.order || Number.NEGATIVE_INFINITY))
+}
 
 /**
  * 基于 src/router/routes/modules 文件结构动态生成路由
  */
 export function getRoutesFromModules() {
-	const menuModules: AppRouteObject[] = [];
+  const menuModules: AppRouteObject[] = []
 
-	const modules = import.meta.glob('./routes/modules/**/*.tsx', {
-		eager: true
-	});
-	for (const key in modules) {
-		const mod = (modules as any)[key].default || {};
-		const modList = Array.isArray(mod) ? [...mod] : [mod];
-		menuModules.push(...modList);
-	}
-	return menuModules;
+  const modules = import.meta.glob('./routes/modules/**/*.tsx', {
+    eager: true
+  })
+  for (const key in modules) {
+    const mod = (modules as any)[key].default || {}
+    const modList = Array.isArray(mod) ? [...mod] : [mod]
+    menuModules.push(...modList)
+  }
+  return menuModules
 }
 
 /**
  * return the routes will be used in sidebar menu
  */
 export function getMenuRoutes(appRouteObjects: AppRouteObject[]) {
-	// return menuFilter(getMenuModules());
-	return menuFilter(appRouteObjects);
+  // return menuFilter(getMenuModules());
+  return menuFilter(appRouteObjects)
 }
 
 /**
  * return flatten routes
  */
 export function flattenMenuRoutes(routes: AppRouteObject[]) {
-	return routes.reduce<RouteMeta[]>((prev, item) => {
-		const { meta, children } = item;
-		if (meta) prev.push(meta);
-		if (children) prev.push(...flattenMenuRoutes(children));
-		return prev;
-	}, []);
+  return routes.reduce<RouteMeta[]>((prev, item) => {
+    const { meta, children } = item
+    if (meta) prev.push(meta)
+    if (children) prev.push(...flattenMenuRoutes(children))
+    return prev
+  }, [])
 }

+ 54 - 52
src/store/userStore.ts

@@ -1,19 +1,17 @@
 import { useNavigate } from 'react-router'
 import { create } from 'zustand'
 import { createJSONStorage, persist } from 'zustand/middleware'
-import type { UserInfo, UserToken } from '#/entity'
+import type { UserToken } from '#/entity'
 import { StorageEnum } from '#/enum'
+import { toast } from 'sonner'
 import { loginByUsername } from '@/api/services/login'
 import { getMenu } from '@/api/services/menu'
+import { getUserDetail, getUserInfo } from '@/api/services/user'
 import { deepClone, encryption } from '@/utils/util'
 import { isURL } from '@/utils/validate'
 import type { ILoginForm } from '@/pages/sys/login/LoginForm'
 
-// import { useMutation } from '@tanstack/react-query'
-// import { toast } from 'sonner'
-// import userService, { type SignInReq } from '@/api/services/userService'
-
-// const { VITE_APP_HOMEPAGE: HOMEPAGE } = import.meta.env
+const { VITE_APP_HOMEPAGE: HOMEPAGE } = import.meta.env
 
 function addPath(ele: API.MenuResultItem & Record<string, any>, first?: boolean) {
   const menu = {
@@ -43,11 +41,10 @@ function addPath(ele: API.MenuResultItem & Record<string, any>, first?: boolean)
 }
 
 type UserStore = {
-  userInfo: Partial<UserInfo>
+  userInfo: Partial<API.SysUser>
   tenantInfo: Partial<API.UserDetail>
   permissions: Record<string, boolean>
   menu: API.MenuResult
-
   userToken: UserToken
   expires_in: number | null
   tenant_id?: number
@@ -55,21 +52,23 @@ type UserStore = {
   refresh_token: string
   // 使用 actions 命名空间来存放所有的 action
   actions: {
-    setUserInfo: (userInfo: UserInfo) => void
+    setUserInfo: (userInfo: API.SysUser) => void
+    setTenantInfo: (userInfo: API.UserDetail) => void
     setTenantId: (tenant_id: number) => void
     setExpiresIn: (expires_in: number) => void
     setAccessToken: (access_token: string) => void
     setRefreshToken: (refresh_token: string) => void
     setMenu: (params: { menu?: UserStore['menu']; type?: boolean }) => void
     setPermissions: (permissions: string[]) => void
-    setUserToken: (token: UserToken) => void
     clearUserInfoAndToken: () => void
+    getUserDetail: () => Promise<void>
+    getUserInfo: () => Promise<void>
   }
 }
 
 export const useUserSessionStore = create<UserStore>()(
   persist(
-    (set) => ({
+    (set, get) => ({
       userInfo: {},
       tenantInfo: {},
       permissions: {},
@@ -83,6 +82,9 @@ export const useUserSessionStore = create<UserStore>()(
         setUserInfo: (userInfo) => {
           set({ userInfo })
         },
+        setTenantInfo(tenantInfo) {
+          set({ tenantInfo })
+        },
         setTenantId: (tenant_id) => {
           set({ tenant_id })
         },
@@ -106,12 +108,42 @@ export const useUserSessionStore = create<UserStore>()(
           }
           set({ permissions: list })
         },
-
-        setUserToken: (userToken) => {
-          set({ userToken })
-        },
         clearUserInfoAndToken() {
-          set({ userInfo: {}, userToken: {} })
+          set({
+            userInfo: {},
+            tenantInfo: {},
+            permissions: {},
+            menu: [],
+            expires_in: null,
+            tenant_id: undefined,
+            access_token: '',
+            refresh_token: ''
+          })
+        },
+        getUserInfo() {
+          const { actions } = get()
+          return new Promise<void>((resolve) => {
+            getUserInfo().then(async (res) => {
+              if (res) {
+                const data = res.data
+                actions.setUserInfo(data.sysUser)
+                actions.setPermissions(data.permissions || [])
+                actions.getUserDetail()
+                resolve()
+              }
+            })
+          })
+        },
+        getUserDetail() {
+          const currentState = get()
+          return new Promise<void>((resolve) => {
+            getUserDetail(currentState.userInfo.userId!).then((res) => {
+              if (res) {
+                currentState.actions.setTenantInfo(res.data)
+                resolve()
+              }
+            })
+          })
         }
       }
     }),
@@ -170,48 +202,19 @@ export const useAccessToken = () => useUserSessionStore((state) => state.access_
 export const useRefreshToken = () => useUserSessionStore((state) => state.refresh_token)
 export const useUserToken = () => useUserSessionStore((state) => state.userToken)
 export const useUserMenu = () => useUserSessionStore((state) => state.menu)
-export const useUserPermission = () => useUserSessionStore((state) => state.userInfo.permissions)
+export const useUserPermission = () => useUserSessionStore((state) => state.permissions)
 export const useUserActions = () => useUserSessionStore((state) => state.actions)
 
-// export const useSignIn = () => {
-//   const navigatge = useNavigate()
-//   const { setUserToken, setUserInfo } = useUserActions()
-
-//   const signInMutation = useMutation({
-//     mutationFn: userService.signin
-//   })
-
-//   const signIn = async (data: SignInReq) => {
-//     try {
-//       const res = await signInMutation.mutateAsync(data)
-//       const { user, accessToken, refreshToken } = res
-//       setUserToken({ accessToken, refreshToken })
-//       setUserInfo(user)
-//       navigatge(HOMEPAGE, { replace: true })
-//       toast.success('Sign in success!')
-//     } catch (err) {
-//       toast.error(err.message, {
-//         position: 'top-center'
-//       })
-//     }
-//   }
-
-//   return signIn
-// }
-
 // 根据用户名登录
 export const useLoginByUsername = () => {
-  const navigate = useNavigate()
   const getMenuFn = useGetMenu()
   const { setAccessToken, setRefreshToken, setExpiresIn } = useUserActions()
   const loginFn = (userInfo: Omit<ILoginForm, 'tenant_name'>) => {
-    console.log(userInfo)
     const user = encryption({
       data: userInfo,
       key: 'REmAZJQfbiDpdXCf',
       param: ['password']
     })
-    console.log(user)
     return new Promise<void>((resolve, reject) => {
       loginByUsername(user.username, user.password, user.code, user.randomStr)
         .then(async (res) => {
@@ -220,16 +223,15 @@ export const useLoginByUsername = () => {
             setAccessToken(data.access_token)
             setRefreshToken(data.refresh_token)
             setExpiresIn(data.expires_in)
-            // await loadRoutes(router)
             await getMenuFn()
-            // 跳转到首页
-            navigate('/dashboard/index')
+            toast.success('登录成功!', { position: 'top-center' })
             resolve()
           } else {
             reject('获取用户信息失败')
           }
         })
         .catch((err) => {
+          toast.error(err || '登录失败!', { position: 'top-center' })
           reject(err)
         })
     })
@@ -238,14 +240,13 @@ export const useLoginByUsername = () => {
 }
 
 export const useGetMenu = () => {
+  const navigate = useNavigate()
   const { setMenu } = useUserActions()
   return (obj?: { id?: number; type?: boolean }) => {
     return new Promise((resolve) => {
       getMenu(obj?.id as number).then((res) => {
         if (res) {
-          console.log(res)
-          const data = res.data
-          const menu: API.MenuResult = deepClone(data)
+          const menu: API.MenuResult = deepClone(res.data)
           menu.forEach((ele) => {
             addPath(ele)
           })
@@ -254,6 +255,7 @@ export const useGetMenu = () => {
             type,
             menu
           })
+          navigate(HOMEPAGE, { replace: true })
           resolve(menu)
         }
       })

+ 123 - 123
src/theme/tokens/color.ts

@@ -1,50 +1,50 @@
-import { rgbAlpha } from '@/utils/theme';
-import { ThemeColorPresets } from '#/enum';
+import { ThemeColorPresets } from '#/enum'
+import { rgbAlpha } from '@/utils/theme'
 
 export const presetsColors = {
-	[ThemeColorPresets.Default]: {
-		lighter: '#C8FAD6',
-		light: '#5BE49B',
-		default: '#00A76F',
-		dark: '#007867',
-		darker: '#004B50'
-	},
-	[ThemeColorPresets.Cyan]: {
-		lighter: '#CCF4FE',
-		light: '#68CDF9',
-		default: '#078DEE',
-		dark: '#0351AB',
-		darker: '#012972'
-	},
-	[ThemeColorPresets.Purple]: {
-		lighter: '#EBD6FD',
-		light: '#B985F4',
-		default: '#7635DC',
-		dark: '#431A9E',
-		darker: '#200A69'
-	},
-	[ThemeColorPresets.Blue]: {
-		lighter: '#D1E9FC',
-		light: '#76B0F1',
-		default: '#2065D1',
-		dark: '#103996',
-		darker: '#061B64'
-	},
-	[ThemeColorPresets.Orange]: {
-		lighter: '#FEF4D4',
-		light: '#FED680',
-		default: '#FDA92D',
-		dark: '#B66816',
-		darker: '#793908'
-	},
-	[ThemeColorPresets.Red]: {
-		lighter: '#FFE3D5',
-		light: '#FF9882',
-		default: '#FF3030',
-		dark: '#B71833',
-		darker: '#7A0930'
-	}
-};
+  [ThemeColorPresets.Default]: {
+    lighter: '#CCF4FE',
+    light: '#68CDF9',
+    default: '#078DEE',
+    dark: '#0351AB',
+    darker: '#012972'
+  },
+  [ThemeColorPresets.Cyan]: {
+    lighter: '#C8FAD6',
+    light: '#5BE49B',
+    default: '#00A76F',
+    dark: '#007867',
+    darker: '#004B50'
+  },
+  [ThemeColorPresets.Purple]: {
+    lighter: '#EBD6FD',
+    light: '#B985F4',
+    default: '#7635DC',
+    dark: '#431A9E',
+    darker: '#200A69'
+  },
+  [ThemeColorPresets.Blue]: {
+    lighter: '#D1E9FC',
+    light: '#76B0F1',
+    default: '#2065D1',
+    dark: '#103996',
+    darker: '#061B64'
+  },
+  [ThemeColorPresets.Orange]: {
+    lighter: '#FEF4D4',
+    light: '#FED680',
+    default: '#FDA92D',
+    dark: '#B66816',
+    darker: '#793908'
+  },
+  [ThemeColorPresets.Red]: {
+    lighter: '#FFE3D5',
+    light: '#FF9882',
+    default: '#FF3030',
+    dark: '#B71833',
+    darker: '#7A0930'
+  }
+}
 
 /**
  * We recommend picking colors with these values for [Eva Color Design](https://colors.eva.design/):
@@ -55,87 +55,87 @@ export const presetsColors = {
  *  + darker : 900
  */
 export const paletteColors = {
-	primary: presetsColors[ThemeColorPresets.Default],
-	secondary: {
-		lighter: '#D6E4FF',
-		light: '#84A9FF',
-		default: '#3366FF',
-		dark: '#1939B7',
-		darker: '#091A7A'
-	},
-	success: {
-		lighter: '#D8FBDE',
-		light: '#86E8AB',
-		default: '#36B37E',
-		dark: '#1B806A',
-		darker: '#0A5554'
-	},
-	warning: {
-		lighter: '#FFF5CC',
-		light: '#FFD666',
-		default: '#FFAB00',
-		dark: '#B76E00',
-		darker: '#7A4100'
-	},
-	error: {
-		lighter: '#FFE9D5',
-		light: '#FFAC82',
-		default: '#FF5630',
-		dark: '#B71D18',
-		darker: '#7A0916'
-	},
-	info: {
-		lighter: '#CAFDF5',
-		light: '#61F3F3',
-		default: '#00B8D9',
-		dark: '#006C9C',
-		darker: '#003768'
-	},
-	gray: {
-		'100': '#F9FAFB',
-		'200': '#F4F6F8',
-		'300': '#DFE3E8',
-		'400': '#C4CDD5',
-		'500': '#919EAB',
-		'600': '#637381',
-		'700': '#454F5B',
-		'800': '#1C252E',
-		'900': '#141A21'
-	}
-};
+  primary: presetsColors[ThemeColorPresets.Default],
+  secondary: {
+    lighter: '#D6E4FF',
+    light: '#84A9FF',
+    default: '#3366FF',
+    dark: '#1939B7',
+    darker: '#091A7A'
+  },
+  success: {
+    lighter: '#D8FBDE',
+    light: '#86E8AB',
+    default: '#36B37E',
+    dark: '#1B806A',
+    darker: '#0A5554'
+  },
+  warning: {
+    lighter: '#FFF5CC',
+    light: '#FFD666',
+    default: '#FFAB00',
+    dark: '#B76E00',
+    darker: '#7A4100'
+  },
+  error: {
+    lighter: '#FFE9D5',
+    light: '#FFAC82',
+    default: '#FF5630',
+    dark: '#B71D18',
+    darker: '#7A0916'
+  },
+  info: {
+    lighter: '#CAFDF5',
+    light: '#61F3F3',
+    default: '#00B8D9',
+    dark: '#006C9C',
+    darker: '#003768'
+  },
+  gray: {
+    '100': '#F9FAFB',
+    '200': '#F4F6F8',
+    '300': '#DFE3E8',
+    '400': '#C4CDD5',
+    '500': '#919EAB',
+    '600': '#637381',
+    '700': '#454F5B',
+    '800': '#1C252E',
+    '900': '#141A21'
+  }
+}
 
 export const commonColors = {
-	white: '#FFFFFF',
-	black: '#000000',
-	border: rgbAlpha(paletteColors.gray[500], 0.2)
-};
+  white: '#FFFFFF',
+  black: '#000000',
+  border: rgbAlpha(paletteColors.gray[500], 0.2)
+}
 
 export const lightColorTokens = {
-	palette: paletteColors,
-	common: commonColors,
-	text: {
-		primary: '#1C252E',
-		secondary: '#637381',
-		disabled: '#919EAB'
-	},
-	background: {
-		default: '#FFFFFF',
-		paper: '#FFFFFF',
-		neutral: '#F4F6F8'
-	}
-};
+  palette: paletteColors,
+  common: commonColors,
+  text: {
+    primary: '#1C252E',
+    secondary: '#637381',
+    disabled: '#919EAB'
+  },
+  background: {
+    default: '#FFFFFF',
+    paper: '#FFFFFF',
+    neutral: '#F4F6F8'
+  }
+}
 
 export const darkColorTokens = {
-	palette: paletteColors,
-	common: commonColors,
-	text: {
-		primary: '#FFFFFF',
-		secondary: '#919EAB',
-		disabled: '#637381'
-	},
-	background: {
-		default: '#161c24',
-		paper: '#212b36',
-		neutral: '#28323D'
-	}
-};
+  palette: paletteColors,
+  common: commonColors,
+  text: {
+    primary: '#FFFFFF',
+    secondary: '#919EAB',
+    disabled: '#637381'
+  },
+  background: {
+    default: '#161c24',
+    paper: '#212b36',
+    neutral: '#28323D'
+  }
+}

+ 6 - 0
types/enum.ts

@@ -1,3 +1,9 @@
+export enum LockFlag {
+  OK = 'OK',
+  LOCKED = 'LOCKED',
+  DELETED = 'DELETED'
+}
+
 export enum BasicStatus {
   DISABLE = 0,
   ENABLE = 1

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.