记录一下项目中的axios封装
将代码分成 5 个文件
1.axios.ts 代码的核心封装
import axios, {
AxiosInstance,
InternalAxiosRequestConfig,
AxiosResponse,
} from 'axios'
import type { Result, UploadFileParams, RequestOptions } from './type'
import { cloneDeep } from 'lodash-es'
import { isFunction } from '@/utils/is'
import { AxiosCanceler } from './axiosCancel'
export class VAxios {
private service: AxiosInstance
private options: any
constructor(config: InternalAxiosRequestConfig) {
this.options = config
this.service = axios.create(config)
this.setupInterceptors()
}
/**
* @description 获取请求拦截器实例
* @returns
*/
private getTransform() {
const { transform } = this.options
return transform
}
/**
* @description: 重新配置axios
*/
configAxios(config: any) {
if (!this.service) {
return
}
this.createAxios(config)
}
/**
* @description: 创建axios实例
*/
private createAxios(config: any): void {
this.service = axios.create(config)
}
/**
* @description 获取axios实例
* @returns
*/
getAxios(): AxiosInstance {
return this.service
}
/**
* @description: 设置通用header
*/
setHeader(headers: any): void {
if (!this.service) {
return
}
Object.assign(this.service.defaults.headers, headers)
}
/**
* @description: 请求方法
*/
request(
config: InternalAxiosRequestConfig,
options?: RequestOptions
): Promise {
let conf: InternalAxiosRequestConfig = cloneDeep(config)
const transform = this.getTransform()
const { requestOptions } = this.options
const opt: RequestOptions = Object.assign({}, requestOptions, options)
const { beforeRequestHook, requestCatch, transformRequestData } =
transform || {}
if (beforeRequestHook && isFunction(beforeRequestHook)) {
conf = beforeRequestHook(conf, opt)
}
if (import.meta.env.VITE_WEB_BASE_API) {
conf.url = import.meta.env.VITE_WEB_BASE_API + conf.url
} else {
conf.url = import.meta.env.VITE_SERVER + conf.url
}
//这里重新 赋值成最新的配置
// @ts-ignore
conf.requestOptions = opt
return new Promise((resolve, reject) => {
this.service
.request>(conf)
.then((res: AxiosResponse) => {
// 请求是否被取消
const isCancel = axios.isCancel(res)
if (
transformRequestData &&
isFunction(transformRequestData) &&
!isCancel
) {
try {
const ret = transformRequestData(res, opt)
resolve(ret)
} catch (err) {
reject(err || new Error('request error!'))
}
return
}
resolve(res as unknown as Promise)
})
.catch((e: Error) => {
if (requestCatch && isFunction(requestCatch)) {
reject(requestCatch(e))
return
}
reject(e)
})
})
}
/**
* @description: 文件上传
*/
uploadFile(
config: InternalAxiosRequestConfig,
params: UploadFileParams
) {
const formData = new window.FormData()
const customFilename = params.name || 'file'
if (params.filename) {
formData.append(customFilename, params.file, params.filename)
} else {
formData.append(customFilename, params.file)
}
if (params.data) {
Object.keys(params.data).forEach((key) => {
const value = params.data![key]
if (Array.isArray(value)) {
value.forEach((item) => {
formData.append(`${key}[]`, item)
})
return
}
formData.append(key, params.data![key])
})
}
return this.service.request({
method: 'POST',
data: formData,
...config,
})
}
/**
* @description: 拦截器配置
*/
setupInterceptors() {
const transform = this.getTransform()
if (!transform) {
return
}
const {
requestInterceptors,
requestInterceptorsCatch,
responseInterceptors,
responseInterceptorsCatch,
} = transform
const axiosCanceler = new AxiosCanceler()
this.service.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
// 请求之前拦截
// window.$loading.start()
const {
headers: { ignoreCancelToken },
} = config as any
const ignoreCancel =
ignoreCancelToken !== undefined
? ignoreCancelToken
: this.options.requestOptions?.ignoreCancelToken
!ignoreCancel && axiosCanceler.addPending(config)
if (requestInterceptors && isFunction(requestInterceptors)) {
config = requestInterceptors(config, this.options)
}
return config
}
)
// 请求拦截器错误捕获
requestInterceptorsCatch &&
isFunction(requestInterceptorsCatch) &&
this.service.interceptors.request.use(undefined, requestInterceptorsCatch)
this.service.interceptors.response.use((res: AxiosResponse) => {
// 请求后拦截
res && axiosCanceler.removePending(res.config)
if (responseInterceptors && isFunction(responseInterceptors)) {
res = responseInterceptors(res)
}
// window.$loading.finish()
return res
})
// 响应结果拦截器错误捕获
responseInterceptorsCatch &&
isFunction(responseInterceptorsCatch) &&
this.service.interceptors.response.use(
undefined,
responseInterceptorsCatch
)
}
}
window.$loading全局注入的loading,使用时用自己项目的loading,不用就注释调
isFunction判断是否是函数
/**
* @description 判断数据类型
* @param value
* @param type
* @returns
*/
export function isTypeof(value: any, type: string): boolean {
return typeof value === type
}
/**
* @description 判断是否是函数
* @param value
* @returns
*/
export function isFunction(value: any): boolean {
return isTypeof(value, 'function')
}
lodash-es是一个非常优秀的工具库
2.type.ts 类型定义
import { type AxiosRequestConfig } from 'axios'
import { AxiosTransform } from './axiosTransform'
export interface CreateAxiosOptions extends AxiosRequestConfig {
transform?: AxiosTransform
requestOptions?: RequestOptions
authenticationScheme?: string
}
// 上传文件
export interface UploadFileParams {
// 其他参数
data?: any
// 文件参数接口字段名
name?: string
// 文件
file: File | Blob
// 文件名称
filename?: string
[key: string]: any
}
export interface RequestOptions {
// 请求参数拼接到url
joinParamsToUrl?: boolean
// 格式化请求参数时间
formatDate?: boolean
// 是否显示提示信息
isShowMessage?: boolean
// 是否解析成JSON
isParseToJson?: boolean
// 成功的文本信息
successMessageText?: string
// 是否显示成功信息
isShowSuccessMessage?: boolean
// 是否显示失败信息
isShowErrorMessage?: boolean
// 错误的文本信息
errorMessageText?: string
// 是否加入url
joinPrefix?: boolean
// 接口地址, 不填则使用默认apiUrl
apiUrl?: string
// 请求拼接路径
urlPrefix?: string
// 错误消息提示类型
errorMessageMode?: 'none' | 'modal'
// 是否添加时间戳
joinTime?: boolean
// 不进行任何处理,直接返回
isTransformResponse?: boolean
// 是否返回原生响应头
isReturnNativeResponse?: boolean
//忽略重复请求
ignoreCancelToken?: boolean
// 是否携带token
withToken?: boolean
}
export interface Result {
code: number
msg: string
data?: T
}
3.axiosTransform.ts 是用于封装和扩展 Axios 请求的核心配置
import type { AxiosRequestConfig, AxiosResponse } from 'axios'
import type { RequestOptions, Result } from './type'
export interface CreateAxiosOptions extends AxiosRequestConfig {
authenticationScheme?: string
transform?: AxiosTransform
requestOptions?: RequestOptions
}
export abstract class AxiosTransform {
/**
* @description: 请求之前处理配置
* @description: Process configuration before request
*/
beforeRequestHook?: (
config: AxiosRequestConfig,
options: RequestOptions
) => AxiosRequestConfig
/**
* @description: 请求成功处理
*/
transformRequestData?: (
res: AxiosResponse,
options: RequestOptions
) => any
/**
* @description: 请求失败处理
*/
requestCatch?: (e: Error) => Promise
/**
* @description: 请求之前的拦截器
*/
requestInterceptors?: (
config: AxiosRequestConfig,
options: CreateAxiosOptions
) => AxiosRequestConfig
/**
* @description: 请求之后的拦截器
*/
responseInterceptors?: (res: AxiosResponse) => AxiosResponse
/**
* @description: 请求之前的拦截器错误处理
*/
requestInterceptorsCatch?: (error: Error) => void
/**
* @description: 请求之后的拦截器错误处理
*/
responseInterceptorsCatch?: (error: Error) => void
}
4.axiosCancel 取消请求
import axios, { type AxiosRequestConfig, type Canceler } from 'axios'
import qs from 'qs'
import { isFunction } from '@/utils/is/index'
// 声明一个 Map 用于存储每个请求的标识 和 取消函数
let pendingMap = new Map()
export const getPendingUrl = (config: AxiosRequestConfig) =>
[
config.method,
config.url,
qs.stringify(config.data),
qs.stringify(config.params),
].join('&')
export class AxiosCanceler {
/**
* 添加请求
* @param {Object} config
*/
addPending(config: AxiosRequestConfig) {
this.removePending(config)
const url = getPendingUrl(config)
config.cancelToken =
config.cancelToken ||
new axios.CancelToken((cancel) => {
if (!pendingMap.has(url)) {
// 如果 pending 中不存在当前请求,则添加进去
pendingMap.set(url, cancel)
}
})
}
/**
* @description: 清空所有pending
*/
removeAllPending() {
pendingMap.forEach((cancel) => {
cancel && isFunction(cancel) && cancel()
})
pendingMap.clear()
}
/**
* 移除请求
* @param {Object} config
*/
removePending(config: AxiosRequestConfig) {
const url = getPendingUrl(config)
if (pendingMap.has(url)) {
// 如果在 pending 中存在当前请求标识,需要取消当前请求,并且移除
const cancel = pendingMap.get(url)
cancel && cancel(url)
pendingMap.delete(url)
}
}
/**
* @description: 重置
*/
reset(): void {
pendingMap = new Map()
}
}
5.index.ts 暴露接口可自行根据项目进行更改,只需更改该文件即可,其他文件可以不动
import { VAxios } from './axios'
import { AxiosTransform } from './axiosTransform'
import { type AxiosResponse } from 'axios'
import { ContentTypeEnum, ResultEnum } from '@/enums/httpEnum'
import { useUserStore } from '@/stores/modules'
import { deepMerge } from '@/utils'
import type { RequestOptions, Result, CreateAxiosOptions } from './types'
import router from '@/router'
/**
* @description: 数据处理,方便区分多种处理方式
*/
const transform: AxiosTransform = {
/**
* @description: 处理请求数据
*/
transformRequestData: (res: AxiosResponse, options: RequestOptions) => {
const { isTransformResponse, isReturnNativeResponse } = options
const { code } = res.data
switch (code) {
case ResultEnum.SUCCESS:
break
case ResultEnum.TOKENLOSE:
window.$message.error('登录过期,请重新登录')
router.push('/login')
break
case ResultEnum.ACCOUNTLOSE:
window.$message.error('账号已封禁,请重新登录')
router.push('/login')
break
case ResultEnum.AUTHORITY:
window.$message.error('权限不足,请联系管理员')
router.push('/login')
break
}
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
if (isReturnNativeResponse) {
return res
}
// 不进行任何处理,直接返回
// 用于页面代码可能需要直接获取code,data,message这些信息时开启
if (!isTransformResponse) {
return res.data
}
if (!res) {
// return '[HTTP] Request has no return value';
throw new Error('请求出错,请稍候重试')
}
return res.data
},
// 请求之前处理config
beforeRequestHook: (config) => {
return config
},
/**
* @description: 请求拦截器处理 (config, options)
*/
requestInterceptors: (config, options) => {
const store = useUserStore()
const token = store.getToken
if (token && (config as any)?.requestOptions?.withToken !== false) {
// jwt token
;(config as any).headers.Authorization = options.authenticationScheme
? `${options.authenticationScheme} ${token}`
: token
}
return config
},
/**
* @description: 响应错误处理
*/
responseInterceptorsCatch: (error: any) => {
return Promise.reject(error?.data)
}
}
function createAxios(opt?: Partial) {
return new VAxios(
deepMerge(
{
timeout: 10 * 1000,
authenticationScheme: 'Bearer',
headers: { 'Content-Type': ContentTypeEnum.FORM_URLENCODED },
// 数据处理方式
transform,
// 配置项,下面的选项都可以在独立的接口请求中覆盖
requestOptions: {
// 默认将prefix 添加到url
joinPrefix: true,
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
isReturnNativeResponse: false,
// 需要对返回数据进行处理
isTransformResponse: true,
// post请求的时候添加参数到url
joinParamsToUrl: false,
// 格式化提交参数时间
formatDate: true,
// 消息提示类型
errorMessageMode: 'none',
// 是否加入时间戳
joinTime: true,
// 忽略重复请求
ignoreCancelToken: true,
// 是否携带token
withToken: true
},
withCredentials: false
},
opt || {}
)
)
}
export const http = createAxios()
6.apit.ts中使用示范
// 存放全局请求辅助api
import { http } from '@/utils/http'
import type { BasicResponseModel } from '@/types/api'
/**
* @description 获取所有角色
* @returns
*/
export const getAllRoleListApi = () => {
return http.request({
url: '/role/list',
method: 'get'
})
}
/**
* s@description 上传文件
* @param data
* @returns
*/
export const upLoadFileApi = (data: { file: File; data: { type: string } }) => {
console.log(data)
return http.uploadFile(
{
url: '/upload/picture'
},
{
data: data.data,
file: data.file
}
)
}
觉得有帮助的小伙伴点个赞支持下