提交 | 用户 | age
|
2f6253
|
1 |
// axios配置 可自行根据项目进行更改,只需更改该文件即可,其他文件可以不动 |
陈 |
2 |
// The axios configuration can be changed according to the project, just change the file, other files can be left unchanged |
|
3 |
|
c118e8
|
4 |
import type { AxiosInstance, AxiosResponse } from 'axios'; |
4f35b9
|
5 |
import { clone } from 'lodash-es'; |
4d2fb0
|
6 |
import type { RequestOptions, Result } from '#/axios'; |
b7ce74
|
7 |
import type { AxiosTransform, CreateAxiosOptions } from './axiosTransform'; |
V |
8 |
import { VAxios } from './Axios'; |
2f6253
|
9 |
import { checkStatus } from './checkStatus'; |
4d2fb0
|
10 |
import { useGlobSetting } from '@/hooks/setting'; |
X |
11 |
import { useMessage } from '@/hooks/web/useMessage'; |
|
12 |
import { RequestEnum, ResultEnum, ContentTypeEnum } from '@/enums/httpEnum'; |
|
13 |
import { isString, isUndefined, isNull, isEmpty } from '@/utils/is'; |
|
14 |
import { getToken } from '@/utils/auth'; |
|
15 |
import { setObjToUrlParams, deepMerge } from '@/utils'; |
|
16 |
import { useErrorLogStoreWithOut } from '@/store/modules/errorLog'; |
|
17 |
import { useI18n } from '@/hooks/web/useI18n'; |
50f94b
|
18 |
import { joinTimestamp, formatRequestDate } from './helper'; |
4d2fb0
|
19 |
import { useUserStoreWithOut } from '@/store/modules/user'; |
X |
20 |
import { AxiosRetry } from '@/utils/http/axios/axiosRetry'; |
b1f78c
|
21 |
import axios from 'axios'; |
2f6253
|
22 |
|
737b1b
|
23 |
const globSetting = useGlobSetting(); |
50f94b
|
24 |
const urlPrefix = globSetting.urlPrefix; |
17d16a
|
25 |
const { createMessage, createErrorModal, createSuccessModal } = useMessage(); |
2f6253
|
26 |
|
陈 |
27 |
/** |
|
28 |
* @description: 数据处理,方便区分多种处理方式 |
|
29 |
*/ |
|
30 |
const transform: AxiosTransform = { |
|
31 |
/** |
c0e40f
|
32 |
* @description: 处理响应数据。如果数据不是预期格式,可直接抛出错误 |
2f6253
|
33 |
*/ |
c0e40f
|
34 |
transformResponseHook: (res: AxiosResponse<Result>, options: RequestOptions) => { |
962f90
|
35 |
const { t } = useI18n(); |
b6d5b0
|
36 |
const { isTransformResponse, isReturnNativeResponse } = options; |
56d8af
|
37 |
// 是否返回原生响应头 比如:需要获取响应头时使用该属性 |
Z |
38 |
if (isReturnNativeResponse) { |
|
39 |
return res; |
|
40 |
} |
2f6253
|
41 |
// 不进行任何处理,直接返回 |
陈 |
42 |
// 用于页面代码可能需要直接获取code,data,message这些信息时开启 |
b6d5b0
|
43 |
if (!isTransformResponse) { |
2f6253
|
44 |
return res.data; |
陈 |
45 |
} |
|
46 |
// 错误的时候返回 |
|
47 |
|
|
48 |
const { data } = res; |
|
49 |
if (!data) { |
|
50 |
// return '[HTTP] Request has no return value'; |
b218f1
|
51 |
throw new Error(t('sys.api.apiRequestFailed')); |
2f6253
|
52 |
} |
陈 |
53 |
// 这里 code,result,message为 后台统一的字段,需要在 types.ts内修改为项目自己的接口返回格式 |
|
54 |
const { code, result, message } = data; |
|
55 |
|
|
56 |
// 这里逻辑可以根据项目进行修改 |
|
57 |
const hasSuccess = data && Reflect.has(data, 'code') && code === ResultEnum.SUCCESS; |
b69dcd
|
58 |
if (hasSuccess) { |
17d16a
|
59 |
let successMsg = message; |
bc0990
|
60 |
|
f91d77
|
61 |
if (isNull(successMsg) || isUndefined(successMsg) || isEmpty(successMsg)) { |
bc0990
|
62 |
successMsg = t(`sys.api.operationSuccess`); |
17d16a
|
63 |
} |
bc0990
|
64 |
|
17d16a
|
65 |
if (options.successMessageMode === 'modal') { |
M |
66 |
createSuccessModal({ title: t('sys.api.successTip'), content: successMsg }); |
|
67 |
} else if (options.successMessageMode === 'message') { |
|
68 |
createMessage.success(successMsg); |
|
69 |
} |
e83cb0
|
70 |
return result; |
2f6253
|
71 |
} |
b69dcd
|
72 |
|
无 |
73 |
// 在此处根据自己项目的实际情况对不同的code执行不同的操作 |
|
74 |
// 如果不希望中断当前请求,请return数据,否则直接抛出异常即可 |
49b66e
|
75 |
let timeoutMsg = ''; |
b69dcd
|
76 |
switch (code) { |
无 |
77 |
case ResultEnum.TIMEOUT: |
49b66e
|
78 |
timeoutMsg = t('sys.api.timeoutMessage'); |
6544f8
|
79 |
const userStore = useUserStoreWithOut(); |
236ddf
|
80 |
// 被动登出,带redirect地址 |
X |
81 |
userStore.logout(false); |
49f39d
|
82 |
break; |
b69dcd
|
83 |
default: |
无 |
84 |
if (message) { |
49b66e
|
85 |
timeoutMsg = message; |
b69dcd
|
86 |
} |
2f6253
|
87 |
} |
49b66e
|
88 |
|
9ba157
|
89 |
// errorMessageMode='modal'的时候会显示modal错误弹窗,而不是消息提示,用于一些比较重要的错误 |
49b66e
|
90 |
// errorMessageMode='none' 一般是调用时明确表示不希望自动弹出错误提示 |
X |
91 |
if (options.errorMessageMode === 'modal') { |
|
92 |
createErrorModal({ title: t('sys.api.errorTip'), content: timeoutMsg }); |
|
93 |
} else if (options.errorMessageMode === 'message') { |
|
94 |
createMessage.error(timeoutMsg); |
|
95 |
} |
|
96 |
|
|
97 |
throw new Error(timeoutMsg || t('sys.api.apiRequestFailed')); |
2f6253
|
98 |
}, |
陈 |
99 |
|
|
100 |
// 请求之前处理config |
|
101 |
beforeRequestHook: (config, options) => { |
996f2f
|
102 |
const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true, urlPrefix } = options; |
2f6253
|
103 |
|
陈 |
104 |
if (joinPrefix) { |
50f94b
|
105 |
config.url = `${urlPrefix}${config.url}`; |
2f6253
|
106 |
} |
陈 |
107 |
|
|
108 |
if (apiUrl && isString(apiUrl)) { |
|
109 |
config.url = `${apiUrl}${config.url}`; |
|
110 |
} |
ac1a36
|
111 |
const params = config.params || {}; |
49f39d
|
112 |
const data = config.data || false; |
无 |
113 |
formatDate && data && !isString(data) && formatRequestDate(data); |
6b3195
|
114 |
if (config.method?.toUpperCase() === RequestEnum.GET) { |
ac1a36
|
115 |
if (!isString(params)) { |
c96002
|
116 |
// 给 get 请求加上时间戳参数,避免从缓存中拿数据。 |
50f94b
|
117 |
config.params = Object.assign(params || {}, joinTimestamp(joinTime, false)); |
2f6253
|
118 |
} else { |
陈 |
119 |
// 兼容restful风格 |
50f94b
|
120 |
config.url = config.url + params + `${joinTimestamp(joinTime, true)}`; |
34c09f
|
121 |
config.params = undefined; |
2f6253
|
122 |
} |
陈 |
123 |
} else { |
ac1a36
|
124 |
if (!isString(params)) { |
V |
125 |
formatDate && formatRequestDate(params); |
b1f78c
|
126 |
if ( |
L |
127 |
Reflect.has(config, 'data') && |
|
128 |
config.data && |
|
129 |
(Object.keys(config.data).length > 0 || config.data instanceof FormData) |
|
130 |
) { |
49f39d
|
131 |
config.data = data; |
无 |
132 |
config.params = params; |
|
133 |
} else { |
|
134 |
// 非GET请求如果没有提供data,则将params视为data |
|
135 |
config.data = params; |
|
136 |
config.params = undefined; |
|
137 |
} |
2f6253
|
138 |
if (joinParamsToUrl) { |
49f39d
|
139 |
config.url = setObjToUrlParams( |
无 |
140 |
config.url as string, |
56a966
|
141 |
Object.assign({}, config.params, config.data), |
49f39d
|
142 |
); |
2f6253
|
143 |
} |
陈 |
144 |
} else { |
|
145 |
// 兼容restful风格 |
ac1a36
|
146 |
config.url = config.url + params; |
34c09f
|
147 |
config.params = undefined; |
2f6253
|
148 |
} |
陈 |
149 |
} |
|
150 |
return config; |
|
151 |
}, |
|
152 |
|
|
153 |
/** |
|
154 |
* @description: 请求拦截器处理 |
|
155 |
*/ |
b6d5b0
|
156 |
requestInterceptors: (config, options) => { |
2f6253
|
157 |
// 请求之前处理config |
b7ce74
|
158 |
const token = getToken(); |
d509e8
|
159 |
if (token && (config as Recordable)?.requestOptions?.withToken !== false) { |
2f6253
|
160 |
// jwt token |
590288
|
161 |
(config as Recordable).headers.Authorization = options.authenticationScheme |
b6d5b0
|
162 |
? `${options.authenticationScheme} ${token}` |
V |
163 |
: token; |
2f6253
|
164 |
} |
陈 |
165 |
return config; |
|
166 |
}, |
|
167 |
|
|
168 |
/** |
49b66e
|
169 |
* @description: 响应拦截器处理 |
X |
170 |
*/ |
|
171 |
responseInterceptors: (res: AxiosResponse<any>) => { |
|
172 |
return res; |
|
173 |
}, |
|
174 |
|
|
175 |
/** |
2f6253
|
176 |
* @description: 响应错误处理 |
陈 |
177 |
*/ |
c118e8
|
178 |
responseInterceptorsCatch: (axiosInstance: AxiosInstance, error: any) => { |
962f90
|
179 |
const { t } = useI18n(); |
215d8b
|
180 |
const errorLogStore = useErrorLogStoreWithOut(); |
V |
181 |
errorLogStore.addAjaxErrorInfo(error); |
49b66e
|
182 |
const { response, code, message, config } = error || {}; |
X |
183 |
const errorMessageMode = config?.requestOptions?.errorMessageMode || 'none'; |
de5bf7
|
184 |
const msg: string = response?.data?.error?.message ?? ''; |
V |
185 |
const err: string = error?.toString?.() ?? ''; |
49b66e
|
186 |
let errMessage = ''; |
X |
187 |
|
b1f78c
|
188 |
if (axios.isCancel(error)) { |
L |
189 |
return Promise.reject(error); |
|
190 |
} |
|
191 |
|
2f6253
|
192 |
try { |
陈 |
193 |
if (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1) { |
49b66e
|
194 |
errMessage = t('sys.api.apiTimeoutMessage'); |
2f6253
|
195 |
} |
6b3195
|
196 |
if (err?.includes('Network Error')) { |
49b66e
|
197 |
errMessage = t('sys.api.networkExceptionMsg'); |
X |
198 |
} |
|
199 |
|
|
200 |
if (errMessage) { |
|
201 |
if (errorMessageMode === 'modal') { |
|
202 |
createErrorModal({ title: t('sys.api.errorTip'), content: errMessage }); |
|
203 |
} else if (errorMessageMode === 'message') { |
|
204 |
createMessage.error(errMessage); |
|
205 |
} |
|
206 |
return Promise.reject(error); |
2f6253
|
207 |
} |
陈 |
208 |
} catch (error) { |
590288
|
209 |
throw new Error(error as unknown as string); |
2f6253
|
210 |
} |
49b66e
|
211 |
|
X |
212 |
checkStatus(error?.response?.status, msg, errorMessageMode); |
136cbb
|
213 |
|
C |
214 |
// 添加自动重试机制 保险起见 只针对GET请求 |
|
215 |
const retryRequest = new AxiosRetry(); |
|
216 |
const { isOpenRetry } = config.requestOptions.retryRequest; |
|
217 |
config.method?.toUpperCase() === RequestEnum.GET && |
|
218 |
isOpenRetry && |
|
219 |
// @ts-ignore |
|
220 |
retryRequest.retry(axiosInstance, error); |
661db0
|
221 |
return Promise.reject(error); |
2f6253
|
222 |
}, |
陈 |
223 |
}; |
|
224 |
|
|
225 |
function createAxios(opt?: Partial<CreateAxiosOptions>) { |
|
226 |
return new VAxios( |
19dc88
|
227 |
// 深度合并 |
2f6253
|
228 |
deepMerge( |
陈 |
229 |
{ |
b6d5b0
|
230 |
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#authentication_schemes |
V |
231 |
// authentication schemes,e.g: Bearer |
|
232 |
// authenticationScheme: 'Bearer', |
|
233 |
authenticationScheme: '', |
2f6253
|
234 |
timeout: 10 * 1000, |
陈 |
235 |
// 基础接口地址 |
61d4ef
|
236 |
// baseURL: globSetting.apiUrl, |
996f2f
|
237 |
|
2f6253
|
238 |
headers: { 'Content-Type': ContentTypeEnum.JSON }, |
f646e3
|
239 |
// 如果是form-data格式 |
V |
240 |
// headers: { 'Content-Type': ContentTypeEnum.FORM_URLENCODED }, |
2f6253
|
241 |
// 数据处理方式 |
4f35b9
|
242 |
transform: clone(transform), |
2f6253
|
243 |
// 配置项,下面的选项都可以在独立的接口请求中覆盖 |
陈 |
244 |
requestOptions: { |
|
245 |
// 默认将prefix 添加到url |
|
246 |
joinPrefix: true, |
56d8af
|
247 |
// 是否返回原生响应头 比如:需要获取响应头时使用该属性 |
Z |
248 |
isReturnNativeResponse: false, |
2f6253
|
249 |
// 需要对返回数据进行处理 |
b6d5b0
|
250 |
isTransformResponse: true, |
2f6253
|
251 |
// post请求的时候添加参数到url |
陈 |
252 |
joinParamsToUrl: false, |
|
253 |
// 格式化提交参数时间 |
|
254 |
formatDate: true, |
|
255 |
// 消息提示类型 |
4ce1d5
|
256 |
errorMessageMode: 'message', |
2f6253
|
257 |
// 接口地址 |
陈 |
258 |
apiUrl: globSetting.apiUrl, |
7df9b5
|
259 |
// 接口拼接地址 |
L |
260 |
urlPrefix: urlPrefix, |
f646e3
|
261 |
// 是否加入时间戳 |
V |
262 |
joinTime: true, |
3b8ca4
|
263 |
// 忽略重复请求 |
V |
264 |
ignoreCancelToken: true, |
c99cf5
|
265 |
// 是否携带token |
无 |
266 |
withToken: true, |
136cbb
|
267 |
retryRequest: { |
C |
268 |
isOpenRetry: true, |
|
269 |
count: 5, |
|
270 |
waitTime: 100, |
|
271 |
}, |
2f6253
|
272 |
}, |
陈 |
273 |
}, |
56a966
|
274 |
opt || {}, |
V |
275 |
), |
2f6253
|
276 |
); |
陈 |
277 |
} |
|
278 |
export const defHttp = createAxios(); |
|
279 |
|
|
280 |
// other api url |
|
281 |
// export const otherHttp = createAxios({ |
|
282 |
// requestOptions: { |
|
283 |
// apiUrl: 'xxx', |
7df9b5
|
284 |
// urlPrefix: 'xxx', |
2f6253
|
285 |
// }, |
陈 |
286 |
// }); |