Compare commits

...

4 Commits
main ... v7

  1. 2
      .env
  2. 7
      package.json
  3. 211
      pnpm-lock.yaml
  4. BIN
      public/Alibaba-PuHuiTi-Heavy.ttf
  5. 3
      public/data.json
  6. 34
      src/App.tsx
  7. 15
      src/api/_utils/index.ts
  8. 57
      src/api/http.ts
  9. 17
      src/api/index.ts
  10. 15
      src/index.css
  11. 29
      src/layout/base.tsx
  12. 72
      src/plugins/http/base.ts
  13. 76
      src/plugins/http/index.ts
  14. 0
      src/plugins/http/plugins.ts
  15. 133
      src/plugins/http/plugins/Loading.ts
  16. 36
      src/plugins/http/plugins/Repeat.ts
  17. 34
      src/plugins/request.ts
  18. 16
      src/router-new/index.ts
  19. 54
      src/router/route.tsx
  20. 48
      src/store/account.ts
  21. 9
      src/ui/Login/Login.tsx
  22. 4
      src/ui/Register/Register.tsx
  23. 18
      src/views/About.tsx
  24. 9
      src/views/Child.tsx
  25. 47
      src/views/Home/index.tsx
  26. 11
      src/views/PlayGround.tsx
  27. 34
      src/views/Project.tsx
  28. 2
      src/viewsSys/NoMatch.tsx

2
.env

@ -1,2 +1,2 @@
# appwrite的项目ID
VUE_APPWRITE_PROJECT_ID=
VITE_APP_WRITE_PROJECT_ID=

7
package.json

@ -17,7 +17,7 @@
"@blueprintjs/icons": "^5.14.0",
"@blueprintjs/table": "^5.2.5",
"@types/lodash": "^4.17.13",
"@types/react-router-dom": "^5.3.3",
"ahooks": "^3.8.2",
"appwrite": "^16.0.2",
"axios": "^1.7.7",
"framer-motion": "^11.11.17",
@ -28,9 +28,10 @@
"react-dom": "^18.3.1",
"react-hook-form": "^7.53.2",
"react-markdown": "^9.0.1",
"react-router-dom": "^5.3.4",
"react-router": "^7.0.1",
"react-toastify": "^10.0.6",
"styled-components": "^6.1.13"
"styled-components": "^6.1.13",
"swr": "^2.2.5"
},
"devDependencies": {
"@eslint/js": "^9.13.0",

211
pnpm-lock.yaml

@ -23,9 +23,9 @@ importers:
'@types/lodash':
specifier: ^4.17.13
version: 4.17.13
'@types/react-router-dom':
specifier: ^5.3.3
version: 5.3.3
ahooks:
specifier: ^3.8.2
version: 3.8.2(react@18.3.1)
appwrite:
specifier: ^16.0.2
version: 16.0.2
@ -56,15 +56,18 @@ importers:
react-markdown:
specifier: ^9.0.1
version: 9.0.1(@types/react@18.3.12)(react@18.3.1)
react-router-dom:
specifier: ^5.3.4
version: 5.3.4(react@18.3.1)
react-router:
specifier: ^7.0.1
version: 7.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-toastify:
specifier: ^10.0.6
version: 10.0.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
styled-components:
specifier: ^6
version: 6.1.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
swr:
specifier: ^2.2.5
version: 2.2.5(react@18.3.1)
devDependencies:
'@eslint/js':
specifier: ^9.13.0
@ -537,6 +540,9 @@ packages:
'@swc/types@0.1.15':
resolution: {integrity: sha512-XKaZ+dzDIQ9Ot9o89oJQ/aluI17+VvUnIpYJTcZtvv1iYX6MzHh3Ik2CSR7MdPKpPwfZXHBeCingb2b4PoDVdw==}
'@types/cookie@0.6.0':
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
'@types/debug@4.1.12':
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
@ -549,9 +555,6 @@ packages:
'@types/hast@3.0.4':
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
'@types/history@4.7.11':
resolution: {integrity: sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==}
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
@ -573,12 +576,6 @@ packages:
'@types/react-dom@18.3.1':
resolution: {integrity: sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==}
'@types/react-router-dom@5.3.3':
resolution: {integrity: sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==}
'@types/react-router@5.1.20':
resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==}
'@types/react@18.3.12':
resolution: {integrity: sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==}
@ -666,6 +663,12 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
ahooks@3.8.2:
resolution: {integrity: sha512-iuYD3PWopdw9HqH++3ul6/Oxl9Q9/tP+np4yiYd0mIzN3QfDKnyII28MWUcmSkaHF415R6bUcrl0vD/80NNogg==}
engines: {node: '>=8.0.0'}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
@ -739,6 +742,9 @@ packages:
classnames@2.5.1:
resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
client-only@0.0.1:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
clsx@2.1.1:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
@ -763,6 +769,10 @@ packages:
constant-case@3.0.4:
resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==}
cookie@1.0.2:
resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
engines: {node: '>=18'}
cross-spawn@7.0.5:
resolution: {integrity: sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==}
engines: {node: '>= 8'}
@ -777,6 +787,9 @@ packages:
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
dayjs@1.11.13:
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
debug@4.3.7:
resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
engines: {node: '>=6.0'}
@ -976,12 +989,6 @@ packages:
header-case@2.0.4:
resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==}
history@4.10.1:
resolution: {integrity: sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==}
hoist-non-react-statics@3.3.2:
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
html-url-attributes@3.0.1:
resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==}
@ -1000,6 +1007,9 @@ packages:
inline-style-parser@0.2.4:
resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==}
intersection-observer@0.12.2:
resolution: {integrity: sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==}
is-alphabetical@2.0.1:
resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==}
@ -1028,9 +1038,6 @@ packages:
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
engines: {node: '>=12'}
isarray@0.0.1:
resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==}
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
@ -1046,6 +1053,10 @@ packages:
react:
optional: true
js-cookie@3.0.5:
resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
engines: {node: '>=14'}
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@ -1256,9 +1267,6 @@ packages:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
path-to-regexp@1.9.0:
resolution: {integrity: sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==}
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@ -1333,15 +1341,15 @@ packages:
react: ^16.8.0 || ^17 || ^18
react-dom: ^16.8.0 || ^17 || ^18
react-router-dom@5.3.4:
resolution: {integrity: sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==}
peerDependencies:
react: '>=15'
react-router@5.3.4:
resolution: {integrity: sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==}
react-router@7.0.1:
resolution: {integrity: sha512-WVAhv9oWCNsja5AkK6KLpXJDSJCQizOIyOd4vvB/+eHGbYx5vkhcmcmwWjQ9yqkRClogi+xjEg9fNEOd5EX/tw==}
engines: {node: '>=20.0.0'}
peerDependencies:
react: '>=15'
react: '>=18'
react-dom: '>=18'
peerDependenciesMeta:
react-dom:
optional: true
react-toastify@10.0.6:
resolution: {integrity: sha512-yYjp+omCDf9lhZcrZHKbSq7YMuK0zcYkDFTzfRFgTXkTFHZ1ToxwAonzA4JI5CxA91JpjFLmwEsZEgfYfOqI1A==}
@ -1378,13 +1386,13 @@ packages:
remark-rehype@11.1.1:
resolution: {integrity: sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==}
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-pathname@3.0.0:
resolution: {integrity: sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==}
reusify@1.0.4:
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
@ -1400,6 +1408,10 @@ packages:
scheduler@0.23.2:
resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
screenfull@5.2.0:
resolution: {integrity: sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==}
engines: {node: '>=0.10.0'}
semver@7.6.3:
resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==}
engines: {node: '>=10'}
@ -1408,6 +1420,9 @@ packages:
sentence-case@3.0.4:
resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==}
set-cookie-parser@2.7.1:
resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==}
shallowequal@1.1.0:
resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==}
@ -1453,11 +1468,10 @@ packages:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
tiny-invariant@1.3.3:
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
tiny-warning@1.0.3:
resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==}
swr@2.2.5:
resolution: {integrity: sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==}
peerDependencies:
react: ^16.11.0 || ^17.0.0 || ^18.0.0
to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
@ -1481,6 +1495,9 @@ packages:
tslib@2.6.3:
resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==}
turbo-stream@2.4.0:
resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==}
type-check@0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
@ -1534,9 +1551,6 @@ packages:
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
value-equal@1.0.1:
resolution: {integrity: sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==}
vfile-message@4.0.2:
resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==}
@ -1890,6 +1904,8 @@ snapshots:
dependencies:
'@swc/counter': 0.1.3
'@types/cookie@0.6.0': {}
'@types/debug@4.1.12':
dependencies:
'@types/ms': 0.7.34
@ -1904,8 +1920,6 @@ snapshots:
dependencies:
'@types/unist': 3.0.3
'@types/history@4.7.11': {}
'@types/json-schema@7.0.15': {}
'@types/lodash@4.17.13': {}
@ -1926,17 +1940,6 @@ snapshots:
dependencies:
'@types/react': 18.3.12
'@types/react-router-dom@5.3.3':
dependencies:
'@types/history': 4.7.11
'@types/react': 18.3.12
'@types/react-router': 5.1.20
'@types/react-router@5.1.20':
dependencies:
'@types/history': 4.7.11
'@types/react': 18.3.12
'@types/react@18.3.12':
dependencies:
'@types/prop-types': 15.7.13
@ -2044,6 +2047,19 @@ snapshots:
acorn@8.14.0: {}
ahooks@3.8.2(react@18.3.1):
dependencies:
'@babel/runtime': 7.26.0
dayjs: 1.11.13
intersection-observer: 0.12.2
js-cookie: 3.0.5
lodash: 4.17.21
react: 18.3.1
react-fast-compare: 3.2.2
resize-observer-polyfill: 1.5.1
screenfull: 5.2.0
tslib: 2.6.3
ajv@6.12.6:
dependencies:
fast-deep-equal: 3.1.3
@ -2133,6 +2149,8 @@ snapshots:
classnames@2.5.1: {}
client-only@0.0.1: {}
clsx@2.1.1: {}
color-convert@2.0.1:
@ -2155,6 +2173,8 @@ snapshots:
tslib: 2.6.3
upper-case: 2.0.2
cookie@1.0.2: {}
cross-spawn@7.0.5:
dependencies:
path-key: 3.1.1
@ -2171,6 +2191,8 @@ snapshots:
csstype@3.1.3: {}
dayjs@1.11.13: {}
debug@4.3.7:
dependencies:
ms: 2.1.3
@ -2407,19 +2429,6 @@ snapshots:
capital-case: 1.0.4
tslib: 2.6.3
history@4.10.1:
dependencies:
'@babel/runtime': 7.26.0
loose-envify: 1.4.0
resolve-pathname: 3.0.0
tiny-invariant: 1.3.3
tiny-warning: 1.0.3
value-equal: 1.0.1
hoist-non-react-statics@3.3.2:
dependencies:
react-is: 16.13.1
html-url-attributes@3.0.1: {}
ignore@5.3.2: {}
@ -2433,6 +2442,8 @@ snapshots:
inline-style-parser@0.2.4: {}
intersection-observer@0.12.2: {}
is-alphabetical@2.0.1: {}
is-alphanumerical@2.0.1:
@ -2454,8 +2465,6 @@ snapshots:
is-plain-obj@4.1.0: {}
isarray@0.0.1: {}
isexe@2.0.0: {}
jotai@2.10.3(@types/react@18.3.12)(react@18.3.1):
@ -2463,6 +2472,8 @@ snapshots:
'@types/react': 18.3.12
react: 18.3.1
js-cookie@3.0.5: {}
js-tokens@4.0.0: {}
js-yaml@4.1.0:
@ -2811,10 +2822,6 @@ snapshots:
path-key@3.1.1: {}
path-to-regexp@1.9.0:
dependencies:
isarray: 0.0.1
picocolors@1.1.1: {}
picomatch@2.3.1: {}
@ -2893,29 +2900,15 @@ snapshots:
react-fast-compare: 3.2.2
warning: 4.0.3
react-router-dom@5.3.4(react@18.3.1):
dependencies:
'@babel/runtime': 7.26.0
history: 4.10.1
loose-envify: 1.4.0
prop-types: 15.8.1
react: 18.3.1
react-router: 5.3.4(react@18.3.1)
tiny-invariant: 1.3.3
tiny-warning: 1.0.3
react-router@5.3.4(react@18.3.1):
react-router@7.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@babel/runtime': 7.26.0
history: 4.10.1
hoist-non-react-statics: 3.3.2
loose-envify: 1.4.0
path-to-regexp: 1.9.0
prop-types: 15.8.1
'@types/cookie': 0.6.0
cookie: 1.0.2
react: 18.3.1
react-is: 16.13.1
tiny-invariant: 1.3.3
tiny-warning: 1.0.3
set-cookie-parser: 2.7.1
turbo-stream: 2.4.0
optionalDependencies:
react-dom: 18.3.1(react@18.3.1)
react-toastify@10.0.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
@ -2962,9 +2955,9 @@ snapshots:
unified: 11.0.5
vfile: 6.0.3
resolve-from@4.0.0: {}
resize-observer-polyfill@1.5.1: {}
resolve-pathname@3.0.0: {}
resolve-from@4.0.0: {}
reusify@1.0.4: {}
@ -3000,6 +2993,8 @@ snapshots:
dependencies:
loose-envify: 1.4.0
screenfull@5.2.0: {}
semver@7.6.3: {}
sentence-case@3.0.4:
@ -3008,6 +3003,8 @@ snapshots:
tslib: 2.6.3
upper-case-first: 2.0.2
set-cookie-parser@2.7.1: {}
shallowequal@1.1.0: {}
shebang-command@2.0.0:
@ -3056,9 +3053,11 @@ snapshots:
dependencies:
has-flag: 4.0.0
tiny-invariant@1.3.3: {}
tiny-warning@1.0.3: {}
swr@2.2.5(react@18.3.1):
dependencies:
client-only: 0.0.1
react: 18.3.1
use-sync-external-store: 1.2.2(react@18.3.1)
to-regex-range@5.0.1:
dependencies:
@ -3076,6 +3075,8 @@ snapshots:
tslib@2.6.3: {}
turbo-stream@2.4.0: {}
type-check@0.4.0:
dependencies:
prelude-ls: 1.2.1
@ -3144,8 +3145,6 @@ snapshots:
dependencies:
react: 18.3.1
value-equal@1.0.1: {}
vfile-message@4.0.2:
dependencies:
'@types/unist': 3.0.3

BIN
public/Alibaba-PuHuiTi-Heavy.ttf

Binary file not shown.

3
public/data.json

@ -0,0 +1,3 @@
{
"a": "FUCK"
}

34
src/App.tsx

@ -1,9 +1,23 @@
import AppRouter from "@/router/AppRouter"
import { ToastContainer, Bounce } from 'react-toastify';
import { HashRouter, Navigate, Route, Routes } from "react-router"
import { ToastContainer, Bounce } from "react-toastify"
import BaseLayout from "@/layout/base"
import Home from "@/views/Home"
import NoMatch from "@/viewsSys/NoMatch"
import AccountStore from "@/store/account"
export default function App() {
return <>
<AppRouter></AppRouter>
AccountStore.initUser()
return (
<>
<HashRouter>
<Routes>
<Route path="/" element={<BaseLayout />}>
<Route index element={<Navigate to="home" replace />} />
<Route path="home" element={<Home />} />
<Route path="*" element={<NoMatch />} />
</Route>
</Routes>
</HashRouter>
<ToastContainer
position="bottom-right"
autoClose={5000}
@ -14,16 +28,6 @@ export default function App() {
theme="light"
transition={Bounce}
/>
{/* <ToastContainer
stacked
position="bottom-right"
autoClose={5000}
hideProgressBar
closeOnClick={false}
pauseOnFocusLoss
pauseOnHover
theme="light"
transition={Bounce}
/> */}
</>
)
}

15
src/api/_utils/index.ts

@ -0,0 +1,15 @@
import { Account, Client, ID } from "appwrite"
import { toast } from "react-toastify"
const client = new Client()
client.setProject(import.meta.env.VUE_APPWRITE_PROJECT_ID)
export async function apiRegister(opts: { email: string; password: string }, errText: string) {
const account = new Account(client)
try {
await account.create(ID.unique(), opts.email, opts.password)
} catch (error: any) {
toast.error(error?.message ?? errText)
return Promise.reject(error)
}
}

57
src/api/http.ts

@ -0,0 +1,57 @@
import { Account, Client, ID } from "appwrite"
import { toast } from "react-toastify"
const client = new Client()
client.setProject(import.meta.env.VITE_APP_WRITE_PROJECT_ID)
export async function apiRegister(opts: { email: string; password: string }, errText?: string) {
const account = new Account(client)
try {
await account.create(ID.unique(), opts.email, opts.password)
} catch (error: any) {
toast.error(error?.message ?? errText)
return Promise.reject(error)
}
}
export async function apiLogin(opts: { email: string; password: string }, errText?: string) {
const account = new Account(client)
try {
return await account.createEmailPasswordSession(opts.email, opts.password);
} catch (error: any) {
toast.error(error?.message ?? errText)
return Promise.reject(error)
}
}
async function checkLogin() {
try {
await getAccount()
return true
} catch (error) {
return false
}
}
export async function listSessions(errText?: string) {
const account = new Account(client)
try {
return await await account.listSessions();
} catch (error: any) {
toast.error(error?.message ?? errText)
return Promise.reject(error)
}
}
export async function getAccount(errText?: string) {
const account = new Account(client)
try {
return await await account.get();
} catch (error: any) {
toast.error(error?.message ?? errText)
return Promise.reject(error)
}
}

17
src/api/index.ts

@ -1,15 +1,4 @@
import { Account, Client, ID } from "appwrite"
import { toast } from "react-toastify"
const client = new Client()
import * as Http from "./http"
client.setProject(import.meta.env.VUE_APPWRITE_PROJECT_ID)
export async function apiRegister(opts: { email: string; password: string }, errText: string) {
const account = new Account(client)
try {
await account.create(ID.unique(), opts.email, opts.password)
} catch (error: any) {
toast.error(error?.message ?? errText)
return Promise.reject(error)
}
}
// export * from "./http"
export default Http

15
src/index.css

@ -4,12 +4,23 @@
@import "@blueprintjs/icons/lib/css/blueprint-icons.css";
@import "@blueprintjs/table/lib/css/table.css";
html,body, #root{
@font-face {
font-family: "Alibaba";
src: url("/Alibaba-PuHuiTi-Heavy.ttf") format("woff");
}
html,
body,
#root {
height: 100%;
}
body {
height: 100%;
/* font-family: Alibaba; */
}
.container {
max-width: 1200px;
margin: 0 auto;
}

29
src/layout/base.tsx

@ -1,15 +1,17 @@
import { Alignment, Button, Dialog, DialogBody, DialogFooter, Menu, MenuDivider, MenuItem, Navbar, Popover } from "@blueprintjs/core"
import { ReactNode, useState } from "react"
import { NavLink, useHistory } from "react-router-dom"
import { NavLink, Outlet } from "react-router"
import styled from "styled-components"
import { GlobalStyles } from "./GlobalStyles"
import { Notice } from "@/ui/Notice"
import { Markdown } from "@/ui/Markdown"
import { Register } from "@/ui/Register/Register"
import { Login } from "@/ui/Login/Login"
import AccountStore from "@/store/account"
import { useAtom } from "jotai"
interface PageProps {
children: ReactNode
// children: ReactNode
}
const PageWrapper = styled.div`
@ -30,8 +32,7 @@ function BaseLayout(props: PageProps) {
const [isOpenAbout, setIsOpenAbout] = useState(false)
const [isOpenAuth, setIsOpenAuth] = useState(false)
const [isRegister, setIsRegister] = useState(false)
const router = useHistory()
const [user] = useAtom(AccountStore.userStore)
const about = `
> 退
@ -50,7 +51,7 @@ function BaseLayout(props: PageProps) {
<Navbar.Group align={Alignment.LEFT}>
<Navbar.Heading></Navbar.Heading>
<Navbar.Divider />
<NavLink to={"/home"} strict exact activeClassName="actived">
<NavLink to={"/home"}>
<Button className="bp5-minimal" icon="home" text="首页" />
</NavLink>
<Popover
@ -59,8 +60,8 @@ function BaseLayout(props: PageProps) {
onInteraction={isOpen => setIsOpen(isOpen)}
content={
<Menu>
<MenuItem icon="flow-review-branch" text="天涯笔记" disabled onClick={() => router.push("/project")} />
<MenuItem icon="third-party" text="全聚德" disabled onClick={() => router.push("/project/child")} />
<MenuItem icon="flow-review-branch" text="天涯笔记" disabled />
<MenuItem icon="third-party" text="全聚德" disabled />
<MenuDivider />
<MenuItem icon="document" text="文档">
<MenuItem icon="build" text="CSS选择器示例" />
@ -81,14 +82,22 @@ function BaseLayout(props: PageProps) {
<Notice />
</Navbar.Group>
<Navbar.Group align={Alignment.RIGHT}>
{
!user.isLogin && (
<>
<Button icon="user" className="bp5-minimal" text="登录 / 注册" onClick={() => setIsOpenAuth(true)} />
<Navbar.Divider />
<NavLink to={"/about"}>
</>
)
}
{/* <NavLink to={"/about"}> */}
<Button onClick={() => setIsOpenAbout(true)} className="bp5-minimal" text="关于" />
</NavLink>
{/* </NavLink> */}
</Navbar.Group>
</Navbar>
<ContentWrapper>{props.children}</ContentWrapper>
<ContentWrapper>
<Outlet />
</ContentWrapper>
<Dialog isOpen={isOpenAbout} title="关于我" icon="info-sign" onClose={() => setIsOpenAbout(false)}>
<DialogBody>
<Markdown>{about}</Markdown>

72
src/plugins/http/base.ts

@ -25,37 +25,58 @@ export class Plugin implements IPlugin {
destory() { }
}
export const enum EFor {
Continue,
Break,
}
export abstract class PluginsManager {
static plugins: IPlugin[] = []
static use(plugin: IPlugin) {
static use(plugin: IPlugin | IPlugin[]) {
if (Array.isArray(plugin)) {
PluginsManager.plugins = this.plugins.concat(plugin)
} else PluginsManager.plugins.push(plugin)
}
plugins: IPlugin[] = []
exculdPlugins: string[] = []
use(plugin: IPlugin) {
if (Array.isArray(plugin)) {
this.plugins = this.plugins.concat(plugin)
} else this.plugins.push(plugin)
}
callPluginByName<T>(name: string, key: keyof T, ...argus: any[]) {
async runPlugins(cb: (p: IPlugin) => Promise<EFor | undefined>) {
const array = [...PluginsManager.plugins, ...this.plugins]
for (let i = 0; i < array.length; i++) {
let p = array[i]
let result = await cb(p)
if (result === EFor.Continue) {
continue
} else if (result === EFor.Break) {
break
}
}
}
async _callPluginByName<T>(name: string, key: keyof T, ...argus: any[]) {
const array = [...PluginsManager.plugins, ...this.plugins]
for (let i = 0; i < array.length; i++) {
let p = array[i]
if (this.exculdPlugins.includes(p.name)) continue
if (!p.name) continue
if (name === p.name) {
const fn = (p as T)[key]
typeof fn === "function" && fn.apply(p, argus)
typeof fn === "function" && await fn.apply(p, argus)
}
}
}
callPlugin(key: keyof IPlugin, ...argus: any[]) {
async _callPlugin(key: keyof IPlugin, ...argus: any[]) {
const array = [...PluginsManager.plugins, ...this.plugins]
for (let i = 0; i < array.length; i++) {
let p = array[i]
if (this.exculdPlugins.includes(p.name)) continue
const fn = p[key]
typeof fn === "function" && fn.apply(p, argus)
typeof fn === "function" && await fn.apply(p, argus)
}
}
}
@ -66,25 +87,46 @@ export abstract class httpBase extends PluginsManager {
requestInterceptorId: null | number = null
responseInterceptorId: null | number = null
async callPluginByName<T>(name: string, key: keyof T, ...argus: any[]) {
await this.runPlugins(async (p) => {
if (this.exculdPlugins.includes(p.name)) return EFor.Continue
if (!p.name) return EFor.Continue
if (name === p.name) {
const fn = (p as T)[key]
typeof fn === "function" && await fn.apply(p, argus)
}
})
// await this._callPluginByName<T>(name, key, ...argus)
}
async callPlugin(key: keyof IPlugin, ...argus: any[]) {
await this.runPlugins(async (p) => {
if (this.exculdPlugins.includes(p.name)) return EFor.Continue
const fn = p[key]
typeof fn === "function" && await fn.apply(p, argus)
})
// await this._callPlugin(key, ...argus)
}
create<T>(config?: CreateAxiosDefaults<T>) {
this.instance = axios.create(deepAssign<CreateAxiosDefaults<T>>(axios.defaults, config ?? {}))
this.requestInterceptorId = this.instance.interceptors.request.use(config => {
this.requestInterceptorId = this.instance.interceptors.request.use(async config => {
const argu = { config }
this.callPlugin("beforeRequestConfig", argu)
await this.callPlugin("beforeRequestConfig", argu)
return argu.config
}, error => {
}, async error => {
const argu = { error }
this.callPlugin("beforeRequestError", argu)
return Promise.reject(error)
await this.callPlugin("beforeRequestError", argu)
return Promise.reject(argu.error)
})
this.responseInterceptorId = this.instance.interceptors.response.use(response => {
this.responseInterceptorId = this.instance.interceptors.response.use(async response => {
const argu = { response }
this.callPlugin("beforeResponse", argu)
await this.callPlugin("beforeResponse", argu)
return Promise.resolve(argu.response)
}, (error) => {
}, async (error) => {
const argu = { error }
this.callPlugin("beforeResponseError", argu)
return Promise.reject(error)
await this.callPlugin("beforeResponseError", argu)
return Promise.reject(argu.error)
})
return this.instance
}

76
src/plugins/http/index.ts

@ -1,10 +1,25 @@
import { httpBase, IPlugin } from "./base"
import { CreateAxiosDefaults, AxiosRequestConfig, AxiosResponse } from "axios"
import { RepeatPlugin } from "./Repeat"
const FlyPoll: Map<string, any> = new Map()
interface ICallFly {
pass: (argus: { exculdPlugins?: string[] }) => Fly
get: <T = any, Data = unknown>(...argus: [url: string, config?: AxiosRequestConfig<Data> | undefined]) => () => Promise<AxiosResponse<T>>
post: <T = any, Data = unknown>(...argus: [url: string, config?: AxiosRequestConfig<Data> | undefined]) => () => Promise<AxiosResponse<T>>
put: <T = any, Data = unknown>(...argus: [url: string, config?: AxiosRequestConfig<Data> | undefined]) => () => Promise<AxiosResponse<T>>
delete: <T = any, Data = unknown>(...argus: [url: string, config?: AxiosRequestConfig<Data> | undefined]) => () => Promise<AxiosResponse<T>>
request: <T = any, Data = unknown>(...argus: [config: AxiosRequestConfig<Data>]) => () => Promise<AxiosResponse<T>>
}
class Fly extends httpBase {
static disposeAll() {
if (FlyPoll.size) {
Array.from(FlyPoll.keys()).forEach(v => {
Fly.dispose(v)
})
}
}
static dispose(name: string) {
if (FlyPoll.has(name)) {
const instance = FlyPoll.get(name)
@ -23,9 +38,28 @@ class Fly extends httpBase {
static invoke(name?: string): Fly {
if (!name) name = "$defalut"
const instance = FlyPoll.get(name)
if (!instance) throw new Error("未初始化此实例")
if (!instance) {
throw new Error("未初始化此实例")
}
instance.exculdPlugins = []
return instance
}
static callFn(name?: string): ICallFly {
if (!name) name = "$defalut"
const instance = FlyPoll.get(name)
if (!instance) {
throw new Error("未初始化此实例")
}
instance.exculdPlugins = []
return new Proxy(instance, {
get(target, p: string) {
if (["get", "post", "put", "delete", "request"].includes(p.toLowerCase())) {
return (...argus: any[]) => () => target[p](...argus)
}
return target[p]
},
})
}
static get method() {
return {
@ -44,13 +78,18 @@ class Fly extends httpBase {
}
default: CreateAxiosDefaults = {
// baseURL: process.env.VUE_APP_BASEURL,
baseURL: import.meta.env.BASE_URL,
timeout: 10000,
headers: {
'Content-Type': Fly.contentType.FORM,
}
}
pass({ exculdPlugins }: { exculdPlugins?: string[] }) {
this.exculdPlugins = exculdPlugins ?? []
return this
}
name: string
constructor(name: string) {
@ -72,37 +111,18 @@ class Fly extends httpBase {
request<T = any, Data = unknown>(...argus: [config: AxiosRequestConfig<Data>]) {
return this.instance!.request.apply(this.instance, argus) as Promise<AxiosResponse<T>>
}
#init(config: AxiosRequestConfig) {
this.callPlugin("beforeCreate")
async #init(config: AxiosRequestConfig) {
await this.callPlugin("beforeCreate")
this.create(config)
this.callPlugin("created")
await this.callPlugin("created")
}
#destory() {
this.callPlugin("beforeDestory")
async #destory() {
await this.callPlugin("beforeDestory")
this.destory()
this.callPlugin("destory")
await this.callPlugin("destory")
}
}
export {
Fly
}
// https://api.52vmy.cn/
// Fly.invoke().request({ method: "get", url: "http://localhost:5173/", global: false }).then((res) => console.log(res.data)).catch(console.log)
// Fly.invoke().request({ method: "get", url: "http://localhost:5173/", global: true }).then((res) => console.log(res.data)).catch(console.log)
// Fly.invoke().request({ method: "get", url: "https://api.52vmy.cn/api/wl/word/bing/tu", global: true }).then((res) => console.log(res.data)).catch(console.log)
// Fly.invoke().request({ method: "get", url: "https://api.52vmy.cn/api/wl/word/bing/tu", global: true }).then((res) => console.log(res.data)).catch(console.log)
// Fly.invoke("empty").request({ method: "get", url: "https://api.52vmy.cn/api/wl/yan/bay" }).then((res) => console.log(res.data)).catch(console.log)
// Fly.invoke("empty").request({ method: "get", url: "https://api.52vmy.cn/api/img/tu/girl" }).then((res) => console.log(res.data)).catch(console.log)
// setTimeout(()=>{
// // Fly.invoke("empty").request({method: "get", url: "https://api.52vmy.cn/api/img/tu/man"}).then((res)=>console.log(res.data)).catch(console.log)
// Fly.invoke().callPluginByName<RepeatPlugin>(RepeatPlugin.name, "clearPendingPool")
// }, 0)

0
src/plugins/http/plugins.ts

133
src/plugins/http/plugins/Loading.ts

@ -0,0 +1,133 @@
import { AxiosRequestConfig, AxiosResponse, AxiosError } from "axios"
import { IPlugin } from "../base"
import { Id, toast } from "react-toastify"
declare module "axios" {
interface AxiosRequestConfig {
loadingAttrs?: Partial<IConfig>
}
}
type AxiosRequestConfigPlus = AxiosRequestConfig & {
$loading_ToastID?: Id
$loading_CreateTimeStamp?: number
$loading_ToastShowFn?: () => void
$loading_ShowTimerId?: NodeJS.Timeout
}
interface IConfig {
text: string
min: number
minShow: boolean
minSync: boolean
minShowLong: number
}
// 取消重复请求
export class LoadingPlugin implements IPlugin {
static name: string = "loading"
name: string = "loading"
defaultConfig: IConfig = {
text: "加载中",
min: 1000,
minShow: false,
minSync: true,
minShowLong: 1000,
}
config: IConfig = JSON.parse(JSON.stringify(this.defaultConfig))
constructor(config: Partial<IConfig> = {}) {
; (Object.keys(config) as (keyof IConfig)[]).forEach(v => {
if (config[v]) {
(this.config as any)[v] = config[v]
}
})
}
getConfig<T extends keyof IConfig>(key: T, config: Partial<IConfig> = {}) {
const keys = Object.keys(config) as T[]
for (let i = 0; i < keys.length; i++) {
const v = keys[i];
if (v === key) {
return (config as IConfig)[key]
}
}
return this.config[key]
}
beforeRequestConfig({ config }: { config: AxiosRequestConfigPlus }) {
const text = this.getConfig("text", config.loadingAttrs)
config.$loading_ToastShowFn = () => {
const id = toast.loading(text)
config.$loading_CreateTimeStamp = new Date().getTime()
config.$loading_ToastID = id
}
if (!this.getConfig("minShow", config.loadingAttrs)) {
config.$loading_ToastShowFn()
delete config.$loading_ToastShowFn
} else {
config.$loading_ShowTimerId = setTimeout(() => {
config.$loading_ToastShowFn!()
delete config.$loading_ToastShowFn
}, this.getConfig("min", config.loadingAttrs));
}
}
beforeResponse({ response }: { response: AxiosResponse }) {
return new Promise((resolve) => {
const over = (isOver: boolean) => {
if (isOver) {
resolve(void 0)
}
}
const { config } = response as { config: AxiosRequestConfigPlus }
clearTimeout(config.$loading_ShowTimerId)
if (!this.getConfig("minShow", config.loadingAttrs)) {
const min = this.getConfig("min", config.loadingAttrs)
if (config.$loading_ToastID) {
const [ToastID, showTimeStamp] = [config.$loading_ToastID, config.$loading_CreateTimeStamp]
const oft = new Date().getTime() - showTimeStamp!
if (oft < min) {
setTimeout(() => {
toast.dismiss(ToastID)
over(this.getConfig("minSync", config.loadingAttrs))
}, min - oft);
} else {
over(this.getConfig("minSync", config.loadingAttrs))
toast.dismiss(ToastID)
}
delete config.$loading_ToastID
delete config.$loading_CreateTimeStamp
}
} else {
if (config.$loading_ToastID) {
const min = this.getConfig("minShowLong", config.loadingAttrs)
const [ToastID, showTimeStamp] = [config.$loading_ToastID, config.$loading_CreateTimeStamp]
const oft = new Date().getTime() - showTimeStamp!
if (oft < min) {
setTimeout(() => {
toast.dismiss(ToastID)
over(this.getConfig("minSync", config.loadingAttrs))
}, min - oft);
} else {
over(this.getConfig("minSync", config.loadingAttrs))
toast.dismiss(ToastID)
}
delete config.$loading_ToastID
delete config.$loading_CreateTimeStamp
}
}
over(!this.getConfig("minSync", config.loadingAttrs))
})
}
beforeResponseError(argu: { error: AxiosError }) {
const requestConfig = argu.error.config! as AxiosRequestConfigPlus
clearTimeout(requestConfig.$loading_ShowTimerId)
if (requestConfig.$loading_ToastID) {
const [ToastID] = [requestConfig.$loading_ToastID, requestConfig.$loading_CreateTimeStamp]
toast.dismiss(ToastID)
delete requestConfig.$loading_ToastID
delete requestConfig.$loading_CreateTimeStamp
}
}
}

36
src/plugins/http/Repeat.ts → src/plugins/http/plugins/Repeat.ts

@ -1,5 +1,5 @@
import { isCancel, AxiosRequestConfig, AxiosResponse, AxiosError } from "axios"
import { IPlugin } from "./base"
import { IPlugin } from "../base"
declare module "axios" {
interface AxiosRequestConfig {
@ -7,14 +7,33 @@ declare module "axios" {
* RepeatPlugin插件的clearPendingPool清除
*/
global?: boolean
cancelAttrs?: Partial<IConfig>
}
}
interface IConfig {
text: string
}
// 取消重复请求
export class RepeatPlugin implements IPlugin {
static name: string = "repeat"
name: string = "repeat"
pendingPool = new Map()
defaultConfig: IConfig = {
text: "${url}请求取消"
}
config: IConfig = JSON.parse(JSON.stringify(this.defaultConfig))
constructor(config: Partial<IConfig> = {}) {
; (Object.keys(config) as (keyof IConfig)[]).forEach(v => {
if (config[v]) {
this.config[v] = config[v]
}
})
}
clearPendingPool(whiteList: string[] = []) {
if (!this.pendingPool.size) return
const pendingUrlList = Array.from(this.pendingPool.keys()).filter(url => !whiteList.includes(url))
@ -22,7 +41,7 @@ export class RepeatPlugin implements IPlugin {
pendingUrlList.forEach(pendingUrl => {
if (!this.pendingPool.get(pendingUrl).global) {
this.pendingPool.get(pendingUrl).cancelFn(`${pendingUrl} 请求取消`)
this.pendingPool.get(pendingUrl).cancelFn(`${this.config.text}`.replace(/\$\{(.*?)}/g, (_, t) => t === "url" ? pendingUrl : ''))
this.pendingPool.delete(pendingUrl)
}
})
@ -40,12 +59,13 @@ export class RepeatPlugin implements IPlugin {
const { config } = response
this.pendingPool.delete(config.url)
}
beforeResponseError({ error }: { error: AxiosError }) {
const requestConfig = error.config!
if (!isCancel(error)) this.pendingPool.delete(requestConfig.url)
if (!error) return
if (isCancel(error)) {
throw new Error((requestConfig.signal as AbortSignal)?.reason || error.message || `请求'${requestConfig.url}' 被取消`)
beforeResponseError(argu: { error: AxiosError }) {
const requestConfig = argu.error.config!
if (!isCancel(argu.error)) this.pendingPool.delete(requestConfig.url)
if (!argu.error) return
if (isCancel(argu.error)) {
argu.error.message = (requestConfig.signal as AbortSignal)?.reason || argu.error.message || `请求'${requestConfig.url}' 被取消`
return
}
}
}

34
src/plugins/request.ts

@ -1,14 +1,40 @@
import { Fly } from "./http"
import { RepeatPlugin } from "./http/Repeat"
import { RepeatPlugin } from "./http/plugins/Repeat"
import { LoadingPlugin } from "./http/plugins/Loading"
const allPlugin = {
loading: new LoadingPlugin(),
repeat: new RepeatPlugin(),
}
Fly.use([allPlugin.loading])
Fly.init("$defalut", {
timeout: 10000,
}, [new RepeatPlugin()])
}, [])
Fly.init("empty", {
Fly.init("up", {
timeout: 10000,
})
}, [allPlugin.repeat])
// Fly.disposeAll()
// https://api.52vmy.cn/
// Fly.invoke().request({ method: "get", url: "http://localhost:5173/asd", global: false }).then((res) => console.log(res.data)).catch(console.log)
// Fly.invoke().request({ method: "get", url: "http://localhost:5173/", global: true }).then((res) => console.log(res.data)).catch(console.log)
// Fly.invoke().request({ method: "get", url: "https://api.52vmy.cn/api/wl/word/bing/tu", global: true }).then((res) => console.log(res.data)).catch(console.log)
// Fly.invoke().request({ method: "get", url: "https://api.52vmy.cn/api/wl/word/bing/tu", global: true }).then((res) => console.log(res.data)).catch(console.log)
// Fly.invoke("empty").request({ method: "get", url: "https://api.52vmy.cn/api/wl/yan/bay" }).then((res) => console.log(res.data)).catch(console.log)
// Fly.invoke("empty").request({ method: "get", url: "https://api.52vmy.cn/api/img/tu/girl" }).then((res) => console.log(res.data)).catch(console.log)
// setTimeout(()=>{
// Fly.invoke("empty").request({method: "get", url: "https://api.52vmy.cn/api/img/tu/man"}).then((res)=>console.log(res.data)).catch(console.log)
// Fly.invoke().callPluginByName<RepeatPlugin>(RepeatPlugin.name, "clearPendingPool")
// }, 0)
export {
Fly

16
src/router-new/index.ts

@ -0,0 +1,16 @@
import { createHashRouter } from "react-router-dom"
const router = createHashRouter([
{
path: "/",
element: ()=>import("@/views/Home"),
loader: rootLoader,
children: [
{
path: "team",
element: <Team />,
loader: teamLoader,
},
],
},
])

54
src/router/route.tsx

@ -38,33 +38,33 @@ const routesArray = [
// loading: () => <Loading color="blue"></Loading>,
root: true,
},
{
path: "/project",
component: lazy(() => import("@/views/Project")),
exact: false,
root: true,
meta: {
auth: false,
},
children: [
{
path: "/project/child",
component: lazy(() => import("@/views/Child")),
exact: true,
children: [],
meta: {
auth: true,
},
},
],
},
{
path: "/about",
component: lazy(() => import("@/views/About")),
exact: true,
root: true,
children: [],
},
// {
// path: "/project",
// component: lazy(() => import("@/views/Project")),
// exact: false,
// root: true,
// meta: {
// auth: false,
// },
// children: [
// {
// path: "/project/child",
// component: lazy(() => import("@/views/Child")),
// exact: true,
// children: [],
// meta: {
// auth: true,
// },
// },
// ],
// },
// {
// path: "/about",
// component: lazy(() => import("@/views/About")),
// exact: true,
// root: true,
// children: [],
// },
{
path: "*",
exact: false,

48
src/store/account.ts

@ -1,9 +1,45 @@
import { atom } from "jotai"
import { atom, useAtom } from "jotai"
import Http from "@/api"
import { useEffect } from "react"
import { atomWithStorage } from 'jotai/utils';
const userStore = {
userAtom: atom({
token: "",
}),
const userStore = atomWithStorage<{
isLogin: boolean
userInfo: any
}>('user', {
isLogin: false,
userInfo: null
});
// const userStore = atom<{
// isLogin: boolean
// userInfo: any
// }>({
// isLogin: false,
// userInfo: null
// });
function initUser() {
const [user, setUser] = useAtom(userStore)
let isRequest = false
useEffect(() => {
if (isRequest) return
isRequest = true
Http.getAccount().then(res => {
setUser({
...user,
isLogin: true,
userInfo: res
})
}).catch(() => {
setUser({
...user,
isLogin: false,
userInfo: false
})
})
}, [])
return [user, setUser]
}
export { userStore }
export default { userStore, initUser }

9
src/ui/Login/Login.tsx

@ -1,3 +1,4 @@
import Http from "@/api"
import { Button, FormGroup, InputGroup, Intent, Tooltip } from "@blueprintjs/core"
import { FC, ReactNode, useCallback, useState } from "react"
import { SubmitHandler, useForm } from "react-hook-form"
@ -22,8 +23,12 @@ export function Login({ onSuccess, children }: IProps) {
handleSubmit,
formState: { errors },
} = useForm<Inputs>()
const onSubmit: SubmitHandler<Inputs> = data => {
console.log(data)
const onSubmit: SubmitHandler<Inputs> = async (data) => {
console.log(await Http.apiLogin({
email: data.email,
password: data.password,
}));
onSuccess && onSuccess()
}

4
src/ui/Register/Register.tsx

@ -1,6 +1,6 @@
// import { Tag } from "@blueprintjs/core";
import { apiRegister } from "@/api"
import Http from "@/api"
import { Button, FormGroup, InputGroup, Intent, Tooltip } from "@blueprintjs/core"
import { FC, FormEvent, ReactNode, useCallback, useEffect, useRef, useState } from "react"
import { useForm } from "react-hook-form"
@ -33,7 +33,7 @@ export function Register({ onSuccess, children }: IProps) {
if (repeatPassword !== password) return toast.error("两次输入的密码不一致")
setIsLoading(true)
try {
await toast.promise(apiRegister({ email, password }, "注册失败"), {
await toast.promise(Http.apiRegister({ email, password }, "注册失败"), {
pending: '注册中',
success: '注册成功',
error: '注册失败'

18
src/views/About.tsx

@ -1,18 +0,0 @@
import withPage from "@/base/withPage";
import { Button, Dialog, DialogBody, DialogFooter } from "@blueprintjs/core";
import { useCallback, useState } from "react";
function About() {
const [isOpen, setIsOpen] = useState(false);
const toggleOverlay = useCallback(() => setIsOpen(open => !open), [setIsOpen]);
const handleClose = useCallback(() => setIsOpen(false), []);
return <>
<Button text="点击打开美丽的按钮" onClick={toggleOverlay} />
<Dialog isOpen={isOpen} title="关于我" icon="info-sign" onClose={handleClose}>
<DialogBody>{/* body contents here */}</DialogBody>
<DialogFooter actions={<Button intent="primary" text="关闭" onClick={handleClose} />} />
</Dialog>
</>
}
export default withPage(About)

9
src/views/Child.tsx

@ -1,9 +0,0 @@
import { Button } from "@blueprintjs/core";
function Child() {
return <>
<Button text="点击打开美丽的按钮"/>
</>
}
export default Child

47
src/views/Home/index.tsx

@ -1,31 +1,42 @@
import withPage from "@/base/withPage"
import { Hero } from "@/ui/Hero"
import { Button } from "@blueprintjs/core"
import { ReactNode, useCallback } from "react"
// import { Fly } from "@/plugins/request"
import { toast } from "react-toastify"
import { Fly } from "@/plugins/request"
import useSWRMutation from 'swr/mutation'
import Http from "@/api";
interface IProps {
children: ReactNode
// children: ReactNode
}
export default withPage(function Project({}: IProps) {
const onClick = useCallback(() => {
toast.success("Wow so easy !")
// Fly.invoke().request<{a: number}>({
// method: "get",
// url: "http://api.juheapi.com/japi/toh",
// global: false
// }).then((res) => console.log(res.data)).catch(console.log)
}, [])
export default function Home({ }: IProps) {
const { data, error, trigger, isMutating } = useSWRMutation("/data.json", (url: string) => {
return Fly.invoke().request<{ a: number }>({
method: "get",
url: url
})
}, {
onSuccess(resule, params) {
console.log(resule, params);
},
onError(resule, params) {
console.log(resule, params);
},
});
const aa = () => {
Http.getAccount()
}
return (
<>
<Hero></Hero>
{/* <Hero></Hero> */}
<div className="container">
<Button onClick={onClick}></Button>
<div>{JSON.stringify(data?.data)}</div>
<div>{error?.message}</div>
{isMutating ? 1 : 2}
<Button onClick={() => trigger()}></Button>
<Button onClick={() => aa()}>Sessions</Button>
</div>
</>
)
})
}

11
src/views/PlayGround.tsx

@ -1,11 +0,0 @@
import withPage from "@/base/withPage"
function PlayGround() {
return (
<>
<div>PlayGround</div>
</>
)
}
export default withPage(PlayGround)

34
src/views/Project.tsx

@ -1,34 +0,0 @@
import withPage from "@/base/withPage"
import { useHistory } from "react-router-dom"
import styled from "styled-components"
const Title = styled.h1`
font-size: 1.5em;
text-align: center;
color: #bf4f74;
`
const Wrapper = styled.section`
padding: 4em;
background: papayawhip;
`
export default withPage(function Project({ children }) {
const router = useHistory()
function toChild() {
router.push("/project/child")
}
return (
<>
<Wrapper>
<Title>Hello World!</Title>
</Wrapper>
<div>
<div onClick={toChild} style={{ height: "200px" }}>
vaas
</div>
{children}
</div>
</>
)
})

2
src/viewsSys/404.tsx → src/viewsSys/NoMatch.tsx

@ -1,6 +1,6 @@
import { LeftIn } from "@/effect"
export default function Page404() {
export default function NoMatch() {
return (
<div className="h-1/1 flex" style={{ display: "flex", alignItems: "center", justifyContent: "center", height: "100%" }}>
<LeftIn className="m-auto">
Loading…
Cancel
Save