feat: 网络请求+发布订阅模块
This commit is contained in:
5
.claude/settings.json
Normal file
5
.claude/settings.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"enabledPlugins": {
|
||||
"frontend-design@claude-plugins-official": true
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@
|
||||
"format": "oxfmt src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.17.0",
|
||||
"naive-ui": "^2.44.1",
|
||||
"pinia": "^3.0.4",
|
||||
"pinia-plugin-persistedstate": "^4.7.1",
|
||||
|
||||
214
pnpm-lock.yaml
generated
214
pnpm-lock.yaml
generated
@@ -8,6 +8,9 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
axios:
|
||||
specifier: ^1.17.0
|
||||
version: 1.17.0
|
||||
naive-ui:
|
||||
specifier: ^2.44.1
|
||||
version: 2.44.1(vue@3.5.35(typescript@6.0.3))
|
||||
@@ -850,6 +853,10 @@ packages:
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
agent-base@6.0.2:
|
||||
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
|
||||
engines: {node: '>= 6.0.0'}
|
||||
|
||||
alien-signals@3.2.1:
|
||||
resolution: {integrity: sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==}
|
||||
|
||||
@@ -872,6 +879,12 @@ packages:
|
||||
async-validator@4.2.5:
|
||||
resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==}
|
||||
|
||||
asynckit@0.4.0:
|
||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||
|
||||
axios@1.17.0:
|
||||
resolution: {integrity: sha512-J8SwNxprqqpbfenehxWYXE7CW+wM1BB4w3+N+g+/Wx40xM4rsLrfPmHHxSWIxJLYDgSY/HqlFPIYb2/S3rxafw==}
|
||||
|
||||
baseline-browser-mapping@2.10.33:
|
||||
resolution: {integrity: sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
@@ -892,6 +905,10 @@ packages:
|
||||
resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
call-bind-apply-helpers@1.0.2:
|
||||
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
caniuse-lite@1.0.30001793:
|
||||
resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==}
|
||||
|
||||
@@ -902,6 +919,10 @@ packages:
|
||||
colorjs.io@0.5.2:
|
||||
resolution: {integrity: sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==}
|
||||
|
||||
combined-stream@1.0.8:
|
||||
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
confbox@0.1.8:
|
||||
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
|
||||
|
||||
@@ -960,10 +981,18 @@ packages:
|
||||
defu@6.1.7:
|
||||
resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==}
|
||||
|
||||
delayed-stream@1.0.0:
|
||||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
detect-libc@2.1.2:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
electron-to-chromium@1.5.364:
|
||||
resolution: {integrity: sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==}
|
||||
|
||||
@@ -978,6 +1007,22 @@ packages:
|
||||
error-stack-parser-es@1.0.5:
|
||||
resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==}
|
||||
|
||||
es-define-property@1.0.1:
|
||||
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-errors@1.3.0:
|
||||
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-object-atoms@1.1.2:
|
||||
resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-set-tostringtag@2.1.0:
|
||||
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
escalade@3.2.0:
|
||||
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -1007,15 +1052,43 @@ packages:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
follow-redirects@1.16.0:
|
||||
resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==}
|
||||
engines: {node: '>=4.0'}
|
||||
peerDependencies:
|
||||
debug: '*'
|
||||
peerDependenciesMeta:
|
||||
debug:
|
||||
optional: true
|
||||
|
||||
form-data@4.0.5:
|
||||
resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
function-bind@1.1.2:
|
||||
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||
|
||||
gensync@1.0.0-beta.2:
|
||||
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
get-intrinsic@1.3.0:
|
||||
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
get-proto@1.0.1:
|
||||
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
gopd@1.2.0:
|
||||
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
graceful-fs@4.2.11:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
|
||||
@@ -1023,6 +1096,18 @@ packages:
|
||||
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
has-symbols@1.1.0:
|
||||
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
has-tostringtag@1.0.2:
|
||||
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
hasown@2.0.4:
|
||||
resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
highlight.js@11.11.1:
|
||||
resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
@@ -1030,6 +1115,10 @@ packages:
|
||||
hookable@5.5.3:
|
||||
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
|
||||
|
||||
https-proxy-agent@5.0.1:
|
||||
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
immutable@5.1.6:
|
||||
resolution: {integrity: sha512-q1swsS8K7L8usSHuOqF2TAoCCkonYz0SG38wLAggaa4Wml70zixIvt2ql4coQ2C2B3hTjltJry4r6bULwgAXLQ==}
|
||||
|
||||
@@ -1191,10 +1280,22 @@ packages:
|
||||
magic-string@0.30.21:
|
||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||
|
||||
math-intrinsics@1.1.0:
|
||||
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
memorystream@0.3.1:
|
||||
resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==}
|
||||
engines: {node: '>= 0.10.0'}
|
||||
|
||||
mime-db@1.52.0:
|
||||
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
mime-types@2.1.35:
|
||||
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
mitt@3.0.1:
|
||||
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
|
||||
|
||||
@@ -1318,6 +1419,10 @@ packages:
|
||||
resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
proxy-from-env@2.1.0:
|
||||
resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
quansync@0.2.11:
|
||||
resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
|
||||
|
||||
@@ -2490,6 +2595,12 @@ snapshots:
|
||||
|
||||
acorn@8.16.0: {}
|
||||
|
||||
agent-base@6.0.2:
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
alien-signals@3.2.1: {}
|
||||
|
||||
ansi-styles@6.2.3: {}
|
||||
@@ -2509,6 +2620,18 @@ snapshots:
|
||||
|
||||
async-validator@4.2.5: {}
|
||||
|
||||
asynckit@0.4.0: {}
|
||||
|
||||
axios@1.17.0:
|
||||
dependencies:
|
||||
follow-redirects: 1.16.0
|
||||
form-data: 4.0.5
|
||||
https-proxy-agent: 5.0.1
|
||||
proxy-from-env: 2.1.0
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
- supports-color
|
||||
|
||||
baseline-browser-mapping@2.10.33: {}
|
||||
|
||||
birpc@2.9.0: {}
|
||||
@@ -2527,6 +2650,11 @@ snapshots:
|
||||
dependencies:
|
||||
run-applescript: 7.1.0
|
||||
|
||||
call-bind-apply-helpers@1.0.2:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
function-bind: 1.1.2
|
||||
|
||||
caniuse-lite@1.0.30001793: {}
|
||||
|
||||
chokidar@5.0.0:
|
||||
@@ -2535,6 +2663,10 @@ snapshots:
|
||||
|
||||
colorjs.io@0.5.2: {}
|
||||
|
||||
combined-stream@1.0.8:
|
||||
dependencies:
|
||||
delayed-stream: 1.0.0
|
||||
|
||||
confbox@0.1.8: {}
|
||||
|
||||
confbox@0.2.4: {}
|
||||
@@ -2581,8 +2713,16 @@ snapshots:
|
||||
|
||||
defu@6.1.7: {}
|
||||
|
||||
delayed-stream@1.0.0: {}
|
||||
|
||||
detect-libc@2.1.2: {}
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
dependencies:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
es-errors: 1.3.0
|
||||
gopd: 1.2.0
|
||||
|
||||
electron-to-chromium@1.5.364: {}
|
||||
|
||||
enhanced-resolve@5.22.1:
|
||||
@@ -2594,6 +2734,21 @@ snapshots:
|
||||
|
||||
error-stack-parser-es@1.0.5: {}
|
||||
|
||||
es-define-property@1.0.1: {}
|
||||
|
||||
es-errors@1.3.0: {}
|
||||
|
||||
es-object-atoms@1.1.2:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
|
||||
es-set-tostringtag@2.1.0:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
get-intrinsic: 1.3.0
|
||||
has-tostringtag: 1.0.2
|
||||
hasown: 2.0.4
|
||||
|
||||
escalade@3.2.0: {}
|
||||
|
||||
escape-string-regexp@5.0.0: {}
|
||||
@@ -2612,19 +2767,68 @@ snapshots:
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.4
|
||||
|
||||
follow-redirects@1.16.0: {}
|
||||
|
||||
form-data@4.0.5:
|
||||
dependencies:
|
||||
asynckit: 0.4.0
|
||||
combined-stream: 1.0.8
|
||||
es-set-tostringtag: 2.1.0
|
||||
hasown: 2.0.4
|
||||
mime-types: 2.1.35
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
function-bind@1.1.2: {}
|
||||
|
||||
gensync@1.0.0-beta.2: {}
|
||||
|
||||
get-intrinsic@1.3.0:
|
||||
dependencies:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
es-define-property: 1.0.1
|
||||
es-errors: 1.3.0
|
||||
es-object-atoms: 1.1.2
|
||||
function-bind: 1.1.2
|
||||
get-proto: 1.0.1
|
||||
gopd: 1.2.0
|
||||
has-symbols: 1.1.0
|
||||
hasown: 2.0.4
|
||||
math-intrinsics: 1.1.0
|
||||
|
||||
get-proto@1.0.1:
|
||||
dependencies:
|
||||
dunder-proto: 1.0.1
|
||||
es-object-atoms: 1.1.2
|
||||
|
||||
gopd@1.2.0: {}
|
||||
|
||||
graceful-fs@4.2.11: {}
|
||||
|
||||
has-flag@4.0.0: {}
|
||||
|
||||
has-symbols@1.1.0: {}
|
||||
|
||||
has-tostringtag@1.0.2:
|
||||
dependencies:
|
||||
has-symbols: 1.1.0
|
||||
|
||||
hasown@2.0.4:
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
highlight.js@11.11.1: {}
|
||||
|
||||
hookable@5.5.3: {}
|
||||
|
||||
https-proxy-agent@5.0.1:
|
||||
dependencies:
|
||||
agent-base: 6.0.2
|
||||
debug: 4.4.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
immutable@5.1.6: {}
|
||||
|
||||
is-docker@3.0.0: {}
|
||||
@@ -2738,8 +2942,16 @@ snapshots:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
math-intrinsics@1.1.0: {}
|
||||
|
||||
memorystream@0.3.1: {}
|
||||
|
||||
mime-db@1.52.0: {}
|
||||
|
||||
mime-types@2.1.35:
|
||||
dependencies:
|
||||
mime-db: 1.52.0
|
||||
|
||||
mitt@3.0.1: {}
|
||||
|
||||
mlly@1.8.2:
|
||||
@@ -2883,6 +3095,8 @@ snapshots:
|
||||
|
||||
powershell-utils@0.1.0: {}
|
||||
|
||||
proxy-from-env@2.1.0: {}
|
||||
|
||||
quansync@0.2.11: {}
|
||||
|
||||
read-package-json-fast@4.0.0:
|
||||
|
||||
17
src/api/request/errorUI.ts
Normal file
17
src/api/request/errorUI.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { createDiscreteApi } from 'naive-ui'
|
||||
import { eventBus, AppEvents } from '@/utils/eventBus'
|
||||
import type { RequestErrorEvent } from './types'
|
||||
|
||||
/** 离散式 API(可在组件外使用) */
|
||||
const { message } = createDiscreteApi(['message'])
|
||||
|
||||
/**
|
||||
* 注册请求错误 UI 展示
|
||||
*
|
||||
* 订阅应用级 REQUEST_ERROR 事件,统一展示错误提示,
|
||||
* 实现网络模块与 UI 的解耦。
|
||||
*/
|
||||
eventBus.on(AppEvents.REQUEST_ERROR, (payload: unknown) => {
|
||||
const { message: errorMessage } = payload as RequestErrorEvent
|
||||
message.error(errorMessage)
|
||||
})
|
||||
5
src/api/request/index.ts
Normal file
5
src/api/request/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export { requestClient } from './requestClient'
|
||||
export type { ApiResponse, RequestErrorEvent, RequestOptions } from './types'
|
||||
|
||||
/** 注册错误 UI 展示(副作用导入,应用启动时执行) */
|
||||
import './errorUI'
|
||||
243
src/api/request/requestClient.ts
Normal file
243
src/api/request/requestClient.ts
Normal file
@@ -0,0 +1,243 @@
|
||||
import axios, { type AxiosInstance, type AxiosRequestConfig, type CreateAxiosDefaults } from 'axios'
|
||||
import { useAccountStore } from '@/stores'
|
||||
import { eventBus, AppEvents } from '@/utils/eventBus'
|
||||
import type { ApiResponse, InternalRequestConfig, RequestErrorEvent } from './types'
|
||||
|
||||
/**
|
||||
* 替换 URL 中的路径参数占位符
|
||||
*
|
||||
* @example
|
||||
* resolvePathParams('/user/:id/posts', { id: 1 }) // → '/user/1/posts'
|
||||
*/
|
||||
function resolvePathParams(url: string, pathParams?: Record<string, string | number>): string {
|
||||
if (!pathParams) return url
|
||||
return url.replace(/:(\w+)/g, (_, key) => {
|
||||
const value = pathParams[key]
|
||||
if (value === undefined) {
|
||||
throw new Error(`路径参数 "${key}" 缺失`)
|
||||
}
|
||||
return String(value)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于 axios 封装的网络请求客户端
|
||||
*
|
||||
* 不依赖任何 UI 库,错误通过事件总线发布,由外部订阅处理。
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // GET 请求
|
||||
* const res = await requestClient.get<UserListResult>('/users', { page: 1 })
|
||||
*
|
||||
* // POST 请求
|
||||
* const res = await requestClient.post<LoginResult>('/auth/login', { username, password })
|
||||
* ```
|
||||
*/
|
||||
class RequestClient {
|
||||
/** axios 实例 */
|
||||
private instance: AxiosInstance
|
||||
|
||||
constructor(config?: CreateAxiosDefaults) {
|
||||
this.instance = axios.create({
|
||||
baseURL: '/api',
|
||||
timeout: 15000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
...config
|
||||
})
|
||||
|
||||
this.setupInterceptors()
|
||||
}
|
||||
|
||||
/** 注册请求和响应拦截器 */
|
||||
private setupInterceptors(): void {
|
||||
// === 请求拦截器 ===
|
||||
this.instance.interceptors.request.use(
|
||||
(config) => {
|
||||
const reqConfig = config as InternalRequestConfig
|
||||
|
||||
// 替换路径参数 :param → 实际值
|
||||
if (reqConfig.pathParams && config.url) {
|
||||
config.url = resolvePathParams(config.url, reqConfig.pathParams)
|
||||
}
|
||||
|
||||
// 跳过鉴权时不注入 token
|
||||
if (!reqConfig?.skipAuth) {
|
||||
const accountStore = useAccountStore()
|
||||
if (accountStore.token) {
|
||||
config.headers.Authorization = `Bearer ${accountStore.token}`
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// === 响应拦截器 ===
|
||||
// 拦截器内部将 axiosResponse 解包为 ApiResponse,
|
||||
// 返回类型与 axios 类型签名不匹配,但公共方法已声明正确返回类型。
|
||||
this.instance.interceptors.response.use(
|
||||
// @ts-expect-error 拦截器解包 axiosResponse → ApiResponse
|
||||
(response) => {
|
||||
const data = response.data as ApiResponse
|
||||
|
||||
// 业务成功
|
||||
if (data.code === 0) {
|
||||
return data
|
||||
}
|
||||
|
||||
// 业务错误事件载荷
|
||||
const errorEvent: RequestErrorEvent = {
|
||||
type: 'business',
|
||||
code: data.code,
|
||||
message: data.message || '请求失败'
|
||||
}
|
||||
|
||||
// 401 → 额外发布未授权事件,解耦路由
|
||||
if (data.code === 401) {
|
||||
eventBus.emit(AppEvents.UNAUTHORIZED)
|
||||
}
|
||||
|
||||
// 发布请求错误事件,由 errorUI 模块订阅展示
|
||||
eventBus.emit(AppEvents.REQUEST_ERROR, errorEvent)
|
||||
return Promise.reject(data)
|
||||
},
|
||||
(error) => {
|
||||
let errorEvent: RequestErrorEvent
|
||||
|
||||
if (error.response?.status === 401) {
|
||||
eventBus.emit(AppEvents.UNAUTHORIZED)
|
||||
errorEvent = {
|
||||
type: 'http',
|
||||
code: 401,
|
||||
message: '登录已过期,请重新登录'
|
||||
}
|
||||
} else if (!error.response) {
|
||||
errorEvent = {
|
||||
type: 'network',
|
||||
message: '网络异常,请检查网络连接'
|
||||
}
|
||||
} else {
|
||||
errorEvent = {
|
||||
type: 'http',
|
||||
code: error.response.status,
|
||||
message: `请求错误 (${error.response.status})`
|
||||
}
|
||||
}
|
||||
|
||||
eventBus.emit(AppEvents.REQUEST_ERROR, errorEvent)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用请求方法
|
||||
*
|
||||
* 适用于路径参数、混合参数等复杂场景。
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // 路径参数 + 查询参数
|
||||
* requestClient.request<PostList>({
|
||||
* method: 'GET',
|
||||
* url: '/user/:id/posts',
|
||||
* params: { page: 1 },
|
||||
* pathParams: { id: 1 }
|
||||
* })
|
||||
*
|
||||
* // 路径参数 + 请求体
|
||||
* requestClient.request<Result>({
|
||||
* method: 'POST',
|
||||
* url: '/user/:id/post',
|
||||
* data: body,
|
||||
* pathParams: { id: 1 }
|
||||
* })
|
||||
*
|
||||
* // 自定义请求头 + 跳过鉴权
|
||||
* requestClient.request<Result>({
|
||||
* method: 'POST',
|
||||
* url: '/public/upload',
|
||||
* data: formData,
|
||||
* headers: { 'Content-Type': 'multipart/form-data' },
|
||||
* requestOptions: { skipAuth: true }
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
request<T = unknown>(config: InternalRequestConfig): Promise<ApiResponse<T>> {
|
||||
return this.instance.request(config)
|
||||
}
|
||||
|
||||
/**
|
||||
* GET 请求
|
||||
* @param url - 请求地址
|
||||
* @param params - 查询参数
|
||||
* @param config - 额外配置(pathParams、requestOptions 等)
|
||||
*/
|
||||
get<T = unknown>(
|
||||
url: string,
|
||||
params?: Record<string, unknown>,
|
||||
config?: InternalRequestConfig
|
||||
): Promise<ApiResponse<T>> {
|
||||
return this.instance.get(url, { params, ...config })
|
||||
}
|
||||
|
||||
/**
|
||||
* POST 请求
|
||||
* @param url - 请求地址
|
||||
* @param data - 请求体
|
||||
* @param config - 额外配置(pathParams、requestOptions 等)
|
||||
*/
|
||||
post<T = unknown>(
|
||||
url: string,
|
||||
data?: unknown,
|
||||
config?: InternalRequestConfig
|
||||
): Promise<ApiResponse<T>> {
|
||||
return this.instance.post(url, data, config)
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT 请求
|
||||
* @param url - 请求地址
|
||||
* @param data - 请求体
|
||||
* @param config - 额外配置(pathParams、requestOptions 等)
|
||||
*/
|
||||
put<T = unknown>(
|
||||
url: string,
|
||||
data?: unknown,
|
||||
config?: InternalRequestConfig
|
||||
): Promise<ApiResponse<T>> {
|
||||
return this.instance.put(url, data, config)
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE 请求
|
||||
* @param url - 请求地址
|
||||
* @param config - 额外配置(pathParams、requestOptions 等)
|
||||
*/
|
||||
delete<T = unknown>(url: string, config?: InternalRequestConfig): Promise<ApiResponse<T>> {
|
||||
return this.instance.delete(url, config)
|
||||
}
|
||||
|
||||
/**
|
||||
* PATCH 请求
|
||||
* @param url - 请求地址
|
||||
* @param data - 请求体
|
||||
* @param config - 额外配置(pathParams、requestOptions 等)
|
||||
*/
|
||||
patch<T = unknown>(
|
||||
url: string,
|
||||
data?: unknown,
|
||||
config?: InternalRequestConfig
|
||||
): Promise<ApiResponse<T>> {
|
||||
return this.instance.patch(url, data, config)
|
||||
}
|
||||
}
|
||||
|
||||
/** 请求客户端单例 */
|
||||
export const requestClient = new RequestClient()
|
||||
29
src/api/request/types.ts
Normal file
29
src/api/request/types.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { AxiosRequestConfig } from 'axios'
|
||||
|
||||
/** 后端统一响应格式 */
|
||||
export interface ApiResponse<T = unknown> {
|
||||
/** 业务状态码,0 表示成功 */
|
||||
code: number
|
||||
/** 响应消息 */
|
||||
message: string
|
||||
/** 响应数据 */
|
||||
data: T
|
||||
}
|
||||
|
||||
/** 扩展的请求配置,包含自定义选项 */
|
||||
export interface InternalRequestConfig extends AxiosRequestConfig {
|
||||
/** 路径参数,替换 URL 中的 :param 占位符(如 /user/:id → /user/1) */
|
||||
pathParams?: Record<string, string | number>
|
||||
/** 跳过鉴权注入(不携带 Authorization 头) */
|
||||
skipAuth?: boolean
|
||||
}
|
||||
|
||||
/** 请求错误事件载荷 */
|
||||
export interface RequestErrorEvent {
|
||||
/** 错误类型 */
|
||||
type: 'business' | 'http' | 'network'
|
||||
/** 业务状态码(business 类型时有效) */
|
||||
code?: number
|
||||
/** 错误消息 */
|
||||
message: string
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { eventBus, AppEvents } from '@/utils/eventBus'
|
||||
import { useAccountStore } from '@/stores'
|
||||
import { createRouterGuard } from './guard.ts'
|
||||
import routes from './routes.ts'
|
||||
|
||||
@@ -13,4 +15,11 @@ const router = createRouter({
|
||||
// 创建路由守卫
|
||||
createRouterGuard(router)
|
||||
|
||||
/** 订阅 401 未授权事件:清除 token 并跳转登录页 */
|
||||
eventBus.on(AppEvents.UNAUTHORIZED, () => {
|
||||
const accountStore = useAccountStore()
|
||||
accountStore.token = ''
|
||||
router.push('/auth/login')
|
||||
})
|
||||
|
||||
export default router
|
||||
|
||||
91
src/utils/eventBus.ts
Normal file
91
src/utils/eventBus.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
/** 事件处理器类型 */
|
||||
type EventHandler = (...args: unknown[]) => void
|
||||
|
||||
/** 应用级事件常量 */
|
||||
export const AppEvents = {
|
||||
/** 401 未授权 */
|
||||
UNAUTHORIZED: 'app:unauthorized',
|
||||
/** 请求错误(业务错误 / HTTP 错误 / 网络错误) */
|
||||
REQUEST_ERROR: 'app:request:error'
|
||||
} as const
|
||||
|
||||
/**
|
||||
* 发布订阅事件总线
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // 注册事件
|
||||
* eventBus.on(AppEvents.UNAUTHORIZED, () => {
|
||||
* router.push('/auth/login')
|
||||
* })
|
||||
*
|
||||
* // 发布事件
|
||||
* eventBus.emit(AppEvents.UNAUTHORIZED)
|
||||
* ```
|
||||
*/
|
||||
class EventBus {
|
||||
/** 事件名 → 处理器集合 */
|
||||
private handlers = new Map<string, Set<EventHandler>>()
|
||||
|
||||
/**
|
||||
* 注册事件处理器
|
||||
* @param event - 事件名
|
||||
* @param handler - 处理器函数
|
||||
*/
|
||||
on(event: string, handler: EventHandler): void {
|
||||
if (!this.handlers.has(event)) {
|
||||
this.handlers.set(event, new Set())
|
||||
}
|
||||
this.handlers.get(event)!.add(handler)
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册一次性事件处理器(触发后自动取消注册)
|
||||
* @param event - 事件名
|
||||
* @param handler - 处理器函数
|
||||
*/
|
||||
once(event: string, handler: EventHandler): void {
|
||||
const wrapper: EventHandler = (...args: unknown[]) => {
|
||||
handler(...args)
|
||||
this.off(event, wrapper)
|
||||
}
|
||||
this.on(event, wrapper)
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消注册事件处理器
|
||||
* @param event - 事件名
|
||||
* @param handler - 要取消的处理器函数
|
||||
*/
|
||||
off(event: string, handler: EventHandler): void {
|
||||
this.handlers.get(event)?.delete(handler)
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布事件
|
||||
* @param event - 事件名
|
||||
* @param args - 传递给处理器的参数
|
||||
*/
|
||||
emit(event: string, ...args: unknown[]): void {
|
||||
const handlers = this.handlers.get(event)
|
||||
if (!handlers) return
|
||||
handlers.forEach((handler) => {
|
||||
handler(...args)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除指定事件的所有处理器,或清除所有事件
|
||||
* @param event - 可选,不传则清除全部
|
||||
*/
|
||||
clear(event?: string): void {
|
||||
if (event) {
|
||||
this.handlers.delete(event)
|
||||
} else {
|
||||
this.handlers.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 事件总线单例 */
|
||||
export const eventBus = new EventBus()
|
||||
@@ -64,14 +64,34 @@ const rules: FormRules = {
|
||||
{
|
||||
required: true,
|
||||
message: '请输入登陆账号',
|
||||
trigger: ['blur']
|
||||
trigger: ['blur'],
|
||||
validator: (rule: FormItemRule, value: string) => {
|
||||
if (value.length < 2) {
|
||||
return new Error('账号长度不能小于2位')
|
||||
} else if (value.length > 20) {
|
||||
return new Error('账号长度不能大于20位')
|
||||
} else if (!/^[A-Za-z][A-Za-z0-9]*$/.test(value)) {
|
||||
return new Error('账号以字母开头,只能包含字母和数字')
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入登陆密码',
|
||||
trigger: ['blur']
|
||||
trigger: ['blur'],
|
||||
validator: (rule: FormItemRule, value: string) => {
|
||||
if (value.length < 6) {
|
||||
return new Error('密码长度不能小于6位')
|
||||
} else if (value.length > 20) {
|
||||
return new Error('密码长度不能大于20位')
|
||||
} else if (!/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/.test(value)) {
|
||||
return new Error('密码必须包含大小写字母和数字')
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user