362 lines
13 KiB
TypeScript
362 lines
13 KiB
TypeScript
import type { AxiosRequestConfig, Method } from 'axios'
|
||
import axios from 'axios'
|
||
import { ElLoading, ElNotification, type LoadingOptions } from 'element-plus'
|
||
import { refreshToken } from '/@/api/common'
|
||
import { i18n } from '/@/lang/index'
|
||
import router from '/@/router/index'
|
||
import adminBaseRoute from '/@/router/static/adminBase'
|
||
import { useAdminInfo } from '/@/stores/adminInfo'
|
||
import { useConfig } from '/@/stores/config'
|
||
import { useUserInfo } from '/@/stores/userInfo'
|
||
import { encrypt_aes, decrypt_aes, generateRandomNumber } from './crypto'
|
||
|
||
window.requests = []
|
||
window.tokenRefreshing = false
|
||
const pendingMap = new Map()
|
||
const loadingInstance: LoadingInstance = {
|
||
target: null,
|
||
count: 0,
|
||
}
|
||
|
||
/**
|
||
* 根据运行环境获取基础请求URL
|
||
*/
|
||
export const getUrl = (): string => {
|
||
const value: string = import.meta.env.VITE_AXIOS_BASE_URL as string
|
||
return value == 'getCurrentDomain' ? window.location.protocol + '//' + window.location.host : value
|
||
}
|
||
|
||
/**
|
||
* 根据运行环境获取基础请求URL的端口
|
||
*/
|
||
export const getUrlPort = (): string => {
|
||
const url = getUrl()
|
||
return new URL(url).port
|
||
}
|
||
|
||
/**
|
||
* 创建`Axios`
|
||
* 默认开启`reductDataFormat(简洁响应)`,返回类型为`ApiPromise`
|
||
* 关闭`reductDataFormat`,返回类型则为`AxiosPromise`
|
||
*/
|
||
function createAxios<Data = any, T = ApiPromise<Data>>(axiosConfig: AxiosRequestConfig, options: Options = {}, loading: LoadingOptions = {}): T {
|
||
const config = useConfig()
|
||
const adminInfo = useAdminInfo()
|
||
const userInfo = useUserInfo()
|
||
|
||
const Axios = axios.create({
|
||
baseURL: getUrl(),
|
||
timeout: 1000 * 10,
|
||
headers: {
|
||
'think-lang': config.lang.defaultLang,
|
||
server: true,
|
||
},
|
||
responseType: 'json',
|
||
})
|
||
|
||
// // 自定义后台入口
|
||
// if (adminBaseRoute.path != '/admin' && isAdminApp() && /^\/admin\//.test(axiosConfig.url!)) {
|
||
// axiosConfig.url = axiosConfig.url!.replace(/^\/admin\//, adminBaseRoute.path + '.php/')
|
||
// }
|
||
// alert(axiosConfig.data)
|
||
|
||
// 合并默认请求选项
|
||
options = Object.assign(
|
||
{
|
||
CancelDuplicateRequest: true, // 是否开启取消重复请求, 默认为 true
|
||
loading: false, // 是否开启loading层效果, 默认为false
|
||
reductDataFormat: true, // 是否开启简洁的数据结构响应, 默认为true
|
||
showErrorMessage: true, // 是否开启接口错误信息展示,默认为true
|
||
showCodeMessage: true, // 是否开启code不为1时的信息提示, 默认为true
|
||
showSuccessMessage: false, // 是否开启code为1时的信息提示, 默认为false
|
||
anotherToken: '', // 当前请求使用另外的用户token
|
||
},
|
||
options
|
||
)
|
||
|
||
// 请求拦截
|
||
Axios.interceptors.request.use(
|
||
(config) => {
|
||
const v = generateRandomNumber(16)
|
||
removePending(config)
|
||
options.CancelDuplicateRequest && addPending(config)
|
||
// 创建loading实例
|
||
if (options.loading) {
|
||
loadingInstance.count++
|
||
if (loadingInstance.count === 1) {
|
||
loadingInstance.target = ElLoading.service(loading)
|
||
}
|
||
}
|
||
|
||
if (config.method === 'post' && config.data) {
|
||
// 对data进行加密
|
||
const formData = new FormData()
|
||
formData.append('param', encrypt_aes(config.data, v))
|
||
config.data = formData
|
||
} else if (config.method === 'get' && config.params) {
|
||
// 对params进行加密
|
||
const formData = new FormData()
|
||
formData.append('param', encrypt_aes(config.params, v))
|
||
config.params = formData
|
||
}
|
||
|
||
// 自动携带token
|
||
if (config.headers) {
|
||
config.headers.v = v
|
||
const token = adminInfo.getToken()
|
||
if (token) (config.headers as anyObj).token = encrypt_aes(token, v)
|
||
}
|
||
|
||
return config
|
||
},
|
||
(error) => {
|
||
return Promise.reject(error)
|
||
}
|
||
)
|
||
|
||
// 响应拦截
|
||
Axios.interceptors.response.use(
|
||
(response) => {
|
||
removePending(response.config)
|
||
options.loading && closeLoading(options) // 关闭loading
|
||
|
||
if (response.config.responseType == 'json') {
|
||
if (response.data && response.data.code !== 200) {
|
||
// if (response.data.code == 409) {
|
||
// if (!window.tokenRefreshing) {
|
||
// window.tokenRefreshing = true
|
||
// return refreshToken()
|
||
// .then((res) => {
|
||
// adminInfo.setToken(res.data.token, 'auth')
|
||
// response.headers.token = `${res.data.token}`
|
||
// window.requests.forEach((cb) => cb(res.data.token, 'admin-refresh'))
|
||
// window.requests = []
|
||
// return Axios(response.config)
|
||
// })
|
||
// .catch((err) => {
|
||
// adminInfo.removeToken()
|
||
// router.push({ name: 'adminLogin' })
|
||
// return Promise.reject(err)
|
||
// })
|
||
// .finally(() => {
|
||
// window.tokenRefreshing = false
|
||
// })
|
||
// } else {
|
||
// return new Promise((resolve) => {
|
||
// // 用函数形式将 resolve 存入,等待刷新后再执行
|
||
// window.requests.push((token: string, type: string) => {
|
||
// response.headers.token = `${token}`
|
||
// resolve(Axios(response.config))
|
||
// })
|
||
// })
|
||
// }
|
||
// }
|
||
// if (options.showCodeMessage) {
|
||
// ElNotification({
|
||
// type: 'error',
|
||
// message: response.data.msg,
|
||
// })
|
||
// }
|
||
// // 自动跳转到路由name或path
|
||
// if (response.data.code == 302) {
|
||
// router.push({ path: response.data.data.routePath ?? '', name: response.data.data.routeName ?? '' })
|
||
// }
|
||
if (response.data.code == 400) {
|
||
let routerPath = adminBaseRoute.path
|
||
userInfo.removeToken()
|
||
routerPath += '/login'
|
||
router.push({ path: routerPath })
|
||
}
|
||
if (response.data.code == 401) {
|
||
let routerPath = adminBaseRoute.path
|
||
userInfo.removeToken()
|
||
routerPath += '/login'
|
||
router.push({ path: routerPath })
|
||
}
|
||
// code不等于1, 页面then内的具体逻辑就不执行了
|
||
return Promise.reject(response.data)
|
||
} else if (options.showSuccessMessage && response.data && response.data.code == 1) {
|
||
ElNotification({
|
||
message: response.data.msg ? response.data.msg : i18n.global.t('axios.Operation successful'),
|
||
type: 'success',
|
||
})
|
||
}
|
||
}
|
||
|
||
return options.reductDataFormat ? response.data : response
|
||
},
|
||
(error) => {
|
||
error.config && removePending(error.config)
|
||
options.loading && closeLoading(options) // 关闭loading
|
||
options.showErrorMessage && httpErrorStatusHandle(error) // 处理错误状态码
|
||
return Promise.reject(error) // 错误继续返回给到具体页面
|
||
}
|
||
)
|
||
return Axios(axiosConfig) as T
|
||
}
|
||
|
||
export default createAxios
|
||
|
||
/**
|
||
* 处理异常
|
||
* @param {*} error
|
||
*/
|
||
function httpErrorStatusHandle(error: any) {
|
||
// 处理被取消的请求
|
||
if (axios.isCancel(error)) return console.error(i18n.global.t('axios.Automatic cancellation due to duplicate request:') + error.message)
|
||
let message = ''
|
||
if (error && error.response) {
|
||
switch (error.response.status) {
|
||
case 302:
|
||
message = i18n.global.t('axios.Interface redirected!')
|
||
break
|
||
case 400:
|
||
message = i18n.global.t('axios.Incorrect parameter!')
|
||
break
|
||
case 401:
|
||
message = i18n.global.t('axios.You do not have permission to operate!')
|
||
break
|
||
case 403:
|
||
message = i18n.global.t('axios.You do not have permission to operate!')
|
||
break
|
||
case 404:
|
||
message = i18n.global.t('axios.Error requesting address:') + error.response.config.url
|
||
break
|
||
case 408:
|
||
message = i18n.global.t('axios.Request timed out!')
|
||
break
|
||
case 409:
|
||
message = i18n.global.t('axios.The same data already exists in the system!')
|
||
break
|
||
case 500:
|
||
message = i18n.global.t('axios.Server internal error!')
|
||
break
|
||
case 501:
|
||
message = i18n.global.t('axios.Service not implemented!')
|
||
break
|
||
case 502:
|
||
message = i18n.global.t('axios.Gateway error!')
|
||
break
|
||
case 503:
|
||
message = i18n.global.t('axios.Service unavailable!')
|
||
break
|
||
case 504:
|
||
message = i18n.global.t('axios.The service is temporarily unavailable Please try again later!')
|
||
break
|
||
case 505:
|
||
message = i18n.global.t('axios.HTTP version is not supported!')
|
||
break
|
||
default:
|
||
message = i18n.global.t('axios.Abnormal problem, please contact the website administrator!')
|
||
break
|
||
}
|
||
}
|
||
if (error.message.includes('timeout')) message = i18n.global.t('axios.Network request timeout!')
|
||
if (error.message.includes('Network'))
|
||
message = window.navigator.onLine ? i18n.global.t('axios.Server exception!') : i18n.global.t('axios.You are disconnected!')
|
||
|
||
ElNotification({
|
||
type: 'error',
|
||
message,
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 关闭Loading层实例
|
||
*/
|
||
function closeLoading(options: Options) {
|
||
if (options.loading && loadingInstance.count > 0) loadingInstance.count--
|
||
if (loadingInstance.count === 0) {
|
||
loadingInstance.target.close()
|
||
loadingInstance.target = null
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 储存每个请求的唯一cancel回调, 以此为标识
|
||
*/
|
||
function addPending(config: AxiosRequestConfig) {
|
||
const pendingKey = getPendingKey(config)
|
||
config.cancelToken =
|
||
config.cancelToken ||
|
||
new axios.CancelToken((cancel) => {
|
||
if (!pendingMap.has(pendingKey)) {
|
||
pendingMap.set(pendingKey, cancel)
|
||
}
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 删除重复的请求
|
||
*/
|
||
function removePending(config: AxiosRequestConfig) {
|
||
const pendingKey = getPendingKey(config)
|
||
if (pendingMap.has(pendingKey)) {
|
||
const cancelToken = pendingMap.get(pendingKey)
|
||
cancelToken(pendingKey)
|
||
pendingMap.delete(pendingKey)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 生成每个请求的唯一key
|
||
*/
|
||
function getPendingKey(config: AxiosRequestConfig) {
|
||
let { data } = config
|
||
const { url, method, params, headers } = config
|
||
if (typeof data === 'string') {
|
||
try {
|
||
data = JSON.parse(data)
|
||
} catch (e) {}
|
||
} // response里面返回的config.data是个字符串对象
|
||
return [
|
||
url,
|
||
method,
|
||
headers && (headers as anyObj).token ? (headers as anyObj).token : '',
|
||
headers && (headers as anyObj)['token'] ? (headers as anyObj)['token'] : '',
|
||
JSON.stringify(params),
|
||
JSON.stringify(data),
|
||
].join('&')
|
||
}
|
||
|
||
/**
|
||
* 根据请求方法组装请求数据/参数
|
||
*/
|
||
export function requestPayload(method: Method, data: anyObj) {
|
||
if (method == 'GET') {
|
||
return {
|
||
params: data,
|
||
}
|
||
} else if (method == 'POST') {
|
||
return {
|
||
data: data,
|
||
}
|
||
}
|
||
}
|
||
|
||
interface LoadingInstance {
|
||
target: any
|
||
count: number
|
||
}
|
||
interface Options {
|
||
// 是否开启取消重复请求, 默认为 true
|
||
CancelDuplicateRequest?: boolean
|
||
// 是否开启loading层效果, 默认为false
|
||
loading?: boolean
|
||
// 是否开启简洁的数据结构响应, 默认为true
|
||
reductDataFormat?: boolean
|
||
// 是否开启接口错误信息展示,默认为true
|
||
showErrorMessage?: boolean
|
||
// 是否开启code不为0时的信息提示, 默认为true
|
||
showCodeMessage?: boolean
|
||
// 是否开启code为0时的信息提示, 默认为false
|
||
showSuccessMessage?: boolean
|
||
// 当前请求使用另外的用户token
|
||
anotherToken?: string
|
||
}
|
||
|
||
/*
|
||
* 感谢掘金@橙某人提供的思路和分享
|
||
* 本axios封装详细解释请参考:https://juejin.cn/post/6968630178163458084?share_token=7831c9e0-bea0-469e-8028-b587e13681a8#heading-27
|
||
*/
|