一、安装 Axios 和 qs
pnpm add axios
pnpm add qs
pnpm add @types/qs --save-dev
二、创建 src/utils/request.ts 文件
// src/utils/request.ts
import type { AxiosRequestConfig, AxiosResponse } from 'axios'
import { message, Modal } from 'ant-design-vue'
import axios, { CanceledError } from 'axios'
import qs from 'qs'
import { ResCodeEnum } from '@/api/enum/common.enum'
import { useUserStore } from '@/store/modules/user'
export interface RequestOptions extends AxiosRequestConfig {
/** 是否直接将数据从响应中提取出,例如直接返回 res.data,而忽略 res.code 等信息 */
isReturnResult?: boolean
/** 请求成功时提示信息 */
successMsg?: string
/** 请求失败时提示信息 */
errorMsg?: string
/** 成功时,是否显示后端返回的成功信息 */
showSuccessMsg?: boolean
/** 失败时,是否显示后端返回的失败信息 */
showErrorMsg?: boolean
/**
* 请求类型; 默认会配置成 json
* 当是 form 时,请求头中的 'Content-Type' 会配置成 'multipart/form-data'
* 当是 json 时,请求头中的 'Content-Type' 会配置成 'application/json'
*/
requestType?: 'json' | 'form'
}
const UNKNOWN_ERROR = '未知错误,请重试'
// 创建一个 axios 实例
const service = axios.create({
// 真实请求的路径前缀
baseURL: import.meta.env.VITE_BASE_API_URL,
// 指定请求超时的毫秒数(0 表示无超时时间),如果请求超过 10000 毫秒,请求将被中断
timeout: 10000,
/**
* 负责将 params 序列化的函数
*/
paramsSerializer(params) {
// qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'brackets' }); // 输出: "a[]=b&a[]=c"
// qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'indices' }); // 输出: "a[0]=b&a[1]=c"
// qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'repeat' }); // 输出: "a=b&a=c"
// qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'comma' }); // 输出: "a=b,c"
return qs.stringify(params, { arrayFormat: 'brackets' })
},
})
// 添加请求拦截器
service.interceptors.request.use(
(config) => {
const userStore = useUserStore()
const token = userStore.token
if (token && config.headers) {
// 向请求头中添加 token 信息
Object.defineProperty(config.headers, 'Authorization', `Bearer ${token}`)
}
return config
},
(error) => {
Promise.reject(error)
},
)
// 添加响应拦截器
service.interceptors.response.use(
(response: AxiosResponse<{ code: number, [key: string]: any }>) => {
const res = response.data
if (res.code !== ResCodeEnum.SUCCESS) {
message.error(res.message || UNKNOWN_ERROR)
// 未登录 or token过期
if ([ResCodeEnum.NOT_LOGIN, ResCodeEnum.TOKEN_EXPIRATION].includes(res.code)) {
Modal.confirm({
title: '警告',
content: res.message || '账号异常,您可以取消停留在该页上,或重新登录',
okText: '重新登录',
cancelText: '取消',
onOk: () => {
localStorage.clear()
window.location.reload()
},
})
}
// throw other
const error = new Error(res.message || UNKNOWN_ERROR) as Error & { code: any }
error.code = res.code
return Promise.reject(error)
}
else {
return response
}
},
(error) => {
if (!(error instanceof CanceledError)) {
// 处理 422 或者 500 的错误异常提示
const errMsg = error?.response?.data?.message ?? UNKNOWN_ERROR
message.error({ content: errMsg, key: errMsg })
error.message = errMsg
}
return Promise.reject(error)
},
)
export function request<T = any>(url: string, config: RequestOptions): Promise<T>
export function request<T = any>(config: RequestOptions): Promise<T>
export async function request<T = any>(_url: string | RequestOptions, _config: RequestOptions = {}): Promise<T> {
const url = typeof _url === 'string' ? _url : _url.url
const config = typeof _url === 'string' ? _config : _url
try {
// 兼容 from data 文件上传的情况
const { requestType = 'json', ...rest } = config
const response = (await service.request({
url,
...rest,
headers: {
...rest.headers,
...{ 'Content-Type': requestType === 'form' ? 'multipart/form-data' : 'application/json' },
},
})) as AxiosResponse<T>
const { data } = response
const code = (data as any)?.code
const message = (data as any)?.message
const hasSuccess = data && Reflect.has(data, 'code') && code === ResCodeEnum.SUCCESS
if (hasSuccess) {
const { successMsg, showSuccessMsg } = config
if (successMsg) {
message.success(successMsg)
}
else if (showSuccessMsg && message) {
message.success(message)
}
}
return data
}
catch (error: any) {
return Promise.reject(error)
}
}