You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ocr-web/src/utils/http/axios/index.ts

349 lines
11 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// axios配置 可自行根据项目进行更改,只需更改该文件即可,其他文件可以不动
import type { AxiosResponse } from 'axios'
import axios from 'axios'
import { useRoute, useRouter } from 'vue-router'
import { VAxios } from './Axios'
import type { AxiosTransform } from './axiosTransform'
import { checkStatus } from './checkStatus'
import { formatRequestDate, joinTimestamp } from './helper'
import type { CreateAxiosOptions, RequestOptions, Result } from './types'
import { ContentTypeEnum, RequestEnum, ResultEnum } from '@/enums/httpEnum'
import { PageEnum } from '@/enums/pageEnum'
import { isString } from '@/utils/is/'
import { deepMerge, isUrl } from '@/utils'
import { setObjToUrlParams } from '@/utils/urlUtils'
import { useUser } from '@/store/modules/user'
import router from '@/router'
import { storage } from '@/utils/Storage'
import { TOKEN_EXPIRATION_TIME } from '@/store/mutation-types'
// const globSetting = useGlobSetting()
const urlPrefix = '/api'
let isRefreshingToken = false
let requests: Function[] = []
/**
* @description: 数据处理,方便区分多种处理方式
*/
const transform: AxiosTransform = {
/**
* @description: 处理请求数据
*/
transformRequestData: (res: AxiosResponse<Result>, options: RequestOptions) => {
const {
isShowMessage = true,
isShowErrorMessage,
isShowSuccessMessage,
successMessageText,
errorMessageText,
isTransformResponse,
isReturnNativeResponse,
} = options
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
if (isReturnNativeResponse)
return res
// 不进行任何处理,直接返回
// 用于页面代码可能需要直接获取codedatamessage这些信息时开启
if (!isTransformResponse)
return res.data
const { data } = res
// eslint-disable-next-line dot-notation
const $dialog = window['$dialog']
// eslint-disable-next-line dot-notation
const $message = window['$message']
if (!data) {
// return '[HTTP] Request has no return value';
throw new Error('请求出错,请稍候重试')
}
// 这里 coderesultmessage为 后台统一的字段,需要修改为项目自己的接口返回格式
const { code, result, message } = data
// 请求成功
const hasSuccess = data && Reflect.has(data, 'code') && (code === ResultEnum.SUCCESS)
// 是否显示提示信息
if (isShowMessage) {
if (hasSuccess && (successMessageText || isShowSuccessMessage)) {
// 是否显示自定义信息提示
$dialog.success({
type: 'success',
content: successMessageText || message || '操作成功!',
})
}
else if (!hasSuccess && (errorMessageText || isShowErrorMessage)) {
// 是否显示自定义信息提示
$message.error(message || errorMessageText || '操作失败!')
}
else if (!hasSuccess && options.errorMessageMode === 'modal') {
// errorMessageMode=custom-modal的时候会显示modal错误弹窗而不是消息提示用于一些比较重要的错误
$dialog.info({
title: '提示',
content: message,
positiveText: '确定',
onPositiveClick: () => { },
})
}
}
// 接口请求成功,直接返回结果
if (code === ResultEnum.SUCCESS)
return result
// 接口请求错误,统一提示错误信息 这里逻辑可以根据项目进行修改
let errorMsg = message
switch (code) {
// 请求失败
case ResultEnum.ERROR:
$message.error(errorMsg)
break
// 登录超时
case ResultEnum.TIMEOUT:
if (router.currentRoute.value?.name === PageEnum.BASE_LOGIN_NAME)
return
// 到登录页
errorMsg = '登录超时,请重新登录!'
$dialog.warning({
title: '提示',
content: '登录身份已失效,请重新登录!',
positiveText: '确定',
// negativeText: '取消',
closable: false,
maskClosable: false,
onPositiveClick: () => {
storage.clear()
window.location.href = PageEnum.BASE_LOGIN
},
onNegativeClick: () => { },
})
break
}
throw new Error(errorMsg)
},
// 请求之前处理config
beforeRequestHook: (config, options) => {
const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true, urlPrefix } = options
const isUrlStr = isUrl(config.url as string)
if (!isUrlStr && joinPrefix)
config.url = `${urlPrefix}${config.url}`
if (!isUrlStr && apiUrl && isString(apiUrl))
config.url = `${apiUrl}${config.url}`
const params = config.params || {}
const data = config.data || false
if (config.method?.toUpperCase() === RequestEnum.GET) {
if (!isString(params)) {
// 给 get 请求加上时间戳参数,避免从缓存中拿数据。
config.params = Object.assign(params || {}, joinTimestamp(joinTime, false))
}
else {
// 兼容restful风格
config.url = `${config.url + params}${joinTimestamp(joinTime, true)}`
config.params = undefined
}
}
else {
if (!isString(params)) {
formatDate && formatRequestDate(params)
if (Reflect.has(config, 'data') && config.data && Object.keys(config.data).length > 0) {
config.data = data
config.params = params
}
else {
config.data = params
config.params = undefined
}
if (joinParamsToUrl) {
config.url = setObjToUrlParams(
config.url as string,
Object.assign({}, config.params, config.data),
)
}
}
else {
// 兼容restful风格
config.url = config.url + params
config.params = undefined
}
}
return config
},
/**
* @description: 请求拦截器处理
*/
requestInterceptors: (config, options) => {
// 请求之前处理config
const userStore = useUser()
const token = userStore.getToken
if (token && (config as Recordable)?.requestOptions?.withToken !== false)
(config as Recordable).headers.logintoken = token
const tenantId = userStore.getTenantId
if (tenantId)
(config as Recordable).headers['X-Tenant-Id'] = tenantId
return config
},
responseInterceptors: async (response: any) => {
const userStore = useUser()
// eslint-disable-next-line ts/no-use-before-define
const instance = http.getAxios()
const config = response.config
const whitelist = ['/api/captcha/captchatoken', '/api/captcha/captchaImage', '/api/backstage/adminlogin', '/api/oauthweb/token']
const expirationTime = storage.get(TOKEN_EXPIRATION_TIME)
// console.log('剩余失效时间(分):', (expirationTime - Date.now()) / 1000 / 60, config.url)
if (expirationTime && (expirationTime - Date.now()) <= 0 && !whitelist.includes(config.url as string)) {
if (!isRefreshingToken) {
isRefreshingToken = true
return userStore.refreshToken().then((res: any) => {
(config as Recordable).headers.logintoken = res.token
requests.forEach((item: Function) => item(res.token))
requests = []
return instance(config)
}).catch((res) => {
const route = useRoute()
const router = useRouter()
router
.replace({
name: 'Login',
query: {
redirect: route.fullPath,
},
})
.finally(() => location.reload())
}).finally(() => {
isRefreshingToken = false
})
}
else {
return new Promise((resolve) => {
// console.log('刷新token中暂存此请求', config.url)
requests.push((token) => {
(config as Recordable).headers.logintoken = token
resolve(instance(config)) // 执行请求,
})
})
}
}
return response
},
/**
* @description: 响应错误处理
*/
responseInterceptorsCatch: async (error: any) => {
// eslint-disable-next-line dot-notation
const $dialog = window['$dialog']
// eslint-disable-next-line dot-notation
const $message = window['$message']
const { response, code, message } = error || {}
// TODO 此处要根据后端接口返回格式修改
const msg: string
= response && response.data && response.data.message ? response.data.message : ''
const err: string = error.toString()
try {
if (code === 'ECONNABORTED' && message.includes('timeout')) {
$message.error('接口请求超时,请刷新页面重试!')
return
}
if (err && err.includes('Network Error')) {
$dialog.info({
title: '网络异常',
content: '请检查您的网络连接是否正常',
positiveText: '确定',
// negativeText: '取消',
closable: false,
maskClosable: false,
onPositiveClick: () => { },
onNegativeClick: () => { },
})
return Promise.reject(error)
}
}
catch (error) {
throw new Error(error as any)
}
// 请求是否被取消
const isCancel = axios.isCancel(error)
if (!isCancel)
checkStatus(error.response && error.response.status, msg)
else
console.warn(error, '请求被取消!')
// return Promise.reject(error);
return Promise.reject(response?.data)
},
}
function createAxios(opt?: Partial<CreateAxiosOptions>) {
return new VAxios(
deepMerge(
{
timeout: 30 * 1000,
authenticationScheme: '',
// 接口前缀
prefixUrl: urlPrefix,
headers: { 'Content-Type': ContentTypeEnum.JSON },
// 数据处理方式
transform,
// 配置项,下面的选项都可以在独立的接口请求中覆盖
requestOptions: {
// 默认将prefix 添加到url
joinPrefix: true,
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
isReturnNativeResponse: false,
// 不需要对返回数据进行处理
isTransformResponse: false,
// post请求的时候添加参数到url
joinParamsToUrl: false,
// 格式化提交参数时间
formatDate: true,
// 消息提示类型
errorMessageMode: 'none',
// 接口地址
apiUrl: '',
// 接口拼接地址
urlPrefix,
// 是否加入时间戳
joinTime: true,
// 忽略重复请求
ignoreCancelToken: true,
// 是否携带token
withToken: true,
},
withCredentials: false,
},
opt || {},
),
)
}
export const http = createAxios()
// 项目,多个不同 api 地址,直接在这里导出多个
// src/api ts 里面接口,就可以单独使用这个请求,
// import { httpTwo } from '@/utils/http/axios'
// export const httpTwo = createAxios({
// requestOptions: {
// apiUrl: 'http://localhost:9001',
// urlPrefix: 'api',
// },
// });