This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "saas2",
|
||||
"version": "0.1.0",
|
||||
"name": "saas3",
|
||||
"version": "3.6.12",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
@@ -13,6 +13,7 @@
|
||||
"@ant-design/icons": "^6.0.0",
|
||||
"@ant-design/pro-components": "^2.8.7",
|
||||
"@ant-design/v5-patch-for-react-19": "^1.0.3",
|
||||
"@emotion/css": "^11.13.5",
|
||||
"@iconify/react": "^4.1.1",
|
||||
"@types/lodash": "^4.17.17",
|
||||
"antd": "^5.25.4",
|
||||
@@ -46,6 +47,7 @@
|
||||
"zustand": "^5.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/bundle-analyzer": "^15.3.3",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/jsonwebtoken": "^9.0.9",
|
||||
|
||||
500
pnpm-lock.yaml
generated
500
pnpm-lock.yaml
generated
@@ -20,6 +20,9 @@ importers:
|
||||
'@ant-design/v5-patch-for-react-19':
|
||||
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)
|
||||
'@emotion/css':
|
||||
specifier: ^11.13.5
|
||||
version: 11.13.5
|
||||
'@iconify/react':
|
||||
specifier: ^4.1.1
|
||||
version: 4.1.1(react@19.1.0)
|
||||
@@ -114,6 +117,9 @@ importers:
|
||||
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))
|
||||
devDependencies:
|
||||
'@next/bundle-analyzer':
|
||||
specifier: ^15.3.3
|
||||
version: 15.3.3
|
||||
'@tailwindcss/postcss':
|
||||
specifier: ^4
|
||||
version: 4.1.8
|
||||
@@ -290,10 +296,47 @@ packages:
|
||||
react: '>=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':
|
||||
resolution: {integrity: sha512-t3yaEOuGu9NlIZ+hIeGbBjFtZT7j2cb2tg0fuaJKeGotchRjjLfrBA9Kwf8quhpP1EUuxModQg04q/mBwyg8uA==}
|
||||
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':
|
||||
resolution: {integrity: sha512-obzSy26cb7Pm7OprWyVpgMpIlrZpZ0B7vbrU0RMbvRg0YAI890S5Xy02Aj1Nhl4+KTbi1lVYHt6HQP8Hm9s+1w==}
|
||||
peerDependencies:
|
||||
@@ -303,6 +346,10 @@ packages:
|
||||
resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==}
|
||||
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':
|
||||
resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==}
|
||||
peerDependencies:
|
||||
@@ -334,21 +381,51 @@ packages:
|
||||
'@emnapi/runtime@1.4.3':
|
||||
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':
|
||||
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':
|
||||
resolution: {integrity: sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==}
|
||||
|
||||
'@emotion/memoize@0.8.1':
|
||||
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':
|
||||
resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==}
|
||||
|
||||
'@emotion/unitless@0.8.1':
|
||||
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':
|
||||
resolution: {integrity: sha512-jed14EjvKjee8mc0eoscGxlg7mSQRkwQG3iX3cPBCO7UlOjz0DtlvTqxqEcHUJGh+z1VJ31Yhu5B9PxfO0zbdg==}
|
||||
peerDependencies:
|
||||
@@ -498,6 +575,9 @@ packages:
|
||||
'@mongodb-js/saslprep@1.2.2':
|
||||
resolution: {integrity: sha512-EB0O3SCSNRUFk66iRCpI+cXzIjdswfCs7F6nOC3RAGJ7xr5YhaicvsRwJ9eyzYvYRlCSDUO/c7g4yNulxKC1WA==}
|
||||
|
||||
'@next/bundle-analyzer@15.3.3':
|
||||
resolution: {integrity: sha512-9gddnjACK6yOa5IkmeFyzcwZh2rscsb6ZspTd7tymPYKQM96fJuKjn9HrRtPNKiMm7ExKNadAJqREmHdBgHZ9A==}
|
||||
|
||||
'@next/env@15.3.3':
|
||||
resolution: {integrity: sha512-OdiMrzCl2Xi0VTjiQQUK0Xh7bJHnOuET2s+3V+Y40WJBAXrJeGA3f+I8MZJ/YQ3mVGi5XGR1L66oFlgqXhQ4Vw==}
|
||||
|
||||
@@ -549,6 +629,9 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@polka/url@1.0.0-next.29':
|
||||
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
|
||||
|
||||
'@rc-component/async-validator@5.0.4':
|
||||
resolution: {integrity: sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==}
|
||||
engines: {node: '>=14.x'}
|
||||
@@ -765,6 +848,9 @@ packages:
|
||||
'@types/node@20.17.57':
|
||||
resolution: {integrity: sha512-f3T4y6VU4fVQDKVqJV4Uppy8c1p/sVvS3peyqxyWnzkqXFJLRU7Y1Bl7rMS1Qe9z0v4M6McY0Fp9yBsgHJUsWQ==}
|
||||
|
||||
'@types/parse-json@4.0.2':
|
||||
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
|
||||
|
||||
'@types/ramda@0.30.2':
|
||||
resolution: {integrity: sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==}
|
||||
|
||||
@@ -816,6 +902,15 @@ packages:
|
||||
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
|
||||
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:
|
||||
resolution: {integrity: sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw==}
|
||||
|
||||
@@ -832,6 +927,10 @@ packages:
|
||||
apexcharts@4.7.0:
|
||||
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:
|
||||
resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
|
||||
|
||||
@@ -854,6 +953,10 @@ packages:
|
||||
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
|
||||
engines: {node: '>=10.16.0'}
|
||||
|
||||
callsites@3.1.0:
|
||||
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
camelize@1.0.1:
|
||||
resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==}
|
||||
|
||||
@@ -926,9 +1029,16 @@ packages:
|
||||
comma-separated-tokens@2.0.3:
|
||||
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:
|
||||
resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==}
|
||||
|
||||
convert-source-map@1.9.0:
|
||||
resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
|
||||
|
||||
cookie@0.7.2:
|
||||
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@@ -940,6 +1050,10 @@ packages:
|
||||
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
cosmiconfig@7.1.0:
|
||||
resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
crc-32@1.2.2:
|
||||
resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==}
|
||||
engines: {node: '>=0.8'}
|
||||
@@ -958,6 +1072,9 @@ packages:
|
||||
dayjs@1.11.13:
|
||||
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
|
||||
|
||||
debounce@1.2.1:
|
||||
resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==}
|
||||
|
||||
debug@4.3.7:
|
||||
resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
@@ -990,6 +1107,9 @@ packages:
|
||||
devlop@1.1.0:
|
||||
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:
|
||||
resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
|
||||
|
||||
@@ -1021,6 +1141,13 @@ packages:
|
||||
resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
|
||||
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:
|
||||
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -1037,18 +1164,36 @@ packages:
|
||||
file-saver@2.0.5:
|
||||
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:
|
||||
resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
function-bind@1.1.2:
|
||||
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||
|
||||
geist@1.4.2:
|
||||
resolution: {integrity: sha512-OQUga/KUc8ueijck6EbtT07L4tZ5+TZgjw8PyWfxo16sL5FWk7gNViPNU8hgCFjy6bJi9yuTP+CRpywzaGN8zw==}
|
||||
peerDependencies:
|
||||
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:
|
||||
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:
|
||||
resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==}
|
||||
|
||||
@@ -1080,12 +1225,19 @@ packages:
|
||||
resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
html-escaper@2.0.2:
|
||||
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
|
||||
|
||||
html-url-attributes@3.0.1:
|
||||
resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==}
|
||||
|
||||
html-void-elements@3.0.0:
|
||||
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
|
||||
|
||||
import-fresh@3.3.1:
|
||||
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
inline-style-parser@0.2.4:
|
||||
resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==}
|
||||
|
||||
@@ -1095,9 +1247,16 @@ packages:
|
||||
is-alphanumerical@2.0.1:
|
||||
resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==}
|
||||
|
||||
is-arrayish@0.2.1:
|
||||
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
|
||||
|
||||
is-arrayish@0.3.2:
|
||||
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:
|
||||
resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==}
|
||||
|
||||
@@ -1108,6 +1267,10 @@ packages:
|
||||
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
is-plain-object@5.0.0:
|
||||
resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
jiti@2.4.2:
|
||||
resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
|
||||
hasBin: true
|
||||
@@ -1115,6 +1278,14 @@ packages:
|
||||
js-tokens@4.0.0:
|
||||
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:
|
||||
resolution: {integrity: sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==}
|
||||
|
||||
@@ -1196,6 +1367,9 @@ packages:
|
||||
resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
|
||||
lines-and-columns@1.2.4:
|
||||
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
||||
|
||||
lodash-es@4.17.21:
|
||||
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
|
||||
|
||||
@@ -1434,6 +1608,10 @@ packages:
|
||||
resolution: {integrity: sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
mrmime@2.0.1:
|
||||
resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
@@ -1471,16 +1649,35 @@ packages:
|
||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
path-type@4.0.0:
|
||||
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
@@ -1818,6 +2015,15 @@ packages:
|
||||
resize-observer-polyfill@1.5.1:
|
||||
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:
|
||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||
|
||||
@@ -1849,6 +2055,10 @@ packages:
|
||||
simple-swizzle@0.2.2:
|
||||
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:
|
||||
resolution: {integrity: sha512-2NCmWxY7A9pYKGXNBfteo4hy14gWu47rg5692peVMst6lQLPKrVjhY+UTEsPI5ceFRJSl3gVgMYaUi/hKuaiKw==}
|
||||
|
||||
@@ -1871,6 +2081,10 @@ packages:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
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:
|
||||
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
|
||||
|
||||
@@ -1917,12 +2131,19 @@ packages:
|
||||
babel-plugin-macros:
|
||||
optional: true
|
||||
|
||||
stylis@4.2.0:
|
||||
resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==}
|
||||
|
||||
stylis@4.3.2:
|
||||
resolution: {integrity: sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==}
|
||||
|
||||
stylis@4.3.6:
|
||||
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:
|
||||
resolution: {integrity: sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A==}
|
||||
peerDependencies:
|
||||
@@ -1949,6 +2170,10 @@ packages:
|
||||
toggle-selection@1.0.6:
|
||||
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:
|
||||
resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -2035,6 +2260,11 @@ packages:
|
||||
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
|
||||
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:
|
||||
resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -2047,6 +2277,18 @@ packages:
|
||||
resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==}
|
||||
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:
|
||||
resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
@@ -2072,6 +2314,10 @@ packages:
|
||||
resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
yaml@1.10.2:
|
||||
resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
zrender@5.6.1:
|
||||
resolution: {integrity: sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==}
|
||||
|
||||
@@ -2363,8 +2609,60 @@ snapshots:
|
||||
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/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)':
|
||||
dependencies:
|
||||
react: 19.1.0
|
||||
@@ -2373,6 +2671,8 @@ snapshots:
|
||||
|
||||
'@ctrl/tinycolor@3.6.1': {}
|
||||
|
||||
'@discoveryjs/json-ext@0.5.7': {}
|
||||
|
||||
'@dnd-kit/accessibility@3.1.1(react@19.1.0)':
|
||||
dependencies:
|
||||
react: 19.1.0
|
||||
@@ -2410,18 +2710,72 @@ snapshots:
|
||||
tslib: 2.8.1
|
||||
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.9.2': {}
|
||||
|
||||
'@emotion/is-prop-valid@1.2.2':
|
||||
dependencies:
|
||||
'@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.8.1': {}
|
||||
|
||||
'@emotion/utils@1.4.2': {}
|
||||
|
||||
'@emotion/weak-memoize@0.4.0': {}
|
||||
|
||||
'@iconify/react@4.1.1(react@19.1.0)':
|
||||
dependencies:
|
||||
'@iconify/types': 2.0.0
|
||||
@@ -2535,6 +2889,13 @@ snapshots:
|
||||
dependencies:
|
||||
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/swc-darwin-arm64@15.3.3':
|
||||
@@ -2561,6 +2922,8 @@ snapshots:
|
||||
'@next/swc-win32-x64-msvc@15.3.3':
|
||||
optional: true
|
||||
|
||||
'@polka/url@1.0.0-next.29': {}
|
||||
|
||||
'@rc-component/async-validator@5.0.4':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.27.4
|
||||
@@ -2772,6 +3135,8 @@ snapshots:
|
||||
dependencies:
|
||||
undici-types: 6.19.8
|
||||
|
||||
'@types/parse-json@4.0.2': {}
|
||||
|
||||
'@types/ramda@0.30.2':
|
||||
dependencies:
|
||||
types-ramda: 0.30.1
|
||||
@@ -2821,6 +3186,12 @@ snapshots:
|
||||
mime-types: 2.1.35
|
||||
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:
|
||||
dependencies:
|
||||
object-assign: 4.1.1
|
||||
@@ -2894,6 +3265,12 @@ snapshots:
|
||||
'@svgdotjs/svg.select.js': 4.0.3(@svgdotjs/svg.js@3.2.4)
|
||||
'@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: {}
|
||||
|
||||
base64id@2.0.0: {}
|
||||
@@ -2908,6 +3285,8 @@ snapshots:
|
||||
dependencies:
|
||||
streamsearch: 1.1.0
|
||||
|
||||
callsites@3.1.0: {}
|
||||
|
||||
camelize@1.0.1: {}
|
||||
|
||||
caniuse-lite@1.0.30001720: {}
|
||||
@@ -2972,8 +3351,12 @@ snapshots:
|
||||
|
||||
comma-separated-tokens@2.0.3: {}
|
||||
|
||||
commander@7.2.0: {}
|
||||
|
||||
compute-scroll-into-view@3.1.1: {}
|
||||
|
||||
convert-source-map@1.9.0: {}
|
||||
|
||||
cookie@0.7.2: {}
|
||||
|
||||
copy-to-clipboard@3.3.3:
|
||||
@@ -2985,6 +3368,14 @@ snapshots:
|
||||
object-assign: 4.1.1
|
||||
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: {}
|
||||
|
||||
css-color-keywords@1.0.0: {}
|
||||
@@ -2999,6 +3390,8 @@ snapshots:
|
||||
|
||||
dayjs@1.11.13: {}
|
||||
|
||||
debounce@1.2.1: {}
|
||||
|
||||
debug@4.3.7:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
@@ -3019,6 +3412,8 @@ snapshots:
|
||||
dependencies:
|
||||
dequal: 2.0.3
|
||||
|
||||
duplexer@0.1.2: {}
|
||||
|
||||
ecdsa-sig-formatter@1.0.11:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
@@ -3072,6 +3467,12 @@ snapshots:
|
||||
|
||||
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: {}
|
||||
|
||||
estree-util-is-identifier-name@3.0.0: {}
|
||||
@@ -3082,14 +3483,28 @@ snapshots:
|
||||
|
||||
file-saver@2.0.5: {}
|
||||
|
||||
find-root@1.1.0: {}
|
||||
|
||||
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)):
|
||||
dependencies:
|
||||
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: {}
|
||||
|
||||
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:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
@@ -3176,10 +3591,17 @@ snapshots:
|
||||
|
||||
highlight.js@11.11.1: {}
|
||||
|
||||
html-escaper@2.0.2: {}
|
||||
|
||||
html-url-attributes@3.0.1: {}
|
||||
|
||||
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: {}
|
||||
|
||||
is-alphabetical@2.0.1: {}
|
||||
@@ -3189,19 +3611,31 @@ snapshots:
|
||||
is-alphabetical: 2.0.1
|
||||
is-decimal: 2.0.1
|
||||
|
||||
is-arrayish@0.2.1: {}
|
||||
|
||||
is-arrayish@0.3.2:
|
||||
optional: true
|
||||
|
||||
is-core-module@2.16.1:
|
||||
dependencies:
|
||||
hasown: 2.0.2
|
||||
|
||||
is-decimal@2.0.1: {}
|
||||
|
||||
is-hexadecimal@2.0.1: {}
|
||||
|
||||
is-plain-obj@4.1.0: {}
|
||||
|
||||
is-plain-object@5.0.0: {}
|
||||
|
||||
jiti@2.4.2: {}
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
jsesc@3.1.0: {}
|
||||
|
||||
json-parse-even-better-errors@2.3.1: {}
|
||||
|
||||
json2mq@0.2.0:
|
||||
dependencies:
|
||||
string-convert: 0.2.1
|
||||
@@ -3277,6 +3711,8 @@ snapshots:
|
||||
lightningcss-win32-arm64-msvc: 1.30.1
|
||||
lightningcss-win32-x64-msvc: 1.30.1
|
||||
|
||||
lines-and-columns@1.2.4: {}
|
||||
|
||||
lodash-es@4.17.21: {}
|
||||
|
||||
lodash.includes@4.3.0: {}
|
||||
@@ -3711,6 +4147,8 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
mrmime@2.0.1: {}
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
nanoid@3.3.11: {}
|
||||
@@ -3744,6 +4182,12 @@ snapshots:
|
||||
|
||||
object-assign@4.1.1: {}
|
||||
|
||||
opener@1.5.2: {}
|
||||
|
||||
parent-module@1.0.1:
|
||||
dependencies:
|
||||
callsites: 3.1.0
|
||||
|
||||
parse-entities@4.0.2:
|
||||
dependencies:
|
||||
'@types/unist': 2.0.11
|
||||
@@ -3754,12 +4198,23 @@ snapshots:
|
||||
is-decimal: 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:
|
||||
dependencies:
|
||||
entities: 6.0.1
|
||||
|
||||
path-parse@1.0.7: {}
|
||||
|
||||
path-to-regexp@8.2.0: {}
|
||||
|
||||
path-type@4.0.0: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
postcss-value-parser@4.2.0: {}
|
||||
@@ -4233,6 +4688,14 @@ snapshots:
|
||||
|
||||
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-stable-stringify@2.5.0: {}
|
||||
@@ -4283,6 +4746,12 @@ snapshots:
|
||||
is-arrayish: 0.3.2
|
||||
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: {}
|
||||
|
||||
socket.io-adapter@2.5.5:
|
||||
@@ -4328,6 +4797,8 @@ snapshots:
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
source-map@0.5.7: {}
|
||||
|
||||
space-separated-tokens@2.0.2: {}
|
||||
|
||||
sparse-bitfield@3.0.3:
|
||||
@@ -4374,10 +4845,14 @@ snapshots:
|
||||
client-only: 0.0.1
|
||||
react: 19.1.0
|
||||
|
||||
stylis@4.2.0: {}
|
||||
|
||||
stylis@4.3.2: {}
|
||||
|
||||
stylis@4.3.6: {}
|
||||
|
||||
supports-preserve-symlinks-flag@1.0.0: {}
|
||||
|
||||
swr@2.3.3(react@19.1.0):
|
||||
dependencies:
|
||||
dequal: 2.0.3
|
||||
@@ -4403,6 +4878,8 @@ snapshots:
|
||||
|
||||
toggle-selection@1.0.6: {}
|
||||
|
||||
totalist@3.0.1: {}
|
||||
|
||||
tr46@5.1.1:
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
@@ -4496,6 +4973,25 @@ snapshots:
|
||||
|
||||
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:
|
||||
dependencies:
|
||||
tr46: 5.1.1
|
||||
@@ -4505,6 +5001,8 @@ snapshots:
|
||||
|
||||
word@0.3.0: {}
|
||||
|
||||
ws@7.5.10: {}
|
||||
|
||||
ws@8.17.1: {}
|
||||
|
||||
xlsx@0.18.5:
|
||||
@@ -4521,6 +5019,8 @@ snapshots:
|
||||
|
||||
yallist@5.0.0: {}
|
||||
|
||||
yaml@1.10.2: {}
|
||||
|
||||
zrender@5.6.1:
|
||||
dependencies:
|
||||
tslib: 2.3.0
|
||||
|
||||
@@ -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;
|
||||
@@ -426,10 +426,10 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
|
||||
// PageContainer 内边距控制 - 完全移除左右空白
|
||||
pageContainer: {
|
||||
// 移除 PageContainer 内容区域的上下内边距 (Block 方向,即垂直方向)
|
||||
paddingBlockPageContainerContent: 0,
|
||||
//paddingBlockPageContainerContent: 0,
|
||||
// 移除 PageContainer 内容区域的左右内边距 (Inline 方向,即水平方向)
|
||||
// 这是消除左右空白的关键配置之一
|
||||
paddingInlinePageContainerContent: 0,
|
||||
//paddingInlinePageContainerContent: 0,
|
||||
}
|
||||
}}
|
||||
siderMenuType="group"
|
||||
@@ -463,23 +463,6 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
|
||||
contentWidth="Fluid"
|
||||
locale="zh-CN"
|
||||
>
|
||||
{/*
|
||||
内容区域边距移除方案说明:
|
||||
|
||||
为了完全移除页面内容的左右空白,采用了三层防护措施:
|
||||
|
||||
1. ProLayout 层级:通过 token.pageContainer.paddingInlinePageContainerContent = 0
|
||||
移除 ProLayout 组件默认的左右内边距
|
||||
|
||||
2. PageContainer 层级:
|
||||
- pageHeaderRender={false} 禁用头部渲染避免额外空间
|
||||
- token.paddingInlinePageContainerContent = 0 再次确保移除左右内边距
|
||||
- style.padding = 0 移除组件自身样式内边距
|
||||
|
||||
3. 最内层 div:padding = '0px' 作为最后一道防线
|
||||
|
||||
这样的多层配置确保在不同版本的 Ant Design Pro 中都能正常工作
|
||||
*/}
|
||||
<PageContainer
|
||||
// 移除默认的页面标题栏
|
||||
header={{ title: null }}
|
||||
@@ -514,6 +497,8 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
|
||||
margin: 0,
|
||||
overflow: 'auto',
|
||||
boxSizing: 'border-box',
|
||||
//background: 'blue',
|
||||
//height: '90vh',
|
||||
}}>
|
||||
{children}
|
||||
</div>
|
||||
@@ -528,7 +513,7 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
|
||||
open={showPersonalInfo}
|
||||
onCancel={handleClosePersonalInfo}
|
||||
footer={null}
|
||||
width={800}
|
||||
width='80%'
|
||||
destroyOnHidden
|
||||
>
|
||||
<PersonalInfo />
|
||||
|
||||
456
src/components/layout/Layout.tsx.bak
Normal file
456
src/components/layout/Layout.tsx.bak
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
@@ -6,7 +6,7 @@
|
||||
* @updated 使用原生fetch替代axios,符合项目技术规范
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import React, { useState, useCallback, useMemo } from 'react';
|
||||
import {
|
||||
Form,
|
||||
Input,
|
||||
@@ -20,9 +20,8 @@ import {
|
||||
Col,
|
||||
Modal,
|
||||
Tabs,
|
||||
Skeleton,
|
||||
} from 'antd';
|
||||
import { useUserInfo } from '@/store/userStore';
|
||||
import { useFullUserInfo } from '@/store/userStore';
|
||||
import {
|
||||
EditOutlined,
|
||||
MailOutlined,
|
||||
@@ -30,6 +29,8 @@ import {
|
||||
UserOutlined,
|
||||
TeamOutlined,
|
||||
IdcardOutlined,
|
||||
NotificationOutlined,
|
||||
ReloadOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { PageHeader } from '@ant-design/pro-components';
|
||||
|
||||
@@ -44,6 +45,7 @@ interface IUser {
|
||||
头像?: string;
|
||||
unionid?: string;
|
||||
openid?: string;
|
||||
bark密钥?: string; // 添加bark密钥字段
|
||||
角色?: {
|
||||
名称: string;
|
||||
描述: string;
|
||||
@@ -60,50 +62,35 @@ interface IUser {
|
||||
}
|
||||
|
||||
const PersonalInfo: React.FC = () => {
|
||||
const userInfo = useUserInfo(); // 从store获取用户信息
|
||||
const { userInfo: userData, refreshUserInfo, hasCompleteInfo, hasRoleHomePage } = useFullUserInfo(); // 使用新的完整用户信息hook
|
||||
const [form] = Form.useForm<IUser>();
|
||||
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 userId = userInfo._id;
|
||||
|
||||
// 获取用户信息的函数,使用 useCallback 优化性能
|
||||
const fetchUserInfo = useCallback(async () => {
|
||||
if (!userId) return;
|
||||
|
||||
// 手动刷新个人信息的函数
|
||||
const handleRefreshUserInfo = useCallback(async () => {
|
||||
setRefreshLoading(true);
|
||||
try {
|
||||
const response = await fetch(`/api/backstage/mine/info/${userId}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('获取个人信息失败');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
setUserData(data);
|
||||
form.setFieldsValue(data);
|
||||
await refreshUserInfo();
|
||||
message.success('个人信息刷新成功');
|
||||
// 更新表单数据
|
||||
form.setFieldsValue(userData);
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '获取个人信息失败');
|
||||
message.error('刷新个人信息失败');
|
||||
} finally {
|
||||
setRefreshLoading(false);
|
||||
}
|
||||
}, [userId, form]);
|
||||
|
||||
// 模块级注释:组件初始化时获取用户信息
|
||||
useEffect(() => {
|
||||
fetchUserInfo();
|
||||
}, [fetchUserInfo]);
|
||||
}, [refreshUserInfo, form, userData]);
|
||||
|
||||
// 更新个人信息的处理函数
|
||||
const onFinish = async (values: IUser) => {
|
||||
if (!userId) return;
|
||||
if (!userData?._id) return;
|
||||
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/backstage/mine/info/${userId}`, {
|
||||
const response = await fetch(`/api/backstage/mine/info/${userData._id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -118,7 +105,7 @@ const PersonalInfo: React.FC = () => {
|
||||
|
||||
message.success('更新成功');
|
||||
setEditModalVisible(false);
|
||||
await fetchUserInfo(); // 更新信息后重新获取
|
||||
await handleRefreshUserInfo(); // 更新信息后重新获取
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '更新失败');
|
||||
} finally {
|
||||
@@ -132,10 +119,10 @@ const PersonalInfo: React.FC = () => {
|
||||
return userData.角色.权限.map((item) => ({
|
||||
title: item.名称,
|
||||
key: item._id,
|
||||
children: item.子级.map((child) => ({
|
||||
children: item.子级?.map((child) => ({
|
||||
title: child.名称,
|
||||
key: child._id,
|
||||
})),
|
||||
})) || [],
|
||||
}));
|
||||
}, [userData]);
|
||||
|
||||
@@ -143,55 +130,134 @@ const PersonalInfo: React.FC = () => {
|
||||
<div>
|
||||
<PageHeader
|
||||
title="个人信息"
|
||||
subTitle="查看和更新您的个人信息"
|
||||
subTitle={
|
||||
hasCompleteInfo && hasRoleHomePage
|
||||
? "查看和更新您的个人信息"
|
||||
: !hasCompleteInfo
|
||||
? "⚠️ 个人信息不完整,请及时更新"
|
||||
: "⚠️ 角色主页路径缺失,请联系管理员配置"
|
||||
}
|
||||
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>,
|
||||
]}
|
||||
/>
|
||||
<Card style={{ marginBottom: 24 }}>
|
||||
{userData ? (
|
||||
<Row gutter={24}>
|
||||
<Col xs={24} sm={24} md={8} lg={6} style={{ textAlign: 'center' }}>
|
||||
<Avatar size={120} src={userData?.头像} icon={<UserOutlined />} />
|
||||
<h2 style={{ marginTop: 16 }}>{userData?.姓名}</h2>
|
||||
<p>{userData?.微信昵称}</p>
|
||||
</Col>
|
||||
<Col xs={24} sm={24} md={16} lg={18}>
|
||||
<Descriptions title="基本信息" column={1} bordered>
|
||||
<Descriptions.Item label={<MailOutlined />}> {userData?.邮箱}</Descriptions.Item>
|
||||
<Descriptions.Item label={<PhoneOutlined />}> {userData?.电话}</Descriptions.Item>
|
||||
<Descriptions.Item label={<TeamOutlined />}> {userData?.团队?.名称}</Descriptions.Item>
|
||||
<Descriptions.Item label={<IdcardOutlined />}> {userData?.角色?.名称}</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</Col>
|
||||
</Row>
|
||||
) : (
|
||||
<Skeleton active />
|
||||
)}
|
||||
</Card>
|
||||
<Row gutter={24}>
|
||||
{/* 左侧用户信息卡片 */}
|
||||
<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 />} />
|
||||
<h2 style={{ marginTop: 16, marginBottom: 8 }}>{userData?.姓名}</h2>
|
||||
<p style={{ color: '#666', marginBottom: 16 }}>{userData?.微信昵称 || '未设置'}</p>
|
||||
|
||||
<Card>
|
||||
<Tabs defaultActiveKey="1">
|
||||
<TabPane tab="团队与角色信息" key="1">
|
||||
<Descriptions column={1} bordered>
|
||||
<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>
|
||||
</TabPane>
|
||||
<TabPane tab="权限列表" key="2">
|
||||
<Tree
|
||||
treeData={permissionsTreeData}
|
||||
//defaultExpandAll//默认展开所有节点
|
||||
//默认收起所有节点
|
||||
defaultExpandParent={false}
|
||||
/>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</Card>
|
||||
<Descriptions column={1} size="small">
|
||||
<Descriptions.Item label={<MailOutlined />}>{userData?.邮箱}</Descriptions.Item>
|
||||
<Descriptions.Item label={<PhoneOutlined />}>{userData?.电话}</Descriptions.Item>
|
||||
<Descriptions.Item label={<TeamOutlined />}>{userData?.团队?.名称 || '未分配'}</Descriptions.Item>
|
||||
<Descriptions.Item label={<IdcardOutlined />}>{userData?.角色?.名称 || '未设置'}</Descriptions.Item>
|
||||
<Descriptions.Item label={
|
||||
<span>
|
||||
<NotificationOutlined style={{ marginRight: 4 }} />
|
||||
Bark推送
|
||||
</span>
|
||||
}>
|
||||
{userData?.bark密钥 ? (
|
||||
<span style={{ color: '#52c41a' }}>✅ 已配置</span>
|
||||
) : (
|
||||
<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>
|
||||
</Col>
|
||||
|
||||
{/* 右侧详细信息 */}
|
||||
<Col xs={24} sm={24} md={16} lg={16} xl={18}>
|
||||
<Card>
|
||||
<Tabs defaultActiveKey="1">
|
||||
<TabPane tab="团队与角色信息" key="1">
|
||||
<Descriptions title="详细信息" column={2} bordered>
|
||||
<Descriptions.Item label="团队名称" span={2}>{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>
|
||||
</TabPane>
|
||||
<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
|
||||
treeData={permissionsTreeData}
|
||||
//defaultExpandAll//默认展开所有节点
|
||||
//默认收起所有节点
|
||||
defaultExpandParent={false}
|
||||
showLine={{ showLeafIcon: false }}
|
||||
/>
|
||||
</div>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{/* 编辑信息的 Modal */}
|
||||
<Modal
|
||||
@@ -200,27 +266,70 @@ const PersonalInfo: React.FC = () => {
|
||||
onCancel={() => setEditModalVisible(false)}
|
||||
footer={null}
|
||||
destroyOnClose
|
||||
width="80%"
|
||||
>
|
||||
<Form form={form} onFinish={onFinish} layout="vertical">
|
||||
<Form.Item name="微信昵称" label="微信昵称">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="姓名" label="姓名" rules={[{ required: true, message: '请输入姓名' }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="电话" label="电话" rules={[{ required: true, message: '请输入电话' }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="邮箱" label="邮箱" rules={[{ required: true, message: '请输入邮箱' }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item style={{ textAlign: 'right' }}>
|
||||
<Button onClick={() => setEditModalVisible(false)} style={{ marginRight: 8 }}>
|
||||
取消
|
||||
</Button>
|
||||
<Button type="primary" htmlType="submit" loading={loading}>
|
||||
更新信息
|
||||
</Button>
|
||||
<Row gutter={24}>
|
||||
{/* 左侧基本信息 */}
|
||||
<Col xs={24} sm={24} md={12} lg={12}>
|
||||
<h4 style={{ marginBottom: 16, color: '#1890ff' }}>基本信息</h4>
|
||||
<Form.Item name="姓名" label="姓名" rules={[{ required: true, message: '请输入姓名' }]}>
|
||||
<Input placeholder="请输入姓名" />
|
||||
</Form.Item>
|
||||
<Form.Item name="电话" label="电话" rules={[{ required: true, message: '请输入电话' }]}>
|
||||
<Input placeholder="请输入电话" />
|
||||
</Form.Item>
|
||||
<Form.Item name="邮箱" label="邮箱" rules={[{ required: true, message: '请输入邮箱' }]}>
|
||||
<Input placeholder="请输入邮箱" />
|
||||
</Form.Item>
|
||||
</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>
|
||||
<Button type="primary" htmlType="submit" loading={loading}>
|
||||
更新信息
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
@@ -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: '/',
|
||||
},
|
||||
};
|
||||
152
src/components/layout/_defaultProps.tsx.bak
Normal file
152
src/components/layout/_defaultProps.tsx.bak
Normal 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',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -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',
|
||||
};
|
||||
@@ -13,6 +13,7 @@ const UserSchema: Schema = new Schema({
|
||||
头像: { type: String }, // 用户头像字段
|
||||
unionid: { type: String, unique: true, sparse: true }, // 添加sparse以允许null值
|
||||
openid: { type: String, unique: true, sparse: true }, // 添加openid字段,sparse允许null值
|
||||
bark密钥: { type: String }, // Bark推送设备密钥,用于iOS推送通知
|
||||
}, { timestamps: true }); // 自动添加创建时间和更新时间
|
||||
UserSchema.index({ 团队: 1 }); // 对团队字段建立索引
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ export interface IUser {
|
||||
头像?: string;
|
||||
unionid?: string;
|
||||
openid?: string;
|
||||
bark密钥?: string;
|
||||
}
|
||||
|
||||
// 定义团队接口类型
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { broadcastUpdate } from './sse'; // 引入广播功能
|
||||
import { Account } from '@/models';
|
||||
import connectDB from '@/lib/connectDB';
|
||||
import dayjs from 'dayjs';
|
||||
@@ -69,9 +68,6 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
|
||||
await account.save();
|
||||
|
||||
// 广播更新
|
||||
broadcastUpdate(account.团队);
|
||||
|
||||
res.status(200).json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('保存增长记录失败:', error);
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -28,7 +28,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
//关联角色
|
||||
.populate({
|
||||
path: '角色',
|
||||
select: '名称 描述 权限',
|
||||
select: '名称 描述 权限 主页', // 添加主页字段
|
||||
populate: {
|
||||
path: '权限',
|
||||
populate: { path: '子级' },
|
||||
@@ -59,6 +59,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
电话: user.电话,
|
||||
unionid: user.unionid,
|
||||
openid: user.openid,
|
||||
bark密钥: user.bark密钥, // 添加bark密钥字段
|
||||
};
|
||||
|
||||
res.status(200).json(userInfo );
|
||||
|
||||
@@ -50,13 +50,14 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
}
|
||||
} else if (req.method === 'PUT') {
|
||||
try {
|
||||
const { 客户, 产品, 成交日期, 应收金额, 收款金额, 待收款, 收款平台, 待收已收, 收款状态, 备注, 导购 } = req.body;
|
||||
const { 客户, 产品, 订单来源, 成交日期, 应收金额, 收款金额, 待收款, 收款平台, 待收已收, 收款状态, 备注, 导购 } = req.body;
|
||||
|
||||
const updatedSalesRecord = await SalesRecord.findByIdAndUpdate(
|
||||
id,
|
||||
{
|
||||
客户,
|
||||
产品,
|
||||
订单来源,
|
||||
成交日期,
|
||||
应收金额,
|
||||
收款金额,
|
||||
|
||||
@@ -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 连接已关闭(客户端断开连接)");
|
||||
});
|
||||
}
|
||||
@@ -35,6 +35,7 @@ export default connectDB(async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
_id: user._id,
|
||||
姓名: user.姓名,
|
||||
邮箱: user.邮箱,
|
||||
电话: user.电话, // 添加电话字段
|
||||
团队: user.团队, // 团队信息
|
||||
角色: {
|
||||
...user.角色.toObject(),
|
||||
@@ -44,6 +45,7 @@ export default connectDB(async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
头像: user.头像,
|
||||
unionid: user.unionid,
|
||||
openid: user.openid,
|
||||
bark密钥: user.bark密钥, // 添加bark密钥字段
|
||||
};
|
||||
|
||||
// 返回用户信息
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,84 +1,186 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Modal, Form, Input, Button, App } from 'antd';
|
||||
import { ISalesRecord } from '@/models/types';
|
||||
/**
|
||||
* @file 发货信息管理模态框组件
|
||||
* @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';
|
||||
|
||||
const { useApp } = App;
|
||||
const { Title, Text } = Typography;
|
||||
|
||||
// 组件属性接口定义
|
||||
interface ShipModalProps {
|
||||
visible: boolean;
|
||||
onOk: () => void;
|
||||
onCancel: () => void;
|
||||
record: ISalesRecord | null; // 传入的销售记录
|
||||
record: ISalesRecord | null;
|
||||
}
|
||||
|
||||
const ShipModal: React.FC<ShipModalProps> = ({ visible, onOk, onCancel, record }) => {
|
||||
const [form] = Form.useForm();
|
||||
const [logisticsNumbers, setLogisticsNumbers] = useState<{ [key: string]: string }>({}); // 保存每个产品的物流单号
|
||||
const userInfo = useUserInfo(); // 获取当前用户信息
|
||||
const { message } = useApp(); // 使用 useApp hook 获取 message 实例
|
||||
// 物流单号映射接口
|
||||
interface LogisticsNumbersMap {
|
||||
[productId: string]: string;
|
||||
}
|
||||
|
||||
// 发货产品信息接口
|
||||
interface ShippingProduct {
|
||||
productId: string;
|
||||
logisticsNumber: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发货信息管理模态框组件
|
||||
* 提供现代化的发货信息录入界面,支持多产品物流单号管理
|
||||
*/
|
||||
const ShipModal: React.FC<ShipModalProps> = ({ visible, onOk, onCancel, record }) => {
|
||||
// ==================== 状态管理 ====================
|
||||
const [form] = Form.useForm();
|
||||
const [logisticsNumbers, setLogisticsNumbers] = useState<LogisticsNumbersMap>({});
|
||||
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
|
||||
|
||||
// ==================== Hooks ====================
|
||||
const userInfo = useUserInfo();
|
||||
const { message } = useApp();
|
||||
|
||||
// ==================== 计算属性 ====================
|
||||
// 客户电话尾号显示
|
||||
const customerPhoneTail = useMemo(() => {
|
||||
return record?.客户?.电话 ? record.客户.电话.slice(-4) : '';
|
||||
}, [record?.客户?.电话]);
|
||||
|
||||
// 产品列表
|
||||
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}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`网络错误: ${response.status}`);
|
||||
}
|
||||
|
||||
const logisticsRecords = await response.json();
|
||||
|
||||
if (logisticsRecords && Array.isArray(logisticsRecords) && logisticsRecords.length > 0) {
|
||||
const updatedLogisticsNumbers: LogisticsNumbersMap = {};
|
||||
|
||||
// 填充已有的物流单号
|
||||
logisticsRecords.forEach((logisticsRecord: any) => {
|
||||
const productId = logisticsRecord.产品?._id || logisticsRecord.产品;
|
||||
if (productId && logisticsRecord.物流单号) {
|
||||
updatedLogisticsNumbers[productId] = logisticsRecord.物流单号;
|
||||
}
|
||||
});
|
||||
|
||||
setLogisticsNumbers(prev => ({ ...prev, ...updatedLogisticsNumbers }));
|
||||
console.log('已加载现有物流记录');
|
||||
} else {
|
||||
console.log('暂无物流记录,将创建新记录');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取物流记录失败:', error);
|
||||
message.warning('获取现有物流记录失败,将创建新记录');
|
||||
}
|
||||
}, [message]);
|
||||
|
||||
// ==================== 副作用处理 ====================
|
||||
useEffect(() => {
|
||||
if (record) {
|
||||
// 清空表单
|
||||
if (record && visible) {
|
||||
// 重置表单和状态
|
||||
form.resetFields();
|
||||
|
||||
// 设置客户电话尾号
|
||||
form.setFieldsValue({
|
||||
客户尾号: record?.客户?.电话 ? record.客户.电话.slice(-4) : '', // 自动填入客户电话尾号
|
||||
客户尾号: customerPhoneTail,
|
||||
});
|
||||
|
||||
// 初始化物流单号状态
|
||||
const initialLogisticsNumbers: { [key: string]: string } = {};
|
||||
record?.产品?.forEach(product => {
|
||||
initialLogisticsNumbers[product._id] = ''; // 初始化每个产品的物流单号为空
|
||||
const initialLogisticsNumbers: LogisticsNumbersMap = {};
|
||||
productList.forEach(product => {
|
||||
initialLogisticsNumbers[product._id] = '';
|
||||
});
|
||||
setLogisticsNumbers(initialLogisticsNumbers);
|
||||
|
||||
// 获取已有的物流记录并填充单号
|
||||
fetch(`/api/tools/logistics?关联记录=${record._id}`)
|
||||
.then(async (response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`网络错误或服务器错误: ${response.status}`);
|
||||
}
|
||||
const logisticsRecords = await response.json();
|
||||
|
||||
// 处理返回的物流记录(可能是空数组)
|
||||
if (logisticsRecords && Array.isArray(logisticsRecords) && logisticsRecords.length > 0) {
|
||||
const updatedLogisticsNumbers: { [key: string]: string } = { ...initialLogisticsNumbers };
|
||||
|
||||
// 遍历物流记录,将已有的单号填充到对应的产品
|
||||
logisticsRecords.forEach((logisticsRecord: any) => {
|
||||
const productId = logisticsRecord.产品?._id || logisticsRecord.产品;
|
||||
if (productId && logisticsRecord.物流单号) {
|
||||
updatedLogisticsNumbers[productId] = logisticsRecord.物流单号;
|
||||
}
|
||||
});
|
||||
|
||||
setLogisticsNumbers(updatedLogisticsNumbers);
|
||||
console.log('已加载现有物流记录');
|
||||
} else {
|
||||
// 没有物流记录是正常情况,不需要错误提示
|
||||
console.log('暂无物流记录,将创建新记录');
|
||||
}
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
console.error('获取物流记录失败:', err);
|
||||
// 只在真正的网络错误时才提示用户
|
||||
message.warning('获取现有物流记录失败,将创建新记录');
|
||||
});
|
||||
// 获取已有物流记录
|
||||
if (record._id) {
|
||||
fetchExistingLogistics(record._id);
|
||||
}
|
||||
}
|
||||
}, [record, form]);
|
||||
}, [record, visible, form, customerPhoneTail, productList, fetchExistingLogistics]);
|
||||
|
||||
const handleOk = async () => {
|
||||
// ==================== 事件处理器 ====================
|
||||
/**
|
||||
* 处理物流单号变更
|
||||
*/
|
||||
const handleLogisticsNumberChange = useCallback((productId: string, value: string) => {
|
||||
const cleanedValue = cleanLogisticsNumber(value);
|
||||
setLogisticsNumbers(prevState => ({
|
||||
...prevState,
|
||||
[productId]: cleanedValue
|
||||
}));
|
||||
}, [cleanLogisticsNumber]);
|
||||
|
||||
/**
|
||||
* 处理表单提交
|
||||
*/
|
||||
const handleSubmit = useCallback(async () => {
|
||||
try {
|
||||
setIsSubmitting(true);
|
||||
const values = await form.validateFields();
|
||||
|
||||
// 过滤出有物流单号的产品
|
||||
const productsWithLogisticsNumbers = (record?.产品 || []) // 确保 record?.产品 始终是数组
|
||||
// 过滤有效的物流信息
|
||||
const productsWithLogisticsNumbers: ShippingProduct[] = productList
|
||||
.map(product => ({
|
||||
productId: product._id,
|
||||
logisticsNumber: logisticsNumbers[product._id]
|
||||
logisticsNumber: logisticsNumbers[product._id]?.trim() || ''
|
||||
}))
|
||||
.filter(item => item.logisticsNumber); // 只保留填写了物流单号的产品
|
||||
.filter(item => item.logisticsNumber);
|
||||
|
||||
if (productsWithLogisticsNumbers.length === 0) {
|
||||
message.error('请至少为一个产品填写物流单号');
|
||||
@@ -89,8 +191,8 @@ const ShipModal: React.FC<ShipModalProps> = ({ visible, onOk, onCancel, record }
|
||||
...values,
|
||||
团队: userInfo.团队?._id,
|
||||
关联记录: record?._id,
|
||||
类型: 'SalesRecord', // 确保类型为销售记录
|
||||
产品: productsWithLogisticsNumbers // 只提交填写了物流单号的产品
|
||||
类型: 'SalesRecord',
|
||||
产品: productsWithLogisticsNumbers
|
||||
};
|
||||
|
||||
const response = await fetch('/api/tools/logistics', {
|
||||
@@ -106,63 +208,168 @@ const ShipModal: React.FC<ShipModalProps> = ({ visible, onOk, onCancel, record }
|
||||
}
|
||||
|
||||
message.success('发货信息提交成功');
|
||||
onOk(); // 关闭模态框
|
||||
} catch (error: unknown) {
|
||||
onOk();
|
||||
} catch (error) {
|
||||
console.error('发货信息提交失败:', error);
|
||||
message.error('发货信息提交失败');
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogisticsNumberChange = (productId: string, value: string) => {
|
||||
setLogisticsNumbers(prevState => ({
|
||||
...prevState,
|
||||
[productId]: value // 更新每个产品的物流单号
|
||||
}));
|
||||
};
|
||||
}, [form, productList, logisticsNumbers, userInfo.团队?._id, record?._id, message, onOk]);
|
||||
|
||||
// ==================== 渲染组件 ====================
|
||||
return (
|
||||
<Modal
|
||||
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}
|
||||
width={680}
|
||||
styles={{
|
||||
body: { padding: '24px' }
|
||||
}}
|
||||
footer={[
|
||||
<Button key="cancel" onClick={onCancel}>
|
||||
<Button key="cancel" onClick={onCancel} size="large">
|
||||
取消
|
||||
</Button>,
|
||||
<Button key="submit" type="primary" onClick={handleOk}>
|
||||
保存
|
||||
<Button
|
||||
key="submit"
|
||||
type="primary"
|
||||
onClick={handleSubmit}
|
||||
loading={isSubmitting}
|
||||
size="large"
|
||||
icon={<CheckCircleOutlined />}
|
||||
>
|
||||
保存发货信息
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item
|
||||
name="客户尾号"
|
||||
label="客户尾号"
|
||||
rules={[{ required: true, message: '请输入客户电话尾号' }]}
|
||||
>
|
||||
<Input placeholder="自动填入客户电话尾号" disabled />
|
||||
</Form.Item>
|
||||
<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
|
||||
name="客户尾号"
|
||||
label="客户电话尾号"
|
||||
rules={[{ required: true, message: '客户电话尾号不能为空' }]}
|
||||
>
|
||||
<Input
|
||||
prefix={<NumberOutlined style={{ color: '#8c8c8c' }} />}
|
||||
placeholder="自动填入"
|
||||
disabled
|
||||
style={{
|
||||
backgroundColor: '#f5f5f5',
|
||||
color: '#262626'
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
{/* 动态生成每个产品的物流单号输入框 */}
|
||||
{record?.产品?.map(product => (
|
||||
<Form.Item
|
||||
key={product._id}
|
||||
label={`物流单号 (${product.名称})`}
|
||||
>
|
||||
<Input
|
||||
placeholder={`请输入${product.名称}的物流单号`}
|
||||
value={logisticsNumbers[product._id] || ''}
|
||||
//onChange={e => handleLogisticsNumberChange(product._id, e.target.value)}
|
||||
onChange={e => {
|
||||
const cleanedValue = e.target.value.replace(/[\s.,/#!$%\^&\*;:{}=\-_`~()<>[\]'"|\\?@+]/g, '');//过滤特殊字符和空格
|
||||
handleLogisticsNumberChange(product._id, cleanedValue);
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
))}
|
||||
<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>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShipModal;
|
||||
export default React.memo(ShipModal);
|
||||
|
||||
641
src/pages/test/test5.tsx
Normal file
641
src/pages/test/test5.tsx
Normal 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;
|
||||
@@ -46,7 +46,11 @@ const useUserStore = create<UserStore>((set, get) => ({
|
||||
set({ userInfo });
|
||||
if (isBrowser) {
|
||||
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缺失');
|
||||
}
|
||||
|
||||
// 使用原生fetch替代axios
|
||||
const response = await fetch(`/api/user?userId=${userId}`, {
|
||||
// 使用完整的个人信息API获取详细数据
|
||||
const response = await fetch(`/api/backstage/mine/info/${userId}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -91,11 +95,10 @@ const useUserStore = create<UserStore>((set, get) => ({
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const updatedUserInfo = data.userInfo;
|
||||
const updatedUserInfo = await response.json();
|
||||
setUserInfo(updatedUserInfo); // 更新用户信息
|
||||
//打印更新成功提示
|
||||
console.log('用户信息更新成功!');
|
||||
console.log('用户信息更新成功!', updatedUserInfo);
|
||||
} catch (error) {
|
||||
console.error('获取用户信息失败:', error);
|
||||
message.error('无法更新用户信息');
|
||||
@@ -107,8 +110,8 @@ const useUserStore = create<UserStore>((set, get) => ({
|
||||
// 为用户的 homePath 提供一个钩子
|
||||
export const useUserHomePath = () => {
|
||||
const userInfo = useUserStore((state: UserStore) => state.userInfo);
|
||||
const homePath = userInfo && userInfo.角色 ? userInfo.角色.主页 : '/index';
|
||||
//console.log('homePath:', homePath);
|
||||
const homePath = userInfo && userInfo.角色 && userInfo.角色.主页 ? userInfo.角色.主页 : '/index';
|
||||
console.log('主页路径:', homePath, '用户角色信息:', userInfo?.角色);
|
||||
return homePath;
|
||||
};
|
||||
|
||||
@@ -122,4 +125,18 @@ export const useUserToken = () => {
|
||||
const refreshToken = useUserStore((state: UserStore) => state.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;
|
||||
|
||||
Reference in New Issue
Block a user