From 32f8d4ec956052ab7e6d3956cc0a233cb97ff859 Mon Sep 17 00:00:00 2001 From: RUI <298977887@qq.com> Date: Thu, 12 Jun 2025 03:12:05 +0800 Subject: [PATCH] 0612.1 --- package.json | 6 +- pnpm-lock.yaml | 500 ++++++++ src/components/TaskController.tsx | 170 --- src/components/layout/Layout.tsx | 25 +- src/components/layout/Layout.tsx.bak | 456 +++++++ src/components/layout/Navbar.tsx | 41 - src/components/layout/PerformanceMonitor.tsx | 51 - src/components/layout/PersonalInfo.tsx | 305 +++-- src/components/layout/_defaultProps.tsx | 26 - src/components/layout/_defaultProps.tsx.bak | 152 +++ src/components/layout/layoutConfig.ts | 79 -- src/models/index.ts | 1 + src/models/types.ts | 1 + .../accounts/accountgrowth/register.ts | 4 - .../backstage/accounts/accountgrowth/sse.ts | 38 - src/pages/api/backstage/accounts/sse.ts | 37 - src/pages/api/backstage/mine/info/[id].ts | 3 +- src/pages/api/backstage/sales/Records/[id].ts | 3 +- src/pages/api/sse.ts | 52 - src/pages/api/user.ts | 2 + src/pages/team/SaleRecord/sales-modal.tsx | 1053 ++++++++++------- src/pages/team/SaleRecord/ship-modal.tsx | 399 +++++-- src/pages/test/test5.tsx | 641 ++++++++++ src/store/userStore.ts | 33 +- 24 files changed, 2959 insertions(+), 1119 deletions(-) delete mode 100644 src/components/TaskController.tsx create mode 100644 src/components/layout/Layout.tsx.bak delete mode 100644 src/components/layout/Navbar.tsx delete mode 100644 src/components/layout/PerformanceMonitor.tsx delete mode 100644 src/components/layout/_defaultProps.tsx create mode 100644 src/components/layout/_defaultProps.tsx.bak delete mode 100644 src/components/layout/layoutConfig.ts delete mode 100644 src/pages/api/backstage/accounts/accountgrowth/sse.ts delete mode 100644 src/pages/api/backstage/accounts/sse.ts delete mode 100644 src/pages/api/sse.ts create mode 100644 src/pages/test/test5.tsx diff --git a/package.json b/package.json index a865abd..47d7771 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a5af8d3..453f87b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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 diff --git a/src/components/TaskController.tsx b/src/components/TaskController.tsx deleted file mode 100644 index f89db4e..0000000 --- a/src/components/TaskController.tsx +++ /dev/null @@ -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(null); - // 是否正在计时的状态 - const [isCounting, setIsCounting] = useState(false); - // 用于存储 EventSource 的引用 - const eventSourceRef = useRef(null); - const lastQueryResultRef = useRef(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 = ( -
- {lastQueryResultRef.current ? ( -

{lastQueryResultRef.current}

- ) : ( -

暂无最新物流详情

- )} -
- );*/ - - // 计算按钮显示的文字 - const displayText = isCounting ? `剩余: ${remainingTime} 秒` : '物流查询'; - - return ( - - - - ); -}; - -export default TaskController; diff --git a/src/components/layout/Layout.tsx b/src/components/layout/Layout.tsx index d7016a1..070d4a0 100644 --- a/src/components/layout/Layout.tsx +++ b/src/components/layout/Layout.tsx @@ -426,10 +426,10 @@ const Layout: React.FC = ({ 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 = ({ 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 中都能正常工作 - */} = ({ children }) => { margin: 0, overflow: 'auto', boxSizing: 'border-box', + //background: 'blue', + //height: '90vh', }}> {children} @@ -528,7 +513,7 @@ const Layout: React.FC = ({ children }) => { open={showPersonalInfo} onCancel={handleClosePersonalInfo} footer={null} - width={800} + width='80%' destroyOnHidden > diff --git a/src/components/layout/Layout.tsx.bak b/src/components/layout/Layout.tsx.bak new file mode 100644 index 0000000..3d5b940 --- /dev/null +++ b/src/components/layout/Layout.tsx.bak @@ -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 ( +
+ {props.children} + +
+ ); + }; + + const List: React.FC<{ title: string; style?: React.CSSProperties }> = ( + props, + ) => { + const { token } = theme.useToken(); + + return ( +
+
+ {props.title} +
+
+ {new Array(6).fill(1).map((_, index) => { + return 具体的解决方案-{index}; + })} +
+
+ ); + }; + + const MenuCard = () => { + const { token } = theme.useToken(); + return ( +
+ + +
+ + +
+ +
+
+ 热门产品 +
+ {new Array(3).fill(1).map((_name, index) => { + return ( +
+ +
+
+ Ant Design +
+
+ 杭州市较知名的 UI 设计语言 +
+
+
+ ); + })} +
+
+ } + > +
+ 企业级资产中心 + +
+ + + ); + }; + + const SearchInput = () => { + const { token } = theme.useToken(); + return ( +
{ + e.stopPropagation(); + e.preventDefault(); + }} + > + + } + placeholder="搜索方案" + variant="borderless" + /> + +
+ ); + }; + + export default () => { + const [settings, setSetting] = useState | 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
; + } + return ( +
+ + { + return document.getElementById('test-pro-layout') || document.body; + }} + > + { + return ( + , + label: '退出登录', + }, + ], + }} + > + {dom} + + ); + }, + }} + actionsRender={(props) => { + if (props.isMobile) return []; + if (typeof window === 'undefined') return []; + return [ + props.layout !== 'side' && document.body.clientWidth > 1400 ? ( + + ) : undefined, + , + , + , + ]; + }} + headerTitleRender={(logo, title, _) => { + const defaultDom = ( + + {logo} + {title} + + ); + if (typeof window === 'undefined') return defaultDom; + if (document.body.clientWidth < 1400) { + return defaultDom; + } + if (_.isMobile) return defaultDom; + return ( + <> + {defaultDom} + + + ); + }} + menuFooterRender={(props) => { + if (props?.collapsed) return undefined; + return ( +
+
© 2021 Made with love
+
by Ant Design
+
+ ); + }} + onMenuHeaderClick={(e) => console.log(e)} + menuItemRender={(item, dom) => ( +
{ + setPathname(item.path || '/welcome'); + }} + > + {dom} +
+ )} + {...settings} + > + 操作, + , + , + ]} + subTitle="简单的描述" + footer={[ + , + , + ]} + > + +
+ + + + { + if (typeof window === 'undefined') return e; + return document.getElementById('test-pro-layout'); + }} + settings={settings} + onSettingChange={(changeSetting) => { + setSetting(changeSetting); + }} + disableUrlParams={false} + /> + + + +
+ ); + }; \ No newline at end of file diff --git a/src/components/layout/Navbar.tsx b/src/components/layout/Navbar.tsx deleted file mode 100644 index 5cc501b..0000000 --- a/src/components/layout/Navbar.tsx +++ /dev/null @@ -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 = ({ isCollapsed, setIsCollapsed }) => { - return ( - - ); -}; - -export default Navbar; diff --git a/src/components/layout/PerformanceMonitor.tsx b/src/components/layout/PerformanceMonitor.tsx deleted file mode 100644 index 3a80d75..0000000 --- a/src/components/layout/PerformanceMonitor.tsx +++ /dev/null @@ -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 = ({ - componentName, - children, - enableLogging = process.env.NODE_ENV === 'development' -}) => { - const renderStartTime = useRef(0); - const renderCount = useRef(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); \ No newline at end of file diff --git a/src/components/layout/PersonalInfo.tsx b/src/components/layout/PersonalInfo.tsx index a8f36e1..0bae70e 100644 --- a/src/components/layout/PersonalInfo.tsx +++ b/src/components/layout/PersonalInfo.tsx @@ -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(); const [loading, setLoading] = useState(false); - const [userData, setUserData] = useState(null); // 保存用户数据 + const [refreshLoading, setRefreshLoading] = useState(false); const [editModalVisible, setEditModalVisible] = useState(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 = () => {
} onClick={() => setEditModalVisible(true)}> + , + , ]} /> - - {userData ? ( - - - } /> -

{userData?.姓名}

-

{userData?.微信昵称}

- - - - }> {userData?.邮箱} - }> {userData?.电话} - }> {userData?.团队?.名称} - }> {userData?.角色?.名称} - - -
- ) : ( - - )} -
+ + {/* 左侧用户信息卡片 */} + + + {userData && userData._id ? ( +
+ } /> +

{userData?.姓名}

+

{userData?.微信昵称 || '未设置'}

+ + + }>{userData?.邮箱} + }>{userData?.电话} + }>{userData?.团队?.名称 || '未分配'} + }>{userData?.角色?.名称 || '未设置'} + + + Bark推送 + + }> + {userData?.bark密钥 ? ( + ✅ 已配置 + ) : ( + ❌ 未配置 + )} + + +
+ ) : ( +
+ +

请先登录以查看个人信息

+
+ )} +
+ - - - - - {userData?.团队?.名称} - {userData?.团队?.拥有者?.姓名} - {userData?.角色?.名称} - {userData?.角色?.描述} - - - - - - - + {/* 右侧详细信息 */} + + + + + + {userData?.团队?.名称} + {userData?.团队?.拥有者?.姓名} + {userData?.角色?.名称} + {userData?.角色?.描述} + + + {userData?.角色?.主页 || '未设置'} + + + + + + + + {userData?.bark密钥 ? ( + ✅ 已配置 + ) : ( + ❌ 未配置 + )} + + + {userData?.bark密钥 ? ( + + {userData.bark密钥.substring(0, 8)}...{userData.bark密钥.substring(userData.bark密钥.length - 8)} + + ) : ( + 未设置 + )} + + +
+

• 在App Store下载并安装"Bark"应用

+

• 打开应用,复制设备密钥

+

• 在个人信息编辑中填入密钥即可接收推送通知

+
+
+
+
+ +
+

我的权限

+ +
+
+
+
+ +
{/* 编辑信息的 Modal */} { onCancel={() => setEditModalVisible(false)} footer={null} destroyOnClose + width="80%" >
- - - - - - - - - - - - - - - + + {/* 左侧基本信息 */} + +

基本信息

+ + + + + + + + + + + + {/* 右侧扩展信息 */} + +

扩展配置

+ + + + + + Bark推送密钥 + + } + extra="从Bark应用中获取的设备密钥,用于接收系统推送通知" + > + {}, + }} + /> + + +
+ + +
+ +
+ + +
+
diff --git a/src/components/layout/_defaultProps.tsx b/src/components/layout/_defaultProps.tsx deleted file mode 100644 index d1197d9..0000000 --- a/src/components/layout/_defaultProps.tsx +++ /dev/null @@ -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: , - component: './DynamicComponent', // 这里应指向实际组件路径 - routes: permission.子级 && generateDynamicRoutes(permission.子级) - })); -}; - -export default { - route: { - path: '/', - routes: [], // 初始化时不包含任何静态路由 - }, - location: { - pathname: '/', - }, -}; diff --git a/src/components/layout/_defaultProps.tsx.bak b/src/components/layout/_defaultProps.tsx.bak new file mode 100644 index 0000000..ff7da5e --- /dev/null +++ b/src/components/layout/_defaultProps.tsx.bak @@ -0,0 +1,152 @@ +import { + ChromeFilled, + CrownFilled, + SmileFilled, + TabletFilled, + } from '@ant-design/icons'; + + export default { + route: { + path: '/', + routes: [ + { + path: '/welcome', + name: '欢迎', + icon: , + component: './Welcome', + }, + { + path: '/admin', + name: '管理端', + icon: , + 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: , + component: './Welcome', + }, + { + path: '/admin/sub-page3', + name: '三级页面', + icon: , + component: './Welcome', + }, + ], + }, + { + name: '用户端', + icon: , + path: '/list', + component: './ListTableList', + routes: [ + { + path: '/list/sub-page', + name: '列表页面', + icon: , + routes: [ + { + path: 'sub-sub-page1', + name: '一一级列表页面', + icon: , + component: './Welcome', + }, + { + path: 'sub-sub-page2', + name: '一二级列表页面', + icon: , + component: './Welcome', + }, + { + path: 'sub-sub-page3', + name: '一三级列表页面', + icon: , + component: './Welcome', + }, + ], + }, + { + path: '/list/sub-page2', + name: '二级列表页面', + icon: , + component: './Welcome', + }, + { + path: '/list/sub-page3', + name: '三级列表页面', + icon: , + component: './Welcome', + }, + ], + }, + { + path: 'https://ant.design', + name: 'Ant Design 官网外链', + icon: , + }, + ], + }, + 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', + }, + ], + }; \ No newline at end of file diff --git a/src/components/layout/layoutConfig.ts b/src/components/layout/layoutConfig.ts deleted file mode 100644 index dc2751c..0000000 --- a/src/components/layout/layoutConfig.ts +++ /dev/null @@ -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 => ({ - 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', -}; \ No newline at end of file diff --git a/src/models/index.ts b/src/models/index.ts index 36d1abe..a070a72 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -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 }); // 对团队字段建立索引 diff --git a/src/models/types.ts b/src/models/types.ts index 23e9363..523dfe6 100644 --- a/src/models/types.ts +++ b/src/models/types.ts @@ -12,6 +12,7 @@ export interface IUser { 头像?: string; unionid?: string; openid?: string; + bark密钥?: string; } // 定义团队接口类型 diff --git a/src/pages/api/backstage/accounts/accountgrowth/register.ts b/src/pages/api/backstage/accounts/accountgrowth/register.ts index 0fe08d5..1f983ce 100644 --- a/src/pages/api/backstage/accounts/accountgrowth/register.ts +++ b/src/pages/api/backstage/accounts/accountgrowth/register.ts @@ -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); diff --git a/src/pages/api/backstage/accounts/accountgrowth/sse.ts b/src/pages/api/backstage/accounts/accountgrowth/sse.ts deleted file mode 100644 index b6357d1..0000000 --- a/src/pages/api/backstage/accounts/accountgrowth/sse.ts +++ /dev/null @@ -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; diff --git a/src/pages/api/backstage/accounts/sse.ts b/src/pages/api/backstage/accounts/sse.ts deleted file mode 100644 index 8815e30..0000000 --- a/src/pages/api/backstage/accounts/sse.ts +++ /dev/null @@ -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; diff --git a/src/pages/api/backstage/mine/info/[id].ts b/src/pages/api/backstage/mine/info/[id].ts index 604bf85..11d56d8 100644 --- a/src/pages/api/backstage/mine/info/[id].ts +++ b/src/pages/api/backstage/mine/info/[id].ts @@ -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 ); diff --git a/src/pages/api/backstage/sales/Records/[id].ts b/src/pages/api/backstage/sales/Records/[id].ts index 44719b7..5904291 100644 --- a/src/pages/api/backstage/sales/Records/[id].ts +++ b/src/pages/api/backstage/sales/Records/[id].ts @@ -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, { 客户, 产品, + 订单来源, 成交日期, 应收金额, 收款金额, diff --git a/src/pages/api/sse.ts b/src/pages/api/sse.ts deleted file mode 100644 index 0cec577..0000000 --- a/src/pages/api/sse.ts +++ /dev/null @@ -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 连接已关闭(客户端断开连接)"); - }); -} diff --git a/src/pages/api/user.ts b/src/pages/api/user.ts index 1ce2dde..81d2bc5 100644 --- a/src/pages/api/user.ts +++ b/src/pages/api/user.ts @@ -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密钥字段 }; // 返回用户信息 diff --git a/src/pages/team/SaleRecord/sales-modal.tsx b/src/pages/team/SaleRecord/sales-modal.tsx index faccc2b..637db5d 100644 --- a/src/pages/team/SaleRecord/sales-modal.tsx +++ b/src/pages/team/SaleRecord/sales-modal.tsx @@ -1,44 +1,56 @@ /** - * 作者: 阿瑞 - * 功能: 销售记录编辑模态框 - * 版本: 1.0.0 + * @file 销售记录编辑模态框组件 + * @author 阿瑞 + * @description 用于编辑销售记录的模态框,支持完整的订单信息管理 + * @version 2.0.0 */ -import React, { useEffect, useState } from 'react'; -import { - Modal, - Form, - Input, - Select, - DatePicker, - Button, - InputNumber, - Row, - Col, - Space, - Spin, - Typography, - Tooltip, - Card, - App + +import React, { useEffect, useState, useCallback, useMemo } from 'react'; +import { + Modal, + Form, + Input, + Select, + DatePicker, + Button, + InputNumber, + Row, + Col, + Space, + Spin, + Typography, + Tooltip, + Card, + App, + Avatar, + Tag, + Divider } from 'antd'; -import { - UserOutlined, - ShoppingCartOutlined, - CalendarOutlined, - DollarOutlined, +import { + UserOutlined, + ShoppingCartOutlined, + CalendarOutlined, + DollarOutlined, BankOutlined, CreditCardOutlined, - PictureOutlined + PictureOutlined, + EditOutlined, + PlusOutlined, + TeamOutlined, + PhoneOutlined, + InfoCircleOutlined, + MoneyCollectOutlined, + ShopOutlined } from '@ant-design/icons'; import dayjs from 'dayjs'; -import { ISalesRecord, ICustomer, IProduct, IPaymentPlatform } from '@/models/types'; +import { ISalesRecord, ICustomer, IProduct, IPaymentPlatform, IAccount } from '@/models/types'; import { useUserInfo } from '@/store/userStore'; const { useApp } = App; - const { Title, Text } = Typography; const { Option } = Select; +// 组件属性接口定义 interface SalesModalProps { visible: boolean; onOk: () => void; @@ -46,135 +58,287 @@ interface SalesModalProps { record?: ISalesRecord | null; } -/** - * 产品选项组件,显示图片、名称和价格 - */ -const ProductSelectOption = ({ product }: { product: IProduct }) => { - const [imageLoaded, setImageLoaded] = useState(false); - const [imageSrc, setImageSrc] = useState(null); - - // 加载产品图片 - useEffect(() => { - if (!product._id) return; - - const fetchImage = async () => { - try { - const response = await fetch(`/api/products/images/${product._id}`); - if (response.ok) { - const data = await response.json(); - if (data && data.image) { - setImageSrc(data.image); - setImageLoaded(true); - } - } - } catch (error: unknown) { - console.error('获取产品图片失败:', error); - } - }; - - fetchImage(); - }, [product._id]); +// 产品选项组件属性接口 +interface ProductSelectOptionProps { + product: IProduct; +} - return ( -
- {/* 产品图片 */} -
- {!imageLoaded ? ( - - ) : ( - {product.名称} - )} -
- - {/* 产品信息 */} -
-
- {product.名称} +/** + * 产品选项组件 + * 在下拉选择器中显示产品图片、名称和价格信息 + */ +const ProductSelectOption: React.FC = React.memo(({ product }) => { + const [imageLoaded, setImageLoaded] = useState(false); + const [imageSrc, setImageSrc] = useState(null); + + // 获取产品图片 + const fetchProductImage = useCallback(async () => { + if (!product._id) return; + + try { + const response = await fetch(`/api/products/images/${product._id}`); + if (response.ok) { + const data = await response.json(); + if (data?.image) { + setImageSrc(data.image); + setImageLoaded(true); + } + } + } catch (error) { + console.error('获取产品图片失败:', error); + } + }, [product._id]); + + useEffect(() => { + fetchProductImage(); + }, [fetchProductImage]); + + return ( +
+ {/* 产品图片容器 */} +
+ {!imageLoaded ? ( + + ) : ( + {product.名称} + )} +
+ + {/* 产品信息 */} +
+
+ {product.名称} +
+ + + ¥{(product as any).价格?.售价 || '未设定'} + +
- - ¥{(product as any).价格?.售价 || '未知'} - -
-
- ); -}; + ); +}); /** * 销售记录编辑模态框组件 + * 提供现代化的销售记录编辑界面,支持宽屏布局和完整的订单管理功能 */ const SalesModal: React.FC = ({ visible, onOk, onCancel, record }) => { + // ==================== 状态管理 ==================== const [form] = Form.useForm(); const [customers, setCustomers] = useState([]); const [products, setProducts] = useState([]); const [paymentPlatforms, setPaymentPlatforms] = useState([]); - const userInfo = useUserInfo(); // 获取当前用户信息 + const [accounts, setAccounts] = useState([]); const [users, setUsers] = useState([]); - const { message } = useApp(); // 使用 useApp hook 获取 message 实例 - - // 加载状态 const [loading, setLoading] = useState(false); const [submitting, setSubmitting] = useState(false); - // 基于收款状态计算的额外字段显示控制 + // ==================== Hooks ==================== + const userInfo = useUserInfo(); + const { message } = useApp(); + + // ==================== 计算属性 ==================== + // 监听收款状态变化 const paymentStatus = Form.useWatch('收款状态', form); + // 是否为编辑模式 + const isEditMode = useMemo(() => Boolean(record?._id), [record?._id]); + + // 模态框标题 + const modalTitle = useMemo(() => ( + + : } + style={{ + backgroundColor: isEditMode ? '#1890ff' : '#52c41a', + fontSize: '16px' + }} + /> + + {isEditMode ? '编辑销售记录' : '新增销售记录'} + + {record && ( + + 订单ID: {record._id?.slice(-6)} + + )} + + ), [isEditMode, record]); + + // ==================== 工具函数 ==================== /** - * 模块级注释:数据初始化与表单设置 - * 当record或团队ID变化时加载相关数据并设置表单初始值 + * 获取用户数据 + */ + const fetchUsers = useCallback(async (teamId: string) => { + try { + const response = await fetch(`/api/backstage/users?teamId=${teamId}`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + setUsers(data.users || []); + return data.users || []; + } catch (error) { + console.error('加载用户数据失败:', error); + message.error('加载用户数据失败'); + return []; + } + }, [message]); + + /** + * 获取客户数据 + */ + const fetchCustomers = useCallback(async (teamId: string) => { + try { + const response = await fetch(`/api/backstage/customers?teamId=${teamId}`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + setCustomers(data.customers || []); + return data.customers || []; + } catch (error) { + console.error('加载客户数据失败:', error); + message.error('加载客户数据失败'); + return []; + } + }, [message]); + + /** + * 获取产品数据 + */ + const fetchProducts = useCallback(async (teamId: string) => { + try { + const response = await fetch(`/api/backstage/products?teamId=${teamId}`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + setProducts(data.products || []); + return data.products || []; + } catch (error) { + console.error('加载产品数据失败:', error); + message.error('加载产品数据失败'); + return []; + } + }, [message]); + + /** + * 获取收款平台数据 + */ + const fetchPaymentPlatforms = useCallback(async (teamId: string) => { + try { + const response = await fetch(`/api/backstage/payment-platforms?teamId=${teamId}`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + setPaymentPlatforms(data.platforms || []); + return data.platforms || []; + } catch (error) { + console.error('加载收款平台数据失败:', error); + message.error('加载收款平台数据失败'); + return []; + } + }, [message]); + + /** + * 获取来源店铺数据 + */ + const fetchAccounts = useCallback(async (teamId: string) => { + try { + const response = await fetch(`/api/backstage/accounts?teamId=${teamId}`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + const sortedAccounts = (data.accounts || []).sort((a: IAccount, b: IAccount) => + parseInt(a.账号编号) - parseInt(b.账号编号) + ); + setAccounts(sortedAccounts); + return sortedAccounts; + } catch (error) { + console.error('加载来源店铺数据失败:', error); + message.error('加载来源店铺数据失败'); + return []; + } + }, [message]); + + /** + * 自动计算待收款金额 + */ + const calculatePendingAmount = useCallback(() => { + const receivableAmount = form.getFieldValue('应收金额') || 0; + const receivedAmount = form.getFieldValue('收款金额') || 0; + + if (form.getFieldValue('收款状态') !== '全款') { + form.setFieldValue('待收款', receivableAmount - receivedAmount); + } else { + form.setFieldValue('待收款', 0); + } + }, [form]); + + // ==================== 副作用处理 ==================== + /** + * 数据初始化与表单设置 */ useEffect(() => { if (visible && userInfo.团队?._id) { setLoading(true); const teamId = userInfo.团队._id; - - // 并行请求数据以提高加载速度 - Promise.all([ - fetchPaymentPlatforms(teamId), - fetchCustomers(teamId), - fetchProducts(teamId), - fetchUsers(teamId) - ]).finally(() => { + + // 并行加载所有必要数据 + Promise.all([ + fetchPaymentPlatforms(teamId), + fetchCustomers(teamId), + fetchProducts(teamId), + fetchUsers(teamId), + fetchAccounts(teamId) + ]).finally(() => { setLoading(false); - - // 如果有记录,设置表单值 - if (record) { - form.setFieldsValue({ - ...record, - 导购: record.导购?._id, - 成交日期: record.成交日期 ? dayjs(record.成交日期) : null, - 客户: record.客户?._id, - 产品: record.产品?.map(p => p._id) || [], - 收款平台: record.收款平台?._id, - }); + + // 设置表单初始值 + if (record) { + form.setFieldsValue({ + ...record, + 导购: record.导购?._id, + 成交日期: record.成交日期 ? dayjs(record.成交日期) : null, + 客户: record.客户?._id, + 产品: record.产品?.map(p => p._id) || [], + 收款平台: record.收款平台?._id, + 订单来源: record.订单来源?._id, + }); } else { form.resetFields(); - // 设置默认值 form.setFieldsValue({ 成交日期: dayjs(), 收款状态: '全款', @@ -183,117 +347,41 @@ const SalesModal: React.FC = ({ visible, onOk, onCancel, record }); } }); - } - }, [record, visible, userInfo.团队?._id]); + } + }, [record, visible, userInfo.团队?._id, form, fetchPaymentPlatforms, fetchCustomers, fetchProducts, fetchUsers, fetchAccounts]); - // 监听应收金额和收款金额变化,自动计算待收款 + /** + * 监听金额变化,自动计算待收款 + */ useEffect(() => { - - // 添加表单值变化监听 - const calculatePendingAmount = () => { - const receivableAmount = form.getFieldValue('应收金额') || 0; - const receivedAmount = form.getFieldValue('收款金额') || 0; - - // 只有当收款状态不是"全款"时才计算 - if (form.getFieldValue('收款状态') !== '全款') { - form.setFieldValue('待收款', receivableAmount - receivedAmount); - } else { - form.setFieldValue('待收款', 0); - } - }; - - form.getFieldInstance('应收金额')?.addEventListener('change', calculatePendingAmount); - form.getFieldInstance('收款金额')?.addEventListener('change', calculatePendingAmount); - + const fieldsToWatch = ['应收金额', '收款金额', '收款状态']; + + const unsubscribe = fieldsToWatch.map(fieldName => { + return form.getFieldInstance(fieldName)?.addEventListener?.('change', calculatePendingAmount); + }); + return () => { - form.getFieldInstance('应收金额')?.removeEventListener('change', calculatePendingAmount); - form.getFieldInstance('收款金额')?.removeEventListener('change', calculatePendingAmount); + unsubscribe.forEach(unsub => { + if (typeof unsub === 'function') unsub(); + }); }; - }, [form]); + }, [form, calculatePendingAmount]); + // ==================== 事件处理器 ==================== /** - * 模块级注释:数据获取函数 + * 处理表单提交 */ - const fetchUsers = async (teamId: string) => { - try { - const response = await fetch(`/api/backstage/users?teamId=${teamId}`); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const data = await response.json(); - setUsers(data.users); - return data.users; - } catch (error: unknown) { - console.error('加载用户数据失败:', error); - message.error('加载用户数据失败'); - return []; - } - }; - - const fetchCustomers = async (teamId: string) => { - try { - const response = await fetch(`/api/backstage/customers?teamId=${teamId}`); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const data = await response.json(); - setCustomers(data.customers); - return data.customers; - } catch (error: unknown) { - console.error('加载客户数据失败:', error); - message.error('加载客户数据失败'); - return []; - } - }; - - const fetchProducts = async (teamId: string) => { - try { - const response = await fetch(`/api/backstage/products?teamId=${teamId}`); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const data = await response.json(); - setProducts(data.products); - return data.products; - } catch (error: unknown) { - console.error('加载产品数据失败:', error); - message.error('加载产品数据失败'); - return []; - } - }; - - const fetchPaymentPlatforms = async (teamId: string) => { - try { - const response = await fetch(`/api/backstage/payment-platforms?teamId=${teamId}`); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const data = await response.json(); - setPaymentPlatforms(data.platforms); - return data.platforms; - } catch (error: unknown) { - console.error('加载收款平台数据失败:', error); - message.error('加载收款平台数据失败'); - return []; - } - }; - - /** - * 模块级注释:表单提交处理 - */ - const handleOk = async () => { + const handleSubmit = useCallback(async () => { try { setSubmitting(true); const values = await form.validateFields(); - - // 准备提交数据 + const updatedRecord = { ...values, 成交日期: values.成交日期 ? values.成交日期.toISOString() : null, }; if (record?._id) { - // 更新已有记录 const response = await fetch(`/api/backstage/sales/Records/${record._id}`, { method: 'PUT', headers: { @@ -301,209 +389,367 @@ const SalesModal: React.FC = ({ visible, onOk, onCancel, record }, body: JSON.stringify(updatedRecord) }); - + if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } - - message.success('订单更新成功'); + + message.success('销售记录更新成功'); } else { - // 创建新记录 (实际未实现) message.error('系统不支持创建新记录,请联系管理员'); return; } - onOk(); // 关闭模态框并刷新页面 + onOk(); } catch (error) { - console.error('订单保存失败:', error); - message.error('订单保存失败,请检查表单数据'); + console.error('销售记录保存失败:', error); + message.error('保存失败,请检查表单数据'); } finally { setSubmitting(false); } - }; + }, [form, record?._id, message, onOk]); + // ==================== 渲染组件 ==================== return ( - - {record ? '编辑订单记录' : '添加订单记录'} - - } + title={modalTitle} onCancel={onCancel} - width={700} - styles={{ - body: { maxHeight: '75vh', overflow: 'auto', padding: '12px 24px' } - }} + width='90%' + centered footer={[ - , - , ]} maskClosable={false} destroyOnHidden={true} > - -
- {loading &&
加载数据中...
} -
+
+ {/* 基本信息区块 */} - 基本信息} - style={{ marginBottom: 16 }} + + } style={{ backgroundColor: '#1890ff' }} /> + 基本信息 + + } + size="small" + style={{ + marginBottom: '24px', + borderRadius: '8px', + boxShadow: '0 2px 8px rgba(0,0,0,0.06)' + }} + styles={{ + body: { padding: '24px' } + }} > - - + + + + + 导购人员 + + } + rules={[{ required: true, message: '请选择导购人员' }]} + > + + + + + + + 客户信息 + + } + rules={[{ required: true, message: '请选择客户' }]} + > + + + + + + + 来源店铺 + + } + rules={[{ required: true, message: '请选择来源店铺' }]} + > + + + + + + + 成交日期 + + } + rules={[{ required: true, message: '请选择成交日期' }]} + > + + + + + + + + + 选择产品 + + } + rules={[{ required: true, message: '请选择产品' }]} > - - - - - - - - - - - - - - - - } - allowClear={false} - /> - - - + + + 备注信息 + + } > - + + + + 财务信息 + + + {/* 财务信息区块 */} - 财务信息} - style={{ marginBottom: 16 }} + + } style={{ backgroundColor: '#fa541c' }} /> + 收款信息 + + } + size="small" + style={{ + marginBottom: '16px', + borderRadius: '8px', + boxShadow: '0 2px 8px rgba(0,0,0,0.06)' + }} + styles={{ + body: { padding: '24px' } + }} > - - + + + + 应收金额 + + } rules={[{ required: true, message: '请输入应收金额' }]} > - } step={10} + placeholder="0.00" + prefix="¥" /> - + + + + 收款金额 + + } + rules={[{ required: true, message: '请输入收款金额' }]} + > + + + + + + 收款平台 + + } rules={[{ required: true, message: '请选择收款平台' }]} > - @@ -511,48 +757,56 @@ const SalesModal: React.FC = ({ visible, onOk, onCancel, record - - + + + + 收款状态 + + } + rules={[{ required: true, message: '请选择收款状态' }]} > - - - - } - step={10} - /> - - - - - - + + 待收款 (应收-已收) @@ -560,33 +814,42 @@ const SalesModal: React.FC = ({ visible, onOk, onCancel, record } rules={[{ required: true, message: '请输入待收款金额' }]} > - } + placeholder="0.00" + prefix="¥" /> - + + 待收已收 (已追回) } > - } + placeholder="0.00" + prefix="¥" /> @@ -599,4 +862,4 @@ const SalesModal: React.FC = ({ visible, onOk, onCancel, record ); }; -export default SalesModal; +export default React.memo(SalesModal); diff --git a/src/pages/team/SaleRecord/ship-modal.tsx b/src/pages/team/SaleRecord/ship-modal.tsx index a090523..6890a5a 100644 --- a/src/pages/team/SaleRecord/ship-modal.tsx +++ b/src/pages/team/SaleRecord/ship-modal.tsx @@ -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 = ({ 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 = ({ visible, onOk, onCancel, record }) => { + // ==================== 状态管理 ==================== + const [form] = Form.useForm(); + const [logisticsNumbers, setLogisticsNumbers] = useState({}); + const [isSubmitting, setIsSubmitting] = useState(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 = ({ 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 = ({ 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 ( + + + 发货信息管理 + + + } onCancel={onCancel} + width={680} + styles={{ + body: { padding: '24px' } + }} footer={[ - , - , ]} > - - - - + + {/* 客户信息区域 */} + + } style={{ backgroundColor: '#52c41a' }} /> + 客户信息 + + } + size="small" + style={{ marginBottom: '20px' }} + styles={{ + body: { padding: '16px' } + }} + > + + +
+ 客户姓名 +
+ + {record?.客户?.姓名 || '未知客户'} + +
+
+ + + + } + placeholder="自动填入" + disabled + style={{ + backgroundColor: '#f5f5f5', + color: '#262626' + }} + /> + + +
+
- {/* 动态生成每个产品的物流单号输入框 */} - {record?.产品?.map(product => ( - - handleLogisticsNumberChange(product._id, e.target.value)} - onChange={e => { - const cleanedValue = e.target.value.replace(/[\s.,/#!$%\^&\*;:{}=\-_`~()<>[\]'"|\\?@+]/g, '');//过滤特殊字符和空格 - handleLogisticsNumberChange(product._id, cleanedValue); - }} - /> - - ))} + + + + 产品物流信息 + 0 ? 'success' : 'default'}> + {filledLogisticsCount}/{productList.length} 已填写 + + + + + {/* 产品物流信息区域 */} + + {productList.map((product, index) => ( + + + + + + + 产品 {index + 1} + + + {product.名称} + + + + + + } + placeholder={`请输入 ${product.名称} 的物流单号`} + value={logisticsNumbers[product._id] || ''} + onChange={e => handleLogisticsNumberChange(product._id, e.target.value)} + style={{ + fontSize: '14px', + borderColor: logisticsNumbers[product._id] ? '#52c41a' : undefined + }} + /> + + + + + + ))} + + + {/* 提示信息 */} + {productList.length === 0 && ( + + + 当前销售记录暂无产品信息 + + + )}
); }; -export default ShipModal; +export default React.memo(ShipModal); diff --git a/src/pages/test/test5.tsx b/src/pages/test/test5.tsx new file mode 100644 index 0000000..23fd29f --- /dev/null +++ b/src/pages/test/test5.tsx @@ -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 = ({ className }) => { + // 聊天消息状态管理 + const [messages, setMessages] = useState([ + { + id: '1', + content: '👋 您好!我是您的AI智能助手。\n\n我可以帮助您解答问题、提供建议、协助创作等。请随时告诉我您需要什么帮助!', + sender: 'ai', + timestamp: new Date(), + } + ]); + + // 输入和UI状态管理 + const [inputValue, setInputValue] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const [isTyping, setIsTyping] = useState(false); + + // DOM引用 + const messagesEndRef = useRef(null); + const inputRef = useRef(null); + const chatContainerRef = useRef(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: , + }, + { + key: 'settings', + label: '聊天设置', + icon: , + }, + { + key: 'feedback', + label: '意见反馈', + icon: , + }, + ]; + + // ============= 渲染函数 ============= + + return ( +
+ {/* 背景装饰 */} +
+
+
+
+ +
+ + {/* 精美的顶部导航栏 */} +
+
+
+
+ } + className="bg-gradient-to-br from-violet-500 via-purple-500 to-blue-500 border-4 border-white shadow-lg animate-pulse-custom" + /> + +
+
+ + AI智能助手 + +
+
+ 在线服务中 + +
+
+
+ + +
+ + {chatStats.totalMessages} 条对话 + +
+ + +
+
+ + {/* 聊天消息区域 */} +
+
+
+ {messages.map((message) => ( + + ))} + + {/* AI思考状态 */} + {isLoading && ( +
+
+ } + className="bg-gradient-to-br from-violet-500 to-blue-500" + /> +
+ + + {isTyping ? 'AI正在思考...' : '准备回复中...'} + +
+
+
+ )} + +
+
+ + {/* 消息输入区域 */} +
+
+
+