diff --git a/package.json b/package.json index d76a695..1a943fc 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e7e7cd6..1464ed8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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 diff --git a/src/constant.ts b/src/constant.ts new file mode 100644 index 0000000..06fc066 --- /dev/null +++ b/src/constant.ts @@ -0,0 +1 @@ +import { slashedBasename } from './modules/basename'; diff --git a/src/modules/basename.ts b/src/modules/basename.ts index 263b1bb..646745b 100644 --- a/src/modules/basename.ts +++ b/src/modules/basename.ts @@ -1 +1,2 @@ export const basename = DEV_SERVER ? '/' : BASE_NAME; +export const slashedBasename = basename ? `${basename}/` : '/'; diff --git a/src/pages/App.tsx b/src/pages/App.tsx index 3994bf3..09eecd4 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -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 ( + } /> } /> diff --git a/src/pages/trade/index.tsx b/src/pages/trade/index.tsx new file mode 100644 index 0000000..5713df5 --- /dev/null +++ b/src/pages/trade/index.tsx @@ -0,0 +1,10 @@ +import { Routes, Route } from 'react-router'; +import { List } from './pages/List'; + +export const App = () => { + return ( + + } /> + + ); +}; diff --git a/src/pages/trade/pages/List.tsx b/src/pages/trade/pages/List.tsx new file mode 100644 index 0000000..70295b4 --- /dev/null +++ b/src/pages/trade/pages/List.tsx @@ -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 ( + +
+ + + + + + + + + + +
+
+ ); +}; + +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[] = [ + { + 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) => ( + + + + + ), + }, + ]; + const [formSearch] = Form.useForm(); + const onSearch = (data: any) => { + console.log('onSearch', data); + store.getList(data); + }; + return ( +
+
+
+ {/* */} + + + + + + +
+ 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); + }, + }} + /> + + + + ); +}; diff --git a/src/pages/trade/query.ts b/src/pages/trade/query.ts new file mode 100644 index 0000000..4d78632 --- /dev/null +++ b/src/pages/trade/query.ts @@ -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, + ); + } +} diff --git a/src/pages/trade/store.ts b/src/pages/trade/store.ts new file mode 100644 index 0000000..6ead73a --- /dev/null +++ b/src/pages/trade/store.ts @@ -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; + init: () => Promise; + getData: (id: string) => Promise; + updateData: (data: any, opts?: { refresh?: boolean }) => Promise; + deleteData: (id: string, opts?: { refresh?: boolean }) => Promise; +}; +export const useTradeStore = create((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; + }, +})); diff --git a/src/pages/vip/constants.ts b/src/pages/vip/constants.ts index 5621b54..440584e 100644 --- a/src/pages/vip/constants.ts +++ b/src/pages/vip/constants.ts @@ -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等', }, { diff --git a/src/pages/vip/pages/VipInfo.tsx b/src/pages/vip/pages/VipInfo.tsx index d7083e7..c07de9e 100644 --- a/src/pages/vip/pages/VipInfo.tsx +++ b/src/pages/vip/pages/VipInfo.tsx @@ -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 ( diff --git a/src/pages/vip/pay-modal/PayModal.tsx b/src/pages/vip/pay-modal/PayModal.tsx index 5243dd6..a7818dc 100644 --- a/src/pages/vip/pay-modal/PayModal.tsx +++ b/src/pages/vip/pay-modal/PayModal.tsx @@ -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((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 ( - setOpen(false)} maskClosable={false} footer={null}> -
+ setOpen(false)} maskClosable={false} footer={null} style={{}}> +

支付确认

请确认支付金额:{money} 元

+ {payMode === 'wx' && wxQrCode && 微信支付} + {payMode === 'alipay' && alipayIframeURL &&