import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'
import Cookies from 'js-cookie'
import { ElMessage, ElLoading } from 'element-plus'
// 响应数据接口
interface ApiResponse<T = any> {
code: number
message: string
data: T
success: boolean
}
// 请求配置接口
interface RequestConfig {
baseURL?: string
timeout?: number
headers?: Record<string, string>
}
// 请求选项接口
interface RequestOptions {
showLoading?: boolean
showError?: boolean
loadingText?: string
noAuth?: boolean
}
// Token相关常量
const TOKEN_KEY = 'access_token'
const REFRESH_TOKEN_KEY = 'refresh_token'
class Request {
private instance: AxiosInstance
private loadingInstance: any = null
constructor(config: RequestConfig = {}) {
// 创建axios实例
this.instance = axios.create({
baseURL: config.baseURL || import.meta.env.VITE_API_BASE_URL || '/api',
timeout: config.timeout || 10000,
headers: {
'Content-Type': 'application/json',
...config.headers
}
})
// 设置请求拦截器
this.setupRequestInterceptor()
// 设置响应拦截器
this.setupResponseInterceptor()
}
// 获取token
private getToken(): string | undefined {
return Cookies.get(TOKEN_KEY)
}
// 设置token
private setToken(token: string): void {
Cookies.set(TOKEN_KEY, token, { expires: 7 }) // 7天过期
}
// 清除token
private clearToken(): void {
Cookies.remove(TOKEN_KEY)
Cookies.remove(REFRESH_TOKEN_KEY)
}
// 刷新token
private async refreshToken(): Promise<boolean> {
try {
const refreshToken = Cookies.get(REFRESH_TOKEN_KEY)
if (!refreshToken) {
return false
}
const response = await axios.post(`${this.instance.defaults.baseURL}/auth/refresh`, {
refreshToken
})
if (response.data.success && response.data.data.token) {
this.setToken(response.data.data.token)
return true
}
return false
} catch (error) {
console.error('刷新token失败:', error)
return false
}
}
// 设置请求拦截器
private setupRequestInterceptor(): void {
this.instance.interceptors.request.use(
(config) => {
// 判断是否需要token
// @ts-ignore
if (!(config.noAuth)) {
const token = this.getToken()
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
}
return config
},
(error) => Promise.reject(error)
)
}
// 设置响应拦截器
private setupResponseInterceptor(): void {
this.instance.interceptors.response.use(
(response: AxiosResponse<ApiResponse>) => {
const { data } = response
// 处理业务错误
if (!data.success) {
ElMessage.error(data.message || '请求失败')
return Promise.reject(new Error(data.message || '请求失败'))
}
return response
},
async (error: AxiosError) => {
const { response, config } = error
// 处理401未授权
if (response?.status === 401) {
const refreshed = await this.refreshToken()
if (!refreshed) {
this.clearToken()
// 跳转到登录页
window.location.href = '/login'
return Promise.reject(new Error('登录已过期,请重新登录'))
}
// 重新发起请求
if (config) {
const token = this.getToken()
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return this.instance.request(config)
}
}
// 处理其他HTTP错误
let errorMessage = '网络错误'
if (response?.status) {
switch (response.status) {
case 400:
errorMessage = '请求参数错误'
break
case 403:
errorMessage = '没有权限访问'
break
case 404:
errorMessage = '请求的资源不存在'
break
case 500:
errorMessage = '服务器内部错误'
break
case 502:
errorMessage = '网关错误'
break
case 503:
errorMessage = '服务不可用'
break
case 504:
errorMessage = '网关超时'
break
default:
errorMessage = `请求失败 (${response.status})`
}
}
ElMessage.error(errorMessage)
return Promise.reject(error)
}
)
}
// 显示加载状态
private showLoading(text: string = '加载中...'): void {
this.loadingInstance = ElLoading.service({
lock: true,
text,
background: 'rgba(0, 0, 0, 0.7)'
})
}
// 隐藏加载状态
private hideLoading(): void {
if (this.loadingInstance) {
this.loadingInstance.close()
this.loadingInstance = null
}
}
// 核心请求方法
async request<T = any>(config: AxiosRequestConfig & RequestOptions): Promise<ApiResponse<T>> {
const { showLoading = false, showError = true, loadingText = '加载中...', ...axiosConfig } = config
// 显示加载状态
if (showLoading) {
this.showLoading(loadingText)
}
try {
const response = await this.instance.request<ApiResponse<T>>(axiosConfig)
return response.data
} catch (error) {
// 如果showError为false,不显示错误消息
if (!showError && error instanceof Error) {
throw error
}
throw error
} finally {
// 隐藏加载状态
if (showLoading) {
this.hideLoading()
}
}
}
// GET请求
async get<T = any>(url: string, params?: any, options?: RequestOptions): Promise<ApiResponse<T>> {
return this.request<T>({
method: 'GET',
url,
params,
...options
})
}
// POST请求
async post<T = any>(url: string, data?: any, options?: RequestOptions): Promise<ApiResponse<T>> {
return this.request<T>({
method: 'POST',
url,
data,
...options
})
}
// PUT请求
async put<T = any>(url: string, data?: any, options?: RequestOptions): Promise<ApiResponse<T>> {
return this.request<T>({
method: 'PUT',
url,
data,
...options
})
}
// DELETE请求
async delete<T = any>(url: string, options?: RequestOptions): Promise<ApiResponse<T>> {
return this.request<T>({
method: 'DELETE',
url,
...options
})
}
// PATCH请求
async patch<T = any>(url: string, data?: any, options?: RequestOptions): Promise<ApiResponse<T>> {
return this.request<T>({
method: 'PATCH',
url,
data,
...options
})
}
// 文件上传
async upload<T = any>(url: string, file: File, options?: RequestOptions): Promise<ApiResponse<T>> {
const formData = new FormData()
formData.append('file', file)
return this.request<T>({
method: 'POST',
url,
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
},
...options
})
}
// 批量文件上传
async uploadMultiple<T = any>(url: string, files: File[], options?: RequestOptions): Promise<ApiResponse<T>> {
const formData = new FormData()
files.forEach((file, index) => {
formData.append(`files[${index}]`, file)
})
return this.request<T>({
method: 'POST',
url,
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
},
...options
})
}
// 下载文件
async download(url: string, filename?: string, options?: RequestOptions): Promise<void> {
try {
const response = await this.instance.request({
method: 'GET',
url,
responseType: 'blob',
...options
})
// 创建blob链接并下载
const blob = new Blob([response.data])
const downloadUrl = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = downloadUrl
link.download = filename || 'download'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(downloadUrl)
} catch (error) {
ElMessage.error('下载失败')
throw error
}
}
// 获取axios实例(用于特殊需求)
getInstance(): AxiosInstance {
return this.instance
}
// 设置默认请求头
setDefaultHeader(key: string, value: string): void {
this.instance.defaults.headers.common[key] = value
}
// 移除默认请求头
removeDefaultHeader(key: string): void {
delete this.instance.defaults.headers.common[key]
}
}
// 创建默认实例
const request = new Request()
// 导出实例和类
export default request
export { Request }
// 导出类型
export type { ApiResponse, RequestOptions, RequestConfig }