add paycenter

This commit is contained in:
xion 2025-04-09 01:23:23 +08:00
parent 8bd517cfcb
commit 5f59158021
14 changed files with 752 additions and 12 deletions

View File

@ -26,12 +26,14 @@
"@emotion/styled": "^11.14.0",
"@kevisual/router": "0.0.10",
"@mui/material": "^7.0.1",
"@types/qrcode": "^1.5.5",
"antd": "^5.24.6",
"clsx": "^2.1.1",
"dayjs": "^1.11.13",
"lodash-es": "^4.17.21",
"lucide-react": "^0.487.0",
"nanoid": "^5.1.5",
"qrcode": "^1.5.4",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-hook-form": "^7.55.0",

226
pnpm-lock.yaml generated
View File

@ -23,6 +23,9 @@ importers:
'@mui/material':
specifier: ^7.0.1
version: 7.0.1(@emotion/react@11.14.0(@types/react@19.1.0)(react@19.1.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.0)(react@19.1.0))(@types/react@19.1.0)(react@19.1.0))(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@types/qrcode':
specifier: ^1.5.5
version: 1.5.5
antd:
specifier: ^5.24.6
version: 5.24.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@ -41,6 +44,9 @@ importers:
nanoid:
specifier: ^5.1.5
version: 5.1.5
qrcode:
specifier: ^1.5.4
version: 1.5.4
react:
specifier: ^19.1.0
version: 19.1.0
@ -926,6 +932,9 @@ packages:
'@types/prop-types@15.7.14':
resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==}
'@types/qrcode@1.5.5':
resolution: {integrity: sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==}
'@types/react-dom@19.1.1':
resolution: {integrity: sha512-jFf/woGTVTjUJsl2O7hcopJ1r0upqoq/vIOoCj0yLh3RIXxWcljlpuZ+vEBRXsymD1jhfeJrlyTy/S1UW+4y1w==}
peerDependencies:
@ -962,6 +971,14 @@ packages:
resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==}
engines: {node: '>= 8.0.0'}
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
antd@5.24.6:
resolution: {integrity: sha512-xIlTa/1CTbgkZsdU/dOXkYvJXb9VoiMwsaCzpKFH2zAEY3xqOfwQ57/DdG7lAdrWP7QORtSld4UA6suxzuTHXw==}
peerDependencies:
@ -988,12 +1005,19 @@ packages:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
camelcase@5.3.1:
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
engines: {node: '>=6'}
caniuse-lite@1.0.30001684:
resolution: {integrity: sha512-G1LRwLIQjBQoyq0ZJGqGIJUXzJ8irpbjHLpVRXDvBEScFJ9b17sgK6vlx0GAJFE21okD7zXl08rRRUfq6HdoEQ==}
classnames@2.5.1:
resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
cliui@6.0.0:
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
clsx@1.2.1:
resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==}
engines: {node: '>=6'}
@ -1002,6 +1026,13 @@ packages:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
@ -1048,6 +1079,10 @@ packages:
supports-color:
optional: true
decamelize@1.2.0:
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
engines: {node: '>=0.10.0'}
deepmerge@4.3.1:
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
engines: {node: '>=0.10.0'}
@ -1060,6 +1095,9 @@ packages:
resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==}
engines: {node: '>=8'}
dijkstrajs@1.0.3:
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
dom-helpers@5.2.1:
resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
@ -1074,6 +1112,9 @@ packages:
electron-to-chromium@1.5.65:
resolution: {integrity: sha512-PWVzBjghx7/wop6n22vS2MLU8tKGd4Q91aCEGhG/TYmW6PP5OcSXcdnxTe1NNt0T66N8D6jxh4kC8UsdzOGaIw==}
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
enhanced-resolve@5.18.1:
resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==}
engines: {node: '>=10.13.0'}
@ -1128,6 +1169,10 @@ packages:
find-root@1.1.0:
resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==}
find-up@4.1.0:
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
engines: {node: '>=8'}
form-data-encoder@1.7.2:
resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==}
@ -1151,6 +1196,10 @@ packages:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
get-caller-file@2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
get-intrinsic@1.3.0:
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'}
@ -1220,6 +1269,10 @@ packages:
resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
engines: {node: '>= 0.4'}
is-fullwidth-code-point@3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
is-module@1.0.0:
resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==}
@ -1316,6 +1369,10 @@ packages:
lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
locate-path@5.0.0:
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
engines: {node: '>=8'}
lodash-es@4.17.21:
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
@ -1395,6 +1452,18 @@ packages:
zod:
optional: true
p-limit@2.3.0:
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
engines: {node: '>=6'}
p-locate@4.1.0:
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
engines: {node: '>=8'}
p-try@2.2.0:
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
engines: {node: '>=6'}
parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
@ -1403,6 +1472,10 @@ packages:
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
engines: {node: '>=8'}
path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
path-parse@1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
@ -1424,6 +1497,10 @@ packages:
resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
engines: {node: '>=12'}
pngjs@5.0.0:
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
engines: {node: '>=10.13.0'}
postcss@8.5.3:
resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==}
engines: {node: ^10 || ^12 || >=14}
@ -1431,6 +1508,11 @@ packages:
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
qrcode@1.5.4:
resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==}
engines: {node: '>=10.13.0'}
hasBin: true
rc-cascader@3.33.1:
resolution: {integrity: sha512-Kyl4EJ7ZfCBuidmZVieegcbFw0RcU5bHHSbtEdmuLYd0fYHCAiYKZ6zon7fWAVyC6rWWOOib0XKdTSf7ElC9rg==}
peerDependencies:
@ -1744,6 +1826,13 @@ packages:
regenerator-runtime@0.14.1:
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
require-main-filename@2.0.0:
resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
resize-observer-polyfill@1.5.1:
resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}
@ -1782,6 +1871,9 @@ packages:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true
set-blocking@2.0.0:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
set-cookie-parser@2.7.1:
resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==}
@ -1796,6 +1888,14 @@ packages:
string-convert@0.2.1:
resolution: {integrity: sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==}
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
stylis@4.2.0:
resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==}
@ -1909,6 +2009,13 @@ packages:
whatwg-url@5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
which-module@2.0.1:
resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
wrap-ansi@6.2.0:
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
engines: {node: '>=8'}
ws@8.18.1:
resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==}
engines: {node: '>=10.0.0'}
@ -1921,6 +2028,9 @@ packages:
utf-8-validate:
optional: true
y18n@4.0.3:
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
@ -1933,6 +2043,14 @@ packages:
engines: {node: '>= 14'}
hasBin: true
yargs-parser@18.1.3:
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
engines: {node: '>=6'}
yargs@15.4.1:
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
engines: {node: '>=8'}
zustand@5.0.3:
resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==}
engines: {node: '>=12.20.0'}
@ -2713,6 +2831,10 @@ snapshots:
'@types/prop-types@15.7.14': {}
'@types/qrcode@1.5.5':
dependencies:
'@types/node': 22.14.0
'@types/react-dom@19.1.1(@types/react@19.1.0)':
dependencies:
'@types/react': 19.1.0
@ -2750,6 +2872,12 @@ snapshots:
dependencies:
humanize-ms: 1.2.1
ansi-regex@5.0.1: {}
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
antd@5.24.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
'@ant-design/colors': 7.2.0
@ -2830,14 +2958,28 @@ snapshots:
callsites@3.1.0: {}
camelcase@5.3.1: {}
caniuse-lite@1.0.30001684: {}
classnames@2.5.1: {}
cliui@6.0.0:
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 6.2.0
clsx@1.2.1: {}
clsx@2.1.1: {}
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
color-name@1.1.4: {}
combined-stream@1.0.8:
dependencies:
delayed-stream: 1.0.0
@ -2874,12 +3016,16 @@ snapshots:
dependencies:
ms: 2.1.3
decamelize@1.2.0: {}
deepmerge@4.3.1: {}
delayed-stream@1.0.0: {}
detect-libc@2.0.3: {}
dijkstrajs@1.0.3: {}
dom-helpers@5.2.1:
dependencies:
'@babel/runtime': 7.27.0
@ -2895,6 +3041,8 @@ snapshots:
electron-to-chromium@1.5.65: {}
emoji-regex@8.0.0: {}
enhanced-resolve@5.18.1:
dependencies:
graceful-fs: 4.2.11
@ -2961,6 +3109,11 @@ snapshots:
find-root@1.1.0: {}
find-up@4.1.0:
dependencies:
locate-path: 5.0.0
path-exists: 4.0.0
form-data-encoder@1.7.2: {}
form-data@4.0.2:
@ -2982,6 +3135,8 @@ snapshots:
gensync@1.0.0-beta.2: {}
get-caller-file@2.0.5: {}
get-intrinsic@1.3.0:
dependencies:
call-bind-apply-helpers: 1.0.2
@ -3052,6 +3207,8 @@ snapshots:
dependencies:
hasown: 2.0.2
is-fullwidth-code-point@3.0.0: {}
is-module@1.0.0: {}
is-reference@1.2.1:
@ -3119,6 +3276,10 @@ snapshots:
lines-and-columns@1.2.4: {}
locate-path@5.0.0:
dependencies:
p-locate: 4.1.0
lodash-es@4.17.21: {}
loose-envify@1.4.0:
@ -3177,6 +3338,16 @@ snapshots:
transitivePeerDependencies:
- encoding
p-limit@2.3.0:
dependencies:
p-try: 2.2.0
p-locate@4.1.0:
dependencies:
p-limit: 2.3.0
p-try@2.2.0: {}
parent-module@1.0.1:
dependencies:
callsites: 3.1.0
@ -3188,6 +3359,8 @@ snapshots:
json-parse-even-better-errors: 2.3.1
lines-and-columns: 1.2.4
path-exists@4.0.0: {}
path-parse@1.0.7: {}
path-to-regexp@8.2.0: {}
@ -3200,6 +3373,8 @@ snapshots:
picomatch@4.0.2: {}
pngjs@5.0.0: {}
postcss@8.5.3:
dependencies:
nanoid: 3.3.8
@ -3212,6 +3387,12 @@ snapshots:
object-assign: 4.1.1
react-is: 16.13.1
qrcode@1.5.4:
dependencies:
dijkstrajs: 1.0.3
pngjs: 5.0.0
yargs: 15.4.1
rc-cascader@3.33.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
'@babel/runtime': 7.27.0
@ -3604,6 +3785,10 @@ snapshots:
regenerator-runtime@0.14.1: {}
require-directory@2.1.1: {}
require-main-filename@2.0.0: {}
resize-observer-polyfill@1.5.1: {}
resolve-from@4.0.0: {}
@ -3660,6 +3845,8 @@ snapshots:
semver@6.3.1: {}
set-blocking@2.0.0: {}
set-cookie-parser@2.7.1: {}
source-map-js@1.2.1: {}
@ -3668,6 +3855,16 @@ snapshots:
string-convert@0.2.1: {}
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
strip-ansi@6.0.1:
dependencies:
ansi-regex: 5.0.1
stylis@4.2.0: {}
stylis@4.3.6: {}
@ -3730,8 +3927,18 @@ snapshots:
tr46: 0.0.3
webidl-conversions: 3.0.1
which-module@2.0.1: {}
wrap-ansi@6.2.0:
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
ws@8.18.1: {}
y18n@4.0.3: {}
yallist@3.1.1: {}
yaml@1.10.2: {}
@ -3739,6 +3946,25 @@ snapshots:
yaml@2.5.1:
optional: true
yargs-parser@18.1.3:
dependencies:
camelcase: 5.3.1
decamelize: 1.2.0
yargs@15.4.1:
dependencies:
cliui: 6.0.0
decamelize: 1.2.0
find-up: 4.1.0
get-caller-file: 2.0.5
require-directory: 2.1.1
require-main-filename: 2.0.0
set-blocking: 2.0.0
string-width: 4.2.3
which-module: 2.0.1
y18n: 4.0.3
yargs-parser: 18.1.3
zustand@5.0.3(@types/react@19.1.0)(immer@10.1.1)(react@19.1.0)(use-sync-external-store@1.2.2(react@19.1.0)):
optionalDependencies:
'@types/react': 19.1.0

1
src/constant.ts Normal file
View File

@ -0,0 +1 @@
import { slashedBasename } from './modules/basename';

View File

@ -1 +1,2 @@
export const basename = DEV_SERVER ? '/' : BASE_NAME;
export const slashedBasename = basename ? `${basename}/` : '/';

View File

@ -2,6 +2,7 @@ import { basename } from '../modules/basename';
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
console.log('basename', basename);
import { App as AppVip } from './vip';
import { App as AppTrade } from './trade';
import '@ant-design/v5-patch-for-react-19';
export const App = () => {
@ -12,6 +13,7 @@ export const AppRoute = () => {
return (
<Router basename={basename}>
<Routes>
<Route path='/trade' element={<AppTrade />} />
<Route path='*' element={<AppVip />} />
</Routes>
</Router>

10
src/pages/trade/index.tsx Normal file
View File

@ -0,0 +1,10 @@
import { Routes, Route } from 'react-router';
import { List } from './pages/List';
export const App = () => {
return (
<Routes>
<Route index element={<List />} />
</Routes>
);
};

View File

@ -0,0 +1,246 @@
import { useEffect } from 'react';
import { useTradeStore } from '../store';
import { useShallow } from 'zustand/react/shallow';
import { Modal, Space } from 'antd';
import dayjs from 'dayjs';
import { Table, Button, Select, DatePicker, Input, Form } from 'antd';
import { ColumnType } from 'antd/es/table/interface';
const defaultValues = {
title: '',
userId: '',
level: 'free',
category: 'center',
startDate: dayjs(),
endDate: dayjs().add(1, 'month'),
};
export const EditDialog = () => {
const [form] = Form.useForm();
const store = useTradeStore(
useShallow((state) => ({
formData: state.formData,
setFormData: state.setFormData,
showEdit: state.showEdit,
setShowEdit: state.setShowEdit,
updateData: state.updateData,
})),
);
useEffect(() => {
if (store.showEdit) {
if (store.formData) {
form.setFieldsValue({
...store.formData,
startDate: dayjs(store.formData.startDate),
endDate: dayjs(store.formData.endDate),
});
} else {
form.setFieldsValue(defaultValues);
}
}
return () => {
form.setFieldsValue(defaultValues);
};
}, [store.formData, store.showEdit]);
const onSubmit = async (data: any) => {
// 将dayjs对象转换为时间字符串
const formattedData = {
...data,
id: store.formData?.id,
startDate: data.startDate ? data.startDate.format('YYYY-MM-DD') : undefined,
endDate: data.endDate ? data.endDate.format('YYYY-MM-DD') : undefined,
};
const res = await store.updateData(formattedData, { refresh: true });
if (res.code === 200) {
store.setShowEdit(false);
store.setFormData(undefined);
}
};
const onCancel = () => {
store.setShowEdit(false);
store.setFormData(undefined);
};
const hasId = !!store.formData?.id;
return (
<Modal
title={hasId ? '编辑' : '添加'}
footer={null}
open={store.showEdit}
onCancel={onCancel}
style={{
top: 40,
}}>
<Form
form={form}
labelCol={{
span: 6,
}}
wrapperCol={{
span: 18,
}}
className='flex flex-col gap-4 pt-4 min-w-[400px]'
onFinish={onSubmit}>
<Form.Item name='id' hidden>
<Input />
</Form.Item>
<Form.Item name='title' label='标题' rules={[{ required: true, message: '请输入标题' }]}>
<Input placeholder='请输入标题' />
</Form.Item>
<Form.Item name='userId' label='用户ID' rules={[{ required: true, message: '请输入用户ID' }]}>
<Input placeholder='请输入用户ID' />
</Form.Item>
<Form.Item label=' ' colon={false}>
<Button htmlType='submit'></Button>
</Form.Item>
</Form>
</Modal>
);
};
export const List = () => {
const store = useTradeStore(
useShallow((state) => ({
list: state.list,
pagination: state.pagination,
init: state.init,
setShowEdit: state.setShowEdit,
deleteData: state.deleteData,
setFormData: state.setFormData,
userList: state.userList,
getList: state.getList,
})),
);
useEffect(() => {
store.init();
}, []);
const columns: ColumnType<any>[] = [
{
title: 'ID',
dataIndex: 'id',
align: 'center',
},
{
title: 'out_trade_no',
dataIndex: 'out_trade_no',
align: 'center',
},
{
title: 'Subject',
dataIndex: 'subject',
align: 'center',
},
{
title: '状态',
dataIndex: 'status',
align: 'center',
},
{
title: '金额',
dataIndex: 'money',
align: 'center',
render: (_, record) => {
try {
return (record.money / 100).toFixed(2);
} catch (error) {
return record.money;
}
},
},
{
title: '支付方式',
dataIndex: 'type',
align: 'center',
},
{
title: 'uid',
dataIndex: 'uid',
align: 'center',
render: (_, record) => {
const user = store.userList?.find?.((item) => item.id === record.uid);
return user?.username || record.uid;
},
},
{
title: '数据',
dataIndex: 'data',
align: 'center',
render: (_, record) => {
const target = record.data?.target;
return JSON.stringify(target);
},
},
{
title: '创建时间',
dataIndex: 'createdAt',
align: 'center',
render: (_, record) => {
return record.createdAt ? dayjs(record.createdAt).format('YYYY-MM-DD HH:mm:ss') : '';
},
},
{
title: '操作',
dataIndex: 'action',
align: 'center',
fixed: 'right',
render: (_, record) => (
<Space>
<Button
type='link'
onClick={() => {
store.setShowEdit(true);
store.setFormData(record);
}}>
</Button>
<Button type='link' onClick={() => store.deleteData(record.id)}>
</Button>
</Space>
),
},
];
const [formSearch] = Form.useForm();
const onSearch = (data: any) => {
console.log('onSearch', data);
store.getList(data);
};
return (
<div className='w-full h-full flex flex-col gap-2 bg-gray-100 p-4'>
<div
className='overflow-auto scrollbar relative'
style={{
height: 'calc(100% - 50px)',
overflow: 'auto',
}}>
<Form form={formSearch} onFinish={onSearch} layout='inline' className='sticky top-0 left-0 z-10 flex gap-2 flex-wrap'>
{/* <Button type='primary' onClick={() => store.setShowEdit(true)}>
</Button> */}
<Form.Item className='w-[200px]' name='search' label='标题'>
<Input />
</Form.Item>
<Form.Item>
<Button type='primary' htmlType='submit'>
</Button>
</Form.Item>
</Form>
<Table
className='mt-2'
columns={columns}
rowKey={(record) => record.id}
dataSource={store.list}
pagination={{
pageSize: store.pagination.pageSize,
total: store.pagination.total,
current: store.pagination.page,
onChange: (page, pageSize) => {
store.getList({ page, pageSize });
console.log('onChange', page, pageSize);
},
}}
/>
</div>
<EditDialog />
</div>
);
};

56
src/pages/trade/query.ts Normal file
View File

@ -0,0 +1,56 @@
import { BaseQuery } from '@kevisual/query';
export class TradeQuery extends BaseQuery {
constructor(options: { query: any }) {
super(options);
}
async getList(params?: any, dataOpts?: any) {
return this.query.post(
{
path: 'trade',
key: 'list',
...params,
},
dataOpts,
);
}
async getDetail(id?: string, dataOpts?: any) {
return this.query.post(
{
path: 'trade',
key: 'get',
data: { id },
},
dataOpts,
);
}
async update(data?: any, dataOpts?: any) {
return this.query.post(
{
path: 'trade',
key: 'update',
data,
},
dataOpts,
);
}
async delete(id?: string, dataOpts?: any) {
return this.query.post(
{
path: 'trade',
key: 'delete',
data: { id },
},
dataOpts,
);
}
async getMe(dataOpts?: any) {
return this.query.post<{ id: string }>(
{
path: 'user',
key: 'me',
},
dataOpts,
);
}
}

111
src/pages/trade/store.ts Normal file
View File

@ -0,0 +1,111 @@
import { create } from 'zustand';
import { query } from '@/modules/query';
import { TradeQuery } from './query';
import { toast } from 'react-toastify';
export const queryApi = new TradeQuery({ query });
type Store = {
list: any[];
setList: (list: any[]) => void;
pagination: {
page: number;
pageSize: number;
total: number;
};
userList: any[];
setPagination: (pagination: { page: number; pageSize: number; total: number }) => void;
data: any;
setData: (data: any) => void;
loading: boolean;
setLoading: (loading: boolean) => void;
formData: any;
setFormData: (data: any) => void;
showEdit: boolean;
setShowEdit: (showEdit: boolean) => void;
getList: (params?: { page?: number; pageSize?: number; category?: string; level?: string }) => Promise<any>;
init: () => Promise<void>;
getData: (id: string) => Promise<any>;
updateData: (data: any, opts?: { refresh?: boolean }) => Promise<any>;
deleteData: (id: string, opts?: { refresh?: boolean }) => Promise<any>;
};
export const useTradeStore = create<Store>((set, get) => ({
list: [],
setList: (list) => set({ list }),
userList: [],
pagination: {
page: 1,
pageSize: 2,
total: 0,
},
setPagination: (pagination) => set({ pagination }),
data: null,
setData: (data) => set({ data }),
loading: false,
setLoading: (loading) => set({ loading }),
formData: null,
setFormData: (formData) => set({ formData }),
showEdit: false,
setShowEdit: (showEdit) => set({ showEdit }),
getList: async (params?: any) => {
set({ loading: true });
let { page, pageSize, ...rest } = params || {};
const res = await queryApi.getList({
page: page || 1,
pageSize: pageSize || 10,
...rest,
});
set({ loading: false });
if (res.code === 200) {
set({
list: res.data.list,
userList: res.data.userList || [],
pagination: res.data.pagination,
});
}
return res;
},
init: async () => {
await get().getList();
},
getData: async (id) => {
set({ loading: true });
const res = await queryApi.getDetail(id);
set({ loading: false });
if (res.code === 200) {
const data = res.data;
set({ data });
}
return res;
},
updateData: async (data, opts = { refresh: true }) => {
set({ loading: true });
const res = await queryApi.update(data);
set({ loading: false });
if (res.code === 200) {
set({ data: res.data });
toast.success('更新成功');
} else {
toast.error(res.message || '更新失败');
}
if (opts.refresh) {
await get().getList();
}
return res;
},
deleteData: async (id, opts = { refresh: true }) => {
set({ loading: true });
const res = await queryApi.delete(id);
set({ loading: false });
if (res.code === 200) {
set({ data: null });
toast.success('删除成功');
} else {
toast.error(res.message || '删除失败');
}
if (opts.refresh) {
await get().getList();
}
return res;
},
}));

View File

@ -6,7 +6,7 @@ export const vipFeatureList = [
description: '满足简单的部署应用需求',
features: [
{
title: '部署10个以内的前端应用',
title: '部署20个以内的前端应用',
description: '可以部署10个以内的应用包括网页应用、小程序、H5等',
},
{
@ -24,7 +24,7 @@ export const vipFeatureList = [
description: '支持一下',
features: [
{
title: '部署30个以内的前端应用',
title: '部署50个以内的前端应用',
description: '可以部署30个以内的应用包括网页应用、小程序、H5等',
},
{

View File

@ -15,6 +15,7 @@ interface VipStore {
endDate: string;
category: string;
};
user?: { id: string; [key: string]: any };
setVipInfo: (vipInfo: any) => void;
}
@ -45,7 +46,7 @@ export const VipInfo = () => {
const store = useVipStore();
const payModalStore = usePayModalStore(
useShallow((state) => {
return { setOpen: state.setOpen, setMoney: state.setMoney };
return { setOpen: state.setOpen, setMoney: state.setMoney, setLevel: state.setLevel };
}),
);
useEffect(() => {
@ -93,13 +94,16 @@ export const VipInfo = () => {
window.location.href = '/root/center/';
return;
}
if (clickLevelNumber <= currentLevelNumber) {
toast.info('您已经是该会员等级');
return;
if (clickLevelNumber < currentLevelNumber) {
toast.info('您当前会员等级为' + currentLevel + ',无法降级, 会直接覆盖。需要修改请联系管理员。');
} else if (clickLevelNumber === currentLevelNumber) {
} else if (clickLevelNumber > currentLevelNumber) {
toast.info('您当前会员等级为' + currentLevel + ',会直接覆盖原有会员等级。并重新计算会员时间。');
}
// 打开支付弹窗
payModalStore.setOpen(true);
payModalStore.setMoney(clickLevelNumber);
payModalStore.setLevel(clickLevel);
return;
};
return (

View File

@ -1,29 +1,85 @@
import { Modal } from 'antd';
import { create } from 'zustand';
import { generateQRCode } from '../../../uitls/qrcode';
import { useEffect, useState } from 'react';
import { queryApi } from '../store';
import { toast } from 'react-toastify';
interface PayModalStore {
open: boolean;
setOpen: (open: boolean) => void;
money: number;
setMoney: (money: number) => void;
level: string;
setLevel: (level: string) => void;
}
export const usePayModalStore = create<PayModalStore>((set) => ({
open: false,
setOpen: (open: boolean) => set({ open }),
money: 0,
setMoney: (money: number) => set({ money }),
level: 'money',
setLevel: (level: string) => set({ level }),
}));
export const PayModal = () => {
const { open, setOpen, money } = usePayModalStore();
const { open, setOpen, money, level } = usePayModalStore();
const [wxQrCode, setWxQrCode] = useState('');
const [wxIframeURL, setWxIframeURL] = useState('');
const [alipayIframeURL, setAlipayIframeURL] = useState('');
const [payMode, setPayMode] = useState<'wx' | 'alipay' | 'unset'>('unset');
useEffect(() => {
initQrCode(wxIframeURL);
}, [wxIframeURL]);
const initQrCode = async (url: string) => {
if (!url) return;
const wxQrCode = await generateQRCode(url);
setWxQrCode(wxQrCode);
};
const onPay = async (payMode: 'wx' | 'alipay') => {
const res = await queryApi.vipPay({
level,
money: money * 100,
payMode,
});
if (res.code === 200) {
if (payMode === 'wx') {
const form = res.data.url;
const wxQrCode = await generateQRCode(form);
setWxQrCode(wxQrCode);
} else {
const form = res.data.form;
setAlipayIframeURL(form);
}
} else {
toast.error(res.message || '支付失败');
}
};
return (
<Modal open={open} onCancel={() => setOpen(false)} maskClosable={false} footer={null}>
<div className='px-4 py-4 flex flex-col gap-4 select-none'>
<Modal open={open} onCancel={() => setOpen(false)} maskClosable={false} footer={null} style={{}}>
<div className='px-4 py-4 flex flex-col gap-4 select-none justify-center items-center'>
<h2 className='text-2xl font-bold mb-4'></h2>
<p className='text-lg'>{money} </p>
{payMode === 'wx' && wxQrCode && <img className='w-[210px] h-[210px] ' src={wxQrCode} alt='微信支付' />}
{payMode === 'alipay' && alipayIframeURL && <iframe src={alipayIframeURL} className='w-[210px] h-[210px]' />}
<div className='flex gap-2'>
<button className='bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600 cursor-pointer'></button>
<button className='bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600 cursor-pointer'></button>
<button
className='bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600 cursor-pointer'
onClick={() => {
setPayMode('wx');
onPay('wx');
}}>
</button>
<button
className='bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600 cursor-pointer'
onClick={() => {
setPayMode('alipay');
onPay('alipay');
}}>
</button>
</div>
</div>
</Modal>

View File

@ -21,7 +21,7 @@ export const VipCategory = [
},
{
label: 'AI Chat',
value: 'chat',
value: 'ai-chat',
},
];
export class QueryApi extends BaseQuery {
@ -98,4 +98,23 @@ export class QueryApi extends BaseQuery {
dataOpts,
);
}
async vipPay(
data?: {
level: string;
payMode?: string;
out_trade_no?: string;
month?: number;
money: number;
},
dataOpts?: any,
) {
return this.query.post(
{
path: 'vip',
key: 'pay',
data,
},
dataOpts,
);
}
}

6
src/uitls/qrcode.ts Normal file
View File

@ -0,0 +1,6 @@
import QRCode from 'qrcode';
export const generateQRCode = async (url: string) => {
const qrCode = await QRCode.toDataURL(url);
return qrCode;
};