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