diff --git a/Touchkebao/package-lock.json b/Touchkebao/package-lock.json index 6ddfbaaa..a02079c2 100644 --- a/Touchkebao/package-lock.json +++ b/Touchkebao/package-lock.json @@ -10,6 +10,8 @@ "license": "MIT", "dependencies": { "@ant-design/icons": "^5.6.1", + "@sentry/react": "^10.29.0", + "@tanstack/react-query": "^5.90.12", "antd": "^5.13.1", "antd-mobile": "^5.39.1", "antd-mobile-icons": "^0.3.0", @@ -2025,6 +2027,124 @@ "win32" ] }, + "node_modules/@sentry-internal/browser-utils": { + "version": "10.29.0", + "resolved": "https://registry.npmmirror.com/@sentry-internal/browser-utils/-/browser-utils-10.29.0.tgz", + "integrity": "sha512-M3kycMY6f3KY9a8jDYac+yG0E3ZgWVWSxlOEC5MhYyX+g7mqxkwrb3LFQyuxSm/m+CCgMTCaPOOaB2twXP6EQg==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.29.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry-internal/feedback": { + "version": "10.29.0", + "resolved": "https://registry.npmmirror.com/@sentry-internal/feedback/-/feedback-10.29.0.tgz", + "integrity": "sha512-Y7IRsNeS99cEONu1mZWZc3HvbjNnu59Hgymm0swFFKbdgbCgdT6l85kn2oLsuq4Ew8Dw/pL/Sgpwsl9UgYFpUg==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.29.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry-internal/replay": { + "version": "10.29.0", + "resolved": "https://registry.npmmirror.com/@sentry-internal/replay/-/replay-10.29.0.tgz", + "integrity": "sha512-45NVw9PwB9TQ8z+xJ6G6Za+wmQ1RTA35heBSzR6U4bknj8LmA04k2iwnobvxCBEQXeLfcJEO1vFgagMoqMZMBw==", + "license": "MIT", + "dependencies": { + "@sentry-internal/browser-utils": "10.29.0", + "@sentry/core": "10.29.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry-internal/replay-canvas": { + "version": "10.29.0", + "resolved": "https://registry.npmmirror.com/@sentry-internal/replay-canvas/-/replay-canvas-10.29.0.tgz", + "integrity": "sha512-typY4JrpAQQGPuSyd/BD8+nNCbvTV2UVvKzr+iKgI0m1qc4Dz8tHZ4Nfais2Z8eYn/pL1kqVQN5ERTmJoYFdIw==", + "license": "MIT", + "dependencies": { + "@sentry-internal/replay": "10.29.0", + "@sentry/core": "10.29.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/browser": { + "version": "10.29.0", + "resolved": "https://registry.npmmirror.com/@sentry/browser/-/browser-10.29.0.tgz", + "integrity": "sha512-XdbyIR6F4qoR9Z1JCWTgunVcTJjS9p2Th+v4wYs4ME+ZdLC4tuKKmRgYg3YdSIWCn1CBfIgdI6wqETSf7H6Njw==", + "license": "MIT", + "dependencies": { + "@sentry-internal/browser-utils": "10.29.0", + "@sentry-internal/feedback": "10.29.0", + "@sentry-internal/replay": "10.29.0", + "@sentry-internal/replay-canvas": "10.29.0", + "@sentry/core": "10.29.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/core": { + "version": "10.29.0", + "resolved": "https://registry.npmmirror.com/@sentry/core/-/core-10.29.0.tgz", + "integrity": "sha512-olQ2DU9dA/Bwsz3PtA9KNXRMqBWRQSkPw+MxwWEoU1K1qtiM9L0j6lbEFb5iSY3d7WYD5MB+1d5COugjSBrHtw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/react": { + "version": "10.29.0", + "resolved": "https://registry.npmmirror.com/@sentry/react/-/react-10.29.0.tgz", + "integrity": "sha512-YGaEUXubzil7qssD1koh1fyt0aS8tHB61/6+oNShJ6xZPg03AB42bNMr2/y8fIFx36kb3MiCA5sFoH/ubF0LnQ==", + "license": "MIT", + "dependencies": { + "@sentry/browser": "10.29.0", + "@sentry/core": "10.29.0", + "hoist-non-react-statics": "^3.3.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.14.0 || 17.x || 18.x || 19.x" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.90.12", + "resolved": "https://registry.npmmirror.com/@tanstack/query-core/-/query-core-5.90.12.tgz", + "integrity": "sha512-T1/8t5DhV/SisWjDnaiU2drl6ySvsHj1bHBCWNXd+/T+Hh1cf6JodyEYMd5sgwm+b/mETT4EV3H+zCVczCU5hg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.90.12", + "resolved": "https://registry.npmmirror.com/@tanstack/react-query/-/react-query-5.90.12.tgz", + "integrity": "sha512-graRZspg7EoEaw0a8faiUASCyJrqjKPdqJ9EwuDRUF9mEYJ1YPczI9H+/agJ0mOJkPCJDk0lsz5QTrLZ/jQ2rg==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.90.12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -4397,6 +4517,21 @@ "node": ">= 0.4" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmmirror.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz", diff --git a/Touchkebao/package.json b/Touchkebao/package.json index 0277589c..d95caed2 100644 --- a/Touchkebao/package.json +++ b/Touchkebao/package.json @@ -5,6 +5,8 @@ "private": true, "dependencies": { "@ant-design/icons": "^5.6.1", + "@sentry/react": "^10.29.0", + "@tanstack/react-query": "^5.90.12", "antd": "^5.13.1", "antd-mobile": "^5.39.1", "antd-mobile-icons": "^0.3.0", diff --git a/Touchkebao/pnpm-lock.yaml b/Touchkebao/pnpm-lock.yaml index 818ed7d7..4002da5c 100644 --- a/Touchkebao/pnpm-lock.yaml +++ b/Touchkebao/pnpm-lock.yaml @@ -11,30 +11,36 @@ importers: '@ant-design/icons': specifier: ^5.6.1 version: 5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@sentry/react': + specifier: ^10.29.0 + version: 10.29.0(react@18.3.1) + '@tanstack/react-query': + specifier: ^5.90.12 + version: 5.90.12(react@18.3.1) antd: specifier: ^5.13.1 - version: 5.27.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 5.29.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) antd-mobile: specifier: ^5.39.1 - version: 5.40.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 5.41.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) antd-mobile-icons: specifier: ^0.3.0 version: 0.3.0 axios: specifier: ^1.6.7 - version: 1.11.0 + version: 1.13.2 dayjs: specifier: ^1.11.13 - version: 1.11.13 + version: 1.11.19 dexie: specifier: ^4.2.0 - version: 4.2.0 + version: 4.2.1 echarts: specifier: ^5.6.0 version: 5.6.0 echarts-for-react: specifier: ^3.0.2 - version: 3.0.2(echarts@5.6.0)(react@18.3.1) + version: 3.0.5(echarts@5.6.0)(react@18.3.1) react: specifier: ^18.2.0 version: 18.3.1 @@ -43,7 +49,7 @@ importers: version: 18.3.1(react@18.3.1) react-router-dom: specifier: ^6.20.0 - version: 6.30.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 6.30.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-window: specifier: ^1.8.11 version: 1.8.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -55,26 +61,26 @@ importers: version: 0.6.0 zustand: specifier: ^5.0.6 - version: 5.0.7(@types/react@19.1.10)(react@18.3.1)(use-sync-external-store@1.5.0(react@18.3.1)) + version: 5.0.9(@types/react@19.2.7)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1)) devDependencies: '@types/node': specifier: ^24.0.14 - version: 24.2.1 + version: 24.10.2 '@types/react': specifier: ^19.1.8 - version: 19.1.10 + version: 19.2.7 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.7(@types/react@19.1.10) + version: 19.2.3(@types/react@19.2.7) '@typescript-eslint/eslint-plugin': specifier: ^7.7.0 - version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2) + version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) '@typescript-eslint/parser': specifier: ^7.7.0 - version: 7.18.0(eslint@8.57.1)(typescript@5.9.2) + version: 7.18.0(eslint@8.57.1)(typescript@5.9.3) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.7.0(vite@7.1.2(@types/node@24.2.1)(sass@1.90.0)) + version: 4.7.0(vite@7.2.7(@types/node@24.10.2)(sass@1.95.1)) eslint: specifier: ^8.57.0 version: 8.57.1 @@ -83,7 +89,7 @@ importers: version: 9.1.2(eslint@8.57.1) eslint-plugin-prettier: specifier: ^5.1.3 - version: 5.5.4(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.6.2) + version: 5.5.4(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.7.4) eslint-plugin-react: specifier: ^7.34.1 version: 7.37.5(eslint@8.57.1) @@ -98,23 +104,19 @@ importers: version: 6.1.0(postcss@8.5.6) prettier: specifier: ^3.2.5 - version: 3.6.2 + version: 3.7.4 sass: specifier: ^1.75.0 - version: 1.90.0 + version: 1.95.1 typescript: specifier: ^5.4.5 - version: 5.9.2 + version: 5.9.3 vite: specifier: ^7.0.5 - version: 7.1.2(@types/node@24.2.1)(sass@1.90.0) + version: 7.2.7(@types/node@24.10.2)(sass@1.95.1) packages: - '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} - '@ant-design/colors@7.2.1': resolution: {integrity: sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==} @@ -153,16 +155,16 @@ packages: resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.28.0': - resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==} + '@babel/compat-data@7.28.5': + resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} engines: {node: '>=6.9.0'} - '@babel/core@7.28.0': - resolution: {integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==} + '@babel/core@7.28.5': + resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} engines: {node: '>=6.9.0'} - '@babel/generator@7.28.0': - resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==} + '@babel/generator@7.28.5': + resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} engines: {node: '>=6.9.0'} '@babel/helper-compilation-targets@7.27.2': @@ -177,8 +179,8 @@ packages: resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.27.3': - resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==} + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -191,20 +193,20 @@ packages: resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.27.1': - resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} '@babel/helper-validator-option@7.27.1': resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.28.2': - resolution: {integrity: sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==} + '@babel/helpers@7.28.4': + resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} engines: {node: '>=6.9.0'} - '@babel/parser@7.28.0': - resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==} + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} engines: {node: '>=6.0.0'} hasBin: true @@ -220,20 +222,20 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/runtime@7.28.2': - resolution: {integrity: sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==} + '@babel/runtime@7.28.4': + resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} engines: {node: '>=6.9.0'} '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.0': - resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==} + '@babel/traverse@7.28.5': + resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} engines: {node: '>=6.9.0'} - '@babel/types@7.28.2': - resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} '@emotion/hash@0.8.0': @@ -242,170 +244,170 @@ packages: '@emotion/unitless@0.7.5': resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==} - '@esbuild/aix-ppc64@0.25.8': - resolution: {integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==} + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.8': - resolution: {integrity: sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==} + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.8': - resolution: {integrity: sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==} + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.8': - resolution: {integrity: sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==} + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.8': - resolution: {integrity: sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==} + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.8': - resolution: {integrity: sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==} + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.8': - resolution: {integrity: sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==} + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.8': - resolution: {integrity: sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==} + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.8': - resolution: {integrity: sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==} + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.8': - resolution: {integrity: sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==} + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.8': - resolution: {integrity: sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==} + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.8': - resolution: {integrity: sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==} + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.8': - resolution: {integrity: sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==} + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.8': - resolution: {integrity: sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==} + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.8': - resolution: {integrity: sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==} + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.8': - resolution: {integrity: sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==} + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.8': - resolution: {integrity: sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==} + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.8': - resolution: {integrity: sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==} + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.8': - resolution: {integrity: sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==} + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.8': - resolution: {integrity: sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==} + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.8': - resolution: {integrity: sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==} + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.25.8': - resolution: {integrity: sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==} + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.25.8': - resolution: {integrity: sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==} + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.8': - resolution: {integrity: sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==} + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.8': - resolution: {integrity: sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==} + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.8': - resolution: {integrity: sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==} + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.7.0': - resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} + '@eslint-community/eslint-utils@4.9.0': + resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/regexpp@4.12.1': - resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} '@eslint/eslintrc@2.1.4': @@ -419,8 +421,8 @@ packages: '@floating-ui/core@1.7.3': resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} - '@floating-ui/dom@1.7.3': - resolution: {integrity: sha512-uZA413QEpNuhtb3/iIKoYMSK07keHPYeXF02Zhd6e213j+d1NamLix/mCLxBUDW/Gx52sPH2m+chlUsyaBs/Ag==} + '@floating-ui/dom@1.7.4': + resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} '@floating-ui/utils@0.2.10': resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} @@ -441,6 +443,9 @@ packages: '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} @@ -448,8 +453,8 @@ packages: '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - '@jridgewell/trace-mapping@0.3.30': - resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -492,36 +497,42 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.1': resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.1': resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.1': resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.1': resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.1': resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-win32-arm64@2.5.1': resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} @@ -583,8 +594,8 @@ packages: react: '>=16.9.0' react-dom: '>=16.9.0' - '@rc-component/qrcode@1.0.0': - resolution: {integrity: sha512-L+rZ4HXP2sJ1gHMGHjsg9jlYBX/SLN2D6OxP9Zn3qgtpMWtO2vUfxVFwiogHpAIqs54FnALxraUy/BCO1yRIgg==} + '@rc-component/qrcode@1.1.1': + resolution: {integrity: sha512-LfLGNymzKdUPjXUbRP+xOhIWY4jQ+YMj5MmWAcgcAq1Ij8XP7tRmAXqyuv96XvLUBE/5cA8hLFl9eO1JQMujrA==} engines: {node: '>=8.x'} peerDependencies: react: '>=16.9.0' @@ -631,113 +642,172 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - '@remix-run/router@1.23.0': - resolution: {integrity: sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==} + '@remix-run/router@1.23.1': + resolution: {integrity: sha512-vDbaOzF7yT2Qs4vO6XV1MHcJv+3dgR1sT+l3B8xxOVhUC336prMvqrvsLL/9Dnw2xr6Qhz4J0dmS0llNAbnUmQ==} engines: {node: '>=14.0.0'} '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} - '@rollup/rollup-android-arm-eabi@4.46.2': - resolution: {integrity: sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==} + '@rollup/rollup-android-arm-eabi@4.53.3': + resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.46.2': - resolution: {integrity: sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==} + '@rollup/rollup-android-arm64@4.53.3': + resolution: {integrity: sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.46.2': - resolution: {integrity: sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==} + '@rollup/rollup-darwin-arm64@4.53.3': + resolution: {integrity: sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.46.2': - resolution: {integrity: sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==} + '@rollup/rollup-darwin-x64@4.53.3': + resolution: {integrity: sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.46.2': - resolution: {integrity: sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==} + '@rollup/rollup-freebsd-arm64@4.53.3': + resolution: {integrity: sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.46.2': - resolution: {integrity: sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==} + '@rollup/rollup-freebsd-x64@4.53.3': + resolution: {integrity: sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.46.2': - resolution: {integrity: sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==} + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': + resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==} cpu: [arm] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.46.2': - resolution: {integrity: sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==} + '@rollup/rollup-linux-arm-musleabihf@4.53.3': + resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==} cpu: [arm] os: [linux] + libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.46.2': - resolution: {integrity: sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==} + '@rollup/rollup-linux-arm64-gnu@4.53.3': + resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==} cpu: [arm64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.46.2': - resolution: {integrity: sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==} + '@rollup/rollup-linux-arm64-musl@4.53.3': + resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==} cpu: [arm64] os: [linux] + libc: [musl] - '@rollup/rollup-linux-loongarch64-gnu@4.46.2': - resolution: {integrity: sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==} + '@rollup/rollup-linux-loong64-gnu@4.53.3': + resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==} cpu: [loong64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-ppc64-gnu@4.46.2': - resolution: {integrity: sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==} + '@rollup/rollup-linux-ppc64-gnu@4.53.3': + resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==} cpu: [ppc64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-riscv64-gnu@4.46.2': - resolution: {integrity: sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==} + '@rollup/rollup-linux-riscv64-gnu@4.53.3': + resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==} cpu: [riscv64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.46.2': - resolution: {integrity: sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==} + '@rollup/rollup-linux-riscv64-musl@4.53.3': + resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==} cpu: [riscv64] os: [linux] + libc: [musl] - '@rollup/rollup-linux-s390x-gnu@4.46.2': - resolution: {integrity: sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==} + '@rollup/rollup-linux-s390x-gnu@4.53.3': + resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==} cpu: [s390x] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.46.2': - resolution: {integrity: sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==} + '@rollup/rollup-linux-x64-gnu@4.53.3': + resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==} cpu: [x64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.46.2': - resolution: {integrity: sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==} + '@rollup/rollup-linux-x64-musl@4.53.3': + resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==} cpu: [x64] os: [linux] + libc: [musl] - '@rollup/rollup-win32-arm64-msvc@4.46.2': - resolution: {integrity: sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==} + '@rollup/rollup-openharmony-arm64@4.53.3': + resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.53.3': + resolution: {integrity: sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.46.2': - resolution: {integrity: sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==} + '@rollup/rollup-win32-ia32-msvc@4.53.3': + resolution: {integrity: sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.46.2': - resolution: {integrity: sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==} + '@rollup/rollup-win32-x64-gnu@4.53.3': + resolution: {integrity: sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==} cpu: [x64] os: [win32] + '@rollup/rollup-win32-x64-msvc@4.53.3': + resolution: {integrity: sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==} + cpu: [x64] + os: [win32] + + '@sentry-internal/browser-utils@10.29.0': + resolution: {integrity: sha512-M3kycMY6f3KY9a8jDYac+yG0E3ZgWVWSxlOEC5MhYyX+g7mqxkwrb3LFQyuxSm/m+CCgMTCaPOOaB2twXP6EQg==} + engines: {node: '>=18'} + + '@sentry-internal/feedback@10.29.0': + resolution: {integrity: sha512-Y7IRsNeS99cEONu1mZWZc3HvbjNnu59Hgymm0swFFKbdgbCgdT6l85kn2oLsuq4Ew8Dw/pL/Sgpwsl9UgYFpUg==} + engines: {node: '>=18'} + + '@sentry-internal/replay-canvas@10.29.0': + resolution: {integrity: sha512-typY4JrpAQQGPuSyd/BD8+nNCbvTV2UVvKzr+iKgI0m1qc4Dz8tHZ4Nfais2Z8eYn/pL1kqVQN5ERTmJoYFdIw==} + engines: {node: '>=18'} + + '@sentry-internal/replay@10.29.0': + resolution: {integrity: sha512-45NVw9PwB9TQ8z+xJ6G6Za+wmQ1RTA35heBSzR6U4bknj8LmA04k2iwnobvxCBEQXeLfcJEO1vFgagMoqMZMBw==} + engines: {node: '>=18'} + + '@sentry/browser@10.29.0': + resolution: {integrity: sha512-XdbyIR6F4qoR9Z1JCWTgunVcTJjS9p2Th+v4wYs4ME+ZdLC4tuKKmRgYg3YdSIWCn1CBfIgdI6wqETSf7H6Njw==} + engines: {node: '>=18'} + + '@sentry/core@10.29.0': + resolution: {integrity: sha512-olQ2DU9dA/Bwsz3PtA9KNXRMqBWRQSkPw+MxwWEoU1K1qtiM9L0j6lbEFb5iSY3d7WYD5MB+1d5COugjSBrHtw==} + engines: {node: '>=18'} + + '@sentry/react@10.29.0': + resolution: {integrity: sha512-YGaEUXubzil7qssD1koh1fyt0aS8tHB61/6+oNShJ6xZPg03AB42bNMr2/y8fIFx36kb3MiCA5sFoH/ubF0LnQ==} + engines: {node: '>=18'} + peerDependencies: + react: ^16.14.0 || 17.x || 18.x || 19.x + + '@tanstack/query-core@5.90.12': + resolution: {integrity: sha512-T1/8t5DhV/SisWjDnaiU2drl6ySvsHj1bHBCWNXd+/T+Hh1cf6JodyEYMd5sgwm+b/mETT4EV3H+zCVczCU5hg==} + + '@tanstack/react-query@5.90.12': + resolution: {integrity: sha512-graRZspg7EoEaw0a8faiUASCyJrqjKPdqJ9EwuDRUF9mEYJ1YPczI9H+/agJ0mOJkPCJDk0lsz5QTrLZ/jQ2rg==} + peerDependencies: + react: ^18 || ^19 + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -753,16 +823,19 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - '@types/node@24.2.1': - resolution: {integrity: sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==} + '@types/js-cookie@3.0.6': + resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==} - '@types/react-dom@19.1.7': - resolution: {integrity: sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==} + '@types/node@24.10.2': + resolution: {integrity: sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: - '@types/react': ^19.0.0 + '@types/react': ^19.2.0 - '@types/react@19.1.10': - resolution: {integrity: sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg==} + '@types/react@19.2.7': + resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==} '@typescript-eslint/eslint-plugin@7.18.0': resolution: {integrity: sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==} @@ -849,9 +922,8 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - ahooks@3.9.0: - resolution: {integrity: sha512-r20/C38aFyU3Zqp3620gkdLnxmQhnmWORB3eGGTDlM4i/fOc0GUvM+f2oleMzEu7b3+pHXyzz+FB6ojxsUdYdw==} - engines: {node: '>=8.0.0'} + ahooks@3.9.6: + resolution: {integrity: sha512-Mr7f05swd5SmKlR9SZo5U6M0LsL4ErweLzpdgXjA1JPmnZ78Vr6wzx0jUtvoxrcqGKYnX0Yjc02iEASVxHFPjQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -873,14 +945,14 @@ packages: antd-mobile-v5-count@1.0.1: resolution: {integrity: sha512-YGsiEDCPUDz3SzfXi6gLZn/HpeSMW+jgPc4qiYUr1fSopg3hkUie2TnooJdExgfiETHefH3Ggs58He0OVfegLA==} - antd-mobile@5.40.0: - resolution: {integrity: sha512-nNfkTLiYPsa7A2i7eoG/hr2BM0agR4cfugE2+5HTyGnCg5xQT04Pmt9qEoKv7MOW5BaiiMyjO462Kh8KRF5QBA==} + antd-mobile@5.41.1: + resolution: {integrity: sha512-fS5sTRLKHca5qryEYLGiPDLANK0rbhx8f8xk0Olu6ef00tLe0P9iqHQm0U3UtEBd8S454cilw5uv2J3I79Tbgg==} peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - antd@5.27.0: - resolution: {integrity: sha512-o54dmpooLOc08RSGCkeEQBYAGPxUSmnhmYJKCNTHH46vzjOVxdteu+wPTRVkRbAkDTbs2VcNr5VL7Lu67rPIiA==} + antd@5.29.1: + resolution: {integrity: sha512-TTFVbpKbyL6cPfEoKq6Ya3BIjTUr7uDW9+7Z+1oysRv1gpcN7kQ4luH8r/+rXXwz4n6BIz1iBJ1ezKCdsdNW0w==} peerDependencies: react: '>=16.9.0' react-dom: '>=16.9.0' @@ -934,12 +1006,16 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - axios@1.11.0: - resolution: {integrity: sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==} + axios@1.13.2: + resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==} balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + baseline-browser-mapping@2.9.5: + resolution: {integrity: sha512-D5vIoztZOq1XM54LUdttJVc96ggEsIfju2JBvht06pSzpckp3C7HReun67Bghzrtdsq9XdMGbSSB3v3GhMNmAA==} + hasBin: true + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -950,8 +1026,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.25.2: - resolution: {integrity: sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==} + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -971,8 +1047,8 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - caniuse-lite@1.0.30001734: - resolution: {integrity: sha512-uhE1Ye5vgqju6OI71HTQqcBCZrvHugk0MjLak7Q+HfoBgoq5Bi+5YnwjP4fjDgrtYr/l8MVRBvzz9dPD4KyK0A==} + caniuse-lite@1.0.30001760: + resolution: {integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} @@ -1005,22 +1081,22 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - copy-text-to-clipboard@3.2.0: - resolution: {integrity: sha512-RnJFp1XR/LOBDckxTib5Qjr/PMfkatD0MUCQgdpqS8MdKiNUzBjAQBEN6oUy+jW7LI93BBG3DtMB2KOOKpGs2Q==} + copy-text-to-clipboard@3.2.2: + resolution: {integrity: sha512-T6SqyLd1iLuqPA90J5N4cTalrtovCySh58iiZDGJ6FGznbclKh4UI+FGacQSgFzwKG77W7XT5gwbVEbd9cIH1A==} engines: {node: '>=12'} copy-to-clipboard@3.3.3: resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} - core-js@3.45.0: - resolution: {integrity: sha512-c2KZL9lP4DjkN3hk/an4pWn5b5ZefhRJnAc42n6LJ19kSnbeRbdQZE5dSeE2LBol1OwJD3X1BQvFTAsa8ReeDA==} + core-js@3.47.0: + resolution: {integrity: sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==} cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - csstype@3.1.3: - resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} data-view-buffer@1.0.2: resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} @@ -1034,11 +1110,11 @@ packages: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} - dayjs@1.11.13: - resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + dayjs@1.11.19: + resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} - debug@4.4.1: - resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -1070,8 +1146,8 @@ packages: engines: {node: '>=0.10'} hasBin: true - dexie@4.2.0: - resolution: {integrity: sha512-OSeyyWOUetDy9oFWeddJgi83OnRA3hSFh3RrbltmPgqHszE9f24eUCVLI4mPg0ifsWk0lQTdnS+jyGNrPMvhDA==} + dexie@4.2.1: + resolution: {integrity: sha512-Ckej0NS6jxQ4Po3OrSQBFddayRhTCic2DoCAG5zacOfOVB9P2Q5Xc5uL/nVa7ZVs+HdMnvUPzLFCB/JwpB6Csg==} dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} @@ -1089,17 +1165,17 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - echarts-for-react@3.0.2: - resolution: {integrity: sha512-DRwIiTzx8JfwPOVgGttDytBqdp5VzCSyMRIxubgU/g2n9y3VLUmF2FK7Icmg/sNVkv4+rktmrLN9w22U2yy3fA==} + echarts-for-react@3.0.5: + resolution: {integrity: sha512-YpEI5Ty7O/2nvCfQ7ybNa+S90DwE8KYZWacGvJW4luUqywP7qStQ+pxDlYOmr4jGDu10mhEkiAuMKcUlT4W5vg==} peerDependencies: - echarts: ^3.0.0 || ^4.0.0 || ^5.0.0 + echarts: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 react: ^15.0.0 || >=16.0.0 echarts@5.6.0: resolution: {integrity: sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==} - electron-to-chromium@1.5.200: - resolution: {integrity: sha512-rFCxROw7aOe4uPTfIAx+rXv9cEcGx+buAF4npnhtTqCJk5KDFRnh3+KYj7rdVh6lsFt5/aPs+Irj9rZ33WMA7w==} + electron-to-chromium@1.5.267: + resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} es-abstract@1.24.0: resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} @@ -1133,8 +1209,8 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} - esbuild@0.25.8: - resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==} + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} engines: {node: '>=18'} hasBin: true @@ -1231,8 +1307,9 @@ packages: fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} - fdir@6.4.6: - resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: @@ -1271,8 +1348,8 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} - form-data@4.0.4: - resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} fs.realpath@1.0.0: @@ -1293,6 +1370,10 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -1367,12 +1448,15 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - immutable@5.1.3: - resolution: {integrity: sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==} + immutable@5.1.4: + resolution: {integrity: sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==} import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} @@ -1395,6 +1479,7 @@ packages: intersection-observer@0.12.2: resolution: {integrity: sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==} + deprecated: The Intersection Observer polyfill is no longer needed and can safely be removed. Intersection Observer has been Baseline since 2019. is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} @@ -1436,8 +1521,8 @@ packages: resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} engines: {node: '>= 0.4'} - is-generator-function@1.1.0: - resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} engines: {node: '>= 0.4'} is-glob@4.0.3: @@ -1517,8 +1602,8 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true jsesc@3.1.0: @@ -1621,8 +1706,8 @@ packages: node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} - node-releases@2.0.19: - resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} @@ -1726,8 +1811,8 @@ packages: resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} engines: {node: '>=6.0.0'} - prettier@3.6.2: - resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + prettier@3.7.4: + resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==} engines: {node: '>=14'} hasBin: true @@ -1787,8 +1872,8 @@ packages: react: '>=16.9.0' react-dom: '>=16.9.0' - rc-field-form@2.7.0: - resolution: {integrity: sha512-hgKsCay2taxzVnBPZl+1n4ZondsV78G++XVsMIJCAoioMjlMQR9YwAp7JZDIECzIu2Z66R+f4SFIRrO2DjDNAA==} + rc-field-form@2.7.1: + resolution: {integrity: sha512-vKeSifSJ6HoLaAB+B8aq/Qgm8a3dyxROzCtKNCsBQgiverpc4kWDQihoUwzUj+zNWJOykwSY4dNX3QrGwtVb9A==} engines: {node: '>=8.x'} peerDependencies: react: '>=16.9.0' @@ -1837,8 +1922,8 @@ packages: react: '>=16.9.0' react-dom: '>=16.9.0' - rc-overflow@1.4.1: - resolution: {integrity: sha512-3MoPQQPV1uKyOMVNd6SZfONi+f3st0r8PksexIdBTeIYbMX0Jr+k7pHEDvsXtR4BpCv90/Pv2MovVNhktKrwvw==} + rc-overflow@1.5.0: + resolution: {integrity: sha512-Lm/v9h0LymeUYJf0x39OveU52InkdRXqnn2aYXfWmo8WdOonIKB2kfau+GF0fWq6jPgtdO9yMqveGcK6aIhJmg==} peerDependencies: react: '>=16.9.0' react-dom: '>=16.9.0' @@ -1907,8 +1992,8 @@ packages: react: '*' react-dom: '*' - rc-slider@11.1.8: - resolution: {integrity: sha512-2gg/72YFSpKP+Ja5AjC5DPL1YnV8DEITDQrcc1eASrUYjl0esptaBVJBh5nLTXCCp15eD8EuGjwezVGSHhs9tQ==} + rc-slider@11.1.9: + resolution: {integrity: sha512-h8IknhzSh3FEM9u8ivkskh+Ef4Yo4JRIY2nj7MrH6GQmrwV6mcpJf5/4KgH5JaVI1H3E52yCdpOlVyGZIeph5A==} engines: {node: '>=8.x'} peerDependencies: react: '>=16.9.0' @@ -1927,8 +2012,8 @@ packages: react: '>=16.9.0' react-dom: '>=16.9.0' - rc-table@7.51.1: - resolution: {integrity: sha512-5iq15mTHhvC42TlBLRCoCBLoCmGlbRZAlyF21FonFnS/DIC8DeRqnmdyVREwt2CFbPceM0zSNdEeVfiGaqYsKw==} + rc-table@7.54.0: + resolution: {integrity: sha512-/wDTkki6wBTjwylwAGjpLKYklKo9YgjZwAU77+7ME5mBoS32Q4nAwoqhA2lSge6fobLW3Tap6uc5xfwaL2p0Sw==} engines: {node: '>=8.x'} peerDependencies: react: '>=16.9.0' @@ -1966,8 +2051,8 @@ packages: react: '*' react-dom: '*' - rc-upload@4.9.2: - resolution: {integrity: sha512-nHx+9rbd1FKMiMRYsqQ3NkXUv7COHPBo3X1Obwq9SWS6/diF/A0aJ5OHubvwUAIDs+4RMleljV0pcrNUc823GQ==} + rc-upload@4.11.0: + resolution: {integrity: sha512-ZUyT//2JAehfHzjWowqROcwYJKnZkIUGWaTE/VogVrepSl7AFNbQf4+zGfX4zl9Vrj/Jm8scLO0R6UlPDKK4wA==} peerDependencies: react: '>=16.9.0' react-dom: '>=16.9.0' @@ -1978,8 +2063,8 @@ packages: react: '>=16.9.0' react-dom: '>=16.9.0' - rc-virtual-list@3.19.1: - resolution: {integrity: sha512-DCapO2oyPqmooGhxBuXHM4lFuX+sshQwWqqkuyFA+4rShLe//+GEPVwiDgO+jKtKHtbeYwZoNvetwfHdOf+iUQ==} + rc-virtual-list@3.19.2: + resolution: {integrity: sha512-Ys6NcjwGkuwkeaWBDqfI3xWuZ7rDiQXlH1o2zLfFzATfEgXcqpk8CkgMfbJD81McqjcJVez25a3kPxCR807evA==} engines: {node: '>=8.x'} peerDependencies: react: '>=16.9.0' @@ -2003,15 +2088,15 @@ packages: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} - react-router-dom@6.30.1: - resolution: {integrity: sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==} + react-router-dom@6.30.2: + resolution: {integrity: sha512-l2OwHn3UUnEVUqc6/1VMmR1cvZryZ3j3NzapC2eUXO1dB0sYp5mvwdjiXhpUbRb21eFow3qSxpP8Yv6oAU824Q==} engines: {node: '>=14.0.0'} peerDependencies: react: '>=16.8' react-dom: '>=16.8' - react-router@6.30.1: - resolution: {integrity: sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==} + react-router@6.30.2: + resolution: {integrity: sha512-H2Bm38Zu1bm8KUE5NVWRMzuIyAV8p/JrOaBJAwVmp37AXG72+CZJlEBw6pdn9i5TBgLMhNDgijS4ZlblpHyWTA==} engines: {node: '>=14.0.0'} peerDependencies: react: '>=16.8' @@ -2059,8 +2144,8 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - rollup@4.46.2: - resolution: {integrity: sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==} + rollup@4.53.3: + resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -2082,8 +2167,8 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} - sass@1.90.0: - resolution: {integrity: sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==} + sass@1.95.1: + resolution: {integrity: sha512-uPoDh5NIEZV4Dp5GBodkmNY9tSQfXY02pmCcUo+FR1P+x953HGkpw+vV28D4IqYB6f8webZtwoSaZaiPtpTeMg==} engines: {node: '>=14.0.0'} hasBin: true @@ -2101,8 +2186,8 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.7.2: - resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} engines: {node: '>=10'} hasBin: true @@ -2214,8 +2299,8 @@ packages: resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==} engines: {node: '>=12.22'} - tinyglobby@0.2.14: - resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} to-regex-range@5.0.1: @@ -2261,8 +2346,8 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} - typescript@5.9.2: - resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} hasBin: true @@ -2270,11 +2355,11 @@ packages: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} - undici-types@7.10.0: - resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==} + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - update-browserslist-db@1.1.3: - resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + update-browserslist-db@1.2.2: + resolution: {integrity: sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -2282,16 +2367,16 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - use-sync-external-store@1.5.0: - resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==} + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 vconsole@3.15.1: resolution: {integrity: sha512-KH8XLdrq9T5YHJO/ixrjivHfmF2PC2CdVoK6RWZB4yftMykYIaXY1mxZYAic70vADM54kpMQF+dYmvl5NRNy1g==} - vite@7.1.2: - resolution: {integrity: sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==} + vite@7.2.7: + resolution: {integrity: sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -2372,8 +2457,8 @@ packages: zrender@5.6.1: resolution: {integrity: sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==} - zustand@5.0.7: - resolution: {integrity: sha512-Ot6uqHDW/O2VdYsKLLU8GQu8sCOM1LcoE8RwvLv9uuRT9s6SOHCKs0ZEOhxg+I1Ld+A1Q5lwx+UlKXXUoCZITg==} + zustand@5.0.9: + resolution: {integrity: sha512-ALBtUj0AfjJt3uNRQoL1tL2tMvj6Gp/6e39dnfT6uzpelGru8v1tPOGBzayOWbPJvujM8JojDk3E1LxeFisBNg==} engines: {node: '>=12.20.0'} peerDependencies: '@types/react': '>=18.0.0' @@ -2392,11 +2477,6 @@ packages: snapshots: - '@ampproject/remapping@2.3.0': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.30 - '@ant-design/colors@7.2.1': dependencies: '@ant-design/fast-color': 2.0.6 @@ -2404,18 +2484,18 @@ snapshots: '@ant-design/cssinjs-utils@1.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@ant-design/cssinjs': 1.24.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) '@ant-design/cssinjs@1.24.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 '@emotion/hash': 0.8.0 '@emotion/unitless': 0.7.5 classnames: 2.5.1 - csstype: 3.1.3 + csstype: 3.2.3 rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -2423,7 +2503,7 @@ snapshots: '@ant-design/fast-color@2.0.6': dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 '@ant-design/icons-svg@4.4.2': {} @@ -2431,7 +2511,7 @@ snapshots: dependencies: '@ant-design/colors': 7.2.1 '@ant-design/icons-svg': 4.4.2 - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 classnames: 2.5.1 rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 @@ -2439,7 +2519,7 @@ snapshots: '@ant-design/react-slick@1.1.2(react@18.3.1)': dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 classnames: 2.5.1 json2mq: 0.2.0 react: 18.3.1 @@ -2448,45 +2528,45 @@ snapshots: '@babel/code-frame@7.27.1': dependencies: - '@babel/helper-validator-identifier': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.28.0': {} + '@babel/compat-data@7.28.5': {} - '@babel/core@7.28.0': + '@babel/core@7.28.5': dependencies: - '@ampproject/remapping': 2.3.0 '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.0 + '@babel/generator': 7.28.5 '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0) - '@babel/helpers': 7.28.2 - '@babel/parser': 7.28.0 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) + '@babel/helpers': 7.28.4 + '@babel/parser': 7.28.5 '@babel/template': 7.27.2 - '@babel/traverse': 7.28.0 - '@babel/types': 7.28.2 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.1 + debug: 4.4.3 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/generator@7.28.0': + '@babel/generator@7.28.5': dependencies: - '@babel/parser': 7.28.0 - '@babel/types': 7.28.2 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 '@babel/helper-compilation-targets@7.27.2': dependencies: - '@babel/compat-data': 7.28.0 + '@babel/compat-data': 7.28.5 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.25.2 + browserslist: 4.28.1 lru-cache: 5.1.1 semver: 6.3.1 @@ -2494,17 +2574,17 @@ snapshots: '@babel/helper-module-imports@7.27.1': dependencies: - '@babel/traverse': 7.28.0 - '@babel/types': 7.28.2 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.27.3(@babel/core@7.28.0)': + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.5 '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.0 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -2512,152 +2592,152 @@ snapshots: '@babel/helper-string-parser@7.27.1': {} - '@babel/helper-validator-identifier@7.27.1': {} + '@babel/helper-validator-identifier@7.28.5': {} '@babel/helper-validator-option@7.27.1': {} - '@babel/helpers@7.28.2': + '@babel/helpers@7.28.4': dependencies: '@babel/template': 7.27.2 - '@babel/types': 7.28.2 + '@babel/types': 7.28.5 - '@babel/parser@7.28.0': + '@babel/parser@7.28.5': dependencies: - '@babel/types': 7.28.2 + '@babel/types': 7.28.5 - '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.0)': + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.0)': + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.5 '@babel/helper-plugin-utils': 7.27.1 - '@babel/runtime@7.28.2': {} + '@babel/runtime@7.28.4': {} '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.0 - '@babel/types': 7.28.2 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 - '@babel/traverse@7.28.0': + '@babel/traverse@7.28.5': dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.0 + '@babel/generator': 7.28.5 '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.0 + '@babel/parser': 7.28.5 '@babel/template': 7.27.2 - '@babel/types': 7.28.2 - debug: 4.4.1 + '@babel/types': 7.28.5 + debug: 4.4.3 transitivePeerDependencies: - supports-color - '@babel/types@7.28.2': + '@babel/types@7.28.5': dependencies: '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 '@emotion/hash@0.8.0': {} '@emotion/unitless@0.7.5': {} - '@esbuild/aix-ppc64@0.25.8': + '@esbuild/aix-ppc64@0.25.12': optional: true - '@esbuild/android-arm64@0.25.8': + '@esbuild/android-arm64@0.25.12': optional: true - '@esbuild/android-arm@0.25.8': + '@esbuild/android-arm@0.25.12': optional: true - '@esbuild/android-x64@0.25.8': + '@esbuild/android-x64@0.25.12': optional: true - '@esbuild/darwin-arm64@0.25.8': + '@esbuild/darwin-arm64@0.25.12': optional: true - '@esbuild/darwin-x64@0.25.8': + '@esbuild/darwin-x64@0.25.12': optional: true - '@esbuild/freebsd-arm64@0.25.8': + '@esbuild/freebsd-arm64@0.25.12': optional: true - '@esbuild/freebsd-x64@0.25.8': + '@esbuild/freebsd-x64@0.25.12': optional: true - '@esbuild/linux-arm64@0.25.8': + '@esbuild/linux-arm64@0.25.12': optional: true - '@esbuild/linux-arm@0.25.8': + '@esbuild/linux-arm@0.25.12': optional: true - '@esbuild/linux-ia32@0.25.8': + '@esbuild/linux-ia32@0.25.12': optional: true - '@esbuild/linux-loong64@0.25.8': + '@esbuild/linux-loong64@0.25.12': optional: true - '@esbuild/linux-mips64el@0.25.8': + '@esbuild/linux-mips64el@0.25.12': optional: true - '@esbuild/linux-ppc64@0.25.8': + '@esbuild/linux-ppc64@0.25.12': optional: true - '@esbuild/linux-riscv64@0.25.8': + '@esbuild/linux-riscv64@0.25.12': optional: true - '@esbuild/linux-s390x@0.25.8': + '@esbuild/linux-s390x@0.25.12': optional: true - '@esbuild/linux-x64@0.25.8': + '@esbuild/linux-x64@0.25.12': optional: true - '@esbuild/netbsd-arm64@0.25.8': + '@esbuild/netbsd-arm64@0.25.12': optional: true - '@esbuild/netbsd-x64@0.25.8': + '@esbuild/netbsd-x64@0.25.12': optional: true - '@esbuild/openbsd-arm64@0.25.8': + '@esbuild/openbsd-arm64@0.25.12': optional: true - '@esbuild/openbsd-x64@0.25.8': + '@esbuild/openbsd-x64@0.25.12': optional: true - '@esbuild/openharmony-arm64@0.25.8': + '@esbuild/openharmony-arm64@0.25.12': optional: true - '@esbuild/sunos-x64@0.25.8': + '@esbuild/sunos-x64@0.25.12': optional: true - '@esbuild/win32-arm64@0.25.8': + '@esbuild/win32-arm64@0.25.12': optional: true - '@esbuild/win32-ia32@0.25.8': + '@esbuild/win32-ia32@0.25.12': optional: true - '@esbuild/win32-x64@0.25.8': + '@esbuild/win32-x64@0.25.12': optional: true - '@eslint-community/eslint-utils@4.7.0(eslint@8.57.1)': + '@eslint-community/eslint-utils@4.9.0(eslint@8.57.1)': dependencies: eslint: 8.57.1 eslint-visitor-keys: 3.4.3 - '@eslint-community/regexpp@4.12.1': {} + '@eslint-community/regexpp@4.12.2': {} '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.4.1 + debug: 4.4.3 espree: 9.6.1 globals: 13.24.0 ignore: 5.3.2 import-fresh: 3.3.1 - js-yaml: 4.1.0 + js-yaml: 4.1.1 minimatch: 3.1.2 strip-json-comments: 3.1.1 transitivePeerDependencies: @@ -2669,7 +2749,7 @@ snapshots: dependencies: '@floating-ui/utils': 0.2.10 - '@floating-ui/dom@1.7.3': + '@floating-ui/dom@1.7.4': dependencies: '@floating-ui/core': 1.7.3 '@floating-ui/utils': 0.2.10 @@ -2679,7 +2759,7 @@ snapshots: '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.1 + debug: 4.4.3 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -2691,13 +2771,18 @@ snapshots: '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/resolve-uri@3.1.2': {} '@jridgewell/sourcemap-codec@1.5.5': {} - '@jridgewell/trace-mapping@0.3.30': + '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 @@ -2779,12 +2864,12 @@ snapshots: '@rc-component/async-validator@5.0.4': dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 '@rc-component/color-picker@2.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@ant-design/fast-color': 2.0.6 - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 classnames: 2.5.1 rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 @@ -2792,18 +2877,18 @@ snapshots: '@rc-component/context@1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) '@rc-component/mini-decimal@1.1.0': dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 '@rc-component/mutate-observer@1.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 classnames: 2.5.1 rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 @@ -2811,23 +2896,21 @@ snapshots: '@rc-component/portal@1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 classnames: 2.5.1 rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@rc-component/qrcode@1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@rc-component/qrcode@1.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.28.2 - classnames: 2.5.1 - rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@babel/runtime': 7.28.4 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) '@rc-component/tour@1.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@rc-component/trigger': 2.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) classnames: 2.5.1 @@ -2837,7 +2920,7 @@ snapshots: '@rc-component/trigger@2.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) classnames: 2.5.1 rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -2879,133 +2962,183 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@remix-run/router@1.23.0': {} + '@remix-run/router@1.23.1': {} '@rolldown/pluginutils@1.0.0-beta.27': {} - '@rollup/rollup-android-arm-eabi@4.46.2': + '@rollup/rollup-android-arm-eabi@4.53.3': optional: true - '@rollup/rollup-android-arm64@4.46.2': + '@rollup/rollup-android-arm64@4.53.3': optional: true - '@rollup/rollup-darwin-arm64@4.46.2': + '@rollup/rollup-darwin-arm64@4.53.3': optional: true - '@rollup/rollup-darwin-x64@4.46.2': + '@rollup/rollup-darwin-x64@4.53.3': optional: true - '@rollup/rollup-freebsd-arm64@4.46.2': + '@rollup/rollup-freebsd-arm64@4.53.3': optional: true - '@rollup/rollup-freebsd-x64@4.46.2': + '@rollup/rollup-freebsd-x64@4.53.3': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.46.2': + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.46.2': + '@rollup/rollup-linux-arm-musleabihf@4.53.3': optional: true - '@rollup/rollup-linux-arm64-gnu@4.46.2': + '@rollup/rollup-linux-arm64-gnu@4.53.3': optional: true - '@rollup/rollup-linux-arm64-musl@4.46.2': + '@rollup/rollup-linux-arm64-musl@4.53.3': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.46.2': + '@rollup/rollup-linux-loong64-gnu@4.53.3': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.46.2': + '@rollup/rollup-linux-ppc64-gnu@4.53.3': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.46.2': + '@rollup/rollup-linux-riscv64-gnu@4.53.3': optional: true - '@rollup/rollup-linux-riscv64-musl@4.46.2': + '@rollup/rollup-linux-riscv64-musl@4.53.3': optional: true - '@rollup/rollup-linux-s390x-gnu@4.46.2': + '@rollup/rollup-linux-s390x-gnu@4.53.3': optional: true - '@rollup/rollup-linux-x64-gnu@4.46.2': + '@rollup/rollup-linux-x64-gnu@4.53.3': optional: true - '@rollup/rollup-linux-x64-musl@4.46.2': + '@rollup/rollup-linux-x64-musl@4.53.3': optional: true - '@rollup/rollup-win32-arm64-msvc@4.46.2': + '@rollup/rollup-openharmony-arm64@4.53.3': optional: true - '@rollup/rollup-win32-ia32-msvc@4.46.2': + '@rollup/rollup-win32-arm64-msvc@4.53.3': optional: true - '@rollup/rollup-win32-x64-msvc@4.46.2': + '@rollup/rollup-win32-ia32-msvc@4.53.3': optional: true + '@rollup/rollup-win32-x64-gnu@4.53.3': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.53.3': + optional: true + + '@sentry-internal/browser-utils@10.29.0': + dependencies: + '@sentry/core': 10.29.0 + + '@sentry-internal/feedback@10.29.0': + dependencies: + '@sentry/core': 10.29.0 + + '@sentry-internal/replay-canvas@10.29.0': + dependencies: + '@sentry-internal/replay': 10.29.0 + '@sentry/core': 10.29.0 + + '@sentry-internal/replay@10.29.0': + dependencies: + '@sentry-internal/browser-utils': 10.29.0 + '@sentry/core': 10.29.0 + + '@sentry/browser@10.29.0': + dependencies: + '@sentry-internal/browser-utils': 10.29.0 + '@sentry-internal/feedback': 10.29.0 + '@sentry-internal/replay': 10.29.0 + '@sentry-internal/replay-canvas': 10.29.0 + '@sentry/core': 10.29.0 + + '@sentry/core@10.29.0': {} + + '@sentry/react@10.29.0(react@18.3.1)': + dependencies: + '@sentry/browser': 10.29.0 + '@sentry/core': 10.29.0 + hoist-non-react-statics: 3.3.2 + react: 18.3.1 + + '@tanstack/query-core@5.90.12': {} + + '@tanstack/react-query@5.90.12(react@18.3.1)': + dependencies: + '@tanstack/query-core': 5.90.12 + react: 18.3.1 + '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.28.0 - '@babel/types': 7.28.2 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.28.0 '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.28.2 + '@babel/types': 7.28.5 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.28.0 - '@babel/types': 7.28.2 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 '@types/babel__traverse@7.28.0': dependencies: - '@babel/types': 7.28.2 + '@babel/types': 7.28.5 '@types/estree@1.0.8': {} - '@types/node@24.2.1': - dependencies: - undici-types: 7.10.0 + '@types/js-cookie@3.0.6': {} - '@types/react-dom@19.1.7(@types/react@19.1.10)': + '@types/node@24.10.2': dependencies: - '@types/react': 19.1.10 + undici-types: 7.16.0 - '@types/react@19.1.10': + '@types/react-dom@19.2.3(@types/react@19.2.7)': dependencies: - csstype: 3.1.3 + '@types/react': 19.2.7 - '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2)': + '@types/react@19.2.7': dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.9.2) + csstype: 3.2.3 + + '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.9.3) '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.1)(typescript@5.9.2) - '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.9.2) + '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.9.3) '@typescript-eslint/visitor-keys': 7.18.0 eslint: 8.57.1 graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 - ts-api-utils: 1.4.3(typescript@5.9.2) + ts-api-utils: 1.4.3(typescript@5.9.3) optionalDependencies: - typescript: 5.9.2 + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2)': + '@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3)': dependencies: '@typescript-eslint/scope-manager': 7.18.0 '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.2) + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.3) '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.4.1 + debug: 4.4.3 eslint: 8.57.1 optionalDependencies: - typescript: 5.9.2 + typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -3014,41 +3147,41 @@ snapshots: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/visitor-keys': 7.18.0 - '@typescript-eslint/type-utils@7.18.0(eslint@8.57.1)(typescript@5.9.2)': + '@typescript-eslint/type-utils@7.18.0(eslint@8.57.1)(typescript@5.9.3)': dependencies: - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.2) - '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.9.2) - debug: 4.4.1 + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.3) + '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.9.3) + debug: 4.4.3 eslint: 8.57.1 - ts-api-utils: 1.4.3(typescript@5.9.2) + ts-api-utils: 1.4.3(typescript@5.9.3) optionalDependencies: - typescript: 5.9.2 + typescript: 5.9.3 transitivePeerDependencies: - supports-color '@typescript-eslint/types@7.18.0': {} - '@typescript-eslint/typescript-estree@7.18.0(typescript@5.9.2)': + '@typescript-eslint/typescript-estree@7.18.0(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.4.1 + debug: 4.4.3 globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.7.2 - ts-api-utils: 1.4.3(typescript@5.9.2) + semver: 7.7.3 + ts-api-utils: 1.4.3(typescript@5.9.3) optionalDependencies: - typescript: 5.9.2 + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@7.18.0(eslint@8.57.1)(typescript@5.9.2)': + '@typescript-eslint/utils@7.18.0(eslint@8.57.1)(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@8.57.1) + '@eslint-community/eslint-utils': 4.9.0(eslint@8.57.1) '@typescript-eslint/scope-manager': 7.18.0 '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.2) + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.3) eslint: 8.57.1 transitivePeerDependencies: - supports-color @@ -3068,15 +3201,15 @@ snapshots: '@use-gesture/core': 10.3.0 react: 18.3.1 - '@vitejs/plugin-react@4.7.0(vite@7.1.2(@types/node@24.2.1)(sass@1.90.0))': + '@vitejs/plugin-react@4.7.0(vite@7.2.7(@types/node@24.10.2)(sass@1.95.1))': dependencies: - '@babel/core': 7.28.0 - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.0) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.0) + '@babel/core': 7.28.5 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.5) '@rolldown/pluginutils': 1.0.0-beta.27 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 7.1.2(@types/node@24.2.1)(sass@1.90.0) + vite: 7.2.7(@types/node@24.10.2)(sass@1.95.1) transitivePeerDependencies: - supports-color @@ -3086,10 +3219,11 @@ snapshots: acorn@8.15.0: {} - ahooks@3.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + ahooks@3.9.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 - dayjs: 1.11.13 + '@babel/runtime': 7.28.4 + '@types/js-cookie': 3.0.6 + dayjs: 1.11.19 intersection-observer: 0.12.2 js-cookie: 3.0.5 lodash: 4.17.21 @@ -3117,17 +3251,17 @@ snapshots: antd-mobile-v5-count@1.0.1: {} - antd-mobile@5.40.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + antd-mobile@5.41.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@floating-ui/dom': 1.7.3 + '@floating-ui/dom': 1.7.4 '@rc-component/mini-decimal': 1.1.0 '@react-spring/web': 9.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@use-gesture/react': 10.3.0(react@18.3.1) - ahooks: 3.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + ahooks: 3.9.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) antd-mobile-icons: 0.3.0 antd-mobile-v5-count: 1.0.1 classnames: 2.5.1 - dayjs: 1.11.13 + dayjs: 1.11.19 deepmerge: 4.3.1 nano-memoize: 3.0.16 rc-field-form: 1.44.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -3140,9 +3274,9 @@ snapshots: runes2: 1.1.4 staged-components: 1.1.3(react@18.3.1) tslib: 2.8.1 - use-sync-external-store: 1.5.0(react@18.3.1) + use-sync-external-store: 1.6.0(react@18.3.1) - antd@5.27.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + antd@5.29.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@ant-design/colors': 7.2.1 '@ant-design/cssinjs': 1.24.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -3150,22 +3284,22 @@ snapshots: '@ant-design/fast-color': 2.0.6 '@ant-design/icons': 5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@ant-design/react-slick': 1.1.2(react@18.3.1) - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 '@rc-component/color-picker': 2.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@rc-component/mutate-observer': 1.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@rc-component/qrcode': 1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/qrcode': 1.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@rc-component/tour': 1.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@rc-component/trigger': 2.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) classnames: 2.5.1 copy-to-clipboard: 3.3.3 - dayjs: 1.11.13 + dayjs: 1.11.19 rc-cascader: 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-checkbox: 3.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-collapse: 3.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-dialog: 9.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-drawer: 7.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-dropdown: 4.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-field-form: 2.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-field-form: 2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-image: 7.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-input: 1.8.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-input-number: 9.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -3174,22 +3308,22 @@ snapshots: rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-notification: 5.6.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-pagination: 5.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-picker: 4.11.3(dayjs@1.11.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-picker: 4.11.3(dayjs@1.11.19)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-progress: 4.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-rate: 2.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-segmented: 2.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-select: 14.16.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-slider: 11.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-slider: 11.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-steps: 6.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-switch: 4.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-table: 7.51.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-table: 7.54.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-tabs: 15.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-textarea: 1.10.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-tooltip: 6.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-tree: 5.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-tree-select: 5.27.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-upload: 4.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-upload: 4.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -3271,16 +3405,18 @@ snapshots: dependencies: possible-typed-array-names: 1.1.0 - axios@1.11.0: + axios@1.13.2: dependencies: follow-redirects: 1.15.11 - form-data: 4.0.4 + form-data: 4.0.5 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug balanced-match@1.0.2: {} + baseline-browser-mapping@2.9.5: {} + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -3294,12 +3430,13 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.25.2: + browserslist@4.28.1: dependencies: - caniuse-lite: 1.0.30001734 - electron-to-chromium: 1.5.200 - node-releases: 2.0.19 - update-browserslist-db: 1.1.3(browserslist@4.25.2) + baseline-browser-mapping: 2.9.5 + caniuse-lite: 1.0.30001760 + electron-to-chromium: 1.5.267 + node-releases: 2.0.27 + update-browserslist-db: 1.2.2(browserslist@4.28.1) call-bind-apply-helpers@1.0.2: dependencies: @@ -3320,7 +3457,7 @@ snapshots: callsites@3.1.0: {} - caniuse-lite@1.0.30001734: {} + caniuse-lite@1.0.30001760: {} chalk@4.1.2: dependencies: @@ -3349,13 +3486,13 @@ snapshots: convert-source-map@2.0.0: {} - copy-text-to-clipboard@3.2.0: {} + copy-text-to-clipboard@3.2.2: {} copy-to-clipboard@3.3.3: dependencies: toggle-selection: 1.0.6 - core-js@3.45.0: {} + core-js@3.47.0: {} cross-spawn@7.0.6: dependencies: @@ -3363,7 +3500,7 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - csstype@3.1.3: {} + csstype@3.2.3: {} data-view-buffer@1.0.2: dependencies: @@ -3383,9 +3520,9 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 - dayjs@1.11.13: {} + dayjs@1.11.19: {} - debug@4.4.1: + debug@4.4.3: dependencies: ms: 2.1.3 @@ -3410,7 +3547,7 @@ snapshots: detect-libc@1.0.3: optional: true - dexie@4.2.0: {} + dexie@4.2.1: {} dir-glob@3.0.1: dependencies: @@ -3430,7 +3567,7 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - echarts-for-react@3.0.2(echarts@5.6.0)(react@18.3.1): + echarts-for-react@3.0.5(echarts@5.6.0)(react@18.3.1): dependencies: echarts: 5.6.0 fast-deep-equal: 3.1.3 @@ -3442,7 +3579,7 @@ snapshots: tslib: 2.3.0 zrender: 5.6.1 - electron-to-chromium@1.5.200: {} + electron-to-chromium@1.5.267: {} es-abstract@1.24.0: dependencies: @@ -3545,34 +3682,34 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 - esbuild@0.25.8: + esbuild@0.25.12: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.8 - '@esbuild/android-arm': 0.25.8 - '@esbuild/android-arm64': 0.25.8 - '@esbuild/android-x64': 0.25.8 - '@esbuild/darwin-arm64': 0.25.8 - '@esbuild/darwin-x64': 0.25.8 - '@esbuild/freebsd-arm64': 0.25.8 - '@esbuild/freebsd-x64': 0.25.8 - '@esbuild/linux-arm': 0.25.8 - '@esbuild/linux-arm64': 0.25.8 - '@esbuild/linux-ia32': 0.25.8 - '@esbuild/linux-loong64': 0.25.8 - '@esbuild/linux-mips64el': 0.25.8 - '@esbuild/linux-ppc64': 0.25.8 - '@esbuild/linux-riscv64': 0.25.8 - '@esbuild/linux-s390x': 0.25.8 - '@esbuild/linux-x64': 0.25.8 - '@esbuild/netbsd-arm64': 0.25.8 - '@esbuild/netbsd-x64': 0.25.8 - '@esbuild/openbsd-arm64': 0.25.8 - '@esbuild/openbsd-x64': 0.25.8 - '@esbuild/openharmony-arm64': 0.25.8 - '@esbuild/sunos-x64': 0.25.8 - '@esbuild/win32-arm64': 0.25.8 - '@esbuild/win32-ia32': 0.25.8 - '@esbuild/win32-x64': 0.25.8 + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 escalade@3.2.0: {} @@ -3582,10 +3719,10 @@ snapshots: dependencies: eslint: 8.57.1 - eslint-plugin-prettier@5.5.4(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.6.2): + eslint-plugin-prettier@5.5.4(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.7.4): dependencies: eslint: 8.57.1 - prettier: 3.6.2 + prettier: 3.7.4 prettier-linter-helpers: 1.0.0 synckit: 0.11.11 optionalDependencies: @@ -3626,8 +3763,8 @@ snapshots: eslint@8.57.1: dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@8.57.1) - '@eslint-community/regexpp': 4.12.1 + '@eslint-community/eslint-utils': 4.9.0(eslint@8.57.1) + '@eslint-community/regexpp': 4.12.2 '@eslint/eslintrc': 2.1.4 '@eslint/js': 8.57.1 '@humanwhocodes/config-array': 0.13.0 @@ -3637,7 +3774,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.1 + debug: 4.4.3 doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -3655,7 +3792,7 @@ snapshots: imurmurhash: 0.1.4 is-glob: 4.0.3 is-path-inside: 3.0.3 - js-yaml: 4.1.0 + js-yaml: 4.1.1 json-stable-stringify-without-jsonify: 1.0.1 levn: 0.4.1 lodash.merge: 4.6.2 @@ -3705,7 +3842,7 @@ snapshots: dependencies: reusify: 1.1.0 - fdir@6.4.6(picomatch@4.0.3): + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -3736,7 +3873,7 @@ snapshots: dependencies: is-callable: 1.2.7 - form-data@4.0.4: + form-data@4.0.5: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 @@ -3762,6 +3899,8 @@ snapshots: functions-have-names@1.2.3: {} + generator-function@2.0.1: {} + gensync@1.0.0-beta.2: {} get-intrinsic@1.3.0: @@ -3849,9 +3988,13 @@ snapshots: dependencies: function-bind: 1.1.2 + hoist-non-react-statics@3.3.2: + dependencies: + react-is: 16.13.1 + ignore@5.3.2: {} - immutable@5.1.3: {} + immutable@5.1.4: {} import-fresh@3.3.1: dependencies: @@ -3921,9 +4064,10 @@ snapshots: dependencies: call-bound: 1.0.4 - is-generator-function@1.1.0: + is-generator-function@1.1.2: dependencies: call-bound: 1.0.4 + generator-function: 2.0.1 get-proto: 1.0.1 has-tostringtag: 1.0.2 safe-regex-test: 1.1.0 @@ -4001,7 +4145,7 @@ snapshots: js-tokens@4.0.0: {} - js-yaml@4.1.0: + js-yaml@4.1.1: dependencies: argparse: 2.0.1 @@ -4089,7 +4233,7 @@ snapshots: node-addon-api@7.1.1: optional: true - node-releases@2.0.19: {} + node-releases@2.0.27: {} object-assign@4.1.1: {} @@ -4192,7 +4336,7 @@ snapshots: dependencies: fast-diff: 1.3.0 - prettier@3.6.2: {} + prettier@3.7.4: {} prop-types@15.8.1: dependencies: @@ -4208,7 +4352,7 @@ snapshots: rc-cascader@3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 classnames: 2.5.1 rc-select: 14.16.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-tree: 5.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -4218,7 +4362,7 @@ snapshots: rc-checkbox@3.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 classnames: 2.5.1 rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 @@ -4226,7 +4370,7 @@ snapshots: rc-collapse@3.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 classnames: 2.5.1 rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -4235,7 +4379,7 @@ snapshots: rc-dialog@9.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) classnames: 2.5.1 rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -4245,7 +4389,7 @@ snapshots: rc-drawer@7.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) classnames: 2.5.1 rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -4255,7 +4399,7 @@ snapshots: rc-dropdown@4.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 '@rc-component/trigger': 2.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) classnames: 2.5.1 rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -4264,15 +4408,15 @@ snapshots: rc-field-form@1.44.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 async-validator: 4.2.5 rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-field-form@2.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-field-form@2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 '@rc-component/async-validator': 5.0.4 rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 @@ -4280,7 +4424,7 @@ snapshots: rc-image@7.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) classnames: 2.5.1 rc-dialog: 9.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -4291,7 +4435,7 @@ snapshots: rc-input-number@9.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 '@rc-component/mini-decimal': 1.1.0 classnames: 2.5.1 rc-input: 1.8.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -4301,7 +4445,7 @@ snapshots: rc-input@1.8.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 classnames: 2.5.1 rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 @@ -4309,7 +4453,7 @@ snapshots: rc-mentions@2.20.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 '@rc-component/trigger': 2.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) classnames: 2.5.1 rc-input: 1.8.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -4321,18 +4465,18 @@ snapshots: rc-menu@9.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 '@rc-component/trigger': 2.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) classnames: 2.5.1 rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-overflow: 1.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-overflow: 1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) rc-motion@2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 classnames: 2.5.1 rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 @@ -4340,16 +4484,16 @@ snapshots: rc-notification@5.6.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 classnames: 2.5.1 rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-overflow@1.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-overflow@1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 classnames: 2.5.1 rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -4358,28 +4502,28 @@ snapshots: rc-pagination@5.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 classnames: 2.5.1 rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-picker@4.11.3(dayjs@1.11.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-picker@4.11.3(dayjs@1.11.19)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 '@rc-component/trigger': 2.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) classnames: 2.5.1 - rc-overflow: 1.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-overflow: 1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - dayjs: 1.11.13 + dayjs: 1.11.19 rc-progress@4.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 classnames: 2.5.1 rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 @@ -4387,7 +4531,7 @@ snapshots: rc-rate@2.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 classnames: 2.5.1 rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 @@ -4395,7 +4539,7 @@ snapshots: rc-resize-observer@1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 classnames: 2.5.1 rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 @@ -4404,7 +4548,7 @@ snapshots: rc-segmented@2.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 classnames: 2.5.1 rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -4413,7 +4557,7 @@ snapshots: rc-segmented@2.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 classnames: 2.5.1 rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -4422,19 +4566,19 @@ snapshots: rc-select@14.16.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 '@rc-component/trigger': 2.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) classnames: 2.5.1 rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-overflow: 1.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-overflow: 1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-virtual-list: 3.19.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-virtual-list: 3.19.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-slider@11.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-slider@11.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 classnames: 2.5.1 rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 @@ -4442,7 +4586,7 @@ snapshots: rc-steps@6.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 classnames: 2.5.1 rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 @@ -4450,26 +4594,26 @@ snapshots: rc-switch@4.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 classnames: 2.5.1 rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-table@7.51.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-table@7.54.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 '@rc-component/context': 1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) classnames: 2.5.1 rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-virtual-list: 3.19.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-virtual-list: 3.19.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) rc-tabs@15.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 classnames: 2.5.1 rc-dropdown: 4.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-menu: 9.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -4481,7 +4625,7 @@ snapshots: rc-textarea@1.10.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 classnames: 2.5.1 rc-input: 1.8.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -4491,7 +4635,7 @@ snapshots: rc-tooltip@6.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 '@rc-component/trigger': 2.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) classnames: 2.5.1 rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -4500,7 +4644,7 @@ snapshots: rc-tree-select@5.27.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 classnames: 2.5.1 rc-select: 14.16.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-tree: 5.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -4510,17 +4654,17 @@ snapshots: rc-tree@5.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 classnames: 2.5.1 rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - rc-virtual-list: 3.19.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-virtual-list: 3.19.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rc-upload@4.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-upload@4.11.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 classnames: 2.5.1 rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 @@ -4528,14 +4672,14 @@ snapshots: rc-util@5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) react-is: 18.3.1 - rc-virtual-list@3.19.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + rc-virtual-list@3.19.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 classnames: 2.5.1 rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -4556,21 +4700,21 @@ snapshots: react-refresh@0.17.0: {} - react-router-dom@6.30.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-router-dom@6.30.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@remix-run/router': 1.23.0 + '@remix-run/router': 1.23.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-router: 6.30.1(react@18.3.1) + react-router: 6.30.2(react@18.3.1) - react-router@6.30.1(react@18.3.1): + react-router@6.30.2(react@18.3.1): dependencies: - '@remix-run/router': 1.23.0 + '@remix-run/router': 1.23.1 react: 18.3.1 react-window@1.8.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 memoize-one: 5.2.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -4617,30 +4761,32 @@ snapshots: dependencies: glob: 7.2.3 - rollup@4.46.2: + rollup@4.53.3: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.46.2 - '@rollup/rollup-android-arm64': 4.46.2 - '@rollup/rollup-darwin-arm64': 4.46.2 - '@rollup/rollup-darwin-x64': 4.46.2 - '@rollup/rollup-freebsd-arm64': 4.46.2 - '@rollup/rollup-freebsd-x64': 4.46.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.46.2 - '@rollup/rollup-linux-arm-musleabihf': 4.46.2 - '@rollup/rollup-linux-arm64-gnu': 4.46.2 - '@rollup/rollup-linux-arm64-musl': 4.46.2 - '@rollup/rollup-linux-loongarch64-gnu': 4.46.2 - '@rollup/rollup-linux-ppc64-gnu': 4.46.2 - '@rollup/rollup-linux-riscv64-gnu': 4.46.2 - '@rollup/rollup-linux-riscv64-musl': 4.46.2 - '@rollup/rollup-linux-s390x-gnu': 4.46.2 - '@rollup/rollup-linux-x64-gnu': 4.46.2 - '@rollup/rollup-linux-x64-musl': 4.46.2 - '@rollup/rollup-win32-arm64-msvc': 4.46.2 - '@rollup/rollup-win32-ia32-msvc': 4.46.2 - '@rollup/rollup-win32-x64-msvc': 4.46.2 + '@rollup/rollup-android-arm-eabi': 4.53.3 + '@rollup/rollup-android-arm64': 4.53.3 + '@rollup/rollup-darwin-arm64': 4.53.3 + '@rollup/rollup-darwin-x64': 4.53.3 + '@rollup/rollup-freebsd-arm64': 4.53.3 + '@rollup/rollup-freebsd-x64': 4.53.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.53.3 + '@rollup/rollup-linux-arm-musleabihf': 4.53.3 + '@rollup/rollup-linux-arm64-gnu': 4.53.3 + '@rollup/rollup-linux-arm64-musl': 4.53.3 + '@rollup/rollup-linux-loong64-gnu': 4.53.3 + '@rollup/rollup-linux-ppc64-gnu': 4.53.3 + '@rollup/rollup-linux-riscv64-gnu': 4.53.3 + '@rollup/rollup-linux-riscv64-musl': 4.53.3 + '@rollup/rollup-linux-s390x-gnu': 4.53.3 + '@rollup/rollup-linux-x64-gnu': 4.53.3 + '@rollup/rollup-linux-x64-musl': 4.53.3 + '@rollup/rollup-openharmony-arm64': 4.53.3 + '@rollup/rollup-win32-arm64-msvc': 4.53.3 + '@rollup/rollup-win32-ia32-msvc': 4.53.3 + '@rollup/rollup-win32-x64-gnu': 4.53.3 + '@rollup/rollup-win32-x64-msvc': 4.53.3 fsevents: 2.3.3 run-parallel@1.2.0: @@ -4668,10 +4814,10 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 - sass@1.90.0: + sass@1.95.1: dependencies: chokidar: 4.0.3 - immutable: 5.1.3 + immutable: 5.1.4 source-map-js: 1.2.1 optionalDependencies: '@parcel/watcher': 2.5.1 @@ -4688,7 +4834,7 @@ snapshots: semver@6.3.1: {} - semver@7.7.2: {} + semver@7.7.3: {} set-function-length@1.2.2: dependencies: @@ -4829,9 +4975,9 @@ snapshots: throttle-debounce@5.0.2: {} - tinyglobby@0.2.14: + tinyglobby@0.2.15: dependencies: - fdir: 6.4.6(picomatch@4.0.3) + fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 to-regex-range@5.0.1: @@ -4840,9 +4986,9 @@ snapshots: toggle-selection@1.0.6: {} - ts-api-utils@1.4.3(typescript@5.9.2): + ts-api-utils@1.4.3(typescript@5.9.3): dependencies: - typescript: 5.9.2 + typescript: 5.9.3 tslib@2.3.0: {} @@ -4887,7 +5033,7 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript@5.9.2: {} + typescript@5.9.3: {} unbox-primitive@1.1.0: dependencies: @@ -4896,11 +5042,11 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 - undici-types@7.10.0: {} + undici-types@7.16.0: {} - update-browserslist-db@1.1.3(browserslist@4.25.2): + update-browserslist-db@1.2.2(browserslist@4.28.1): dependencies: - browserslist: 4.25.2 + browserslist: 4.28.1 escalade: 3.2.0 picocolors: 1.1.1 @@ -4908,29 +5054,29 @@ snapshots: dependencies: punycode: 2.3.1 - use-sync-external-store@1.5.0(react@18.3.1): + use-sync-external-store@1.6.0(react@18.3.1): dependencies: react: 18.3.1 vconsole@3.15.1: dependencies: - '@babel/runtime': 7.28.2 - copy-text-to-clipboard: 3.2.0 - core-js: 3.45.0 + '@babel/runtime': 7.28.4 + copy-text-to-clipboard: 3.2.2 + core-js: 3.47.0 mutation-observer: 1.0.3 - vite@7.1.2(@types/node@24.2.1)(sass@1.90.0): + vite@7.2.7(@types/node@24.10.2)(sass@1.95.1): dependencies: - esbuild: 0.25.8 - fdir: 6.4.6(picomatch@4.0.3) + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.46.2 - tinyglobby: 0.2.14 + rollup: 4.53.3 + tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 24.2.1 + '@types/node': 24.10.2 fsevents: 2.3.3 - sass: 1.90.0 + sass: 1.95.1 which-boxed-primitive@1.1.1: dependencies: @@ -4948,7 +5094,7 @@ snapshots: is-async-function: 2.1.1 is-date-object: 1.1.0 is-finalizationregistry: 1.1.1 - is-generator-function: 1.1.0 + is-generator-function: 1.1.2 is-regex: 1.2.1 is-weakref: 1.1.1 isarray: 2.0.5 @@ -4991,8 +5137,8 @@ snapshots: dependencies: tslib: 2.3.0 - zustand@5.0.7(@types/react@19.1.10)(react@18.3.1)(use-sync-external-store@1.5.0(react@18.3.1)): + zustand@5.0.9(@types/react@19.2.7)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1)): optionalDependencies: - '@types/react': 19.1.10 + '@types/react': 19.2.7 react: 18.3.1 - use-sync-external-store: 1.5.0(react@18.3.1) + use-sync-external-store: 1.6.0(react@18.3.1) diff --git a/Touchkebao/src/App.tsx b/Touchkebao/src/App.tsx index 01bf3208..40de9eaa 100644 --- a/Touchkebao/src/App.tsx +++ b/Touchkebao/src/App.tsx @@ -1,13 +1,22 @@ import React from "react"; +import * as Sentry from "@sentry/react"; import AppRouter from "@/router"; import UpdateNotification from "@/components/UpdateNotification"; +const ErrorFallback = () => ( +
+

出现了一些问题

+

我们已经记录了这个问题,正在修复中...

+ +
+); + function App() { return ( - <> + - + ); } diff --git a/Touchkebao/src/hooks/weChat/useChatMessages.ts b/Touchkebao/src/hooks/weChat/useChatMessages.ts new file mode 100644 index 00000000..9b164ce3 --- /dev/null +++ b/Touchkebao/src/hooks/weChat/useChatMessages.ts @@ -0,0 +1,95 @@ +import { useInfiniteQuery } from "@tanstack/react-query"; +import { getChatMessages, getChatroomMessages } from "@/pages/pc/ckbox/api"; +import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; +import { captureError, addPerformanceBreadcrumb } from "@/utils/sentry"; + +const DEFAULT_MESSAGE_PAGE_SIZE = 20; + +/** + * 消息列表 Hook + * 使用 TanStack Query 管理消息数据,自动缓存和分页 + * 使用 Sentry 监控请求性能和错误 + */ +export const useChatMessages = (contact: ContractData | weChatGroup | null) => { + return useInfiniteQuery({ + queryKey: ["chatMessages", contact?.id, contact?.wechatAccountId], + initialPageParam: 1, // TanStack Query v5 必需 + queryFn: async ({ pageParam }) => { + if (!contact) { + throw new Error("联系人信息缺失"); + } + + const startTime = performance.now(); + const params: any = { + wechatAccountId: contact.wechatAccountId, + page: pageParam, + limit: DEFAULT_MESSAGE_PAGE_SIZE, + }; + + const isGroup = "chatroomId" in contact && Boolean(contact.chatroomId); + + if (isGroup) { + params.wechatChatroomId = contact.id; + } else { + params.wechatFriendId = contact.id; + } + + try { + const response = isGroup + ? await getChatroomMessages(params) + : await getChatMessages(params); + + const duration = performance.now() - startTime; + + // ✅ 使用 Sentry 记录请求性能 + addPerformanceBreadcrumb("获取消息列表", { + duration, + contactId: contact.id, + page: pageParam, + messageCount: (response as any)?.list?.length || 0, + isGroup, + }); + + // 如果请求时间超过 1 秒,记录警告 + if (duration > 1000) { + addPerformanceBreadcrumb("慢请求警告", { + duration, + contactId: contact.id, + threshold: 1000, + }); + } + + return response; + } catch (error) { + // ✅ 使用 Sentry 捕获错误 + captureError(error as Error, { + tags: { + action: "getChatMessages", + isGroup: String(isGroup), + }, + extra: { + contactId: contact.id, + page: pageParam, + params, + }, + }); + throw error; + } + }, + getNextPageParam: (lastPage, allPages) => { + // 判断是否还有更多数据 + const lastPageData = lastPage as any; + const hasMore = + lastPageData?.hasNext || + lastPageData?.hasNextPage || + (lastPageData?.list?.length || 0) >= DEFAULT_MESSAGE_PAGE_SIZE; + return hasMore ? allPages.length + 1 : undefined; + }, + enabled: !!contact, // 只有联系人存在时才请求 + staleTime: 5 * 60 * 1000, // 5 分钟缓存 + gcTime: 10 * 60 * 1000, // 10 分钟缓存(v5 使用 gcTime) + // ✅ 使用 Sentry 监控查询状态变化 + retry: 1, + refetchOnWindowFocus: true, + }); +}; diff --git a/Touchkebao/src/hooks/weChat/useMessageGrouping.ts b/Touchkebao/src/hooks/weChat/useMessageGrouping.ts new file mode 100644 index 00000000..210a37fd --- /dev/null +++ b/Touchkebao/src/hooks/weChat/useMessageGrouping.ts @@ -0,0 +1,31 @@ +import { useMemo } from "react"; +import { ChatRecord } from "@/pages/pc/ckbox/data"; +import { formatWechatTime } from "@/utils/common"; + +export interface MessageGroup { + time: string; + messages: ChatRecord[]; +} + +/** + * 消息分组 Hook + * 使用 useMemo 缓存分组结果,减少重复计算 + */ +export const useMessageGrouping = ( + messages: ChatRecord[] | null | undefined, +): MessageGroup[] => { + return useMemo(() => { + const safeMessages = Array.isArray(messages) + ? messages + : Array.isArray((messages as any)?.list) + ? ((messages as any).list as ChatRecord[]) + : []; + + return safeMessages + .filter(msg => msg !== null && msg !== undefined) // 过滤掉null和undefined的消息 + .map(msg => ({ + time: formatWechatTime(String(msg?.wechatTime)), + messages: [msg], + })); + }, [messages]); +}; diff --git a/Touchkebao/src/hooks/weChat/useMessageParser.tsx b/Touchkebao/src/hooks/weChat/useMessageParser.tsx new file mode 100644 index 00000000..8a07f1b0 --- /dev/null +++ b/Touchkebao/src/hooks/weChat/useMessageParser.tsx @@ -0,0 +1,447 @@ +import React, { useCallback } from "react"; +import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; +import { getEmojiPath } from "@/components/EmojiSeclection/wechatEmoji"; +import AudioMessage from "@/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/AudioMessage/AudioMessage"; +import SmallProgramMessage from "@/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/SmallProgramMessage"; +import VideoMessage from "@/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/VideoMessage"; +import LocationMessage from "@/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/LocationMessage"; +import SystemRecommendRemarkMessage from "@/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/SystemRecommendRemarkMessage/index"; +import RedPacketMessage from "@/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/RedPacketMessage"; +import TransferMessage from "@/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransferMessage"; +import styles from "@/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/com.module.scss"; + +const IMAGE_EXT_REGEX = /\.(jpg|jpeg|png|gif|webp|bmp|svg)$/i; +const FILE_EXT_REGEX = /\.(pdf|doc|docx|xls|xlsx|ppt|pptx|txt|zip|rar|7z)$/i; + +const openInNewTab = (url: string) => window.open(url, "_blank"); + +const handleImageError = ( + event: React.SyntheticEvent, + fallbackText: string, +) => { + const target = event.target as HTMLImageElement; + const parent = target.parentElement; + if (parent) { + parent.innerHTML = `
${fallbackText}
`; + } +}; + +interface ImageContentOptions { + src: string; + alt: string; + fallbackText: string; + style?: React.CSSProperties; + wrapperClassName?: string; + withBubble?: boolean; + onClick?: () => void; +} + +const renderImageContent = ({ + src, + alt, + fallbackText, + style = { + maxWidth: "200px", + maxHeight: "200px", + borderRadius: "8px", + }, + wrapperClassName = styles.imageMessage, + withBubble = false, + onClick, +}: ImageContentOptions) => { + const imageNode = ( +
+ {alt} openInNewTab(src))} + onError={event => handleImageError(event, fallbackText)} + /> +
+ ); + + if (withBubble) { + return
{imageNode}
; + } + + return imageNode; +}; + +const renderEmojiContent = (src: string) => + renderImageContent({ + src, + alt: "表情包", + fallbackText: "[表情包加载失败]", + style: { + maxWidth: "120px", + maxHeight: "120px", + }, + wrapperClassName: styles.emojiMessage, + }); + +const renderFileContent = (url: string) => { + const fileName = url.split("/").pop()?.split("?")[0] || "文件"; + const displayName = + fileName.length > 20 ? `${fileName.substring(0, 20)}...` : fileName; + + return ( +
+
+
📄
+
+
{displayName}
+
openInNewTab(url)}> + 点击查看 +
+
+
+
+ ); +}; + +const isHttpUrl = (value: string) => /^https?:\/\//i.test(value); +const isHttpImageUrl = (value: string) => + isHttpUrl(value) && IMAGE_EXT_REGEX.test(value); +const isFileUrl = (value: string) => + isHttpUrl(value) && FILE_EXT_REGEX.test(value); + +const isLegacyEmojiContent = (content: string) => + IMAGE_EXT_REGEX.test(content) || + content.includes("emoji") || + content.includes("sticker"); + +const tryParseContentJson = (content: string): Record | null => { + try { + return JSON.parse(content); + } catch (error) { + return null; + } +}; + +/** + * 消息解析 Hook + * 提取消息解析逻辑,使用 useCallback 优化性能 + */ +export const useMessageParser = (contract: ContractData | weChatGroup) => { + // 判断是否为表情包URL的工具函数 + const isEmojiUrl = useCallback((content: string): boolean => { + return ( + content.includes("ac-weremote-s2.oss-cn-shenzhen.aliyuncs.com") || + /\.(gif|webp|png|jpg|jpeg)$/i.test(content) || + content.includes("emoji") || + content.includes("sticker") || + content.includes("expression") + ); + }, []); + + // 解析表情包文字格式[表情名称]并替换为img标签 + const parseEmojiText = useCallback((text: string): React.ReactNode[] => { + const emojiRegex = /\[([^\]]+)\]/g; + const parts: React.ReactNode[] = []; + let lastIndex = 0; + let match; + + while ((match = emojiRegex.exec(text)) !== null) { + // 添加表情前的文字 + if (match.index > lastIndex) { + parts.push(text.slice(lastIndex, match.index)); + } + + // 获取表情名称并查找对应路径 + const emojiName = match[1]; + const emojiPath = getEmojiPath(emojiName as any); + + if (emojiPath) { + // 如果找到表情,添加img标签 + parts.push( + {emojiName}, + ); + } else { + // 如果没找到表情,保持原文字 + parts.push(match[0]); + } + + lastIndex = emojiRegex.lastIndex; + } + + // 添加剩余的文字 + if (lastIndex < text.length) { + parts.push(text.slice(lastIndex)); + } + + return parts; + }, []); + + // 渲染未知内容 + const renderUnknownContent = useCallback( + ( + rawContent: string, + trimmedContent: string, + msg?: ChatRecord, + contractParam?: ContractData | weChatGroup, + ) => { + if (isLegacyEmojiContent(trimmedContent)) { + return renderEmojiContent(rawContent); + } + + const jsonData = tryParseContentJson(trimmedContent); + + if (jsonData && typeof jsonData === "object") { + // 判断是否为红包消息 + if ( + jsonData.nativeurl && + typeof jsonData.nativeurl === "string" && + jsonData.nativeurl.includes( + "wxpay://c2cbizmessagehandler/hongbao/receivehongbao", + ) + ) { + return ( + + ); + } + + // 判断是否为转账消息 + if ( + jsonData.title === "微信转账" || + (jsonData.transferid && jsonData.feedesc) + ) { + return ( + + ); + } + + if (jsonData.type === "file" && msg && contractParam) { + return ( + + ); + } + + if (jsonData.type === "link" && jsonData.title && jsonData.url) { + const { title, desc, thumbPath, url } = jsonData; + + return ( +
+
openInNewTab(url)} + > + {thumbPath && ( + 链接缩略图 { + const target = event.target as HTMLImageElement; + target.style.display = "none"; + }} + /> + )} +
+
{title}
+ {desc &&
{desc}
} +
+
+
链接
+
+ ); + } + + if ( + jsonData.previewImage && + (jsonData.tencentUrl || jsonData.videoUrl) + ) { + const previewImageUrl = String(jsonData.previewImage).replace( + /[`"']/g, + "", + ); + return ( +
+
+ 视频预览 { + const videoUrl = jsonData.videoUrl || jsonData.tencentUrl; + if (videoUrl) { + openInNewTab(videoUrl); + } + }} + onError={event => { + const target = event.target as HTMLImageElement; + const parent = target.parentElement?.parentElement; + if (parent) { + parent.innerHTML = `
[视频预览加载失败]
`; + } + }} + /> +
+ + + +
+
+
+ ); + } + } + + if (isHttpImageUrl(trimmedContent)) { + return renderImageContent({ + src: rawContent, + alt: "图片消息", + fallbackText: "[图片加载失败]", + }); + } + + if (isFileUrl(trimmedContent)) { + return renderFileContent(trimmedContent); + } + + return ( +
{parseEmojiText(rawContent)}
+ ); + }, + [parseEmojiText], + ); + + // 解析消息内容,根据msgType判断消息类型并返回对应的渲染内容 + const parseMessageContent = useCallback( + ( + content: string | null | undefined, + msg: ChatRecord, + msgType?: number, + ): React.ReactNode => { + // 处理null或undefined的内容 + if (content === null || content === undefined) { + return
消息内容不可用
; + } + + // 统一的错误消息渲染函数 + const renderErrorMessage = (fallbackText: string) => ( +
{fallbackText}
+ ); + + const isStringValue = typeof content === "string"; + const rawContent = isStringValue ? content : ""; + const trimmedContent = rawContent.trim(); + + switch (msgType) { + case 1: // 文本消息 + return ( +
+
+ {parseEmojiText(rawContent)} +
+
+ ); + + case 3: // 图片消息 + if (!isStringValue || !trimmedContent) { + return renderErrorMessage("[图片消息 - 无效链接]"); + } + return renderImageContent({ + src: rawContent, + alt: "图片消息", + fallbackText: "[图片加载失败]", + withBubble: true, + }); + + case 34: // 语音消息 + if (!isStringValue || !trimmedContent) { + return renderErrorMessage("[语音消息 - 无效内容]"); + } + + return ; + + case 43: // 视频消息 + return ( + + ); + + case 47: // 动图表情包(gif、其他表情包) + if (!isStringValue || !trimmedContent) { + return renderErrorMessage("[表情包 - 无效链接]"); + } + + if (isEmojiUrl(trimmedContent)) { + return renderEmojiContent(rawContent); + } + return renderErrorMessage("[表情包]"); + + case 48: // 定位消息 + return ; + + case 49: // 小程序/文章/其他:图文、文件 + return ( + + ); + + case 10002: // 系统推荐备注消息 + return ( + + ); + + default: { + if (!isStringValue || !trimmedContent) { + return renderErrorMessage( + `[未知消息类型${msgType ? ` - ${msgType}` : ""}]`, + ); + } + + return renderUnknownContent( + rawContent, + trimmedContent, + msg, + contract, + ); + } + } + }, + [contract, parseEmojiText, isEmojiUrl, renderUnknownContent], + ); + + return { + parseMessageContent, + parseEmojiText, + isEmojiUrl, + }; +}; diff --git a/Touchkebao/src/hooks/weChat/useWeChatSelectors.ts b/Touchkebao/src/hooks/weChat/useWeChatSelectors.ts new file mode 100644 index 00000000..f96be9fa --- /dev/null +++ b/Touchkebao/src/hooks/weChat/useWeChatSelectors.ts @@ -0,0 +1,129 @@ +import { useWeChatStore } from "@/store/module/weChat/weChat"; +import { useShallow } from "zustand/react/shallow"; +import { useMemo } from "react"; +import type { WeChatState } from "@/store/module/weChat/weChat.data"; + +/** + * 合并多个 selector,减少重渲染 + * 使用 useShallow 进行 shallow 比较,只有对象属性变化时才触发更新 + * 使用 useMemo 稳定 selector 函数引用 + */ +export const useWeChatSelectors = () => { + const selector = useMemo( + () => (state: WeChatState) => ({ + // 消息相关 + currentMessages: state.currentMessages, + currentMessagesHasMore: state.currentMessagesHasMore, + messagesLoading: state.messagesLoading, + isLoadingData: state.isLoadingData, + + // 联系人相关 + currentContract: state.currentContract, + + // UI 状态 + showCheckbox: state.showCheckbox, + EnterModule: state.EnterModule, + showChatRecordModel: state.showChatRecordModel, + + // AI 相关 + isLoadingAiChat: state.isLoadingAiChat, + quoteMessageContent: state.quoteMessageContent, + aiQuoteMessageContent: state.aiQuoteMessageContent, + + // 选中记录 + selectedChatRecords: state.selectedChatRecords, + }), + [], + ); + return useWeChatStore(useShallow(selector)); +}; + +/** + * 消息相关的 selector + * 使用 useShallow 进行 shallow 比较,避免 getSnapshot 警告 + * 使用 useMemo 稳定 selector 函数引用 + */ +export const useMessageSelectors = (): Pick< + WeChatState, + | "currentMessages" + | "currentMessagesHasMore" + | "messagesLoading" + | "isLoadingData" +> => { + const selector = useMemo( + () => (state: WeChatState) => ({ + currentMessages: state.currentMessages, + currentMessagesHasMore: state.currentMessagesHasMore, + messagesLoading: state.messagesLoading, + isLoadingData: state.isLoadingData, + }), + [], + ); + return useWeChatStore(useShallow(selector)); +}; + +/** + * UI 状态相关的 selector + * 使用 useShallow 进行 shallow 比较,避免 getSnapshot 警告 + * 使用 useMemo 稳定 selector 函数引用 + */ +export const useUIStateSelectors = (): Pick< + WeChatState, + "showCheckbox" | "EnterModule" | "showChatRecordModel" +> => { + const selector = useMemo( + () => (state: WeChatState) => ({ + showCheckbox: state.showCheckbox, + EnterModule: state.EnterModule, + showChatRecordModel: state.showChatRecordModel, + }), + [], + ); + return useWeChatStore(useShallow(selector)); +}; + +/** + * AI 相关的 selector + * 使用 useShallow 进行 shallow 比较,避免 getSnapshot 警告 + * 使用 useMemo 稳定 selector 函数引用 + */ +export const useAISelectors = (): Pick< + WeChatState, + "isLoadingAiChat" | "quoteMessageContent" | "aiQuoteMessageContent" +> => { + const selector = useMemo( + () => (state: WeChatState) => ({ + isLoadingAiChat: state.isLoadingAiChat, + quoteMessageContent: state.quoteMessageContent, + aiQuoteMessageContent: state.aiQuoteMessageContent, + }), + [], + ); + return useWeChatStore(useShallow(selector)); +}; + +/** + * 操作方法 selector + * 使用 useShallow 进行 shallow 比较,避免 getSnapshot 警告 + * 虽然方法引用稳定,但返回的对象需要 shallow 比较 + */ +export const useWeChatActions = () => { + const selector = useMemo( + () => (state: WeChatState) => ({ + addMessage: state.addMessage, + updateMessage: state.updateMessage, + recallMessage: state.recallMessage, + loadChatMessages: state.loadChatMessages, + updateShowCheckbox: state.updateShowCheckbox, + updateEnterModule: state.updateEnterModule, + updateQuoteMessageContent: state.updateQuoteMessageContent, + updateIsLoadingAiChat: state.updateIsLoadingAiChat, + updateSelectedChatRecords: state.updateSelectedChatRecords, + updateShowChatRecordModel: state.updateShowChatRecordModel, + setCurrentContact: state.setCurrentContact, + updateAiQuoteMessageContent: state.updateAiQuoteMessageContent, + }), + [], + ); + return useWeChatStore(useShallow(selector)); +}; diff --git a/Touchkebao/src/main.tsx b/Touchkebao/src/main.tsx index dde8d3ad..9edfc483 100644 --- a/Touchkebao/src/main.tsx +++ b/Touchkebao/src/main.tsx @@ -8,6 +8,11 @@ import "dayjs/locale/zh-cn"; import App from "./App"; import "./styles/global.scss"; import { initializeDatabaseFromPersistedUser } from "./utils/db"; +import { initSentry } from "./utils/sentry"; +import { QueryProvider } from "./providers/QueryProvider"; + +// 最先初始化 Sentry(必须在其他代码之前) +initSentry(); // 设置dayjs为中文 dayjs.locale("zh-cn"); @@ -22,7 +27,9 @@ async function bootstrap() { const root = createRoot(document.getElementById("root")!); root.render( - + + + , ); } diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx index b2838f7e..f323cd72 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx @@ -19,10 +19,14 @@ import AudioRecorder from "@/components/Upload/AudioRecorder"; import ToContract from "./components/toContract"; import styles from "./MessageEnter.module.scss"; import { - useWeChatStore, clearAiRequestQueue, manualTriggerAi, } from "@/store/module/weChat/weChat"; +import { + useUIStateSelectors, + useAISelectors, +} from "@/hooks/weChat/useWeChatSelectors"; +import { useWeChatActions } from "@/hooks/weChat/useWeChatSelectors"; import { useContactStore } from "@/store/module/weChat/contacts"; import SelectMap from "./components/selectMap"; const { Footer } = Layout; @@ -183,37 +187,31 @@ const InputToolbar = React.memo( }, ); +InputToolbar.displayName = "InputToolbar"; + const MemoSelectMap: React.FC> = React.memo(props => ); +MemoSelectMap.displayName = "MemoSelectMap"; + const MessageEnter: React.FC = ({ contract }) => { const [inputValue, setInputValue] = useState(""); - const EnterModule = useWeChatStore(state => state.EnterModule); - const updateShowCheckbox = useWeChatStore(state => state.updateShowCheckbox); - const updateEnterModule = useWeChatStore(state => state.updateEnterModule); - const setTransmitModal = useContactStore(state => state.setTransmitModal); - const addMessage = useWeChatStore(state => state.addMessage); - const showChatRecordModel = useWeChatStore( - state => state.showChatRecordModel, - ); - const updateShowChatRecordModel = useWeChatStore( - state => state.updateShowChatRecordModel, - ); - const quoteMessageContent = useWeChatStore( - state => state.quoteMessageContent, - ); - const isLoadingAiChat = useWeChatStore(state => state.isLoadingAiChat); - const updateIsLoadingAiChat = useWeChatStore( - state => state.updateIsLoadingAiChat, - ); - const updateQuoteMessageContent = useWeChatStore( - state => state.updateQuoteMessageContent, - ); - // 获取接待类型:0=人工接待, 1=AI辅助, 2=AI接管 - const aiQuoteMessageContent = useWeChatStore( - state => state.aiQuoteMessageContent, - ); + // ✅ 使用优化的 selector(合并多个 selector,减少重渲染) + const { EnterModule, showChatRecordModel } = useUIStateSelectors(); + const { isLoadingAiChat, quoteMessageContent, aiQuoteMessageContent } = + useAISelectors(); + + const { + updateShowCheckbox, + updateEnterModule, + addMessage, + updateShowChatRecordModel, + updateIsLoadingAiChat, + updateQuoteMessageContent, + } = useWeChatActions(); + + const setTransmitModal = useContactStore(state => state.setTransmitModal); // 判断接待类型 const isAiAssist = aiQuoteMessageContent === 1; // AI辅助 diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransmitModal/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransmitModal/index.tsx index 91f5324b..a3ab2c3a 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransmitModal/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransmitModal/index.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useMemo } from "react"; +import React, { useState, useEffect, useMemo, useCallback } from "react"; import { Modal, Input, @@ -50,7 +50,7 @@ const TransmitModal: React.FC = () => { ); // 加载联系人数据 - const loadContacts = async () => { + const loadContacts = useCallback(async () => { setLoading(true); try { // 从统一联系人表加载所有联系人 @@ -63,9 +63,9 @@ const TransmitModal: React.FC = () => { } finally { setLoading(false); } - }; + }, [currentUserId]); - // 重置状态 + // 重置状态 - 只在 openTransmitModal 变为 true 时执行 useEffect(() => { if (openTransmitModal) { setSearchValue(""); @@ -73,6 +73,8 @@ const TransmitModal: React.FC = () => { setPage(1); loadContacts(); } + // 注意:loadContacts 已经在 useCallback 中稳定,但为了安全,我们只在 openTransmitModal 变化时执行 + // eslint-disable-next-line react-hooks/exhaustive-deps }, [openTransmitModal]); // 过滤联系人 - 支持名称和拼音搜索 diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx index 05d09d01..dce9dc07 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx @@ -21,6 +21,15 @@ import { formatWechatTime } from "@/utils/common"; import { getEmojiPath } from "@/components/EmojiSeclection/wechatEmoji"; import { parseSystemMessage } from "@/utils/filter"; import styles from "./com.module.scss"; +import { + useMessageSelectors, + useUIStateSelectors, +} from "@/hooks/weChat/useWeChatSelectors"; +import { useWeChatActions } from "@/hooks/weChat/useWeChatSelectors"; +import { useMessageParser } from "@/hooks/weChat/useMessageParser"; +import { useMessageGrouping } from "@/hooks/weChat/useMessageGrouping"; +import { Profiler } from "@sentry/react"; +import { addPerformanceBreadcrumb } from "@/utils/sentry"; import { useWeChatStore } from "@/store/module/weChat/weChat"; import { useContactStore } from "@/store/module/weChat/contacts"; import { useCustomerStore } from "@weChatStore/customer"; @@ -324,243 +333,42 @@ const MessageRecord: React.FC = ({ contract }) => { // 选中的聊天记录状态 const [selectedRecords, setSelectedRecords] = useState([]); - const currentMessages = useWeChatStore(state => state.currentMessages); - const currentMessagesHasMore = useWeChatStore( - state => state.currentMessagesHasMore, - ); + // ✅ 使用优化的 selector(合并多个 selector,减少重渲染) + const { + currentMessages, + currentMessagesHasMore, + messagesLoading, + isLoadingData, + } = useMessageSelectors(); + + const { showCheckbox } = useUIStateSelectors(); + + const { + loadChatMessages, + updateShowCheckbox, + updateEnterModule, + updateSelectedChatRecords, + updateQuoteMessageContent, + } = useWeChatActions(); - const loadChatMessages = useWeChatStore(state => state.loadChatMessages); - const messagesLoading = useWeChatStore(state => state.messagesLoading); - const isLoadingData = useWeChatStore(state => state.isLoadingData); - const showCheckbox = useWeChatStore(state => state.showCheckbox); const prevMessagesRef = useRef(currentMessages); const isLoadingMoreRef = useRef(false); const scrollPositionRef = useRef(0); - const updateShowCheckbox = useWeChatStore(state => state.updateShowCheckbox); - const updateEnterModule = useWeChatStore(state => state.updateEnterModule); const currentCustomer = useCustomerStore(state => state.customerList.find(kf => kf.id === contract.wechatAccountId), ); - const updateSelectedChatRecords = useWeChatStore( - state => state.updateSelectedChatRecords, - ); - const setTransmitModal = useContactStore(state => state.setTransmitModal); const [groupRender, setGroupRender] = useState([]); const currentContract = useWeChatStore(state => state.currentContract); - const updateQuoteMessageContent = useWeChatStore( - state => state.updateQuoteMessageContent, - ); - // 判断是否为表情包URL的工具函数 - const isEmojiUrl = (content: string): boolean => { - return ( - content.includes("ac-weremote-s2.oss-cn-shenzhen.aliyuncs.com") || - /\.(gif|webp|png|jpg|jpeg)$/i.test(content) || - content.includes("emoji") || - content.includes("sticker") || - content.includes("expression") - ); - }; + // ✅ 使用 useMessageParser Hook(提取消息解析逻辑) + const { parseMessageContent, parseEmojiText, isEmojiUrl } = + useMessageParser(contract); - // 解析表情包文字格式[表情名称]并替换为img标签 - const parseEmojiText = useCallback((text: string): React.ReactNode[] => { - const emojiRegex = /\[([^\]]+)\]/g; - const parts: React.ReactNode[] = []; - let lastIndex = 0; - let match; - - while ((match = emojiRegex.exec(text)) !== null) { - // 添加表情前的文字 - if (match.index > lastIndex) { - parts.push(text.slice(lastIndex, match.index)); - } - - // 获取表情名称并查找对应路径 - const emojiName = match[1]; - const emojiPath = getEmojiPath(emojiName as any); - - if (emojiPath) { - // 如果找到表情,添加img标签 - parts.push( - {emojiName}, - ); - } else { - // 如果没找到表情,保持原文字 - parts.push(match[0]); - } - - lastIndex = emojiRegex.lastIndex; - } - - // 添加剩余的文字 - if (lastIndex < text.length) { - parts.push(text.slice(lastIndex)); - } - - return parts; - }, []); - - const renderUnknownContent = useCallback( - ( - rawContent: string, - trimmedContent: string, - msg?: ChatRecord, - contractParam?: ContractData | weChatGroup, - ) => { - if (isLegacyEmojiContent(trimmedContent)) { - return renderEmojiContent(rawContent); - } - - const jsonData = tryParseContentJson(trimmedContent); - - if (jsonData && typeof jsonData === "object") { - // 判断是否为红包消息 - if ( - jsonData.nativeurl && - typeof jsonData.nativeurl === "string" && - jsonData.nativeurl.includes( - "wxpay://c2cbizmessagehandler/hongbao/receivehongbao", - ) - ) { - return ( - - ); - } - - // 判断是否为转账消息 - if ( - jsonData.title === "微信转账" || - (jsonData.transferid && jsonData.feedesc) - ) { - return ( - - ); - } - - if (jsonData.type === "file" && msg && contractParam) { - return ( - - ); - } - - if (jsonData.type === "link" && jsonData.title && jsonData.url) { - const { title, desc, thumbPath, url } = jsonData; - - return ( -
-
openInNewTab(url)} - > - {thumbPath && ( - 链接缩略图 { - const target = event.target as HTMLImageElement; - target.style.display = "none"; - }} - /> - )} -
-
{title}
- {desc &&
{desc}
} -
-
-
链接
-
- ); - } - - if ( - jsonData.previewImage && - (jsonData.tencentUrl || jsonData.videoUrl) - ) { - const previewImageUrl = String(jsonData.previewImage).replace( - /[`"']/g, - "", - ); - return ( -
-
- 视频预览 { - const videoUrl = jsonData.videoUrl || jsonData.tencentUrl; - if (videoUrl) { - openInNewTab(videoUrl); - } - }} - onError={event => { - const target = event.target as HTMLImageElement; - const parent = target.parentElement?.parentElement; - if (parent) { - parent.innerHTML = `
[视频预览加载失败]
`; - } - }} - /> -
- - - -
-
-
- ); - } - } - - if (isHttpImageUrl(trimmedContent)) { - return renderImageContent({ - src: rawContent, - alt: "图片消息", - fallbackText: "[图片加载失败]", - }); - } - - if (isFileUrl(trimmedContent)) { - return renderFileContent(trimmedContent); - } - - return ( -
{parseEmojiText(rawContent)}
- ); - }, - [parseEmojiText], - ); + // ✅ 使用 useMessageGrouping Hook(消息分组,使用 useMemo 缓存) + const groupedMessages = useMessageGrouping(currentMessages); useEffect(() => { const fetchGroupMembers = async () => { @@ -607,10 +415,20 @@ const MessageRecord: React.FC = ({ contract }) => { [groupMemberMap], ); + // 定义 scrollToBottom 函数,在 useEffect 之前 + const scrollToBottom = useCallback(() => { + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }, []); + useEffect(() => { const prevMessages = prevMessagesRef.current; const prevLength = prevMessages.length; + // 如果消息数组引用相同,跳过处理(避免不必要的滚动) + if (prevMessages === currentMessages) { + return; + } + const hasVideoStateChange = currentMessages.some((msg, index) => { // 首先检查消息对象本身是否为null或undefined if (!msg || !msg.content) return false; @@ -651,14 +469,20 @@ const MessageRecord: React.FC = ({ contract }) => { if (isLoadingMoreRef.current && currentMessages.length > prevLength) { // 不滚动,等待加载完成后在另一个 useEffect 中恢复滚动位置 } else if (currentMessages.length > prevLength && !hasVideoStateChange) { - scrollToBottom(); + // 使用 setTimeout 延迟滚动,避免在渲染过程中触发状态更新 + setTimeout(() => { + scrollToBottom(); + }, 0); } else if (isLoadingData && !hasVideoStateChange) { - scrollToBottom(); + // 使用 setTimeout 延迟滚动,避免在渲染过程中触发状态更新 + setTimeout(() => { + scrollToBottom(); + }, 0); } - // 更新上一次的消息状态 + // 更新上一次的消息状态(使用浅拷贝避免引用问题) prevMessagesRef.current = currentMessages; - }, [currentMessages, isLoadingData]); + }, [currentMessages, isLoadingData, scrollToBottom]); // 监听加载状态,当加载完成时恢复滚动位置 useEffect(() => { @@ -676,108 +500,6 @@ const MessageRecord: React.FC = ({ contract }) => { } }, [messagesLoading]); - const scrollToBottom = () => { - messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); - }; - - // 解析消息内容,根据msgType判断消息类型并返回对应的渲染内容 - const parseMessageContent = ( - content: string | null | undefined, - msg: ChatRecord, - msgType?: number, - ) => { - // 处理null或undefined的内容 - if (content === null || content === undefined) { - return
消息内容不可用
; - } - - // 统一的错误消息渲染函数 - const renderErrorMessage = (fallbackText: string) => ( -
{fallbackText}
- ); - - const isStringValue = typeof content === "string"; - const rawContent = isStringValue ? content : ""; - const trimmedContent = rawContent.trim(); - - switch (msgType) { - case 1: // 文本消息 - return ( -
-
- {parseEmojiText(rawContent)} -
-
- ); - - case 3: // 图片消息 - if (!isStringValue || !trimmedContent) { - return renderErrorMessage("[图片消息 - 无效链接]"); - } - return renderImageContent({ - src: rawContent, - alt: "图片消息", - fallbackText: "[图片加载失败]", - withBubble: true, - }); - - case 34: // 语音消息 - if (!isStringValue || !trimmedContent) { - return renderErrorMessage("[语音消息 - 无效内容]"); - } - - return ; - - case 43: // 视频消息 - return ( - - ); - - case 47: // 动图表情包(gif、其他表情包) - if (!isStringValue || !trimmedContent) { - return renderErrorMessage("[表情包 - 无效链接]"); - } - - if (isEmojiUrl(trimmedContent)) { - return renderEmojiContent(rawContent); - } - return renderErrorMessage("[表情包]"); - - case 48: // 定位消息 - return ; - - case 49: // 小程序/文章/其他:图文、文件 - return ( - - ); - - case 10002: // 系统推荐备注消息 - return ( - - ); - - default: { - if (!isStringValue || !trimmedContent) { - return renderErrorMessage( - `[未知消息类型${msgType ? ` - ${msgType}` : ""}]`, - ); - } - - return renderUnknownContent(rawContent, trimmedContent, msg, contract); - } - } - }; - // 获取群成员头像 // 清理微信ID前缀 const clearWechatidInContent = useCallback((sender: any, content: string) => { @@ -834,27 +556,6 @@ const MessageRecord: React.FC = ({ contract }) => { return selectedRecords.some(record => record.id === msg.id); }; - // 用于分组消息并添加时间戳的辅助函数 - const groupMessagesByTime = (messages: ChatRecord[] | null | undefined) => { - const safeMessages = Array.isArray(messages) - ? messages - : Array.isArray((messages as any)?.list) - ? ((messages as any).list as ChatRecord[]) - : []; - - return safeMessages - .filter(msg => msg !== null && msg !== undefined) // 过滤掉null和undefined的消息 - .map(msg => ({ - time: formatWechatTime(String(msg?.wechatTime)), - messages: [msg], - })); - }; - - const groupedMessages = useMemo( - () => groupMessagesByTime(currentMessages), - [currentMessages], - ); - const isGroupChat = !!contract.chatroomId; const loadMoreMessages = () => { if (messagesLoading || !currentMessagesHasMore) { @@ -944,95 +645,116 @@ const MessageRecord: React.FC = ({ contract }) => { }; return ( -
-
- {currentMessagesHasMore ? "点击加载更早的信息" : "已经没有更早的消息了"} - {messagesLoading ? : ""} -
- {groupedMessages.map((group, groupIndex) => ( - - {group.messages - .filter(v => [10000, -10001].includes(v.msgType)) - .map(msg => { - // 解析系统消息,提取纯文本(移除img标签和_wc_custom_link_标签) - const parsedText = parseSystemMessage(msg.content); - return ( -
- {parsedText} -
- ); - })} + { + // ✅ 使用 Sentry 监控组件渲染性能 + if (actualDuration > 100) { + addPerformanceBreadcrumb("MessageRecord 慢渲染", { + duration: actualDuration, + phase, + messageCount: currentMessages.length, + contractId: contract.id, + }); + } + }} + > +
+
+ {currentMessagesHasMore + ? "点击加载更早的信息" + : "已经没有更早的消息了"} + {messagesLoading ? : ""} +
+ {groupedMessages.map((group, groupIndex) => ( + + {group.messages + .filter(v => [10000, -10001].includes(v.msgType)) + .map(msg => { + // 解析系统消息,提取纯文本(移除img标签和_wc_custom_link_标签) + const parsedText = parseSystemMessage(msg.content); + return ( +
+ {parsedText} +
+ ); + })} - {group.messages - .filter(v => [570425393, 90000].includes(v.msgType)) - .map(msg => { - // 解析JSON字符串 - let displayContent = msg.content; - try { - const parsedContent = JSON.parse(msg.content); - if ( - parsedContent && - typeof parsedContent === "object" && - parsedContent.content - ) { - displayContent = parsedContent.content; + {group.messages + .filter(v => [570425393, 90000].includes(v.msgType)) + .map(msg => { + // 解析JSON字符串 + let displayContent = msg.content; + try { + const parsedContent = JSON.parse(msg.content); + if ( + parsedContent && + typeof parsedContent === "object" && + parsedContent.content + ) { + displayContent = parsedContent.content; + } + } catch (error) { + // 如果解析失败,使用原始内容 + displayContent = msg.content; } - } catch (error) { - // 如果解析失败,使用原始内容 - displayContent = msg.content; - } - return ( -
- {displayContent} -
- ); - })} -
{group.time}
- {group.messages - .filter(v => ![10000, 570425393, 90000, -10001].includes(v.msgType)) - .map(msg => { - if (!msg) return null; - return ( - - ); - })} -
- ))} -
- {/* 右键菜单组件 */} - - {/* 转发模态框 */} - -
+ return ( +
+ {displayContent} +
+ ); + })} +
{group.time}
+ {group.messages + .filter( + v => ![10000, 570425393, 90000, -10001].includes(v.msgType), + ) + .map(msg => { + if (!msg) return null; + return ( + + ); + })} + + ))} +
+ {/* 右键菜单组件 */} + + {/* 转发模态框 */} + +
+ ); }; diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/FriendsCicle/components/friendCard.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/FriendsCicle/components/friendCard.tsx index ada75633..a222df9a 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/FriendsCicle/components/friendCard.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/FriendsCicle/components/friendCard.tsx @@ -24,6 +24,7 @@ import { import { comfirm } from "@/utils/common"; import { useWeChatStore } from "@/store/module/weChat/weChat"; +import { useShallow } from "zustand/react/shallow"; // 单个朋友圈项目组件 export const FriendCard: React.FC = ({ monent, @@ -37,7 +38,13 @@ export const FriendCard: React.FC = ({ const time = formatTime(monent.createTime); const likesCount = monent?.likeList?.length || 0; const commentsCount = monent?.commentList?.length || 0; - const { updateLikeMoment, updateComment } = useWeChatStore(); + // ✅ 使用 useShallow 避免 getSnapshot 警告 + const { updateLikeMoment, updateComment } = useWeChatStore( + useShallow(state => ({ + updateLikeMoment: state.updateLikeMoment, + updateComment: state.updateComment, + })), + ); // 评论相关状态 const [showCommentInput, setShowCommentInput] = useState(false); diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/FriendsCicle/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/FriendsCicle/index.tsx index d24ea48a..9c10bfa9 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/FriendsCicle/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/FriendsCicle/index.tsx @@ -8,6 +8,7 @@ import styles from "./index.module.scss"; import { fetchFriendsCircleData } from "./api"; import { useCkChatStore } from "@/store/module/ckchat/ckchat"; import { useWeChatStore } from "@/store/module/weChat/weChat"; +import { useShallow } from "zustand/react/shallow"; interface FriendsCircleProps { wechatFriendId?: number; @@ -17,7 +18,13 @@ const FriendsCircle: React.FC = ({ wechatFriendId }) => { const currentKf = useCkChatStore(state => state.kfUserList.find(kf => kf.id === state.kfSelected), ); - const { clearMomentCommon, updateMomentCommonLoading } = useWeChatStore(); + // ✅ 使用 useShallow 避免 getSnapshot 警告 + const { clearMomentCommon, updateMomentCommonLoading } = useWeChatStore( + useShallow(state => ({ + clearMomentCommon: state.clearMomentCommon, + updateMomentCommonLoading: state.updateMomentCommonLoading, + })), + ); const MomentCommon = useWeChatStore(state => state.MomentCommon); const MomentCommonLoading = useWeChatStore( state => state.MomentCommonLoading, diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/index.tsx index 0ac21322..3d36b439 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/index.tsx @@ -53,8 +53,8 @@ const Person: React.FC = ({ contract }) => { // 会话列表 & 当前用户,用于在备注更新后同步会话列表显示 const setSessions = useMessageStore(state => state.setSessions); - const { user } = useUserStore(); - const currentUserId = user?.id || 0; + // ✅ 使用 selector 避免 getSnapshot 警告 + const currentUserId = useUserStore(state => state.user?.id) || 0; // 判断是否为群聊 const isGroup = "chatroomId" in contract; @@ -227,7 +227,8 @@ const Person: React.FC = ({ contract }) => { // 不再需要从useContactStore获取getContactsByCustomer - const { sendCommand } = useWebSocketStore(); + // ✅ 使用 selector 避免 getSnapshot 警告 + const sendCommand = useWebSocketStore(state => state.sendCommand); // 权限控制:检查当前客服是否有群管理权限 const hasGroupManagePermission = () => { diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/index.tsx index 013ec6d9..17cdd278 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/index.tsx @@ -26,7 +26,8 @@ import FollowupReminderModal from "./components/FollowupReminderModal"; import TodoListModal from "./components/TodoListModal"; import ChatRecordSearch from "./components/ChatRecordSearch"; import { setFriendInjectConfig } from "@/pages/pc/ckbox/weChat/api"; -import { useWeChatStore } from "@/store/module/weChat/weChat"; +import { useAISelectors, useUIStateSelectors } from "@/hooks/weChat/useWeChatSelectors"; +import { useWeChatActions } from "@/hooks/weChat/useWeChatSelectors"; import { MessageManager } from "@/utils/dbAction/message"; import { ContactManager } from "@/utils/dbAction/contact"; import { useUserStore } from "@storeModule/user"; @@ -43,18 +44,13 @@ const typeOptions = [ ]; const ChatWindow: React.FC = ({ contract }) => { - const updateAiQuoteMessageContent = useWeChatStore( - state => state.updateAiQuoteMessageContent, - ); - const aiQuoteMessageContent = useWeChatStore( - state => state.aiQuoteMessageContent, - ); - const showChatRecordModel = useWeChatStore( - state => state.showChatRecordModel, - ); - const setCurrentContact = useWeChatStore(state => state.setCurrentContact); - const { user } = useUserStore(); - const currentUserId = user?.id || 0; + // ✅ 使用优化的 selector(合并多个 selector,减少重渲染) + const { aiQuoteMessageContent } = useAISelectors(); + const { showChatRecordModel } = useUIStateSelectors(); + const { updateAiQuoteMessageContent, setCurrentContact } = useWeChatActions(); + + // ✅ 使用 selector 避免 getSnapshot 警告 + const currentUserId = useUserStore(state => state.user?.id) || 0; const [showProfile, setShowProfile] = useState(true); const [followupModalVisible, setFollowupModalVisible] = useState(false); diff --git a/Touchkebao/src/providers/QueryProvider.tsx b/Touchkebao/src/providers/QueryProvider.tsx new file mode 100644 index 00000000..f455394e --- /dev/null +++ b/Touchkebao/src/providers/QueryProvider.tsx @@ -0,0 +1,27 @@ +import React from "react"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 5 * 60 * 1000, // 5 分钟内数据视为新鲜 + gcTime: 10 * 60 * 1000, // 10 分钟缓存(v5 使用 gcTime 替代 cacheTime) + retry: 1, // 失败重试 1 次 + refetchOnWindowFocus: true, // 窗口聚焦时自动刷新 + refetchOnReconnect: true, // 网络重连时自动刷新 + }, + mutations: { + retry: 1, // 失败重试 1 次 + }, + }, +}); + +interface QueryProviderProps { + children: React.ReactNode; +} + +export const QueryProvider: React.FC = ({ children }) => { + return ( + {children} + ); +}; diff --git a/Touchkebao/src/utils/sentry/index.ts b/Touchkebao/src/utils/sentry/index.ts new file mode 100644 index 00000000..a630125a --- /dev/null +++ b/Touchkebao/src/utils/sentry/index.ts @@ -0,0 +1,87 @@ +import * as Sentry from "@sentry/react"; + +/** + * 初始化 Sentry + * 用于错误监控和性能追踪 + */ +export const initSentry = () => { + if (!import.meta.env.VITE_SENTRY_DSN) { + console.warn("Sentry DSN 未配置,跳过初始化"); + return; + } + + Sentry.init({ + dsn: import.meta.env.VITE_SENTRY_DSN, + environment: import.meta.env.MODE, + integrations: [ + new Sentry.BrowserTracing({ + tracingOrigins: [ + "localhost", + import.meta.env.VITE_API_BASE_URL || "/api", + ], + }), + new Sentry.Replay({ + maskAllText: false, + blockAllMedia: false, + }), + ], + tracesSampleRate: import.meta.env.MODE === "development" ? 1.0 : 0.1, + replaysSessionSampleRate: 0.1, + replaysOnErrorSampleRate: 1.0, + ignoreErrors: [ + "NetworkError", + "Failed to fetch", + "chrome-extension://", + "moz-extension://", + ], + }); +}; + +/** + * 手动捕获错误 + */ +export const captureError = ( + error: Error, + context?: { + tags?: Record; + extra?: Record; + }, +) => { + Sentry.captureException(error, { + tags: context?.tags, + extra: context?.extra, + }); +}; + +/** + * 手动捕获消息 + */ +export const captureMessage = ( + message: string, + level: "info" | "warning" | "error" = "info", + context?: { + tags?: Record; + extra?: Record; + }, +) => { + Sentry.captureMessage(message, { + level, + tags: context?.tags, + extra: context?.extra, + }); +}; + +/** + * 添加性能面包屑 + */ +export const addPerformanceBreadcrumb = ( + message: string, + data?: Record, +) => { + Sentry.addBreadcrumb({ + category: "performance", + message, + level: "info", + data, + }); +}; diff --git a/Touchkebao/提示词/性能优化快速参考.md b/Touchkebao/提示词/性能优化快速参考.md new file mode 100644 index 00000000..e1f93a30 --- /dev/null +++ b/Touchkebao/提示词/性能优化快速参考.md @@ -0,0 +1,193 @@ +# 性能优化快速参考指南 + +> 快速查看关键优化点和代码示例 + +## 🚀 快速开始 + +### 1. 安装依赖(如果需要) + +```bash +# 检查 zustand 版本(需要 >= 5.0) +npm list zustand + +# 确认 react-window 已安装 +npm list react-window +``` + +### 2. 创建目录结构 + +```bash +mkdir -p src/hooks/weChat +mkdir -p src/components/MessageRenderer +mkdir -p src/components/AiLoadingIndicator +``` + +--- + +## 📝 核心优化代码模板 + +### 1. Zustand Selector 优化模板 + +```typescript +// ❌ 错误写法(会导致多次重渲染) +const currentMessages = useWeChatStore(state => state.currentMessages); +const messagesLoading = useWeChatStore(state => state.messagesLoading); + +// ✅ 正确写法(合并 selector,使用 shallow) +import { shallow } from "zustand/shallow"; + +const { currentMessages, messagesLoading } = useWeChatStore( + state => ({ + currentMessages: state.currentMessages, + messagesLoading: state.messagesLoading, + }), + shallow, // 关键:使用 shallow 比较 +); +``` + +### 2. React.memo 优化模板 + +```typescript +// ✅ 使用 React.memo 包装组件 +const MyComponent: React.FC = React.memo( + ({ prop1, prop2 }) => { + // 组件逻辑 + }, + (prev, next) => { + // 自定义比较函数 + return prev.prop1.id === next.prop1.id; + }, +); + +MyComponent.displayName = "MyComponent"; +``` + +### 3. useCallback 优化模板 + +```typescript +// ✅ 使用 useCallback 缓存函数 +const handleClick = useCallback( + (id: number) => { + // 处理逻辑 + }, + [dependency1, dependency2], // 依赖项 +); + +// ✅ 使用 useRef 存储不变的引用 +const contractRef = useRef(contract); +contractRef.current = contract; // 更新引用 + +const handleSend = useCallback(() => { + const currentContract = contractRef.current; // 使用引用 + // 处理逻辑 +}, []); // 不需要 contract 作为依赖 +``` + +### 4. useMemo 优化模板 + +```typescript +// ✅ 使用 useMemo 缓存计算结果 +const expensiveValue = useMemo( + () => { + // 复杂计算 + return computeExpensiveValue(data); + }, + [data], // 依赖项 +); +``` + +### 5. 虚拟滚动模板 + +```typescript +import { FixedSizeList } from "react-window"; + + + {({ index, style, data }) => ( +
+ +
+ )} +
+``` + +--- + +## 🔍 常见问题排查 + +### 问题 1:组件仍然频繁重渲染 + +**检查**: + +1. ✅ 是否使用了 `shallow` 比较? +2. ✅ `useCallback` 依赖项是否正确? +3. ✅ `React.memo` 比较函数是否正确? + +### 问题 2:虚拟滚动不工作 + +**检查**: + +1. ✅ 容器高度是否设置? +2. ✅ `itemSize` 是否正确? +3. ✅ `itemCount` 是否正确? + +### 问题 3:性能没有提升 + +**检查**: + +1. ✅ 是否使用了 React DevTools Profiler 测量? +2. ✅ 是否在开发模式下测试?(开发模式性能较差) +3. ✅ 是否有其他性能瓶颈? + +--- + +## 📊 性能指标参考 + +### 优化前(基准) + +- 100 条消息滚动:卡顿明显 +- 组件重渲染:每次状态变化都重渲染 +- 内存占用:1000 条消息 > 200MB + +### 优化后(目标) + +- 100 条消息滚动:流畅(FPS > 30) +- 组件重渲染:减少 60-80% +- 内存占用:1000 条消息 < 100MB + +--- + +## 🛠️ 调试工具 + +### React DevTools Profiler + +1. 安装 React DevTools 浏览器扩展 +2. 打开 Profiler 标签 +3. 点击录制按钮 +4. 执行操作 +5. 停止录制,查看性能数据 + +### Chrome Performance + +1. 打开 Chrome DevTools +2. 切换到 Performance 标签 +3. 点击录制按钮 +4. 执行操作 +5. 停止录制,分析性能数据 + +--- + +## 📚 相关文档 + +- [详细改造计划](./性能优化详细改造计划.md) +- [优化清单](./性能优化改造清单.md) +- [React 代码规范](../提示词/React代码编写规范.md) + +--- + +**最后更新**: 2025-01-XX diff --git a/Touchkebao/性能优化改造清单.md b/Touchkebao/提示词/性能优化改造清单.md similarity index 100% rename from Touchkebao/性能优化改造清单.md rename to Touchkebao/提示词/性能优化改造清单.md diff --git a/Touchkebao/提示词/性能优化详细改造计划.md b/Touchkebao/提示词/性能优化详细改造计划.md new file mode 100644 index 00000000..153629f1 --- /dev/null +++ b/Touchkebao/提示词/性能优化详细改造计划.md @@ -0,0 +1,2985 @@ +# Touchkebao 性能优化详细改造计划 + +> 基于性能优化改造清单,提供可执行的详细实施步骤 + +**文档版本**: v2.0 +**创建日期**: 2025-01-XX +**最后更新**: 2025-01-XX(新增 Sentry 和 TanStack Query) +**预计总工期**: 4-5周(包含新增优化工具) +**负责人**: 开发团队 + +--- + +## 📋 目录 + +- [准备工作](#准备工作) +- [第一阶段:核心组件优化(第1周)](#第一阶段核心组件优化第1周) +- [第二阶段:性能优化(第2周)](#第二阶段性能优化第2周) +- [第三阶段:高级优化(第3-4周)](#第三阶段高级优化第3-4周) +- [测试与验证](#测试与验证) +- [回滚方案](#回滚方案) + +**新增优化工具**: + +- **Sentry**:错误监控和性能追踪(第1周) +- **TanStack Query**:数据缓存和请求优化(第2周) + +--- + +## 准备工作 + +### 1. 环境准备 + +#### 1.1 安装依赖 + +```bash +# 安装 shallow 比较工具(Zustand 5.x 已内置,无需额外安装) +# 检查 zustand 版本 +npm list zustand + +# 如果版本 < 5.0,需要升级 +npm install zustand@^5.0.6 + +# 确认 react-window 已安装 +npm list react-window +# 当前版本:^1.8.11 ✅ + +# ========== 新增优化工具 ========== +# 1. 安装 Sentry(错误监控和性能追踪) +npm install @sentry/react + +# 2. 安装 TanStack Query(数据缓存和请求优化) +npm install @tanstack/react-query +``` + +#### 1.2 创建目录结构 + +```bash +# 创建新的目录结构 +mkdir -p src/hooks/weChat +mkdir -p src/components/MessageRenderer +mkdir -p src/components/AiLoadingIndicator +mkdir -p src/utils/messageParser +mkdir -p src/providers +mkdir -p src/utils/sentry +``` + +#### 1.3 配置 Sentry 和 TanStack Query(必须先完成) + +> **重要**:在开始性能优化前,必须先配置好这两个工具,以便在优化过程中实时监控效果 + +##### 步骤 1.3.1:配置 Sentry + +**文件**: `src/utils/sentry/index.ts` + +```typescript +import * as Sentry from "@sentry/react"; + +/** + * 初始化 Sentry + * 用于错误监控和性能追踪 + */ +export const initSentry = () => { + if (!import.meta.env.VITE_SENTRY_DSN) { + console.warn("Sentry DSN 未配置,跳过初始化"); + return; + } + + Sentry.init({ + dsn: import.meta.env.VITE_SENTRY_DSN, + environment: import.meta.env.MODE, + integrations: [ + new Sentry.BrowserTracing({ + tracingOrigins: [ + "localhost", + import.meta.env.VITE_API_BASE_URL || "/api", + ], + }), + new Sentry.Replay({ + maskAllText: false, + blockAllMedia: false, + }), + ], + tracesSampleRate: import.meta.env.MODE === "development" ? 1.0 : 0.1, + replaysSessionSampleRate: 0.1, + replaysOnErrorSampleRate: 1.0, + ignoreErrors: [ + "NetworkError", + "Failed to fetch", + "chrome-extension://", + "moz-extension://", + ], + }); +}; + +export const captureError = ( + error: Error, + context?: { + tags?: Record; + extra?: Record; + }, +) => { + Sentry.captureException(error, { + tags: context?.tags, + extra: context?.extra, + }); +}; + +export const addPerformanceBreadcrumb = ( + message: string, + data?: Record, +) => { + Sentry.addBreadcrumb({ + category: "performance", + message, + level: "info", + data, + }); +}; +``` + +**文件**: `src/main.tsx` + +```typescript +import { initSentry } from "@/utils/sentry"; + +// 最先初始化 Sentry +initSentry(); + +// ... 其他代码 +``` + +**文件**: `src/App.tsx` + +```typescript +import { SentryErrorBoundary } from "@sentry/react"; + +const ErrorFallback = () => ( +
+

出现了一些问题

+

我们已经记录了这个问题,正在修复中...

+ +
+); + +function App() { + return ( + + {/* 应用内容 */} + + ); +} +``` + +##### 步骤 1.3.2:配置 TanStack Query + +**文件**: `src/providers/QueryProvider.tsx` + +```typescript +import React from "react"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 5 * 60 * 1000, + cacheTime: 10 * 60 * 1000, + retry: 1, + refetchOnWindowFocus: true, + refetchOnReconnect: true, + }, + mutations: { + retry: 1, + }, + }, +}); + +interface QueryProviderProps { + children: React.ReactNode; +} + +export const QueryProvider: React.FC = ({ children }) => { + return ( + + {children} + {import.meta.env.MODE === "development" && ( + + )} + + ); +}; +``` + +**文件**: `src/main.tsx` 或 `src/App.tsx` + +```typescript +import { QueryProvider } from "@/providers/QueryProvider"; + +function App() { + return ( + + {/* 应用内容 */} + + ); +} +``` + +#### 1.4 性能基准测试(使用 Sentry 记录) + +**在开始优化前,记录当前性能指标:** + +1. **使用 React DevTools Profiler** + - 记录 MessageRecord 组件渲染时间 + - 记录 MessageEnter 组件渲染时间 + - 记录重渲染次数 + +2. **使用 Sentry 记录性能基准** + + ```typescript + import { addPerformanceBreadcrumb } from "@/utils/sentry"; + + // 在关键组件中添加性能记录 + addPerformanceBreadcrumb("性能优化前 - MessageRecord 渲染", { + renderTime: 150, // ms + messageCount: 100, + }); + ``` + +3. **使用 Chrome Performance** + - 记录页面加载时间 + - 记录长任务(Long Tasks) + - 记录内存占用 + +4. **手动测试** + - 测试 100 条消息滚动性能 + - 测试 500 条消息滚动性能 + - 测试输入框响应时间 + +**保存基准数据到**: `docs/performance-baseline.md` + +**验收标准**: + +- [ ] Sentry 初始化成功,能够看到性能数据 +- [ ] TanStack Query 配置成功,DevTools 可用 +- [ ] 基准性能数据已记录 + +--- + +## 第一阶段:核心组件优化(第1周) + +> **重要提示**:本阶段所有优化任务都会同时使用 Sentry 进行性能监控和错误追踪,使用 TanStack Query 优化数据获取 + +### 任务 0.1:Sentry 和 TanStack Query 基础配置(已完成) + +> ✅ **已完成**:如果依赖已安装,此任务已完成。如果未完成,请先完成准备工作中的配置步骤。 + +#### 步骤 0.1.1:安装和配置 Sentry + +**文件**: `src/utils/sentry/index.ts` + +```typescript +import * as Sentry from "@sentry/react"; + +/** + * 初始化 Sentry + * 用于错误监控和性能追踪 + */ +export const initSentry = () => { + if (!import.meta.env.VITE_SENTRY_DSN) { + console.warn("Sentry DSN 未配置,跳过初始化"); + return; + } + + Sentry.init({ + dsn: import.meta.env.VITE_SENTRY_DSN, + environment: import.meta.env.MODE, + integrations: [ + new Sentry.BrowserTracing({ + // 追踪 API 请求性能 + tracingOrigins: [ + "localhost", + import.meta.env.VITE_API_BASE_URL || "/api", + ], + }), + new Sentry.Replay({ + // 错误回放,帮助调试 + maskAllText: false, + blockAllMedia: false, + }), + ], + // 性能追踪采样率(开发环境 100%,生产环境可降低) + tracesSampleRate: import.meta.env.MODE === "development" ? 1.0 : 0.1, + // 会话回放采样率 + replaysSessionSampleRate: 0.1, + replaysOnErrorSampleRate: 1.0, + // 忽略某些错误 + ignoreErrors: [ + // 忽略网络错误(可能是用户网络问题) + "NetworkError", + "Failed to fetch", + // 忽略浏览器扩展错误 + "chrome-extension://", + "moz-extension://", + ], + }); +}; + +/** + * 手动捕获错误 + */ +export const captureError = ( + error: Error, + context?: { + tags?: Record; + extra?: Record; + }, +) => { + Sentry.captureException(error, { + tags: context?.tags, + extra: context?.extra, + }); +}; + +/** + * 手动捕获消息 + */ +export const captureMessage = ( + message: string, + level: "info" | "warning" | "error" = "info", + context?: { + tags?: Record; + extra?: Record; + }, +) => { + Sentry.captureMessage(message, { + level, + tags: context?.tags, + extra: context?.extra, + }); +}; + +/** + * 添加性能面包屑 + */ +export const addPerformanceBreadcrumb = ( + message: string, + data?: Record, +) => { + Sentry.addBreadcrumb({ + category: "performance", + message, + level: "info", + data, + }); +}; +``` + +#### 步骤 0.1.2:在应用入口初始化 + +**文件**: `src/main.tsx` 或 `src/App.tsx` + +```typescript +import { initSentry } from "@/utils/sentry"; + +// 在应用启动时初始化 Sentry +initSentry(); + +// ... 其他代码 +``` + +#### 步骤 0.1.3:包装根组件 + +**文件**: `src/App.tsx` + +```typescript +import { SentryErrorBoundary } from "@sentry/react"; + +const ErrorFallback = () => ( +
+

出现了一些问题

+

我们已经记录了这个问题,正在修复中...

+ +
+); + +function App() { + return ( + + {/* 应用内容 */} + + ); +} +``` + +#### 步骤 0.1.4:添加关键错误监控 + +**文件**: `src/store/module/websocket/msgManage.ts` + +```typescript +import { captureError, captureMessage } from "@/utils/sentry"; + +// 在关键错误处添加监控 +CmdNotify: async (message: WebSocketMessage) => { + if (["Auth failed", "Kicked out"].includes(message.notify)) { + captureMessage("WebSocket 认证失败", "error", { + tags: { source: "websocket", type: message.notify }, + extra: { message }, + }); + // ... 原有逻辑 + } +}, +``` + +**文件**: `src/store/module/weChat/weChat.ts` + +```typescript +import { captureError } from "@/utils/sentry"; + +loadChatMessages: async (Init: boolean, pageOverride?: number) => { + try { + // ... 原有逻辑 + } catch (error) { + captureError(error as Error, { + tags: { action: "loadChatMessages" }, + extra: { contactId: contact?.id, page: nextPage }, + }); + console.error("获取聊天消息失败:", error); + } +}, +``` + +#### 步骤 0.1.5:添加性能监控 + +**文件**: `src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx` + +```typescript +import { Profiler } from "@sentry/react"; +import { addPerformanceBreadcrumb } from "@/utils/sentry"; + +const MessageRecord: React.FC = ({ contract }) => { + return ( + { + // 记录组件渲染开始时间 + addPerformanceBreadcrumb("MessageRecord render start", { + contractId: contract.id, + }); + }} + onRender={(id, phase, actualDuration) => { + // 记录组件渲染性能 + if (actualDuration > 100) { + // 如果渲染时间超过 100ms,记录警告 + addPerformanceBreadcrumb("MessageRecord slow render", { + duration: actualDuration, + phase, + contractId: contract.id, + }); + } + }} + > + {/* 组件内容 */} + + ); +}; +``` + +**验收标准**: + +- [ ] Sentry 初始化成功 +- [ ] 错误能够正确上报 +- [ ] 性能数据能够正确记录 +- [ ] 不影响现有功能 +- [ ] 生产环境配置正确 + +**预期收益**: + +- ✅ **实时错误监控** +- ✅ **性能问题主动发现** +- ✅ **为后续优化提供基准数据** + +--- + +### 任务 1.1:优化 Zustand Selector 使用(优先级:🔥🔥🔥) + +**预计时间**: 2小时 +**影响范围**: 所有使用 useWeChatStore 的组件 + +> **同时使用 Sentry 监控**:在优化过程中使用 Sentry Profiler 监控组件渲染性能变化 + +#### 步骤 1.1.1:创建自定义 Selector Hook + +**文件**: `src/hooks/weChat/useWeChatSelectors.ts` + +```typescript +import { useWeChatStore } from "@/store/module/weChat/weChat"; +import { shallow } from "zustand/shallow"; + +/** + * 合并多个 selector,减少重渲染 + * 使用 shallow 比较,只有对象属性变化时才触发更新 + */ +export const useWeChatSelectors = () => { + return useWeChatStore( + state => ({ + // 消息相关 + currentMessages: state.currentMessages, + currentMessagesHasMore: state.currentMessagesHasMore, + messagesLoading: state.messagesLoading, + isLoadingData: state.isLoadingData, + + // 联系人相关 + currentContract: state.currentContract, + + // UI 状态 + showCheckbox: state.showCheckbox, + EnterModule: state.EnterModule, + showChatRecordModel: state.showChatRecordModel, + + // AI 相关 + isLoadingAiChat: state.isLoadingAiChat, + quoteMessageContent: state.quoteMessageContent, + aiQuoteMessageContent: state.aiQuoteMessageContent, + + // 选中记录 + selectedChatRecords: state.selectedChatRecords, + }), + shallow, // 使用 shallow 比较 + ); +}; + +/** + * 消息相关的 selector + */ +export const useMessageSelectors = () => { + return useWeChatStore( + state => ({ + currentMessages: state.currentMessages, + currentMessagesHasMore: state.currentMessagesHasMore, + messagesLoading: state.messagesLoading, + isLoadingData: state.isLoadingData, + }), + shallow, + ); +}; + +/** + * UI 状态相关的 selector + */ +export const useUIStateSelectors = () => { + return useWeChatStore( + state => ({ + showCheckbox: state.showCheckbox, + EnterModule: state.EnterModule, + showChatRecordModel: state.showChatRecordModel, + }), + shallow, + ); +}; + +/** + * AI 相关的 selector + */ +export const useAISelectors = () => { + return useWeChatStore( + state => ({ + isLoadingAiChat: state.isLoadingAiChat, + quoteMessageContent: state.quoteMessageContent, + aiQuoteMessageContent: state.aiQuoteMessageContent, + }), + shallow, + ); +}; + +/** + * 操作方法 selector(这些方法引用稳定,不需要 shallow) + */ +export const useWeChatActions = () => { + return useWeChatStore(state => ({ + addMessage: state.addMessage, + updateMessage: state.updateMessage, + recallMessage: state.recallMessage, + loadChatMessages: state.loadChatMessages, + updateShowCheckbox: state.updateShowCheckbox, + updateEnterModule: state.updateEnterModule, + updateQuoteMessageContent: state.updateQuoteMessageContent, + updateIsLoadingAiChat: state.updateIsLoadingAiChat, + updateSelectedChatRecords: state.updateSelectedChatRecords, + updateShowChatRecordModel: state.updateShowChatRecordModel, + setCurrentContact: state.setCurrentContact, + })); +}; +``` + +#### 步骤 1.1.2:更新 MessageRecord 组件 + +**文件**: `src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx` + +**修改前**: + +```typescript +const currentMessages = useWeChatStore(state => state.currentMessages); +const currentMessagesHasMore = useWeChatStore( + state => state.currentMessagesHasMore, +); +const loadChatMessages = useWeChatStore(state => state.loadChatMessages); +const messagesLoading = useWeChatStore(state => state.messagesLoading); +const isLoadingData = useWeChatStore(state => state.isLoadingData); +const showCheckbox = useWeChatStore(state => state.showCheckbox); +// ... 更多 selector +``` + +**修改后**: + +```typescript +import { + useMessageSelectors, + useUIStateSelectors, +} from "@/hooks/weChat/useWeChatSelectors"; +import { useWeChatActions } from "@/hooks/weChat/useWeChatSelectors"; + +const MessageRecord: React.FC = ({ contract }) => { + // 合并 selector + const { + currentMessages, + currentMessagesHasMore, + messagesLoading, + isLoadingData, + } = useMessageSelectors(); + + const { showCheckbox } = useUIStateSelectors(); + + const { + loadChatMessages, + updateShowCheckbox, + updateEnterModule, + updateSelectedChatRecords, + } = useWeChatActions(); + + // ... 其他代码 +}; +``` + +**测试要点**: + +- [ ] 确认组件功能正常 +- [ ] 使用 React DevTools 确认重渲染次数减少 +- [ ] 使用 Sentry Profiler 监控渲染性能 +- [ ] 测试消息列表滚动性能 + +**性能监控**: + +```typescript +import { Profiler } from "@sentry/react"; +import { addPerformanceBreadcrumb } from "@/utils/sentry"; + +// 在 MessageRecord 组件外包装 Profiler + { + addPerformanceBreadcrumb("MessageRecord 渲染性能", { + phase, + duration: actualDuration, + optimization: "selector-optimized", + }); + }} +> + + +``` + +#### 步骤 1.1.3:更新 MessageEnter 组件 + +**文件**: `src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx` + +**修改前**: + +```typescript +const EnterModule = useWeChatStore(state => state.EnterModule); +const updateShowCheckbox = useWeChatStore(state => state.updateShowCheckbox); +const updateEnterModule = useWeChatStore(state => state.updateEnterModule); +// ... 12个独立的 selector +``` + +**修改后**: + +```typescript +import { + useUIStateSelectors, + useAISelectors, +} from "@/hooks/weChat/useWeChatSelectors"; +import { useWeChatActions } from "@/hooks/weChat/useWeChatSelectors"; + +const MessageEnter: React.FC = ({ contract }) => { + // 合并 selector + const { EnterModule, showChatRecordModel } = useUIStateSelectors(); + const { isLoadingAiChat, quoteMessageContent, aiQuoteMessageContent } = + useAISelectors(); + + const { + updateShowCheckbox, + updateEnterModule, + addMessage, + updateQuoteMessageContent, + updateIsLoadingAiChat, + updateShowChatRecordModel, + } = useWeChatActions(); + + // ... 其他代码 +}; +``` + +**测试要点**: + +- [ ] 确认输入框功能正常 +- [ ] 确认 AI 功能正常 +- [ ] 确认文件上传功能正常 +- [ ] 使用 React DevTools 确认重渲染次数减少 + +#### 步骤 1.1.4:更新 ChatWindow 组件 + +**文件**: `src/pages/pc/ckbox/weChat/components/ChatWindow/index.tsx` + +**修改**: + +```typescript +import { + useAISelectors, + useUIStateSelectors, +} from "@/hooks/weChat/useWeChatSelectors"; +import { useWeChatActions } from "@/hooks/weChat/useWeChatSelectors"; + +const ChatWindow: React.FC = ({ contract }) => { + const { aiQuoteMessageContent, showChatRecordModel } = useAISelectors(); + const { updateAiQuoteMessageContent, setCurrentContact } = useWeChatActions(); + + // ... 其他代码 +}; +``` + +**验收标准**: + +- [ ] 所有组件功能正常 +- [ ] React DevTools 显示重渲染次数减少 50%+ +- [ ] 无 TypeScript 错误 +- [ ] 无 ESLint 错误 + +--- + +### 任务 1.2:MessageRecord 组件拆分(优先级:🔥🔥🔥) + +**预计时间**: 1天 +**影响范围**: MessageRecord 组件及其子组件 + +> **同时使用 TanStack Query**:在拆分过程中,将消息数据获取迁移到 TanStack Query,实现数据缓存和自动刷新 +> **同时使用 Sentry**:监控组件拆分后的性能变化 + +#### 步骤 1.2.1:提取消息解析 Hook + +**文件**: `src/hooks/weChat/useMessageParser.ts` + +```typescript +import { useCallback, useMemo } from "react"; +import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; +import { getEmojiPath } from "@/components/EmojiSeclection/wechatEmoji"; +import AudioMessage from "@/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/AudioMessage/AudioMessage"; +import SmallProgramMessage from "@/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/SmallProgramMessage"; +import VideoMessage from "@/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/VideoMessage"; +import LocationMessage from "@/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/LocationMessage"; +import SystemRecommendRemarkMessage from "@/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/SystemRecommendRemarkMessage"; +import RedPacketMessage from "@/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/RedPacketMessage"; +import TransferMessage from "@/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/TransferMessage"; +import styles from "@/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/com.module.scss"; + +const IMAGE_EXT_REGEX = /\.(jpg|jpeg|png|gif|webp|bmp|svg)$/i; +const FILE_EXT_REGEX = /\.(pdf|doc|docx|xls|xlsx|ppt|pptx|txt|zip|rar|7z)$/i; + +/** + * 消息解析 Hook + * 提取 parseMessageContent 逻辑,使用 useCallback 优化 + */ +export const useMessageParser = ( + contract: ContractData | weChatGroup, +) => { + // 解析表情文本 + const parseEmojiText = useCallback((text: string): React.ReactNode[] => { + const emojiRegex = /\[([^\]]+)\]/g; + const parts: React.ReactNode[] = []; + let lastIndex = 0; + let match; + + while ((match = emojiRegex.exec(text)) !== null) { + if (match.index > lastIndex) { + parts.push(text.slice(lastIndex, match.index)); + } + + const emojiName = match[1]; + const emojiPath = getEmojiPath(emojiName as any); + + if (emojiPath) { + parts.push( + {emojiName}, + ); + } else { + parts.push(match[0]); + } + + lastIndex = emojiRegex.lastIndex; + } + + if (lastIndex < text.length) { + parts.push(text.slice(lastIndex)); + } + + return parts; + }, []); + + // 解析消息内容 + const parseMessageContent = useCallback( + ( + content: string | null | undefined, + msg: ChatRecord, + msgType?: number, + ): React.ReactNode => { + if (content === null || content === undefined) { + return
消息内容不可用
; + } + + const isStringValue = typeof content === "string"; + const rawContent = isStringValue ? content : ""; + const trimmedContent = rawContent.trim(); + + switch (msgType) { + case 1: // 文本消息 + return ( +
+
+ {parseEmojiText(rawContent)} +
+
+ ); + + case 3: // 图片消息 + if (!isStringValue || !trimmedContent) { + return
[图片消息 - 无效链接]
; + } + return ( +
+ 图片消息 window.open(rawContent, "_blank")} + /> +
+ ); + + case 34: // 语音消息 + return ; + + case 43: // 视频消息 + return ( + + ); + + case 47: // 动图表情包 + return ( +
+ 表情包 +
+ ); + + case 48: // 定位消息 + return ; + + case 49: // 小程序/文件 + return ( + + ); + + case 10002: // 系统推荐备注消息 + return ( + + ); + + default: { + // 处理 JSON 格式的复杂消息 + try { + const jsonData = JSON.parse(trimmedContent); + if (jsonData && typeof jsonData === "object") { + // 红包消息 + if ( + jsonData.nativeurl && + jsonData.nativeurl.includes("wxpay://c2cbizmessagehandler/hongbao/receivehongbao") + ) { + return ( + + ); + } + + // 转账消息 + if ( + jsonData.title === "微信转账" || + (jsonData.transferid && jsonData.feedesc) + ) { + return ( + + ); + } + } + } catch (e) { + // 不是 JSON,继续处理 + } + + // 默认文本消息 + return ( +
+
+ {parseEmojiText(rawContent)} +
+
+ ); + } + } + }, + [contract, parseEmojiText], + ); + + return { + parseMessageContent, + parseEmojiText, + }; +}; +``` + +#### 步骤 1.2.2:提取消息分组 Hook + +**文件**: `src/hooks/weChat/useMessageGrouping.ts` + +```typescript +import { useMemo } from "react"; +import { ChatRecord } from "@/pages/pc/ckbox/data"; +import { formatWechatTime } from "@/utils/common"; + +export interface MessageGroup { + time: string; + messages: ChatRecord[]; +} + +/** + * 消息分组 Hook + * 使用 useMemo 缓存分组结果 + */ +export const useMessageGrouping = ( + messages: ChatRecord[] | null | undefined, +): MessageGroup[] => { + return useMemo(() => { + const safeMessages = Array.isArray(messages) + ? messages + : Array.isArray((messages as any)?.list) + ? ((messages as any).list as ChatRecord[]) + : []; + + return safeMessages + .filter(msg => msg !== null && msg !== undefined) + .map(msg => ({ + time: formatWechatTime(String(msg?.wechatTime)), + messages: [msg], + })); + }, [messages]); +}; +``` + +#### 步骤 1.2.3:提取右键菜单组件 + +**文件**: `src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/MessageContextMenu/index.tsx` + +```typescript +import React, { useCallback, useState } from "react"; +import { ChatRecord } from "@/pages/pc/ckbox/data"; +import ClickMenu from "../ClickMeau"; +import { useWeChatActions } from "@/hooks/weChat/useWeChatSelectors"; +import { useContactStore } from "@/store/module/weChat/contacts"; +import { fetchReCallApi, fetchVoiceToTextApi } from "../../api"; + +interface MessageContextMenuProps { + onQuote?: (messageData: ChatRecord) => void; +} + +export const MessageContextMenu: React.FC = ({ + onQuote, +}) => { + const [contextMenu, setContextMenu] = useState({ + visible: false, + x: 0, + y: 0, + messageData: null as ChatRecord | null, + }); + const [nowIsOwn, setNowIsOwn] = useState(false); + + const { + updateEnterModule, + updateShowCheckbox, + updateSelectedChatRecords, + updateQuoteMessageContent, + } = useWeChatActions(); + const { showCheckbox } = useUIStateSelectors(); + const setTransmitModal = useContactStore(state => state.setTransmitModal); + + const handleContextMenu = useCallback( + (e: React.MouseEvent, msg: ChatRecord, isOwn: boolean) => { + e.preventDefault(); + setContextMenu({ + visible: true, + x: e.clientX, + y: e.clientY, + messageData: msg, + }); + setNowIsOwn(isOwn); + }, + [], + ); + + const handleCloseContextMenu = useCallback(() => { + setContextMenu({ + visible: false, + x: 0, + y: 0, + messageData: null, + }); + }, []); + + const handleForwardMessage = useCallback( + (messageData: ChatRecord) => { + updateSelectedChatRecords([messageData]); + setTransmitModal(true); + }, + [updateSelectedChatRecords, setTransmitModal], + ); + + const handRecall = useCallback((messageData: ChatRecord) => { + fetchReCallApi({ + friendMessageId: messageData?.wechatFriendId ? messageData.id : 0, + chatroomMessageId: messageData?.wechatFriendId ? 0 : messageData.id, + seq: +new Date(), + }); + }, []); + + const handVoiceToText = useCallback((messageData: ChatRecord) => { + fetchVoiceToTextApi({ + friendMessageId: messageData?.wechatFriendId ? messageData.id : 0, + chatroomMessageId: messageData?.wechatFriendId ? 0 : messageData.id, + seq: +new Date(), + }); + }, []); + + const handCommad = useCallback( + (action: string) => { + if (!contextMenu.messageData) return; + + switch (action) { + case "transmit": + handleForwardMessage(contextMenu.messageData); + break; + case "multipleForwarding": + updateEnterModule(!showCheckbox ? "multipleForwarding" : "common"); + updateShowCheckbox(!showCheckbox); + break; + case "quote": + onQuote?.(contextMenu.messageData); + break; + case "recall": + handRecall(contextMenu.messageData); + break; + case "voiceToText": + handVoiceToText(contextMenu.messageData); + break; + default: + break; + } + handleCloseContextMenu(); + }, + [ + contextMenu.messageData, + showCheckbox, + updateEnterModule, + updateShowCheckbox, + handleForwardMessage, + handRecall, + handVoiceToText, + onQuote, + handleCloseContextMenu, + ], + ); + + return { + handleContextMenu, + contextMenu: { + ...contextMenu, + isOwn: nowIsOwn, + onClose: handleCloseContextMenu, + onCommad: handCommad, + }, + }; +}; +``` + +#### 步骤 1.2.4:重构 MessageRecord 主组件(整合 TanStack Query) + +**文件**: `src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx` + +**重构后结构**(整合 TanStack Query 和 Sentry): + +```typescript +import React, { useRef, useEffect, useCallback, useMemo } from "react"; +import { LoadingOutlined } from "@ant-design/icons"; +import { Profiler } from "@sentry/react"; +import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; +import { useMessageSelectors, useUIStateSelectors } from "@/hooks/weChat/useWeChatSelectors"; +import { useWeChatActions } from "@/hooks/weChat/useWeChatSelectors"; +import { useMessageParser } from "@/hooks/weChat/useMessageParser"; +import { useMessageGrouping } from "@/hooks/weChat/useMessageGrouping"; +import { useChatMessages } from "@/hooks/weChat/useChatMessages"; // 使用 TanStack Query +import { MessageContextMenu } from "./components/MessageContextMenu"; +import { MessageItem } from "./components/MessageItem"; +import { parseSystemMessage } from "@/utils/filter"; +import { addPerformanceBreadcrumb } from "@/utils/sentry"; +import styles from "./com.module.scss"; + +interface MessageRecordProps { + contract: ContractData | weChatGroup; +} + +const MessageRecord: React.FC = React.memo( + ({ contract }) => { + const messagesEndRef = useRef(null); + const messagesContainerRef = useRef(null); + const [selectedRecords, setSelectedRecords] = React.useState([]); + + // ✅ 使用 TanStack Query 获取消息数据(自动缓存) + const { + data: messagesData, + isLoading: messagesLoading, + isFetching: isFetchingMessages, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + } = useChatMessages(contract); + + // 扁平化分页数据 + const currentMessages = useMemo(() => { + if (!messagesData?.pages) return []; + return messagesData.pages.flatMap(page => page.list || []); + }, [messagesData]); + + // 使用优化的 selector(UI 状态) + const { showCheckbox } = useUIStateSelectors(); + const { updateSelectedChatRecords } = useWeChatActions(); + + // 使用提取的 Hook + const { parseMessageContent } = useMessageParser(contract); + const groupedMessages = useMessageGrouping(currentMessages); + const { handleContextMenu, contextMenu } = MessageContextMenu({ + onQuote: handleQuote, + }); + + // 加载更多消息 + const loadMoreMessages = useCallback(() => { + if (hasNextPage && !isFetchingNextPage) { + addPerformanceBreadcrumb("加载更多消息", { + contactId: contract.id, + currentCount: currentMessages.length, + }); + fetchNextPage(); + } + }, [hasNextPage, isFetchingNextPage, fetchNextPage, contract.id, currentMessages.length]); + + // 性能监控:记录消息数量变化 + useEffect(() => { + addPerformanceBreadcrumb("消息列表更新", { + contactId: contract.id, + messageCount: currentMessages.length, + isLoading: messagesLoading, + }); + }, [currentMessages.length, contract.id, messagesLoading]); + + return ( + { + if (actualDuration > 100) { + addPerformanceBreadcrumb("MessageRecord 慢渲染", { + duration: actualDuration, + phase, + messageCount: currentMessages.length, + contractId: contract.id, + }); + } + }} + > +
+ {/* 加载更多按钮 */} + {hasNextPage && ( +
+ {isFetchingNextPage ? ( + + ) : ( + "点击加载更早的信息" + )} +
+ )} + + {/* 消息列表渲染 */} + {groupedMessages.map((group, groupIndex) => ( + + {/* 消息渲染逻辑 */} + + ))} + +
+
+ + ); + }, + (prev, next) => prev.contract.id === next.contract.id, +); + +MessageRecord.displayName = "MessageRecord"; + +export default MessageRecord; +``` + +**验收标准**: + +- [ ] MessageRecord 组件代码行数 < 300 行 +- [ ] 所有功能正常 +- [ ] 无 TypeScript 错误 +- [ ] 性能测试通过 +- [ ] TanStack Query 缓存正常工作(切换联系人时立即显示缓存数据) +- [ ] Sentry 性能数据正常记录 +- [ ] 重复请求减少(通过 React Query DevTools 验证) + +--- + +### 任务 1.3:MessageEnter 组件拆分(优先级:🔥🔥🔥) + +**预计时间**: 1天 +**影响范围**: MessageEnter 组件 + +> **同时使用 TanStack Query**:消息发送使用 TanStack Query Mutation,实现乐观更新 +> **同时使用 Sentry**:监控消息发送性能和错误 + +#### 步骤 1.3.1:提取 AI 加载指示器组件 + +**文件**: `src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/AiLoadingIndicator.tsx` + +```typescript +import React from "react"; +import { Button } from "antd"; +import { CloseOutlined } from "@ant-design/icons"; +import styles from "../MessageEnter.module.scss"; + +interface AiLoadingIndicatorProps { + onCancel: () => void; +} + +export const AiLoadingIndicator: React.FC = React.memo( + ({ onCancel }) => { + return ( +
+
+
+
+
+
+
+
+
🧠
+
+
+
+
+
+
+
+
+ AI 正在思考 + + . + . + . + +
+ +
+
+ 正在分析消息内容,为您生成智能回复 +
+
+
+ ); + }, +); + +AiLoadingIndicator.displayName = "AiLoadingIndicator"; +``` + +#### 步骤 1.3.2:提取消息发送 Hook(使用 TanStack Query + Sentry) + +**文件**: `src/hooks/weChat/useMessageSend.ts` + +```typescript +import { useCallback, useRef } from "react"; +import { message } from "antd"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { ContractData, weChatGroup, ChatRecord } from "@/pages/pc/ckbox/data"; +import { useWebSocketStore } from "@/store/module/websocket/websocket"; +import { useWeChatActions } from "./useWeChatSelectors"; +import { clearAiRequestQueue } from "@/store/module/weChat/weChat"; +import { captureError, addPerformanceBreadcrumb } from "@/utils/sentry"; + +interface UseMessageSendProps { + contract: ContractData | weChatGroup; + isLoadingAiChat: boolean; + updateIsLoadingAiChat: (loading: boolean) => void; + updateQuoteMessageContent: (content: string) => void; +} + +/** + * 消息发送 Hook + * 使用 TanStack Query Mutation 实现乐观更新和错误处理 + * 使用 Sentry 监控发送性能和错误 + */ +export const useMessageSend = ({ + contract, + isLoadingAiChat, + updateIsLoadingAiChat, + updateQuoteMessageContent, +}: UseMessageSendProps) => { + const queryClient = useQueryClient(); + const { sendCommand } = useWebSocketStore.getState(); + const contractRef = useRef(contract); + contractRef.current = contract; + + // ✅ 使用 TanStack Query Mutation + const sendMessageMutation = useMutation({ + mutationFn: async (content: string) => { + const startTime = performance.now(); + const messageId = +Date.now(); + const currentContract = contractRef.current; + + const params = { + wechatAccountId: currentContract.wechatAccountId, + wechatChatroomId: currentContract?.chatroomId ? currentContract.id : 0, + wechatFriendId: currentContract?.chatroomId ? 0 : currentContract.id, + msgSubType: 0, + msgType: 1, + content, + seq: messageId, + }; + + sendCommand("CmdSendMessage", params); + + const duration = performance.now() - startTime; + addPerformanceBreadcrumb("消息发送", { + duration, + contactId: currentContract.id, + messageLength: content.length, + }); + + return { messageId, content, params }; + }, + // ✅ 乐观更新:立即更新 UI + onMutate: async content => { + // 取消正在进行的查询 + await queryClient.cancelQueries({ + queryKey: ["chatMessages", contract.id], + }); + + // 保存当前数据快照 + const previousMessages = queryClient.getQueryData([ + "chatMessages", + contract.id, + ]); + + // 乐观更新:立即添加消息 + const messageId = +Date.now(); + const newMessage: ChatRecord = { + id: messageId, + wechatAccountId: contract.wechatAccountId, + wechatFriendId: contract?.chatroomId ? 0 : contract.id, + wechatChatroomId: contract?.chatroomId ? contract.id : 0, + tenantId: 0, + accountId: 0, + synergyAccountId: 0, + content, + msgType: 1, + msgSubType: 0, + msgSvrId: "", + isSend: true, + createTime: new Date().toISOString(), + isDeleted: false, + deleteTime: "", + sendStatus: 1, // 发送中 + wechatTime: Date.now(), + origin: 0, + msgId: 0, + recalled: false, + seq: messageId, + }; + + queryClient.setQueryData(["chatMessages", contract.id], (old: any) => { + if (!old?.pages) return old; + return { + ...old, + pages: [ + { + ...old.pages[0], + list: [newMessage, ...(old.pages[0]?.list || [])], + }, + ...old.pages.slice(1), + ], + }; + }); + + return { previousMessages }; + }, + // ✅ 错误处理:回滚乐观更新并上报错误 + onError: (error, variables, context) => { + captureError(error as Error, { + tags: { action: "sendMessage" }, + extra: { + contactId: contract.id, + content: variables, + }, + }); + + // 回滚乐观更新 + if (context?.previousMessages) { + queryClient.setQueryData( + ["chatMessages", contract.id], + context.previousMessages, + ); + } + + message.error("消息发送失败,请重试"); + }, + // ✅ 成功后使缓存失效,重新获取最新数据 + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ["chatMessages", contract.id], + }); + }, + }); + + const handleSend = useCallback( + async (content?: string) => { + const messageContent = content || ""; + + if (!messageContent || !messageContent.trim()) { + console.warn("消息内容为空,取消发送"); + return; + } + + // 用户主动发送消息时,取消AI请求 + if (!content && isLoadingAiChat) { + console.log("👤 用户主动发送消息,取消AI生成"); + clearAiRequestQueue("用户主动发送"); + updateIsLoadingAiChat(false); + } + + // ✅ 使用 TanStack Query Mutation + sendMessageMutation.mutate(messageContent, { + onSuccess: () => { + updateQuoteMessageContent(""); + }, + }); + }, + [ + isLoadingAiChat, + updateIsLoadingAiChat, + updateQuoteMessageContent, + sendMessageMutation, + ], + ); + + return { + handleSend, + isSending: sendMessageMutation.isLoading, + }; +}; +``` + +#### 步骤 1.3.3:提取文件上传 Hook + +**文件**: `src/hooks/weChat/useFileUpload.ts` + +```typescript +import { useCallback, useRef } from "react"; +import { ContractData, weChatGroup, ChatRecord } from "@/pages/pc/ckbox/data"; +import { useWebSocketStore } from "@/store/module/websocket/websocket"; +import { useWeChatActions } from "./useWeChatSelectors"; + +const FileType = { + TEXT: 1, + IMAGE: 2, + VIDEO: 3, + AUDIO: 4, + FILE: 5, +}; + +const IMAGE_FORMATS = [ + "jpg", + "jpeg", + "png", + "gif", + "bmp", + "webp", + "svg", + "ico", +]; +const VIDEO_FORMATS = [ + "mp4", + "avi", + "mov", + "wmv", + "flv", + "mkv", + "webm", + "3gp", + "rmvb", +]; + +const getMsgTypeByFileFormat = (filePath: string): number => { + const extension = filePath.toLowerCase().split(".").pop() || ""; + if (IMAGE_FORMATS.includes(extension)) return 3; + if (VIDEO_FORMATS.includes(extension)) return 43; + return 49; +}; + +interface UseFileUploadProps { + contract: ContractData | weChatGroup; +} + +export const useFileUpload = ({ contract }: UseFileUploadProps) => { + const { addMessage } = useWeChatActions(); + const { sendCommand } = useWebSocketStore.getState(); + const contractRef = useRef(contract); + contractRef.current = contract; + + const handleFileUploaded = useCallback( + ( + filePath: { url: string; name: string; durationMs?: number }, + fileType: number, + ) => { + const currentContract = contractRef.current; + let msgType = 1; + let content: any = ""; + + if ([FileType.TEXT].includes(fileType)) { + msgType = getMsgTypeByFileFormat(filePath.url); + } else if ([FileType.IMAGE].includes(fileType)) { + msgType = 3; + content = filePath.url; + } else if ([FileType.AUDIO].includes(fileType)) { + msgType = 34; + content = JSON.stringify({ + url: filePath.url, + durationMs: filePath.durationMs, + }); + } else if ([FileType.FILE].includes(fileType)) { + msgType = getMsgTypeByFileFormat(filePath.url); + if (msgType === 3) { + content = filePath.url; + } + if (msgType === 43) { + content = filePath.url; + } + if (msgType === 49) { + content = JSON.stringify({ + type: "file", + title: filePath.name, + url: filePath.url, + }); + } + } + + const messageId = +Date.now(); + const params = { + wechatAccountId: currentContract.wechatAccountId, + wechatChatroomId: currentContract?.chatroomId ? currentContract.id : 0, + wechatFriendId: currentContract?.chatroomId ? 0 : currentContract.id, + msgSubType: 0, + msgType, + content: content, + seq: messageId, + }; + + const localMessage: ChatRecord = { + id: messageId, + wechatAccountId: currentContract.wechatAccountId, + wechatFriendId: currentContract?.chatroomId ? 0 : currentContract.id, + wechatChatroomId: currentContract?.chatroomId ? currentContract.id : 0, + tenantId: 0, + accountId: 0, + synergyAccountId: 0, + content: params.content, + msgType: msgType, + msgSubType: 0, + msgSvrId: "", + isSend: true, + createTime: new Date().toISOString(), + isDeleted: false, + deleteTime: "", + sendStatus: 1, + wechatTime: Date.now(), + origin: 0, + msgId: 0, + recalled: false, + seq: messageId, + }; + + addMessage(localMessage); + sendCommand("CmdSendMessage", params); + }, + [addMessage], + ); + + return { + handleFileUploaded, + }; +}; +``` + +#### 步骤 1.3.4:重构 MessageEnter 主组件 + +**文件**: `src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx` + +**重构后结构**(简化版): + +```typescript +import React, { useState, useEffect, useCallback } from "react"; +import { Layout, Button } from "antd"; +import { SendOutlined } from "@ant-design/icons"; +import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; +import { useUIStateSelectors, useAISelectors } from "@/hooks/weChat/useWeChatSelectors"; +import { useWeChatActions } from "@/hooks/weChat/useWeChatSelectors"; +import { useMessageSend } from "@/hooks/weChat/useMessageSend"; +import { useFileUpload } from "@/hooks/weChat/useFileUpload"; +import { AiLoadingIndicator } from "./components/AiLoadingIndicator"; +import { InputToolbar } from "./components/InputToolbar"; +import styles from "./MessageEnter.module.scss"; + +const { Footer } = Layout; + +interface MessageEnterProps { + contract: ContractData | weChatGroup; +} + +const MessageEnter: React.FC = React.memo( + ({ contract }) => { + const [inputValue, setInputValue] = useState(""); + const { EnterModule } = useUIStateSelectors(); + const { isLoadingAiChat, quoteMessageContent, aiQuoteMessageContent } = useAISelectors(); + const { + updateIsLoadingAiChat, + updateQuoteMessageContent, + } = useWeChatActions(); + + const { handleSend } = useMessageSend({ + contract, + isLoadingAiChat, + updateIsLoadingAiChat, + updateQuoteMessageContent, + }); + + const { handleFileUploaded } = useFileUpload({ contract }); + + // ... 其他逻辑 + + return ( +
+ {isLoadingAiChat ? ( + + ) : ( + // 输入区域 + )} +
+ ); + }, + (prev, next) => prev.contract.id === next.contract.id, +); + +MessageEnter.displayName = "MessageEnter"; + +export default MessageEnter; +``` + +**验收标准**: + +- [ ] MessageEnter 组件代码行数 < 300 行 +- [ ] 所有功能正常 +- [ ] 无 TypeScript 错误 +- [ ] 性能测试通过 +- [ ] 消息发送使用乐观更新(立即显示在列表中) +- [ ] Sentry 记录发送性能和错误 +- [ ] 发送失败时自动回滚 + +--- + +### 任务 1.4:添加 React.memo 优化(优先级:🔥🔥) + +**预计时间**: 2小时 + +#### 步骤 1.4.1:包装 ChatWindow 组件 + +**文件**: `src/pages/pc/ckbox/weChat/components/ChatWindow/index.tsx` + +```typescript +const ChatWindow: React.FC = React.memo( + ({ contract }) => { + // ... 现有代码 + }, + (prev, next) => { + // 只有 contract.id 变化时才重新渲染 + return prev.contract.id === next.contract.id; + }, +); + +ChatWindow.displayName = "ChatWindow"; +``` + +#### 步骤 1.4.2:检查 MessageItem 的 memo 比较函数 + +**文件**: `src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx` + +**当前代码**(第302-309行): + +```typescript +(prev, next) => + prev.msg === next.msg && + prev.isGroup === next.isGroup && + prev.showCheckbox === next.showCheckbox && + prev.isSelected === next.isSelected && + prev.currentCustomerAvatar === next.currentCustomerAvatar && + prev.contract === next.contract, +``` + +**优化建议**: + +```typescript +(prev, next) => { + // 使用深度比较或 ID 比较 + return ( + prev.msg.id === next.msg.id && + prev.msg.content === next.msg.content && + prev.msg.sendStatus === next.msg.sendStatus && + prev.isGroup === next.isGroup && + prev.showCheckbox === next.showCheckbox && + prev.isSelected === next.isSelected && + prev.currentCustomerAvatar === next.currentCustomerAvatar && + prev.contract.id === next.contract.id + ); +}; +``` + +**验收标准**: + +- [ ] 所有主要组件都使用 React.memo +- [ ] 比较函数正确 +- [ ] 功能正常 + +--- + +## 第二阶段:性能优化(第2周) + +### 任务 2.0:全面迁移到 TanStack Query(优先级:🔥🔥🔥) + +**预计时间**: 2-3天(部分已在任务 1.2 和 1.3 中完成) +**影响范围**: 所有 API 数据获取 + +> **说明**:在任务 1.2 和 1.3 中已经部分使用了 TanStack Query,本任务完成剩余的数据获取迁移 +> **同时使用 Sentry**:监控 API 请求性能和错误 + +#### 步骤 2.0.1:配置 QueryClient + +**文件**: `src/providers/QueryProvider.tsx` + +```typescript +import React from "react"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; + +// 创建 QueryClient 实例 +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + // 数据在 5 分钟内视为新鲜,不会重新请求 + staleTime: 5 * 60 * 1000, + // 缓存时间 10 分钟 + cacheTime: 10 * 60 * 1000, + // 失败重试 1 次 + retry: 1, + // 窗口聚焦时自动刷新 + refetchOnWindowFocus: true, + // 网络重连时自动刷新 + refetchOnReconnect: true, + }, + mutations: { + // 失败重试 1 次 + retry: 1, + }, + }, +}); + +interface QueryProviderProps { + children: React.ReactNode; +} + +export const QueryProvider: React.FC = ({ children }) => { + return ( + + {children} + {/* 开发环境显示 React Query DevTools */} + {import.meta.env.MODE === "development" && ( + + )} + + ); +}; +``` + +#### 步骤 2.0.2:在应用入口使用 QueryProvider + +**文件**: `src/main.tsx` 或 `src/App.tsx` + +```typescript +import { QueryProvider } from "@/providers/QueryProvider"; + +function App() { + return ( + + {/* 应用内容 */} + + ); +} +``` + +#### 步骤 2.0.3:创建消息列表 Hook + +**文件**: `src/hooks/weChat/useChatMessages.ts` + +```typescript +import { useInfiniteQuery } from "@tanstack/react-query"; +import { getChatMessages, getChatroomMessages } from "@/pages/pc/ckbox/api"; +import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; +import { captureError, addPerformanceBreadcrumb } from "@/utils/sentry"; + +const DEFAULT_MESSAGE_PAGE_SIZE = 20; + +/** + * 消息列表 Hook + * 使用 TanStack Query 管理消息数据,自动缓存和分页 + */ +export const useChatMessages = (contact: ContractData | weChatGroup | null) => { + return useInfiniteQuery({ + queryKey: ["chatMessages", contact?.id, contact?.wechatAccountId], + queryFn: async ({ pageParam = 1 }) => { + if (!contact) { + throw new Error("联系人信息缺失"); + } + + const startTime = performance.now(); + const params: any = { + wechatAccountId: contact.wechatAccountId, + page: pageParam, + limit: DEFAULT_MESSAGE_PAGE_SIZE, + }; + + const isGroup = "chatroomId" in contact && Boolean(contact.chatroomId); + + try { + const response = isGroup + ? await getChatroomMessages(params) + : await getChatMessages(params); + + const duration = performance.now() - startTime; + + // ✅ 使用 Sentry 记录请求性能 + addPerformanceBreadcrumb("获取消息列表", { + duration, + contactId: contact.id, + page: pageParam, + messageCount: response?.list?.length || 0, + isGroup, + }); + + // 如果请求时间超过 1 秒,记录警告 + if (duration > 1000) { + addPerformanceBreadcrumb("慢请求警告", { + duration, + contactId: contact.id, + threshold: 1000, + }); + } + + return response; + } catch (error) { + // ✅ 使用 Sentry 捕获错误 + captureError(error as Error, { + tags: { + action: "getChatMessages", + isGroup: String(isGroup), + }, + extra: { + contactId: contact.id, + page: pageParam, + params, + }, + }); + throw error; + } + }, + getNextPageParam: (lastPage, allPages) => { + const hasMore = + lastPage?.hasNext || + lastPage?.hasNextPage || + (lastPage?.list?.length || 0) >= DEFAULT_MESSAGE_PAGE_SIZE; + return hasMore ? allPages.length + 1 : undefined; + }, + enabled: !!contact, + staleTime: 5 * 60 * 1000, + cacheTime: 10 * 60 * 1000, + // ✅ 使用 Sentry 监控查询状态变化 + onError: error => { + captureError(error as Error, { + tags: { query: "useChatMessages" }, + extra: { contactId: contact?.id }, + }); + }, + }); +}; +``` + +#### 步骤 2.0.4:创建联系人列表 Hook + +**文件**: `src/hooks/weChat/useContacts.ts` + +```typescript +import { useQuery } from "@tanstack/react-query"; +import { getContactList } from "@/pages/pc/ckbox/api"; + +/** + * 联系人列表 Hook + * 使用 TanStack Query 管理联系人数据 + */ +export const useContacts = () => { + return useQuery({ + queryKey: ["contacts"], + queryFn: getContactList, + staleTime: 10 * 60 * 1000, // 10 分钟缓存 + cacheTime: 30 * 60 * 1000, // 30 分钟缓存 + refetchOnWindowFocus: true, // 窗口聚焦时刷新 + }); +}; +``` + +#### 步骤 2.0.5:迁移 MessageRecord 组件 + +**文件**: `src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx` + +**修改前**: + +```typescript +const { currentMessages, messagesLoading } = useMessageSelectors(); +const { loadChatMessages } = useWeChatActions(); + +// 手动调用加载 +useEffect(() => { + loadChatMessages(true); +}, [contact.id]); +``` + +**修改后**: + +```typescript +import { useChatMessages } from "@/hooks/weChat/useChatMessages"; + +const MessageRecord: React.FC = ({ contract }) => { + // 使用 TanStack Query 管理消息数据 + const { + data: messagesData, + isLoading: messagesLoading, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + } = useChatMessages(contract); + + // 扁平化分页数据 + const currentMessages = useMemo(() => { + if (!messagesData?.pages) return []; + return messagesData.pages.flatMap(page => page.list || []); + }, [messagesData]); + + // 加载更多消息 + const loadMoreMessages = () => { + if (hasNextPage && !isFetchingNextPage) { + fetchNextPage(); + } + }; + + // ... 其他逻辑 +}; +``` + +#### 步骤 2.0.6:优化消息发送(乐观更新) + +**文件**: `src/hooks/weChat/useSendMessage.ts` + +```typescript +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { useWebSocketStore } from "@/store/module/websocket/websocket"; +import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; + +/** + * 消息发送 Hook(使用乐观更新) + */ +export const useSendMessage = (contact: ContractData | weChatGroup) => { + const queryClient = useQueryClient(); + const { sendCommand } = useWebSocketStore.getState(); + + return useMutation({ + mutationFn: async (content: string) => { + const messageId = +Date.now(); + const params = { + wechatAccountId: contact.wechatAccountId, + wechatChatroomId: contact?.chatroomId ? contact.id : 0, + wechatFriendId: contact?.chatroomId ? 0 : contact.id, + msgSubType: 0, + msgType: 1, + content, + seq: messageId, + }; + + sendCommand("CmdSendMessage", params); + return { messageId, content }; + }, + // 乐观更新:立即更新 UI,无需等待服务器响应 + onMutate: async content => { + // 取消正在进行的查询,避免覆盖乐观更新 + await queryClient.cancelQueries({ + queryKey: ["chatMessages", contact.id], + }); + + // 保存当前数据快照 + const previousMessages = queryClient.getQueryData([ + "chatMessages", + contact.id, + ]); + + // 乐观更新:立即添加消息到列表 + queryClient.setQueryData(["chatMessages", contact.id], (old: any) => { + const newMessage: ChatRecord = { + id: +Date.now(), + content, + isSend: true, + sendStatus: 1, // 发送中 + // ... 其他字段 + }; + return { + ...old, + pages: [ + { + ...old.pages[0], + list: [newMessage, ...(old.pages[0]?.list || [])], + }, + ...old.pages.slice(1), + ], + }; + }); + + return { previousMessages }; + }, + // 如果失败,回滚乐观更新 + onError: (err, variables, context) => { + if (context?.previousMessages) { + queryClient.setQueryData( + ["chatMessages", contact.id], + context.previousMessages, + ); + } + }, + // 成功后,使缓存失效,重新获取最新数据 + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ["chatMessages", contact.id], + }); + }, + }); +}; +``` + +#### 步骤 2.0.7:更新 MessageEnter 组件使用新 Hook + +**文件**: `src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx` + +```typescript +import { useSendMessage } from "@/hooks/weChat/useSendMessage"; + +const MessageEnter: React.FC = ({ contract }) => { + const { mutate: sendMessage, isLoading: isSending } = + useSendMessage(contract); + + const handleSend = useCallback( + async (content?: string) => { + const messageContent = content || inputValue; + if (!messageContent?.trim()) return; + + sendMessage(messageContent, { + onSuccess: () => { + setInputValue(""); + }, + onError: error => { + message.error("消息发送失败"); + // Sentry 会自动捕获错误 + }, + }); + }, + [inputValue, sendMessage], + ); + + // ... 其他逻辑 +}; +``` + +**验收标准**: + +- [ ] TanStack Query 配置正确 +- [ ] 消息列表使用新 Hook +- [ ] 联系人列表使用新 Hook +- [ ] 重复请求减少 50%+(通过 React Query DevTools 验证) +- [ ] 缓存功能正常(切换联系人时立即显示缓存) +- [ ] 乐观更新正常工作 +- [ ] Sentry 性能数据正常记录 +- [ ] 功能正常,无回归 + +**预期收益**: + +- ✅ **减少 50-70% 的重复请求** +- ✅ **代码减少 30-40%** +- ✅ **用户体验显著提升**(立即显示缓存数据) +- ✅ **更好的错误处理**(Sentry 自动捕获) +- ✅ **性能监控**(Sentry 自动追踪) + +--- + +### 任务 2.1:实现虚拟滚动(优先级:🔥🔥🔥) + +**预计时间**: 2天 +**影响范围**: MessageRecord 组件 + +> **同时使用 Sentry**:监控虚拟滚动性能,记录滚动流畅度 +> **配合 TanStack Query**:虚拟滚动配合 TanStack Query 的分页加载 + +#### 步骤 2.1.1:安装依赖(如果需要) + +```bash +# react-window 已安装,无需额外安装 +npm list react-window +``` + +#### 步骤 2.1.2:创建虚拟滚动组件 + +**文件**: `src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/VirtualizedMessageList.tsx` + +```typescript +import React, { useRef, useEffect, useCallback } from "react"; +import { FixedSizeList, ListChildComponentProps } from "react-window"; +import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; +import { MessageGroup } from "@/hooks/weChat/useMessageGrouping"; +import { MessageItem } from "./MessageItem"; +import { parseSystemMessage } from "@/utils/filter"; +import styles from "../com.module.scss"; + +interface VirtualizedMessageListProps { + groupedMessages: MessageGroup[]; + contract: ContractData | weChatGroup; + isGroupChat: boolean; + showCheckbox: boolean; + currentCustomerAvatar?: string; + renderGroupUser: (msg: ChatRecord) => { avatar: string; nickname: string }; + clearWechatidInContent: (sender: any, content: string) => string; + parseMessageContent: ( + content: string | null | undefined, + msg: ChatRecord, + msgType?: number, + ) => React.ReactNode; + isMessageSelected: (msg: ChatRecord) => boolean; + onCheckboxChange: (checked: boolean, msg: ChatRecord) => void; + onContextMenu: (e: React.MouseEvent, msg: ChatRecord, isOwn: boolean) => void; + containerRef: React.RefObject; +} + +/** + * 虚拟滚动消息列表项 + */ +const VirtualizedMessageItem: React.FC< + ListChildComponentProps<{ + groups: MessageGroup[]; + props: Omit; + }> +> = ({ index, style, data }) => { + const { groups, props } = data; + const group = groups[index]; + + return ( +
+ {/* 时间分隔符 */} + {group.messages + .filter(v => [10000, -10001].includes(v.msgType)) + .map(msg => { + const parsedText = parseSystemMessage(msg.content); + return ( +
+ {parsedText} +
+ ); + })} + + {/* 其他系统消息 */} + {group.messages + .filter(v => [570425393, 90000].includes(v.msgType)) + .map(msg => { + let displayContent = msg.content; + try { + const parsedContent = JSON.parse(msg.content); + if ( + parsedContent && + typeof parsedContent === "object" && + parsedContent.content + ) { + displayContent = parsedContent.content; + } + } catch (error) { + // 忽略解析错误 + } + return ( +
+ {displayContent} +
+ ); + })} + + {/* 时间标签 */} +
{group.time}
+ + {/* 消息项 */} + {group.messages + .filter(v => ![10000, 570425393, 90000, -10001].includes(v.msgType)) + .map(msg => { + if (!msg) return null; + return ( + + ); + })} +
+ ); +}; + +/** + * 虚拟滚动消息列表 + */ +export const VirtualizedMessageList: React.FC = ({ + groupedMessages, + containerRef, + ...props +}) => { + const listRef = useRef(null); + const [listHeight, setListHeight] = React.useState(600); + + // 监听容器高度变化 + useEffect(() => { + const updateHeight = () => { + if (containerRef.current) { + const height = containerRef.current.clientHeight; + setListHeight(height); + } + }; + + updateHeight(); + const resizeObserver = new ResizeObserver(updateHeight); + if (containerRef.current) { + resizeObserver.observe(containerRef.current); + } + + return () => { + resizeObserver.disconnect(); + }; + }, [containerRef]); + + // 滚动到底部 + const scrollToBottom = useCallback(() => { + if (listRef.current && groupedMessages.length > 0) { + listRef.current.scrollToItem(groupedMessages.length - 1, "end"); + } + }, [groupedMessages.length]); + + // 当新消息到达时,滚动到底部 + useEffect(() => { + scrollToBottom(); + }, [groupedMessages.length, scrollToBottom]); + + // 估算每个消息组的高度(可以根据实际情况调整) + const estimateItemSize = useCallback((index: number) => { + const group = groupedMessages[index]; + // 基础高度:时间标签 + 消息项 + const baseHeight = 40; // 时间标签高度 + const messageHeight = 60; // 每条消息平均高度 + return baseHeight + group.messages.length * messageHeight; + }, [groupedMessages]); + + // 使用平均高度(简化实现,实际可以使用 VariableSizeList) + const averageItemSize = useMemo(() => { + if (groupedMessages.length === 0) return 100; + const totalSize = groupedMessages.reduce( + (sum, group) => sum + 40 + group.messages.length * 60, + 0, + ); + return Math.ceil(totalSize / groupedMessages.length); + }, [groupedMessages]); + + return ( + + {VirtualizedMessageItem} + + ); +}; +``` + +#### 步骤 2.1.3:集成到 MessageRecord + +**文件**: `src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx` + +```typescript +import { VirtualizedMessageList } from "./components/VirtualizedMessageList"; + +// 在 MessageRecord 组件中使用 +const MessageRecord: React.FC = ({ contract }) => { + // ... 现有代码 + + // 如果消息数量 > 50,使用虚拟滚动 + const shouldUseVirtualization = groupedMessages.length > 50; + + return ( +
+ {shouldUseVirtualization ? ( + + ) : ( + // 原有的渲染逻辑(小列表不使用虚拟滚动) + )} +
+ ); +}; +``` + +**验收标准**: + +- [ ] 100+ 条消息滚动流畅(FPS > 30) +- [ ] 1000+ 条消息可以正常加载 +- [ ] 内存占用明显降低(< 100MB for 1000 messages) +- [ ] Sentry 记录滚动性能数据 +- [ ] 配合 TanStack Query 分页加载正常 +- [ ] 功能正常 + +**性能监控**: + +```typescript +// 在虚拟滚动组件中添加性能监控 +useEffect(() => { + const observer = new PerformanceObserver(list => { + for (const entry of list.getEntries()) { + if (entry.entryType === "measure" && entry.name === "scroll") { + addPerformanceBreadcrumb("虚拟滚动性能", { + duration: entry.duration, + messageCount: groupedMessages.length, + }); + } + } + }); + observer.observe({ entryTypes: ["measure"] }); + return () => observer.disconnect(); +}, [groupedMessages.length]); +``` + +--- + +### 任务 2.2:useMemo/useCallback 优化(优先级:🔥🔥) + +**预计时间**: 1天 + +#### 步骤 2.2.1:优化 groupedMessages + +已在 `useMessageGrouping.ts` 中使用 `useMemo`,无需额外优化。 + +#### 步骤 2.2.2:优化 renderGroupUser + +**文件**: `src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx` + +```typescript +const renderGroupUser = useCallback( + (msg: ChatRecord) => { + if (!msg) { + return { avatar: "", nickname: "" }; + } + + const member = msg.senderWechatId + ? groupMemberMap.get(msg.senderWechatId) + : undefined; + + return { + avatar: member?.avatar || msg?.avatar, + nickname: member?.nickname || msg?.senderNickname, + }; + }, + [groupMemberMap], // 依赖 groupMemberMap +); +``` + +#### 步骤 2.2.3:优化其他回调函数 + +检查所有 `useCallback` 的依赖项,确保正确。 + +**验收标准**: + +- [ ] 所有回调函数都使用 useCallback +- [ ] 依赖项正确 +- [ ] 无性能警告 + +--- + +## 第三阶段:高级优化(第3-4周) + +### 任务 3.1:图片懒加载(优先级:⚠️) + +**预计时间**: 1天 + +#### 步骤 3.1.1:创建图片懒加载组件 + +**文件**: `src/components/LazyImage/index.tsx` + +```typescript +import React, { useState, useEffect, useRef } from "react"; + +interface LazyImageProps { + src: string; + alt: string; + style?: React.CSSProperties; + className?: string; + onClick?: () => void; + fallbackText?: string; +} + +export const LazyImage: React.FC = ({ + src, + alt, + style, + className, + onClick, + fallbackText, +}) => { + const [isLoaded, setIsLoaded] = useState(false); + const [isInView, setIsInView] = useState(false); + const imgRef = useRef(null); + + useEffect(() => { + const observer = new IntersectionObserver( + entries => { + entries.forEach(entry => { + if (entry.isIntersecting) { + setIsInView(true); + observer.unobserve(entry.target); + } + }); + }, + { + rootMargin: "50px", // 提前50px开始加载 + }, + ); + + if (imgRef.current) { + observer.observe(imgRef.current); + } + + return () => { + if (imgRef.current) { + observer.unobserve(imgRef.current); + } + }; + }, []); + + return ( +
+ {isInView ? ( + {alt} setIsLoaded(true)} + onError={() => { + if (fallbackText) { + // 显示错误文本 + } + }} + /> + ) : ( +
加载中...
+ )} +
+ ); +}; +``` + +#### 步骤 3.1.2:在消息渲染中使用 + +更新 `useMessageParser.ts` 中的图片渲染逻辑。 + +**验收标准**: + +- [ ] 图片按需加载 +- [ ] 滚动性能提升 +- [ ] 内存占用降低 + +--- + +### 任务 3.2:代码分割(优先级:⚠️) + +**预计时间**: 0.5天 + +#### 步骤 3.2.1:路由懒加载 + +**文件**: `src/router/index.tsx` + +```typescript +import { lazy, Suspense } from "react"; +import { Spin } from "antd"; + +const ChatWindow = lazy( + () => import("@/pages/pc/ckbox/weChat/components/ChatWindow"), +); + +const LoadingFallback = () => ( +
+ +
+); + +// 在路由中使用 +}> + + +``` + +#### 步骤 3.2.2:组件懒加载 + +**文件**: `src/pages/pc/ckbox/weChat/components/ChatWindow/index.tsx` + +```typescript +import { lazy, Suspense } from "react"; + +const ProfileCard = lazy(() => import("./components/ProfileCard")); +const FollowupReminderModal = lazy( + () => import("./components/FollowupReminderModal"), +); +const TodoListModal = lazy(() => import("./components/TodoListModal")); +``` + +**验收标准**: + +- [ ] 首屏加载时间减少 +- [ ] 代码分割正确 +- [ ] 功能正常 + +--- + +## 测试与验证 + +### 性能测试清单 + +#### 1. 功能测试 + +- [ ] 消息发送功能正常 +- [ ] 消息接收功能正常 +- [ ] 文件上传功能正常 +- [ ] AI 功能正常 +- [ ] 右键菜单功能正常 +- [ ] 消息转发功能正常 + +#### 2. 性能测试 + +- [ ] 100 条消息滚动流畅(FPS > 30) +- [ ] 500 条消息滚动流畅(FPS > 30) +- [ ] 1000 条消息可以正常加载 +- [ ] 输入框响应时间 < 50ms +- [ ] 组件重渲染次数减少 50%+ + +#### 3. 内存测试 + +- [ ] 1000 条消息内存占用 < 100MB +- [ ] 长时间使用无内存泄漏 + +#### 4. 兼容性测试 + +- [ ] Chrome 浏览器正常 +- [ ] Firefox 浏览器正常 +- [ ] Edge 浏览器正常 + +--- + +## 回滚方案 + +### 如果优化导致问题 + +1. **立即回滚** + + ```bash + git revert + ``` + +2. **分阶段回滚** + - 如果某个任务有问题,只回滚该任务 + - 保留其他已完成的优化 + +3. **回滚检查清单** + - [ ] 功能恢复正常 + - [ ] 性能恢复到优化前水平 + - [ ] 无错误日志 + +--- + +## 进度跟踪 + +### 第一周进度 + +- [ ] ✅ 任务 0.1:Sentry 和 TanStack Query 基础配置(已完成) +- [ ] 任务 1.1:Zustand Selector 优化(2小时,使用 Sentry 监控) +- [ ] 任务 1.2:MessageRecord 组件拆分(1天,整合 TanStack Query + Sentry) +- [ ] 任务 1.3:MessageEnter 组件拆分(1天,整合 TanStack Query + Sentry) +- [ ] 任务 1.4:React.memo 优化(2小时,使用 Sentry 监控) + +### 第二周进度 + +- [ ] 任务 2.0:全面迁移到 TanStack Query(2-3天,部分已在第一周完成) +- [ ] 任务 2.1:虚拟滚动实现(2天,配合 TanStack Query + Sentry 监控) +- [ ] 任务 2.2:useMemo/useCallback 优化(1天,使用 Sentry 监控) + +### 第三周进度 + +- [ ] 任务 3.1:图片懒加载 +- [ ] 任务 3.2:代码分割 + +### 第四周进度 + +- [ ] 性能测试与优化 +- [ ] 文档更新 +- [ ] 代码审查 + +--- + +## 注意事项 + +1. **每次任务完成后** + - 提交代码 + - 更新进度 + - 进行功能测试 + +2. **遇到问题** + - 记录问题 + - 分析原因 + - 寻求帮助 + +3. **性能监控**(贯穿整个优化过程) + - ✅ **Sentry**:自动监控组件渲染、API 请求、错误 + - ✅ **TanStack Query DevTools**:监控数据缓存和请求状态(开发环境) + - ✅ **React DevTools Profiler**:手动分析组件性能 + - ✅ **Chrome Performance**:分析运行时性能 + - 记录性能指标 + - 对比优化前后 + +**性能监控最佳实践**: + +```typescript +// 1. 在关键组件使用 Sentry Profiler + + + + +// 2. 在 API 请求中使用性能监控 +const startTime = performance.now(); +const data = await apiCall(); +addPerformanceBreadcrumb("API请求", { + duration: performance.now() - startTime, +}); + +// 3. 使用 TanStack Query DevTools 查看缓存状态 +// 开发环境自动显示,无需额外配置 +``` + +--- + +## 📊 优化工具使用指南 + +### Sentry 使用 + +1. **查看错误报告** + - 访问 Sentry 后台 + - 查看错误趋势和详情 + - 分析错误上下文 + +2. **查看性能数据** + - 查看 Transactions 页面 + - 分析慢请求和慢组件 + - 识别性能瓶颈 + +3. **错误回放** + - 查看用户操作回放 + - 重现错误场景 + - 快速定位问题 + +### TanStack Query 使用 + +1. **开发环境 DevTools** + - 打开 React Query DevTools + - 查看查询状态和缓存 + - 调试查询问题 + +2. **缓存管理** + - 使用 `queryClient.invalidateQueries` 使缓存失效 + - 使用 `queryClient.setQueryData` 手动更新缓存 + - 使用 `queryClient.getQueryData` 读取缓存 + +3. **性能优化** + - 调整 `staleTime` 和 `cacheTime` + - 使用 `enabled` 控制查询时机 + - 使用 `refetchOnWindowFocus` 控制自动刷新 + +--- + +**文档维护**: 每次任务完成后更新进度和遇到的问题 + +--- + +## 🎯 优化工具协同使用指南 + +### 在性能优化过程中同时使用 Sentry 和 TanStack Query + +#### 1. 组件优化时 + +**使用 Sentry Profiler 监控渲染性能**: + +```typescript +import { Profiler } from "@sentry/react"; +import { addPerformanceBreadcrumb } from "@/utils/sentry"; + + { + addPerformanceBreadcrumb("组件渲染", { + component: "ComponentName", + phase, + duration: actualDuration, + optimization: "selector-optimized", // 标记优化类型 + }); + }} +> + + +``` + +#### 2. 数据获取时 + +**使用 TanStack Query + Sentry 监控**: + +```typescript +import { useQuery } from "@tanstack/react-query"; +import { captureError, addPerformanceBreadcrumb } from "@/utils/sentry"; + +const { data, isLoading, error } = useQuery({ + queryKey: ["data"], + queryFn: async () => { + const startTime = performance.now(); + try { + const result = await fetchData(); + addPerformanceBreadcrumb("数据获取", { + duration: performance.now() - startTime, + dataSize: result.length, + }); + return result; + } catch (err) { + captureError(err as Error, { + tags: { query: "fetchData" }, + }); + throw err; + } + }, +}); +``` + +#### 3. 消息发送时 + +**使用 TanStack Query Mutation + Sentry**: + +```typescript +import { useMutation } from "@tanstack/react-query"; +import { captureError } from "@/utils/sentry"; + +const mutation = useMutation({ + mutationFn: sendMessage, + onError: error => { + captureError(error, { + tags: { action: "sendMessage" }, + }); + }, +}); +``` + +#### 4. 性能对比 + +**优化前后对比**: + +```typescript +// 优化前 +addPerformanceBreadcrumb("优化前 - MessageRecord", { + renderTime: 150, + messageCount: 100, +}); + +// 优化后 +addPerformanceBreadcrumb("优化后 - MessageRecord", { + renderTime: 50, // 提升 66% + messageCount: 100, + optimization: "memo + query", +}); +``` + +### 工具分工 + +| 工具 | 职责 | 使用场景 | +| ---------------------- | ------------------ | --------------------------------------- | +| **Sentry** | 错误监控、性能追踪 | 所有错误、慢请求、组件渲染性能 | +| **TanStack Query** | 数据缓存、请求管理 | 所有 API 数据获取、消息列表、联系人列表 | +| **React DevTools** | 组件分析 | 手动分析组件性能 | +| **Chrome Performance** | 运行时分析 | 分析长任务、内存占用 | + +### 最佳实践 + +1. **每个优化任务都添加 Sentry 监控** + - 记录优化前的性能 + - 记录优化后的性能 + - 对比优化效果 + +2. **所有 API 请求使用 TanStack Query** + - 自动缓存 + - 自动重试 + - 自动错误处理 + +3. **关键操作添加错误监控** + - 消息发送 + - 文件上传 + - WebSocket 连接 + +4. **定期查看 Sentry 数据** + - 发现性能瓶颈 + - 追踪优化效果 + - 快速定位问题 + +--- + +**最后更新**: 2025-01-XX +**版本**: v2.0(整合 Sentry 和 TanStack Query 到所有优化任务)