code4eat il y a 1 mois
commit
a9ae405b43
100 fichiers modifiés avec 6920 ajouts et 0 suppressions
  1. 9 0
      .env.development
  2. 24 0
      .gitignore
  3. 54 0
      README.md
  4. 28 0
      eslint.config.js
  5. 13 0
      index.html
  6. 3591 0
      package-lock.json
  7. 35 0
      package.json
  8. BIN
      public/arrow_down.png
  9. BIN
      public/arrow_up.png
  10. BIN
      public/baoyangwanchengicon.png
  11. BIN
      public/baoyangweiwanchengicon.png
  12. BIN
      public/bottomBg.png
  13. 0 0
      public/bottom_decoration.png
  14. 0 0
      public/button_bg.png
  15. BIN
      public/choutibeijingtu_bg.png
  16. BIN
      public/chuangkoushudizuo_bg.png
  17. BIN
      public/component_header_bg.png
  18. BIN
      public/daijiaofei_icon.png
  19. BIN
      public/daijiuzheng_icon.png
  20. BIN
      public/daiquhao_icon.png
  21. 9 0
      public/device_example.svg
  22. 0 0
      public/drawer_close_btn.png
  23. 0 0
      public/drawer_title_bg.png
  24. 0 0
      public/drawer_title_icon.png
  25. BIN
      public/fayaocard_bg.png
  26. BIN
      public/jianquan_tishi.png
  27. BIN
      public/jiantouzhuangshi.png
  28. BIN
      public/jinriyucezongliang_bg.png
  29. BIN
      public/juxingkapian_bg.png
  30. BIN
      public/kucunbeijing.png
  31. BIN
      public/kucunicon.png
  32. BIN
      public/lanseshuangbiaopan.png
  33. BIN
      public/layout_content_bg.png
  34. BIN
      public/layout_header_bg.png
  35. BIN
      public/layout_header_bg2.png
  36. BIN
      public/layout_header_bg3.png
  37. BIN
      public/lvseshuangbiaopan.png
  38. 4 0
      public/maintenance_icon.svg
  39. BIN
      public/paihangkapian.png
  40. BIN
      public/qingseshuangbiaopan.png
  41. BIN
      public/qipaozhaotu_bg.png
  42. BIN
      public/queyaolvicon.png
  43. BIN
      public/shebeibaoyang_bg.png
  44. BIN
      public/shebeichukuheshuicon.png
  45. BIN
      public/shebeirukuheshuicon.png
  46. BIN
      public/shebeixunjian_bg.png
  47. BIN
      public/shebeiyilanbanner.png
  48. BIN
      public/side_benyue.png
  49. BIN
      public/side_benyue_gray.png
  50. BIN
      public/side_bottom.png
  51. BIN
      public/side_bottom_light.png
  52. BIN
      public/side_nav_1.png
  53. BIN
      public/side_nav_1_active.png
  54. BIN
      public/side_nav_2.png
  55. BIN
      public/side_nav_2_active.png
  56. BIN
      public/side_nav_3.png
  57. BIN
      public/side_nav_3_active.png
  58. BIN
      public/side_nav_active_highlight.png
  59. BIN
      public/side_nav_bg.png
  60. BIN
      public/side_other.png
  61. BIN
      public/side_other_light.png
  62. BIN
      public/tabweixuanzhong.png
  63. 3 0
      public/tabweixuanzhong.svg
  64. BIN
      public/tabxuanzhong.png
  65. 4 0
      public/tabxuanzhong.svg
  66. 0 0
      public/title-bg.png
  67. 12 0
      public/title_decorator.svg
  68. 13 0
      public/title_icon.svg
  69. 13 0
      public/title_right.svg
  70. 1 0
      public/vite.svg
  71. BIN
      public/yugexiaohaotianshuicon.png
  72. BIN
      public/yujingdizuo_bg.png
  73. BIN
      public/zaikupinzgingshuicon.png
  74. BIN
      public/zhongjianbiaotizhuangshi_bg.png
  75. BIN
      public/ziseshuangbiaopan.png
  76. BIN
      public/侧航左1H.png
  77. BIN
      public/侧航左1N.png
  78. BIN
      public/侧航左2H.png
  79. BIN
      public/侧航左2N.png
  80. BIN
      public/侧航左3H.png
  81. BIN
      public/侧航左3N.png
  82. BIN
      public/侧航左4H.png
  83. BIN
      public/侧航左4N.png
  84. BIN
      public/侧航左5H.png
  85. BIN
      public/侧航左5N.png
  86. 41 0
      src/App.css
  87. 937 0
      src/App.tsx
  88. BIN
      src/assets/DingTalk JinBuTi.ttf
  89. BIN
      src/assets/DingTalk Sans.ttf
  90. 0 0
      src/assets/react.svg
  91. 43 0
      src/components/AuthCheck.tsx
  92. 89 0
      src/components/AuthTest.tsx
  93. 559 0
      src/components/DrawerButtons.tsx
  94. 47 0
      src/components/LoadingSpinner.tsx
  95. 35 0
      src/components/RightTopTime.tsx
  96. 251 0
      src/components/charts/ConsumedMedicineTop8Chart.tsx
  97. 143 0
      src/components/charts/CoreMetricsChart.tsx
  98. 144 0
      src/components/charts/CyanDualGaugeChart.tsx
  99. 400 0
      src/components/charts/DailyMedicineQuantityChart.tsx
  100. 418 0
      src/components/charts/DailyPrescriptionChart.tsx

+ 9 - 0
.env.development

@@ -0,0 +1,9 @@
+###
+ # @Author: code4eat awesomedema@gmail.com
+ # @Date: 2025-06-05 10:21:51
+ # @LastEditors: code4eat awesomedema@gmail.com
+ # @LastEditTime: 2025-06-05 11:33:25
+ # @FilePath: /bi2025/.env.development
+ # @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+### 
+VITE_USE_MOCK=false

+ 24 - 0
.gitignore

@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 54 - 0
README.md

@@ -0,0 +1,54 @@
+# React + TypeScript + Vite
+
+This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
+
+Currently, two official plugins are available:
+
+- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
+
+## Expanding the ESLint configuration
+
+If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
+
+```js
+export default tseslint.config({
+  extends: [
+    // Remove ...tseslint.configs.recommended and replace with this
+    ...tseslint.configs.recommendedTypeChecked,
+    // Alternatively, use this for stricter rules
+    ...tseslint.configs.strictTypeChecked,
+    // Optionally, add this for stylistic rules
+    ...tseslint.configs.stylisticTypeChecked,
+  ],
+  languageOptions: {
+    // other options...
+    parserOptions: {
+      project: ['./tsconfig.node.json', './tsconfig.app.json'],
+      tsconfigRootDir: import.meta.dirname,
+    },
+  },
+})
+```
+
+You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
+
+```js
+// eslint.config.js
+import reactX from 'eslint-plugin-react-x'
+import reactDom from 'eslint-plugin-react-dom'
+
+export default tseslint.config({
+  plugins: {
+    // Add the react-x and react-dom plugins
+    'react-x': reactX,
+    'react-dom': reactDom,
+  },
+  rules: {
+    // other rules...
+    // Enable its recommended typescript rules
+    ...reactX.configs['recommended-typescript'].rules,
+    ...reactDom.configs.recommended.rules,
+  },
+})
+```

+ 28 - 0
eslint.config.js

@@ -0,0 +1,28 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import tseslint from 'typescript-eslint'
+
+export default tseslint.config(
+  { ignores: ['dist'] },
+  {
+    extends: [js.configs.recommended, ...tseslint.configs.recommended],
+    files: ['**/*.{ts,tsx}'],
+    languageOptions: {
+      ecmaVersion: 2020,
+      globals: globals.browser,
+    },
+    plugins: {
+      'react-hooks': reactHooks,
+      'react-refresh': reactRefresh,
+    },
+    rules: {
+      ...reactHooks.configs.recommended.rules,
+      'react-refresh/only-export-components': [
+        'warn',
+        { allowConstantExport: true },
+      ],
+    },
+  },
+)

+ 13 - 0
index.html

@@ -0,0 +1,13 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Vite + React + TS</title>
+  </head>
+  <body>
+    <div id="root"></div>
+    <script type="module" src="/src/main.tsx"></script>
+  </body>
+</html>

+ 3591 - 0
package-lock.json

@@ -0,0 +1,3591 @@
+{
+  "name": "bi2025",
+  "version": "0.0.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "bi2025",
+      "version": "0.0.0",
+      "dependencies": {
+        "@types/styled-components": "^5.1.34",
+        "axios": "^1.9.0",
+        "echarts": "^5.6.0",
+        "echarts-for-react": "^3.0.2",
+        "echarts-liquidfill": "^3.1.0",
+        "react": "^19.1.0",
+        "react-dom": "^19.1.0",
+        "styled-components": "^6.1.18"
+      },
+      "devDependencies": {
+        "@eslint/js": "^9.25.0",
+        "@types/react": "^19.1.2",
+        "@types/react-dom": "^19.1.2",
+        "@vitejs/plugin-react": "^4.4.1",
+        "eslint": "^9.25.0",
+        "eslint-plugin-react-hooks": "^5.2.0",
+        "eslint-plugin-react-refresh": "^0.4.19",
+        "globals": "^16.0.0",
+        "typescript": "~5.8.3",
+        "typescript-eslint": "^8.30.1",
+        "vite": "^6.3.5"
+      }
+    },
+    "node_modules/@ampproject/remapping": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
+      "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+      "dev": true,
+      "dependencies": {
+        "@jridgewell/gen-mapping": "^0.3.5",
+        "@jridgewell/trace-mapping": "^0.3.24"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@babel/code-frame": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+      "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+      "dev": true,
+      "dependencies": {
+        "@babel/helper-validator-identifier": "^7.27.1",
+        "js-tokens": "^4.0.0",
+        "picocolors": "^1.1.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/compat-data": {
+      "version": "7.27.3",
+      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.3.tgz",
+      "integrity": "sha512-V42wFfx1ymFte+ecf6iXghnnP8kWTO+ZLXIyZq+1LAXHHvTZdVxicn4yiVYdYMGaCO3tmqub11AorKkv+iodqw==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/core": {
+      "version": "7.27.3",
+      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.3.tgz",
+      "integrity": "sha512-hyrN8ivxfvJ4i0fIJuV4EOlV0WDMz5Ui4StRTgVaAvWeiRCilXgwVvxJKtFQ3TKtHgJscB2YiXKGNJuVwhQMtA==",
+      "dev": true,
+      "dependencies": {
+        "@ampproject/remapping": "^2.2.0",
+        "@babel/code-frame": "^7.27.1",
+        "@babel/generator": "^7.27.3",
+        "@babel/helper-compilation-targets": "^7.27.2",
+        "@babel/helper-module-transforms": "^7.27.3",
+        "@babel/helpers": "^7.27.3",
+        "@babel/parser": "^7.27.3",
+        "@babel/template": "^7.27.2",
+        "@babel/traverse": "^7.27.3",
+        "@babel/types": "^7.27.3",
+        "convert-source-map": "^2.0.0",
+        "debug": "^4.1.0",
+        "gensync": "^1.0.0-beta.2",
+        "json5": "^2.2.3",
+        "semver": "^6.3.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/babel"
+      }
+    },
+    "node_modules/@babel/generator": {
+      "version": "7.27.3",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.3.tgz",
+      "integrity": "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q==",
+      "dev": true,
+      "dependencies": {
+        "@babel/parser": "^7.27.3",
+        "@babel/types": "^7.27.3",
+        "@jridgewell/gen-mapping": "^0.3.5",
+        "@jridgewell/trace-mapping": "^0.3.25",
+        "jsesc": "^3.0.2"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-compilation-targets": {
+      "version": "7.27.2",
+      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
+      "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
+      "dev": true,
+      "dependencies": {
+        "@babel/compat-data": "^7.27.2",
+        "@babel/helper-validator-option": "^7.27.1",
+        "browserslist": "^4.24.0",
+        "lru-cache": "^5.1.1",
+        "semver": "^6.3.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-module-imports": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
+      "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+      "dev": true,
+      "dependencies": {
+        "@babel/traverse": "^7.27.1",
+        "@babel/types": "^7.27.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-module-transforms": {
+      "version": "7.27.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz",
+      "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==",
+      "dev": true,
+      "dependencies": {
+        "@babel/helper-module-imports": "^7.27.1",
+        "@babel/helper-validator-identifier": "^7.27.1",
+        "@babel/traverse": "^7.27.3"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0"
+      }
+    },
+    "node_modules/@babel/helper-plugin-utils": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
+      "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-string-parser": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+      "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-validator-identifier": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
+      "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-validator-option": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+      "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helpers": {
+      "version": "7.27.3",
+      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.3.tgz",
+      "integrity": "sha512-h/eKy9agOya1IGuLaZ9tEUgz+uIRXcbtOhRtUyyMf8JFmn1iT13vnl/IGVWSkdOCG/pC57U4S1jnAabAavTMwg==",
+      "dev": true,
+      "dependencies": {
+        "@babel/template": "^7.27.2",
+        "@babel/types": "^7.27.3"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/parser": {
+      "version": "7.27.3",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.3.tgz",
+      "integrity": "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw==",
+      "dev": true,
+      "dependencies": {
+        "@babel/types": "^7.27.3"
+      },
+      "bin": {
+        "parser": "bin/babel-parser.js"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-react-jsx-self": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+      "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+      "dev": true,
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.27.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-react-jsx-source": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+      "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+      "dev": true,
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.27.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/template": {
+      "version": "7.27.2",
+      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+      "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+      "dev": true,
+      "dependencies": {
+        "@babel/code-frame": "^7.27.1",
+        "@babel/parser": "^7.27.2",
+        "@babel/types": "^7.27.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/traverse": {
+      "version": "7.27.3",
+      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.3.tgz",
+      "integrity": "sha512-lId/IfN/Ye1CIu8xG7oKBHXd2iNb2aW1ilPszzGcJug6M8RCKfVNcYhpI5+bMvFYjK7lXIM0R+a+6r8xhHp2FQ==",
+      "dev": true,
+      "dependencies": {
+        "@babel/code-frame": "^7.27.1",
+        "@babel/generator": "^7.27.3",
+        "@babel/parser": "^7.27.3",
+        "@babel/template": "^7.27.2",
+        "@babel/types": "^7.27.3",
+        "debug": "^4.3.1",
+        "globals": "^11.1.0"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/traverse/node_modules/globals": {
+      "version": "11.12.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+      "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/@babel/types": {
+      "version": "7.27.3",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.3.tgz",
+      "integrity": "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==",
+      "dev": true,
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.27.1",
+        "@babel/helper-validator-identifier": "^7.27.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@emotion/is-prop-valid": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz",
+      "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==",
+      "dependencies": {
+        "@emotion/memoize": "^0.8.1"
+      }
+    },
+    "node_modules/@emotion/memoize": {
+      "version": "0.8.1",
+      "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
+      "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA=="
+    },
+    "node_modules/@emotion/unitless": {
+      "version": "0.8.1",
+      "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz",
+      "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ=="
+    },
+    "node_modules/@esbuild/aix-ppc64": {
+      "version": "0.25.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz",
+      "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "aix"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-arm": {
+      "version": "0.25.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz",
+      "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-arm64": {
+      "version": "0.25.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz",
+      "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-x64": {
+      "version": "0.25.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz",
+      "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/darwin-arm64": {
+      "version": "0.25.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz",
+      "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/darwin-x64": {
+      "version": "0.25.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz",
+      "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/freebsd-arm64": {
+      "version": "0.25.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz",
+      "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/freebsd-x64": {
+      "version": "0.25.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz",
+      "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-arm": {
+      "version": "0.25.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz",
+      "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-arm64": {
+      "version": "0.25.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz",
+      "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-ia32": {
+      "version": "0.25.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz",
+      "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-loong64": {
+      "version": "0.25.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz",
+      "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-mips64el": {
+      "version": "0.25.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz",
+      "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==",
+      "cpu": [
+        "mips64el"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-ppc64": {
+      "version": "0.25.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz",
+      "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-riscv64": {
+      "version": "0.25.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz",
+      "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-s390x": {
+      "version": "0.25.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz",
+      "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-x64": {
+      "version": "0.25.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz",
+      "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/netbsd-arm64": {
+      "version": "0.25.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz",
+      "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/netbsd-x64": {
+      "version": "0.25.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz",
+      "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openbsd-arm64": {
+      "version": "0.25.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz",
+      "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openbsd-x64": {
+      "version": "0.25.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz",
+      "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/sunos-x64": {
+      "version": "0.25.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz",
+      "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-arm64": {
+      "version": "0.25.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz",
+      "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-ia32": {
+      "version": "0.25.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz",
+      "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-x64": {
+      "version": "0.25.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz",
+      "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@eslint-community/eslint-utils": {
+      "version": "4.7.0",
+      "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
+      "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
+      "dev": true,
+      "dependencies": {
+        "eslint-visitor-keys": "^3.4.3"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      },
+      "peerDependencies": {
+        "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+      }
+    },
+    "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+      "version": "3.4.3",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+      "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+      "dev": true,
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/@eslint-community/regexpp": {
+      "version": "4.12.1",
+      "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
+      "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
+      "dev": true,
+      "engines": {
+        "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+      }
+    },
+    "node_modules/@eslint/config-array": {
+      "version": "0.20.0",
+      "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz",
+      "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==",
+      "dev": true,
+      "dependencies": {
+        "@eslint/object-schema": "^2.1.6",
+        "debug": "^4.3.1",
+        "minimatch": "^3.1.2"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      }
+    },
+    "node_modules/@eslint/config-helpers": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz",
+      "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==",
+      "dev": true,
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      }
+    },
+    "node_modules/@eslint/core": {
+      "version": "0.14.0",
+      "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz",
+      "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==",
+      "dev": true,
+      "dependencies": {
+        "@types/json-schema": "^7.0.15"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      }
+    },
+    "node_modules/@eslint/eslintrc": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
+      "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
+      "dev": true,
+      "dependencies": {
+        "ajv": "^6.12.4",
+        "debug": "^4.3.2",
+        "espree": "^10.0.1",
+        "globals": "^14.0.0",
+        "ignore": "^5.2.0",
+        "import-fresh": "^3.2.1",
+        "js-yaml": "^4.1.0",
+        "minimatch": "^3.1.2",
+        "strip-json-comments": "^3.1.1"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/@eslint/eslintrc/node_modules/globals": {
+      "version": "14.0.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+      "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/@eslint/js": {
+      "version": "9.27.0",
+      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz",
+      "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==",
+      "dev": true,
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "url": "https://eslint.org/donate"
+      }
+    },
+    "node_modules/@eslint/object-schema": {
+      "version": "2.1.6",
+      "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz",
+      "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
+      "dev": true,
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      }
+    },
+    "node_modules/@eslint/plugin-kit": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz",
+      "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==",
+      "dev": true,
+      "dependencies": {
+        "@eslint/core": "^0.14.0",
+        "levn": "^0.4.1"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      }
+    },
+    "node_modules/@humanfs/core": {
+      "version": "0.19.1",
+      "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+      "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+      "dev": true,
+      "engines": {
+        "node": ">=18.18.0"
+      }
+    },
+    "node_modules/@humanfs/node": {
+      "version": "0.16.6",
+      "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
+      "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
+      "dev": true,
+      "dependencies": {
+        "@humanfs/core": "^0.19.1",
+        "@humanwhocodes/retry": "^0.3.0"
+      },
+      "engines": {
+        "node": ">=18.18.0"
+      }
+    },
+    "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
+      "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
+      "dev": true,
+      "engines": {
+        "node": ">=18.18"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/nzakas"
+      }
+    },
+    "node_modules/@humanwhocodes/module-importer": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+      "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+      "dev": true,
+      "engines": {
+        "node": ">=12.22"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/nzakas"
+      }
+    },
+    "node_modules/@humanwhocodes/retry": {
+      "version": "0.4.3",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+      "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=18.18"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/nzakas"
+      }
+    },
+    "node_modules/@jridgewell/gen-mapping": {
+      "version": "0.3.8",
+      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
+      "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
+      "dev": true,
+      "dependencies": {
+        "@jridgewell/set-array": "^1.2.1",
+        "@jridgewell/sourcemap-codec": "^1.4.10",
+        "@jridgewell/trace-mapping": "^0.3.24"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/resolve-uri": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+      "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/set-array": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+      "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/sourcemap-codec": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+      "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+      "dev": true
+    },
+    "node_modules/@jridgewell/trace-mapping": {
+      "version": "0.3.25",
+      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+      "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+      "dev": true,
+      "dependencies": {
+        "@jridgewell/resolve-uri": "^3.1.0",
+        "@jridgewell/sourcemap-codec": "^1.4.14"
+      }
+    },
+    "node_modules/@nodelib/fs.scandir": {
+      "version": "2.1.5",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+      "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+      "dev": true,
+      "dependencies": {
+        "@nodelib/fs.stat": "2.0.5",
+        "run-parallel": "^1.1.9"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@nodelib/fs.stat": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+      "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+      "dev": true,
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@nodelib/fs.walk": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+      "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+      "dev": true,
+      "dependencies": {
+        "@nodelib/fs.scandir": "2.1.5",
+        "fastq": "^1.6.0"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@rolldown/pluginutils": {
+      "version": "1.0.0-beta.9",
+      "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.9.tgz",
+      "integrity": "sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==",
+      "dev": true
+    },
+    "node_modules/@rollup/rollup-android-arm-eabi": {
+      "version": "4.41.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz",
+      "integrity": "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-android-arm64": {
+      "version": "4.41.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.1.tgz",
+      "integrity": "sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-arm64": {
+      "version": "4.41.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.1.tgz",
+      "integrity": "sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-x64": {
+      "version": "4.41.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.1.tgz",
+      "integrity": "sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-arm64": {
+      "version": "4.41.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.1.tgz",
+      "integrity": "sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-x64": {
+      "version": "4.41.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.1.tgz",
+      "integrity": "sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+      "version": "4.41.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.1.tgz",
+      "integrity": "sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+      "version": "4.41.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.1.tgz",
+      "integrity": "sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-gnu": {
+      "version": "4.41.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.1.tgz",
+      "integrity": "sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-musl": {
+      "version": "4.41.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.1.tgz",
+      "integrity": "sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
+      "version": "4.41.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.1.tgz",
+      "integrity": "sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+      "version": "4.41.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.1.tgz",
+      "integrity": "sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+      "version": "4.41.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.1.tgz",
+      "integrity": "sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-riscv64-musl": {
+      "version": "4.41.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.1.tgz",
+      "integrity": "sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-s390x-gnu": {
+      "version": "4.41.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.1.tgz",
+      "integrity": "sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-gnu": {
+      "version": "4.41.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.1.tgz",
+      "integrity": "sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-musl": {
+      "version": "4.41.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.1.tgz",
+      "integrity": "sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-arm64-msvc": {
+      "version": "4.41.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.1.tgz",
+      "integrity": "sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-ia32-msvc": {
+      "version": "4.41.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.1.tgz",
+      "integrity": "sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-x64-msvc": {
+      "version": "4.41.1",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.1.tgz",
+      "integrity": "sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@types/babel__core": {
+      "version": "7.20.5",
+      "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+      "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+      "dev": true,
+      "dependencies": {
+        "@babel/parser": "^7.20.7",
+        "@babel/types": "^7.20.7",
+        "@types/babel__generator": "*",
+        "@types/babel__template": "*",
+        "@types/babel__traverse": "*"
+      }
+    },
+    "node_modules/@types/babel__generator": {
+      "version": "7.27.0",
+      "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+      "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+      "dev": true,
+      "dependencies": {
+        "@babel/types": "^7.0.0"
+      }
+    },
+    "node_modules/@types/babel__template": {
+      "version": "7.4.4",
+      "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+      "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+      "dev": true,
+      "dependencies": {
+        "@babel/parser": "^7.1.0",
+        "@babel/types": "^7.0.0"
+      }
+    },
+    "node_modules/@types/babel__traverse": {
+      "version": "7.20.7",
+      "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz",
+      "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==",
+      "dev": true,
+      "dependencies": {
+        "@babel/types": "^7.20.7"
+      }
+    },
+    "node_modules/@types/estree": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
+      "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
+      "dev": true
+    },
+    "node_modules/@types/hoist-non-react-statics": {
+      "version": "3.3.6",
+      "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz",
+      "integrity": "sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==",
+      "dependencies": {
+        "@types/react": "*",
+        "hoist-non-react-statics": "^3.3.0"
+      }
+    },
+    "node_modules/@types/json-schema": {
+      "version": "7.0.15",
+      "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+      "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+      "dev": true
+    },
+    "node_modules/@types/react": {
+      "version": "19.1.6",
+      "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.6.tgz",
+      "integrity": "sha512-JeG0rEWak0N6Itr6QUx+X60uQmN+5t3j9r/OVDtWzFXKaj6kD1BwJzOksD0FF6iWxZlbE1kB0q9vtnU2ekqa1Q==",
+      "dependencies": {
+        "csstype": "^3.0.2"
+      }
+    },
+    "node_modules/@types/react-dom": {
+      "version": "19.1.5",
+      "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.5.tgz",
+      "integrity": "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==",
+      "dev": true,
+      "peerDependencies": {
+        "@types/react": "^19.0.0"
+      }
+    },
+    "node_modules/@types/styled-components": {
+      "version": "5.1.34",
+      "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.34.tgz",
+      "integrity": "sha512-mmiVvwpYklFIv9E8qfxuPyIt/OuyIrn6gMOAMOFUO3WJfSrSE+sGUoa4PiZj77Ut7bKZpaa6o1fBKS/4TOEvnA==",
+      "dependencies": {
+        "@types/hoist-non-react-statics": "*",
+        "@types/react": "*",
+        "csstype": "^3.0.2"
+      }
+    },
+    "node_modules/@types/stylis": {
+      "version": "4.2.5",
+      "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz",
+      "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw=="
+    },
+    "node_modules/@typescript-eslint/eslint-plugin": {
+      "version": "8.33.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.0.tgz",
+      "integrity": "sha512-CACyQuqSHt7ma3Ns601xykeBK/rDeZa3w6IS6UtMQbixO5DWy+8TilKkviGDH6jtWCo8FGRKEK5cLLkPvEammQ==",
+      "dev": true,
+      "dependencies": {
+        "@eslint-community/regexpp": "^4.10.0",
+        "@typescript-eslint/scope-manager": "8.33.0",
+        "@typescript-eslint/type-utils": "8.33.0",
+        "@typescript-eslint/utils": "8.33.0",
+        "@typescript-eslint/visitor-keys": "8.33.0",
+        "graphemer": "^1.4.0",
+        "ignore": "^7.0.0",
+        "natural-compare": "^1.4.0",
+        "ts-api-utils": "^2.1.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "@typescript-eslint/parser": "^8.33.0",
+        "eslint": "^8.57.0 || ^9.0.0",
+        "typescript": ">=4.8.4 <5.9.0"
+      }
+    },
+    "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
+      "version": "7.0.4",
+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz",
+      "integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==",
+      "dev": true,
+      "engines": {
+        "node": ">= 4"
+      }
+    },
+    "node_modules/@typescript-eslint/parser": {
+      "version": "8.33.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.33.0.tgz",
+      "integrity": "sha512-JaehZvf6m0yqYp34+RVnihBAChkqeH+tqqhS0GuX1qgPpwLvmTPheKEs6OeCK6hVJgXZHJ2vbjnC9j119auStQ==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/scope-manager": "8.33.0",
+        "@typescript-eslint/types": "8.33.0",
+        "@typescript-eslint/typescript-estree": "8.33.0",
+        "@typescript-eslint/visitor-keys": "8.33.0",
+        "debug": "^4.3.4"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "eslint": "^8.57.0 || ^9.0.0",
+        "typescript": ">=4.8.4 <5.9.0"
+      }
+    },
+    "node_modules/@typescript-eslint/project-service": {
+      "version": "8.33.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.0.tgz",
+      "integrity": "sha512-d1hz0u9l6N+u/gcrk6s6gYdl7/+pp8yHheRTqP6X5hVDKALEaTn8WfGiit7G511yueBEL3OpOEpD+3/MBdoN+A==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/tsconfig-utils": "^8.33.0",
+        "@typescript-eslint/types": "^8.33.0",
+        "debug": "^4.3.4"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@typescript-eslint/scope-manager": {
+      "version": "8.33.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.0.tgz",
+      "integrity": "sha512-LMi/oqrzpqxyO72ltP+dBSP6V0xiUb4saY7WLtxSfiNEBI8m321LLVFU9/QDJxjDQG9/tjSqKz/E3380TEqSTw==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "8.33.0",
+        "@typescript-eslint/visitor-keys": "8.33.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@typescript-eslint/tsconfig-utils": {
+      "version": "8.33.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.0.tgz",
+      "integrity": "sha512-sTkETlbqhEoiFmGr1gsdq5HyVbSOF0145SYDJ/EQmXHtKViCaGvnyLqWFFHtEXoS0J1yU8Wyou2UGmgW88fEug==",
+      "dev": true,
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "typescript": ">=4.8.4 <5.9.0"
+      }
+    },
+    "node_modules/@typescript-eslint/type-utils": {
+      "version": "8.33.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.33.0.tgz",
+      "integrity": "sha512-lScnHNCBqL1QayuSrWeqAL5GmqNdVUQAAMTaCwdYEdWfIrSrOGzyLGRCHXcCixa5NK6i5l0AfSO2oBSjCjf4XQ==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/typescript-estree": "8.33.0",
+        "@typescript-eslint/utils": "8.33.0",
+        "debug": "^4.3.4",
+        "ts-api-utils": "^2.1.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "eslint": "^8.57.0 || ^9.0.0",
+        "typescript": ">=4.8.4 <5.9.0"
+      }
+    },
+    "node_modules/@typescript-eslint/types": {
+      "version": "8.33.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.0.tgz",
+      "integrity": "sha512-DKuXOKpM5IDT1FA2g9x9x1Ug81YuKrzf4mYX8FAVSNu5Wo/LELHWQyM1pQaDkI42bX15PWl0vNPt1uGiIFUOpg==",
+      "dev": true,
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree": {
+      "version": "8.33.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.0.tgz",
+      "integrity": "sha512-vegY4FQoB6jL97Tu/lWRsAiUUp8qJTqzAmENH2k59SJhw0Th1oszb9Idq/FyyONLuNqT1OADJPXfyUNOR8SzAQ==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/project-service": "8.33.0",
+        "@typescript-eslint/tsconfig-utils": "8.33.0",
+        "@typescript-eslint/types": "8.33.0",
+        "@typescript-eslint/visitor-keys": "8.33.0",
+        "debug": "^4.3.4",
+        "fast-glob": "^3.3.2",
+        "is-glob": "^4.0.3",
+        "minimatch": "^9.0.4",
+        "semver": "^7.6.0",
+        "ts-api-utils": "^2.1.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "typescript": ">=4.8.4 <5.9.0"
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+      "dev": true,
+      "dependencies": {
+        "balanced-match": "^1.0.0"
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+      "version": "9.0.5",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+      "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+      "dev": true,
+      "dependencies": {
+        "brace-expansion": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
+      "version": "7.7.2",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+      "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+      "dev": true,
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@typescript-eslint/utils": {
+      "version": "8.33.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.33.0.tgz",
+      "integrity": "sha512-lPFuQaLA9aSNa7D5u2EpRiqdAUhzShwGg/nhpBlc4GR6kcTABttCuyjFs8BcEZ8VWrjCBof/bePhP3Q3fS+Yrw==",
+      "dev": true,
+      "dependencies": {
+        "@eslint-community/eslint-utils": "^4.7.0",
+        "@typescript-eslint/scope-manager": "8.33.0",
+        "@typescript-eslint/types": "8.33.0",
+        "@typescript-eslint/typescript-estree": "8.33.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "eslint": "^8.57.0 || ^9.0.0",
+        "typescript": ">=4.8.4 <5.9.0"
+      }
+    },
+    "node_modules/@typescript-eslint/visitor-keys": {
+      "version": "8.33.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.0.tgz",
+      "integrity": "sha512-7RW7CMYoskiz5OOGAWjJFxgb7c5UNjTG292gYhWeOAcFmYCtVCSqjqSBj5zMhxbXo2JOW95YYrUWJfU0zrpaGQ==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "8.33.0",
+        "eslint-visitor-keys": "^4.2.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@vitejs/plugin-react": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.0.tgz",
+      "integrity": "sha512-JuLWaEqypaJmOJPLWwO335Ig6jSgC1FTONCWAxnqcQthLTK/Yc9aH6hr9z/87xciejbQcnP3GnA1FWUSWeXaeg==",
+      "dev": true,
+      "dependencies": {
+        "@babel/core": "^7.26.10",
+        "@babel/plugin-transform-react-jsx-self": "^7.25.9",
+        "@babel/plugin-transform-react-jsx-source": "^7.25.9",
+        "@rolldown/pluginutils": "1.0.0-beta.9",
+        "@types/babel__core": "^7.20.5",
+        "react-refresh": "^0.17.0"
+      },
+      "engines": {
+        "node": "^14.18.0 || >=16.0.0"
+      },
+      "peerDependencies": {
+        "vite": "^4.2.0 || ^5.0.0 || ^6.0.0"
+      }
+    },
+    "node_modules/acorn": {
+      "version": "8.14.1",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
+      "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
+      "dev": true,
+      "bin": {
+        "acorn": "bin/acorn"
+      },
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/acorn-jsx": {
+      "version": "5.3.2",
+      "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+      "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+      "dev": true,
+      "peerDependencies": {
+        "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+      }
+    },
+    "node_modules/ajv": {
+      "version": "6.12.6",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+      "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+      "dev": true,
+      "dependencies": {
+        "fast-deep-equal": "^3.1.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/epoberezkin"
+      }
+    },
+    "node_modules/ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "dev": true,
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/argparse": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+      "dev": true
+    },
+    "node_modules/asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+    },
+    "node_modules/axios": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
+      "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
+      "dependencies": {
+        "follow-redirects": "^1.15.6",
+        "form-data": "^4.0.0",
+        "proxy-from-env": "^1.1.0"
+      }
+    },
+    "node_modules/balanced-match": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+      "dev": true
+    },
+    "node_modules/brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
+      "dependencies": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "node_modules/braces": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+      "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+      "dev": true,
+      "dependencies": {
+        "fill-range": "^7.1.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/browserslist": {
+      "version": "4.25.0",
+      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz",
+      "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/browserslist"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "dependencies": {
+        "caniuse-lite": "^1.0.30001718",
+        "electron-to-chromium": "^1.5.160",
+        "node-releases": "^2.0.19",
+        "update-browserslist-db": "^1.1.3"
+      },
+      "bin": {
+        "browserslist": "cli.js"
+      },
+      "engines": {
+        "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+      }
+    },
+    "node_modules/call-bind-apply-helpers": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+      "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/callsites": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+      "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/camelize": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
+      "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/caniuse-lite": {
+      "version": "1.0.30001720",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001720.tgz",
+      "integrity": "sha512-Ec/2yV2nNPwb4DnTANEV99ZWwm3ZWfdlfkQbWSDDt+PsXEVYwlhPH8tdMaPunYTKKmz7AnHi2oNEi1GcmKCD8g==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ]
+    },
+    "node_modules/chalk": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^4.1.0",
+        "supports-color": "^7.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/chalk?sponsor=1"
+      }
+    },
+    "node_modules/color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "dev": true,
+      "dependencies": {
+        "color-name": "~1.1.4"
+      },
+      "engines": {
+        "node": ">=7.0.0"
+      }
+    },
+    "node_modules/color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+      "dev": true
+    },
+    "node_modules/combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "dependencies": {
+        "delayed-stream": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+      "dev": true
+    },
+    "node_modules/convert-source-map": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+      "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+      "dev": true
+    },
+    "node_modules/cross-spawn": {
+      "version": "7.0.6",
+      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+      "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+      "dev": true,
+      "dependencies": {
+        "path-key": "^3.1.0",
+        "shebang-command": "^2.0.0",
+        "which": "^2.0.1"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/css-color-keywords": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+      "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/css-to-react-native": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
+      "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
+      "dependencies": {
+        "camelize": "^1.0.0",
+        "css-color-keywords": "^1.0.0",
+        "postcss-value-parser": "^4.0.2"
+      }
+    },
+    "node_modules/csstype": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+      "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
+    },
+    "node_modules/debug": {
+      "version": "4.4.1",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
+      "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
+      "dev": true,
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/deep-is": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+      "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+      "dev": true
+    },
+    "node_modules/delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/dunder-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+      "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.2.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/echarts": {
+      "version": "5.6.0",
+      "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz",
+      "integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==",
+      "dependencies": {
+        "tslib": "2.3.0",
+        "zrender": "5.6.1"
+      }
+    },
+    "node_modules/echarts-for-react": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/echarts-for-react/-/echarts-for-react-3.0.2.tgz",
+      "integrity": "sha512-DRwIiTzx8JfwPOVgGttDytBqdp5VzCSyMRIxubgU/g2n9y3VLUmF2FK7Icmg/sNVkv4+rktmrLN9w22U2yy3fA==",
+      "dependencies": {
+        "fast-deep-equal": "^3.1.3",
+        "size-sensor": "^1.0.1"
+      },
+      "peerDependencies": {
+        "echarts": "^3.0.0 || ^4.0.0 || ^5.0.0",
+        "react": "^15.0.0 || >=16.0.0"
+      }
+    },
+    "node_modules/echarts-liquidfill": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/echarts-liquidfill/-/echarts-liquidfill-3.1.0.tgz",
+      "integrity": "sha512-5Dlqs/jTsdTUAsd+K5LPLLTgrbbNORUSBQyk8PSy1Mg2zgHDWm83FmvA4s0ooNepCJojFYRITTQ4GU1UUSKYLw==",
+      "peerDependencies": {
+        "echarts": "^5.0.1"
+      }
+    },
+    "node_modules/electron-to-chromium": {
+      "version": "1.5.161",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.161.tgz",
+      "integrity": "sha512-hwtetwfKNZo/UlwHIVBlKZVdy7o8bIZxxKs0Mv/ROPiQQQmDgdm5a+KvKtBsxM8ZjFzTaCeLoodZ8jiBE3o9rA==",
+      "dev": true
+    },
+    "node_modules/es-define-property": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+      "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-errors": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-object-atoms": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+      "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+      "dependencies": {
+        "es-errors": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-set-tostringtag": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+      "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.6",
+        "has-tostringtag": "^1.0.2",
+        "hasown": "^2.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/esbuild": {
+      "version": "0.25.5",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
+      "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==",
+      "dev": true,
+      "hasInstallScript": true,
+      "bin": {
+        "esbuild": "bin/esbuild"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "optionalDependencies": {
+        "@esbuild/aix-ppc64": "0.25.5",
+        "@esbuild/android-arm": "0.25.5",
+        "@esbuild/android-arm64": "0.25.5",
+        "@esbuild/android-x64": "0.25.5",
+        "@esbuild/darwin-arm64": "0.25.5",
+        "@esbuild/darwin-x64": "0.25.5",
+        "@esbuild/freebsd-arm64": "0.25.5",
+        "@esbuild/freebsd-x64": "0.25.5",
+        "@esbuild/linux-arm": "0.25.5",
+        "@esbuild/linux-arm64": "0.25.5",
+        "@esbuild/linux-ia32": "0.25.5",
+        "@esbuild/linux-loong64": "0.25.5",
+        "@esbuild/linux-mips64el": "0.25.5",
+        "@esbuild/linux-ppc64": "0.25.5",
+        "@esbuild/linux-riscv64": "0.25.5",
+        "@esbuild/linux-s390x": "0.25.5",
+        "@esbuild/linux-x64": "0.25.5",
+        "@esbuild/netbsd-arm64": "0.25.5",
+        "@esbuild/netbsd-x64": "0.25.5",
+        "@esbuild/openbsd-arm64": "0.25.5",
+        "@esbuild/openbsd-x64": "0.25.5",
+        "@esbuild/sunos-x64": "0.25.5",
+        "@esbuild/win32-arm64": "0.25.5",
+        "@esbuild/win32-ia32": "0.25.5",
+        "@esbuild/win32-x64": "0.25.5"
+      }
+    },
+    "node_modules/escalade": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+      "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/escape-string-regexp": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+      "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/eslint": {
+      "version": "9.27.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz",
+      "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==",
+      "dev": true,
+      "dependencies": {
+        "@eslint-community/eslint-utils": "^4.2.0",
+        "@eslint-community/regexpp": "^4.12.1",
+        "@eslint/config-array": "^0.20.0",
+        "@eslint/config-helpers": "^0.2.1",
+        "@eslint/core": "^0.14.0",
+        "@eslint/eslintrc": "^3.3.1",
+        "@eslint/js": "9.27.0",
+        "@eslint/plugin-kit": "^0.3.1",
+        "@humanfs/node": "^0.16.6",
+        "@humanwhocodes/module-importer": "^1.0.1",
+        "@humanwhocodes/retry": "^0.4.2",
+        "@types/estree": "^1.0.6",
+        "@types/json-schema": "^7.0.15",
+        "ajv": "^6.12.4",
+        "chalk": "^4.0.0",
+        "cross-spawn": "^7.0.6",
+        "debug": "^4.3.2",
+        "escape-string-regexp": "^4.0.0",
+        "eslint-scope": "^8.3.0",
+        "eslint-visitor-keys": "^4.2.0",
+        "espree": "^10.3.0",
+        "esquery": "^1.5.0",
+        "esutils": "^2.0.2",
+        "fast-deep-equal": "^3.1.3",
+        "file-entry-cache": "^8.0.0",
+        "find-up": "^5.0.0",
+        "glob-parent": "^6.0.2",
+        "ignore": "^5.2.0",
+        "imurmurhash": "^0.1.4",
+        "is-glob": "^4.0.0",
+        "json-stable-stringify-without-jsonify": "^1.0.1",
+        "lodash.merge": "^4.6.2",
+        "minimatch": "^3.1.2",
+        "natural-compare": "^1.4.0",
+        "optionator": "^0.9.3"
+      },
+      "bin": {
+        "eslint": "bin/eslint.js"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "url": "https://eslint.org/donate"
+      },
+      "peerDependencies": {
+        "jiti": "*"
+      },
+      "peerDependenciesMeta": {
+        "jiti": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/eslint-plugin-react-hooks": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz",
+      "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      },
+      "peerDependencies": {
+        "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
+      }
+    },
+    "node_modules/eslint-plugin-react-refresh": {
+      "version": "0.4.20",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.20.tgz",
+      "integrity": "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==",
+      "dev": true,
+      "peerDependencies": {
+        "eslint": ">=8.40"
+      }
+    },
+    "node_modules/eslint-scope": {
+      "version": "8.3.0",
+      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz",
+      "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==",
+      "dev": true,
+      "dependencies": {
+        "esrecurse": "^4.3.0",
+        "estraverse": "^5.2.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/eslint-visitor-keys": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
+      "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+      "dev": true,
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/espree": {
+      "version": "10.3.0",
+      "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
+      "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
+      "dev": true,
+      "dependencies": {
+        "acorn": "^8.14.0",
+        "acorn-jsx": "^5.3.2",
+        "eslint-visitor-keys": "^4.2.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/esquery": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+      "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+      "dev": true,
+      "dependencies": {
+        "estraverse": "^5.1.0"
+      },
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
+    "node_modules/esrecurse": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+      "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+      "dev": true,
+      "dependencies": {
+        "estraverse": "^5.2.0"
+      },
+      "engines": {
+        "node": ">=4.0"
+      }
+    },
+    "node_modules/estraverse": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+      "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+      "dev": true,
+      "engines": {
+        "node": ">=4.0"
+      }
+    },
+    "node_modules/esutils": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+      "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/fast-deep-equal": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+    },
+    "node_modules/fast-glob": {
+      "version": "3.3.3",
+      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+      "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+      "dev": true,
+      "dependencies": {
+        "@nodelib/fs.stat": "^2.0.2",
+        "@nodelib/fs.walk": "^1.2.3",
+        "glob-parent": "^5.1.2",
+        "merge2": "^1.3.0",
+        "micromatch": "^4.0.8"
+      },
+      "engines": {
+        "node": ">=8.6.0"
+      }
+    },
+    "node_modules/fast-glob/node_modules/glob-parent": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+      "dev": true,
+      "dependencies": {
+        "is-glob": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/fast-json-stable-stringify": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+      "dev": true
+    },
+    "node_modules/fast-levenshtein": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+      "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+      "dev": true
+    },
+    "node_modules/fastq": {
+      "version": "1.19.1",
+      "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
+      "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
+      "dev": true,
+      "dependencies": {
+        "reusify": "^1.0.4"
+      }
+    },
+    "node_modules/file-entry-cache": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+      "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+      "dev": true,
+      "dependencies": {
+        "flat-cache": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=16.0.0"
+      }
+    },
+    "node_modules/fill-range": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+      "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+      "dev": true,
+      "dependencies": {
+        "to-regex-range": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/find-up": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+      "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+      "dev": true,
+      "dependencies": {
+        "locate-path": "^6.0.0",
+        "path-exists": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/flat-cache": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+      "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+      "dev": true,
+      "dependencies": {
+        "flatted": "^3.2.9",
+        "keyv": "^4.5.4"
+      },
+      "engines": {
+        "node": ">=16"
+      }
+    },
+    "node_modules/flatted": {
+      "version": "3.3.3",
+      "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+      "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+      "dev": true
+    },
+    "node_modules/follow-redirects": {
+      "version": "1.15.9",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
+      "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/RubenVerborgh"
+        }
+      ],
+      "engines": {
+        "node": ">=4.0"
+      },
+      "peerDependenciesMeta": {
+        "debug": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/form-data": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
+      "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "es-set-tostringtag": "^2.1.0",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/fsevents": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/function-bind": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/gensync": {
+      "version": "1.0.0-beta.2",
+      "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+      "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/get-intrinsic": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+      "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "es-define-property": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "es-object-atoms": "^1.1.1",
+        "function-bind": "^1.1.2",
+        "get-proto": "^1.0.1",
+        "gopd": "^1.2.0",
+        "has-symbols": "^1.1.0",
+        "hasown": "^2.0.2",
+        "math-intrinsics": "^1.1.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+      "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+      "dependencies": {
+        "dunder-proto": "^1.0.1",
+        "es-object-atoms": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/glob-parent": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+      "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+      "dev": true,
+      "dependencies": {
+        "is-glob": "^4.0.3"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
+    "node_modules/globals": {
+      "version": "16.2.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-16.2.0.tgz",
+      "integrity": "sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg==",
+      "dev": true,
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/gopd": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/graphemer": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+      "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+      "dev": true
+    },
+    "node_modules/has-flag": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/has-symbols": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-tostringtag": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+      "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+      "dependencies": {
+        "has-symbols": "^1.0.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/hasown": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+      "dependencies": {
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/hoist-non-react-statics": {
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+      "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+      "dependencies": {
+        "react-is": "^16.7.0"
+      }
+    },
+    "node_modules/ignore": {
+      "version": "5.3.2",
+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+      "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+      "dev": true,
+      "engines": {
+        "node": ">= 4"
+      }
+    },
+    "node_modules/import-fresh": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+      "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+      "dev": true,
+      "dependencies": {
+        "parent-module": "^1.0.0",
+        "resolve-from": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/imurmurhash": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+      "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.8.19"
+      }
+    },
+    "node_modules/is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-glob": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+      "dev": true,
+      "dependencies": {
+        "is-extglob": "^2.1.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-number": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.12.0"
+      }
+    },
+    "node_modules/isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+      "dev": true
+    },
+    "node_modules/js-tokens": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+      "dev": true
+    },
+    "node_modules/js-yaml": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+      "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+      "dev": true,
+      "dependencies": {
+        "argparse": "^2.0.1"
+      },
+      "bin": {
+        "js-yaml": "bin/js-yaml.js"
+      }
+    },
+    "node_modules/jsesc": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+      "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+      "dev": true,
+      "bin": {
+        "jsesc": "bin/jsesc"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/json-buffer": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+      "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+      "dev": true
+    },
+    "node_modules/json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+      "dev": true
+    },
+    "node_modules/json-stable-stringify-without-jsonify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+      "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+      "dev": true
+    },
+    "node_modules/json5": {
+      "version": "2.2.3",
+      "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+      "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+      "dev": true,
+      "bin": {
+        "json5": "lib/cli.js"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/keyv": {
+      "version": "4.5.4",
+      "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+      "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+      "dev": true,
+      "dependencies": {
+        "json-buffer": "3.0.1"
+      }
+    },
+    "node_modules/levn": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+      "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+      "dev": true,
+      "dependencies": {
+        "prelude-ls": "^1.2.1",
+        "type-check": "~0.4.0"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/locate-path": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+      "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+      "dev": true,
+      "dependencies": {
+        "p-locate": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/lodash.merge": {
+      "version": "4.6.2",
+      "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+      "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+      "dev": true
+    },
+    "node_modules/lru-cache": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+      "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+      "dev": true,
+      "dependencies": {
+        "yallist": "^3.0.2"
+      }
+    },
+    "node_modules/math-intrinsics": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+      "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/merge2": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+      "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+      "dev": true,
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/micromatch": {
+      "version": "4.0.8",
+      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+      "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+      "dev": true,
+      "dependencies": {
+        "braces": "^3.0.3",
+        "picomatch": "^2.3.1"
+      },
+      "engines": {
+        "node": ">=8.6"
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/minimatch": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+      "dev": true,
+      "dependencies": {
+        "brace-expansion": "^1.1.7"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "dev": true
+    },
+    "node_modules/nanoid": {
+      "version": "3.3.11",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+      "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
+      "engines": {
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+      }
+    },
+    "node_modules/natural-compare": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+      "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+      "dev": true
+    },
+    "node_modules/node-releases": {
+      "version": "2.0.19",
+      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
+      "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
+      "dev": true
+    },
+    "node_modules/optionator": {
+      "version": "0.9.4",
+      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+      "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+      "dev": true,
+      "dependencies": {
+        "deep-is": "^0.1.3",
+        "fast-levenshtein": "^2.0.6",
+        "levn": "^0.4.1",
+        "prelude-ls": "^1.2.1",
+        "type-check": "^0.4.0",
+        "word-wrap": "^1.2.5"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/p-limit": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+      "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+      "dev": true,
+      "dependencies": {
+        "yocto-queue": "^0.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/p-locate": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+      "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+      "dev": true,
+      "dependencies": {
+        "p-limit": "^3.0.2"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/parent-module": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+      "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+      "dev": true,
+      "dependencies": {
+        "callsites": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/path-exists": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+      "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/path-key": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+      "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
+    },
+    "node_modules/picomatch": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+      "dev": true,
+      "engines": {
+        "node": ">=8.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/postcss": {
+      "version": "8.4.49",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
+      "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/postcss"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "dependencies": {
+        "nanoid": "^3.3.7",
+        "picocolors": "^1.1.1",
+        "source-map-js": "^1.2.1"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      }
+    },
+    "node_modules/postcss-value-parser": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+      "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
+    },
+    "node_modules/prelude-ls": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+      "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/proxy-from-env": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+    },
+    "node_modules/punycode": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+      "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/queue-microtask": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+      "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ]
+    },
+    "node_modules/react": {
+      "version": "19.1.0",
+      "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
+      "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/react-dom": {
+      "version": "19.1.0",
+      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
+      "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
+      "dependencies": {
+        "scheduler": "^0.26.0"
+      },
+      "peerDependencies": {
+        "react": "^19.1.0"
+      }
+    },
+    "node_modules/react-is": {
+      "version": "16.13.1",
+      "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+      "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+    },
+    "node_modules/react-refresh": {
+      "version": "0.17.0",
+      "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+      "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/resolve-from": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+      "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/reusify": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+      "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+      "dev": true,
+      "engines": {
+        "iojs": ">=1.0.0",
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/rollup": {
+      "version": "4.41.1",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz",
+      "integrity": "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==",
+      "dev": true,
+      "dependencies": {
+        "@types/estree": "1.0.7"
+      },
+      "bin": {
+        "rollup": "dist/bin/rollup"
+      },
+      "engines": {
+        "node": ">=18.0.0",
+        "npm": ">=8.0.0"
+      },
+      "optionalDependencies": {
+        "@rollup/rollup-android-arm-eabi": "4.41.1",
+        "@rollup/rollup-android-arm64": "4.41.1",
+        "@rollup/rollup-darwin-arm64": "4.41.1",
+        "@rollup/rollup-darwin-x64": "4.41.1",
+        "@rollup/rollup-freebsd-arm64": "4.41.1",
+        "@rollup/rollup-freebsd-x64": "4.41.1",
+        "@rollup/rollup-linux-arm-gnueabihf": "4.41.1",
+        "@rollup/rollup-linux-arm-musleabihf": "4.41.1",
+        "@rollup/rollup-linux-arm64-gnu": "4.41.1",
+        "@rollup/rollup-linux-arm64-musl": "4.41.1",
+        "@rollup/rollup-linux-loongarch64-gnu": "4.41.1",
+        "@rollup/rollup-linux-powerpc64le-gnu": "4.41.1",
+        "@rollup/rollup-linux-riscv64-gnu": "4.41.1",
+        "@rollup/rollup-linux-riscv64-musl": "4.41.1",
+        "@rollup/rollup-linux-s390x-gnu": "4.41.1",
+        "@rollup/rollup-linux-x64-gnu": "4.41.1",
+        "@rollup/rollup-linux-x64-musl": "4.41.1",
+        "@rollup/rollup-win32-arm64-msvc": "4.41.1",
+        "@rollup/rollup-win32-ia32-msvc": "4.41.1",
+        "@rollup/rollup-win32-x64-msvc": "4.41.1",
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/run-parallel": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+      "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "dependencies": {
+        "queue-microtask": "^1.2.2"
+      }
+    },
+    "node_modules/scheduler": {
+      "version": "0.26.0",
+      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
+      "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="
+    },
+    "node_modules/semver": {
+      "version": "6.3.1",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+      "dev": true,
+      "bin": {
+        "semver": "bin/semver.js"
+      }
+    },
+    "node_modules/shallowequal": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+      "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
+    },
+    "node_modules/shebang-command": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+      "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+      "dev": true,
+      "dependencies": {
+        "shebang-regex": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/shebang-regex": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+      "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/size-sensor": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/size-sensor/-/size-sensor-1.0.2.tgz",
+      "integrity": "sha512-2NCmWxY7A9pYKGXNBfteo4hy14gWu47rg5692peVMst6lQLPKrVjhY+UTEsPI5ceFRJSl3gVgMYaUi/hKuaiKw=="
+    },
+    "node_modules/source-map-js": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/strip-json-comments": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+      "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/styled-components": {
+      "version": "6.1.18",
+      "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.18.tgz",
+      "integrity": "sha512-Mvf3gJFzZCkhjY2Y/Fx9z1m3dxbza0uI9H1CbNZm/jSHCojzJhQ0R7bByrlFJINnMzz/gPulpoFFGymNwrsMcw==",
+      "dependencies": {
+        "@emotion/is-prop-valid": "1.2.2",
+        "@emotion/unitless": "0.8.1",
+        "@types/stylis": "4.2.5",
+        "css-to-react-native": "3.2.0",
+        "csstype": "3.1.3",
+        "postcss": "8.4.49",
+        "shallowequal": "1.1.0",
+        "stylis": "4.3.2",
+        "tslib": "2.6.2"
+      },
+      "engines": {
+        "node": ">= 16"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/styled-components"
+      },
+      "peerDependencies": {
+        "react": ">= 16.8.0",
+        "react-dom": ">= 16.8.0"
+      }
+    },
+    "node_modules/styled-components/node_modules/tslib": {
+      "version": "2.6.2",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+      "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+    },
+    "node_modules/stylis": {
+      "version": "4.3.2",
+      "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz",
+      "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg=="
+    },
+    "node_modules/supports-color": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+      "dev": true,
+      "dependencies": {
+        "has-flag": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/tinyglobby": {
+      "version": "0.2.14",
+      "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
+      "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
+      "dev": true,
+      "dependencies": {
+        "fdir": "^6.4.4",
+        "picomatch": "^4.0.2"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/SuperchupuDev"
+      }
+    },
+    "node_modules/tinyglobby/node_modules/fdir": {
+      "version": "6.4.5",
+      "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz",
+      "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==",
+      "dev": true,
+      "peerDependencies": {
+        "picomatch": "^3 || ^4"
+      },
+      "peerDependenciesMeta": {
+        "picomatch": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/tinyglobby/node_modules/picomatch": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
+      "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/to-regex-range": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+      "dev": true,
+      "dependencies": {
+        "is-number": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=8.0"
+      }
+    },
+    "node_modules/ts-api-utils": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
+      "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=18.12"
+      },
+      "peerDependencies": {
+        "typescript": ">=4.8.4"
+      }
+    },
+    "node_modules/tslib": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
+      "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
+    },
+    "node_modules/type-check": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+      "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+      "dev": true,
+      "dependencies": {
+        "prelude-ls": "^1.2.1"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/typescript": {
+      "version": "5.8.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
+      "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
+      "dev": true,
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=14.17"
+      }
+    },
+    "node_modules/typescript-eslint": {
+      "version": "8.33.0",
+      "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.33.0.tgz",
+      "integrity": "sha512-5YmNhF24ylCsvdNW2oJwMzTbaeO4bg90KeGtMjUw0AGtHksgEPLRTUil+coHwCfiu4QjVJFnjp94DmU6zV7DhQ==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/eslint-plugin": "8.33.0",
+        "@typescript-eslint/parser": "8.33.0",
+        "@typescript-eslint/utils": "8.33.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "eslint": "^8.57.0 || ^9.0.0",
+        "typescript": ">=4.8.4 <5.9.0"
+      }
+    },
+    "node_modules/update-browserslist-db": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
+      "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/browserslist"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "dependencies": {
+        "escalade": "^3.2.0",
+        "picocolors": "^1.1.1"
+      },
+      "bin": {
+        "update-browserslist-db": "cli.js"
+      },
+      "peerDependencies": {
+        "browserslist": ">= 4.21.0"
+      }
+    },
+    "node_modules/uri-js": {
+      "version": "4.4.1",
+      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+      "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+      "dev": true,
+      "dependencies": {
+        "punycode": "^2.1.0"
+      }
+    },
+    "node_modules/vite": {
+      "version": "6.3.5",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
+      "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
+      "dev": true,
+      "dependencies": {
+        "esbuild": "^0.25.0",
+        "fdir": "^6.4.4",
+        "picomatch": "^4.0.2",
+        "postcss": "^8.5.3",
+        "rollup": "^4.34.9",
+        "tinyglobby": "^0.2.13"
+      },
+      "bin": {
+        "vite": "bin/vite.js"
+      },
+      "engines": {
+        "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/vitejs/vite?sponsor=1"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.3"
+      },
+      "peerDependencies": {
+        "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+        "jiti": ">=1.21.0",
+        "less": "*",
+        "lightningcss": "^1.21.0",
+        "sass": "*",
+        "sass-embedded": "*",
+        "stylus": "*",
+        "sugarss": "*",
+        "terser": "^5.16.0",
+        "tsx": "^4.8.1",
+        "yaml": "^2.4.2"
+      },
+      "peerDependenciesMeta": {
+        "@types/node": {
+          "optional": true
+        },
+        "jiti": {
+          "optional": true
+        },
+        "less": {
+          "optional": true
+        },
+        "lightningcss": {
+          "optional": true
+        },
+        "sass": {
+          "optional": true
+        },
+        "sass-embedded": {
+          "optional": true
+        },
+        "stylus": {
+          "optional": true
+        },
+        "sugarss": {
+          "optional": true
+        },
+        "terser": {
+          "optional": true
+        },
+        "tsx": {
+          "optional": true
+        },
+        "yaml": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vite/node_modules/fdir": {
+      "version": "6.4.5",
+      "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz",
+      "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==",
+      "dev": true,
+      "peerDependencies": {
+        "picomatch": "^3 || ^4"
+      },
+      "peerDependenciesMeta": {
+        "picomatch": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vite/node_modules/picomatch": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
+      "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/vite/node_modules/postcss": {
+      "version": "8.5.4",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz",
+      "integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/postcss"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "dependencies": {
+        "nanoid": "^3.3.11",
+        "picocolors": "^1.1.1",
+        "source-map-js": "^1.2.1"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      }
+    },
+    "node_modules/which": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+      "dev": true,
+      "dependencies": {
+        "isexe": "^2.0.0"
+      },
+      "bin": {
+        "node-which": "bin/node-which"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/word-wrap": {
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+      "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/yallist": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+      "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+      "dev": true
+    },
+    "node_modules/yocto-queue": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+      "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/zrender": {
+      "version": "5.6.1",
+      "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.1.tgz",
+      "integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==",
+      "dependencies": {
+        "tslib": "2.3.0"
+      }
+    }
+  }
+}

+ 35 - 0
package.json

@@ -0,0 +1,35 @@
+{
+  "name": "bi2025",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "tsc -b && vite build",
+    "lint": "eslint .",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "@types/styled-components": "^5.1.34",
+    "axios": "^1.9.0",
+    "echarts": "^5.6.0",
+    "echarts-for-react": "^3.0.2",
+    "echarts-liquidfill": "^3.1.0",
+    "react": "^19.1.0",
+    "react-dom": "^19.1.0",
+    "styled-components": "^6.1.18"
+  },
+  "devDependencies": {
+    "@eslint/js": "^9.25.0",
+    "@types/react": "^19.1.2",
+    "@types/react-dom": "^19.1.2",
+    "@vitejs/plugin-react": "^4.4.1",
+    "eslint": "^9.25.0",
+    "eslint-plugin-react-hooks": "^5.2.0",
+    "eslint-plugin-react-refresh": "^0.4.19",
+    "globals": "^16.0.0",
+    "typescript": "~5.8.3",
+    "typescript-eslint": "^8.30.1",
+    "vite": "^6.3.5"
+  }
+}

BIN
public/arrow_down.png


BIN
public/arrow_up.png


BIN
public/baoyangwanchengicon.png


BIN
public/baoyangweiwanchengicon.png


BIN
public/bottomBg.png


+ 0 - 0
public/bottom_decoration.png


+ 0 - 0
public/button_bg.png


BIN
public/choutibeijingtu_bg.png


BIN
public/chuangkoushudizuo_bg.png


BIN
public/component_header_bg.png


BIN
public/daijiaofei_icon.png


BIN
public/daijiuzheng_icon.png


BIN
public/daiquhao_icon.png


+ 9 - 0
public/device_example.svg

@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 120" fill="none">
+  <rect x="10" y="10" width="180" height="100" rx="5" fill="#1A2A4C" stroke="#4080FF" stroke-width="2"/>
+  <rect x="20" y="20" width="160" height="60" rx="3" fill="#2C3E5D"/>
+  <rect x="30" y="85" width="40" height="15" rx="2" fill="#4080FF" fill-opacity="0.5"/>
+  <rect x="80" y="85" width="40" height="15" rx="2" fill="#4080FF" fill-opacity="0.5"/>
+  <rect x="130" y="85" width="40" height="15" rx="2" fill="#4080FF" fill-opacity="0.5"/>
+  <circle cx="100" cy="50" r="25" fill="#4080FF" fill-opacity="0.3" stroke="#4080FF" stroke-width="1"/>
+  <path d="M90 50 L97 57 L110 43" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+</svg> 

+ 0 - 0
public/drawer_close_btn.png


+ 0 - 0
public/drawer_title_bg.png


+ 0 - 0
public/drawer_title_icon.png


BIN
public/fayaocard_bg.png


BIN
public/jianquan_tishi.png


BIN
public/jiantouzhuangshi.png


BIN
public/jinriyucezongliang_bg.png


BIN
public/juxingkapian_bg.png


BIN
public/kucunbeijing.png


BIN
public/kucunicon.png


BIN
public/lanseshuangbiaopan.png


BIN
public/layout_content_bg.png


BIN
public/layout_header_bg.png


BIN
public/layout_header_bg2.png


BIN
public/layout_header_bg3.png


BIN
public/lvseshuangbiaopan.png


+ 4 - 0
public/maintenance_icon.svg

@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#FFFFFF" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
+  <path d="M3.6 9l1.5 1.5M18.9 15l1.5 1.5M16 3l-3.5 3.5M8 13l-5 5M19.4 6.8l-4.7 4.7M12 12l-4.7 4.7" />
+  <path d="M9 9l1.5 1.5M13.5 13.5L15 15M10.5 4.5L12 6M6 10.5L7.5 12M3 21l7.5-7.5M13.5 10.5l7.5-7.5" />
+</svg> 

BIN
public/paihangkapian.png


BIN
public/qingseshuangbiaopan.png


BIN
public/qipaozhaotu_bg.png


BIN
public/queyaolvicon.png


BIN
public/shebeibaoyang_bg.png


BIN
public/shebeichukuheshuicon.png


BIN
public/shebeirukuheshuicon.png


BIN
public/shebeixunjian_bg.png


BIN
public/shebeiyilanbanner.png


BIN
public/side_benyue.png


BIN
public/side_benyue_gray.png


BIN
public/side_bottom.png


BIN
public/side_bottom_light.png


BIN
public/side_nav_1.png


BIN
public/side_nav_1_active.png


BIN
public/side_nav_2.png


BIN
public/side_nav_2_active.png


BIN
public/side_nav_3.png


BIN
public/side_nav_3_active.png


BIN
public/side_nav_active_highlight.png


BIN
public/side_nav_bg.png


BIN
public/side_other.png


BIN
public/side_other_light.png


BIN
public/tabweixuanzhong.png


+ 3 - 0
public/tabweixuanzhong.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 40" fill="none">
+  <rect x="0" y="0" width="100" height="34" rx="6" fill="#1A2A4C" fill-opacity="0.5" />
+</svg> 

BIN
public/tabxuanzhong.png


+ 4 - 0
public/tabxuanzhong.svg

@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 40" fill="none">
+  <rect x="0" y="0" width="100" height="34" rx="6" fill="#4080FF" />
+  <path d="M42 34 L50 40 L58 34 Z" fill="#4080FF" />
+</svg> 

+ 0 - 0
public/title-bg.png


+ 12 - 0
public/title_decorator.svg

@@ -0,0 +1,12 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none">
+  <defs>
+    <linearGradient id="blueGradient" x1="0%" y1="0%" x2="0%" y2="100%">
+      <stop offset="0%" stop-color="#4D9EFF" />
+      <stop offset="100%" stop-color="#167EFF" />
+    </linearGradient>
+  </defs>
+  <!-- 左侧竖条 -->
+  <rect x="2" y="2" width="4" height="9" rx="0.5" fill="url(#blueGradient)" />
+  <!-- 右侧竖条 -->
+  <rect x="10" y="5" width="4" height="6" rx="0.5" fill="url(#blueGradient)" />
+</svg> 

+ 13 - 0
public/title_icon.svg

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="28" height="22" viewBox="0 0 28 22" fill="none">
+  <defs>
+    <linearGradient id="blueGradient" x1="0%" y1="0%" x2="0%" y2="100%">
+      <stop offset="0%" stop-color="#60BBFF" />
+      <stop offset="100%" stop-color="#0066CC" />
+    </linearGradient>
+  </defs>
+  <!-- 左侧大矩形 - 明显向右倾斜 -->
+  <path d="M2 3 L12 3 L14 17 L4 17 Z" fill="url(#blueGradient)" />
+  <!-- 左侧小矩形 - 明显向右倾斜 -->
+  <path d="M13 8 L18 8 L19 13 L14 13 Z" fill="url(#blueGradient)" />
+</svg> 

+ 13 - 0
public/title_right.svg

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="28" height="22" viewBox="0 0 28 22" fill="none">
+  <defs>
+    <linearGradient id="blueGradient" x1="0%" y1="0%" x2="0%" y2="100%">
+      <stop offset="0%" stop-color="#60BBFF" />
+      <stop offset="100%" stop-color="#0066CC" />
+    </linearGradient>
+  </defs>
+  <!-- 右侧大矩形 - 明显向左倾斜 (因为整体已翻转) -->
+  <path d="M26 3 L16 3 L14 17 L24 17 Z" fill="url(#blueGradient)" />
+  <!-- 右侧小矩形 - 明显向左倾斜 (因为整体已翻转) -->
+  <path d="M15 8 L10 8 L9 13 L14 13 Z" fill="url(#blueGradient)" />
+</svg> 

+ 1 - 0
public/vite.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

BIN
public/yugexiaohaotianshuicon.png


BIN
public/yujingdizuo_bg.png


BIN
public/zaikupinzgingshuicon.png


BIN
public/zhongjianbiaotizhuangshi_bg.png


BIN
public/ziseshuangbiaopan.png


BIN
public/侧航左1H.png


BIN
public/侧航左1N.png


BIN
public/侧航左2H.png


BIN
public/侧航左2N.png


BIN
public/侧航左3H.png


BIN
public/侧航左3N.png


BIN
public/侧航左4H.png


BIN
public/侧航左4N.png


BIN
public/侧航左5H.png


BIN
public/侧航左5N.png


+ 41 - 0
src/App.css

@@ -0,0 +1,41 @@
+#root {
+  /* max-width: 1280px; */
+  margin: 0 auto;
+  text-align: center;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}

+ 937 - 0
src/App.tsx

@@ -0,0 +1,937 @@
+import React, { useState, useEffect, createContext, useContext } from 'react';
+import styled from 'styled-components';
+import {
+  DashboardContainer,
+  Header,
+  Content,
+  ContentInner,
+  ComponentContainer,
+  TimeDisplay,
+  Footer,
+  SecondScreenContentInner,
+  ThirdScreenContentInner
+} from './components/styled/DashboardStyles';
+import HospitalStatusChart from './components/charts/HospitalStatusChart';
+import MedicineStatusChart from './components/charts/MedicineStatusChart';
+import HourlyTrendChart from './components/charts/HourlyTrendChart';
+import TodayForecastTotalChart from './components/charts/TodayForecastTotalChart';
+import WindowMonitorChart from './components/charts/WindowMonitorChart';
+import PatientMedicineTimeChart from './components/charts/PatientMedicineTimeChart';
+import InventoryManagement from './components/charts/InventoryManagement';
+import MedicineExpiryChart from './components/charts/MedicineExpiryChart';
+import DeviceOverviewChart from './components/charts/DeviceOverviewChart';
+import DailyPrescriptionChart from './components/charts/DailyPrescriptionChart';
+import DailyMedicineQuantityChart from './components/charts/DailyMedicineQuantityChart';
+import PeakHoursChart from './components/charts/PeakHoursChart';
+import DualGaugeChart from './components/charts/DualGaugeChart';
+import PurpleDualGaugeChart from './components/charts/PurpleDualGaugeChart';
+import CyanDualGaugeChart from './components/charts/CyanDualGaugeChart';
+import GreenDualGaugeChart from './components/charts/GreenDualGaugeChart';
+import PharmacistWorkloadChart from './components/charts/PharmacistWorkloadChart';
+import PrescriptionDistributionChart from './components/charts/PrescriptionDistributionChart';
+import ConsumedMedicineTop8Chart from './components/charts/ConsumedMedicineTop8Chart';
+import PrescriptionBoxDistributionChart from './components/charts/PrescriptionBoxDistributionChart';
+import DepartmentPrescriptionTop10Chart from './components/charts/DepartmentPrescriptionTop10Chart';
+import DrawerButtons from './components/DrawerButtons';
+import { apiGet } from './utils/request'; // 导入请求工具
+import { getCurrentDateTime } from './utils/dateUtils'; // 导入日期工具
+import AuthCheck from './components/AuthCheck';
+import LoadingSpinner from './components/LoadingSpinner';
+import AuthTest from './components/AuthTest';
+import { useAuth } from './hooks/useAuth';
+import './App.css';
+
+// 模块刷新事件前缀
+const REFRESH_EVENT_PREFIX = 'refresh-module-';
+
+// 全局刷新事件名称(用于兼容旧代码)
+export const GLOBAL_REFRESH_EVENT = 'refresh-dashboard-data';
+
+// 全局默认日期变量,供所有组件使用
+export let GLOBAL_DEFAULT_DATE = '';
+
+// 提供一个全局函数来获取默认日期
+// 这确保了即使组件在默认日期加载前初始化,也能在需要时获取到正确的日期
+export function getDefaultDate() {
+  // 如果全局默认日期已设置且不为空,返回它
+  if (GLOBAL_DEFAULT_DATE && GLOBAL_DEFAULT_DATE.trim() !== '') {
+    return GLOBAL_DEFAULT_DATE;
+  }
+  // 否则返回当前日期
+  return getCurrentDateTime();
+}
+
+// 创建日期上下文
+interface DateContextType {
+  defaultDateTime: string;
+  selectedMonth: number;
+}
+
+export const DateContext = createContext<DateContextType>({ 
+  defaultDateTime: '', 
+  selectedMonth: 0 
+});
+
+// 提供一个钩子函数,便于组件使用日期上下文
+export const useDefaultDate = () => useContext(DateContext);
+
+// 获取基于选择月份的时间范围
+export const getSelectedMonthRange = (defaultDateTime: string, selectedMonth: number) => {
+  // 修改:优先使用默认时间作为基准,如果没有默认时间才使用当前时间
+  // 这样确保第三屏模块也能优先使用默认时间
+  const baseDate = defaultDateTime ? new Date(defaultDateTime) : new Date();
+  
+  // 计算目标月份(当前月减去偏移量)
+  const targetDate = new Date(baseDate.getFullYear(), baseDate.getMonth() - selectedMonth, 1);
+  const year = targetDate.getFullYear();
+  const month = targetDate.getMonth(); // 0-11
+  
+  // 目标月第一天
+  const startDate = new Date(year, month, 1);
+  // 目标月最后一天
+  const endDate = new Date(year, month + 1, 0);
+  
+  // 格式化为 YYYY-MM-DD
+  const formatDate = (date: Date) => {
+    const y = date.getFullYear();
+    const m = String(date.getMonth() + 1).padStart(2, '0');
+    const d = String(date.getDate()).padStart(2, '0');
+    return `${y}-${m}-${d}`;
+  };
+  
+  console.log(`月份选择逻辑 - 基准时间: ${baseDate.toISOString()}, 选择偏移: ${selectedMonth}, 目标月份: ${year}-${String(month + 1).padStart(2, '0')}`);
+  
+  return {
+    startTime: formatDate(startDate),
+    endTime: formatDate(endDate)
+  };
+};
+
+// 简化版:全局刷新函数
+export const refreshAllModules = () => {
+  const event = new CustomEvent(GLOBAL_REFRESH_EVENT);
+  window.dispatchEvent(event);
+  console.log('触发全局数据刷新');
+};
+
+// 模块配置,包含id和code
+export const MODULE_CONFIG = {
+  // 第一屏模块
+  MEDICINE_STATUS: { id: 'medicine-status', code: '18' }, // 发药状态一览
+  TODAY_FORECAST: { id: 'today-forecast', code: '19' }, // 今日预测总量
+  HOURLY_TREND: { id: 'hourly-trend', code: '21' }, // 分时趋势
+  WINDOW_MONITOR: { id: 'window-monitor', code: '20' }, // 窗口监控
+  HOSPITAL_STATUS: { id: 'hospital-status', code: '24' }, // 追溯码情况
+  PATIENT_MEDICINE_TIME: { id: 'patient-medicine-time', code: '22' }, // 患者取药时间
+  
+  // 第二屏模块
+  INVENTORY_MANAGEMENT: { id: 'inventory-management', code: '25' }, // 库存管理
+  MEDICINE_EXPIRY: { id: 'medicine-expiry', code: '26' }, // 药品效期
+  DEVICE_MAINTENANCE: { id: 'device-maintenance', code: '28' }, // 设备保养
+  DEVICE_INSPECTION: { id: 'device-inspection', code: '29' }, // 设备巡检
+  
+  // 第三屏模块
+  DAILY_PRESCRIPTION: { id: 'daily-prescription', code: '33' }, // 每周处方概况
+  DAILY_MEDICINE_QUANTITY: { id: 'daily-medicine-quantity', code: '34' }, // 每周药品概况  
+  DUAL_GAUGE: { id: 'dual-gauge', code: '37' }, // 第2模块:取药效率监控
+  PHARMACIST_WORKLOAD: { id: 'pharmacist-workload', code: '42' }, // 药师工作量统计
+  PURPLE_DUAL_GAUGE: { id: 'purple-dual-gauge', code: '43' }, // 紫色双仪表盘
+  GREEN_DUAL_GAUGE: { id: 'green-dual-gauge', code: '44' }, // 第5模块:绿色双仪表盘(月度转换状态)
+  CONSUMED_MEDICINE_TOP8: { id: 'consumed-medicine-top8', code: '41' }, // 消耗药品Top8
+  PEAK_HOURS: { id: 'peak-hours', code: '38' }, // 每周高峰时刻
+  CYAN_DUAL_GAUGE: { id: 'cyan-dual-gauge', code: '39' }, // 第8模块:青色双仪表盘
+  DEPARTMENT_PRESCRIPTION_TOP10: { id: 'department-prescription-top10', code: '40' }, // 科室处方量Top10
+  PRESCRIPTION_DISTRIBUTION: { id: 'prescription-distribution', code: '35' }, // 处方品规数分布
+  PRESCRIPTION_BOX_DISTRIBUTION: { id: 'prescription-box-distribution', code: '36' } // 处方盒数分布
+};
+
+// 添加背景装饰元素
+const BackgroundDecorator = styled.div`
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  pointer-events: none;
+  overflow: hidden;
+  z-index: 0;
+  
+  &::before, &::after {
+    content: '';
+    position: absolute;
+    background-size: contain;
+    background-repeat: no-repeat;
+    opacity: 0.1;
+  }
+  
+  &::before {
+    width: 300px;
+    height: 300px;
+    top: 10%;
+    left: -100px;
+    background-image: radial-gradient(circle, rgba(0, 180, 255, 0.3) 0%, rgba(0, 0, 0, 0) 70%);
+  }
+  
+  &::after {
+    width: 400px;
+    height: 400px;
+    bottom: 5%;
+    right: -150px;
+    background-image: radial-gradient(circle, rgba(0, 255, 200, 0.2) 0%, rgba(0, 0, 0, 0) 70%);
+  }
+`;
+
+// 底部导航按钮样式
+const NavButton = styled.button<{ active: boolean }>`
+  background: transparent;
+  background-image: url(${props => props.active ? '/tabxuanzhong.png' : '/tabweixuanzhong.png'});
+  background-size: 100% 100%;
+  background-repeat: no-repeat;
+  color: ${props => props.active ? '#0A1433' : '#CCDDFF'};
+  display: flex;
+  position: relative;
+  top:-1.5vw;
+  justify-content: center ;
+  align-items: center;
+  border: none;
+  width: 7vw;
+  margin: 0 0.2vw;
+  font-size: 0.8vw;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  text-align: center;
+  
+  &:focus {
+    outline: none;
+  }
+`;
+
+// 第二屏左侧按钮容器
+const SecondScreenLeftButtons = styled.div`
+  position: fixed;
+  left: 1vw;
+  top: 79%;
+  transform: translateY(-50%);
+  display: flex;
+  flex-direction: column;
+  z-index: 1000;
+`;
+
+// 第二屏左侧单个按钮
+const SecondScreenButton = styled.div<{ isActive: boolean; buttonIndex: number }>`
+  width: 1.5vw;
+  height: 6vh;
+  background-image: ${props => 
+    props.isActive 
+      ? `url('/侧航左${props.buttonIndex}H.png')` 
+      : `url('/侧航左${props.buttonIndex}N.png')`};
+  background-repeat: no-repeat;
+  background-size: 100% 100%;
+  background-position: center;
+  border: none;
+  border-radius: 0;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  color: ${props => props.isActive ? '#0A1433' : '#CCDDFF'};
+  font-size: 0.6vw;
+  font-family: 'DingTalk JinBuTi', 'PingFang SC', 'Microsoft YaHei', sans-serif;
+  cursor: pointer;
+  position: relative;
+  padding: 0;
+  writing-mode: vertical-lr;
+  text-orientation: upright;
+  letter-spacing: 0.1vw;
+  margin-bottom: -1vh;
+  transition: all 0.3s ease;
+  
+  &:last-child {
+    margin-bottom: 0;
+  }
+`;
+
+// 第三屏左下角月份选择按钮容器
+const ThirdScreenMonthButtons = styled.div`
+  position: fixed;
+  left: 1vw;
+  bottom: 7.5vh;
+  display: flex;
+  flex-direction: column;
+  z-index: 1000;
+`;
+
+// 第三屏月份选择单个按钮
+const MonthSelectButton = styled.div<{ isActive: boolean; isCurrentMonth?: boolean; isBottomButton?: boolean }>`
+  width: 1.2vw;
+  height: ${props => props.isCurrentMonth ? '6.5vh' : '5.5vh'};
+  background: ${props => {
+    // 如果是本月按钮
+    if (props.isCurrentMonth) {
+      return props.isActive ? "url('/side_benyue.png')" : "url('/side_benyue_gray.png')";
+    }
+    // 如果是最底部按钮
+    if (props.isBottomButton) {
+      return props.isActive ? "url('/side_bottom_light.png')" : "url('/side_bottom.png')";
+    }
+    // 其他月份按钮
+    return props.isActive ? "url('/side_other_light.png')" : "url('/side_other.png')";
+  }};
+  background-size: 100% 100%;
+  background-repeat: no-repeat;
+  background-position: center;
+  border-radius: 0.3vw;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  color: ${props => props.isActive ? '#0A1433' : '#CCDDFF'}; /* 修改颜色:选中时为#0A1433,未选中时为#CCDDFF */
+  font-size: 0.7vw;
+  cursor: pointer;
+  font-family:'PingFang SC';
+  margin-bottom: -1.5vh;
+  writing-mode: vertical-lr; /* 文字竖向排布 */
+  text-orientation: upright; /* 文字保持正立 */
+
+
+  &:hover {
+    background: ${props => {
+      // 悬浮时,本月按钮
+      if (props.isCurrentMonth) {
+        return props.isActive ? "url('/side_benyue.png')" : "url('/side_benyue_gray.png')";
+      }
+      // 悬浮时,最底部按钮
+      if (props.isBottomButton) {
+        return props.isActive ? "url('/side_bottom_light.png')" : "url('/side_bottom.png')";
+      }
+      // 悬浮时,其他按钮
+      return props.isActive ? "url('/side_other_light.png')" : "url('/side_other.png')";
+    }};
+    background-size: 100% 100%;
+    background-repeat: no-repeat;
+    background-position: center;
+ 
+  }
+  
+  &:last-child {
+    margin-bottom: 0;
+  }
+  
+`;
+
+// 默认刷新间隔(毫秒)- 当接口获取失败时使用
+const DEFAULT_REFRESH_INTERVAL = 60000; // 默认1分钟
+
+// 刷新时间配置类型
+interface RefreshRateItem {
+  code: string;
+  value: string;
+  expandOne?: number; // 屏幕编号字段
+  expandTwo?: number; // 第三屏位置字段  
+  defaultFlag?: boolean; // 模块显示/隐藏标志
+  [key: string]: any;
+}
+
+// 设备字典类型
+interface EquipDictItem {
+  id: number;
+  code: string;
+  name: string;
+  value: string | null;
+  type: string;
+  defaultFlag: boolean;
+  sort: number;
+  createUser: string | null;
+  createTime: string | null;
+  updateUser: string | null;
+  updateTime: string;
+  deleteUser: string | null;
+  deleteTime: string | null;
+  delFlag: boolean;
+}
+
+const App: React.FC = () => {
+  // 鉴权检查
+  const { isAuthenticated, isLoading: authLoading, error: authError } = useAuth();
+  
+  const [currentTime, setCurrentTime] = useState<string>('');
+  const [refreshRates, setRefreshRates] = useState<RefreshRateItem[]>([]);
+  const [hospitalName, setHospitalName] = useState('药房运营监控');
+  const [defaultDateTime, setDefaultDateTime] = useState('');
+  const [isLoading, setIsLoading] = useState(true); // 添加加载状态
+  const [isDateReady, setIsDateReady] = useState(false); // 添加日期准备状态
+  const [currentScreen, setCurrentScreen] = useState<number>(1); // 当前显示的屏幕,1表示第一屏,2表示第二屏
+  const [activeSecondScreenButton, setActiveSecondScreenButton] = useState<string>('');
+  const [equipDict, setEquipDict] = useState<EquipDictItem[]>([]); // 设备字典数据
+  const [selectedMonth, setSelectedMonth] = useState<number>(0); // 选中的月份,0表示本月,1表示上个月,以此类推
+  const [showModule6, setShowModule6] = useState<boolean>(true); // 控制第6个模块的显示(默认显示消耗药品Top8)
+  const [showModule9, setShowModule9] = useState<boolean>(false); // 控制第9个模块(处方盒数分布)的显示
+  const [thirdScreenModules, setThirdScreenModules] = useState<React.ReactNode[]>([]);
+  const [forceUpdate, setForceUpdate] = useState<number>(0); // 强制更新计数器 // 第三屏模块动态排列
+
+  // 第三屏模块映射表 - 根据code映射到对应的组件
+  const getThirdScreenModuleComponent = (code: string): React.ReactNode => {
+    switch (code) {
+      case '33': // 每日总方数统计
+        return <DailyPrescriptionChart />;
+      case '37': // 取药效率监控
+        return <DualGaugeChart />;
+      case '42': // 药师工作量统计
+        return <PharmacistWorkloadChart />;
+      case '34': // 每日药品数量概况
+        return <DailyMedicineQuantityChart />;
+      case '44': // 绿色双仪表盘(月度转换状态)
+        return <GreenDualGaugeChart />;
+      case '35': // 处方品规数分布
+        return <PrescriptionDistributionChart />;
+      case '38': // 高峰时刻
+        return <PeakHoursChart />;
+      case '39': // 青色双仪表盘
+        return <CyanDualGaugeChart />;
+      case '36': // 处方盒数分布
+        return <PrescriptionBoxDistributionChart />;
+      case '41': // 消耗药品Top8
+        return <ConsumedMedicineTop8Chart />;
+      case '43': // 紫色双仪表盘
+        return <PurpleDualGaugeChart />;
+      case '40': // 科室处方量Top10
+        return <DepartmentPrescriptionTop10Chart />;
+      default:
+        return null;
+    }
+  };
+
+  // 使用默认的第三屏模块排列(当接口失败时使用)
+  const useDefaultThirdScreenArrangement = () => {
+    const defaultModules = [
+      <ComponentContainer key="default-1"><DailyPrescriptionChart /></ComponentContainer>,
+      <ComponentContainer key="default-2"><DualGaugeChart /></ComponentContainer>,
+      <ComponentContainer key="default-3"><PharmacistWorkloadChart /></ComponentContainer>,
+      <ComponentContainer key="default-4"><DailyMedicineQuantityChart /></ComponentContainer>,
+      <ComponentContainer key="default-5"><GreenDualGaugeChart /></ComponentContainer>,
+      <ComponentContainer key="default-6"><PrescriptionDistributionChart /></ComponentContainer>,
+      <ComponentContainer key="default-7"><PeakHoursChart /></ComponentContainer>,
+      <ComponentContainer key="default-8"><CyanDualGaugeChart /></ComponentContainer>,
+      <ComponentContainer key="default-9"><PrescriptionBoxDistributionChart /></ComponentContainer>,
+    ];
+    setThirdScreenModules(defaultModules);
+    console.log('使用默认第三屏模块排列');
+  };
+
+  // 根据defaultFlag和expandTwo字段排列第三屏模块
+  const arrangeThirdScreenModules = (refreshRatesData: RefreshRateItem[]) => {
+    console.log('=== arrangeThirdScreenModules 开始执行 ===');
+    console.log('接收到的配置数据长度:', refreshRatesData.length);
+    
+    const moduleComponents: React.ReactNode[] = [];
+    
+    // 遍历刷新配置数据,根据defaultFlag和expandTwo字段生成模块
+    refreshRatesData.forEach((item) => {
+      // 首先检查是否是第三屏模块(expandOne=3)且defaultFlag为true
+      const isThirdScreen = item.expandOne === 3;
+      const shouldDisplay = item.defaultFlag === true && isThirdScreen;
+      
+      if (shouldDisplay) {
+        const position = item.expandTwo || 0;
+        // 确保位置在1-9范围内
+        if (position >= 1 && position <= 9) {
+          const component = getThirdScreenModuleComponent(item.code);
+          if (component) {
+            // 计算Grid坐标
+            const row = Math.floor((position - 1) / 3) + 1;
+            const col = ((position - 1) % 3) + 1;
+            
+            moduleComponents.push(
+              <ComponentContainer 
+                key={`module-${item.code}-${position}-${Date.now()}`} 
+                style={{ 
+                  gridArea: `${row} / ${col} / ${row + 1} / ${col + 1}`
+                }}
+              >
+                {component}
+              </ComponentContainer>
+            );
+            
+            console.log(`第三屏模块 ${item.code} (expandTwo: ${item.expandTwo}) 放置在Grid位置 ${position},Grid坐标: 行${row} 列${col},defaultFlag: ${item.defaultFlag}`);
+          }
+        } else if (position === 0) {
+          // 如果expandTwo为0或未设置,但defaultFlag为true,记录日志但不显示
+          console.log(`第三屏模块 ${item.code} defaultFlag为true,但expandTwo未设置有效位置 (${item.expandTwo})`);
+        }
+      } else {
+        if (isThirdScreen) {
+          console.log(`第三屏模块 ${item.code} 被隐藏,defaultFlag: ${item.defaultFlag}`);
+        }
+        // 非第三屏模块不记录日志,避免干扰
+      }
+    });
+    
+    // 直接设置模块数组,不依赖位置顺序,完全由grid-area控制
+    setThirdScreenModules(moduleComponents);
+    
+    // 触发强制更新以确保重新渲染
+    setForceUpdate(prev => prev + 1);
+    
+    console.log('第三屏模块排列完成,共', moduleComponents.length, '个模块显示');
+    console.log('thirdScreenModules 数组:', moduleComponents.map((comp: any) => comp.key));
+    
+    // 显示最终的模块配置
+    const modulePositions = refreshRatesData
+      .filter(item => item.expandOne === 3 && item.defaultFlag === true && (item.expandTwo || 0) >= 1 && (item.expandTwo || 0) <= 9)
+      .map(item => ({ code: item.code, position: item.expandTwo || 0 }))
+      .sort((a, b) => a.position - b.position);
+    
+    console.log('模块位置映射:', modulePositions.map(m => `位置${m.position}: code${m.code}`).join(', '));
+  };
+
+  // 获取默认日期 - 只在组件挂载时执行一次
+  useEffect(() => {
+    setIsLoading(true); // 开始加载
+    setIsDateReady(false); // 重置日期准备状态
+    
+    // 初始化默认的第三屏模块排列
+    useDefaultThirdScreenArrangement();
+    
+    // 在进行任何其他操作前,先获取默认日期
+    apiGet('/getSysParameter', { params: { parameterCode: 'DEFAULT_DATE' } })
+      .then(res => {
+        // 检查返回值是否存在且非空
+        if (res.data && res.data.data && res.data.data.value && res.data.data.value.trim() !== '') {
+          const dateValue = res.data.data.value;
+          setDefaultDateTime(dateValue);
+          // 更新全局变量,供其他组件使用
+          GLOBAL_DEFAULT_DATE = dateValue;
+          console.log('获取到默认日期:', dateValue);
+        } else {
+          // 如果接口返回成功但值为空,将全局变量设置为空字符串
+          // 这样getDefaultDate()函数会每次都返回当前时间
+          setDefaultDateTime('');
+          GLOBAL_DEFAULT_DATE = '';
+          console.log('接口返回的日期为空,全局变量设置为空,将使用实时当前时间');
+        }
+      })
+      .catch(error => {
+        console.error('获取默认日期失败:', error);
+        // 如果接口失败,将全局变量设置为空字符串
+        // 这样getDefaultDate()函数会每次都返回当前时间
+        setDefaultDateTime('');
+        GLOBAL_DEFAULT_DATE = '';
+        console.log('接口请求失败,全局变量设置为空,将使用实时当前时间');
+      })
+      .finally(() => {
+        setIsDateReady(true); // 日期已准备好
+      });
+  }, []);
+  
+  // 所有其他数据加载都依赖于日期准备好
+  useEffect(() => {
+    // 只有当日期准备好后,才加载其他数据
+    if (!isDateReady) return;
+    
+    // 获取医院名称
+    apiGet('/getSysParameter', { params: { parameterCode: 'HOSPITAL_NAME' } })
+      .then(res => {
+        if (res.data && res.data.data && res.data.data.value) {
+          setHospitalName(res.data.data.value);
+        }
+      })
+      .catch(error => {
+        console.error('获取医院名称失败:', error);
+      });
+      
+    // 获取刷新配置
+    const fetchRefreshRates = async () => {
+      try {
+        // 调用获取刷新时间的接口
+        const response = await apiGet('/getDict', { params: { dictCode: 'refresh_rate' } });
+        if (response.data && response.data.data && Array.isArray(response.data.data)) {
+          setRefreshRates(response.data.data);
+          console.log('获取到模块刷新时间配置:', response.data.data);
+          
+          // 过滤出第三屏相关的配置进行调试
+          const thirdScreenConfigs = response.data.data.filter((item: RefreshRateItem) => 
+            item.expandOne === 3 || (parseInt(item.code) >= 33 && parseInt(item.code) <= 44)
+          );
+          console.log('第三屏相关配置:', thirdScreenConfigs);
+          
+          // 根据defaultFlag和expandTwo字段排列第三屏模块
+          arrangeThirdScreenModules(response.data.data);
+        } else {
+          console.warn('刷新时间接口返回格式不符合预期:', response.data);
+          // 如果接口失败,使用默认排列
+          useDefaultThirdScreenArrangement();
+        }
+      } catch (error) {
+        console.error('获取刷新时间失败:', error);
+        // 如果接口失败,使用默认排列
+        useDefaultThirdScreenArrangement();
+      }
+    };
+
+    // 获取设备字典数据
+    const fetchEquipDict = async () => {
+      try {
+        const response = await apiGet('/equipMana/getEquipDict');
+        if (response.data && response.data.data && Array.isArray(response.data.data)) {
+          const equipData = response.data.data;
+          setEquipDict(equipData);
+          console.log('获取到设备字典数据:', equipData);
+          
+          // 如果有设备数据,自动选择第一个设备
+          if (equipData.length > 0) {
+            const firstEquipCode = equipData[0].code;
+            setActiveSecondScreenButton(firstEquipCode);
+            console.log('自动选择第一个设备:', firstEquipCode, equipData[0].name);
+          }
+        } else {
+          console.warn('设备字典接口返回格式不符合预期:', response.data);
+        }
+      } catch (error) {
+        console.error('获取设备字典数据失败:', error);
+      }
+    };
+
+    // 并行获取数据
+    Promise.all([fetchRefreshRates(), fetchEquipDict()])
+      .finally(() => {
+        setIsLoading(false); // 所有数据加载完成
+      });
+  }, [isDateReady]);
+
+  // 为所有模块设置独立的刷新定时器,而不是统一定时器
+  useEffect(() => {
+    if (!refreshRates.length) return;
+    
+    console.log('=== 开始设置模块刷新定时器 ===');
+    console.log('refreshRates配置:', refreshRates);
+    
+    // 检查模块22是否在配置中
+    const module22Config = refreshRates.find(item => item.code === '22');
+    console.log('🔍 模块22在配置中?', module22Config ? '是' : '否');
+    if (module22Config) {
+      console.log('🔍 模块22配置详情:', module22Config);
+    } else {
+      console.log('❌ 模块22不在刷新配置中!这可能是1秒刷新的原因');
+    }
+    
+    // 输出所有模块代码列表
+    const allModuleCodes = refreshRates.map(item => item.code);
+    console.log('📋 所有模块代码列表:', allModuleCodes);
+    
+    const timers: number[] = [];
+    
+    // 为每个模块设置独立的定时器
+    refreshRates.forEach(item => {
+      const moduleCode = item.code;
+      const refreshInterval = Math.max(10, parseInt(item.value) || 60); // 最小10秒,默认60秒
+      
+
+      
+      // 为每个模块设置独立的定时器
+      const timer = setInterval(() => {
+        // 触发特定模块的刷新事件
+        const moduleRefreshEvent = new CustomEvent(GLOBAL_REFRESH_EVENT, {
+          detail: { moduleCode: moduleCode }
+        });
+        window.dispatchEvent(moduleRefreshEvent);
+
+      }, refreshInterval * 1000);
+      
+      timers.push(timer);
+    });
+    
+    // 清理所有定时器
+    return () => {
+      timers.forEach(timer => clearInterval(timer));
+    };
+  }, [refreshRates]);
+
+  // 更新当前时间
+  useEffect(() => {
+    const updateTime = () => {
+      const now = new Date();
+      const year = now.getFullYear();
+      const month = String(now.getMonth() + 1).padStart(2, '0');
+      const day = String(now.getDate()).padStart(2, '0');
+      const hours = String(now.getHours()).padStart(2, '0');
+      const minutes = String(now.getMinutes()).padStart(2, '0');
+
+      setCurrentTime(`${year}-${month}-${day} ${hours}:${minutes}`);
+    };
+
+    updateTime();
+    const timer = setInterval(updateTime, 60000);
+
+    return () => clearInterval(timer);
+  }, []);
+
+  // 切换屏幕显示
+  const handleScreenChange = (screenNum: number) => {
+    setCurrentScreen(screenNum);
+  };
+
+  // 第二屏按钮点击处理
+  const handleSecondScreenButtonClick = (buttonName: string) => {
+    setActiveSecondScreenButton(buttonName);
+    console.log(`点击了第二屏按钮: ${buttonName}`);
+  };
+
+  // 第三屏月份选择处理
+  const handleMonthSelect = (monthOffset: number) => {
+    setSelectedMonth(monthOffset);
+    console.log(`选择了月份偏移: ${monthOffset}`);
+    // 触发全局刷新,让所有第三屏组件使用新的月份
+    refreshAllModules();
+  };
+
+  // 生成月份按钮数据
+  const generateMonthButtons = () => {
+    const buttons = [];
+    // 修复:使用与getSelectedMonthRange相同的基准时间
+    const baseDate = defaultDateTime ? new Date(defaultDateTime) : new Date();
+    
+    for (let i = 0; i < 6; i++) {
+      const targetDate = new Date(baseDate.getFullYear(), baseDate.getMonth() - i, 1);
+      const year = targetDate.getFullYear();
+      const month = targetDate.getMonth() + 1;
+      
+      let label;
+      if (i === 0) {
+        label = '本月';
+      } else {
+        label = `${month}`;
+      }
+      
+      buttons.push({
+        offset: i,
+        label: label,
+        isCurrentMonth: i === 0,
+        year: year,
+        month: month
+      });
+    }
+    
+    return buttons;
+  };
+
+  // 渲染第一屏内容
+  const renderFirstScreen = () => (
+    <ContentInner>
+      {/* 容器1 - 发药状态一览 */}
+      <ComponentContainer>
+        <MedicineStatusChart />
+      </ComponentContainer>
+
+      {/* 容器2 - 今日预测总量 */}
+      <ComponentContainer>
+        <TodayForecastTotalChart />
+      </ComponentContainer>
+
+      {/* 容器3 - 分时趋势 */}
+      <ComponentContainer>
+        <HourlyTrendChart />
+      </ComponentContainer>
+
+      {/* 容器4 - 窗口监控 */}
+      <ComponentContainer>
+        <WindowMonitorChart />
+      </ComponentContainer>
+
+      {/* 容器5 - 追溯码情况 */}
+      <ComponentContainer>
+        <HospitalStatusChart />
+      </ComponentContainer>
+
+      {/* 容器6 - 患者取药时间 */}
+      <ComponentContainer>
+        <PatientMedicineTimeChart />
+      </ComponentContainer>
+    </ContentInner>
+  );
+
+  // 渲染第二屏内容
+  const renderSecondScreen = () => (
+    <SecondScreenContentInner>
+      {/* 第二屏第一个模块 - 库存管理 (左上) */}
+      <ComponentContainer>
+        <InventoryManagement 
+          selectedEquipCode={activeSecondScreenButton}
+          selectedEquipName={
+            equipDict.find(item => item.code === activeSecondScreenButton)?.name || activeSecondScreenButton
+          }
+        />
+      </ComponentContainer>
+      
+      {/* 第二屏第二个模块 - 药品效期 (左下) */}
+      <ComponentContainer>
+        <MedicineExpiryChart 
+          selectedEquipCode={activeSecondScreenButton}
+          selectedEquipName={
+            equipDict.find(item => item.code === activeSecondScreenButton)?.name || activeSecondScreenButton
+          }
+        />
+      </ComponentContainer>
+      
+      {/* 第二屏第三个模块 - 设备一览 (右侧) */}
+      <ComponentContainer>
+        <DeviceOverviewChart 
+          selectedEquipCode={activeSecondScreenButton}
+          selectedEquipName={
+            equipDict.find(item => item.code === activeSecondScreenButton)?.name || activeSecondScreenButton
+          }
+          selectedEquipValue={
+            equipDict.find(item => item.code === activeSecondScreenButton)?.value || ''
+          }
+        />
+      </ComponentContainer>
+    </SecondScreenContentInner>
+  );
+
+  // 渲染第三屏内容 - 使用动态排列的模块
+  const renderThirdScreen = () => {
+    console.log('renderThirdScreen 被调用,thirdScreenModules.length:', thirdScreenModules.length);
+    console.log('当前使用:', thirdScreenModules.length > 0 ? '动态模块' : '默认布局');
+    return (
+      <ThirdScreenContentInner key={`third-screen-${forceUpdate}`}>
+        {thirdScreenModules.length > 0 ? (
+          // 直接渲染所有模块,位置完全由每个模块的grid-area样式控制
+          thirdScreenModules
+        ) : (
+        // 如果动态模块还没有加载完成,显示加载状态或使用默认布局
+        <>
+          <ComponentContainer><DailyPrescriptionChart /></ComponentContainer>
+          <ComponentContainer><DualGaugeChart /></ComponentContainer>
+          <ComponentContainer><PharmacistWorkloadChart /></ComponentContainer>
+          <ComponentContainer><DailyMedicineQuantityChart /></ComponentContainer>
+          <ComponentContainer><GreenDualGaugeChart /></ComponentContainer>
+          <ComponentContainer><PrescriptionDistributionChart /></ComponentContainer>
+          <ComponentContainer><PeakHoursChart /></ComponentContainer>
+          <ComponentContainer><CyanDualGaugeChart /></ComponentContainer>
+          <ComponentContainer><PrescriptionBoxDistributionChart /></ComponentContainer>
+        </>
+      )}
+    </ThirdScreenContentInner>
+    );
+  };
+
+  // 如果正在进行鉴权检查,显示加载状态
+  if (authLoading) {
+    return <LoadingSpinner text="正在验证权限..." />;
+  }
+  
+  // 如果鉴权失败,显示无权限界面
+  if (!isAuthenticated) {
+    return <AuthCheck>{null}</AuthCheck>;
+  }
+
+  return (
+    <DashboardContainer>
+      <BackgroundDecorator />
+      <Header secondScreen={currentScreen === 2} thirdScreen={currentScreen === 3}>
+        {/* 院名,左上角显示 */}
+        <div style={{
+          position: 'absolute',
+          left: '2vw',
+          top: '68%',
+          transform: 'translateY(-50%)',
+          borderRadius: '0.4vw',
+          padding: '0.3vw 1vw',
+          fontSize: '1.5vw',
+          color: '#fff',
+          fontWeight: 500,
+          fontFamily: 'DingTalk JinBuTi',
+          letterSpacing: '1px',
+          boxShadow: '0 0 6px rgba(0,0,0,0.12)'
+        }}>
+          {hospitalName}
+        </div>
+        {/* <h1>本日运营数据</h1> */}
+        <TimeDisplay>{currentTime}</TimeDisplay>
+      </Header>
+      
+      {isLoading ? (
+        <div style={{
+          display: 'flex',
+          justifyContent: 'center',
+          alignItems: 'center',
+          height: 'calc(100vh - 10vh)',
+          color: '#fff',
+          fontSize: '1.5vw'
+        }}>
+          数据加载中...
+        </div>
+      ) : (
+        <DateContext.Provider value={{ defaultDateTime, selectedMonth }}>
+          <Content>
+            {currentScreen === 1 ? renderFirstScreen() : 
+             currentScreen === 2 ? renderSecondScreen() : 
+             renderThirdScreen()}
+          </Content>
+        </DateContext.Provider>
+      )}
+      
+      {/* 第二屏左侧按钮 - 只有当设备数量大于1时才显示 */}
+      {currentScreen === 2 && equipDict.length > 1 && (
+        <SecondScreenLeftButtons>
+          {/* 根据设备字典数组动态渲染按钮 */}
+          {equipDict.map((item, index) => (
+            <SecondScreenButton 
+              key={item.id}
+              isActive={activeSecondScreenButton === item.code} 
+              buttonIndex={index + 1}
+              onClick={() => handleSecondScreenButtonClick(item.code)}
+            >
+              {item.name}
+            </SecondScreenButton>
+          ))}
+        </SecondScreenLeftButtons>
+      )}
+      
+      {/* 第三屏左下角月份选择按钮 */}
+      {currentScreen === 3 && (
+        <ThirdScreenMonthButtons>
+          {generateMonthButtons().map((button, index, array) => (
+            <MonthSelectButton
+              key={button.offset}
+              isActive={selectedMonth === button.offset}
+              isCurrentMonth={button.isCurrentMonth}
+              isBottomButton={index === array.length - 1}
+              onClick={() => handleMonthSelect(button.offset)}
+            >
+              {button.label}
+            </MonthSelectButton>
+          ))}
+        </ThirdScreenMonthButtons>
+      )}
+
+      {/* 添加右侧抽屉按钮 */}
+      <DrawerButtons currentScreen={currentScreen} equipCode={activeSecondScreenButton} dateTime={defaultDateTime} />
+      
+      {/* 开发模式鉴权测试工具 */}
+      {import.meta.env.DEV && <AuthTest />}
+      
+      {/* 底部导航 */}
+      <Footer>
+        <NavButton 
+          active={currentScreen === 1}
+          onClick={() => handleScreenChange(1)}
+        >
+          本日数据
+        </NavButton>
+        <NavButton 
+          active={currentScreen === 2}
+          onClick={() => handleScreenChange(2)}
+        >
+          库存/设备
+        </NavButton>
+        <NavButton 
+          active={currentScreen === 3}
+          onClick={() => handleScreenChange(3)}
+        >
+          阶段统计
+        </NavButton>
+      </Footer>
+    </DashboardContainer>
+  );
+};
+
+export default App;

BIN
src/assets/DingTalk JinBuTi.ttf


BIN
src/assets/DingTalk Sans.ttf


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
src/assets/react.svg


+ 43 - 0
src/components/AuthCheck.tsx

@@ -0,0 +1,43 @@
+/*
+ * @Author: code4eat awesomedema@gmail.com
+ * @Date: 2025-06-25 14:32:48
+ * @LastEditors: code4eat awesomedema@gmail.com
+ * @LastEditTime: 2025-06-25 14:44:24
+ * @FilePath: /bi2025/src/components/AuthCheck.tsx
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ */
+import React from 'react';
+import styled from 'styled-components';
+
+const AuthContainer = styled.div`
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100vw;
+  height: 100vh;
+  background: transparent;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  z-index: 9999;
+`;
+
+const AuthImage = styled.img`
+  width:30vw;
+  height: 20vw;
+  object-fit: contain;
+`;
+
+interface AuthCheckProps {
+  children: React.ReactNode;
+}
+
+const AuthCheck: React.FC<AuthCheckProps> = ({ children }) => {
+  return (
+    <AuthContainer>
+      <AuthImage src="/jianquan_tishi.png" alt="鉴权提示" />
+    </AuthContainer>
+  );
+};
+
+export default AuthCheck; 

+ 89 - 0
src/components/AuthTest.tsx

@@ -0,0 +1,89 @@
+import React, { useState } from 'react';
+import styled from 'styled-components';
+import { apiPost } from '../utils/request';
+
+const TestContainer = styled.div`
+  position: fixed;
+  top: 20px;
+  right: 20px;
+  background: rgba(0, 0, 0, 0.8);
+  color: white;
+  padding: 15px;
+  border-radius: 8px;
+  font-size: 12px;
+  z-index: 10000;
+  min-width: 300px;
+`;
+
+const TestButton = styled.button`
+  background: #4A90E2;
+  color: white;
+  border: none;
+  padding: 5px 10px;
+  margin: 5px;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 12px;
+  
+  &:hover {
+    background: #357ABD;
+  }
+`;
+
+const TestInfo = styled.div`
+  margin: 10px 0;
+  padding: 10px;
+  background: rgba(255, 255, 255, 0.1);
+  border-radius: 4px;
+  
+  pre {
+    margin: 0;
+    white-space: pre-wrap;
+    font-size: 11px;
+  }
+`;
+
+const AuthTest: React.FC = () => {
+  const [testResult, setTestResult] = useState<string>('');
+
+  const testAuth = async () => {
+    try {
+      const response = await apiPost('/checkKeygen');
+      const { status, msg, data } = response.data || {};
+      
+      let resultText = `鉴权接口响应:\n`;
+      resultText += `status: ${status}\n`;
+      resultText += `msg: "${msg || ''}"\n`;
+      resultText += `data: ${data}\n\n`;
+      
+      if (status === 200 && (!msg || msg === '')) {
+        resultText += `✅ 权限验证成功`;
+      } else {
+        resultText += `❌ 权限验证失败: ${msg || '未知错误'}`;
+      }
+      
+      setTestResult(resultText);
+    } catch (error: any) {
+      setTestResult(`权限验证失败:\n${error.message}\n${JSON.stringify(error.response?.data || {}, null, 2)}`);
+    }
+  };
+
+  const clearTest = () => {
+    setTestResult('');
+  };
+
+  return (
+    <TestContainer>
+      <div>鉴权测试工具</div>
+      <TestButton onClick={testAuth}>测试鉴权接口</TestButton>
+      <TestButton onClick={clearTest}>清除结果</TestButton>
+      {testResult && (
+        <TestInfo>
+          <pre>{testResult}</pre>
+        </TestInfo>
+      )}
+    </TestContainer>
+  );
+};
+
+export default AuthTest; 

+ 559 - 0
src/components/DrawerButtons.tsx

@@ -0,0 +1,559 @@
+import React, { useState, useEffect } from 'react';
+import styled from 'styled-components';
+import { apiGet } from '../utils/request';
+
+// 按钮容器
+const ButtonsContainer = styled.div`
+  position: fixed;
+  right: 1vw;
+  bottom: 8vh;
+  display: flex;
+  flex-direction: column;
+  z-index: 1000;
+`;
+
+// 单个按钮
+const Button = styled.div<{ isActive: boolean; buttonIndex: number }>`
+  width: 1.5vw;
+  height: 9.5vh;
+  background-image: ${props => 
+    props.isActive 
+      ? `url('/side_nav_${props.buttonIndex}_active.png')` 
+      : `url('/side_nav_${props.buttonIndex}.png')`};
+  background-repeat: no-repeat;
+  background-size: 100% 100%;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  color: ${props => props.isActive ? '#0A1433' : '#CCDDFF'};
+  font-size: 0.6vw;
+  cursor: pointer;
+  position: relative;
+  padding: 0;
+  writing-mode: vertical-lr;
+  text-orientation: upright;
+  letter-spacing: 0.1vw;
+  margin-bottom: -1vh; /* 使用vh单位实现自适应的负边距 */
+`;
+
+// 抽屉容器
+const Drawer = styled.div<{ isOpen: boolean }>`
+  position: fixed;
+  right: ${props => props.isOpen ? '3vw' : '-50vw'};
+  top: 12vh;
+  width: 46.5vw;
+  height: 80vh;
+  background: url('/choutibeijingtu_bg.png') no-repeat;
+  background-size: 100% 100%;
+  background-position: center;
+  transition: right 0.3s;
+  z-index: 999;
+  display: flex;
+  flex-direction: column;
+  padding: 0;
+  padding-top: 1.5%;
+  overflow: hidden;
+  background-color: #051833;
+  &::after {
+    content: '';
+    position: absolute;
+    bottom: 3vh;
+    left: 15%;
+    right: 10%;
+    height: 2px;
+    background: url('/bottom_decoration.png') no-repeat;
+    background-size: contain;
+    background-position: center;
+  }
+`;
+
+// 模块标题
+const ModuleTitle = styled.div`
+  position: relative;
+  width: 65%;
+  height: 1.6vw;
+  padding-left: 4%;
+  text-align: left;
+  line-height: 1.6vw;
+  font-size: 0.8vw;
+  color:#E6F2FF;
+  font-family: 'DingTalk JinBuTi', 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
+  background: url('/component_header_bg.png') no-repeat;
+  background-size: 100% 100%;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  
+  &::before {
+    content: '';
+    display: none;
+  }
+  
+  &::after {
+    content: '';
+    display: none;
+  }
+`;
+
+// 关闭按钮
+const CloseButton = styled.div`
+  position: absolute;
+  z-index: 99;
+  right: 2.5vw;
+  bottom: 0.5vw;
+  width: 2vw;
+  height: 2vw;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  color: transparent;
+  cursor: pointer;
+  font-size: 1.5vw;
+  background: url('/drawer_close_btn.png') no-repeat;
+  background-size: contain;
+  background-position: center;
+`;
+
+// 抽屉内容
+const DrawerContent = styled.div`
+  flex: 1;
+  overflow-y: auto;
+  color: #CCE6FF;
+  padding: 1vh 1.5vw;
+  
+  &::-webkit-scrollbar {
+    width: 0.5vw;
+  }
+  
+  &::-webkit-scrollbar-track {
+    background: rgba(10, 20, 51, 0.2);
+    border-radius: 10px;
+  }
+  
+  &::-webkit-scrollbar-thumb {
+    background: rgba(78, 128, 229, 0.5);
+    border-radius: 10px;
+  }
+`;
+
+// 药品列表容器
+const MedicineList = styled.div`
+  display: flex;
+  flex-direction: column;
+  gap: 1vh;
+  width: 100%;
+  padding-top: 1vw;
+  padding-right: 1vw;
+`;
+
+// 药品项目
+const MedicineItem = styled.div`
+  display: flex;
+  align-items: center;
+  width: 100%;
+  height: 4.5vh;
+  background: url('/paihangkapian.png') no-repeat;
+  background-size: 100% 100%;
+  background-position: center;
+  /* border-radius: 0.5vw; */
+  padding: 0;
+  position: relative;
+  /* overflow: hidden; */
+  border: none;
+  box-shadow: none;
+`;
+
+// 序号样式
+const SerialNumber = styled.div`
+  width: 2.5vw;
+  height: 100%;
+  font-size: 1vw;
+  font-weight: bold;
+  color: #CCE6FF;
+  text-align: center;
+  z-index: 1;
+  /* background: linear-gradient(90deg, rgba(78, 128, 229, 0.2) 0%, rgba(31, 50, 80, 0) 100%); */
+  display: flex;
+  justify-content: center;
+  align-items: center;
+`;
+
+// 药品信息区域
+const MedicineInfo = styled.div`
+  display: flex;
+  flex-direction: column;
+  margin-left: 0.5vw;
+  flex: 1;
+  justify-content: center;
+`;
+
+// 药品名称
+const MedicineName = styled.div`
+  font-size: 0.7vw;
+  color: #fff;
+  display: flex;
+  font-weight:bold; 
+  flex-direction: row;
+  align-items: flex-start;
+  gap: 0.5vw;
+  margin-bottom: 0.1vw;
+  white-space: nowrap;
+  
+  span {
+    color: #CCE6FF;
+    font-size: 0.8vw;
+  }
+`;
+
+// 药品厂家
+const MedicineManufacturer = styled.div`
+  font-size: 0.6vw;
+  color: #A3B8CC;
+  text-align: left;
+`;
+
+// 数据指标容器
+const DataSection = styled.div`
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+  width: 60%;
+  /* gap: 1.2vw; */
+`;
+
+// 单个指标
+const DataItem = styled.div`
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+  flex: 0.15s;
+  min-width: 0;
+`;
+
+// 指标标题
+const DataTitle = styled.div`
+  font-size: 0.55vw;
+  color: #CCE6FF;
+  text-align: left;
+  margin-bottom: 0.1vw;
+`;
+
+// 指标数值
+const DataValue = styled.div<{ color?: string }>`
+  font-size: 0.8vw;
+  color: ${props => props.color || '#CCE6FF'};
+  font-family: 'DingTalk JinBuTi', 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
+  text-align: left;
+`;
+
+// 按钮类型
+type ButtonType = '补药清单' | '效期清单' | '滞销清单' | null;
+
+interface DrawerButtonsProps {
+  currentScreen: number;
+  equipCode: string;
+  dateTime: string;
+}
+
+const DrawerButtons: React.FC<DrawerButtonsProps> = ({ currentScreen, equipCode, dateTime }) => {
+  // 所有Hooks必须在组件体最顶层调用,不能放在if语句后面
+  const [activeDrawer, setActiveDrawer] = useState<ButtonType>(null);
+  const [supplementData, setSupplementData] = useState<any[]>([]);
+  const [loading, setLoading] = useState(false);
+  const [error, setError] = useState<string | null>(null);
+  const [expiryList, setExpiryList] = useState<any[]>([]);
+  const [slowList, setSlowList] = useState<any[]>([]);
+  
+  // 打开指定抽屉
+  const openDrawer = (type: ButtonType) => {
+    setActiveDrawer(type);
+  };
+  
+  // 关闭抽屉
+  const closeDrawer = () => {
+    setActiveDrawer(null);
+  };
+  
+  // 补药清单接口请求
+  useEffect(() => {
+    if (activeDrawer === '补药清单') {
+      setLoading(true);
+      setError(null);
+      apiGet('/equipMana/drugReplenishedList', {
+        params: {
+          dateTime,
+          equipCode: equipCode === 'total' ? '0' : equipCode
+        }
+      })
+        .then((res: any) => {
+          if (res.data && res.data.data && Array.isArray(res.data.data)) {
+            setSupplementData(res.data.data);
+          } else {
+            setSupplementData([]);
+            setError('无数据');
+          }
+        })
+        .catch(() => {
+          setSupplementData([]);
+          setError('接口请求失败');
+        })
+        .finally(() => setLoading(false));
+    }
+    if (activeDrawer === '效期清单') {
+      setLoading(true);
+      setError(null);
+      apiGet('/equipMana/drugExpiryDateList', {
+        params: {
+          dateTime,
+          equipCode: equipCode === 'total' ? '0' : equipCode
+        }
+      })
+        .then((res: any) => {
+          if (res.data && res.data.data && Array.isArray(res.data.data)) {
+            setExpiryList(res.data.data);
+          } else {
+            setExpiryList([]);
+            setError('无数据');
+          }
+        })
+        .catch(() => {
+          setExpiryList([]);
+          setError('接口请求失败');
+        })
+        .finally(() => setLoading(false));
+    }
+    if (activeDrawer === '滞销清单') {
+      setLoading(true);
+      setError(null);
+      apiGet('/equipMana/drugSlowMovingList', {
+        params: {
+          dateTime,
+          equipCode: equipCode === 'total' ? '0' : equipCode
+        }
+      })
+        .then((res: any) => {
+          if (res.data && res.data.data && Array.isArray(res.data.data)) {
+            setSlowList(res.data.data);
+          } else {
+            setSlowList([]);
+            setError('无数据');
+          }
+        })
+        .catch(() => {
+          setSlowList([]);
+          setError('接口请求失败');
+        })
+        .finally(() => setLoading(false));
+    }
+  }, [activeDrawer, equipCode, dateTime]);
+  
+  // 效期清单数据结构调整
+  const expiryData = [
+    { id: 1, name: '阿魏酸哌嗪片', spec: '[50mgx180.00]', manufacturer: '四川成都亨达制药厂', expiry: '2024/6/31', quantity: 210, overdueStock: 26 },
+    { id: 2, name: '阿魏酸哌嗪片', spec: '[50mgx180.00]', manufacturer: '四川成都亨达制药厂', expiry: '2024/6/31', quantity: 210, overdueStock: 26 },
+    { id: 3, name: '阿魏酸哌嗪片', spec: '[50mgx180.00]', manufacturer: '四川成都亨达制药厂', expiry: '2024/6/31', quantity: 210, overdueStock: 26 },
+    { id: 4, name: '阿魏酸哌嗪片', spec: '[50mgx180.00]', manufacturer: '四川成都亨达制药厂', expiry: '2024/6/31', quantity: 210, overdueStock: 26 },
+    { id: 5, name: '阿魏酸哌嗪片', spec: '[50mgx180.00]', manufacturer: '四川成都亨达制药厂', expiry: '2024/6/31', quantity: 210, overdueStock: 26 },
+    { id: 6, name: '阿魏酸哌嗪片', spec: '[50mgx180.00]', manufacturer: '四川成都亨达制药厂', expiry: '2024/6/31', quantity: 210, overdueStock: 26 },
+    { id: 7, name: '阿魏酸哌嗪片', spec: '[50mgx180.00]', manufacturer: '四川成都亨达制药厂', expiry: '2024/6/31', quantity: 210, overdueStock: 26 },
+  ];
+
+  // 滞销清单数据结构调整
+  const slowMovingData = [
+    { id: 1, name: '阿魏酸哌嗪片', spec: '[50mgx180.00]', manufacturer: '四川成都亨达制药厂', batchNo: '250305w082', inPackCount: 2930, inPieceCount: 2930, inDate: '2024/6/1', expiry: '2024/6/31', slowDays: 26 },
+    { id: 2, name: '阿魏酸哌嗪片', spec: '[50mgx180.00]', manufacturer: '四川成都亨达制药厂', batchNo: '250305w082', inPackCount: 2930, inPieceCount: 2930, inDate: '2024/6/1', expiry: '2024/6/31', slowDays: 26 },
+    { id: 3, name: '阿魏酸哌嗪片', spec: '[50mgx180.00]', manufacturer: '四川成都亨达制药厂', batchNo: '250305w082', inPackCount: 2930, inPieceCount: 2930, inDate: '2024/6/1', expiry: '2024/6/31', slowDays: 26 },
+    { id: 4, name: '阿魏酸哌嗪片', spec: '[50mgx180.00]', manufacturer: '四川成都亨达制药厂', batchNo: '250305w082', inPackCount: 2930, inPieceCount: 2930, inDate: '2024/6/1', expiry: '2024/6/31', slowDays: 26 },
+    { id: 5, name: '阿魏酸哌嗪片', spec: '[50mgx180.00]', manufacturer: '四川成都亨达制药厂', batchNo: '250305w082', inPackCount: 2930, inPieceCount: 2930, inDate: '2024/6/1', expiry: '2024/6/31', slowDays: 26 },
+    { id: 6, name: '阿魏酸哌嗪片', spec: '[50mgx180.00]', manufacturer: '四川成都亨达制药厂', batchNo: '250305w082', inPackCount: 2930, inPieceCount: 2930, inDate: '2024/6/1', expiry: '2024/6/31', slowDays: 26 },
+    { id: 7, name: '阿魏酸哌嗪片', spec: '[50mgx180.00]', manufacturer: '四川成都亨达制药厂', batchNo: '250305w082', inPackCount: 2930, inPieceCount: 2930, inDate: '2024/6/1', expiry: '2024/6/31', slowDays: 26 },
+    { id: 8, name: '阿魏酸哌嗪片', spec: '[50mgx180.00]', manufacturer: '四川成都亨达制药厂', batchNo: '250305w082', inPackCount: 2930, inPieceCount: 2930, inDate: '2024/6/1', expiry: '2024/6/31', slowDays: 26 },
+    { id: 9, name: '阿魏酸哌嗪片', spec: '[50mgx180.00]', manufacturer: '四川成都亨达制药厂', batchNo: '250305w082', inPackCount: 2930, inPieceCount: 2930, inDate: '2024/6/1', expiry: '2024/6/31', slowDays: 26 },
+    { id: 10, name: '阿魏酸哌嗪片', spec: '[50mgx180.00]', manufacturer: '四川成都亨达制药厂', batchNo: '250305w082', inPackCount: 2930, inPieceCount: 2930, inDate: '2024/6/1', expiry: '2024/6/31', slowDays: 26 },
+    { id: 11, name: '阿魏酸哌嗪片', spec: '[50mgx180.00]', manufacturer: '四川成都亨达制药厂', batchNo: '250305w082', inPackCount: 2930, inPieceCount: 2930, inDate: '2024/6/1', expiry: '2024/6/31', slowDays: 26 },
+    { id: 12, name: '阿魏酸哌嗪片', spec: '[50mgx180.00]', manufacturer: '四川成都亨达制药厂', batchNo: '250305w082', inPackCount: 2930, inPieceCount: 2930, inDate: '2024/6/1', expiry: '2024/6/31', slowDays: 26 },
+  ];
+
+  // 格式化编号为两位数
+  const formatId = (id: number): string => {
+    return id < 10 ? `0${id}` : `${id}`;
+  };
+
+  return (
+    <>
+      {currentScreen === 2 && (
+        <>
+          <ButtonsContainer>
+            <Button 
+              isActive={activeDrawer === '补药清单'} 
+              onClick={() => openDrawer('补药清单')}
+              buttonIndex={1}
+            >
+              补药清单
+            </Button>
+            <Button 
+              isActive={activeDrawer === '效期清单'} 
+              onClick={() => openDrawer('效期清单')}
+              buttonIndex={2}
+            >
+              效期清单
+            </Button>
+            <Button 
+              isActive={activeDrawer === '滞销清单'} 
+              onClick={() => openDrawer('滞销清单')}
+              buttonIndex={3}
+            >
+              滞销清单
+            </Button>
+          </ButtonsContainer>
+          
+          <Drawer isOpen={activeDrawer === '补药清单'}>
+            <CloseButton onClick={closeDrawer}>×</CloseButton>
+            <ModuleTitle>补药清单</ModuleTitle>
+            <DrawerContent>
+              {loading ? (
+                <div style={{color:'#7E9BCC',textAlign:'center',padding:'2vw'}}>数据加载中...</div>
+              ) : error ? (
+                <div style={{color:'#F7B500',textAlign:'center',padding:'2vw'}}>{error}</div>
+              ) : (
+                <MedicineList>
+                  {supplementData.map((item, idx) => (
+                    <MedicineItem key={`suppl-${idx}`}>
+                      <SerialNumber>{formatId(idx+1)}</SerialNumber>
+                      <MedicineInfo style={{marginLeft:0}} >
+                        <MedicineName>
+                          {item.medicationname}
+                        </MedicineName>
+                        <MedicineManufacturer>
+                          {item.brandname}
+                        </MedicineManufacturer>
+                      </MedicineInfo>
+                      <DataSection style={{width: '40%',gap: '0.3vw'}}>
+                        <DataItem style={{flex: 0.4}}>
+                          <DataTitle>包装量</DataTitle>
+                          <DataValue color="#B3D9FF">{item.specificationquantity}</DataValue>
+                        </DataItem>
+                        <DataItem style={{flex: 0.4}}>
+                          <DataTitle>补药包装数</DataTitle>
+                          <DataValue color="#B3D9FF">{item.needbox}</DataValue>
+                        </DataItem>
+                        <DataItem style={{flex: 0.4}}>
+                          <DataTitle>缺药率</DataTitle>
+                          <DataValue color="#B3D9FF">{item.needper}</DataValue>
+                        </DataItem>
+                        <DataItem style={{flex: 0.4}}>
+                          <DataTitle >合理库存</DataTitle>
+                          <DataValue color="#B3D9FF">{item.total}</DataValue>
+                        </DataItem>
+                        <DataItem style={{flex: 0.4}}>
+                          <DataTitle>库存</DataTitle>
+                          <DataValue color="#FFEA80">{item.quantity}</DataValue>
+                        </DataItem>
+                      </DataSection>
+                    </MedicineItem>
+                  ))}
+                </MedicineList>
+              )}
+            </DrawerContent>
+          </Drawer>
+          
+          <Drawer isOpen={activeDrawer === '效期清单'}>
+            <CloseButton onClick={closeDrawer}>×</CloseButton>
+            <ModuleTitle>效期清单</ModuleTitle>
+            <DrawerContent>
+              {loading ? (
+                <div style={{color:'#7E9BCC',textAlign:'center',padding:'2vw'}}>数据加载中...</div>
+              ) : error ? (
+                <div style={{color:'#F7B500',textAlign:'center',padding:'2vw'}}>{error}</div>
+              ) : (
+                <MedicineList>
+                  {expiryList.map((item, idx) => (
+                    <MedicineItem key={`expiry-${idx}`}>
+                      <SerialNumber>{formatId(idx+1)}</SerialNumber>
+                      <MedicineInfo style={{marginLeft:0,width:'30%'}}>
+                        <MedicineName>
+                          {item.medicationName}
+                        </MedicineName>
+                        <MedicineManufacturer>
+                          {item.manufacturer}
+                        </MedicineManufacturer>
+                      </MedicineInfo>
+                      <DataSection style={{width: '60%'}}>
+                        <DataItem style={{flex: 0.25,marginRight:'0.8vw'}}>
+                          <DataTitle>效期</DataTitle>
+                          <DataValue color="#80EAFF">{item.expiryDate}</DataValue>
+                        </DataItem>
+                        <DataItem style={{flex: 0.15}}>
+                          <DataTitle>库存</DataTitle>
+                          <DataValue color="#80EAFF">{item.stock}</DataValue>
+                        </DataItem>
+                        <DataItem style={{flex: 0.15}}>
+                          <DataTitle>到期天数</DataTitle>
+                          <DataValue color="#FFEA80">{item.daysToExpire}</DataValue>
+                        </DataItem>
+                      </DataSection>
+                    </MedicineItem>
+                  ))}
+                </MedicineList>
+              )}
+            </DrawerContent>
+          </Drawer>
+          
+          <Drawer isOpen={activeDrawer === '滞销清单'}>
+            <CloseButton onClick={closeDrawer}>×</CloseButton>
+            <ModuleTitle>滞销清单</ModuleTitle>
+            <DrawerContent>
+              {loading ? (
+                <div style={{color:'#7E9BCC',textAlign:'center',padding:'2vw'}}>数据加载中...</div>
+              ) : error ? (
+                <div style={{color:'#F7B500',textAlign:'center',padding:'2vw'}}>{error}</div>
+              ) : (
+                <MedicineList>
+                  {slowList.map((item, idx) => (
+                    <MedicineItem key={`slow-${idx}`}>
+                      <SerialNumber>{formatId(idx+1)}</SerialNumber>
+                      <MedicineInfo style={{width: '40%',overflow: 'hidden',marginLeft:0}}>
+                        <MedicineName style={{width: '100%', maxWidth: '100%', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap'}}>
+                          {item.medicationname}
+                        </MedicineName>
+                        <MedicineManufacturer style={{width: '100%'}}>
+                          {item.brandname}
+                        </MedicineManufacturer>
+                      </MedicineInfo>
+                      <DataSection style={{width: '60%'}}>
+                        
+                        <DataItem style={{flex: 0.2}}>
+                          <DataTitle>批号</DataTitle>
+                          <DataValue style={{color: '#80FFEA'}}>{item.batchnumber?item.batchnumber:'-'}</DataValue>
+                        </DataItem>
+                        <DataItem style={{flex: 0.2}}>
+                          <DataTitle>入库包装数</DataTitle>
+                          <DataValue style={{color: '#80FFEA'}}>{item.inputPackageNum}</DataValue>
+                        </DataItem>
+                        <DataItem style={{flex: 0.2}}>
+                          <DataTitle>入库日期</DataTitle>
+                          <DataValue style={{color: '#80FFEA'}}>{item.inputdate ? item.inputdate.replace(/-/g, '/') : ''}</DataValue>
+                        </DataItem>
+                        <DataItem style={{flex: 0.2}}>
+                          <DataTitle>效期</DataTitle>
+                          <DataValue style={{color: '#80FFEA'}}>{item.expirydate}</DataValue>
+                        </DataItem>
+                        <DataItem style={{flex: 0.15}}>
+                          <DataTitle>滞销天数</DataTitle>
+                          <DataValue style={{color: '#FFEA80'}}>{item.unsoldDays}</DataValue>
+                        </DataItem>
+                      </DataSection>
+                    </MedicineItem>
+                  ))}
+                </MedicineList>
+              )}
+            </DrawerContent>
+          </Drawer>
+        </>
+      )}
+    </>
+  );
+};
+
+export default DrawerButtons;

+ 47 - 0
src/components/LoadingSpinner.tsx

@@ -0,0 +1,47 @@
+import React from 'react';
+import styled, { keyframes } from 'styled-components';
+
+const spin = keyframes`
+  0% { transform: rotate(0deg); }
+  100% { transform: rotate(360deg); }
+`;
+
+const LoadingContainer = styled.div`
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  height: 100vh;
+  background: linear-gradient(135deg, #0A1433 0%, #1a2863 50%, #0A1433 100%);
+  color: #FFFFFF;
+`;
+
+const Spinner = styled.div`
+  width: 3vw;
+  height: 3vw;
+  border: 0.2vw solid rgba(255, 255, 255, 0.2);
+  border-top: 0.2vw solid #FFFFFF;
+  border-radius: 50%;
+  animation: ${spin} 1s linear infinite;
+  margin-bottom: 1vw;
+`;
+
+const LoadingText = styled.div`
+  font-size: 1.2vw;
+  font-family: 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
+`;
+
+interface LoadingSpinnerProps {
+  text?: string;
+}
+
+const LoadingSpinner: React.FC<LoadingSpinnerProps> = ({ text = '正在加载...' }) => {
+  return (
+    <LoadingContainer>
+      <Spinner />
+      <LoadingText>{text}</LoadingText>
+    </LoadingContainer>
+  );
+};
+
+export default LoadingSpinner; 

+ 35 - 0
src/components/RightTopTime.tsx

@@ -0,0 +1,35 @@
+import React, { useEffect, useState } from 'react';
+import styled from 'styled-components';
+import { formatDateTime } from '../utils/dateUtils';
+
+// 右上角时间容器样式,绝对定位
+const TimeBox = styled.div`
+  position: absolute;
+  top: 1.2vw;
+  right: 2vw;
+  z-index: 10;
+  color: #E6F2FF;
+  font-size: 2vw;
+  font-family: 'DingTalk JinBuTi', 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
+  letter-spacing: 1px;
+  text-shadow: 0 2px 8px rgba(0,0,0,0.18);
+`;
+
+const RightTopTime: React.FC = () => {
+  // 当前时间state
+  const [now, setNow] = useState(() => formatDateTime(new Date()).slice(0, 16)); // 只保留到分钟
+
+  useEffect(() => {
+    // 每分钟更新时间
+    const timer = setInterval(() => {
+      setNow(formatDateTime(new Date()).slice(0, 16));
+    }, 60000);
+    // 首次挂载时立即更新时间
+    setNow(formatDateTime(new Date()).slice(0, 16));
+    return () => clearInterval(timer);
+  }, []);
+
+  return <TimeBox>{now}</TimeBox>;
+};
+
+export default RightTopTime; 

+ 251 - 0
src/components/charts/ConsumedMedicineTop8Chart.tsx

@@ -0,0 +1,251 @@
+import React, { useState, useEffect } from 'react';
+import styled from 'styled-components';
+import { apiGet } from '../../utils/request';
+import { useDefaultDate, getSelectedMonthRange } from '../../App';
+import useGlobalRefresh from '../../hooks/useGlobalRefresh';
+
+// 组件容器样式 - 确保完全填充Grid单元格
+const ChartWrapper = styled.div`
+  width: 100%;
+  height: 100%;
+  position: relative;
+  overflow: hidden;
+  border: 1px solid #2980B9;
+  box-sizing: border-box;
+  display: flex;
+  flex-direction: column;
+`;
+
+// 组件标题 - 使用与其他模块相同的样式
+const PanelHeader = styled.div`
+  position: relative;
+  width: 95%;
+  height: 1.6vw;
+  min-height: 1.6vw;
+  max-height: 1.6vw;
+  padding-left: 5.5%;
+  text-align: left;
+  line-height: 1.6vw;
+  font-size: 0.8vw;
+  color: #E6F2FF;
+  font-family: 'DingTalk JinBuTi', 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
+  background: url('/component_header_bg.png') no-repeat;
+  background-size: 100% 100%;
+  flex-shrink: 0;
+`;
+
+// 表格容器 - 占据剩余空间,确保与其他模块一致
+const TableContainer = styled.div`
+  width: 100%;
+  flex: 1;
+  overflow: hidden;
+`;
+
+// 表格样式
+const Table = styled.table`
+  width: 100%;
+  height: 100%;
+  border-collapse: collapse;
+  font-family: 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif !important;
+`;
+
+// 表头样式
+const TableHeader = styled.thead`
+  tr {
+    background:#051833;
+  }
+  
+  th {
+    padding: 0.3vw 1.2vw;
+    font-size: 0.7vw;
+    font-weight: normal;
+    color: #CCE6FF;
+    white-space: nowrap;
+    font-family: 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif !important;
+    
+    &:first-child {
+      width: 35%;
+      text-align: left;
+      padding-left: 3.2vw; /* 增加左边距,与药品名称文字对齐 */
+    }
+    
+    &:nth-child(2) {
+      width: 20%;
+      text-align: left;
+    }
+    
+    &:nth-child(3) {
+      width: 45%;
+      text-align: left;
+    }
+  }
+`;
+
+// 表体样式
+const TableBody = styled.tbody`
+  tr {
+    &:nth-child(odd) {
+      background: #162840;
+    }
+    
+    &:nth-child(even) {
+      background: #051833;
+    }
+    
+    &:hover {
+      background: rgba(6, 30, 93, 0.6);
+    }
+  }
+  
+  td {
+    padding: 0.2vw 1.2vw;
+    font-size: 0.65vw;
+    color: #FFFFFF;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    font-family: 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif !important;
+    
+    &:first-child {
+      text-align: left;
+      color: #FFFFFF;
+    }
+    
+    &:nth-child(2) {
+      text-align: left;
+    }
+    
+    &:nth-child(3) {
+      text-align: left;
+    }
+  }
+`;
+
+// 排名号码样式
+const RankNumber = styled.span<{ rank: string }>`
+  display: inline-block;
+  min-width: 1.5vw;
+  text-align: center;
+  font-size: 0.7vw;
+  font-weight: bold;
+  margin-right: 0.5vw;
+  font-family: 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif !important;
+  color: ${props => {
+    const rankNum = parseInt(props.rank);
+    if (rankNum <= 3) return '#FF4D6A'; // 前三名红色
+    return '#CCE6FF'; // 剩余浅蓝色
+  }};
+`;
+
+// 数据类型定义
+interface MedicineData {
+  rank: string;
+  drugName: string;
+  drugSpec: string;
+  firmName: string;
+  amount: number;
+}
+
+const ConsumedMedicineTop8Chart: React.FC = () => {
+  const [data, setData] = useState<MedicineData[]>([]);
+  const [loading, setLoading] = useState<boolean>(true);
+  const { defaultDateTime, selectedMonth } = useDefaultDate();
+
+  // 获取数据
+  const fetchData = async () => {
+    try {
+      setLoading(true);
+      
+      // 使用选中月份获取时间范围,支持月份选择功能
+      const { startTime, endTime } = getSelectedMonthRange(defaultDateTime, selectedMonth);
+      
+      const response = await apiGet('/stageStats/drugsConsumptionTop', {
+        params: {
+          startTime,
+          endTime
+        }
+      });
+      
+      if (response.data && response.data.data && Array.isArray(response.data.data)) {
+        // 最多只取前8条数据
+        const limitedData = response.data.data.slice(0, 8);
+        setData(limitedData);
+      } else {
+        console.warn('消耗药品Top8 - 接口返回格式不符合预期:', response.data);
+        // 设置为空数组,不使用模拟数据
+        setData([]);
+      }
+    } catch (error) {
+      console.error('获取消耗药品Top8数据失败:', error);
+      // 设置为空数组,不使用模拟数据
+      setData([]);
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  // 组件挂载时和依赖变化时获取数据
+  useEffect(() => {
+    fetchData();
+  }, [defaultDateTime, selectedMonth]);
+
+  // 使用刷新Hook,配置对应的模块代码
+  useGlobalRefresh(fetchData, '41');
+
+  return (
+    <ChartWrapper>
+      <PanelHeader>消耗药品Top8</PanelHeader>
+      <TableContainer>
+        {loading ? (
+          <div style={{
+            display: 'flex',
+            justifyContent: 'center',
+            alignItems: 'center',
+            height: '100%',
+            color: '#CCE6FF',
+            fontSize: '0.8vw'
+          }}>
+            数据加载中...
+          </div>
+        ) : data.length === 0 ? (
+          <div style={{
+            display: 'flex',
+            justifyContent: 'center',
+            alignItems: 'center',
+            height: '100%',
+            color: '#CCE6FF',
+            fontSize: '0.8vw'
+          }}>
+            暂无数据
+          </div>
+        ) : (
+          <Table>
+            <TableHeader>
+              <tr>
+                <th>药品名称</th>
+                <th>药品规格</th>
+                <th>厂商</th>
+              </tr>
+            </TableHeader>
+            <TableBody>
+              {data.map((item, index) => (
+                <tr key={index}>
+                  <td>
+                    <RankNumber rank={item.rank}>
+                      {String(item.rank).padStart(2, '0')}
+                    </RankNumber>
+                    {item.drugName}
+                  </td>
+                  <td>{item.drugSpec}</td>
+                  <td>{item.firmName}</td>
+                </tr>
+              ))}
+            </TableBody>
+          </Table>
+        )}
+      </TableContainer>
+    </ChartWrapper>
+  );
+};
+
+export default ConsumedMedicineTop8Chart; 

+ 143 - 0
src/components/charts/CoreMetricsChart.tsx

@@ -0,0 +1,143 @@
+import React, { useEffect, useState } from 'react';
+import ReactECharts from 'echarts-for-react';
+import * as echarts from 'echarts';
+import { PanelContainer, PanelTitle, MetricsContainer, MetricCard, ProgressContainer } from '../styled/DashboardStyles';
+import styled from 'styled-components';
+import { getCoreMetrics } from '../../utils/mockData';
+
+// 指标卡片包装器
+const MetricCardsWrapper = styled.div`
+  display: flex;
+  justify-content: space-around;
+  align-items: center;
+  width: 100%;
+`;
+
+// 进度条图表包装器
+const ChartWrapper = styled.div`
+  display: flex;
+  height: 100%;
+  flex-direction: column;
+`;
+
+// 进度图表和指标布局
+const ProgressAndMetrics = styled.div`
+  display: grid;
+  grid-template-columns: 1fr 2fr;
+  height: calc(100% - 30px);
+  gap: 10px;
+`;
+
+const CoreMetricsChart: React.FC = () => {
+  const [metrics, setMetrics] = useState(getCoreMetrics());
+  
+  useEffect(() => {
+    const timer = setInterval(() => {
+      setMetrics(getCoreMetrics());
+    }, 30000);
+    
+    return () => clearInterval(timer);
+  }, []);
+  
+  // 环形进度图配置
+  const getProgressOption = () => {
+    return {
+      series: [
+        {
+          type: 'pie',
+          radius: ['75%', '90%'],
+          avoidLabelOverlap: false,
+          label: {
+            show: false,
+          },
+          emphasis: {
+            scale: false
+          },
+          silent: true,
+          animation: false,
+          data: [
+            { 
+              value: 20, 
+              name: '待就诊',
+              itemStyle: {
+                color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
+                  { offset: 0, color: '#00b4ff' },
+                  { offset: 1, color: '#00fffc' }
+                ])
+              }
+            },
+            { 
+              value: 80, 
+              name: '剩余',
+              itemStyle: {
+                color: 'rgba(0, 50, 100, 0.3)'
+              }
+            }
+          ]
+        },
+        {
+          type: 'pie',
+          radius: ['65%', '70%'],
+          avoidLabelOverlap: false,
+          label: {
+            show: false,
+          },
+          emphasis: {
+            scale: false
+          },
+          silent: true,
+          animation: false,
+          data: [
+            { 
+              value: 100, 
+              name: '背景圆环',
+              itemStyle: {
+                color: 'rgba(0, 100, 220, 0.1)'
+              }
+            }
+          ]
+        }
+      ]
+    };
+  };
+  
+  // 渲染指标卡片
+  const renderMetricCard = (title: string, value: number, ratio: number, change: number, isDown: boolean = false) => {
+    return (
+      <MetricCard>
+        <div className="title">{title} (人)</div>
+        <div className="value">{value}</div>
+        <div className={`change ${isDown ? 'down' : 'up'}`}>
+          较上月 {isDown ? '↓' : '↑'}{change}%
+        </div>
+      </MetricCard>
+    );
+  };
+  
+  return (
+    <PanelContainer>
+      <PanelTitle>统计状态一览</PanelTitle>
+      <ProgressAndMetrics>
+        <ChartWrapper>
+          <ProgressContainer>
+            <div className="progress-value">20</div>
+            <ReactECharts 
+              option={getProgressOption()} 
+              style={{ height: '100%', width: '100%' }} 
+              opts={{ renderer: 'canvas' }}
+            />
+          </ProgressContainer>
+          <div style={{ textAlign: 'center', fontSize: '14px', color: '#7e9bcc', marginTop: '5px' }}>待就诊</div>
+        </ChartWrapper>
+        
+        <MetricCardsWrapper>
+          {renderMetricCard('已就人数', metrics.todayPatients, metrics.patientRatio, metrics.patientChange)}
+          {renderMetricCard('机构处方', metrics.prescriptions, metrics.prescriptionRatio, metrics.prescriptionChange, true)}
+          {renderMetricCard('机备药品', metrics.medicines, metrics.medicineRatio, metrics.medicineChange)}
+        </MetricCardsWrapper>
+      </ProgressAndMetrics>
+    </PanelContainer>
+  );
+};
+
+export default CoreMetricsChart; 

+ 144 - 0
src/components/charts/CyanDualGaugeChart.tsx

@@ -0,0 +1,144 @@
+import React, { useEffect, useState } from 'react';
+import styled from 'styled-components';
+import { apiGet } from '../../utils/request';
+import { useDefaultDate, getSelectedMonthRange } from '../../App';
+import useGlobalRefresh from '../../hooks/useGlobalRefresh';
+
+// 组件容器样式
+const ChartWrapper = styled.div`
+  width: 100%;
+  height: 100%;
+  position: relative;
+  overflow: hidden;
+  border: 1px solid #2980B9;
+`;
+
+// 内容容器
+const ContentContainer = styled.div`
+  position: relative;
+  width: 100%;
+  height: 100%;
+  background: url('/qingseshuangbiaopan.png') no-repeat center center;
+  background-size: 90% auto;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+`;
+
+// 双仪表盘容器
+const DualGaugeContainer = styled.div`
+  position: relative;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: space-around;
+  padding: 1vw 0vw;
+`;
+
+// 单个仪表盘容器
+const GaugeContainer = styled.div`
+  position: relative;
+  width: 40%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  margin-top: 1vw;
+`;
+
+// 主要数值显示
+const MainValue = styled.div`
+  font-size: 1.5vw;
+  color: #FFFFFF;
+  font-family: 'DingTalk JinBuTi', 'PingFang SC', sans-serif;
+  text-align: center;
+  margin-bottom: 0.5vw;
+  text-shadow: 0 0 0.5vw rgba(255, 255, 255, 0.3);
+`;
+
+// 描述文字
+const Description = styled.div`
+  font-size: 0.8vw;
+  color: #CCE6FF;
+  text-align: center;
+  font-family: 'Source Han Sans SC', 'PingFang SC', sans-serif;
+`;
+
+// 数据接口类型 - 根据新接口字段定义
+interface IntDiscPrescPassRateData {
+  internalErrorCount: number;      // 调剂内差
+  prescriptionPassRate: number;    // 处方合格率
+}
+
+const CyanDualGaugeChart: React.FC = () => {
+  const [data, setData] = useState<IntDiscPrescPassRateData>({ internalErrorCount: 0, prescriptionPassRate: 0 });
+  const [loading, setLoading] = useState(true);
+  const { defaultDateTime, selectedMonth } = useDefaultDate();
+
+  // 获取数据
+  const fetchData = async () => {
+    try {
+      setLoading(true);
+      
+      // 使用选中月份获取时间范围,支持月份选择功能
+      const { startTime, endTime } = getSelectedMonthRange(defaultDateTime, selectedMonth);
+      
+      const response = await apiGet('/stageStats/intDiscPrescPassRate', {
+        params: {
+          startTime,
+          endTime
+        }
+      });
+      
+      if (response.data && response.data.data) {
+        setData(response.data.data);
+      } else {
+        console.warn('调剂内差及处方合格率 - 接口返回格式不符合预期:', response.data);
+        // 设置为默认值,不使用模拟数据
+        setData({ internalErrorCount: 0, prescriptionPassRate: 0 });
+      }
+    } catch (error) {
+      console.error('获取调剂内差及处方合格率数据失败:', error);
+      // 设置为默认值,不使用模拟数据
+      setData({ internalErrorCount: 0, prescriptionPassRate: 0 });
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  // 组件挂载时获取数据
+  useEffect(() => {
+    fetchData();
+  }, [defaultDateTime, selectedMonth]);
+
+  // 使用全局刷新钩子,指定模块代码'39'(青色双仪表盘)
+  useGlobalRefresh(fetchData, '39');
+
+  return (
+    <ChartWrapper>
+      <ContentContainer>
+        <DualGaugeContainer>
+          {/* 左侧仪表盘 - 调剂内差 */}
+          <GaugeContainer>
+            <MainValue>
+              {loading ? '--' : `${data.internalErrorCount}次`}
+            </MainValue>
+            <Description>调剂内差</Description>
+          </GaugeContainer>
+          
+          {/* 右侧仪表盘 - 处方合格率 */}
+          <GaugeContainer>
+            <MainValue>
+              {loading ? '--' : `${(data.prescriptionPassRate * 100).toFixed(2)}%`}
+            </MainValue>
+            <Description>处方合格率</Description>
+          </GaugeContainer>
+        </DualGaugeContainer>
+      </ContentContainer>
+    </ChartWrapper>
+  );
+};
+
+export default CyanDualGaugeChart; 

+ 400 - 0
src/components/charts/DailyMedicineQuantityChart.tsx

@@ -0,0 +1,400 @@
+import React, { useEffect, useState, useRef } from 'react';
+import * as echarts from 'echarts';
+import styled from 'styled-components';
+import { apiGet } from '../../utils/request';
+import { useDefaultDate, getSelectedMonthRange } from '../../App';
+import useGlobalRefresh from '../../hooks/useGlobalRefresh';
+
+// 组件容器样式 - 与第一个模块保持一致
+const ChartWrapper = styled.div`
+  width: 100%;
+  height: 100%;
+  position: relative;
+  overflow: hidden;
+  border: 1px solid #2980B9;
+`;
+
+// 组件标题 - 使用与其他模块相同的样式
+const PanelHeader = styled.div`
+  position: relative;
+  width: 95%;
+  height: 1.6vw;
+  padding-left: 5.5%;
+  text-align: left;
+  line-height: 1.6vw;
+  font-size: 0.8vw;
+  color: #E6F2FF;
+  font-family: 'DingTalk JinBuTi', 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
+  background: url('/component_header_bg.png') no-repeat;
+  background-size: 100% 100%;
+`;
+
+// 图表容器 - 统一与其他模块一致的布局
+const ChartContainer = styled.div`
+  width: 100%;
+  height: calc(100% - 1.6vw);
+  padding: 0.5vw;
+  box-sizing: border-box;
+  overflow: hidden;
+`;
+
+// 数据接口类型 - 根据新接口字段定义
+interface WeeklyDrugData {
+  weekDay: number;          // 星期(数字)
+  weekDayLabel: string;     // 星期(文本)
+  totalCount: number;       // 日均发药盒数
+  machineCount: number;     // 日均机发盒数
+  directSendRate: number;   // 机发率
+}
+
+// 这个函数已不再需要,将在fetchData中直接使用getSelectedMonthRange
+
+const DailyMedicineQuantityChart: React.FC = () => {
+  const chartRef = useRef<HTMLDivElement>(null);
+  const chartInstance = useRef<echarts.ECharts | null>(null);
+  const [data, setData] = useState<WeeklyDrugData[]>([]);
+  const [loading, setLoading] = useState(true);
+  const { defaultDateTime, selectedMonth } = useDefaultDate();
+
+  // 获取数据
+  const fetchData = async () => {
+    try {
+      setLoading(true);
+      const { startTime, endTime } = getSelectedMonthRange(defaultDateTime, selectedMonth);
+      
+      const response = await apiGet('/stageStats/weeklyDrugOverview', {
+        params: {
+          startTime,
+          endTime
+        }
+      });
+      
+      if (response.data && response.data.data && Array.isArray(response.data.data)) {
+        setData(response.data.data);
+      } else {
+        console.warn('每周药品概况 - 接口返回格式不符合预期:', response.data);
+        // 设置为空数组,不使用模拟数据
+        setData([]);
+      }
+    } catch (error) {
+      console.error('获取每周药品概况数据失败:', error);
+      // 设置为空数组,不使用模拟数据
+      setData([]);
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  // 初始化图表 - 双柱状图+折线图
+  const initChart = () => {
+    if (!chartRef.current || data.length === 0) return;
+
+    // 销毁已存在的图表实例
+    if (chartInstance.current) {
+      chartInstance.current.dispose();
+    }
+
+    // 创建新的图表实例
+    chartInstance.current = echarts.init(chartRef.current);
+
+    const option = {
+      legend: {
+        data: ['日均发药盒数', '日均机发盒数', '机发率'],
+        top: '2%',
+        left: 'center',
+        textStyle: {
+          color: '#CCE6FF',
+          fontSize: '0.6vw'
+        },
+        itemWidth: 10,
+        itemHeight: 8,
+        itemGap: 20
+      },
+      grid: {
+        left: '0%',
+        right: '0%',
+        top: '15%',
+        bottom: '5%',
+        containLabel: true
+      },
+      xAxis: {
+        type: 'category',
+        data: data.map(item => item.weekDayLabel),
+        axisLine: {
+          lineStyle: {
+            color: '#1F324D'
+          }
+        },
+        axisLabel: {
+          color: '#CCE6FF',
+          fontSize: '0.6vw',
+          margin: 5
+        },
+        axisTick: {
+          show: false
+        }
+      },
+      yAxis: [
+        {
+          type: 'value',
+          position: 'left',
+          max: function() {
+            // 获取两个柱状图数据的最大值
+            const maxValue = Math.max(
+              ...data.map(item => Math.max(item.totalCount, item.machineCount))
+            );
+            
+            // 如果最大值很小(小于100),设置max为最大值的1.5倍
+            if (maxValue < 100) {
+              return Math.ceil(maxValue * 1.5);
+            }
+            
+            // 如果最大值较大,设置为1.2倍并向上取整到合适的数值
+            const scaledMax = maxValue * 1.2;
+            
+            // 根据数值大小选择合适的取整方式
+            if (scaledMax < 1000) {
+              return Math.ceil(scaledMax / 100) * 100;
+            } else if (scaledMax < 10000) {
+              return Math.ceil(scaledMax / 500) * 500;
+            } else {
+              return Math.ceil(scaledMax / 1000) * 1000;
+            }
+          },
+          axisLine: {
+            show: false
+          },
+          axisTick: {
+            show: false
+          },
+          axisLabel: {
+            color: '#CCE6FF',
+            fontSize: '0.55vw',
+          },
+          splitLine: {
+            lineStyle: {
+              color: '#1F324D',
+              type: 'solid'
+            }
+          }
+        },
+        {
+          type: 'value',
+          position: 'right',
+          min: function() {
+            // 接口返回的是小数(0.65表示65%),动态计算显示范围
+            const values = data.map(item => item.directSendRate);
+            const minValue = Math.min(...values);
+            const maxValue = Math.max(...values);
+            const range = maxValue - minValue;
+            
+            // 如果数据范围很小,扩大显示范围
+            if (range < 0.2) {
+              return Math.max(0, minValue - 0.1);
+            } else {
+              return Math.max(0, minValue - range * 0.2);
+            }
+          },
+          max: function() {
+            const values = data.map(item => item.directSendRate);
+            const minValue = Math.min(...values);
+            const maxValue = Math.max(...values);
+            const range = maxValue - minValue;
+            
+            // 如果数据范围很小,扩大显示范围
+            if (range < 0.2) {
+              return Math.min(1, maxValue + 0.1);
+            } else {
+              return Math.min(1, maxValue + range * 0.2);
+            }
+          },
+          axisLine: {
+            show: false
+          },
+          axisTick: {
+            show: false
+          },
+          axisLabel: {
+            color: '#CCE6FF',
+            fontSize: '0.55vw',
+            formatter: function(value: number) {
+              return (value * 100).toFixed(0) + '%';
+            }
+          },
+          splitLine: {
+            show: false
+          }
+        }
+      ],
+      series: [
+        {
+          name: '日均发药盒数',
+          type: 'bar',
+          yAxisIndex: 0,
+          data: data.map(item => item.totalCount),
+          barWidth: '20%',
+          barGap: '10%',
+          itemStyle: {
+            borderRadius: [3, 3, 0, 0],
+            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+              { offset: 0, color: '#66B3FF' },
+              { offset: 1, color: 'rgba(102,179,255,0)' }
+            ]),
+          },
+          emphasis: {
+            itemStyle: {
+              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                { offset: 0, color: '#66B3FF' },
+                { offset: 1, color: 'rgba(102,179,255,0.2)' }
+              ])
+            }
+          },
+          label: {
+            show: false
+          }
+        },
+        {
+          name: '日均机发盒数',
+          type: 'bar',
+          yAxisIndex: 0,
+          data: data.map(item => item.machineCount),
+          barWidth: '20%',
+          itemStyle: {
+            borderRadius: [3, 3, 0, 0],
+            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+              { offset: 0, color: '#F3F9FF' },
+              { offset: 1, color: 'rgba(243,249,255,0)' }
+            ]),
+          },
+          emphasis: {
+            itemStyle: {
+              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                { offset: 0, color: '#F3F9FF' },
+                { offset: 1, color: 'rgba(243,249,255,0.2)' }
+              ])
+            }
+          },
+          label: {
+            show: false
+          }
+        },
+        {
+          name: '机发率',
+          type: 'line',
+          yAxisIndex: 1,
+          data: data.map(item => item.directSendRate),
+          lineStyle: {
+            color: '#99FFAA',
+            width: 2
+          },
+          itemStyle: {
+            color: '#99FFAA'
+          },
+          symbol: 'none',
+          smooth: true
+        }
+      ],
+      tooltip: {
+        trigger: 'axis',
+        backgroundColor: 'rgba(15, 35, 85, 0.95)',
+        borderColor: 'rgba(0, 212, 255, 0.6)',
+        borderWidth: 1,
+        textStyle: {
+          color: '#FFFFFF',
+          fontFamily: 'DingTalk JinBuTi',
+          fontSize: '0.6vw'
+        },
+        formatter: (params: any) => {
+          const totalData = params.find((p: any) => p.seriesName === '日均发药盒数');
+          const machineData = params.find((p: any) => p.seriesName === '日均机发盒数');
+          const lineData = params.find((p: any) => p.seriesType === 'line');
+          const dataItem = data[totalData.dataIndex];
+          return `<div style="padding: 0.3vw; font-size: 0.6vw; line-height: 1.4;">
+                    <div style="color: #00D4FF; font-weight: bold; margin-bottom: 0.3vw; font-size: 0.65vw;">${totalData.name}</div>
+                    <div style="margin-bottom: 0.2vw; display: flex; align-items: center;">
+                      <span style="color: #66B3FF; margin-right: 0.3vw; font-size: 0.8vw;">●</span> 
+                      <span>日均发药盒数: </span>
+                      <span style="color: #FFFFFF; font-weight: bold; margin-left: 0.2vw;">${dataItem.totalCount}</span>
+                    </div>
+                    <div style="margin-bottom: 0.2vw; display: flex; align-items: center;">
+                      <span style="color: #F3F9FF; margin-right: 0.3vw; font-size: 0.8vw;">●</span> 
+                      <span>日均机发盒数: </span>
+                      <span style="color: #FFFFFF; font-weight: bold; margin-left: 0.2vw;">${dataItem.machineCount}</span>
+                    </div>
+                    <div style="display: flex; align-items: center;">
+                      <span style="color: #99FFAA; margin-right: 0.3vw; font-size: 0.8vw;">●</span> 
+                      <span>机发率: </span>
+                      <span style="color: #FFFFFF; font-weight: bold; margin-left: 0.2vw;">${(dataItem.directSendRate * 100).toFixed(1)}%</span>
+                    </div>
+                  </div>`;
+        }
+      }
+    };
+
+    chartInstance.current.setOption(option);
+  };
+
+  // 监听数据变化,重新渲染图表
+  useEffect(() => {
+    if (data.length > 0) {
+      initChart();
+    }
+  }, [data]);
+
+  // 组件挂载时获取数据
+  useEffect(() => {
+    fetchData();
+  }, [defaultDateTime, selectedMonth]);
+
+  // 监听窗口大小变化,确保图表自适应
+  useEffect(() => {
+    const handleResize = () => {
+      if (chartInstance.current) {
+        chartInstance.current.resize();
+      }
+    };
+
+    window.addEventListener('resize', handleResize);
+    
+    // 使用ResizeObserver监听容器大小变化
+    const resizeObserver = new ResizeObserver(() => {
+      if (chartInstance.current) {
+        chartInstance.current.resize();
+      }
+    });
+    
+    if (chartRef.current) {
+      resizeObserver.observe(chartRef.current);
+    }
+
+    return () => {
+      window.removeEventListener('resize', handleResize);
+      resizeObserver.disconnect();
+      if (chartInstance.current) {
+        chartInstance.current.dispose();
+      }
+    };
+  }, []);
+
+  // 使用刷新Hook,配置对应的模块代码
+  useGlobalRefresh(fetchData, '34');
+
+  return (
+    <ChartWrapper>
+      <PanelHeader>每周药品概况</PanelHeader>
+      <ChartContainer>
+        <div 
+          ref={chartRef} 
+          style={{ 
+            width: '100%', 
+            height: '100%',
+            opacity: loading ? 0.5 : 1,
+            transition: 'opacity 0.3s ease'
+          }} 
+        />
+      </ChartContainer>
+    </ChartWrapper>
+  );
+};
+
+export default DailyMedicineQuantityChart; 

+ 418 - 0
src/components/charts/DailyPrescriptionChart.tsx

@@ -0,0 +1,418 @@
+import React, { useEffect, useState, useRef } from 'react';
+import * as echarts from 'echarts';
+import styled from 'styled-components';
+import { apiGet } from '../../utils/request';
+import { useDefaultDate, getSelectedMonthRange } from '../../App';
+import useGlobalRefresh from '../../hooks/useGlobalRefresh';
+
+// 组件容器样式 - 完全按照设计稿
+const ChartWrapper = styled.div`
+  width: 100%;
+  height: 100%;
+  position: relative;
+  overflow: hidden;
+  border: 1px solid #2980B9;
+`;
+
+// 组件标题 - 使用与其他模块相同的样式
+const PanelHeader = styled.div`
+  position: relative;
+  width: 95%;
+  height: 1.6vw;
+  padding-left: 5.5%;
+  text-align: left;
+  line-height: 1.6vw;
+  font-size: 0.8vw;
+  color: #E6F2FF;
+  font-family: 'DingTalk JinBuTi', 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
+  background: url('/component_header_bg.png') no-repeat;
+  background-size: 100% 100%;
+`;
+
+// 图表容器 - 统一与其他模块一致的布局
+const ChartContainer = styled.div`
+  width: 100%;
+  height: calc(100% - 1.6vw);
+  padding: 0.5vw;
+  box-sizing: border-box;
+  overflow: hidden;
+`;
+
+// 数据接口类型 - 根据新接口字段定义
+interface WeeklyPrescriptionData {
+  weekDay: number;          // 星期(数字)
+  weekDayLabel: string;     // 星期(文本)
+  totalCount: number;       // 日均处方数
+  machineCount: number;     // 日均直发处方数
+  directSendRate: number;   // 直发率
+}
+
+// 获取月份的开始和结束时间,优先使用默认时间
+const getMonthRange = (defaultDateTime: string) => {
+  // 如果有默认时间,优先使用默认时间;否则使用当前时间
+  const baseDate = defaultDateTime ? new Date(defaultDateTime) : new Date();
+  const year = baseDate.getFullYear();
+  const month = baseDate.getMonth(); // 0-11
+  
+  // 当月第一天
+  const startDate = new Date(year, month, 1);
+  // 当月最后一天
+  const endDate = new Date(year, month + 1, 0);
+  
+  // 格式化为 YYYY-MM-DD
+  const formatDate = (date: Date) => {
+    const y = date.getFullYear();
+    const m = String(date.getMonth() + 1).padStart(2, '0');
+    const d = String(date.getDate()).padStart(2, '0');
+    return `${y}-${m}-${d}`;
+  };
+  
+  return {
+    startTime: formatDate(startDate),
+    endTime: formatDate(endDate)
+  };
+};
+
+const DailyPrescriptionChart: React.FC = () => {
+  const chartRef = useRef<HTMLDivElement>(null);
+  const chartInstance = useRef<echarts.ECharts | null>(null);
+  const [data, setData] = useState<WeeklyPrescriptionData[]>([]);
+  const [loading, setLoading] = useState(true);
+  const { defaultDateTime, selectedMonth } = useDefaultDate();
+
+  // 获取数据
+  const fetchData = async () => {
+    try {
+      setLoading(true);
+      const { startTime, endTime } = getSelectedMonthRange(defaultDateTime, selectedMonth);
+      
+      const response = await apiGet('/stageStats/weeklyPrescriptionOverview', {
+        params: {
+          startTime,
+          endTime
+        }
+      });
+      
+      if (response.data && response.data.data && Array.isArray(response.data.data)) {
+        setData(response.data.data);
+      } else {
+        console.warn('每周处方概况 - 接口返回格式不符合预期:', response.data);
+        // 设置为空数组,不使用模拟数据
+        setData([]);
+      }
+    } catch (error) {
+      console.error('获取每周处方概况数据失败:', error);
+      // 设置为空数组,不使用模拟数据
+      setData([]);
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  // 初始化图表 - 完全自适应容器
+  const initChart = () => {
+    if (!chartRef.current || data.length === 0) return;
+
+    // 销毁已存在的图表实例
+    if (chartInstance.current) {
+      chartInstance.current.dispose();
+    }
+
+    // 创建新的图表实例
+    chartInstance.current = echarts.init(chartRef.current);
+
+    const option = {
+      legend: {
+        data: ['日均处方数', '直发率'],
+        top: '2%',
+        left: 'center',
+        textStyle: {
+          color: '#CCE6FF',
+          fontSize: '0.6vw'
+        },
+        itemWidth: 10,
+        itemHeight: 8,
+        itemGap: 20
+      },
+      grid: {
+        left: '0%',
+        right: '0%',
+        top: '15%',
+        bottom: '5%',
+        containLabel: true
+      },
+      xAxis: {
+        type: 'category',
+        data: data.map(item => item.weekDayLabel),
+        axisLine: {
+          lineStyle: {
+            color: '#1F324D'
+          }
+        },
+        axisLabel: {
+          color: '#CCE6FF',
+          fontSize: '0.6vw',
+          margin: 5
+        },
+        axisTick: {
+          show: false
+        }
+      },
+      yAxis: [
+        {
+          type: 'value',
+          position: 'left',
+          max: function() {
+            // 获取柱状图数据的最大值
+            const maxValue = Math.max(...data.map(item => item.totalCount));
+            
+            // 如果最大值很小(小于100),设置max为最大值的1.5倍,确保柱子不会太矮
+            if (maxValue < 100) {
+              return Math.ceil(maxValue * 1.5);
+            }
+            
+            // 如果最大值较大,设置为1.2倍并向上取整到合适的数值
+            const scaledMax = maxValue * 1.2;
+            
+            // 根据数值大小选择合适的取整方式
+            if (scaledMax < 1000) {
+              return Math.ceil(scaledMax / 100) * 100; // 向上取整到百位
+            } else if (scaledMax < 10000) {
+              return Math.ceil(scaledMax / 500) * 500; // 向上取整到500的倍数
+            } else {
+              return Math.ceil(scaledMax / 1000) * 1000; // 向上取整到千位
+            }
+          },
+          axisLine: {
+            show: false
+          },
+          axisTick: {
+            show: false
+          },
+          axisLabel: {
+            color: '#CCE6FF',
+            fontSize: '0.55vw',
+          },
+          splitLine: {
+            lineStyle: {
+              color: '#1F324D',
+              type: 'solid'
+            }
+          }
+        },
+        {
+          type: 'value',
+          position: 'right',
+          min: function() {
+            // 接口返回的是小数(0.65表示65%),动态计算显示范围
+            const values = data.map(item => item.directSendRate);
+            const minValue = Math.min(...values);
+            const maxValue = Math.max(...values);
+            const range = maxValue - minValue;
+            
+            // 如果数据范围很小,扩大显示范围
+            if (range < 0.2) {
+              return Math.max(0, minValue - 0.1);
+            } else {
+              return Math.max(0, minValue - range * 0.2);
+            }
+          },
+          max: function() {
+            const values = data.map(item => item.directSendRate);
+            const minValue = Math.min(...values);
+            const maxValue = Math.max(...values);
+            const range = maxValue - minValue;
+            
+            // 如果数据范围很小,扩大显示范围
+            if (range < 0.2) {
+              return Math.min(1, maxValue + 0.1);
+            } else {
+              return Math.min(1, maxValue + range * 0.2);
+            }
+          },
+          axisLine: {
+            show: false
+          },
+          axisTick: {
+            show: false
+          },
+          axisLabel: {
+            color: '#CCE6FF',
+            fontSize: '0.55vw',
+            formatter: function(value: number) {
+              return (value * 100).toFixed(0) + '%';
+            }
+          },
+          splitLine: {
+            show: false
+          }
+        }
+      ],
+      series: [
+        {
+          name: '日均处方数',
+          type: 'bar',
+          yAxisIndex: 0,
+          data: data.map(item => item.totalCount),
+          barWidth: '35%',
+          barMaxWidth: 80,
+          itemStyle: {
+            borderRadius: [3, 3, 0, 0],
+            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+              { offset: 0, color: '#66B3FF' },
+              { offset: 1, color: 'rgba(102,179,255,0)' }
+            ]),
+          },
+          emphasis: {
+            itemStyle: {
+              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                { offset: 0, color: '#66B3FF' },
+                { offset: 1, color: 'rgba(102,179,255,0.2)' }
+              ])
+            }
+          },
+          label: {
+            show: false
+          }
+        },
+        {
+          name: '直发率',
+          type: 'line',
+          yAxisIndex: 1,
+          data: data.map(item => item.directSendRate),
+          lineStyle: {
+            color: '#99FFAA',
+            width: 2
+          },
+          itemStyle: {
+            color: '#99FFAA'
+          },
+          symbol: 'none',
+          smooth: true
+        }
+      ],
+      tooltip: {
+        trigger: 'axis',
+        backgroundColor: 'rgba(15, 35, 85, 0.95)',
+        borderColor: 'rgba(0, 212, 255, 0.6)',
+        borderWidth: 1,
+        textStyle: {
+          color: '#FFFFFF',
+          fontFamily: 'DingTalk JinBuTi',
+          fontSize: '0.6vw'
+        },
+        formatter: (params: any) => {
+          const barData = params.find((p: any) => p.seriesType === 'bar');
+          const lineData = params.find((p: any) => p.seriesType === 'line');
+          const dataItem = data[barData.dataIndex];
+          return `<div style="padding: 0.3vw; font-size: 0.6vw; line-height: 1.4;">
+                    <div style="color: #00D4FF; font-weight: bold; margin-bottom: 0.3vw; font-size: 0.65vw;">${barData.name}</div>
+                    <div style="margin-bottom: 0.2vw; display: flex; align-items: center;">
+                      <span style="color: #66B3FF; margin-right: 0.3vw; font-size: 0.8vw;">●</span> 
+                      <span>日均处方数: </span>
+                      <span style="color: #FFFFFF; font-weight: bold; margin-left: 0.2vw;">${dataItem.totalCount}</span>
+                    </div>
+                    <div style="margin-bottom: 0.2vw; display: flex; align-items: center;">
+                      <span style="color: #66B3FF; margin-right: 0.3vw; font-size: 0.8vw;">●</span> 
+                      <span>直发处方数: </span>
+                      <span style="color: #FFFFFF; font-weight: bold; margin-left: 0.2vw;">${dataItem.machineCount}</span>
+                    </div>
+                    <div style="display: flex; align-items: center;">
+                      <span style="color: #99FFAA; margin-right: 0.3vw; font-size: 0.8vw;">●</span> 
+                      <span>直发率: </span>
+                      <span style="color: #FFFFFF; font-weight: bold; margin-left: 0.2vw;">${(dataItem.directSendRate * 100).toFixed(1)}%</span>
+                    </div>
+                  </div>`;
+        }
+      }
+    };
+
+    chartInstance.current.setOption(option);
+  };
+
+  // 监听数据变化,重新渲染图表
+  useEffect(() => {
+    if (data.length > 0) {
+      initChart();
+    }
+  }, [data]);
+
+  // 组件挂载时获取数据
+  useEffect(() => {
+    fetchData();
+  }, [defaultDateTime, selectedMonth]);
+
+  // 监听窗口大小变化,确保图表自适应
+  useEffect(() => {
+    const handleResize = () => {
+      if (chartInstance.current) {
+        chartInstance.current.resize();
+      }
+    };
+
+    window.addEventListener('resize', handleResize);
+    
+    // 使用ResizeObserver监听容器大小变化
+    const resizeObserver = new ResizeObserver(() => {
+      if (chartInstance.current) {
+        chartInstance.current.resize();
+      }
+    });
+    
+    if (chartRef.current) {
+      resizeObserver.observe(chartRef.current);
+    }
+
+    return () => {
+      window.removeEventListener('resize', handleResize);
+      resizeObserver.disconnect();
+      if (chartInstance.current) {
+        chartInstance.current.dispose();
+      }
+    };
+  }, []);
+
+  // 使用刷新Hook,配置对应的模块代码'33'
+  useGlobalRefresh(fetchData, '33');
+
+  return (
+    <ChartWrapper>
+      <PanelHeader>每周处方概况</PanelHeader>
+      <ChartContainer>
+        {loading ? (
+          <div style={{
+            display: 'flex',
+            justifyContent: 'center',
+            alignItems: 'center',
+            height: '100%',
+            color: '#CCE6FF',
+            fontSize: '0.8vw'
+          }}>
+            数据加载中...
+          </div>
+        ) : data.length === 0 ? (
+          <div style={{
+            display: 'flex',
+            justifyContent: 'center',
+            alignItems: 'center',
+            height: '100%',
+            color: '#CCE6FF',
+            fontSize: '0.8vw'
+          }}>
+            暂无数据
+          </div>
+        ) : (
+          <div 
+            ref={chartRef} 
+            style={{ 
+              width: '100%', 
+              height: '100%'
+            }} 
+          />
+        )}
+      </ChartContainer>
+    </ChartWrapper>
+  );
+};
+
+export default DailyPrescriptionChart; 

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff