0612.1
All checks were successful
Next.js CI/CD 流水线 / deploy (push) Successful in 4m58s

This commit is contained in:
2025-06-12 03:12:05 +08:00
parent 27733bd91e
commit 32f8d4ec95
24 changed files with 2959 additions and 1119 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "saas2", "name": "saas3",
"version": "0.1.0", "version": "3.6.12",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
@@ -13,6 +13,7 @@
"@ant-design/icons": "^6.0.0", "@ant-design/icons": "^6.0.0",
"@ant-design/pro-components": "^2.8.7", "@ant-design/pro-components": "^2.8.7",
"@ant-design/v5-patch-for-react-19": "^1.0.3", "@ant-design/v5-patch-for-react-19": "^1.0.3",
"@emotion/css": "^11.13.5",
"@iconify/react": "^4.1.1", "@iconify/react": "^4.1.1",
"@types/lodash": "^4.17.17", "@types/lodash": "^4.17.17",
"antd": "^5.25.4", "antd": "^5.25.4",
@@ -46,6 +47,7 @@
"zustand": "^5.0.5" "zustand": "^5.0.5"
}, },
"devDependencies": { "devDependencies": {
"@next/bundle-analyzer": "^15.3.3",
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
"@types/file-saver": "^2.0.7", "@types/file-saver": "^2.0.7",
"@types/jsonwebtoken": "^9.0.9", "@types/jsonwebtoken": "^9.0.9",

500
pnpm-lock.yaml generated
View File

@@ -20,6 +20,9 @@ importers:
'@ant-design/v5-patch-for-react-19': '@ant-design/v5-patch-for-react-19':
specifier: ^1.0.3 specifier: ^1.0.3
version: 1.0.3(antd@5.25.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) version: 1.0.3(antd@5.25.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@emotion/css':
specifier: ^11.13.5
version: 11.13.5
'@iconify/react': '@iconify/react':
specifier: ^4.1.1 specifier: ^4.1.1
version: 4.1.1(react@19.1.0) version: 4.1.1(react@19.1.0)
@@ -114,6 +117,9 @@ importers:
specifier: ^5.0.5 specifier: ^5.0.5
version: 5.0.5(@types/react@19.1.6)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)) version: 5.0.5(@types/react@19.1.6)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0))
devDependencies: devDependencies:
'@next/bundle-analyzer':
specifier: ^15.3.3
version: 15.3.3
'@tailwindcss/postcss': '@tailwindcss/postcss':
specifier: ^4 specifier: ^4
version: 4.1.8 version: 4.1.8
@@ -290,10 +296,47 @@ packages:
react: '>=19.0.0' react: '>=19.0.0'
react-dom: '>=19.0.0' react-dom: '>=19.0.0'
'@babel/code-frame@7.27.1':
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
engines: {node: '>=6.9.0'}
'@babel/generator@7.27.5':
resolution: {integrity: sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==}
engines: {node: '>=6.9.0'}
'@babel/helper-module-imports@7.27.1':
resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==}
engines: {node: '>=6.9.0'}
'@babel/helper-string-parser@7.27.1':
resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
engines: {node: '>=6.9.0'}
'@babel/helper-validator-identifier@7.27.1':
resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
engines: {node: '>=6.9.0'}
'@babel/parser@7.27.5':
resolution: {integrity: sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==}
engines: {node: '>=6.0.0'}
hasBin: true
'@babel/runtime@7.27.4': '@babel/runtime@7.27.4':
resolution: {integrity: sha512-t3yaEOuGu9NlIZ+hIeGbBjFtZT7j2cb2tg0fuaJKeGotchRjjLfrBA9Kwf8quhpP1EUuxModQg04q/mBwyg8uA==} resolution: {integrity: sha512-t3yaEOuGu9NlIZ+hIeGbBjFtZT7j2cb2tg0fuaJKeGotchRjjLfrBA9Kwf8quhpP1EUuxModQg04q/mBwyg8uA==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
'@babel/template@7.27.2':
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
engines: {node: '>=6.9.0'}
'@babel/traverse@7.27.4':
resolution: {integrity: sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==}
engines: {node: '>=6.9.0'}
'@babel/types@7.27.6':
resolution: {integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==}
engines: {node: '>=6.9.0'}
'@chenshuai2144/sketch-color@1.0.9': '@chenshuai2144/sketch-color@1.0.9':
resolution: {integrity: sha512-obzSy26cb7Pm7OprWyVpgMpIlrZpZ0B7vbrU0RMbvRg0YAI890S5Xy02Aj1Nhl4+KTbi1lVYHt6HQP8Hm9s+1w==} resolution: {integrity: sha512-obzSy26cb7Pm7OprWyVpgMpIlrZpZ0B7vbrU0RMbvRg0YAI890S5Xy02Aj1Nhl4+KTbi1lVYHt6HQP8Hm9s+1w==}
peerDependencies: peerDependencies:
@@ -303,6 +346,10 @@ packages:
resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==} resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==}
engines: {node: '>=10'} engines: {node: '>=10'}
'@discoveryjs/json-ext@0.5.7':
resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==}
engines: {node: '>=10.0.0'}
'@dnd-kit/accessibility@3.1.1': '@dnd-kit/accessibility@3.1.1':
resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==} resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==}
peerDependencies: peerDependencies:
@@ -334,21 +381,51 @@ packages:
'@emnapi/runtime@1.4.3': '@emnapi/runtime@1.4.3':
resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==} resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==}
'@emotion/babel-plugin@11.13.5':
resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==}
'@emotion/cache@11.14.0':
resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==}
'@emotion/css@11.13.5':
resolution: {integrity: sha512-wQdD0Xhkn3Qy2VNcIzbLP9MR8TafI0MJb7BEAXKp+w4+XqErksWR4OXomuDzPsN4InLdGhVe6EYcn2ZIUCpB8w==}
'@emotion/hash@0.8.0': '@emotion/hash@0.8.0':
resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==} resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==}
'@emotion/hash@0.9.2':
resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==}
'@emotion/is-prop-valid@1.2.2': '@emotion/is-prop-valid@1.2.2':
resolution: {integrity: sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==} resolution: {integrity: sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==}
'@emotion/memoize@0.8.1': '@emotion/memoize@0.8.1':
resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==} resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==}
'@emotion/memoize@0.9.0':
resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==}
'@emotion/serialize@1.3.3':
resolution: {integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==}
'@emotion/sheet@1.4.0':
resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==}
'@emotion/unitless@0.10.0':
resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==}
'@emotion/unitless@0.7.5': '@emotion/unitless@0.7.5':
resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==} resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==}
'@emotion/unitless@0.8.1': '@emotion/unitless@0.8.1':
resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==} resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==}
'@emotion/utils@1.4.2':
resolution: {integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==}
'@emotion/weak-memoize@0.4.0':
resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==}
'@iconify/react@4.1.1': '@iconify/react@4.1.1':
resolution: {integrity: sha512-jed14EjvKjee8mc0eoscGxlg7mSQRkwQG3iX3cPBCO7UlOjz0DtlvTqxqEcHUJGh+z1VJ31Yhu5B9PxfO0zbdg==} resolution: {integrity: sha512-jed14EjvKjee8mc0eoscGxlg7mSQRkwQG3iX3cPBCO7UlOjz0DtlvTqxqEcHUJGh+z1VJ31Yhu5B9PxfO0zbdg==}
peerDependencies: peerDependencies:
@@ -498,6 +575,9 @@ packages:
'@mongodb-js/saslprep@1.2.2': '@mongodb-js/saslprep@1.2.2':
resolution: {integrity: sha512-EB0O3SCSNRUFk66iRCpI+cXzIjdswfCs7F6nOC3RAGJ7xr5YhaicvsRwJ9eyzYvYRlCSDUO/c7g4yNulxKC1WA==} resolution: {integrity: sha512-EB0O3SCSNRUFk66iRCpI+cXzIjdswfCs7F6nOC3RAGJ7xr5YhaicvsRwJ9eyzYvYRlCSDUO/c7g4yNulxKC1WA==}
'@next/bundle-analyzer@15.3.3':
resolution: {integrity: sha512-9gddnjACK6yOa5IkmeFyzcwZh2rscsb6ZspTd7tymPYKQM96fJuKjn9HrRtPNKiMm7ExKNadAJqREmHdBgHZ9A==}
'@next/env@15.3.3': '@next/env@15.3.3':
resolution: {integrity: sha512-OdiMrzCl2Xi0VTjiQQUK0Xh7bJHnOuET2s+3V+Y40WJBAXrJeGA3f+I8MZJ/YQ3mVGi5XGR1L66oFlgqXhQ4Vw==} resolution: {integrity: sha512-OdiMrzCl2Xi0VTjiQQUK0Xh7bJHnOuET2s+3V+Y40WJBAXrJeGA3f+I8MZJ/YQ3mVGi5XGR1L66oFlgqXhQ4Vw==}
@@ -549,6 +629,9 @@ packages:
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@polka/url@1.0.0-next.29':
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
'@rc-component/async-validator@5.0.4': '@rc-component/async-validator@5.0.4':
resolution: {integrity: sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==} resolution: {integrity: sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==}
engines: {node: '>=14.x'} engines: {node: '>=14.x'}
@@ -765,6 +848,9 @@ packages:
'@types/node@20.17.57': '@types/node@20.17.57':
resolution: {integrity: sha512-f3T4y6VU4fVQDKVqJV4Uppy8c1p/sVvS3peyqxyWnzkqXFJLRU7Y1Bl7rMS1Qe9z0v4M6McY0Fp9yBsgHJUsWQ==} resolution: {integrity: sha512-f3T4y6VU4fVQDKVqJV4Uppy8c1p/sVvS3peyqxyWnzkqXFJLRU7Y1Bl7rMS1Qe9z0v4M6McY0Fp9yBsgHJUsWQ==}
'@types/parse-json@4.0.2':
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
'@types/ramda@0.30.2': '@types/ramda@0.30.2':
resolution: {integrity: sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==} resolution: {integrity: sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==}
@@ -816,6 +902,15 @@ packages:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
acorn-walk@8.3.4:
resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==}
engines: {node: '>=0.4.0'}
acorn@8.15.0:
resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
engines: {node: '>=0.4.0'}
hasBin: true
add-dom-event-listener@1.1.0: add-dom-event-listener@1.1.0:
resolution: {integrity: sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw==} resolution: {integrity: sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw==}
@@ -832,6 +927,10 @@ packages:
apexcharts@4.7.0: apexcharts@4.7.0:
resolution: {integrity: sha512-iZSrrBGvVlL+nt2B1NpqfDuBZ9jX61X9I2+XV0hlYXHtTwhwLTHDKGXjNXAgFBDLuvSYCB/rq2nPWVPRv2DrGA==} resolution: {integrity: sha512-iZSrrBGvVlL+nt2B1NpqfDuBZ9jX61X9I2+XV0hlYXHtTwhwLTHDKGXjNXAgFBDLuvSYCB/rq2nPWVPRv2DrGA==}
babel-plugin-macros@3.1.0:
resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==}
engines: {node: '>=10', npm: '>=6'}
bail@2.0.2: bail@2.0.2:
resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
@@ -854,6 +953,10 @@ packages:
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
engines: {node: '>=10.16.0'} engines: {node: '>=10.16.0'}
callsites@3.1.0:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
camelize@1.0.1: camelize@1.0.1:
resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==}
@@ -926,9 +1029,16 @@ packages:
comma-separated-tokens@2.0.3: comma-separated-tokens@2.0.3:
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
commander@7.2.0:
resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
engines: {node: '>= 10'}
compute-scroll-into-view@3.1.1: compute-scroll-into-view@3.1.1:
resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==} resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==}
convert-source-map@1.9.0:
resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
cookie@0.7.2: cookie@0.7.2:
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
@@ -940,6 +1050,10 @@ packages:
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
engines: {node: '>= 0.10'} engines: {node: '>= 0.10'}
cosmiconfig@7.1.0:
resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==}
engines: {node: '>=10'}
crc-32@1.2.2: crc-32@1.2.2:
resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==}
engines: {node: '>=0.8'} engines: {node: '>=0.8'}
@@ -958,6 +1072,9 @@ packages:
dayjs@1.11.13: dayjs@1.11.13:
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
debounce@1.2.1:
resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==}
debug@4.3.7: debug@4.3.7:
resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
engines: {node: '>=6.0'} engines: {node: '>=6.0'}
@@ -990,6 +1107,9 @@ packages:
devlop@1.1.0: devlop@1.1.0:
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
duplexer@0.1.2:
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
ecdsa-sig-formatter@1.0.11: ecdsa-sig-formatter@1.0.11:
resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
@@ -1021,6 +1141,13 @@ packages:
resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
engines: {node: '>=0.12'} engines: {node: '>=0.12'}
error-ex@1.3.2:
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
escape-string-regexp@4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
escape-string-regexp@5.0.0: escape-string-regexp@5.0.0:
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
engines: {node: '>=12'} engines: {node: '>=12'}
@@ -1037,18 +1164,36 @@ packages:
file-saver@2.0.5: file-saver@2.0.5:
resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==} resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==}
find-root@1.1.0:
resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==}
frac@1.1.2: frac@1.1.2:
resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==} resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==}
engines: {node: '>=0.8'} engines: {node: '>=0.8'}
function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
geist@1.4.2: geist@1.4.2:
resolution: {integrity: sha512-OQUga/KUc8ueijck6EbtT07L4tZ5+TZgjw8PyWfxo16sL5FWk7gNViPNU8hgCFjy6bJi9yuTP+CRpywzaGN8zw==} resolution: {integrity: sha512-OQUga/KUc8ueijck6EbtT07L4tZ5+TZgjw8PyWfxo16sL5FWk7gNViPNU8hgCFjy6bJi9yuTP+CRpywzaGN8zw==}
peerDependencies: peerDependencies:
next: '>=13.2.0' next: '>=13.2.0'
globals@11.12.0:
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
engines: {node: '>=4'}
graceful-fs@4.2.11: graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
gzip-size@6.0.0:
resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==}
engines: {node: '>=10'}
hasown@2.0.2:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
hast-util-from-parse5@8.0.3: hast-util-from-parse5@8.0.3:
resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==}
@@ -1080,12 +1225,19 @@ packages:
resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
html-escaper@2.0.2:
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
html-url-attributes@3.0.1: html-url-attributes@3.0.1:
resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==}
html-void-elements@3.0.0: html-void-elements@3.0.0:
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
import-fresh@3.3.1:
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
engines: {node: '>=6'}
inline-style-parser@0.2.4: inline-style-parser@0.2.4:
resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==} resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==}
@@ -1095,9 +1247,16 @@ packages:
is-alphanumerical@2.0.1: is-alphanumerical@2.0.1:
resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==}
is-arrayish@0.2.1:
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
is-arrayish@0.3.2: is-arrayish@0.3.2:
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
is-core-module@2.16.1:
resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
engines: {node: '>= 0.4'}
is-decimal@2.0.1: is-decimal@2.0.1:
resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==}
@@ -1108,6 +1267,10 @@ packages:
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
engines: {node: '>=12'} engines: {node: '>=12'}
is-plain-object@5.0.0:
resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
engines: {node: '>=0.10.0'}
jiti@2.4.2: jiti@2.4.2:
resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
hasBin: true hasBin: true
@@ -1115,6 +1278,14 @@ packages:
js-tokens@4.0.0: js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
jsesc@3.1.0:
resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
engines: {node: '>=6'}
hasBin: true
json-parse-even-better-errors@2.3.1:
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
json2mq@0.2.0: json2mq@0.2.0:
resolution: {integrity: sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==} resolution: {integrity: sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==}
@@ -1196,6 +1367,9 @@ packages:
resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
lodash-es@4.17.21: lodash-es@4.17.21:
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
@@ -1434,6 +1608,10 @@ packages:
resolution: {integrity: sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==} resolution: {integrity: sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==}
engines: {node: '>=14.0.0'} engines: {node: '>=14.0.0'}
mrmime@2.0.1:
resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
engines: {node: '>=10'}
ms@2.1.3: ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@@ -1471,16 +1649,35 @@ packages:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
opener@1.5.2:
resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==}
hasBin: true
parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
parse-entities@4.0.2: parse-entities@4.0.2:
resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==}
parse-json@5.2.0:
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
engines: {node: '>=8'}
parse5@7.3.0: parse5@7.3.0:
resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
path-parse@1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
path-to-regexp@8.2.0: path-to-regexp@8.2.0:
resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==} resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==}
engines: {node: '>=16'} engines: {node: '>=16'}
path-type@4.0.0:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'}
picocolors@1.1.1: picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -1818,6 +2015,15 @@ packages:
resize-observer-polyfill@1.5.1: resize-observer-polyfill@1.5.1:
resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
resolve@1.22.10:
resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==}
engines: {node: '>= 0.4'}
hasBin: true
safe-buffer@5.2.1: safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
@@ -1849,6 +2055,10 @@ packages:
simple-swizzle@0.2.2: simple-swizzle@0.2.2:
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
sirv@2.0.4:
resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==}
engines: {node: '>= 10'}
size-sensor@1.0.2: size-sensor@1.0.2:
resolution: {integrity: sha512-2NCmWxY7A9pYKGXNBfteo4hy14gWu47rg5692peVMst6lQLPKrVjhY+UTEsPI5ceFRJSl3gVgMYaUi/hKuaiKw==} resolution: {integrity: sha512-2NCmWxY7A9pYKGXNBfteo4hy14gWu47rg5692peVMst6lQLPKrVjhY+UTEsPI5ceFRJSl3gVgMYaUi/hKuaiKw==}
@@ -1871,6 +2081,10 @@ packages:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
source-map@0.5.7:
resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==}
engines: {node: '>=0.10.0'}
space-separated-tokens@2.0.2: space-separated-tokens@2.0.2:
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
@@ -1917,12 +2131,19 @@ packages:
babel-plugin-macros: babel-plugin-macros:
optional: true optional: true
stylis@4.2.0:
resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==}
stylis@4.3.2: stylis@4.3.2:
resolution: {integrity: sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==} resolution: {integrity: sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==}
stylis@4.3.6: stylis@4.3.6:
resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==}
supports-preserve-symlinks-flag@1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
swr@2.3.3: swr@2.3.3:
resolution: {integrity: sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A==} resolution: {integrity: sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A==}
peerDependencies: peerDependencies:
@@ -1949,6 +2170,10 @@ packages:
toggle-selection@1.0.6: toggle-selection@1.0.6:
resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==}
totalist@3.0.1:
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
engines: {node: '>=6'}
tr46@5.1.1: tr46@5.1.1:
resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -2035,6 +2260,11 @@ packages:
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
engines: {node: '>=12'} engines: {node: '>=12'}
webpack-bundle-analyzer@4.10.1:
resolution: {integrity: sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==}
engines: {node: '>= 10.13.0'}
hasBin: true
whatwg-url@14.2.0: whatwg-url@14.2.0:
resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -2047,6 +2277,18 @@ packages:
resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==} resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==}
engines: {node: '>=0.8'} engines: {node: '>=0.8'}
ws@7.5.10:
resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==}
engines: {node: '>=8.3.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: ^5.0.2
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
ws@8.17.1: ws@8.17.1:
resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
@@ -2072,6 +2314,10 @@ packages:
resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
engines: {node: '>=18'} engines: {node: '>=18'}
yaml@1.10.2:
resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
engines: {node: '>= 6'}
zrender@5.6.1: zrender@5.6.1:
resolution: {integrity: sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==} resolution: {integrity: sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==}
@@ -2363,8 +2609,60 @@ snapshots:
react: 19.1.0 react: 19.1.0
react-dom: 19.1.0(react@19.1.0) react-dom: 19.1.0(react@19.1.0)
'@babel/code-frame@7.27.1':
dependencies:
'@babel/helper-validator-identifier': 7.27.1
js-tokens: 4.0.0
picocolors: 1.1.1
'@babel/generator@7.27.5':
dependencies:
'@babel/parser': 7.27.5
'@babel/types': 7.27.6
'@jridgewell/gen-mapping': 0.3.8
'@jridgewell/trace-mapping': 0.3.25
jsesc: 3.1.0
'@babel/helper-module-imports@7.27.1':
dependencies:
'@babel/traverse': 7.27.4
'@babel/types': 7.27.6
transitivePeerDependencies:
- supports-color
'@babel/helper-string-parser@7.27.1': {}
'@babel/helper-validator-identifier@7.27.1': {}
'@babel/parser@7.27.5':
dependencies:
'@babel/types': 7.27.6
'@babel/runtime@7.27.4': {} '@babel/runtime@7.27.4': {}
'@babel/template@7.27.2':
dependencies:
'@babel/code-frame': 7.27.1
'@babel/parser': 7.27.5
'@babel/types': 7.27.6
'@babel/traverse@7.27.4':
dependencies:
'@babel/code-frame': 7.27.1
'@babel/generator': 7.27.5
'@babel/parser': 7.27.5
'@babel/template': 7.27.2
'@babel/types': 7.27.6
debug: 4.4.1
globals: 11.12.0
transitivePeerDependencies:
- supports-color
'@babel/types@7.27.6':
dependencies:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.27.1
'@chenshuai2144/sketch-color@1.0.9(react@19.1.0)': '@chenshuai2144/sketch-color@1.0.9(react@19.1.0)':
dependencies: dependencies:
react: 19.1.0 react: 19.1.0
@@ -2373,6 +2671,8 @@ snapshots:
'@ctrl/tinycolor@3.6.1': {} '@ctrl/tinycolor@3.6.1': {}
'@discoveryjs/json-ext@0.5.7': {}
'@dnd-kit/accessibility@3.1.1(react@19.1.0)': '@dnd-kit/accessibility@3.1.1(react@19.1.0)':
dependencies: dependencies:
react: 19.1.0 react: 19.1.0
@@ -2410,18 +2710,72 @@ snapshots:
tslib: 2.8.1 tslib: 2.8.1
optional: true optional: true
'@emotion/babel-plugin@11.13.5':
dependencies:
'@babel/helper-module-imports': 7.27.1
'@babel/runtime': 7.27.4
'@emotion/hash': 0.9.2
'@emotion/memoize': 0.9.0
'@emotion/serialize': 1.3.3
babel-plugin-macros: 3.1.0
convert-source-map: 1.9.0
escape-string-regexp: 4.0.0
find-root: 1.1.0
source-map: 0.5.7
stylis: 4.2.0
transitivePeerDependencies:
- supports-color
'@emotion/cache@11.14.0':
dependencies:
'@emotion/memoize': 0.9.0
'@emotion/sheet': 1.4.0
'@emotion/utils': 1.4.2
'@emotion/weak-memoize': 0.4.0
stylis: 4.2.0
'@emotion/css@11.13.5':
dependencies:
'@emotion/babel-plugin': 11.13.5
'@emotion/cache': 11.14.0
'@emotion/serialize': 1.3.3
'@emotion/sheet': 1.4.0
'@emotion/utils': 1.4.2
transitivePeerDependencies:
- supports-color
'@emotion/hash@0.8.0': {} '@emotion/hash@0.8.0': {}
'@emotion/hash@0.9.2': {}
'@emotion/is-prop-valid@1.2.2': '@emotion/is-prop-valid@1.2.2':
dependencies: dependencies:
'@emotion/memoize': 0.8.1 '@emotion/memoize': 0.8.1
'@emotion/memoize@0.8.1': {} '@emotion/memoize@0.8.1': {}
'@emotion/memoize@0.9.0': {}
'@emotion/serialize@1.3.3':
dependencies:
'@emotion/hash': 0.9.2
'@emotion/memoize': 0.9.0
'@emotion/unitless': 0.10.0
'@emotion/utils': 1.4.2
csstype: 3.1.3
'@emotion/sheet@1.4.0': {}
'@emotion/unitless@0.10.0': {}
'@emotion/unitless@0.7.5': {} '@emotion/unitless@0.7.5': {}
'@emotion/unitless@0.8.1': {} '@emotion/unitless@0.8.1': {}
'@emotion/utils@1.4.2': {}
'@emotion/weak-memoize@0.4.0': {}
'@iconify/react@4.1.1(react@19.1.0)': '@iconify/react@4.1.1(react@19.1.0)':
dependencies: dependencies:
'@iconify/types': 2.0.0 '@iconify/types': 2.0.0
@@ -2535,6 +2889,13 @@ snapshots:
dependencies: dependencies:
sparse-bitfield: 3.0.3 sparse-bitfield: 3.0.3
'@next/bundle-analyzer@15.3.3':
dependencies:
webpack-bundle-analyzer: 4.10.1
transitivePeerDependencies:
- bufferutil
- utf-8-validate
'@next/env@15.3.3': {} '@next/env@15.3.3': {}
'@next/swc-darwin-arm64@15.3.3': '@next/swc-darwin-arm64@15.3.3':
@@ -2561,6 +2922,8 @@ snapshots:
'@next/swc-win32-x64-msvc@15.3.3': '@next/swc-win32-x64-msvc@15.3.3':
optional: true optional: true
'@polka/url@1.0.0-next.29': {}
'@rc-component/async-validator@5.0.4': '@rc-component/async-validator@5.0.4':
dependencies: dependencies:
'@babel/runtime': 7.27.4 '@babel/runtime': 7.27.4
@@ -2772,6 +3135,8 @@ snapshots:
dependencies: dependencies:
undici-types: 6.19.8 undici-types: 6.19.8
'@types/parse-json@4.0.2': {}
'@types/ramda@0.30.2': '@types/ramda@0.30.2':
dependencies: dependencies:
types-ramda: 0.30.1 types-ramda: 0.30.1
@@ -2821,6 +3186,12 @@ snapshots:
mime-types: 2.1.35 mime-types: 2.1.35
negotiator: 0.6.3 negotiator: 0.6.3
acorn-walk@8.3.4:
dependencies:
acorn: 8.15.0
acorn@8.15.0: {}
add-dom-event-listener@1.1.0: add-dom-event-listener@1.1.0:
dependencies: dependencies:
object-assign: 4.1.1 object-assign: 4.1.1
@@ -2894,6 +3265,12 @@ snapshots:
'@svgdotjs/svg.select.js': 4.0.3(@svgdotjs/svg.js@3.2.4) '@svgdotjs/svg.select.js': 4.0.3(@svgdotjs/svg.js@3.2.4)
'@yr/monotone-cubic-spline': 1.0.3 '@yr/monotone-cubic-spline': 1.0.3
babel-plugin-macros@3.1.0:
dependencies:
'@babel/runtime': 7.27.4
cosmiconfig: 7.1.0
resolve: 1.22.10
bail@2.0.2: {} bail@2.0.2: {}
base64id@2.0.0: {} base64id@2.0.0: {}
@@ -2908,6 +3285,8 @@ snapshots:
dependencies: dependencies:
streamsearch: 1.1.0 streamsearch: 1.1.0
callsites@3.1.0: {}
camelize@1.0.1: {} camelize@1.0.1: {}
caniuse-lite@1.0.30001720: {} caniuse-lite@1.0.30001720: {}
@@ -2972,8 +3351,12 @@ snapshots:
comma-separated-tokens@2.0.3: {} comma-separated-tokens@2.0.3: {}
commander@7.2.0: {}
compute-scroll-into-view@3.1.1: {} compute-scroll-into-view@3.1.1: {}
convert-source-map@1.9.0: {}
cookie@0.7.2: {} cookie@0.7.2: {}
copy-to-clipboard@3.3.3: copy-to-clipboard@3.3.3:
@@ -2985,6 +3368,14 @@ snapshots:
object-assign: 4.1.1 object-assign: 4.1.1
vary: 1.1.2 vary: 1.1.2
cosmiconfig@7.1.0:
dependencies:
'@types/parse-json': 4.0.2
import-fresh: 3.3.1
parse-json: 5.2.0
path-type: 4.0.0
yaml: 1.10.2
crc-32@1.2.2: {} crc-32@1.2.2: {}
css-color-keywords@1.0.0: {} css-color-keywords@1.0.0: {}
@@ -2999,6 +3390,8 @@ snapshots:
dayjs@1.11.13: {} dayjs@1.11.13: {}
debounce@1.2.1: {}
debug@4.3.7: debug@4.3.7:
dependencies: dependencies:
ms: 2.1.3 ms: 2.1.3
@@ -3019,6 +3412,8 @@ snapshots:
dependencies: dependencies:
dequal: 2.0.3 dequal: 2.0.3
duplexer@0.1.2: {}
ecdsa-sig-formatter@1.0.11: ecdsa-sig-formatter@1.0.11:
dependencies: dependencies:
safe-buffer: 5.2.1 safe-buffer: 5.2.1
@@ -3072,6 +3467,12 @@ snapshots:
entities@6.0.1: {} entities@6.0.1: {}
error-ex@1.3.2:
dependencies:
is-arrayish: 0.2.1
escape-string-regexp@4.0.0: {}
escape-string-regexp@5.0.0: {} escape-string-regexp@5.0.0: {}
estree-util-is-identifier-name@3.0.0: {} estree-util-is-identifier-name@3.0.0: {}
@@ -3082,14 +3483,28 @@ snapshots:
file-saver@2.0.5: {} file-saver@2.0.5: {}
find-root@1.1.0: {}
frac@1.1.2: {} frac@1.1.2: {}
function-bind@1.1.2: {}
geist@1.4.2(next@15.3.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)): geist@1.4.2(next@15.3.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)):
dependencies: dependencies:
next: 15.3.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) next: 15.3.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
globals@11.12.0: {}
graceful-fs@4.2.11: {} graceful-fs@4.2.11: {}
gzip-size@6.0.0:
dependencies:
duplexer: 0.1.2
hasown@2.0.2:
dependencies:
function-bind: 1.1.2
hast-util-from-parse5@8.0.3: hast-util-from-parse5@8.0.3:
dependencies: dependencies:
'@types/hast': 3.0.4 '@types/hast': 3.0.4
@@ -3176,10 +3591,17 @@ snapshots:
highlight.js@11.11.1: {} highlight.js@11.11.1: {}
html-escaper@2.0.2: {}
html-url-attributes@3.0.1: {} html-url-attributes@3.0.1: {}
html-void-elements@3.0.0: {} html-void-elements@3.0.0: {}
import-fresh@3.3.1:
dependencies:
parent-module: 1.0.1
resolve-from: 4.0.0
inline-style-parser@0.2.4: {} inline-style-parser@0.2.4: {}
is-alphabetical@2.0.1: {} is-alphabetical@2.0.1: {}
@@ -3189,19 +3611,31 @@ snapshots:
is-alphabetical: 2.0.1 is-alphabetical: 2.0.1
is-decimal: 2.0.1 is-decimal: 2.0.1
is-arrayish@0.2.1: {}
is-arrayish@0.3.2: is-arrayish@0.3.2:
optional: true optional: true
is-core-module@2.16.1:
dependencies:
hasown: 2.0.2
is-decimal@2.0.1: {} is-decimal@2.0.1: {}
is-hexadecimal@2.0.1: {} is-hexadecimal@2.0.1: {}
is-plain-obj@4.1.0: {} is-plain-obj@4.1.0: {}
is-plain-object@5.0.0: {}
jiti@2.4.2: {} jiti@2.4.2: {}
js-tokens@4.0.0: {} js-tokens@4.0.0: {}
jsesc@3.1.0: {}
json-parse-even-better-errors@2.3.1: {}
json2mq@0.2.0: json2mq@0.2.0:
dependencies: dependencies:
string-convert: 0.2.1 string-convert: 0.2.1
@@ -3277,6 +3711,8 @@ snapshots:
lightningcss-win32-arm64-msvc: 1.30.1 lightningcss-win32-arm64-msvc: 1.30.1
lightningcss-win32-x64-msvc: 1.30.1 lightningcss-win32-x64-msvc: 1.30.1
lines-and-columns@1.2.4: {}
lodash-es@4.17.21: {} lodash-es@4.17.21: {}
lodash.includes@4.3.0: {} lodash.includes@4.3.0: {}
@@ -3711,6 +4147,8 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
mrmime@2.0.1: {}
ms@2.1.3: {} ms@2.1.3: {}
nanoid@3.3.11: {} nanoid@3.3.11: {}
@@ -3744,6 +4182,12 @@ snapshots:
object-assign@4.1.1: {} object-assign@4.1.1: {}
opener@1.5.2: {}
parent-module@1.0.1:
dependencies:
callsites: 3.1.0
parse-entities@4.0.2: parse-entities@4.0.2:
dependencies: dependencies:
'@types/unist': 2.0.11 '@types/unist': 2.0.11
@@ -3754,12 +4198,23 @@ snapshots:
is-decimal: 2.0.1 is-decimal: 2.0.1
is-hexadecimal: 2.0.1 is-hexadecimal: 2.0.1
parse-json@5.2.0:
dependencies:
'@babel/code-frame': 7.27.1
error-ex: 1.3.2
json-parse-even-better-errors: 2.3.1
lines-and-columns: 1.2.4
parse5@7.3.0: parse5@7.3.0:
dependencies: dependencies:
entities: 6.0.1 entities: 6.0.1
path-parse@1.0.7: {}
path-to-regexp@8.2.0: {} path-to-regexp@8.2.0: {}
path-type@4.0.0: {}
picocolors@1.1.1: {} picocolors@1.1.1: {}
postcss-value-parser@4.2.0: {} postcss-value-parser@4.2.0: {}
@@ -4233,6 +4688,14 @@ snapshots:
resize-observer-polyfill@1.5.1: {} resize-observer-polyfill@1.5.1: {}
resolve-from@4.0.0: {}
resolve@1.22.10:
dependencies:
is-core-module: 2.16.1
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
safe-buffer@5.2.1: {} safe-buffer@5.2.1: {}
safe-stable-stringify@2.5.0: {} safe-stable-stringify@2.5.0: {}
@@ -4283,6 +4746,12 @@ snapshots:
is-arrayish: 0.3.2 is-arrayish: 0.3.2
optional: true optional: true
sirv@2.0.4:
dependencies:
'@polka/url': 1.0.0-next.29
mrmime: 2.0.1
totalist: 3.0.1
size-sensor@1.0.2: {} size-sensor@1.0.2: {}
socket.io-adapter@2.5.5: socket.io-adapter@2.5.5:
@@ -4328,6 +4797,8 @@ snapshots:
source-map-js@1.2.1: {} source-map-js@1.2.1: {}
source-map@0.5.7: {}
space-separated-tokens@2.0.2: {} space-separated-tokens@2.0.2: {}
sparse-bitfield@3.0.3: sparse-bitfield@3.0.3:
@@ -4374,10 +4845,14 @@ snapshots:
client-only: 0.0.1 client-only: 0.0.1
react: 19.1.0 react: 19.1.0
stylis@4.2.0: {}
stylis@4.3.2: {} stylis@4.3.2: {}
stylis@4.3.6: {} stylis@4.3.6: {}
supports-preserve-symlinks-flag@1.0.0: {}
swr@2.3.3(react@19.1.0): swr@2.3.3(react@19.1.0):
dependencies: dependencies:
dequal: 2.0.3 dequal: 2.0.3
@@ -4403,6 +4878,8 @@ snapshots:
toggle-selection@1.0.6: {} toggle-selection@1.0.6: {}
totalist@3.0.1: {}
tr46@5.1.1: tr46@5.1.1:
dependencies: dependencies:
punycode: 2.3.1 punycode: 2.3.1
@@ -4496,6 +4973,25 @@ snapshots:
webidl-conversions@7.0.0: {} webidl-conversions@7.0.0: {}
webpack-bundle-analyzer@4.10.1:
dependencies:
'@discoveryjs/json-ext': 0.5.7
acorn: 8.15.0
acorn-walk: 8.3.4
commander: 7.2.0
debounce: 1.2.1
escape-string-regexp: 4.0.0
gzip-size: 6.0.0
html-escaper: 2.0.2
is-plain-object: 5.0.0
opener: 1.5.2
picocolors: 1.1.1
sirv: 2.0.4
ws: 7.5.10
transitivePeerDependencies:
- bufferutil
- utf-8-validate
whatwg-url@14.2.0: whatwg-url@14.2.0:
dependencies: dependencies:
tr46: 5.1.1 tr46: 5.1.1
@@ -4505,6 +5001,8 @@ snapshots:
word@0.3.0: {} word@0.3.0: {}
ws@7.5.10: {}
ws@8.17.1: {} ws@8.17.1: {}
xlsx@0.18.5: xlsx@0.18.5:
@@ -4521,6 +5019,8 @@ snapshots:
yallist@5.0.0: {} yallist@5.0.0: {}
yaml@1.10.2: {}
zrender@5.6.1: zrender@5.6.1:
dependencies: dependencies:
tslib: 2.3.0 tslib: 2.3.0

View File

@@ -1,170 +0,0 @@
import React, { useState, useRef, useEffect } from 'react';
import { Button, message, Popover } from 'antd';
import { PoweroffOutlined } from '@ant-design/icons';
// 自定义 Hook用于管理定时器
function useInterval(callback: () => void, delay: number | null) {
const savedCallback = useRef<() => void>(() => {});
// 保存最新的回调函数,以便在定时器触发时使用
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// 当 delay 发生变化时,设置或清除定时器
useEffect(() => {
if (delay !== null) {
const id = setInterval(() => savedCallback.current && savedCallback.current(), delay);
return () => clearInterval(id); // 清除定时器以防止内存泄漏
}
}, [delay]);
}
const TaskController: React.FC = () => {
// 剩余时间的状态
const [remainingTime, setRemainingTime] = useState<number | null>(null);
// 是否正在计时的状态
const [isCounting, setIsCounting] = useState<boolean>(false);
// 用于存储 EventSource 的引用
const eventSourceRef = useRef<EventSource | null>(null);
const lastQueryResultRef = useRef<string | null>(null); // 用于存储上次查询的结果
// 开始任务并启动倒计时的函数
const startTask = async () => {
if (!isCounting) {
setIsCounting(true);
// 在计时开始时进行物流查询
await updateLogisticsDetails();
console.log('使用 EventSource 启动倒计时...');
// 启动 SSE 连接
const eventSource = new EventSource('/api/sse');
eventSourceRef.current = eventSource;
// 当 SSE 连接成功时触发
eventSource.onopen = () => {
console.log('客户端 SSE 连接已打开');
};
// 当收到服务器发送的消息时触发
eventSource.onmessage = (event) => {
const time = parseInt(event.data, 10);
setRemainingTime(time);
//console.log('接收到事件:', time);
};
// 当发生错误时触发
eventSource.onerror = (error) => {
console.error('SSE 错误:', error);
eventSource.close();
setIsCounting(false); // 关闭倒计时
};
}
};
// 本地定时器,每秒更新 UI 中的剩余时间
useInterval(() => {
if (remainingTime !== null && remainingTime > 0) {
setRemainingTime(remainingTime - 1);
} else if (remainingTime === 0) {
// 移除或注释掉关闭 SSE 连接的代码
//if (eventSourceRef.current) {
// eventSourceRef.current.close(); // 关闭 SSE 连接
//}
setIsCounting(false); // 停止倒计时
startTask(); // 计时结束后自动开始新的一轮查询
}
}, isCounting ? 1000 : null);
useEffect(() => {
// 页面加载时检查是否有剩余时间
if (remainingTime === null) {
// 创建 EventSource 连接以检查服务器端是否有剩余时间
const eventSource = new EventSource('/api/sse');
eventSourceRef.current = eventSource;
eventSource.onmessage = (event) => {
const time = parseInt(event.data, 10);
setRemainingTime(time);
if (time > 0) {
setIsCounting(true); // 如果有剩余时间,则开始倒计时
}
};
eventSource.onerror = (error) => {
console.error('SSE 错误:', error);
eventSource.close();
};
// 检查本地存储或其他持久化方式,恢复上次查询结果
const savedLastQueryResult = localStorage.getItem('lastQueryResult');
if (savedLastQueryResult) {
lastQueryResultRef.current = savedLastQueryResult; // 恢复查询结果
}
// 在组件卸载时关闭 SSE 连接,防止内存泄漏
return () => {
if (eventSourceRef.current) {
eventSourceRef.current.close();
}
};
}
}, [remainingTime]);
// 物流查询并保存结果的函数
const updateLogisticsDetails = async () => {
try {
const response = await fetch('/api/tools/SFExpress/updateLogisticsDetails', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
});
// 处理响应数据
const data = await response.json();
message.success('更新物流详情成功');
//console.log('更新物流详情成功:', data);
lastQueryResultRef.current = data.message;
// 将查询结果保存到 localStorage
localStorage.setItem('lastQueryResult', data.message);
} catch (error) {
message.error('更新物流详情失败');
//console.error('更新物流详情失败:', error);
}
};
// 用于显示在 Popover 中的内容
/*const popoverContent = (
<div>
{lastQueryResultRef.current ? (
<p>{lastQueryResultRef.current}</p>
) : (
<p>暂无最新物流详情</p>
)}
</div>
);*/
// 计算按钮显示的文字
const displayText = isCounting ? `剩余: ${remainingTime}` : '物流查询';
return (
<Popover
//content={popoverContent}
//title="最新物流详情"
placement="top"
>
<Button
className="text-white bg-blue-500 hover:bg-blue-700"
type="primary"
icon={<PoweroffOutlined />}
loading={isCounting} // 按钮加载状态取决于是否在计时中
onClick={startTask} // 点击按钮启动任务
disabled={isCounting || (remainingTime !== null && remainingTime > 0)} // 在计时中或剩余时间大于0时禁用按钮
>
{displayText}
</Button>
</Popover>
);
};
export default TaskController;

View File

@@ -426,10 +426,10 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
// PageContainer 内边距控制 - 完全移除左右空白 // PageContainer 内边距控制 - 完全移除左右空白
pageContainer: { pageContainer: {
// 移除 PageContainer 内容区域的上下内边距 (Block 方向,即垂直方向) // 移除 PageContainer 内容区域的上下内边距 (Block 方向,即垂直方向)
paddingBlockPageContainerContent: 0, //paddingBlockPageContainerContent: 0,
// 移除 PageContainer 内容区域的左右内边距 (Inline 方向,即水平方向) // 移除 PageContainer 内容区域的左右内边距 (Inline 方向,即水平方向)
// 这是消除左右空白的关键配置之一 // 这是消除左右空白的关键配置之一
paddingInlinePageContainerContent: 0, //paddingInlinePageContainerContent: 0,
} }
}} }}
siderMenuType="group" siderMenuType="group"
@@ -463,23 +463,6 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
contentWidth="Fluid" contentWidth="Fluid"
locale="zh-CN" locale="zh-CN"
> >
{/*
内容区域边距移除方案说明:
为了完全移除页面内容的左右空白,采用了三层防护措施:
1. ProLayout 层级:通过 token.pageContainer.paddingInlinePageContainerContent = 0
移除 ProLayout 组件默认的左右内边距
2. PageContainer 层级:
- pageHeaderRender={false} 禁用头部渲染避免额外空间
- token.paddingInlinePageContainerContent = 0 再次确保移除左右内边距
- style.padding = 0 移除组件自身样式内边距
3. 最内层 divpadding = '0px' 作为最后一道防线
这样的多层配置确保在不同版本的 Ant Design Pro 中都能正常工作
*/}
<PageContainer <PageContainer
// 移除默认的页面标题栏 // 移除默认的页面标题栏
header={{ title: null }} header={{ title: null }}
@@ -514,6 +497,8 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
margin: 0, margin: 0,
overflow: 'auto', overflow: 'auto',
boxSizing: 'border-box', boxSizing: 'border-box',
//background: 'blue',
//height: '90vh',
}}> }}>
{children} {children}
</div> </div>
@@ -528,7 +513,7 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
open={showPersonalInfo} open={showPersonalInfo}
onCancel={handleClosePersonalInfo} onCancel={handleClosePersonalInfo}
footer={null} footer={null}
width={800} width='80%'
destroyOnHidden destroyOnHidden
> >
<PersonalInfo /> <PersonalInfo />

View File

@@ -0,0 +1,456 @@
import {
CaretDownFilled,
DoubleRightOutlined,
GithubFilled,
InfoCircleFilled,
LogoutOutlined,
PlusCircleFilled,
QuestionCircleFilled,
SearchOutlined,
} from '@ant-design/icons';
import type { ProSettings } from '@ant-design/pro-components';
import {
PageContainer,
ProCard,
ProConfigProvider,
ProLayout,
SettingDrawer,
} from '@ant-design/pro-components';
import { css } from '@emotion/css';
import {
Button,
ConfigProvider,
Divider,
Dropdown,
Input,
Popover,
theme,
} from 'antd';
import React, { useState } from 'react';
import defaultProps from './_defaultProps';
const Item: React.FC<{ children: React.ReactNode }> = (props) => {
const { token } = theme.useToken();
return (
<div
className={css`
color: ${token.colorTextSecondary};
font-size: 14px;
cursor: pointer;
line-height: 22px;
margin-bottom: 8px;
&:hover {
color: ${token.colorPrimary};
}
`}
style={{
width: '33.33%',
}}
>
{props.children}
<DoubleRightOutlined
style={{
marginInlineStart: 4,
}}
/>
</div>
);
};
const List: React.FC<{ title: string; style?: React.CSSProperties }> = (
props,
) => {
const { token } = theme.useToken();
return (
<div
style={{
width: '100%',
...props.style,
}}
>
<div
style={{
fontSize: 16,
color: token.colorTextHeading,
lineHeight: '24px',
fontWeight: 500,
marginBlockEnd: 16,
}}
>
{props.title}
</div>
<div
style={{
display: 'flex',
flexWrap: 'wrap',
}}
>
{new Array(6).fill(1).map((_, index) => {
return <Item key={index}>-{index}</Item>;
})}
</div>
</div>
);
};
const MenuCard = () => {
const { token } = theme.useToken();
return (
<div
style={{
display: 'flex',
alignItems: 'center',
}}
>
<Divider
style={{
height: '1.5em',
}}
type="vertical"
/>
<Popover
placement="bottom"
overlayStyle={{
width: 'calc(100vw - 24px)',
padding: '24px',
paddingTop: 8,
height: '307px',
borderRadius: '0 0 6px 6px',
}}
content={
<div style={{ display: 'flex', padding: '32px 40px' }}>
<div style={{ flex: 1 }}>
<List title="金融解决方案" />
<List
title="其他解决方案"
style={{
marginBlockStart: 32,
}}
/>
</div>
<div
style={{
width: '308px',
borderInlineStart: '1px solid ' + token.colorBorder,
paddingInlineStart: 16,
}}
>
<div
className={css`
font-size: 14px;
color: ${token.colorText};
line-height: 22px;
`}
>
</div>
{new Array(3).fill(1).map((_name, index) => {
return (
<div
key={index}
className={css`
border-radius: 4px;
padding: 16px;
margin-top: 4px;
display: flex;
cursor: pointer;
&:hover {
background-color: ${token.colorBgTextHover};
}
`}
>
<img src="https://gw.alipayobjects.com/zos/antfincdn/6FTGmLLmN/bianzu%25252013.svg" />
<div
style={{
marginInlineStart: 14,
}}
>
<div
className={css`
font-size: 14px;
color: ${token.colorText};
line-height: 22px;
`}
>
Ant Design
</div>
<div
className={css`
font-size: 12px;
color: ${token.colorTextSecondary};
line-height: 20px;
`}
>
UI
</div>
</div>
</div>
);
})}
</div>
</div>
}
>
<div
style={{
color: token.colorTextHeading,
fontWeight: 500,
cursor: 'pointer',
display: 'flex',
gap: 4,
paddingInlineStart: 8,
paddingInlineEnd: 12,
alignItems: 'center',
}}
className={css`
&:hover {
background-color: ${token.colorBgTextHover};
}
`}
>
<span> </span>
<CaretDownFilled />
</div>
</Popover>
</div>
);
};
const SearchInput = () => {
const { token } = theme.useToken();
return (
<div
key="SearchOutlined"
aria-hidden
style={{
display: 'flex',
alignItems: 'center',
marginInlineEnd: 24,
}}
onMouseDown={(e) => {
e.stopPropagation();
e.preventDefault();
}}
>
<Input
style={{
borderRadius: 4,
marginInlineEnd: 12,
backgroundColor: token.colorBgTextHover,
}}
prefix={
<SearchOutlined
style={{
color: token.colorTextLightSolid,
}}
/>
}
placeholder="搜索方案"
variant="borderless"
/>
<PlusCircleFilled
style={{
color: token.colorPrimary,
fontSize: 24,
}}
/>
</div>
);
};
export default () => {
const [settings, setSetting] = useState<Partial<ProSettings> | undefined>({
fixSiderbar: true,
layout: 'mix',
splitMenus: true,
});
const [pathname, setPathname] = useState('/list/sub-page/sub-sub-page1');
const [num, setNum] = useState(40);
if (typeof document === 'undefined') {
return <div />;
}
return (
<div
id="test-pro-layout"
style={{
height: '100vh',
overflow: 'auto',
}}
>
<ProConfigProvider hashed={false}>
<ConfigProvider
getTargetContainer={() => {
return document.getElementById('test-pro-layout') || document.body;
}}
>
<ProLayout
prefixCls="my-prefix"
bgLayoutImgList={[
{
src: 'https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png',
left: 85,
bottom: 100,
height: '303px',
},
{
src: 'https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png',
bottom: -68,
right: -45,
height: '303px',
},
{
src: 'https://img.alicdn.com/imgextra/i3/O1CN018NxReL1shX85Yz6Cx_!!6000000005798-2-tps-884-496.png',
bottom: 0,
left: 0,
width: '331px',
},
]}
{...defaultProps}
location={{
pathname,
}}
token={{
header: {
colorBgMenuItemSelected: 'rgba(0,0,0,0.04)',
},
}}
siderMenuType="group"
menu={{
collapsedShowGroupTitle: true,
}}
avatarProps={{
src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
size: 'small',
title: '七妮妮',
render: (_props, dom) => {
return (
<Dropdown
menu={{
items: [
{
key: 'logout',
icon: <LogoutOutlined />,
label: '退出登录',
},
],
}}
>
{dom}
</Dropdown>
);
},
}}
actionsRender={(props) => {
if (props.isMobile) return [];
if (typeof window === 'undefined') return [];
return [
props.layout !== 'side' && document.body.clientWidth > 1400 ? (
<SearchInput />
) : undefined,
<InfoCircleFilled key="InfoCircleFilled" />,
<QuestionCircleFilled key="QuestionCircleFilled" />,
<GithubFilled key="GithubFilled" />,
];
}}
headerTitleRender={(logo, title, _) => {
const defaultDom = (
<a>
{logo}
{title}
</a>
);
if (typeof window === 'undefined') return defaultDom;
if (document.body.clientWidth < 1400) {
return defaultDom;
}
if (_.isMobile) return defaultDom;
return (
<>
{defaultDom}
<MenuCard />
</>
);
}}
menuFooterRender={(props) => {
if (props?.collapsed) return undefined;
return (
<div
style={{
textAlign: 'center',
paddingBlockStart: 12,
}}
>
<div>© 2021 Made with love</div>
<div>by Ant Design</div>
</div>
);
}}
onMenuHeaderClick={(e) => console.log(e)}
menuItemRender={(item, dom) => (
<div
onClick={() => {
setPathname(item.path || '/welcome');
}}
>
{dom}
</div>
)}
{...settings}
>
<PageContainer
token={{
paddingInlinePageContainerContent: num,
}}
extra={[
<Button key="3"></Button>,
<Button key="2"></Button>,
<Button
key="1"
type="primary"
onClick={() => {
setNum(num > 0 ? 0 : 40);
}}
>
</Button>,
]}
subTitle="简单的描述"
footer={[
<Button key="3"></Button>,
<Button key="2" type="primary">
</Button>,
]}
>
<ProCard
style={{
height: '200vh',
minHeight: 800,
}}
>
<div />
</ProCard>
</PageContainer>
<SettingDrawer
pathname={pathname}
enableDarkTheme
getContainer={(e: any) => {
if (typeof window === 'undefined') return e;
return document.getElementById('test-pro-layout');
}}
settings={settings}
onSettingChange={(changeSetting) => {
setSetting(changeSetting);
}}
disableUrlParams={false}
/>
</ProLayout>
</ConfigProvider>
</ProConfigProvider>
</div>
);
};

View File

@@ -1,41 +0,0 @@
// src/components/layout/Navbar.tsx
import React from 'react';
import Link from 'next/link';
import { HomeOutlined, LockOutlined, MenuFoldOutlined, MenuUnfoldOutlined, UserOutlined } from '@ant-design/icons';
interface NavbarProps {
isCollapsed: boolean;
setIsCollapsed: (collapsed: boolean) => void;
}
const Navbar: React.FC<NavbarProps> = ({ isCollapsed, setIsCollapsed }) => {
return (
<nav className={`bg-gray-800 text-white fixed inset-y-0 left-0 z-10 ${isCollapsed ? 'w-20' : 'w-64'} transition-all duration-300 flex flex-col items-center`}>
<div className="text-xl font-bold mt-5 mb-4">
AOUN
</div>
<button onClick={() => setIsCollapsed(!isCollapsed)} className="text-lg p-2">
{isCollapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
</button>
<ul className={`flex flex-col space-y-2 w-full p-4 ${isCollapsed ? 'items-center' : 'items-start'}`}>
<li className="w-full">
<Link href="/home" className={`hover:text-gray-300 flex ${isCollapsed ? 'justify-center' : 'justify-start'}`}>
{isCollapsed ? <HomeOutlined /> : '首页'}
</Link>
</li>
<li className="w-full">
<Link href="/management/permission" className={`hover:text-gray-300 flex ${isCollapsed ? 'justify-center' : 'justify-start'}`}>
{isCollapsed ? <LockOutlined /> : '权限管理'}
</Link>
</li>
<li className="w-full">
<Link href="/management/role" className={`hover:text-gray-300 flex ${isCollapsed ? 'justify-center' : 'justify-start'}`}>
{isCollapsed ? <UserOutlined /> : '角色管理'}
</Link>
</li>
</ul>
</nav>
);
};
export default Navbar;

View File

@@ -1,51 +0,0 @@
/**
* 性能监控组件
* 作者: 阿瑞
* 功能: 监控组件渲染性能,开发环境下显示性能指标
* 版本: v1.0
*/
import React, { useEffect, useRef } from 'react';
interface PerformanceMonitorProps {
componentName: string;
children: React.ReactNode;
enableLogging?: boolean;
}
const PerformanceMonitor: React.FC<PerformanceMonitorProps> = ({
componentName,
children,
enableLogging = process.env.NODE_ENV === 'development'
}) => {
const renderStartTime = useRef<number>(0);
const renderCount = useRef<number>(0);
useEffect(() => {
if (!enableLogging) return;
renderCount.current += 1;
const renderEndTime = performance.now();
const renderDuration = renderEndTime - renderStartTime.current;
// 记录渲染性能
if (renderDuration > 100) {
console.warn(`⚠️ ${componentName} 渲染耗时过长: ${renderDuration.toFixed(2)}ms`);
} else if (renderDuration > 50) {
console.log(`${componentName} 渲染耗时: ${renderDuration.toFixed(2)}ms`);
}
// 记录渲染次数
if (renderCount.current > 10) {
console.log(`🔄 ${componentName} 已渲染 ${renderCount.current}`);
}
});
// 记录渲染开始时间
if (enableLogging) {
renderStartTime.current = performance.now();
}
return <>{children}</>;
};
export default React.memo(PerformanceMonitor);

View File

@@ -6,7 +6,7 @@
* @updated 使用原生fetch替代axios符合项目技术规范 * @updated 使用原生fetch替代axios符合项目技术规范
*/ */
import React, { useState, useEffect, useCallback, useMemo } from 'react'; import React, { useState, useCallback, useMemo } from 'react';
import { import {
Form, Form,
Input, Input,
@@ -20,9 +20,8 @@ import {
Col, Col,
Modal, Modal,
Tabs, Tabs,
Skeleton,
} from 'antd'; } from 'antd';
import { useUserInfo } from '@/store/userStore'; import { useFullUserInfo } from '@/store/userStore';
import { import {
EditOutlined, EditOutlined,
MailOutlined, MailOutlined,
@@ -30,6 +29,8 @@ import {
UserOutlined, UserOutlined,
TeamOutlined, TeamOutlined,
IdcardOutlined, IdcardOutlined,
NotificationOutlined,
ReloadOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { PageHeader } from '@ant-design/pro-components'; import { PageHeader } from '@ant-design/pro-components';
@@ -44,6 +45,7 @@ interface IUser {
头像?: string; 头像?: string;
unionid?: string; unionid?: string;
openid?: string; openid?: string;
bark密钥?: string; // 添加bark密钥字段
?: { ?: {
名称: string; 名称: string;
描述: string; 描述: string;
@@ -60,50 +62,35 @@ interface IUser {
} }
const PersonalInfo: React.FC = () => { const PersonalInfo: React.FC = () => {
const userInfo = useUserInfo(); // 从store获取用户信息 const { userInfo: userData, refreshUserInfo, hasCompleteInfo, hasRoleHomePage } = useFullUserInfo(); // 使用新的完整用户信息hook
const [form] = Form.useForm<IUser>(); const [form] = Form.useForm<IUser>();
const [loading, setLoading] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false);
const [userData, setUserData] = useState<IUser | null>(null); // 保存用户数据 const [refreshLoading, setRefreshLoading] = useState<boolean>(false);
const [editModalVisible, setEditModalVisible] = useState<boolean>(false); const [editModalVisible, setEditModalVisible] = useState<boolean>(false);
const userId = userInfo._id;
// 获取用户信息的函数,使用 useCallback 优化性能
const fetchUserInfo = useCallback(async () => {
if (!userId) return;
// 手动刷新个人信息的函数
const handleRefreshUserInfo = useCallback(async () => {
setRefreshLoading(true);
try { try {
const response = await fetch(`/api/backstage/mine/info/${userId}`, { await refreshUserInfo();
method: 'GET', message.success('个人信息刷新成功');
headers: { // 更新表单数据
'Content-Type': 'application/json', form.setFieldsValue(userData);
},
});
if (!response.ok) {
throw new Error('获取个人信息失败');
}
const data = await response.json();
setUserData(data);
form.setFieldsValue(data);
} catch (error: any) { } catch (error: any) {
message.error(error.message || '获取个人信息失败'); message.error('刷新个人信息失败');
} finally {
setRefreshLoading(false);
} }
}, [userId, form]); }, [refreshUserInfo, form, userData]);
// 模块级注释:组件初始化时获取用户信息
useEffect(() => {
fetchUserInfo();
}, [fetchUserInfo]);
// 更新个人信息的处理函数 // 更新个人信息的处理函数
const onFinish = async (values: IUser) => { const onFinish = async (values: IUser) => {
if (!userId) return; if (!userData?._id) return;
setLoading(true); setLoading(true);
try { try {
const response = await fetch(`/api/backstage/mine/info/${userId}`, { const response = await fetch(`/api/backstage/mine/info/${userData._id}`, {
method: 'PUT', method: 'PUT',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@@ -118,7 +105,7 @@ const PersonalInfo: React.FC = () => {
message.success('更新成功'); message.success('更新成功');
setEditModalVisible(false); setEditModalVisible(false);
await fetchUserInfo(); // 更新信息后重新获取 await handleRefreshUserInfo(); // 更新信息后重新获取
} catch (error: any) { } catch (error: any) {
message.error(error.message || '更新失败'); message.error(error.message || '更新失败');
} finally { } finally {
@@ -132,10 +119,10 @@ const PersonalInfo: React.FC = () => {
return userData...map((item) => ({ return userData...map((item) => ({
title: item.名称, title: item.名称,
key: item._id, key: item._id,
children: item.子级.map((child) => ({ children: item.子级?.map((child) => ({
title: child.名称, title: child.名称,
key: child._id, key: child._id,
})), })) || [],
})); }));
}, [userData]); }, [userData]);
@@ -143,55 +130,134 @@ const PersonalInfo: React.FC = () => {
<div> <div>
<PageHeader <PageHeader
title="个人信息" title="个人信息"
subTitle="查看和更新您的个人信息" subTitle={
hasCompleteInfo && hasRoleHomePage
? "查看和更新您的个人信息"
: !hasCompleteInfo
? "⚠️ 个人信息不完整,请及时更新"
: "⚠️ 角色主页路径缺失,请联系管理员配置"
}
extra={[ extra={[
<Button key="edit" type="primary" icon={<EditOutlined />} onClick={() => setEditModalVisible(true)}> <Button
key="refresh"
icon={<ReloadOutlined />}
onClick={handleRefreshUserInfo}
loading={refreshLoading}
style={{ marginRight: 8 }}
>
</Button>,
<Button
key="edit"
type="primary"
icon={<EditOutlined />}
onClick={() => {
setEditModalVisible(true);
form.setFieldsValue(userData); // 打开模态框时设置表单数据
}}
>
</Button>, </Button>,
]} ]}
/> />
<Card style={{ marginBottom: 24 }}>
{userData ? (
<Row gutter={24}> <Row gutter={24}>
<Col xs={24} sm={24} md={8} lg={6} style={{ textAlign: 'center' }}> {/* 左侧用户信息卡片 */}
<Col xs={24} sm={24} md={8} lg={8} xl={6}>
<Card style={{ marginBottom: 24 }}>
{userData && userData._id ? (
<div style={{ textAlign: 'center' }}>
<Avatar size={120} src={userData?.} icon={<UserOutlined />} /> <Avatar size={120} src={userData?.} icon={<UserOutlined />} />
<h2 style={{ marginTop: 16 }}>{userData?.}</h2> <h2 style={{ marginTop: 16, marginBottom: 8 }}>{userData?.}</h2>
<p>{userData?.}</p> <p style={{ color: '#666', marginBottom: 16 }}>{userData?. || '未设置'}</p>
</Col>
<Col xs={24} sm={24} md={16} lg={18}> <Descriptions column={1} size="small">
<Descriptions title="基本信息" column={1} bordered>
<Descriptions.Item label={<MailOutlined />}>{userData?.}</Descriptions.Item> <Descriptions.Item label={<MailOutlined />}>{userData?.}</Descriptions.Item>
<Descriptions.Item label={<PhoneOutlined />}>{userData?.}</Descriptions.Item> <Descriptions.Item label={<PhoneOutlined />}>{userData?.}</Descriptions.Item>
<Descriptions.Item label={<TeamOutlined />}> {userData?.?.}</Descriptions.Item> <Descriptions.Item label={<TeamOutlined />}>{userData?.?. || '未分配'}</Descriptions.Item>
<Descriptions.Item label={<IdcardOutlined />}> {userData?.?.}</Descriptions.Item> <Descriptions.Item label={<IdcardOutlined />}>{userData?.?. || '未设置'}</Descriptions.Item>
</Descriptions> <Descriptions.Item label={
</Col> <span>
</Row> <NotificationOutlined style={{ marginRight: 4 }} />
Bark推送
</span>
}>
{userData?.bark密钥 ? (
<span style={{ color: '#52c41a' }}> </span>
) : ( ) : (
<Skeleton active /> <span style={{ color: '#ff4d4f' }}> </span>
)}
</Descriptions.Item>
</Descriptions>
</div>
) : (
<div style={{ textAlign: 'center', padding: '40px 20px' }}>
<UserOutlined style={{ fontSize: 48, color: '#ccc', marginBottom: 16 }} />
<p></p>
</div>
)} )}
</Card> </Card>
</Col>
{/* 右侧详细信息 */}
<Col xs={24} sm={24} md={16} lg={16} xl={18}>
<Card> <Card>
<Tabs defaultActiveKey="1"> <Tabs defaultActiveKey="1">
<TabPane tab="团队与角色信息" key="1"> <TabPane tab="团队与角色信息" key="1">
<Descriptions column={1} bordered> <Descriptions title="详细信息" column={2} bordered>
<Descriptions.Item label="团队名称">{userData?.?.}</Descriptions.Item> <Descriptions.Item label="团队名称" span={2}>{userData?.?.}</Descriptions.Item>
<Descriptions.Item label="团队拥有者">{userData?.?.?.}</Descriptions.Item> <Descriptions.Item label="团队拥有者">{userData?.?.?.}</Descriptions.Item>
<Descriptions.Item label="角色名称">{userData?.?.}</Descriptions.Item> <Descriptions.Item label="角色名称">{userData?.?.}</Descriptions.Item>
<Descriptions.Item label="角色描述">{userData?.?.}</Descriptions.Item> <Descriptions.Item label="角色描述" span={2}>{userData?.?.}</Descriptions.Item>
<Descriptions.Item label="主页路径" span={2}>
<code style={{ backgroundColor: '#f5f5f5', padding: '4px 8px', borderRadius: '4px' }}>
{userData?.?. || '未设置'}
</code>
</Descriptions.Item>
</Descriptions> </Descriptions>
</TabPane> </TabPane>
<TabPane tab="权限列表" key="2"> <TabPane tab="推送配置" key="2">
<Descriptions title="Bark推送配置" column={2} bordered>
<Descriptions.Item label="推送状态">
{userData?.bark密钥 ? (
<span style={{ color: '#52c41a' }}> </span>
) : (
<span style={{ color: '#ff4d4f' }}> </span>
)}
</Descriptions.Item>
<Descriptions.Item label="设备密钥">
{userData?.bark密钥 ? (
<code style={{ backgroundColor: '#f5f5f5', padding: '4px 8px', borderRadius: '4px' }}>
{userData.bark密钥.substring(0, 8)}...{userData.bark密钥.substring(userData.bark密钥.length - 8)}
</code>
) : (
<span style={{ color: '#999' }}></span>
)}
</Descriptions.Item>
<Descriptions.Item label="配置说明" span={2}>
<div>
<p style={{ margin: '4px 0' }}> App Store下载并安装"Bark"</p>
<p style={{ margin: '4px 0' }}> </p>
<p style={{ margin: '4px 0' }}> </p>
</div>
</Descriptions.Item>
</Descriptions>
</TabPane>
<TabPane tab="权限列表" key="3">
<div style={{ padding: '16px 0' }}>
<h4 style={{ marginBottom: 16 }}></h4>
<Tree <Tree
treeData={permissionsTreeData} treeData={permissionsTreeData}
//defaultExpandAll//默认展开所有节点 //defaultExpandAll//默认展开所有节点
//默认收起所有节点 //默认收起所有节点
defaultExpandParent={false} defaultExpandParent={false}
showLine={{ showLeafIcon: false }}
/> />
</div>
</TabPane> </TabPane>
</Tabs> </Tabs>
</Card> </Card>
</Col>
</Row>
{/* 编辑信息的 Modal */} {/* 编辑信息的 Modal */}
<Modal <Modal
@@ -200,27 +266,70 @@ const PersonalInfo: React.FC = () => {
onCancel={() => setEditModalVisible(false)} onCancel={() => setEditModalVisible(false)}
footer={null} footer={null}
destroyOnClose destroyOnClose
width="80%"
> >
<Form form={form} onFinish={onFinish} layout="vertical"> <Form form={form} onFinish={onFinish} layout="vertical">
<Form.Item name="微信昵称" label="微信昵称"> <Row gutter={24}>
<Input /> {/* 左侧基本信息 */}
</Form.Item> <Col xs={24} sm={24} md={12} lg={12}>
<h4 style={{ marginBottom: 16, color: '#1890ff' }}></h4>
<Form.Item name="姓名" label="姓名" rules={[{ required: true, message: '请输入姓名' }]}> <Form.Item name="姓名" label="姓名" rules={[{ required: true, message: '请输入姓名' }]}>
<Input /> <Input placeholder="请输入姓名" />
</Form.Item> </Form.Item>
<Form.Item name="电话" label="电话" rules={[{ required: true, message: '请输入电话' }]}> <Form.Item name="电话" label="电话" rules={[{ required: true, message: '请输入电话' }]}>
<Input /> <Input placeholder="请输入电话" />
</Form.Item> </Form.Item>
<Form.Item name="邮箱" label="邮箱" rules={[{ required: true, message: '请输入邮箱' }]}> <Form.Item name="邮箱" label="邮箱" rules={[{ required: true, message: '请输入邮箱' }]}>
<Input /> <Input placeholder="请输入邮箱" />
</Form.Item> </Form.Item>
<Form.Item style={{ textAlign: 'right' }}> </Col>
{/* 右侧扩展信息 */}
<Col xs={24} sm={24} md={12} lg={12}>
<h4 style={{ marginBottom: 16, color: '#1890ff' }}></h4>
<Form.Item name="微信昵称" label="微信昵称">
<Input placeholder="请输入微信昵称" />
</Form.Item>
<Form.Item
name="bark密钥"
label={
<span>
<NotificationOutlined style={{ marginRight: 4 }} />
Bark推送密钥
</span>
}
extra="从Bark应用中获取的设备密钥用于接收系统推送通知"
>
<Input.Password
placeholder="请输入Bark设备密钥"
visibilityToggle={{
visible: false,
onVisibleChange: () => {},
}}
/>
</Form.Item>
</Col>
</Row>
<Form.Item style={{ textAlign: 'right', marginTop: 24, marginBottom: 0 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Button
icon={<ReloadOutlined />}
onClick={handleRefreshUserInfo}
loading={refreshLoading}
style={{ marginRight: 'auto' }}
>
</Button>
<div>
<Button onClick={() => setEditModalVisible(false)} style={{ marginRight: 8 }}> <Button onClick={() => setEditModalVisible(false)} style={{ marginRight: 8 }}>
</Button> </Button>
<Button type="primary" htmlType="submit" loading={loading}> <Button type="primary" htmlType="submit" loading={loading}>
</Button> </Button>
</div>
</div>
</Form.Item> </Form.Item>
</Form> </Form>
</Modal> </Modal>

View File

@@ -1,26 +0,0 @@
// src/components/layout/_defaultProps.tsx
import { MenuDataItem } from '@ant-design/pro-components';
import { Icon } from '@iconify/react';
// 假设这个函数从外部传入,用于根据用户权限生成菜单
export const generateDynamicRoutes = (permissions: any[]): MenuDataItem[] => {
//打印权限,输出为数组
//console.log("permissions",permissions);
return permissions.map(permission => ({
path: permission.路径,
name: permission.名称,
icon: <Icon icon={permission.Icon} width="24" height="24" />,
component: './DynamicComponent', // 这里应指向实际组件路径
routes: permission.子级 && generateDynamicRoutes(permission.)
}));
};
export default {
route: {
path: '/',
routes: [], // 初始化时不包含任何静态路由
},
location: {
pathname: '/',
},
};

View File

@@ -0,0 +1,152 @@
import {
ChromeFilled,
CrownFilled,
SmileFilled,
TabletFilled,
} from '@ant-design/icons';
export default {
route: {
path: '/',
routes: [
{
path: '/welcome',
name: '欢迎',
icon: <SmileFilled />,
component: './Welcome',
},
{
path: '/admin',
name: '管理端',
icon: <CrownFilled />,
access: 'canAdmin',
component: './Admin',
routes: [
{
path: '/admin/sub-page1',
name: '一级页面',
icon: 'https://gw.alipayobjects.com/zos/antfincdn/upvrAjAPQX/Logo_Tech%252520UI.svg',
component: './Welcome',
},
{
path: '/admin/sub-page2',
name: '二级页面',
icon: <CrownFilled />,
component: './Welcome',
},
{
path: '/admin/sub-page3',
name: '三级页面',
icon: <CrownFilled />,
component: './Welcome',
},
],
},
{
name: '用户端',
icon: <TabletFilled />,
path: '/list',
component: './ListTableList',
routes: [
{
path: '/list/sub-page',
name: '列表页面',
icon: <CrownFilled />,
routes: [
{
path: 'sub-sub-page1',
name: '一一级列表页面',
icon: <CrownFilled />,
component: './Welcome',
},
{
path: 'sub-sub-page2',
name: '一二级列表页面',
icon: <CrownFilled />,
component: './Welcome',
},
{
path: 'sub-sub-page3',
name: '一三级列表页面',
icon: <CrownFilled />,
component: './Welcome',
},
],
},
{
path: '/list/sub-page2',
name: '二级列表页面',
icon: <CrownFilled />,
component: './Welcome',
},
{
path: '/list/sub-page3',
name: '三级列表页面',
icon: <CrownFilled />,
component: './Welcome',
},
],
},
{
path: 'https://ant.design',
name: 'Ant Design 官网外链',
icon: <ChromeFilled />,
},
],
},
location: {
pathname: '/',
},
appList: [
{
icon: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
title: 'Ant Design',
desc: '杭州市较知名的 UI 设计语言',
url: 'https://ant.design',
},
{
icon: 'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png',
title: 'AntV',
desc: '蚂蚁集团全新一代数据可视化解决方案',
url: 'https://antv.vision/',
target: '_blank',
},
{
icon: 'https://gw.alipayobjects.com/zos/antfincdn/upvrAjAPQX/Logo_Tech%252520UI.svg',
title: 'Pro Components',
desc: '专业级 UI 组件库',
url: 'https://procomponents.ant.design/',
},
{
icon: 'https://img.alicdn.com/tfs/TB1zomHwxv1gK0jSZFFXXb0sXXa-200-200.png',
title: 'umi',
desc: '插件化的企业级前端应用框架。',
url: 'https://umijs.org/zh-CN/docs',
},
{
icon: 'https://gw.alipayobjects.com/zos/bmw-prod/8a74c1d3-16f3-4719-be63-15e467a68a24/km0cv8vn_w500_h500.png',
title: 'qiankun',
desc: '可能是你见过最完善的微前端解决方案🧐',
url: 'https://qiankun.umijs.org/',
},
{
icon: 'https://gw.alipayobjects.com/zos/rmsportal/XuVpGqBFxXplzvLjJBZB.svg',
title: '语雀',
desc: '知识创作与分享工具',
url: 'https://www.yuque.com/',
},
{
icon: 'https://gw.alipayobjects.com/zos/rmsportal/LFooOLwmxGLsltmUjTAP.svg',
title: 'Kitchen ',
desc: 'Sketch 工具集',
url: 'https://kitchen.alipay.com/',
},
{
icon: 'https://gw.alipayobjects.com/zos/bmw-prod/d3e3eb39-1cd7-4aa5-827c-877deced6b7e/lalxt4g3_w256_h256.png',
title: 'dumi',
desc: '为组件开发场景而生的文档工具',
url: 'https://d.umijs.org/zh-CN',
},
],
};

View File

@@ -1,79 +0,0 @@
/**
* Layout布局配置文件
* 作者: 阿瑞
* 功能: 集中管理Layout组件的配置项和常量
* 版本: v1.0
*/
import { type ProSettings } from '@ant-design/pro-components';
// 默认头像配置
export const DEFAULT_AVATAR = {
src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD$IOql2/weixintupian_20170331104822.jpg',
size: 'small' as const,
};
// 默认路由配置
export const DEFAULT_PROPS = {
route: {
path: '/',
routes: [],
},
location: {
pathname: '/',
},
};
// ProLayout基础设置
export const getLayoutSettings = (navTheme: string, colorPrimary: string): Partial<ProSettings> => ({
fixSiderbar: true,
layout: "mix",
splitMenus: false,
navTheme: navTheme as any,
contentWidth: "Fluid",
colorPrimary,
title: "私域管理系统V3",
siderMenuType: "sub",
fixedHeader: true,
menuHeaderRender: false,
});
// 菜单配置
export const MENU_CONFIG = {
collapsedShowGroupTitle: true,
};
// 内容样式配置
export const getContentStyle = (navTheme: string) => ({
backgroundColor: navTheme === 'light' ? '#fff' : '',
height: '100%',
width: '100%',
});
// 模态框配置
export const MODAL_CONFIG = {
personalInfo: {
title: "个人资料",
width: 800,
destroyOnClose: true,
},
mySales: {
title: "我的业绩",
width: 800,
destroyOnClose: true,
},
};
// 性能优化配置
export const PERFORMANCE_CONFIG = {
enableMonitoring: process.env.NODE_ENV === 'development',
renderThreshold: 50, // 渲染时间阈值(ms)
warningThreshold: 100, // 警告阈值(ms)
};
// 主题配置
export const THEME_CONFIG = {
storageKey: 'navTheme',
colorStorageKey: 'colorPrimary',
defaultTheme: 'light' as const,
defaultColor: '#1677FF',
};

View File

@@ -13,6 +13,7 @@ const UserSchema: Schema = new Schema({
: { type: String }, // 用户头像字段 : { type: String }, // 用户头像字段
unionid: { type: String, unique: true, sparse: true }, // 添加sparse以允许null值 unionid: { type: String, unique: true, sparse: true }, // 添加sparse以允许null值
openid: { type: String, unique: true, sparse: true }, // 添加openid字段sparse允许null值 openid: { type: String, unique: true, sparse: true }, // 添加openid字段sparse允许null值
bark密钥: { type: String }, // Bark推送设备密钥用于iOS推送通知
}, { timestamps: true }); // 自动添加创建时间和更新时间 }, { timestamps: true }); // 自动添加创建时间和更新时间
UserSchema.index({ 团队: 1 }); // 对团队字段建立索引 UserSchema.index({ 团队: 1 }); // 对团队字段建立索引

View File

@@ -12,6 +12,7 @@ export interface IUser {
头像?: string; 头像?: string;
unionid?: string; unionid?: string;
openid?: string; openid?: string;
bark密钥?: string;
} }
// 定义团队接口类型 // 定义团队接口类型

View File

@@ -1,5 +1,4 @@
import { NextApiRequest, NextApiResponse } from 'next'; import { NextApiRequest, NextApiResponse } from 'next';
import { broadcastUpdate } from './sse'; // 引入广播功能
import { Account } from '@/models'; import { Account } from '@/models';
import connectDB from '@/lib/connectDB'; import connectDB from '@/lib/connectDB';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@@ -69,9 +68,6 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
await account.save(); await account.save();
// 广播更新
broadcastUpdate(account.);
res.status(200).json({ success: true }); res.status(200).json({ success: true });
} catch (error) { } catch (error) {
console.error('保存增长记录失败:', error); console.error('保存增长记录失败:', error);

View File

@@ -1,38 +0,0 @@
//src\pages\api\backstage\accounts\accountgrowth\sse.ts
import { NextApiRequest, NextApiResponse } from 'next';
import { Account } from '@/models';
let clients: NextApiResponse[] = [];
const handler = (req: NextApiRequest, res: NextApiResponse) => {
const { teamId } = req.query;
// 设置 SSE 头
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// 添加客户端
clients.push(res);
// 监听连接关闭事件,移除客户端
req.on('close', () => {
clients = clients.filter(client => client !== res);
});
// 每次新连接时发送当前数据
Account.find({ 团队: teamId }).select('_id 账号编号 微信号 日增长数据').then((accounts) => {
res.write(`data: ${JSON.stringify({ accounts })}\n\n`);
});
};
// 广播更新
export const broadcastUpdate = (teamId: string) => {
Account.find({ 团队: teamId }).select('_id 账号编号 微信号 日增长数据').then((accounts) => {
clients.forEach((client) => {
client.write(`data: ${JSON.stringify({ accounts })}\n\n`);
});
});
};
export default handler;

View File

@@ -1,37 +0,0 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { Account } from '@/models';
let clients: NextApiResponse[] = [];
const handler = (req: NextApiRequest, res: NextApiResponse) => {
const { teamId } = req.query;
// 设置 SSE 头
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// 添加客户端
clients.push(res);
// 监听连接关闭事件,移除客户端
req.on('close', () => {
clients = clients.filter(client => client !== res);
});
// 每次新连接时发送当前数据
Account.find({ 团队: teamId }).select('_id 账号编号 微信号 日增长数据').then((accounts) => {
res.write(`data: ${JSON.stringify({ accounts })}\n\n`);
});
};
// 广播更新
export const broadcastUpdate = (teamId: string) => {
Account.find({ 团队: teamId }).select('_id 账号编号 微信号 日增长数据').then((accounts) => {
clients.forEach((client) => {
client.write(`data: ${JSON.stringify({ accounts })}\n\n`);
});
});
};
export default handler;

View File

@@ -28,7 +28,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
//关联角色 //关联角色
.populate({ .populate({
path: '角色', path: '角色',
select: '名称 描述 权限', select: '名称 描述 权限 主页', // 添加主页字段
populate: { populate: {
path: '权限', path: '权限',
populate: { path: '子级' }, populate: { path: '子级' },
@@ -59,6 +59,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
电话: user.电话, 电话: user.电话,
unionid: user.unionid, unionid: user.unionid,
openid: user.openid, openid: user.openid,
bark密钥: user.bark密钥, // 添加bark密钥字段
}; };
res.status(200).json(userInfo ); res.status(200).json(userInfo );

View File

@@ -50,13 +50,14 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
} }
} else if (req.method === 'PUT') { } else if (req.method === 'PUT') {
try { try {
const { , , , , , , , , , , } = req.body; const { , , , , , , , , , , , } = req.body;
const updatedSalesRecord = await SalesRecord.findByIdAndUpdate( const updatedSalesRecord = await SalesRecord.findByIdAndUpdate(
id, id,
{ {
, ,
, ,
,
, ,
, ,
, ,

View File

@@ -1,52 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
// 全局变量,用于存储剩余时间
let remainingTimeGlobal: number | null = null;
// 全局变量,用于存储计时器的 ID
let intervalIdGlobal: NodeJS.Timeout | null = null;
export default function handler(req: NextApiRequest, res: NextApiResponse) {
console.log("SSE 连接已打开");
// 设置响应头,指定为 SSE 流式传输
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.flushHeaders(); // 立即刷新响应头
// 如果全局倒计时未开始,则初始化
if (remainingTimeGlobal === null) {
remainingTimeGlobal = 600; // 默认30秒倒计时
}
// 发送时间更新的函数
const sendTimeUpdate = () => {
if (remainingTimeGlobal !== null && remainingTimeGlobal >= 0) {
//console.log(`发送更新: ${remainingTimeGlobal}`);
res.write(`data: ${remainingTimeGlobal}\n\n`); // 发送剩余时间给客户端
remainingTimeGlobal -= 1;
} else {
// 当倒计时结束时清除计时器并关闭 SSE 连接
/*
if (intervalIdGlobal) clearInterval(intervalIdGlobal);
remainingTimeGlobal = null;
intervalIdGlobal = null;
console.log("SSE 连接已关闭(倒计时结束)");
res.end(); // 结束 SSE 响应*/
// 当倒计时结束时重置倒计时,并继续保持连接
remainingTimeGlobal = 600; // 重置倒计时时间
}
};
// 如果计时器未启动,则启动
if (!intervalIdGlobal) {
intervalIdGlobal = setInterval(sendTimeUpdate, 1000);
}
// 立即发送当前的剩余时间
sendTimeUpdate();
req.on('close', () => {
console.log("SSE 连接已关闭(客户端断开连接)");
});
}

View File

@@ -35,6 +35,7 @@ export default connectDB(async (req: NextApiRequest, res: NextApiResponse) => {
_id: user._id, _id: user._id,
姓名: user.姓名, 姓名: user.姓名,
邮箱: user.邮箱, 邮箱: user.邮箱,
电话: user.电话, // 添加电话字段
团队: user.团队, // 团队信息 团队: user.团队, // 团队信息
: { : {
...user..toObject(), ...user..toObject(),
@@ -44,6 +45,7 @@ export default connectDB(async (req: NextApiRequest, res: NextApiResponse) => {
头像: user.头像, 头像: user.头像,
unionid: user.unionid, unionid: user.unionid,
openid: user.openid, openid: user.openid,
bark密钥: user.bark密钥, // 添加bark密钥字段
}; };
// 返回用户信息 // 返回用户信息

File diff suppressed because it is too large Load Diff

View File

@@ -1,51 +1,116 @@
import React, { useEffect, useState } from 'react'; /**
import { Modal, Form, Input, Button, App } from 'antd'; * @file 发货信息管理模态框组件
import { ISalesRecord } from '@/models/types'; * @author 阿瑞
* @description 用于管理销售记录的发货信息,支持批量产品物流单号录入
* @version 1.2.0
*/
const { useApp } = App; import React, { useEffect, useState, useCallback, useMemo } from 'react';
import {
Modal,
Form,
Input,
Button,
App,
Card,
Space,
Typography,
Divider,
Row,
Col,
Tag,
Avatar
} from 'antd';
import {
TruckOutlined,
UserOutlined,
ShoppingCartOutlined,
NumberOutlined,
CheckCircleOutlined
} from '@ant-design/icons';
import { ISalesRecord } from '@/models/types';
import { useUserInfo } from '@/store/userStore'; import { useUserInfo } from '@/store/userStore';
const { useApp } = App;
const { Title, Text } = Typography;
// 组件属性接口定义
interface ShipModalProps { interface ShipModalProps {
visible: boolean; visible: boolean;
onOk: () => void; onOk: () => void;
onCancel: () => void; onCancel: () => void;
record: ISalesRecord | null; // 传入的销售记录 record: ISalesRecord | null;
} }
// 物流单号映射接口
interface LogisticsNumbersMap {
[productId: string]: string;
}
// 发货产品信息接口
interface ShippingProduct {
productId: string;
logisticsNumber: string;
}
/**
* 发货信息管理模态框组件
* 提供现代化的发货信息录入界面,支持多产品物流单号管理
*/
const ShipModal: React.FC<ShipModalProps> = ({ visible, onOk, onCancel, record }) => { const ShipModal: React.FC<ShipModalProps> = ({ visible, onOk, onCancel, record }) => {
// ==================== 状态管理 ====================
const [form] = Form.useForm(); const [form] = Form.useForm();
const [logisticsNumbers, setLogisticsNumbers] = useState<{ [key: string]: string }>({}); // 保存每个产品的物流单号 const [logisticsNumbers, setLogisticsNumbers] = useState<LogisticsNumbersMap>({});
const userInfo = useUserInfo(); // 获取当前用户信息 const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
const { message } = useApp(); // 使用 useApp hook 获取 message 实例
useEffect(() => { // ==================== Hooks ====================
if (record) { const userInfo = useUserInfo();
// 清空表单 const { message } = useApp();
form.resetFields();
form.setFieldsValue({
客户尾号: record?.客户?.电话 ? record...slice(-4) : '', // 自动填入客户电话尾号
});
// 初始化物流单号状态 // ==================== 计算属性 ====================
const initialLogisticsNumbers: { [key: string]: string } = {}; // 客户电话尾号显示
record?.?.forEach(product => { const customerPhoneTail = useMemo(() => {
initialLogisticsNumbers[product._id] = ''; // 初始化每个产品的物流单号为空 return record?.?. ? record...slice(-4) : '';
}); }, [record?.?.]);
setLogisticsNumbers(initialLogisticsNumbers);
// 产品列表
const productList = useMemo(() => {
return record?. || [];
}, [record?.]);
// 已填写物流单号的产品数量
const filledLogisticsCount = useMemo(() => {
return Object.values(logisticsNumbers).filter(number => number.trim()).length;
}, [logisticsNumbers]);
// ==================== 工具函数 ====================
/**
* 清理和格式化物流单号
* @param value 原始输入值
* @returns 清理后的物流单号
*/
const cleanLogisticsNumber = useCallback((value: string): string => {
return value.replace(/[\s.,/#!$%\^&\*;:{}=\-_`~()<>[\]'"|\\?@+]/g, '');
}, []);
/**
* 获取现有物流记录
* @param recordId 销售记录ID
*/
const fetchExistingLogistics = useCallback(async (recordId: string) => {
try {
const response = await fetch(`/api/tools/logistics?关联记录=${recordId}`);
// 获取已有的物流记录并填充单号
fetch(`/api/tools/logistics?关联记录=${record._id}`)
.then(async (response) => {
if (!response.ok) { if (!response.ok) {
throw new Error(`网络错误或服务器错误: ${response.status}`); throw new Error(`网络错误: ${response.status}`);
} }
const logisticsRecords = await response.json(); const logisticsRecords = await response.json();
// 处理返回的物流记录(可能是空数组)
if (logisticsRecords && Array.isArray(logisticsRecords) && logisticsRecords.length > 0) { if (logisticsRecords && Array.isArray(logisticsRecords) && logisticsRecords.length > 0) {
const updatedLogisticsNumbers: { [key: string]: string } = { ...initialLogisticsNumbers }; const updatedLogisticsNumbers: LogisticsNumbersMap = {};
// 遍历物流记录,将已有的单号填充到对应的产品 // 填充已有的物流单号
logisticsRecords.forEach((logisticsRecord: any) => { logisticsRecords.forEach((logisticsRecord: any) => {
const productId = logisticsRecord.?._id || logisticsRecord.; const productId = logisticsRecord.?._id || logisticsRecord.;
if (productId && logisticsRecord.) { if (productId && logisticsRecord.) {
@@ -53,32 +118,69 @@ const ShipModal: React.FC<ShipModalProps> = ({ visible, onOk, onCancel, record }
} }
}); });
setLogisticsNumbers(updatedLogisticsNumbers); setLogisticsNumbers(prev => ({ ...prev, ...updatedLogisticsNumbers }));
console.log('已加载现有物流记录'); console.log('已加载现有物流记录');
} else { } else {
// 没有物流记录是正常情况,不需要错误提示
console.log('暂无物流记录,将创建新记录'); console.log('暂无物流记录,将创建新记录');
} }
}) } catch (error) {
.catch((err: unknown) => { console.error('获取物流记录失败:', error);
console.error('获取物流记录失败:', err);
// 只在真正的网络错误时才提示用户
message.warning('获取现有物流记录失败,将创建新记录'); message.warning('获取现有物流记录失败,将创建新记录');
});
} }
}, [record, form]); }, [message]);
const handleOk = async () => { // ==================== 副作用处理 ====================
useEffect(() => {
if (record && visible) {
// 重置表单和状态
form.resetFields();
// 设置客户电话尾号
form.setFieldsValue({
客户尾号: customerPhoneTail,
});
// 初始化物流单号状态
const initialLogisticsNumbers: LogisticsNumbersMap = {};
productList.forEach(product => {
initialLogisticsNumbers[product._id] = '';
});
setLogisticsNumbers(initialLogisticsNumbers);
// 获取已有物流记录
if (record._id) {
fetchExistingLogistics(record._id);
}
}
}, [record, visible, form, customerPhoneTail, productList, fetchExistingLogistics]);
// ==================== 事件处理器 ====================
/**
* 处理物流单号变更
*/
const handleLogisticsNumberChange = useCallback((productId: string, value: string) => {
const cleanedValue = cleanLogisticsNumber(value);
setLogisticsNumbers(prevState => ({
...prevState,
[productId]: cleanedValue
}));
}, [cleanLogisticsNumber]);
/**
* 处理表单提交
*/
const handleSubmit = useCallback(async () => {
try { try {
setIsSubmitting(true);
const values = await form.validateFields(); const values = await form.validateFields();
// 过滤出有物流单号的产品 // 过滤有效的物流信息
const productsWithLogisticsNumbers = (record?. || []) // 确保 record?.产品 始终是数组 const productsWithLogisticsNumbers: ShippingProduct[] = productList
.map(product => ({ .map(product => ({
productId: product._id, productId: product._id,
logisticsNumber: logisticsNumbers[product._id] logisticsNumber: logisticsNumbers[product._id]?.trim() || ''
})) }))
.filter(item => item.logisticsNumber); // 只保留填写了物流单号的产品 .filter(item => item.logisticsNumber);
if (productsWithLogisticsNumbers.length === 0) { if (productsWithLogisticsNumbers.length === 0) {
message.error('请至少为一个产品填写物流单号'); message.error('请至少为一个产品填写物流单号');
@@ -89,8 +191,8 @@ const ShipModal: React.FC<ShipModalProps> = ({ visible, onOk, onCancel, record }
...values, ...values,
团队: userInfo.团队?._id, 团队: userInfo.团队?._id,
关联记录: record?._id, 关联记录: record?._id,
: 'SalesRecord', // 确保类型为销售记录 : 'SalesRecord',
产品: productsWithLogisticsNumbers // 只提交填写了物流单号的产品 产品: productsWithLogisticsNumbers
}; };
const response = await fetch('/api/tools/logistics', { const response = await fetch('/api/tools/logistics', {
@@ -106,63 +208,168 @@ const ShipModal: React.FC<ShipModalProps> = ({ visible, onOk, onCancel, record }
} }
message.success('发货信息提交成功'); message.success('发货信息提交成功');
onOk(); // 关闭模态框 onOk();
} catch (error: unknown) { } catch (error) {
console.error('发货信息提交失败:', error); console.error('发货信息提交失败:', error);
message.error('发货信息提交失败'); message.error('发货信息提交失败');
} finally {
setIsSubmitting(false);
} }
}; }, [form, productList, logisticsNumbers, userInfo.?._id, record?._id, message, onOk]);
const handleLogisticsNumberChange = (productId: string, value: string) => {
setLogisticsNumbers(prevState => ({
...prevState,
[productId]: value // 更新每个产品的物流单号
}));
};
// ==================== 渲染组件 ====================
return ( return (
<Modal <Modal
open={visible} open={visible}
title="发货信息" title={
<Space align="center" size="middle">
<TruckOutlined style={{ fontSize: '18px', color: '#1890ff' }} />
<Title level={4} style={{ margin: 0 }}>
</Title>
</Space>
}
onCancel={onCancel} onCancel={onCancel}
width={680}
styles={{
body: { padding: '24px' }
}}
footer={[ footer={[
<Button key="cancel" onClick={onCancel}> <Button key="cancel" onClick={onCancel} size="large">
</Button>, </Button>,
<Button key="submit" type="primary" onClick={handleOk}> <Button
key="submit"
type="primary"
onClick={handleSubmit}
loading={isSubmitting}
size="large"
icon={<CheckCircleOutlined />}
>
</Button>, </Button>,
]} ]}
> >
<Form form={form} layout="vertical"> <Form form={form} layout="vertical" size="large">
{/* 客户信息区域 */}
<Card
title={
<Space
style={{ marginTop: '16px' }}
align="center"
>
<Avatar icon={<UserOutlined />} style={{ backgroundColor: '#52c41a' }} />
<Text
style={{ fontSize: '16px', marginLeft: '8px' }}
strong></Text>
</Space>
}
size="small"
style={{ marginBottom: '20px' }}
styles={{
body: { padding: '16px' }
}}
>
<Row gutter={16}>
<Col span={12}>
<div style={{ marginTop: '8px' }}>
<Text type="secondary" style={{ fontSize: '14px' }}></Text>
<div style={{ marginTop: '8px' }}>
<Text strong style={{ fontSize: '16px' }}>
{record?.?. || '未知客户'}
</Text>
</div>
</div>
</Col>
<Col span={12}>
<Form.Item <Form.Item
name="客户尾号" name="客户尾号"
label="客户尾号" label="客户电话尾号"
rules={[{ required: true, message: '请输入客户电话尾号' }]} rules={[{ required: true, message: '客户电话尾号不能为空' }]}
>
<Input placeholder="自动填入客户电话尾号" disabled />
</Form.Item>
{/* 动态生成每个产品的物流单号输入框 */}
{record?.?.map(product => (
<Form.Item
key={product._id}
label={`物流单号 (${product.})`}
> >
<Input <Input
placeholder={`请输入${product.}的物流单号`} prefix={<NumberOutlined style={{ color: '#8c8c8c' }} />}
value={logisticsNumbers[product._id] || ''} placeholder="自动填入"
//onChange={e => handleLogisticsNumberChange(product._id, e.target.value)} disabled
onChange={e => { style={{
const cleanedValue = e.target.value.replace(/[\s.,/#!$%\^&\*;:{}=\-_`~()<>[\]'"|\\?@+]/g, '');//过滤特殊字符和空格 backgroundColor: '#f5f5f5',
handleLogisticsNumberChange(product._id, cleanedValue); color: '#262626'
}} }}
/> />
</Form.Item> </Form.Item>
</Col>
</Row>
</Card>
<Divider orientation="left" orientationMargin="0">
<Space align="center">
<ShoppingCartOutlined style={{ color: '#1890ff' }} />
<Text strong></Text>
<Tag color={filledLogisticsCount > 0 ? 'success' : 'default'}>
{filledLogisticsCount}/{productList.length}
</Tag>
</Space>
</Divider>
{/* 产品物流信息区域 */}
<Row gutter={[16, 16]}>
{productList.map((product, index) => (
<Col span={24} key={product._id}>
<Card
size="small"
style={{
border: logisticsNumbers[product._id] ? '1px solid #52c41a' : '1px solid #d9d9d9',
backgroundColor: logisticsNumbers[product._id] ? '#f6ffed' : '#fafafa'
}}
styles={{
body: { padding: '16px' }
}}
>
<Row gutter={16} align="middle">
<Col span={6}>
<Space direction="vertical" size="small">
<Text type="secondary" style={{ fontSize: '12px' }}>
{index + 1}
</Text>
<Text strong style={{ fontSize: '14px' }}>
{product.}
</Text>
</Space>
</Col>
<Col span={18}>
<Form.Item
label={`物流单号`}
style={{ margin: 0 }}
>
<Input
prefix={<TruckOutlined style={{ color: '#8c8c8c' }} />}
placeholder={`请输入 ${product.} 的物流单号`}
value={logisticsNumbers[product._id] || ''}
onChange={e => handleLogisticsNumberChange(product._id, e.target.value)}
style={{
fontSize: '14px',
borderColor: logisticsNumbers[product._id] ? '#52c41a' : undefined
}}
/>
</Form.Item>
</Col>
</Row>
</Card>
</Col>
))} ))}
</Row>
{/* 提示信息 */}
{productList.length === 0 && (
<Card style={{ textAlign: 'center', marginTop: '20px' }}>
<Text type="secondary">
</Text>
</Card>
)}
</Form> </Form>
</Modal> </Modal>
); );
}; };
export default ShipModal; export default React.memo(ShipModal);

641
src/pages/test/test5.tsx Normal file
View File

@@ -0,0 +1,641 @@
/**
* AI聊天页面组件 - 专业UI/UX设计版
* 作者: 阿瑞
* 功能: 现代化AI对话界面注重视觉美观与用户体验
* 版本: 2.0.0
*/
import React, { useState, useRef, useEffect, useMemo } from 'react';
import {
Button,
Input,
Avatar,
Typography,
Space,
Spin,
message,
Tooltip,
Dropdown,
Badge
} from 'antd';
import {
SendOutlined,
RobotOutlined,
UserOutlined,
ClearOutlined,
CopyOutlined,
LikeOutlined,
DislikeOutlined,
MoreOutlined,
ThunderboltOutlined,
StarOutlined,
SettingOutlined,
DownloadOutlined
} from '@ant-design/icons';
const { TextArea } = Input;
const { Text, Title } = Typography;
// ============= 自定义样式 =============
const customStyles = `
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes pulse {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
}
.animate-slideUp {
animation: slideUp 0.4s ease-out;
}
.animate-fadeIn {
animation: fadeIn 0.3s ease-in;
}
.animate-pulse-custom {
animation: pulse 2s infinite;
}
/* 美化滚动条 */
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.05);
border-radius: 3px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: rgba(139, 69, 19, 0.2);
border-radius: 3px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: rgba(139, 69, 19, 0.3);
}
/* 消息气泡悬浮效果 */
.message-bubble {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.message-bubble:hover {
transform: translateY(-2px);
}
/* 按钮悬浮效果增强 */
.hover-lift {
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.hover-lift:hover {
transform: translateY(-1px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
}
/* 渐变文本 */
.gradient-text {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* 毛玻璃效果增强 */
.glass-effect {
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
background-color: rgba(255, 255, 255, 0.85);
border: 1px solid rgba(255, 255, 255, 0.125);
}
/* 输入框聚焦效果 */
.ant-input:focus,
.ant-input-focused {
border-color: rgba(99, 102, 241, 0.6) !important;
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1) !important;
}
`;
// 注入样式到页面
if (typeof document !== 'undefined') {
const styleElement = document.createElement('style');
styleElement.textContent = customStyles;
if (!document.querySelector('[data-ai-chat-styles]')) {
styleElement.setAttribute('data-ai-chat-styles', 'true');
document.head.appendChild(styleElement);
}
}
// ============= 类型定义区域 =============
interface ChatMessage {
id: string;
content: string;
sender: 'user' | 'ai';
timestamp: Date;
isLoading?: boolean;
liked?: boolean;
disliked?: boolean;
}
interface ChatPageProps {
className?: string;
}
// ============= 主组件 =============
const AIChatPage: React.FC<ChatPageProps> = ({ className }) => {
// 聊天消息状态管理
const [messages, setMessages] = useState<ChatMessage[]>([
{
id: '1',
content: '👋 您好我是您的AI智能助手。\n\n我可以帮助您解答问题、提供建议、协助创作等。请随时告诉我您需要什么帮助',
sender: 'ai',
timestamp: new Date(),
}
]);
// 输入和UI状态管理
const [inputValue, setInputValue] = useState<string>('');
const [isLoading, setIsLoading] = useState<boolean>(false);
const [isTyping, setIsTyping] = useState<boolean>(false);
// DOM引用
const messagesEndRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<any>(null);
const chatContainerRef = useRef<HTMLDivElement>(null);
// ============= 核心功能函数 =============
/**
* 优雅的滚动到底部动画
*/
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({
behavior: 'smooth',
block: 'end'
});
};
useEffect(() => {
const timer = setTimeout(scrollToBottom, 100);
return () => clearTimeout(timer);
}, [messages]);
/**
* 发送消息处理 - 增强交互体验
*/
const handleSendMessage = async () => {
if (!inputValue.trim()) {
message.warning('💡 请输入您的问题');
inputRef.current?.focus();
return;
}
const userMessage: ChatMessage = {
id: Date.now().toString(),
content: inputValue.trim(),
sender: 'user',
timestamp: new Date(),
};
setMessages(prev => [...prev, userMessage]);
setInputValue('');
setIsLoading(true);
setIsTyping(true);
// 模拟真实AI响应体验
setTimeout(() => {
setIsTyping(false);
const aiMessage: ChatMessage = {
id: (Date.now() + 1).toString(),
content: generateAIResponse(userMessage.content),
sender: 'ai',
timestamp: new Date(),
};
setMessages(prev => [...prev, aiMessage]);
setIsLoading(false);
setTimeout(() => {
inputRef.current?.focus();
}, 300);
}, Math.random() * 1000 + 1500);
};
/**
* 消息反馈处理
*/
const handleMessageFeedback = (messageId: string, type: 'like' | 'dislike') => {
setMessages(prev => prev.map(msg =>
msg.id === messageId
? {
...msg,
liked: type === 'like' ? !msg.liked : false,
disliked: type === 'dislike' ? !msg.disliked : false
}
: msg
));
message.success(type === 'like' ? '👍 感谢您的反馈!' : '👎 我们会继续改进');
};
/**
* 清空聊天记录
*/
const handleClearChat = () => {
setMessages([{
id: '1',
content: '🔄 聊天记录已清空\n\n我是您的AI智能助手准备好为您提供帮助了',
sender: 'ai',
timestamp: new Date(),
}]);
message.success('✨ 聊天记录已清空');
};
/**
* 复制消息内容
*/
const handleCopyMessage = async (content: string) => {
try {
await navigator.clipboard.writeText(content);
message.success('📋 消息已复制到剪贴板');
} catch (error) {
message.error('复制失败,请手动选择复制');
}
};
// ============= 工具函数 =============
/**
* 智能AI回复生成
*/
const generateAIResponse = (userInput: string): string => {
const responses = [
`✨ 我理解您关于"${userInput}"的问题。\n\n让我为您详细分析这确实是一个很有深度的话题。从多个维度来看我们可以考虑以下几个方面...\n\n如果您需要更具体的信息请告诉我您最关心的是哪个方面`,
`🎯 关于"${userInput}"这个话题,我很乐意为您解答。\n\n基于我的理解这个问题涉及到几个关键点\n• 首先需要考虑...\n• 其次要注意...\n• 最后建议...\n\n您希望我深入探讨哪个方面呢`,
`💡 您提出了一个很棒的问题!"${userInput}"确实值得深入讨论。\n\n我的建议是\n\n1⃣ 从基础概念开始理解\n2⃣ 分析具体应用场景\n3⃣ 考虑实际操作方法\n\n需要我详细展开其中任何一个部分吗`,
`🚀 很高兴您询问"${userInput}"相关的内容!\n\n这个领域有很多有趣的发展。让我为您整理一些关键信息\n\n▸ 核心要点:...\n▸ 实践建议:...\n▸ 注意事项:...\n\n如果您想了解更多细节我随时可以为您深入解释`
];
return responses[Math.floor(Math.random() * responses.length)];
};
// ============= 计算属性 =============
const chatStats = useMemo(() => ({
totalMessages: messages.length,
userMessages: messages.filter(m => m.sender === 'user').length,
aiMessages: messages.filter(m => m.sender === 'ai').length,
}), [messages]);
// 更多操作菜单
const moreMenuItems = [
{
key: 'export',
label: '导出聊天记录',
icon: <DownloadOutlined />,
},
{
key: 'settings',
label: '聊天设置',
icon: <SettingOutlined />,
},
{
key: 'feedback',
label: '意见反馈',
icon: <StarOutlined />,
},
];
// ============= 渲染函数 =============
return (
<div className={`min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-100 ${className}`}>
{/* 背景装饰 */}
<div className="fixed inset-0 overflow-hidden pointer-events-none">
<div className="absolute -top-40 -right-40 w-80 h-80 bg-gradient-to-br from-purple-400/20 to-pink-400/20 rounded-full blur-3xl"></div>
<div className="absolute -bottom-40 -left-40 w-80 h-80 bg-gradient-to-tr from-blue-400/20 to-cyan-400/20 rounded-full blur-3xl"></div>
</div>
<div className="relative z-10 max-w-5xl mx-auto h-screen flex flex-col p-6">
{/* 精美的顶部导航栏 */}
<div className="glass-effect rounded-3xl p-6 mb-6 shadow-xl shadow-black/5 hover-lift">
<div className="flex justify-between items-center">
<div className="flex items-center space-x-4">
<div className="relative">
<Avatar
size={48}
icon={<RobotOutlined />}
className="bg-gradient-to-br from-violet-500 via-purple-500 to-blue-500 border-4 border-white shadow-lg animate-pulse-custom"
/>
<Badge
status="processing"
className="absolute -bottom-1 -right-1"
/>
</div>
<div>
<Title level={3} className="mb-1 gradient-text">
AI智能助手
</Title>
<div className="flex items-center space-x-2">
<div className="w-2 h-2 bg-green-400 rounded-full animate-pulse"></div>
<Text className="text-sm text-gray-500 font-medium">线</Text>
<ThunderboltOutlined className="text-yellow-500 text-xs" />
</div>
</div>
</div>
<Space size="middle">
<div className="px-4 py-2 bg-gradient-to-r from-blue-50 to-indigo-50 rounded-full border border-blue-100">
<Text className="text-sm font-medium text-blue-700">
{chatStats.totalMessages}
</Text>
</div>
<Tooltip title="清空聊天记录" placement="bottom">
<Button
icon={<ClearOutlined />}
type="text"
onClick={handleClearChat}
className="w-10 h-10 rounded-full hover:bg-red-50 hover:text-red-500 transition-all duration-300"
/>
</Tooltip>
<Dropdown menu={{ items: moreMenuItems }} placement="bottomRight">
<Button
icon={<MoreOutlined />}
type="text"
className="w-10 h-10 rounded-full hover:bg-gray-50 transition-all duration-300"
/>
</Dropdown>
</Space>
</div>
</div>
{/* 聊天消息区域 */}
<div className="flex-1 glass-effect rounded-3xl shadow-xl shadow-black/5 overflow-hidden">
<div
ref={chatContainerRef}
className="h-full flex flex-col"
>
<div className="flex-1 overflow-y-auto p-8 space-y-6 custom-scrollbar">
{messages.map((message) => (
<MessageBubble
key={message.id}
message={message}
onCopy={handleCopyMessage}
onFeedback={handleMessageFeedback}
/>
))}
{/* AI思考状态 */}
{isLoading && (
<div className="flex justify-start animate-fadeIn">
<div className="flex items-center space-x-3 bg-gradient-to-r from-gray-50 to-blue-50 rounded-3xl px-6 py-4 border border-gray-100/50 shadow-sm">
<Avatar
size={32}
icon={<RobotOutlined />}
className="bg-gradient-to-br from-violet-500 to-blue-500"
/>
<div className="flex items-center space-x-2">
<Spin size="small" />
<Text className="text-sm text-gray-600 font-medium">
{isTyping ? 'AI正在思考...' : '准备回复中...'}
</Text>
</div>
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
{/* 消息输入区域 */}
<div className="p-8 bg-gradient-to-r from-gray-50/50 to-blue-50/30 border-t border-white/20">
<div className="flex items-end space-x-4">
<div className="flex-1 relative">
<TextArea
ref={inputRef}
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="✨ 输入您的问题,开始精彩对话..."
autoSize={{ minRows: 1, maxRows: 6 }}
onPressEnter={(e) => {
if (!e.shiftKey) {
e.preventDefault();
handleSendMessage();
}
}}
className="border-0 bg-white/80 backdrop-blur-sm rounded-2xl px-4 py-3 text-base shadow-sm hover:shadow-md focus:shadow-lg transition-all duration-300 resize-none"
style={{
boxShadow: '0 4px 20px rgba(0,0,0,0.04)',
}}
/>
<div className="flex justify-between items-center mt-3 px-2">
<Text className="text-xs text-gray-400">
Enter发送 Shift+Enter换行
</Text>
<Text className="text-xs text-gray-400">
{inputValue.length}/2000
</Text>
</div>
</div>
<Button
type="primary"
icon={<SendOutlined />}
onClick={handleSendMessage}
loading={isLoading}
disabled={!inputValue.trim()}
size="large"
className="h-14 px-8 bg-gradient-to-r from-violet-500 via-purple-500 to-blue-500 border-0 rounded-2xl shadow-lg hover:shadow-xl transform hover:scale-105 transition-all duration-300 font-medium"
>
</Button>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
// ============= 子组件 =============
/**
* 精美的消息气泡组件
*/
interface MessageBubbleProps {
message: ChatMessage;
onCopy: (content: string) => void;
onFeedback: (messageId: string, type: 'like' | 'dislike') => void;
}
const MessageBubble: React.FC<MessageBubbleProps> = React.memo(({ message, onCopy, onFeedback }) => {
const isUser = message.sender === 'user';
const [isHovered, setIsHovered] = useState(false);
return (
<div
className={`flex ${isUser ? 'justify-end' : 'justify-start'} animate-slideUp`}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<div className={`flex items-end space-x-3 max-w-[85%] ${isUser ? 'flex-row-reverse space-x-reverse' : ''}`}>
{/* 头像 */}
<Avatar
size={40}
icon={isUser ? <UserOutlined /> : <RobotOutlined />}
className={`flex-shrink-0 shadow-lg border-2 border-white ${
isUser
? "bg-gradient-to-br from-emerald-400 via-blue-500 to-purple-600"
: "bg-gradient-to-br from-violet-500 via-purple-500 to-blue-500"
}`}
/>
{/* 消息内容容器 */}
<div className="flex flex-col space-y-2">
{/* 主消息气泡 */}
<div
className={`message-bubble relative px-6 py-4 rounded-3xl backdrop-blur-sm border ${
isUser
? 'bg-gradient-to-br from-violet-500 via-purple-500 to-blue-500 text-white border-violet-200/30 rounded-br-lg shadow-lg shadow-violet-500/25'
: 'bg-white/90 text-gray-800 border-gray-100/50 rounded-bl-lg shadow-lg shadow-black/5 hover:shadow-xl'
} ${isHovered ? 'transform scale-[1.02]' : ''}`}
>
{/* 消息内容 */}
<div className={`text-base leading-relaxed ${isUser ? 'text-white' : 'text-gray-800'}`}>
{message.content.split('\n').map((line, index) => (
<div key={index} className={index > 0 ? 'mt-2' : ''}>
{line}
</div>
))}
</div>
{/* 消息尾巴装饰 */}
<div
className={`absolute w-4 h-4 ${
isUser
? 'bottom-0 right-0 bg-gradient-to-br from-violet-500 to-blue-500 -mr-2 mb-2 transform rotate-45'
: 'bottom-0 left-0 bg-white -ml-2 mb-2 transform rotate-45 border-l border-b border-gray-100/50'
}`}
/>
</div>
{/* 时间戳和操作按钮 */}
<div className={`flex items-center space-x-3 px-3 ${isUser ? 'flex-row-reverse' : ''} transition-opacity duration-300 ${isHovered ? 'opacity-100' : 'opacity-60'}`}>
<Text className="text-xs text-gray-400 font-medium">
{formatTime(message.timestamp)}
</Text>
{!isUser && (
<Space size={1}>
<Tooltip title="复制消息">
<Button
type="text"
size="small"
icon={<CopyOutlined />}
onClick={() => onCopy(message.content)}
className="w-7 h-7 rounded-full text-gray-400 hover:text-blue-500 hover:bg-blue-50 transition-all duration-200"
/>
</Tooltip>
<Tooltip title={message.liked ? "已点赞" : "有用"}>
<Button
type="text"
size="small"
icon={<LikeOutlined />}
onClick={() => onFeedback(message.id, 'like')}
className={`w-7 h-7 rounded-full transition-all duration-200 ${
message.liked
? 'text-green-500 bg-green-50'
: 'text-gray-400 hover:text-green-500 hover:bg-green-50'
}`}
/>
</Tooltip>
<Tooltip title={message.disliked ? "已反馈" : "无用"}>
<Button
type="text"
size="small"
icon={<DislikeOutlined />}
onClick={() => onFeedback(message.id, 'dislike')}
className={`w-7 h-7 rounded-full transition-all duration-200 ${
message.disliked
? 'text-red-500 bg-red-50'
: 'text-gray-400 hover:text-red-500 hover:bg-red-50'
}`}
/>
</Tooltip>
</Space>
)}
</div>
</div>
</div>
</div>
);
});
MessageBubble.displayName = 'MessageBubble';
// ============= 工具函数 =============
/**
* 格式化时间显示的通用函数
*/
function formatTime(date: Date): string {
const now = new Date();
const diff = now.getTime() - date.getTime();
if (diff < 60000) return '刚刚';
if (diff < 3600000) return `${Math.floor(diff / 60000)}分钟前`;
if (diff < 86400000) return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
return date.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' });
}
export default AIChatPage;

View File

@@ -46,7 +46,11 @@ const useUserStore = create<UserStore>((set, get) => ({
set({ userInfo }); set({ userInfo });
if (isBrowser) { if (isBrowser) {
localStorage.setItem('userInfo', JSON.stringify(userInfo)); localStorage.setItem('userInfo', JSON.stringify(userInfo));
//console.log('setUserInfo用户信息:', userInfo); console.log('setUserInfo更新用户信息:', {
姓名: userInfo.姓名,
电话: userInfo.电话,
bark密钥: userInfo.bark密钥 ? '已配置' : '未配置'
});
} }
}, },
// 设置访问令牌和刷新令牌 // 设置访问令牌和刷新令牌
@@ -78,8 +82,8 @@ const useUserStore = create<UserStore>((set, get) => ({
throw new Error('用户未登录或用户ID缺失'); throw new Error('用户未登录或用户ID缺失');
} }
// 使用原生fetch替代axios // 使用完整的个人信息API获取详细数据
const response = await fetch(`/api/user?userId=${userId}`, { const response = await fetch(`/api/backstage/mine/info/${userId}`, {
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@@ -91,11 +95,10 @@ const useUserStore = create<UserStore>((set, get) => ({
throw new Error(`HTTP error! status: ${response.status}`); throw new Error(`HTTP error! status: ${response.status}`);
} }
const data = await response.json(); const updatedUserInfo = await response.json();
const updatedUserInfo = data.userInfo;
setUserInfo(updatedUserInfo); // 更新用户信息 setUserInfo(updatedUserInfo); // 更新用户信息
//打印更新成功提示 //打印更新成功提示
console.log('用户信息更新成功!'); console.log('用户信息更新成功!', updatedUserInfo);
} catch (error) { } catch (error) {
console.error('获取用户信息失败:', error); console.error('获取用户信息失败:', error);
message.error('无法更新用户信息'); message.error('无法更新用户信息');
@@ -107,8 +110,8 @@ const useUserStore = create<UserStore>((set, get) => ({
// 为用户的 homePath 提供一个钩子 // 为用户的 homePath 提供一个钩子
export const useUserHomePath = () => { export const useUserHomePath = () => {
const userInfo = useUserStore((state: UserStore) => state.userInfo); const userInfo = useUserStore((state: UserStore) => state.userInfo);
const homePath = userInfo && userInfo. ? userInfo.. : '/index'; const homePath = userInfo && userInfo. && userInfo.. ? userInfo.. : '/index';
//console.log('homePath:', homePath); console.log('主页路径:', homePath, '用户角色信息:', userInfo?.);
return homePath; return homePath;
}; };
@@ -122,4 +125,18 @@ export const useUserToken = () => {
const refreshToken = useUserStore((state: UserStore) => state.refreshToken); const refreshToken = useUserStore((state: UserStore) => state.refreshToken);
return { accessToken, refreshToken }; return { accessToken, refreshToken };
}; };
// 导出一个完整的用户信息钩子,包含刷新功能
export const useFullUserInfo = () => {
const userInfo = useUserStore((state: UserStore) => state.userInfo);
const { fetchAndSetUserInfo } = useUserStore((state: UserStore) => state.actions);
return {
userInfo,
refreshUserInfo: fetchAndSetUserInfo,
hasCompleteInfo: !!(userInfo._id && userInfo. && userInfo. && userInfo.),
hasRoleHomePage: !!(userInfo. && userInfo..)
};
};
export default useUserStore; export default useUserStore;