| | |
| | | // axios配置 可自行根据项目进行更改,只需更改该文件即可,其他文件可以不动 |
| | | // The axios configuration can be changed according to the project, just change the file, other files can be left unchanged |
| | | |
| | | import type { AxiosResponse } from 'axios'; |
| | | import type { RequestOptions, Result } from '/#/axios'; |
| | | import type { AxiosInstance, AxiosResponse } from 'axios'; |
| | | import { clone } from 'lodash-es'; |
| | | import type { RequestOptions, Result } from '#/axios'; |
| | | import type { AxiosTransform, CreateAxiosOptions } from './axiosTransform'; |
| | | import { VAxios } from './Axios'; |
| | | import { checkStatus } from './checkStatus'; |
| | | import { useGlobSetting } from '/@/hooks/setting'; |
| | | import { useMessage } from '/@/hooks/web/useMessage'; |
| | | import { RequestEnum, ResultEnum, ContentTypeEnum } from '/@/enums/httpEnum'; |
| | | import { isString } from '/@/utils/is'; |
| | | import { getToken } from '/@/utils/auth'; |
| | | import { setObjToUrlParams, deepMerge } from '/@/utils'; |
| | | import { useErrorLogStoreWithOut } from '/@/store/modules/errorLog'; |
| | | import { useI18n } from '/@/hooks/web/useI18n'; |
| | | import { useGlobSetting } from '@/hooks/setting'; |
| | | import { useMessage } from '@/hooks/web/useMessage'; |
| | | import { RequestEnum, ResultEnum, ContentTypeEnum } from '@/enums/httpEnum'; |
| | | import { isString, isUndefined, isNull, isEmpty } from '@/utils/is'; |
| | | import { getToken } from '@/utils/auth'; |
| | | import { setObjToUrlParams, deepMerge } from '@/utils'; |
| | | import { useErrorLogStoreWithOut } from '@/store/modules/errorLog'; |
| | | import { useI18n } from '@/hooks/web/useI18n'; |
| | | import { joinTimestamp, formatRequestDate } from './helper'; |
| | | import { useUserStoreWithOut } from '@/store/modules/user'; |
| | | import { AxiosRetry } from '@/utils/http/axios/axiosRetry'; |
| | | import axios from 'axios'; |
| | | import DragBar from '@/layouts/default/sider/DragBar.vue'; |
| | | |
| | | const globSetting = useGlobSetting(); |
| | | const urlPrefix = globSetting.urlPrefix; |
| | | const { createMessage, createErrorModal } = useMessage(); |
| | | const { createMessage, createErrorModal, createSuccessModal } = useMessage(); |
| | | |
| | | /** |
| | | * @description: 数据处理,方便区分多种处理方式 |
| | | */ |
| | | const transform: AxiosTransform = { |
| | | /** |
| | | * @description: 处理请求数据。如果数据不是预期格式,可直接抛出错误 |
| | | * @description: 处理响应数据。如果数据不是预期格式,可直接抛出错误 |
| | | */ |
| | | transformRequestHook: (res: AxiosResponse<Result>, options: RequestOptions) => { |
| | | transformResponseHook: (res: AxiosResponse<Result>, options: RequestOptions) => { |
| | | const { t } = useI18n(); |
| | | const { isTransformResponse, isReturnNativeResponse } = options; |
| | | // 是否返回原生响应头 比如:需要获取响应头时使用该属性 |
| | |
| | | return res.data; |
| | | } |
| | | // 错误的时候返回 |
| | | |
| | | const { data } = res; |
| | | if (!data) { |
| | | // return '[HTTP] Request has no return value'; |
| | | throw new Error(t('sys.api.apiRequestFailed')); |
| | | } |
| | | // 这里 code,result,message为 后台统一的字段,需要在 types.ts内修改为项目自己的接口返回格式 |
| | | const { code, result, message } = data; |
| | | const { code, result, state } = data; |
| | | const { msg: message } = data; |
| | | |
| | | // 这里逻辑可以根据项目进行修改 |
| | | const hasSuccess = data && Reflect.has(data, 'code') && code === ResultEnum.SUCCESS; |
| | | const isPass = code === ResultEnum.SUCCESS || state === ResultEnum.SUCCESS; |
| | | const isReflect = Reflect.has(data, 'code') || Reflect.has(data, 'state'); |
| | | const hasSuccess = data && isReflect && isPass; |
| | | if (hasSuccess) { |
| | | return result; |
| | | let successMsg = message; |
| | | |
| | | if (isNull(successMsg) || isUndefined(successMsg) || isEmpty(successMsg)) { |
| | | successMsg = t(`sys.api.operationSuccess`); |
| | | } |
| | | |
| | | if (options.successMessageMode === 'modal') { |
| | | createSuccessModal({ title: t('sys.api.successTip'), content: successMsg }); |
| | | } else if (options.successMessageMode === 'message') { |
| | | createMessage.success(successMsg); |
| | | } |
| | | return result || data; |
| | | } |
| | | |
| | | // 在此处根据自己项目的实际情况对不同的code执行不同的操作 |
| | |
| | | switch (code) { |
| | | case ResultEnum.TIMEOUT: |
| | | timeoutMsg = t('sys.api.timeoutMessage'); |
| | | const userStore = useUserStoreWithOut(); |
| | | // 被动登出,带redirect地址 |
| | | userStore.logout(false); |
| | | break; |
| | | default: |
| | | if (message) { |
| | |
| | | } |
| | | } |
| | | |
| | | // errorMessageMode=‘modal’的时候会显示modal错误弹窗,而不是消息提示,用于一些比较重要的错误 |
| | | // errorMessageMode='modal'的时候会显示modal错误弹窗,而不是消息提示,用于一些比较重要的错误 |
| | | // errorMessageMode='none' 一般是调用时明确表示不希望自动弹出错误提示 |
| | | if (options.errorMessageMode === 'modal') { |
| | | createErrorModal({ title: t('sys.api.errorTip'), content: timeoutMsg }); |
| | |
| | | |
| | | // 请求之前处理config |
| | | beforeRequestHook: (config, options) => { |
| | | const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true } = options; |
| | | const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true, urlPrefix } = options; |
| | | |
| | | if (joinPrefix) { |
| | | config.url = `${urlPrefix}${config.url}`; |
| | |
| | | } else { |
| | | if (!isString(params)) { |
| | | formatDate && formatRequestDate(params); |
| | | if (Reflect.has(config, 'data') && config.data && Object.keys(config.data).length > 0) { |
| | | if ( |
| | | Reflect.has(config, 'data') && |
| | | config.data && |
| | | (Object.keys(config.data).length > 0 || config.data instanceof FormData) |
| | | ) { |
| | | config.data = data; |
| | | config.params = params; |
| | | } else { |
| | |
| | | const token = getToken(); |
| | | if (token && (config as Recordable)?.requestOptions?.withToken !== false) { |
| | | // jwt token |
| | | config.headers.Authorization = options.authenticationScheme |
| | | (config as Recordable).headers.Authorization = options.authenticationScheme |
| | | ? `${options.authenticationScheme} ${token}` |
| | | : token; |
| | | } |
| | |
| | | /** |
| | | * @description: 响应错误处理 |
| | | */ |
| | | responseInterceptorsCatch: (error: any) => { |
| | | responseInterceptorsCatch: (axiosInstance: AxiosInstance, error: any) => { |
| | | const { t } = useI18n(); |
| | | const errorLogStore = useErrorLogStoreWithOut(); |
| | | errorLogStore.addAjaxErrorInfo(error); |
| | |
| | | const msg: string = response?.data?.error?.message ?? ''; |
| | | const err: string = error?.toString?.() ?? ''; |
| | | let errMessage = ''; |
| | | |
| | | if (axios.isCancel(error)) { |
| | | return Promise.reject(error); |
| | | } |
| | | |
| | | try { |
| | | if (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1) { |
| | |
| | | return Promise.reject(error); |
| | | } |
| | | } catch (error) { |
| | | throw new Error(error); |
| | | throw new Error(error as unknown as string); |
| | | } |
| | | |
| | | checkStatus(error?.response?.status, msg, errorMessageMode); |
| | | |
| | | // 添加自动重试机制 保险起见 只针对GET请求 |
| | | const retryRequest = new AxiosRetry(); |
| | | const { isOpenRetry } = config.requestOptions.retryRequest; |
| | | config.method?.toUpperCase() === RequestEnum.GET && |
| | | isOpenRetry && |
| | | error?.response?.status !== 401 && |
| | | // @ts-ignore |
| | | retryRequest.retry(axiosInstance, error); |
| | | return Promise.reject(error); |
| | | }, |
| | | }; |
| | | |
| | | function createAxios(opt?: Partial<CreateAxiosOptions>) { |
| | | return new VAxios( |
| | | // 深度合并 |
| | | deepMerge( |
| | | { |
| | | // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#authentication_schemes |
| | | // authentication schemes,e.g: Bearer |
| | | // authenticationScheme: 'Bearer', |
| | | authenticationScheme: '', |
| | | timeout: 10 * 1000, |
| | | timeout: 60 * 1000, |
| | | // 基础接口地址 |
| | | // baseURL: globSetting.apiUrl, |
| | | // 接口可能会有通用的地址部分,可以统一抽取出来 |
| | | urlPrefix: urlPrefix, |
| | | |
| | | headers: { 'Content-Type': ContentTypeEnum.JSON }, |
| | | // 如果是form-data格式 |
| | | // headers: { 'Content-Type': ContentTypeEnum.FORM_URLENCODED }, |
| | | // 数据处理方式 |
| | | transform, |
| | | transform: clone(transform), |
| | | withCredentials: true, |
| | | // 配置项,下面的选项都可以在独立的接口请求中覆盖 |
| | | requestOptions: { |
| | | // 默认将prefix 添加到url |
| | |
| | | errorMessageMode: 'message', |
| | | // 接口地址 |
| | | apiUrl: globSetting.apiUrl, |
| | | // 接口拼接地址 |
| | | urlPrefix: urlPrefix, |
| | | // 是否加入时间戳 |
| | | joinTime: true, |
| | | // 忽略重复请求 |
| | | ignoreCancelToken: true, |
| | | // 是否携带token |
| | | withToken: true, |
| | | retryRequest: { |
| | | isOpenRetry: true, |
| | | count: 5, |
| | | waitTime: 100, |
| | | }, |
| | | }, |
| | | }, |
| | | opt || {}, |
| | |
| | | // export const otherHttp = createAxios({ |
| | | // requestOptions: { |
| | | // apiUrl: 'xxx', |
| | | // urlPrefix: 'xxx', |
| | | // }, |
| | | // }); |