Axios
ts
const LoginName = PageEnum.BASE_LOGIN_NAME
const LoginPath = PageEnum.BASE_LOGIN
const transform: AxiosTransform = {
/**
* @description: 处理请求数据
*/
transformRequestData: (res: AxiosResponse<Result>, options: RequestOptions) => {
const {
isShowMessage = true,
isShowErrorMessage,
isShowSuccessMessage,
successMessageText,
errorMessageText,
isTransformResponse,
isReturnNativeResponse,
} = options
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
if (isReturnNativeResponse)
return res
// 不进行任何处理,直接返回
// 用于页面代码可能需要直接获取code,data,message这些信息时开启
if (!isTransformResponse)
return res.data
const { data } = res
const $dialog = window.$dialog
const $message = window.$message
if (!data) {
// return '[HTTP] Request has no return value';
throw new Error('请求出错,请稍候重试')
}
// 这里 code,result,message为 后台统一的字段,需要修改为项目自己的接口返回格式
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 === LoginName)
return
// 到登录页
errorMsg = '登录超时,请重新登录!'
$dialog.warning({
title: '提示',
content: '登录身份已失效,请重新登录!',
positiveText: '确定',
// negativeText: '取消',
closable: false,
maskClosable: false,
onPositiveClick: () => {
storage.clear()
window.location.href = LoginPath
},
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) {
// jwt token
(config as Recordable).headers.Authorization = options.authenticationScheme
? `${options.authenticationScheme} ${token}`
: token
}
return config
},
/**
* @description: 响应错误处理
*/
responseInterceptorsCatch: (error: any) => {
const $dialog = window.$dialog
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)
},
}js
const a = {
timeout: 10 * 1000,
// authentication
authenticationScheme: '',
// 接口前缀
prefixUrl: urlPrefix,
headers: { 'Content-Type': ContentTypeEnum.JSON },
// 数据处理方式
transform,
// 配置项,下面的选项都可以在独立的接口请求中覆盖
requestOptions: {
// 默认将prefix 添加到url
joinPrefix: true,
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
isReturnNativeResponse: false,
// 需要对返回数据进行处理
isTransformResponse: true,
// post请求的时候添加参数到url
joinParamsToUrl: false,
// 格式化提交参数时间
formatDate: true,
// 消息提示类型
errorMessageMode: 'none',
// 接口地址
apiUrl: globSetting.apiUrl,
// 接口拼接地址
urlPrefix,
// 是否加入时间戳
joinTime: true,
// 忽略重复请求
ignoreCancelToken: true,
// 是否携带token
withToken: true,
},
withCredentials: false,
}创建实例
自定义实例默认参数
ts
import type { AxiosInstance } from 'axios'
import axios from 'axios'
// 设置创建的实例的默认参数
const instance: AxiosInstance = axios.create({
baseURL: '/api',
timeout: 30000
})
// 实例创建后修改默认值
instance.defaults.headers.common.Authorization = AUTH_TOKEN配置的优先级
- 请求的配置参数
- 实例的 default 参数
- axios 库中的默认参数
Token
sh
pnpm add js-cookie
pnpm add @types/js-cookie -Dts
import Cookies from 'js-cookie'
const TokenKey = 'Admin-Token'
export function getToken() {
return Cookies.get(TokenKey)
}
export function setToken(token: string) {
return Cookies.set(TokenKey, token)
}
export function removeToken() {
return Cookies.remove(TokenKey)
}方法封装
ts
export function useGet<R = any, T = any>(url: string, params?: T, config?: AxiosRequestConfig & RequestConfigExtra): Promise<ResponseBody<R>> {
const options = {
url,
params,
method: RequestEnum.GET,
...config,
}
return instancePromise<R, T>(options)
}
export function usePost<R = any, T = any>(url: string, data?: T, config?: AxiosRequestConfig & RequestConfigExtra): Promise<ResponseBody<R>> {
const options = {
url,
data,
method: RequestEnum.POST,
...config,
}
return instancePromise<R, T>(options)
}
export function usePut<R = any, T = any>(url: string, data?: T, config?: AxiosRequestConfig & RequestConfigExtra): Promise<ResponseBody<R>> {
const options = {
url,
data,
method: RequestEnum.PUT,
...config,
}
return instancePromise<R, T>(options)
}
export function useDelete<R = any, T = any>(url: string, data?: T, config?: AxiosRequestConfig & RequestConfigExtra): Promise<ResponseBody<R>> {
const options = {
url,
data,
method: RequestEnum.DELETE,
...config,
}
return instancePromise<R, T>(options)
}请求拦截器
请求发送前添加 token
ts
import Axios, { AxiosRequestConfig } from 'axios'
import storage from '@/utils/storage'
function authRequestInterceptor(config: AxiosRequestConfig) {
const token = storage.getToken()
if (token)
config.headers.authorization = `${token}`
config.headers.Accept = 'application/json'
return config
}
export const axios = Axios.create({})
axios.interceptors.request.use(authRequestInterceptor)响应拦截器
ts
import Axios, { AxiosRequestConfig } from 'axios'
import { API_URL } from '@/config'
import { useNotificationStore } from '@/stores/notifications'
export const axios = Axios.create({
baseURL: API_URL,
})
axios.interceptors.response.use(
(response) => {
return response.data
},
(error) => {
const message = error.response?.data?.message || error.message
useNotificationStore.getState().addNotification({
type: 'error',
title: 'Error',
message,
})
return Promise.reject(error)
},
)Adapter
Axios 可以在浏览器和 Node.js 中使用,浏览器中创建 XMLHttpRequests, node.js 创建 http 请求
ts
function getDefaultAdapter() {
let adapter
if (typeof XMLHttpRequest !== 'undefined') {
// For browsers use XHR adapter
adapter = require('../adapters/xhr')
}
else if (
typeof process !== 'undefined'
&& Object.prototype.toString.call(process) === '[object process]'
) {
// For node use HTTP adapter
adapter = require('../adapters/http')
}
return adapter
}允许指定已处理请求
ts
const settle = require('./../core/settle')
module.exports = function myAdapter(config) {
// At this point:
// - config has been merged with defaults
// - request transformers have already run
// - request interceptors have already run
// Make the request using config provided
// Upon response settle the Promise
return new Promise((resolve, reject) => {
const response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config,
request,
}
settle(resolve, reject, response)
// From here:
// - response transformers will run
// - response interceptors will run
})
}ts
instance.defaults.adapter = (config: AxiosRequestConfig) => {
return new Promise((resolve, reject) => {
const getUrl = () => {
const fullPath = buildFullPath(config.baseURL, config.url)
return buildURL(fullPath, config.params, config.paramsSerializer)
}
const method = (config.method || 'get').toUpperCase()
const requestTask = uni.request({
method: method as uniapp.RequestOptions['method'],
url: getUrl(),
header: config.headers,
data: config.data,
responseType: 'text',
dataType: 'json',
complete: function complete(response) {
const {
data,
statusCode,
errMsg,
header,
} = response as RequestCompleteCallbackResult
const axiosResponse: AxiosResponse = {
data,
status: statusCode,
statusText: errMsg,
headers: header,
config,
request: requestTask,
}
settle(resolve, reject, axiosResponse)
},
})
})
}取消请求
XSRF
文件上传
form-data
ts
export interface UploadFileParams {
// Other parameters
data?: Recordable
// File parameter interface field name
name?: string
// file name
file: File | Blob
// file name
filename?: string
[key: string]: any
}文件下载
ts
async function downloadTo(url: string, filepath: string, { retryTooManyRequests }: { retryTooManyRequests: boolean }): Promise<void> {
const writer = fs.createWriteStream(filepath)
const response = await axios({
url,
method: 'GET',
validateStatus: (status) => {
if (status >= 200 && status < 300)
return true
else if (retryTooManyRequests && status === 429)
return true
else
return false
},
responseType: 'stream',
})
if (response.status === 429) {
const retryAfter = response.headers['retry-after']
if (!retryAfter) {
throw new Error(`${url}: 429 without retry-after header`)
}
else {
debug(`${url}: 429, retry after ${retryAfter} seconds`)
await sleep(retryAfter)
return await downloadTo(url, filepath, { retryTooManyRequests })
}
}
response.data.pipe(writer)
return new Promise((resolve, reject) => {
writer.on('finish', resolve)
writer.on('error', reject)
})
}错误处理
ts
export interface AxiosError<T = any, D = any> extends Error {
config: AxiosRequestConfig<D>
code?: string
request?: any
response?: AxiosResponse<T, D>
isAxiosError: boolean
toJSON: () => object
}