├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .nuxtrc ├── CHANGELOG.md ├── README.md ├── commitlint.config.ts ├── eslint.config.mjs ├── package.json ├── playground ├── api │ ├── ajax.ts │ └── index.ts ├── app.vue ├── error.vue ├── nuxt.config.ts ├── package.json ├── pages │ ├── index.vue │ ├── test-duplicate.vue │ ├── test-handler.vue │ └── test-reactive.vue └── server │ └── api │ ├── get-ip.ts │ ├── get-list.ts │ └── get-num.ts ├── pnpm-lock.yaml ├── renovate.json ├── src ├── module.ts └── runtime │ ├── ajax.ts │ └── type.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | 4 | # Logs 5 | *.log* 6 | 7 | # Temp directories 8 | .temp 9 | .tmp 10 | .cache 11 | 12 | # Yarn 13 | **/.yarn/cache 14 | **/.yarn/*state* 15 | 16 | # Generated dirs 17 | dist 18 | 19 | # Nuxt 20 | .nuxt 21 | .output 22 | .vercel_build_output 23 | .build-* 24 | .env 25 | .netlify 26 | 27 | # Env 28 | .env 29 | 30 | # Testing 31 | reports 32 | coverage 33 | *.lcov 34 | .nyc_output 35 | 36 | # VSCode 37 | .vscode/* 38 | !.vscode/settings.json 39 | !.vscode/tasks.json 40 | !.vscode/launch.json 41 | !.vscode/extensions.json 42 | !.vscode/*.code-snippets 43 | 44 | # Intellij idea 45 | *.iml 46 | .idea 47 | 48 | # OSX 49 | .DS_Store 50 | .AppleDouble 51 | .LSOverride 52 | .AppleDB 53 | .AppleDesktop 54 | Network Trash Folder 55 | Temporary Items 56 | .apdisk 57 | 58 | package-lock.json -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | npx --no-install commitlint --edit "$1" 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npx lint-staged 2 | -------------------------------------------------------------------------------- /.nuxtrc: -------------------------------------------------------------------------------- 1 | imports.autoImport=false 2 | typescript.includeWorkspace=true 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # nuxt-custom-fetch 2 | 3 | ## v2.3.1 4 | 5 | [compare changes](https://github.com/xjccc/nuxt-custom-fetch/compare/v2.3.0...v2.3.1) 6 | 7 | ### 🩹 Fixes 8 | 9 | - Show logs error ([#86](https://github.com/xjccc/nuxt-custom-fetch/pull/86)) 10 | 11 | ### ❤️ Contributors 12 | 13 | - Xjccc ([@xjccc](https://github.com/xjccc)) 14 | 15 | ## v2.3.0 16 | 17 | [compare changes](https://github.com/xjccc/nuxt-custom-fetch/compare/v2.2.0...v2.3.0) 18 | 19 | ### 🚀 Enhancements 20 | 21 | - Add client side watch ([#80](https://github.com/xjccc/nuxt-custom-fetch/pull/80)) 22 | 23 | ### ❤️ Contributors 24 | 25 | - Xjccc ([@xjccc](https://github.com/xjccc)) 26 | 27 | ## v2.2.0 28 | 29 | [compare changes](https://github.com/xjccc/nuxt-custom-fetch/compare/v2.1.4...v2.2.0) 30 | 31 | ### 🚀 Enhancements 32 | 33 | - Request logs ([#75](https://github.com/xjccc/nuxt-custom-fetch/pull/75)) 34 | 35 | ### ❤️ Contributors 36 | 37 | - Xjccc ([@xjccc](https://github.com/xjccc)) 38 | 39 | ## v2.1.4 40 | 41 | [compare changes](https://github.com/xjccc/nuxt-custom-fetch/compare/v2.1.2...v2.1.4) 42 | 43 | ### 📦 Build 44 | 45 | - V2.1.2 ([8eabc5c](https://github.com/xjccc/nuxt-custom-fetch/commit/8eabc5c)) 46 | 47 | ### 🏡 Chore 48 | 49 | - **release:** V2.1.3 ([fb27560](https://github.com/xjccc/nuxt-custom-fetch/commit/fb27560)) 50 | - Changelog ([0bd5a5a](https://github.com/xjccc/nuxt-custom-fetch/commit/0bd5a5a)) 51 | 52 | ### ❤️ Contributors 53 | 54 | - Xjccc ([@xjccc](http://github.com/xjccc)) 55 | 56 | ## v2.1.3 57 | 58 | [compare changes](https://github.com/xjccc/nuxt-custom-fetch/compare/v2.1.2...v2.1.3) 59 | 60 | ## v2.1.2 61 | 62 | [compare changes](https://github.com/xjccc/nuxt-custom-fetch/compare/v2.1.1...v2.1.2) 63 | 64 | ### 📖 Documentation 65 | 66 | - Deprecate pending ([74c9330](https://github.com/xjccc/nuxt-custom-fetch/commit/74c9330)) 67 | 68 | ### 🏡 Chore 69 | 70 | - **release:** V2.1.2 ([966a999](https://github.com/xjccc/nuxt-custom-fetch/commit/966a999)) 71 | 72 | ### ❤️ Contributors 73 | 74 | - Xjccc ([@xjccc](http://github.com/xjccc)) 75 | 76 | ### 📖 Documentation 77 | 78 | - Deprecate pending ([74c9330](https://github.com/xjccc/nuxt-custom-fetch/commit/74c9330)) 79 | 80 | ### ❤️ Contributors 81 | 82 | - Xjccc ([@xjccc](http://github.com/xjccc)) 83 | 84 | ## v2.1.1 85 | 86 | [compare changes](https://github.com/xjccc/nuxt-custom-fetch/compare/v2.1.0...v2.1.1) 87 | 88 | ### 🩹 Fixes 89 | 90 | - Set immutableKey ([abbcbcf](https://github.com/xjccc/nuxt-custom-fetch/commit/abbcbcf)) 91 | 92 | ### ❤️ Contributors 93 | 94 | - Xjccc ([@xjccc](http://github.com/xjccc)) 95 | 96 | ## v2.1.0 97 | 98 | 99 | ### 🚀 Enhancements 100 | 101 | - 创建nuxt-custom-fetch ([77f946f](https://github.com/xjccc/nuxt-custom-fetch/commit/77f946f)) 102 | - Nuxt-custom-fetch添加key ([2a56d35](https://github.com/xjccc/nuxt-custom-fetch/commit/2a56d35)) 103 | - 增加useParamsHandler\paramsHandler ([3a9f7ca](https://github.com/xjccc/nuxt-custom-fetch/commit/3a9f7ca)) 104 | - ImmutableKey ([b0fae1d](https://github.com/xjccc/nuxt-custom-fetch/commit/b0fae1d)) 105 | - Commitlint ([82f0fd9](https://github.com/xjccc/nuxt-custom-fetch/commit/82f0fd9)) 106 | - Cz-git ([f53e1bc](https://github.com/xjccc/nuxt-custom-fetch/commit/f53e1bc)) 107 | - Lint-staged ([54b3ad0](https://github.com/xjccc/nuxt-custom-fetch/commit/54b3ad0)) 108 | 109 | ### 🩹 Fixes 110 | 111 | - 修改版本号 ([8c8df3b](https://github.com/xjccc/nuxt-custom-fetch/commit/8c8df3b)) 112 | - 更新playground ([4462a03](https://github.com/xjccc/nuxt-custom-fetch/commit/4462a03)) 113 | - 修改devProxy ([0e9a591](https://github.com/xjccc/nuxt-custom-fetch/commit/0e9a591)) 114 | - Custom-fetch type ([948d72f](https://github.com/xjccc/nuxt-custom-fetch/commit/948d72f)) 115 | - Generate hash with url ([e3a3337](https://github.com/xjccc/nuxt-custom-fetch/commit/e3a3337)) 116 | - ParamsHandler ([701b16c](https://github.com/xjccc/nuxt-custom-fetch/commit/701b16c)) 117 | - Type ([c84c6f2](https://github.com/xjccc/nuxt-custom-fetch/commit/c84c6f2)) 118 | - Url type ([d02ccfc](https://github.com/xjccc/nuxt-custom-fetch/commit/d02ccfc)) 119 | - Client fetch use $fetch ([85d3977](https://github.com/xjccc/nuxt-custom-fetch/commit/85d3977)) 120 | - Add auto-key ([e73295c](https://github.com/xjccc/nuxt-custom-fetch/commit/e73295c)) 121 | - Reactive params ([e173c5d](https://github.com/xjccc/nuxt-custom-fetch/commit/e173c5d)) 122 | 123 | ### 💅 Refactors 124 | 125 | - Get\post ([13a03e6](https://github.com/xjccc/nuxt-custom-fetch/commit/13a03e6)) 126 | - Generate hash key ([b3edd53](https://github.com/xjccc/nuxt-custom-fetch/commit/b3edd53)) 127 | 128 | ### 📖 Documentation 129 | 130 | - Readme ([85d7f7c](https://github.com/xjccc/nuxt-custom-fetch/commit/85d7f7c)) 131 | - Update ([36d600f](https://github.com/xjccc/nuxt-custom-fetch/commit/36d600f)) 132 | 133 | ### 📦 Build 134 | 135 | - Update nuxt@3.8 ([9bd4327](https://github.com/xjccc/nuxt-custom-fetch/commit/9bd4327)) 136 | - Release ([57f30ae](https://github.com/xjccc/nuxt-custom-fetch/commit/57f30ae)) 137 | - V2.0.0 ([e98cf75](https://github.com/xjccc/nuxt-custom-fetch/commit/e98cf75)) 138 | - V2.0.1 ([ee7b903](https://github.com/xjccc/nuxt-custom-fetch/commit/ee7b903)) 139 | 140 | ### 🏡 Chore 141 | 142 | - Delete eslint ([16b4f84](https://github.com/xjccc/nuxt-custom-fetch/commit/16b4f84)) 143 | - Delete @nuxtjs/eslint-config-typescript ([9f537f9](https://github.com/xjccc/nuxt-custom-fetch/commit/9f537f9)) 144 | - Remove eslint-plugin-vue ([14c2e00](https://github.com/xjccc/nuxt-custom-fetch/commit/14c2e00)) 145 | - Postinstall -> prepare ([8d5fd0c](https://github.com/xjccc/nuxt-custom-fetch/commit/8d5fd0c)) 146 | 147 | ### ❤️ Contributors 148 | 149 | - Xjccc ([@xjccc](http://github.com/xjccc)) 150 | 151 | ## 2.0.4 152 | 153 | ### Patch Changes 154 | 155 | - fix reactive params 156 | 157 | ## 2.0.3 158 | 159 | ### Patch Changes 160 | 161 | - feat immutableKey 162 | 163 | ## 2.0.2 164 | 165 | ### Patch Changes 166 | 167 | - add autoKey 168 | 169 | ## 2.0.1 170 | 171 | ### Patch Changes 172 | 173 | - update playground 174 | 175 | ## 2.0.0 176 | 177 | ### Major Changes 178 | 179 | - client fetch use $fetch 180 | 181 | ## 1.1.7 182 | 183 | ### Patch Changes 184 | 185 | - change url type 186 | 187 | ## 1.1.6 188 | 189 | ### Patch Changes 190 | 191 | - fix paramsHandler params type 192 | 193 | ## 1.1.5 194 | 195 | ### Patch Changes 196 | 197 | - fix paramsHandler 198 | 199 | ## 1.1.4 200 | 201 | ### Patch Changes 202 | 203 | - generate with url 204 | 205 | ## 1.1.3 206 | 207 | ### Patch Changes 208 | 209 | - generate hash key 210 | 211 | ## 1.1.2 212 | 213 | ### Patch Changes 214 | 215 | - update/types 216 | 217 | ## 1.1.1 218 | 219 | ### Patch Changes 220 | 221 | - update 222 | 223 | ## 1.1.0 224 | 225 | ### Minor Changes 226 | 227 | - update nuxt@3.8 228 | 229 | ## 1.0.8 230 | 231 | ### Patch Changes 232 | 233 | - useParamsHandler\paramsHandler 234 | 235 | ## 1.0.7 236 | 237 | ### Patch Changes 238 | 239 | - 添加 key 240 | 241 | ## 1.0.6 242 | 243 | ### Patch Changes 244 | 245 | - refactor: get\post 246 | - 修改 get\post 返回值 247 | - 修改 docs\修改 get\post 248 | 249 | ## 1.0.6-alpha.1 250 | 251 | ### Patch Changes 252 | 253 | - 修改 get\post 返回值 254 | - Updated dependencies 255 | - nuxt-custom-fetch@1.0.6-alpha.1 256 | 257 | ## 1.0.6-alpha.0 258 | 259 | ### Patch Changes 260 | 261 | - refactor: get\post 262 | 263 | ## 1.0.5 264 | 265 | ### Patch Changes 266 | 267 | - nuxt-custom-fetch\docs: 更新/nuxt-error-and-cache\fix: 修改最低支持版本 268 | 269 | ## 1.0.4 270 | 271 | ### Patch Changes 272 | 273 | - docs 更新 274 | 275 | ## 1.0.3 276 | 277 | ### Patch Changes 278 | 279 | - 修改 onResponse 不需要返回值 280 | 281 | ## 1.0.2 282 | 283 | ### Patch Changes 284 | 285 | - 增加 HTTP 拦截器,config 拦截器调用 286 | 287 | ## 1.0.1 288 | 289 | ### Patch Changes 290 | 291 | - 增加 AsyncDataOptions 292 | 293 | ## 1.0.0 294 | 295 | ### Major Changes 296 | 297 | - v1.0.0 298 | 299 | ### Patch Changes 300 | 301 | - 自定义封装 useAsyncData 302 | - 去除 useHandler 参数 303 | 304 | ## 0.0.2-alpha.0 305 | 306 | ### Patch Changes 307 | 308 | - 自定义封装 useAsyncData 309 | - 去除 useHandler 参数 310 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nuxt Module 2 | 3 | 简单封装 useAsyncData 4 | 5 | `version > 3.0.0` 6 | 7 | ## 注意 8 | 9 | 请求的使用形式,类似于之前的`ajax`,修改参数后,重新调用封装方法。 10 | 11 | 1. 在 onMounted 中请求无效(version > 3.0.0) 12 | 13 | - 在下一个 nextTick 中执行,或者设置{server: false},watch pending 变化 14 | 15 | 2. `refresh`和`execute`使用上也需要注意,参数不是响应式的,所以会一直是第一次请求的参数 16 | 17 | 3. 如果传入响应式对象,使用`watch`监听,会自动重新请求 18 | 19 | ## 安装 20 | 21 | ```bash 22 | pnpm add nuxt-custom-fetch 23 | ``` 24 | 25 | ```ts 26 | // nuxt.config.ts 27 | export default defineNuxtConfig({ 28 | modules: ['nuxt-custom-fetch'] 29 | }) 30 | ``` 31 | 32 | ## 使用 33 | 34 | 实例化全局 CustomFetch 35 | 36 | ```ts 37 | const ajax = new CustomFetch({ 38 | baseURL: '' 39 | }) 40 | 41 | // `http|get|post` 42 | ajax.get('/api/user') 43 | 44 | ajax.post('/api/user') 45 | 46 | ajax.http({ 47 | method: 'GET' 48 | }) 49 | ``` 50 | 51 | ## 类型 52 | 53 | ### useAsyncDataType 54 | 55 | [use-async-data-type](https://nuxt.com/docs/api/composables/use-async-data#type) 56 | 57 | ```ts 58 | export declare class CustomFetch { 59 | baseURL: any 60 | immutableKey: boolean 61 | showLogs: boolean 62 | params: HTTPConfig 63 | baseHandler: HTTPConfig['handler'] 64 | interceptors: Interceptors 65 | offline: typeof Noop 66 | constructor (config?: HTTPConfig) 67 | private baseConfig 68 | http(url: NitroFetchRequest, config: HTTPConfig & { 69 | method: FetchMethod 70 | }, options?: AsyncDataOptions): CustomFetchReturnValue 71 | get(url: NitroFetchRequest, config?: HTTPConfig, options?: AsyncDataOptions): CustomFetchReturnValue 72 | post(url: NitroFetchRequest, config?: HTTPConfig, options?: AsyncDataOptions): CustomFetchReturnValue 73 | } 74 | 75 | export type FetchMethod = 'options' | 'GET' | 'POST' | 'get' | 'HEAD' | 'PATCH' | 'PUT' | 'DELETE' | 'CONNECT' | 'OPTIONS' | 'TRACE' | 'post' | 'head' | 'patch' | 'put' | 'delete' | 'connect' | 'trace' | undefined 76 | export interface HTTPConfig extends Omit { 77 | key?: string 78 | immutableKey?: boolean 79 | baseURL?: string 80 | useHandler?: boolean 81 | handler?: (params: Record) => Record 82 | offline?: () => void 83 | } 84 | export interface Interceptors { 85 | onRequest?: FetchOptions['onRequest'] 86 | onRequestError?: FetchOptions['onRequestError'] 87 | onResponse?: FetchOptions['onResponse'] 88 | onResponseError?: FetchOptions['onResponseError'] 89 | } 90 | ``` 91 | 92 | ## 示例 93 | 94 | ```ts 95 | // api.ts 96 | export const getInfo = (params: Record) => 97 | ajax.get('/api/get-ip', { params }, {}) 98 | ``` 99 | 100 | ```ts 101 | // index.vue 102 | import { getInfo } from './ajax' 103 | const { data, error, pending } = await getInfo({ 104 | sign: 123 105 | }) 106 | console.log(data.value) 107 | ``` 108 | 109 | ## Development 110 | 111 | - Run `npm run dev:prepare` to generate type stubs. 112 | - Use `npm run dev` to start [playground](./playground) in development mode. 113 | -------------------------------------------------------------------------------- /commitlint.config.ts: -------------------------------------------------------------------------------- 1 | /** @type {import('cz-git').UserConfig} */ 2 | module.exports = { 3 | extends: ['@commitlint/config-conventional'], 4 | rules: { 5 | 'type-enum': [2, 'always', ['feat', 'fix', 'update', 'docs', 'style', 'refactor', 'test', 'revert', 'bug', 'build', 'ci', 'chore', 'perf']] 6 | }, 7 | prompt: { 8 | useEmoji: true, 9 | types: [ 10 | { 11 | value: 'feat', 12 | name: 'feat: A new feature', 13 | emoji: '✨' 14 | }, 15 | { 16 | value: 'update', 17 | name: 'update: Config or Function Update', 18 | emoji: '🙌' 19 | }, 20 | { 21 | value: 'fix', 22 | name: 'fix: A bug fix', 23 | emoji: '🐛' 24 | }, 25 | { 26 | value: 'docs', 27 | name: 'docs: Documentation only changes', 28 | emoji: '📚' 29 | }, 30 | { 31 | value: 'style', 32 | name: 'style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)', 33 | emoji: '💎' 34 | }, 35 | { 36 | value: 'refactor', 37 | name: 'refactor: A code change that neither fixes a bug nor adds a feature', 38 | emoji: '📦' 39 | }, 40 | { 41 | value: 'perf', 42 | name: 'perf: A code change that improves performance', 43 | emoji: '🚀' 44 | }, 45 | { 46 | value: 'test', 47 | name: 'test: Adding missing tests or correcting existing tests', 48 | emoji: '🚨' 49 | }, 50 | { 51 | value: 'build', 52 | name: 'build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)', 53 | emoji: '🛠' 54 | }, 55 | { 56 | value: 'ci', 57 | name: 'ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)', 58 | emoji: '⚙️' 59 | }, 60 | { 61 | value: 'chore', 62 | name: 'chore: Other changes that don\'t modify src or test files', 63 | emoji: '♻️' 64 | }, 65 | { 66 | value: 'revert', 67 | name: 'revert: Reverts a previous commit', 68 | emoji: '🗑' 69 | } 70 | ] 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // eslint.config.mjs 2 | import antfu from '@antfu/eslint-config' 3 | 4 | export default antfu( 5 | { 6 | typescript: true, 7 | vue: true, 8 | md: true, 9 | stylistic: { 10 | indent: 2, 11 | quotes: 'single', 12 | semi: false 13 | }, 14 | lessOpinionated: true 15 | }, 16 | { 17 | rules: { 18 | 'vue/block-order': 0, 19 | 'no-console': 0, 20 | 'node/prefer-global/process': 0, 21 | 'style/comma-dangle': [2, 'never'], 22 | 'function-paren-newline': ['error', 'multiline'], 23 | 'object-property-newline': ['error', { allowAllPropertiesOnSameLine: false }], 24 | 'object-curly-spacing': ['error', 'always'], 25 | 'object-curly-newline': ['error', { 26 | multiline: true, 27 | consistent: true 28 | }], 29 | 'style/space-before-function-paren': [2, 'always'], 30 | 'style/space-before-blocks': [2, 'always'] 31 | } 32 | } 33 | ) 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-custom-fetch", 3 | "type": "module", 4 | "version": "2.3.1", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/xjccc/nuxt-custom-fetch.git" 9 | }, 10 | "exports": { 11 | ".": { 12 | "import": "./dist/module.mjs" 13 | } 14 | }, 15 | "main": "./dist/module.mjs", 16 | "types": "./dist/types.d.mts", 17 | "files": [ 18 | "dist" 19 | ], 20 | "scripts": { 21 | "prepack": "nuxt-module-build build", 22 | "dev": "nuxi dev playground", 23 | "dev:build": "nuxi build playground", 24 | "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground", 25 | "echo:husky": "echo 'npx lint-staged' > .husky/pre-commit && echo 'npx --no-install commitlint --edit \"$1\"' > .husky/commit-msg", 26 | "lint": "eslint .", 27 | "lint:fix": "eslint . --fix", 28 | "prepare": "husky", 29 | "commit": "git-cz", 30 | "release": "changelogen --release && pnpm prepack && npm publish && git push --follow-tags" 31 | }, 32 | "config": { 33 | "commitizen": { 34 | "path": "node_modules/cz-git" 35 | } 36 | }, 37 | "dependencies": { 38 | "@nuxt/kit": "^3.16.2", 39 | "ohash": "^2.0.11" 40 | }, 41 | "devDependencies": { 42 | "@antfu/eslint-config": "^4.12.0", 43 | "@commitlint/cli": "^19.8.0", 44 | "@commitlint/config-conventional": "^19.8.0", 45 | "@nuxt/module-builder": "^1.0.1", 46 | "@nuxt/schema": "^3.16.2", 47 | "@types/node": "^22.14.1", 48 | "changelogen": "^0.6.1", 49 | "commitizen": "^4.3.1", 50 | "cz-git": "^1.11.1", 51 | "eslint": "^9.24.0", 52 | "husky": "^9.1.7", 53 | "lint-staged": "^15.5.1", 54 | "nitro": "npm:nitropack", 55 | "nuxt": "^3.16.2", 56 | "typescript": "5.8.3" 57 | }, 58 | "husky": { 59 | "hooks": { 60 | "pre-commit": "lint-staged" 61 | } 62 | }, 63 | "lint-staged": { 64 | "*": "eslint . --fix" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /playground/api/ajax.ts: -------------------------------------------------------------------------------- 1 | import type { FetchOptions } from 'ofetch' 2 | import { CustomFetch } from '#imports' 3 | 4 | const ajax = new CustomFetch({ 5 | baseURL: '', 6 | handler (params = {}) { 7 | const obj = JSON.stringify(params) 8 | return { a: obj } 9 | } 10 | }) 11 | 12 | interface ArticleList { 13 | type: number 14 | id: number 15 | title: string 16 | imgsrc: string[] 17 | publishdatetime: string 18 | publishdate: string 19 | commentcount: number 20 | clickcount: number 21 | murl: string 22 | categoryid2name: string 23 | } 24 | 25 | export interface ArticleInfo { 26 | data: ArticleList[] 27 | totalpage: number 28 | totalrecord: number 29 | status: number 30 | msg: string 31 | } 32 | 33 | // 获取经销商列表 34 | export function getArticleListData (params: FetchOptions['params']) { 35 | return ajax.get('/Article/GetSubCategorySeriesInfo', { 36 | key: '', 37 | query: params, 38 | baseURL: '/proxy/cms' 39 | }, { pick: ['totalpage'] }) 40 | } 41 | -------------------------------------------------------------------------------- /playground/api/index.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from '#imports' 2 | import { CustomFetch } from '#imports' 3 | 4 | const ajax = new CustomFetch({ baseURL: '/api' }) 5 | 6 | export function getListReactive (page: Ref) { 7 | return ajax.get<{ 8 | data: number[] 9 | nums: number 10 | }>('/get-list', { params: { page } }, { watch: [() => page.value] }) 11 | } 12 | 13 | export function getList (page: number) { 14 | return ajax.get<{ 15 | data: number[] 16 | nums: number 17 | }>('/get-list', { params: { page } }) 18 | } 19 | 20 | export function getNum (page: number) { 21 | return ajax.get<{ 22 | data: number[] 23 | nums: number 24 | }>('/get-num', { params: { page } }) 25 | } 26 | -------------------------------------------------------------------------------- /playground/app.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /playground/error.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 15 | -------------------------------------------------------------------------------- /playground/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineNuxtConfig } from 'nuxt/config' 2 | import MyModule from '../src/module' 3 | 4 | export default defineNuxtConfig({ 5 | modules: [ 6 | MyModule 7 | ], 8 | 9 | // ssr: false, 10 | vite: { server: { fs: { allow: ['../../../node_modules/'] } } }, 11 | 12 | nitro: { 13 | devProxy: { 14 | '/proxy/cms': { 15 | target: 'https://cms-api-test.360che.com', 16 | changeOrigin: true 17 | } 18 | } 19 | }, 20 | 21 | compatibilityDate: '2024-12-03' 22 | }) 23 | -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-module-playground", 3 | "private": true 4 | } 5 | -------------------------------------------------------------------------------- /playground/pages/index.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 48 | 49 | 51 | -------------------------------------------------------------------------------- /playground/pages/test-duplicate.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 69 | -------------------------------------------------------------------------------- /playground/pages/test-handler.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 61 | -------------------------------------------------------------------------------- /playground/pages/test-reactive.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 62 | -------------------------------------------------------------------------------- /playground/server/api/get-ip.ts: -------------------------------------------------------------------------------- 1 | import { defineEventHandler } from 'h3' 2 | 3 | export default defineEventHandler((event) => { 4 | const { req } = event 5 | // console.log(req,'请求') 6 | const ip = req.socket.remoteAddress || req.connection.remoteAddress || req.headers['x-forwarded-for'] 7 | return { ip } 8 | }) 9 | -------------------------------------------------------------------------------- /playground/server/api/get-list.ts: -------------------------------------------------------------------------------- 1 | import { defineEventHandler, getQuery } from 'h3' 2 | 3 | export default defineEventHandler(async (event) => { 4 | const query = getQuery(event) 5 | const page = query.page as number 6 | // if (+page === 3) { 7 | // console.log(page, 1111111) 8 | 9 | // throw new Error('error la') 10 | // } 11 | // await new Promise((resolve, reject) => { 12 | // setTimeout(() => { 13 | // resolve(1) 14 | // }, 2000) 15 | // }) 16 | 17 | return { 18 | nums: page, 19 | data: getNumbersForPage(page) 20 | } 21 | }) 22 | 23 | function getNumbersForPage (page: number) { 24 | const itemsPerPage = 10 25 | const start = (page - 1) * itemsPerPage + 1 26 | const numbers = [] 27 | 28 | for (let i = start; i < start + itemsPerPage; i++) { 29 | numbers.push(i) 30 | } 31 | 32 | return numbers 33 | } 34 | -------------------------------------------------------------------------------- /playground/server/api/get-num.ts: -------------------------------------------------------------------------------- 1 | import { defineEventHandler, getQuery } from 'h3' 2 | 3 | export default defineEventHandler(async (event) => { 4 | const query = getQuery(event) 5 | const page = query.page as number 6 | 7 | await new Promise((resolve) => { 8 | setTimeout(() => { 9 | resolve(1) 10 | }, 2000) 11 | }) 12 | 13 | return { nums: page } 14 | }) 15 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["github>unjs/renovate-config"] 3 | } 4 | -------------------------------------------------------------------------------- /src/module.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'node:url' 2 | import { addImports, createResolver, defineNuxtModule } from '@nuxt/kit' 3 | 4 | export interface ModuleOptions {} 5 | 6 | export default defineNuxtModule({ 7 | meta: { 8 | name: 'nuxt-custom-fetch', 9 | configKey: 'customFetch' 10 | }, 11 | defaults: {}, 12 | setup (options, nuxt) { 13 | const { resolve } = createResolver(import.meta.url) 14 | const runtimeDir = fileURLToPath(new URL('./runtime', import.meta.url)) 15 | nuxt.options.build.transpile.push(runtimeDir) 16 | addImports({ 17 | from: resolve(runtimeDir, 'ajax'), 18 | name: 'CustomFetch' 19 | }) 20 | } 21 | }) 22 | -------------------------------------------------------------------------------- /src/runtime/ajax.ts: -------------------------------------------------------------------------------- 1 | import type { MaybeRef, Ref } from '#imports' 2 | import type { NitroFetchRequest } from 'nitro' 3 | import type { AsyncData, AsyncDataOptions, AsyncDataRequestStatus, NuxtError } from 'nuxt/app' 4 | import type { FetchContext, FetchMethod, FetchResponse, HTTPConfig, Interceptors, KeysOf, PickFrom } from './type' 5 | import { createError, getCurrentScope, onScopeDispose, reactive, ref, toValue, unref, useAsyncData, useNuxtApp, useRequestFetch, watch } from '#imports' 6 | import { hash, serialize } from 'ohash' 7 | 8 | type CustomFetchReturnValue = AsyncData> | null, (ErrorT extends Error | NuxtError ? ErrorT : NuxtError) | null> 9 | 10 | function Noop () { } 11 | function generateOptionSegments (opts: HTTPConfig & { method: FetchMethod }) { 12 | const segments: Array> = [toValue(opts.method as MaybeRef | undefined)?.toUpperCase() || 'GET', toValue(opts.baseURL)] 13 | for (const _obj of [opts.params || opts.query]) { 14 | const obj = toValue(_obj) 15 | if (!obj) { 16 | continue 17 | } 18 | 19 | const unwrapped: Record = {} 20 | for (const [key, value] of Object.entries(obj)) { 21 | unwrapped[toValue(key)] = toValue(value) 22 | } 23 | 24 | segments.push(unwrapped) 25 | } 26 | return segments 27 | } 28 | function pick (obj: Record, keys: string[]) { 29 | const newObj: any = {} 30 | for (const key of keys) { 31 | newObj[key] = obj[key] 32 | } 33 | 34 | return newObj 35 | } 36 | 37 | const _cachedController = new Map() 38 | 39 | export class CustomFetch { 40 | baseURL 41 | immutableKey = false 42 | params: HTTPConfig = {} 43 | baseHandler: HTTPConfig['handler'] 44 | interceptors: Interceptors = {} 45 | offline = Noop 46 | showLogs = false 47 | 48 | constructor (config: HTTPConfig = { 49 | baseURL: '' 50 | }) { 51 | this.params = { ...config } 52 | this.baseURL = config.baseURL 53 | this.immutableKey = config.immutableKey ?? false 54 | 55 | this.showLogs = config.showLogs ?? import.meta.dev 56 | 57 | if (config.handler) { 58 | this.baseHandler = config.handler 59 | } 60 | 61 | this.offline = config?.offline || Noop 62 | 63 | this.interceptors = { 64 | onRequest: config.onRequest || Noop, 65 | onRequestError: config.onRequestError || Noop, 66 | onResponse: config.onResponse || Noop, 67 | onResponseError: config.onResponseError || Noop 68 | } 69 | } 70 | 71 | private baseConfig (config: HTTPConfig): HTTPConfig { 72 | const { useHandler = true, handler, query = {}, params = {} } = config 73 | const baseHandler = handler || this.baseHandler 74 | const _name = Object.keys(query).length ? 'query' : 'params' 75 | const mergeObj = { 76 | ...query, 77 | ...params 78 | } 79 | if (useHandler && baseHandler && typeof baseHandler === 'function') { 80 | return { [_name]: baseHandler(mergeObj) } 81 | } 82 | 83 | return { [_name]: { ...mergeObj } } 84 | } 85 | 86 | http(url: NitroFetchRequest, config: HTTPConfig & { method: FetchMethod }, options: AsyncDataOptions = {}): CustomFetchReturnValue { 87 | config.baseURL = config?.baseURL || this.baseURL 88 | Object.assign(config, this.baseConfig(config)) 89 | const generateOptionSegmentsWithConfig = generateOptionSegments(config) 90 | if (this.showLogs && import.meta.client) { 91 | let bodyLogs 92 | try { 93 | bodyLogs = serialize(config.body) 94 | } 95 | catch (error) { 96 | console.warn('[Custom Fetch] couldn\'t serialize \`Body:\`', error) 97 | } 98 | console.warn('[Custom Fetch] \`Request:\`', url, ' \`Query:\`', serialize(generateOptionSegmentsWithConfig), ' \`Body:\`', bodyLogs) 99 | } 100 | const { onRequest, onRequestError, onResponse, onResponseError, offline, handler, useHandler, showLogs, immutableKey, ...restAjaxConfig } = config 101 | 102 | if (import.meta.client && navigator && !navigator.onLine) { 103 | if (offline) { 104 | offline() 105 | } 106 | } 107 | 108 | const interceptors = this.interceptors 109 | const _config = reactive({ ...restAjaxConfig }) 110 | 111 | const defaultOptions = { 112 | onRequest (ctx: FetchContext) { 113 | [interceptors.onRequest, onRequest].forEach((fns) => { 114 | if (Array.isArray(fns)) { 115 | fns.forEach(fn => fn?.(ctx)) 116 | } 117 | else { 118 | fns?.(ctx) 119 | } 120 | }) 121 | }, 122 | onRequestError (ctx: FetchContext & { error: Error }) { 123 | [interceptors.onRequestError, onRequestError].forEach((fns) => { 124 | if (Array.isArray(fns)) { 125 | fns.forEach(fn => fn?.(ctx)) 126 | } 127 | else { 128 | fns?.(ctx) 129 | } 130 | }) 131 | 132 | throw createError({ 133 | statusCode: 400, 134 | statusMessage: ctx.error.message, 135 | message: ctx.error.message, 136 | fatal: true 137 | }) 138 | }, 139 | onResponse (ctx: FetchContext & { response: FetchResponse }) { 140 | [interceptors.onResponse, onResponse].forEach((fns) => { 141 | if (Array.isArray(fns)) { 142 | fns.forEach(fn => fn?.(ctx)) 143 | } 144 | else { 145 | fns?.(ctx) 146 | } 147 | }) 148 | }, 149 | onResponseError (ctx: FetchContext & { response: FetchResponse }) { 150 | [interceptors.onResponseError, onResponseError].forEach((fns) => { 151 | if (Array.isArray(fns)) { 152 | fns.forEach(fn => fn?.(ctx)) 153 | } 154 | else { 155 | fns?.(ctx) 156 | } 157 | }) 158 | throw createError({ 159 | statusCode: 500, 160 | statusMessage: ctx.request.toString(), 161 | message: ctx.response._data, 162 | fatal: true 163 | }) 164 | } 165 | } 166 | 167 | const hashValue: Array> = ['custom_fetch:', url as string] 168 | if (!this.immutableKey && !immutableKey) { 169 | hashValue.push(...generateOptionSegmentsWithConfig) 170 | } 171 | 172 | const key = hash(hashValue).replace(/[-_]/g, '').slice(0, 10) 173 | 174 | options.default = options.default ?? (() => null) 175 | 176 | const _handler = () => { 177 | if (_cachedController.get(key)) { 178 | _cachedController.get(key)?.abort?.() 179 | } 180 | 181 | return useRequestFetch()(url as string, { 182 | signal: _cachedController.get(key)?.signal, 183 | ...defaultOptions, 184 | ..._config 185 | }) as unknown as Promise 186 | } 187 | 188 | const controller = typeof AbortController !== 'undefined' ? new AbortController() : ({} as AbortController) 189 | const nuxtApp = useNuxtApp() 190 | 191 | // const instance = getCurrentInstance() 192 | // if (import.meta.client && !nuxtApp.isHydrating && (!instance || instance?.isMounted)) { 193 | if (import.meta.client && !nuxtApp.isHydrating) { 194 | const asyncData: { 195 | data: Ref 196 | /** 197 | * @deprecated This may be removed in a future major version. 198 | */ 199 | pending: Ref 200 | error: Ref<(ErrorT extends Error | NuxtError ? ErrorT : NuxtError) | null> 201 | status: Ref 202 | refresh?: () => Promise 203 | execute?: () => Promise 204 | } = { 205 | data: ref(null), 206 | pending: ref(true), 207 | error: ref(null), 208 | status: ref('idle') 209 | } 210 | 211 | asyncData.pending.value = true 212 | asyncData.status.value = 'pending' 213 | const promise = new Promise((resolve, reject) => { 214 | try { 215 | resolve(_handler()) 216 | _cachedController.set(key, controller) 217 | } 218 | catch (err) { 219 | reject(err) 220 | } 221 | }) 222 | .then(async (_result) => { 223 | let result = _result as unknown as DataT 224 | if (options?.transform) { 225 | result = await options.transform(_result) 226 | } 227 | 228 | if (options?.pick) { 229 | result = pick(result as any, options.pick) as DataT 230 | } 231 | 232 | asyncData.data.value = result 233 | asyncData.error.value = null 234 | asyncData.status.value = 'success' 235 | return asyncData 236 | }) 237 | .catch((error: any) => { 238 | asyncData.error.value = createError(error) as any 239 | asyncData.data.value = unref(options?.default!()) 240 | asyncData.status.value = 'error' 241 | return asyncData 242 | }) 243 | .finally(() => { 244 | asyncData.pending.value = false 245 | _cachedController.delete(key) 246 | }) 247 | 248 | asyncData.refresh = asyncData.execute = () => _handler().then(result => asyncData.data.value = result) 249 | const hasScope = getCurrentScope() 250 | if (options.watch) { 251 | const unsub = watch(options.watch, async () => { 252 | asyncData.refresh!() 253 | }) 254 | if (hasScope) { 255 | onScopeDispose(unsub) 256 | } 257 | } 258 | 259 | return promise as any 260 | } 261 | 262 | return useAsyncData(config.key || key, _handler, options) 263 | } 264 | 265 | get(url: NitroFetchRequest, config: HTTPConfig = {}, options?: AsyncDataOptions) { 266 | return this.http(url, { 267 | ...config, 268 | method: 'GET' 269 | }, options) 270 | } 271 | 272 | post(url: NitroFetchRequest, config: HTTPConfig = {}, options?: AsyncDataOptions) { 273 | return this.http(url, { 274 | ...config, 275 | method: 'POST' 276 | }, options) 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /src/runtime/type.ts: -------------------------------------------------------------------------------- 1 | import type { FetchOptions } from 'ofetch' 2 | 3 | export type { FetchContext, FetchResponse } from 'ofetch' 4 | 5 | export type PickFrom> = T extends Array ? T : T extends Record ? (keyof T extends K[number] ? T : K[number] extends never ? T : Pick) : T 6 | export type KeysOf = Array 7 | 8 | export type FetchMethod = 9 | | 'options' 10 | | 'GET' 11 | | 'POST' 12 | | 'get' 13 | | 'HEAD' 14 | | 'PATCH' 15 | | 'PUT' 16 | | 'DELETE' 17 | | 'CONNECT' 18 | | 'OPTIONS' 19 | | 'TRACE' 20 | | 'post' 21 | | 'head' 22 | | 'patch' 23 | | 'put' 24 | | 'delete' 25 | | 'connect' 26 | | 'trace' 27 | | undefined 28 | 29 | export interface HTTPConfig extends Omit { 30 | key?: string 31 | immutableKey?: boolean 32 | showLogs?: boolean 33 | interceptors?: Interceptors 34 | baseURL?: string 35 | useHandler?: boolean 36 | handler?: (params: Record) => Record 37 | offline?: () => void 38 | } 39 | 40 | export interface Interceptors { 41 | onRequest?: FetchOptions['onRequest'] 42 | onRequestError?: FetchOptions['onRequestError'] 43 | onResponse?: FetchOptions['onResponse'] 44 | onResponseError?: FetchOptions['onResponseError'] 45 | } 46 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.nuxt/tsconfig.json", 3 | "exclude": [ 4 | "dist", 5 | "node_modules", 6 | "playground" 7 | ] 8 | } 9 | --------------------------------------------------------------------------------