vue-axios-optimize 2.1.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +19 -317
  2. package/index.js +1 -1
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,12 +1,17 @@
1
1
  # vue-axios-optimize使用说明
2
2
 
3
3
  vue-axios-optimize是一个对axios请求优化的vue插件包,引入此包之后,并进行相关配置,即可轻松实现全局请求加载动画,重复请求时取消前面的请求或者阻止后面的请求,并且可实现请求并发数量控制以及接口缓存。
4
- [可阅读此文章 了解](https://mp.weixin.qq.com/s?__biz=MzUyMjkxNDc3Mg==&mid=2247484964&idx=1&sn=3cbd0fdcd5f8877acd1179df96fa5776&chksm=f9c5d89fceb2518917cb2e313d2b302a335f70ebf4999d570484b697264f1353d66730fff961&token=319626610&lang=zh_CN#rd)
4
+ [可阅读此文章 了解](https://mp.weixin.qq.com/s/fhsYQ96-ui7JS4oG_C3btg)
5
5
 
6
6
  ## 安装
7
7
 
8
8
  ```shell
9
- npm install vue-axios-optimize -S
9
+ # npm
10
+ npm install vue-axios-optimize --save
11
+ # yarn
12
+ yarn add vue-axios-optimize --save
13
+ # pnpm
14
+ pnpm add vue-axios-optimize --save
10
15
  ```
11
16
 
12
17
  ## axios版本
@@ -33,205 +38,6 @@ api目录大致如下
33
38
  > > >
34
39
  > > > axios-optimize.js
35
40
 
36
- axios-optimize.js文件内容大致如下
37
-
38
- ```js
39
- import axios from "axios"
40
- import instance from "@/api/axios"
41
- import axiosOptimize from "vue-axios-optimize"
42
- import store from "@/store"
43
- import {MessageBox} from "element-ui"
44
-
45
- const AxiosOptimize = new axiosOptimize(axios, instance, {
46
- maxReqNum: 2, // 同时最多请求数量 选配 默认 4
47
- cacheNum: 2, // 缓存数量 选配 默认10
48
- responseTypesStr: "arraybuffer,blob", // 当axios请求设置 responseType为arraybuffer或blob时,直接返回原始数据 选配
49
- showLoadingFun: (config, requestingNum) => { // 当需要加载动画的请求数量不为0时的回调函数
50
- store.commit("app/SET_LOADING_STATUS", true)
51
- },
52
- hideLoadingFun: (config, requestingNum) => { // 当需要加载动画的请求数量为0时的回调函数
53
- store.commit("app/SET_LOADING_STATUS", false)
54
- },
55
- responseResultFun: (res) => { // 响应数据格式化
56
- return res.data
57
- },
58
- openRefresh: true, // 是否开启无感知刷新token 以下配置均与无感知刷新token有关 如无需配置无感知刷新token 可忽略
59
- code: "code", // 判断响应码的字段标识 默认code
60
- accessToken: "accessToken", // headers存放accessToken的字段名 默认 accessToken 用于判断当前接口是否使用的最新的token
61
- accessTokenExpirationCode: 401, // accessToken过期时 接口响应状态码 默认 数字类型 401
62
- refreshTokenExpirationCode: 403, // 刷新token接口请求报错时的 响应状态码 默认 数字类型 403
63
- setAccessTokenFun: (config, accessToken) => { // 设置接口请求token的 并返回新的config 默认为 config.headers.Authorization = "Bearer " + accessToken
64
- config.headers.Authorization = "Bearer " + accessToken
65
- return config
66
- },
67
- getRefreshTokenFun: () => { // 获取refreshToken的方法
68
- return store.getters.refreshToken
69
- },
70
- getAccessTokenFun: () => { // 获取accessToken的方法
71
- return store.getters.accessToken
72
- },
73
- refreshTokenStore: (refreshToken) => { // 调用刷新token的方法 并将获取到的数据存储到cookie 且需要将接口返回的数据 resolve(data)
74
- return store.dispatch("user/refreshToken", refreshToken)
75
- },
76
- reloginFun: async(response) => { // 刷新token接口也报错后的处理逻辑 建议进行类似如下方法处理
77
- // 重新登录方法 弹框提示 点击确认后 建议清除 缓存中token等信息后 刷新页面
78
- MessageBox.alert(`${response.message}`, "提示", {
79
- confirmButtonText: "OK",
80
- showClose: false,
81
- callback: async(action) => {
82
- if (action === "confirm") {
83
- // 清除缓存中的token等信息
84
- store.commit("user/CLEAR_AUTH")
85
- // 刷新浏览器是为了 跳转登录页时query的redirect 会带上 当前页面地址(路由拦截处的逻辑)
86
- window.location.reload()
87
- }
88
- }
89
- })
90
- }
91
- })
92
-
93
- export default AxiosOptimize
94
- ```
95
-
96
- axios.js的内容需要做些细微的更改 大致内容如下
97
-
98
- ```js
99
-
100
- import axios from "axios"
101
- import Message from "@/plugins/reset-message.js"
102
- import store from "@/store"
103
-
104
- // 创建axios实例
105
- const service = axios.create({
106
- baseURL: XXX,
107
- timeout: XXX // 请求超时时间
108
- })
109
-
110
- // request拦截器
111
- service.interceptors.request.use(
112
- config => {
113
- const accessToken = store.getters.accessToken
114
- if (accessToken) {
115
- // 配置无感知刷新token时 建议请求头多加个 accessToken, 如为其他字段可 配置 为其他字段 用于判断当前接口携带的accessToken
116
- config.headers.accessToken = accessToken
117
- // 用于做接口权限标识
118
- config.headers.Authorization = "Bearer " + accessToken
119
- }
120
-
121
- return config
122
- },
123
- error => {
124
- // 需要return
125
- return Promise.reject(error)
126
- }
127
- )
128
-
129
- // respone拦截器
130
- service.interceptors.response.use(
131
- async response => {
132
-
133
- const {data, config, status, request} = response
134
- // 如果自定义代码不是200,则判断为错误。
135
- if (status !== 200) {
136
- Message({
137
- message: data.message || "Error",
138
- type: "error",
139
- duration: 5 * 1000
140
- })
141
- // 错误响应时,多返回一个config 和 responseErr设置为true 让vue-axios-optimize 拿到config 以及知道 这是一个错误的响应数据
142
- return { ...data, config, responseErr: true}
143
- } else {
144
- // 二进制数据则直接返回
145
- if (request.responseType === "blob" || request.responseType === "arraybuffer"){
146
- // 直接返回数据
147
- return response
148
- }
149
- if (data.code === 200) {
150
- // 正常响应时,多返回一个config 让vue-axios-optimize 拿到config
151
- return { ...data, config}
152
- }
153
- if (data.code === 401 || data.code === 403) {
154
- // 错误响应时,多返回一个config 和 responseErr设置为true 让vue-axios-optimize 拿到config 以及知道 这是一个错误的响应数据
155
- return { ...data, config, responseErr: true}
156
- }
157
- Message({
158
- message: data.message || "Error",
159
- type: "error",
160
- duration: 5 * 1000
161
- })
162
- // 错误响应时,多返回一个config 和 responseErr设置为true 让vue-axios-optimize 拿到config 以及知道 这是一个错误的响应数据
163
- return { ...data, config, responseErr: true}
164
- }
165
- },
166
- error => {
167
- // 取消请求时 不报错误信息
168
- if (error.name !== "CanceledError") {
169
- Message.error(error.message)
170
- }
171
- // 需要return
172
- return Promise.reject(error)
173
- }
174
- )
175
-
176
- export default service
177
- ```
178
-
179
-
180
-
181
- 接口处使用,如commo-api.js 大致如下
182
-
183
- ```js
184
- import axiosRequest from "@/api/axios-optimize"
185
- // 需要配置 请求接口时 不加载全局动画 则 配置 noShowLoading: true
186
- // 需要配置 缓存的接口 则 配置 cache: true 否则 配置为 false 或 不配置
187
- // 需要配置 重复请求时 取消前面的请求 则 配置 preventDuplicateRequestsType: "cancel"
188
- // 需要配置 重复请求时 禁用后面的请求 则 配置 preventDuplicateRequestsType: "prevent"
189
- // 需要配置 返回数据是否添加 IS_COMPLETE 是否完成字段 则 配置 showIsComplete: true
190
- // 需要配置 当同时请求很多接口,且还有很多接口在队列时,有接口报错,是否终止所有队列中的接口请求 removeRemainingTasksWhenError: true
191
- // 需要配置 全路经(包括入参数据)为接口唯一标识的 则配置 fullPath: true 否则 配置为 false 或 不配置 否则仅仅以URL为唯一标识
192
- // 当需要配置无感知刷新token时,对于刷新token接口需要配置 isRefreshToken 为true 否则配置为 false 或者不 配置
193
-
194
- // get 请求demo
195
- export function getDemo(
196
- data = {},
197
- config = {
198
- noShowLoading: true, // 配置不展示加载动画
199
- preventDuplicateRequestsType: "cancel" // 配置重复请求时 取消前面的请求
200
- }
201
- ) {
202
- return axiosRequest.get(`/xiaobu-admin/getDemo`, { params: data, ...config })
203
- }
204
-
205
- // post 请求demo
206
- export function postDemo(data = {}, config = {}) {
207
- return axiosRequest.post(`/xiaobu-admin/postDemo`, data, config)
208
- }
209
-
210
- // delete 请求demo1 使用data作为参数 参数不展示在 请求路径上
211
- export function deleteDemo1(data = {}, config = {}) {
212
- return axiosRequest.delete(`/xiaobu-admin/deleteDemo1`, { data, ...config })
213
- }
214
-
215
- // delete 请求demo2 使用params作为参数 参数展示在 请求路径上
216
- export function deleteDemo2(data = {}, config = {}) {
217
- return axiosRequest.delete(`/xiaobu-admin/deleteDemo2`, {
218
- params: data,
219
- ...config
220
- })
221
- }
222
-
223
- // put 请求demo
224
- export function putDemo(data = {}, config = {}) {
225
- return axiosRequest.put(`/xiaobu-admin/putDemo`, data, config)
226
- }
227
-
228
- // patch 请求demo
229
- export function patchDemo(data = {}, config = {}) {
230
- return axiosRequest.patch(`/xiaobu-admin/patchDemo`, data, config)
231
- }
232
-
233
- ```
234
-
235
41
  ## new axiosOptimize(axios, instance, options)参数说明
236
42
 
237
43
  | 参数 | 说明 | 来源 |
@@ -240,118 +46,6 @@ export function patchDemo(data = {}, config = {}) {
240
46
  | instance | 创建好的axios实例,已经配置好请求头等相关内容 | import instance from '@/api/axios.js' |
241
47
  | options | 一些配置,详情继续阅读文档 | 自定义配置 |
242
48
 
243
- ## options配置
244
-
245
- | 参数 | 类型 | 说明 | 是否必传 | 默认值 |
246
- | ------------------------------------------------------------ | -------- | ------------------------------------------------------------ | ------------------------- | ------------------------------------------------------------ |
247
- | maxReqNum | Number | 同时最大可请求数量,高并发任务时,可同时进行的任务处理数量 | 否 | 4 |
248
- | cacheNum | Number | 可缓存的键值对个数 | 否 | 10 |
249
- | showLoadingFun(config, requestingNum) | Function | 展示动画,config为该请求的config,requestingNum为目前正在请求几个接口,此处可执行一些展示全局动画的操作 | 否 | 无 |
250
- | hideLoadingFun(config, requestingNum) | Function | 隐藏动画,config为该请求的config,requestingNum为目前正在请求几个接口,此时应该为0,此处可执行关闭全局动画的操作 | 否 | 无 |
251
- | openRefresh | Boolean | 是否开启无感知刷新token | 否 | false |
252
- | refreshApiUrl | String | 请求刷新token接口的api地址 如/xiaobu-admin/refresh-token | 当openRefresh为true时必传 | 无 |
253
- | getRefreshTokenFun() | Function | 获取当前项目的 refreshToken方法, 记得 return 回去 | 当openRefresh为true时必传 | 无 |
254
- | getAccessTokenFun() | Function | 获取当前项目的 accessToken方法, 记得 return 回去 | 当openRefresh为true时必传 | 无 |
255
- | reloginFun(response) | Function | response 为axios实例中返回的数据,此处建议执行清除token相关缓存并弹框提示,点击确认进行刷新页面操作 | 当openRefresh为true时必传 | 无 |
256
- | refreshTokenStore(refreshToken) | Function | refreshToken为当前浏览器缓存的refreshToken,需要返回一个Promise,利用此refreshToken调用接口获取新的accessToken 并设置。v1.0.6及以上无需返回任何数据 | 当openRefresh为true时必传 | 无 |
257
- | setAccessTokenFun(config, accessToken) (v1.0.8已废除, V2.0.2 弄回来 并添加默认值) | Function | config为该请求的config,accessToken 为新获取的accessToken, 此处需要将accessToken设置到请求头中 | 当openRefresh为true时必传 | setAccessTokenFun: (config, accessToken) => { // 设置接口请求token的 并返回新的config 默认为 config.headers.Authorization = "Bearer " + accessToken config.headers.Authorization = "Bearer " + accessToken return config }, |
258
- | responseResultFun(res) | Function | res为axios实例返回的res,格式化接口请求成功后返回的数据 | 非必传 | axios实例返回的res |
259
- | formatAccessTokenFun(data) v1.0.6已废除 | Function | data为responseResultFun报错时返回的res | 非必传 | axios实例返回的res.data.accessToken |
260
- | accessTokenExpirationCode | number | 配置accessToken过期返回的业务状态码 | 非必传 | 401 |
261
- | refreshTokenExpirationCode | number | 配置refreshToken过期返回的业务状态码 | 非必传 | 403 |
262
- | responseTypesStr(v1.0.2) | String | 配置数据返回类型,实现当此类型时返回原封不动的响应数据的data数据 | 非必传 | 无 |
263
- | timeout(V2.1.0) | Number | 配置缓存数据时,伪加载动画的请求时间。获取缓存数据时会执行showLoadingFun,timeout毫秒后会执行hideLoadingFun。 | 非必传 | 300 |
264
-
265
- ## store/modules/user.js代码大致如下
266
-
267
- ```ts
268
- import { setCookie, getCookie, removeCookie, accessTokenKey, refreshTokenKey } from "@/utils/cookie"
269
- import { commonApi } from "@/api"
270
-
271
- const getDefaultState = () => {
272
- return {
273
- accessToken: getCookie(accessTokenKey) || "", // 认证token
274
- refreshToken: getCookie(refreshTokenKey) || "", // 刷新token
275
- }
276
- }
277
- const state = getDefaultState()
278
-
279
- const mutations = {
280
- // 重置初始化值
281
- RESET_STATE: (state) => {
282
- Object.assign(state, getDefaultState())
283
- },
284
- // 设置token
285
- SET_TOKEN: (state, data) => {
286
- const { accessToken, refreshToken, expiresIn} = data
287
- state.accessToken = accessToken
288
- setCookie(accessTokenKey, accessToken, expiresIn)
289
- state.refreshToken = refreshToken
290
- setCookie(refreshTokenKey, refreshToken, expiresIn)
291
- },
292
-
293
- // 清除token 及其他可能影响操作的缓存数据
294
- CLEAR_AUTH: (state) => {
295
- state.accessToken = ""
296
- removeCookie(accessTokenKey)
297
- state.refreshToken = ""
298
- removeCookie(refreshTokenKey)
299
- // ...
300
- }
301
- }
302
-
303
- const actions = {
304
- // 设置项目token
305
- setToken({commit}, data) {
306
- return new Promise((resolve, reject) => {
307
- commit("SET_TOKEN", data)
308
- resolve()
309
- })
310
- },
311
- // 刷新token
312
- refreshToken({commit}, refreshToken) {
313
- return new Promise((resolve, reject) => {
314
- commonApi.refreshToken({refreshToken}).then(data => {
315
- commit("SET_TOKEN", data)
316
- resolve()
317
- })
318
- })
319
- },
320
- }
321
-
322
- export default {
323
- namespaced: true,
324
- state,
325
- mutations,
326
- actions
327
- }
328
-
329
-
330
-
331
- ```
332
-
333
- ## 注意
334
-
335
- 如果要配置无感知刷新token时,对后端是有要求的,需要做到当accessToken过期refreshToken未过期时,请求接口的业务状态码 code为 配置的 accessTokenExpirationCode 的值, 默认401,当refreshToken过期或其他问题报错导致系统无法运行的错误时 业务状态码code 应为配置的 refreshTokenExpirationCode, 默认403
336
-
337
- 当部分页面接口请求在catch处有操作时 需添加语句, 参考如下示例
338
-
339
- ```js
340
- this.loading = true
341
- testApi.getUserList(this.addDateRange(this.queryParams, this.dateRange)).then(res => {
342
- const response = res.data
343
- this.list = response.rows
344
- this.total = response.total
345
- this.loading = false
346
- }).catch(err => {
347
- // 此处需要添加 如果是取消请求 或者是 阻止请求时 不进行操作
348
- if (err.name === "CanceledError") {
349
- return
350
- }
351
- this.loading = false
352
- })
353
- ```
354
-
355
49
  ## 特别注意
356
50
 
357
51
  vue2项目的vue.config.js 需要添加如下配置,对插件包进行编译。
@@ -370,13 +64,11 @@ module.exports = {
370
64
 
371
65
  如有疑问,可关注微信公众号【爆米花小布】进行咨询。
372
66
 
373
- ![微信公众号](https://qiniu.xiaobusoft.com/gongzhonghao_QR_code.png)
374
-
375
-
67
+ ![微信公众号](http://120.76.229.63/static/gongzhonghao_QR_code.png)
376
68
 
377
69
  ## 打赏
378
70
 
379
- ![打赏二维码](https://qiniu.xiaobusoft.com/qr-card.jpg)
71
+ ![打赏二维码](http://120.76.229.63/static/qr-card.png)
380
72
 
381
73
  微信公众号【爆米花小布】,抖音号【爆米花小布】 更多好玩的插件
382
74
  [vue2-element-dict字典包插件使用文档](https://www.xiaobusoft.com/vue2-element-dict/)
@@ -399,6 +91,16 @@ module.exports = {
399
91
 
400
92
  ## 更新日志
401
93
 
94
+ ### 3.0.0
95
+
96
+ 1. 【优化】无感知续签AccessToken时无需配置多余请求头
97
+ 2. 【修改】accessToken配置改为authorizationKey,且默认值由accessToken改为authorization
98
+ 3. 【修改】setAccessTokenFun配置改为setAuthorizationFun
99
+ 4. 【修改】getAccessTokenFun配置改为getAuthorizationFun
100
+ 5. 【修改】默认最大高并发数量由原来的4改为6
101
+ 6. 【优化】responseTypesStr新增默认配置 "arraybuffer,blob"
102
+ 7. 【优化】请求上的配置showIsComplete配置值即为多返回的字段名称,表示此接口已全部请求完毕
103
+
402
104
  ### 2.1.1
403
105
 
404
106
  1. 【fix】修复get请求和delete请求配置fullPath参数为 true时,会报错的bug
package/index.js CHANGED
@@ -1 +1 @@
1
- class e{instance;requestObj={};requestingNum=0;queue=[];isRefresh=!1;isShowReLoginDialog=!1;runningCount=0;maxReqNum=4;tasks=[];cache;cacheNum=10;status="start";runingReqKeys=[];showLoadingFun=()=>{};hideLoadingFun=()=>{};timeout=300;add(e,t,s){return new Promise(((i,n)=>{try{if("pause"===this.status||"prevent"===s?.preventDuplicateRequestsType){if(this.tasks.some((e=>e.requestKey===t))||this.runingReqKeys.includes(t))return}if("cancel"===s?.preventDuplicateRequestsType){if(this.tasks.some((e=>e.requestKey===t))&&(this.tasks=this.tasks.filter((e=>e.requestKey!==t))),this.runingReqKeys.includes(t))return this.maxReqNum++,this.runingReqKeys.push(t),this.tasks.unshift({isCancel:!0,task:e,requestKey:t,config:s,resolve:i,reject:n}),void this._run()}this.runingReqKeys.push(t),this.tasks.push({task:e,requestKey:t,config:s,resolve:i,reject:n}),this._run()}catch(e){n(e)}}))}_pause(){this.status="pause"}_start(){this.status="start",this._run()}_run(){if("pause"!==this.status)for(;this.runningCount<this.maxReqNum&&this.tasks.length>0;){const{isCancel:e,task:t,requestKey:s,config:i,resolve:n,reject:r}=this.tasks.shift();e&&this.maxReqNum--,this.runningCount++,t().then((e=>{this.runningCount--,"[object Object]"===Object.prototype.toString.call(e)&&i?.showIsComplete?n({...e,IS_COMPLETE:0===this.runningCount}):n(e)})).catch((e=>{if(this.runningCount--,(e?.name&&"CanceledError"!==e?.name||!e?.name)&&i?.removeRemainingTasksWhenError){const e=this.tasks.map((e=>e.requestKey));this.tasks=[],this.runingReqKeys=this.runingReqKeys.filter((t=>!e.includes(t)))}r(e)})).finally((()=>{this.runingReqKeys.splice(this.runingReqKeys.indexOf(s),1),this._run()}))}}getCache(e,t){const{url:s,data:i={},config:n,method:r}=t;return new Promise(((t,h)=>{if(!this.cache.has(e)){delete n.cache;return void(["get","delete"].includes(r)?this[r](s,n):this[r](s,i,n)).then((s=>{this.setCache(e,s),t(s)})).catch((e=>{h(e)}))}const a=this.cache.get(e);this.cache.delete(e),this.cache.set(e,a),this.showLoadingFun(),setTimeout((()=>{this.hideLoadingFun(),t(a)}),this.timeout)}))}setCache(e,t){if(this.cache.has(e)&&this.cache.delete(e),this.cache.set(e,t),this.cache.size>this.cacheNum){const e=this.cache.keys().next().value;this.cache.delete(e)}}removeCache(){this.cache&&this.cache.clear()}get(e,t){if(t.cache){const s={url:e,config:t,method:"get"},i=t.params,n=this.generateKey(s,i);return this.getCache(n,s)}const s=t.params;if(delete t.params,t.isRefreshToken)return this.instance.request({url:e,params:s,...t,method:"GET"});const i=t.fullPath?this.generateKey({url:e,method:"get"},s):"GET#"+e;return this.add((()=>this.instance.request({url:e,params:s,...t,method:"GET"})),i,t)}post(e,t={},s={}){if(s.cache){const i={url:e,data:t,config:s,method:"post"},n=this.generateKey(i,t);return this.getCache(n,i)}if(s.isRefreshToken)return this.instance.request({url:e,data:t,...s,method:"POST"});const i=s.fullPath?this.generateKey({url:e,method:"post"},t):"POST#"+e;return this.add((()=>this.instance.request({url:e,data:t,...s,method:"POST"})),i,s)}delete(e,t={}){if(t.cache){const s={url:e,config:t,method:"delete"},i=t.params||t.data||{},n=this.generateKey(s,i);return this.getCache(n,s)}if(t.params){const s=t?.params||{};if(delete t.params,t.isRefreshToken)return this.instance.request({url:e,params:s,...t,method:"DELETE"});const i=t.fullPath?this.generateKey({url:e,method:"delete"},s):"DELETE#"+e;return this.add((()=>this.instance.request({url:e,params:s,...t,method:"DELETE"},i,t)))}const s=t?.data||{};if(delete t.data,t.isRefreshToken)return this.instance.request({url:e,data:s,...t,method:"DELETE"});const i=t.fullPath?this.generateKey({url:e,method:"delete"},s):"DELETE#"+e;return this.add((()=>this.instance.request({url:e,data:s,...t,method:"DELETE"},i,t)))}patch(e,t={},s={}){if(s.cache){const i={url:e,data:t,config:s,method:"patch"},n=this.generateKey(i,t);return this.getCache(n,i)}if(s.isRefreshToken)return this.instance.request({url:e,data:t,...s,method:"PATCH"});const i=s.fullPath?this.generateKey({url:e,method:"patch"},t):"PATCH#"+e;return this.add((()=>this.instance.request({url:e,data:t,...s,method:"PATCH"},i,s)))}put(e,t={},s={}){if(s.cache){const i={url:e,data:t,config:s,method:"put"},n=this.generateKey(i,t);return this.getCache(n,i)}if(s.isRefreshToken)return this.instance.request({url:e,data:t,...s,method:"PUT"});const i=s.fullPath?this.generateKey({url:e,method:"put"},t):"PUT#"+e;return this.add((()=>this.instance.request({url:e,data:t,...s,method:"PUT"},i,s)))}generateKey(e,t){const{method:s,url:i}=e;let n=s+"#"+i;const r=[];for(const[e,s]of Object.entries(t))r.push(`${e}=${s}`);return r.length>0&&(n+="?"+r.join("&")),n}generateKeyByConfig(e){if(e.isRefreshToken||!e.fullPath)return e.url;const{url:t,method:s,data:i,params:n}=e;let r={};try{i&&Object.assign(r,JSON.parse(i))}catch{r={}}n&&Object.assign(r,n);const h={url:t,method:s};return this.generateKey(h,r)}constructor(e,t,s){const i=s.code||"code",n=s.accessToken||"accessToken",r=s.setAccessTokenFun||function(e,t){return e.headers.Authorization="Bearer "+t,e};s.showLoadingFun&&(this.showLoadingFun=s.showLoadingFun),s.hideLoadingFun&&(this.hideLoadingFun=s.hideLoadingFun),this.timeout=s?.timeout||this.timeout,this.maxReqNum=s?.maxReqNum||this.maxReqNum,this.cacheNum=s?.cacheNum||this.cacheNum,this.cache=new Map,this.instance=t,this.instance.interceptors.request.use((t=>{const s=this.generateKeyByConfig(t);t?.noShowLoading||(this.requestingNum++,this.showLoadingFun(t,this.requestingNum));if(t.url&&t.preventDuplicateRequestsType&&"cancel"===t.preventDuplicateRequestsType){this.requestObj[s]&&this.requestObj[s].cancel({config:t});const i=e.CancelToken;this.requestObj[s]=i.source(),t.cancelToken=this.requestObj[s].token}return t}),(e=>Promise.reject(e))),this.instance.interceptors.response.use((async e=>{const{config:t,responseErr:h}=e,a=this.generateKeyByConfig(t);if(t?.noShowLoading||(this.requestingNum--,this.requestingNum<=0&&(this.requestObj={},this.hideLoadingFun(t,this.requestingNum))),t&&this.requestObj[a]&&delete this.requestObj[a],h){if(s.openRefresh){const h=s.accessTokenExpirationCode||401;if(Number(e[i])===h){this._pause();const i=s.getRefreshTokenFun();if(!i){if(this.isShowReLoginDialog)return;return this.isShowReLoginDialog=!0,this.removeCache(),s.reloginFun(e),Promise.reject(e||"Error")}if(this.isRefresh)return new Promise((e=>{this.queue.push((()=>{e(this.instance.request(t))}))}));this.isRefresh=!0;try{const e=t.headers[n],h=s.getAccessTokenFun();return h!==e?(this._start(),this.instance.request(r(t,h))):(await s.refreshTokenStore(i),this._start(),this.queue.forEach((e=>e())),this.instance.request(t))}catch{if(this.isShowReLoginDialog)return;return this.isShowReLoginDialog=!0,this.removeCache(),s.reloginFun(e),Promise.reject(e||"Error")}finally{this.queue=[],this.isRefresh=!1}}const a=s.refreshTokenExpirationCode||403;if(Number(e[i])===a){if(this.isShowReLoginDialog)return;return this.isShowReLoginDialog=!0,this.removeCache(),s.reloginFun(e),Promise.reject(e||"Error")}}return Promise.reject(e||"Error")}if(s?.responseTypesStr?.toLocaleLowerCase().split(",").includes(t?.responseType?.toLocaleLowerCase()))return e.data;const u=JSON.parse(JSON.stringify(e));return t.cache||delete u.responseErr,delete u.config,s.responseResultFun?s.responseResultFun(u):u}),(e=>{const t=e?.message?.config||{},i=this.generateKeyByConfig(t);return t?.noShowLoading||(this.requestingNum--,this.requestingNum<=0&&(this.requestObj={},s.hideLoadingFun&&s.hideLoadingFun(t,this.requestingNum))),"CanceledError"!==e?.name&&delete this.requestObj[i],Promise.reject(e)}))}}export default e;
1
+ class e{instance;requestObj={};requestingNum=0;queue=[];isRefresh=!1;isShowReLoginDialog=!1;runningCount=0;maxReqNum=6;tasks=[];cache;cacheNum=10;status="start";runingReqKeys=[];showLoadingFun=()=>{};hideLoadingFun=()=>{};timeout=300;add(e,t,s){return new Promise(((i,n)=>{try{if("pause"===this.status||"prevent"===s?.preventDuplicateRequestsType){if(this.tasks.some((e=>e.requestKey===t))||this.runingReqKeys.includes(t))return}if("cancel"===s?.preventDuplicateRequestsType){if(this.tasks.some((e=>e.requestKey===t))&&(this.tasks=this.tasks.filter((e=>e.requestKey!==t))),this.runingReqKeys.includes(t))return this.maxReqNum++,this.runingReqKeys.push(t),this.tasks.unshift({isCancel:!0,task:e,requestKey:t,config:s,resolve:i,reject:n}),void this._run()}this.runingReqKeys.push(t),this.tasks.push({task:e,requestKey:t,config:s,resolve:i,reject:n}),this._run()}catch(e){n(e)}}))}_pause(){this.status="pause"}_start(){this.status="start",this._run()}_run(){if("pause"!==this.status)for(;this.runningCount<this.maxReqNum&&this.tasks.length>0;){const{isCancel:e,task:t,requestKey:s,config:i,resolve:n,reject:r}=this.tasks.shift();e&&this.maxReqNum--,this.runningCount++,t().then((e=>{this.runningCount--,"[object Object]"===Object.prototype.toString.call(e)&&i?.showIsComplete?n({...e,[i.showIsComplete]:0===this.runningCount}):n(e)})).catch((e=>{if(this.runningCount--,(e?.name&&"CanceledError"!==e?.name||!e?.name)&&i?.removeRemainingTasksWhenError){const e=this.tasks.map((e=>e.requestKey));this.tasks=[],this.runingReqKeys=this.runingReqKeys.filter((t=>!e.includes(t)))}r(e)})).finally((()=>{this.runingReqKeys.splice(this.runingReqKeys.indexOf(s),1),this._run()}))}}getCache(e,t){const{url:s,data:i={},config:n,method:r}=t;return new Promise(((t,h)=>{if(!this.cache.has(e)){delete n.cache;return void(["get","delete"].includes(r)?this[r](s,n):this[r](s,i,n)).then((s=>{this.setCache(e,s),t(s)})).catch((e=>{h(e)}))}const a=this.cache.get(e);this.cache.delete(e),this.cache.set(e,a),this.showLoadingFun(),setTimeout((()=>{this.hideLoadingFun(),t(a)}),this.timeout)}))}setCache(e,t){if(this.cache.has(e)&&this.cache.delete(e),this.cache.set(e,t),this.cache.size>this.cacheNum){const e=this.cache.keys().next().value;this.cache.delete(e)}}removeCache(){this.cache&&this.cache.clear()}get(e,t){if(t.cache){const s={url:e,config:t,method:"get"},i=t.params,n=this.generateKey(s,i);return this.getCache(n,s)}const s=t.params;if(delete t.params,t.isRefreshToken)return this.instance.request({url:e,params:s,...t,method:"GET"});const i=t.fullPath?this.generateKey({url:e,method:"get"},s):"GET#"+e;return this.add((()=>this.instance.request({url:e,params:s,...t,method:"GET"})),i,t)}post(e,t={},s={}){if(s.cache){const i={url:e,data:t,config:s,method:"post"},n=this.generateKey(i,t);return this.getCache(n,i)}if(s.isRefreshToken)return this.instance.request({url:e,data:t,...s,method:"POST"});const i=s.fullPath?this.generateKey({url:e,method:"post"},t):"POST#"+e;return this.add((()=>this.instance.request({url:e,data:t,...s,method:"POST"})),i,s)}delete(e,t={}){if(t.cache){const s={url:e,config:t,method:"delete"},i=t.params||t.data||{},n=this.generateKey(s,i);return this.getCache(n,s)}if(t.params){const s=t?.params||{};if(delete t.params,t.isRefreshToken)return this.instance.request({url:e,params:s,...t,method:"DELETE"});const i=t.fullPath?this.generateKey({url:e,method:"delete"},s):"DELETE#"+e;return this.add((()=>this.instance.request({url:e,params:s,...t,method:"DELETE"},i,t)))}const s=t?.data||{};if(delete t.data,t.isRefreshToken)return this.instance.request({url:e,data:s,...t,method:"DELETE"});const i=t.fullPath?this.generateKey({url:e,method:"delete"},s):"DELETE#"+e;return this.add((()=>this.instance.request({url:e,data:s,...t,method:"DELETE"},i,t)))}patch(e,t={},s={}){if(s.cache){const i={url:e,data:t,config:s,method:"patch"},n=this.generateKey(i,t);return this.getCache(n,i)}if(s.isRefreshToken)return this.instance.request({url:e,data:t,...s,method:"PATCH"});const i=s.fullPath?this.generateKey({url:e,method:"patch"},t):"PATCH#"+e;return this.add((()=>this.instance.request({url:e,data:t,...s,method:"PATCH"},i,s)))}put(e,t={},s={}){if(s.cache){const i={url:e,data:t,config:s,method:"put"},n=this.generateKey(i,t);return this.getCache(n,i)}if(s.isRefreshToken)return this.instance.request({url:e,data:t,...s,method:"PUT"});const i=s.fullPath?this.generateKey({url:e,method:"put"},t):"PUT#"+e;return this.add((()=>this.instance.request({url:e,data:t,...s,method:"PUT"},i,s)))}generateKey(e,t){const{method:s,url:i}=e;let n=s+"#"+i;const r=[];for(const[e,s]of Object.entries(t))r.push(`${e}=${s}`);return r.length>0&&(n+="?"+r.join("&")),n}generateKeyByConfig(e){if(e.isRefreshToken||!e.fullPath)return e.url;const{url:t,method:s,data:i,params:n}=e;let r={};try{i&&Object.assign(r,JSON.parse(i))}catch{r={}}n&&Object.assign(r,n);const h={url:t,method:s};return this.generateKey(h,r)}constructor(e,t,s){const i=s.code||"code",n=s.authorizationKey||"Authorization",r=s.responseTypesStr||"arraybuffer,blob",h=s.setAuthorizationFun||function(e,t){return e.headers[n]=t,e};s.showLoadingFun&&(this.showLoadingFun=s.showLoadingFun),s.hideLoadingFun&&(this.hideLoadingFun=s.hideLoadingFun),this.timeout=s?.timeout||this.timeout,this.maxReqNum=s?.maxReqNum||this.maxReqNum,this.cacheNum=s?.cacheNum||this.cacheNum,this.cache=new Map,this.instance=t,this.instance.interceptors.request.use((t=>{const s=this.generateKeyByConfig(t);t?.noShowLoading||(this.requestingNum++,this.showLoadingFun(t,this.requestingNum));if(t.url&&t.preventDuplicateRequestsType&&"cancel"===t.preventDuplicateRequestsType){this.requestObj[s]&&this.requestObj[s].cancel({config:t});const i=e.CancelToken;this.requestObj[s]=i.source(),t.cancelToken=this.requestObj[s].token}return t}),(e=>Promise.reject(e))),this.instance.interceptors.response.use((async e=>{const{config:t,responseErr:a}=e,u=this.generateKeyByConfig(t);if(t?.noShowLoading||(this.requestingNum--,this.requestingNum<=0&&(this.requestObj={},this.hideLoadingFun(t,this.requestingNum))),t&&this.requestObj[u]&&delete this.requestObj[u],a){if(s.openRefresh){const r=s.accessTokenExpirationCode||401;if(Number(e[i])===r){this._pause();const i=s.getRefreshTokenFun();if(!i){if(this.isShowReLoginDialog)return;return this.isShowReLoginDialog=!0,this.removeCache(),s.reloginFun(e),Promise.reject(e||"Error")}if(this.isRefresh)return new Promise((e=>{this.queue.push((()=>{e(this.instance.request(t))}))}));this.isRefresh=!0;try{const e=t.headers[n],r=s.getAuthorizationFun();return e!==r?(this._start(),this.instance.request(h(t,r))):(await s.refreshTokenStore(i),this._start(),this.queue.forEach((e=>e())),this.instance.request(t))}catch{if(this.isShowReLoginDialog)return;return this.isShowReLoginDialog=!0,this.removeCache(),s.reloginFun(e),Promise.reject(e||"Error")}finally{this.queue=[],this.isRefresh=!1}}const a=s.refreshTokenExpirationCode||403;if(Number(e[i])===a){if(this.isShowReLoginDialog)return;return this.isShowReLoginDialog=!0,this.removeCache(),s.reloginFun(e),Promise.reject(e||"Error")}}return Promise.reject(e||"Error")}if(r.toLocaleLowerCase().split(",").includes(t?.responseType?.toLocaleLowerCase()))return e.data;const o=JSON.parse(JSON.stringify(e));return t.cache||delete o.responseErr,delete o.config,s.responseResultFun?s.responseResultFun(o):o}),(e=>{const t=e?.message?.config||{},i=this.generateKeyByConfig(t);return t?.noShowLoading||(this.requestingNum--,this.requestingNum<=0&&(this.requestObj={},s.hideLoadingFun&&s.hideLoadingFun(t,this.requestingNum))),"CanceledError"!==e?.name&&delete this.requestObj[i],Promise.reject(e)}))}}export default e;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue-axios-optimize",
3
- "version": "2.1.1",
3
+ "version": "3.0.0",
4
4
  "description": "vue项目对axios请求的优化,实现可配置展示全局加载动画,可配置是否重复请求时取消前面的请求或者阻止后面的请求,已经控制请求并发数量,接口缓存",
5
5
  "main": "index.js",
6
6
  "scripts": {