185 lines
5.3 KiB
TypeScript
185 lines
5.3 KiB
TypeScript
import axios from 'axios'
|
||
import type { AxiosResponse, AxiosError, InternalAxiosRequestConfig } from 'axios'
|
||
import type { ApiResponse } from '@/api/types'
|
||
import { ElLoading, ElNotification } from 'element-plus'
|
||
import { getStorage, setStorage, removeStorage } from '@/utils/storage'
|
||
import router from '../router'
|
||
|
||
// 定义loading实例类型,用于控制loading的显示和关闭
|
||
let loadingInstance: ReturnType<typeof ElLoading.service> | null = null
|
||
// 记录当前请求数量,防止多个请求重复显示/关闭loading
|
||
let requestCount = 0
|
||
|
||
// 显示loading的方法
|
||
const showLoading = () => {
|
||
if (requestCount === 0 && !loadingInstance) {
|
||
loadingInstance = ElLoading.service({
|
||
lock: true, // 锁定屏幕滚动
|
||
fullscreen: true, // 全屏显示
|
||
})
|
||
}
|
||
requestCount++
|
||
}
|
||
|
||
// 关闭loading的方法
|
||
const hideLoading = () => {
|
||
requestCount--
|
||
if (requestCount <= 0) {
|
||
if (loadingInstance) {
|
||
loadingInstance.close()
|
||
loadingInstance = null
|
||
}
|
||
requestCount = 0 // 重置计数,防止负数
|
||
}
|
||
}
|
||
|
||
// 1. 环境自动识别 + 配置读取(核心修改:适配Vite环境变量)
|
||
const env = import.meta.env
|
||
const baseURL = env.VITE_API_BASE_URL || ''
|
||
const timeout = Number(env.VITE_REQUEST_TIMEOUT) || 10000
|
||
|
||
// 2. 创建axios实例
|
||
const service = axios.create({
|
||
baseURL,
|
||
timeout,
|
||
headers: { 'Content-Type': 'application/json;charset=utf-8' },
|
||
})
|
||
|
||
// 3. 请求拦截器:直接使用 axios 内置类型
|
||
const requestInterceptor = (config: InternalAxiosRequestConfig) => {
|
||
console.log('请求配置', config)
|
||
|
||
// 显示loading(可通过config配置项控制是否显示,比如某些请求不需要loading)
|
||
if (config.headers?.showLoading !== false) {
|
||
showLoading()
|
||
}
|
||
// 添加token到请求头
|
||
const token = getStorage('token')
|
||
if (token) {
|
||
config.headers.token = token
|
||
}
|
||
return config
|
||
}
|
||
|
||
const requestErrorInterceptor = (error: AxiosError) => {
|
||
// 请求拦截器出错时也要关闭loading
|
||
ElNotification({
|
||
message: error.message || '未知的请求错误!',
|
||
type: 'error',
|
||
duration: 2000,
|
||
})
|
||
hideLoading()
|
||
|
||
return Promise.reject(error)
|
||
}
|
||
|
||
service.interceptors.request.use(requestInterceptor, requestErrorInterceptor)
|
||
|
||
// 4. 响应拦截器
|
||
const responseInterceptor = <T = unknown>(response: AxiosResponse<ApiResponse<T>>) => {
|
||
//关闭loading(无论成功失败都要关)
|
||
hideLoading()
|
||
if (response.headers['new_token'] !== undefined) {
|
||
setStorage('token', response.headers['new_token'] as string)
|
||
}
|
||
console.log('响应response', response)
|
||
const { data } = response
|
||
console.log('响应数据data', data)
|
||
// 判断业务状态码
|
||
if (data.code == 200 || data.code == 0) {
|
||
// 仅当业务成功时,才弹成功提示
|
||
ElNotification({
|
||
message: data.msg || '操作成功!',
|
||
type: 'success',
|
||
duration: 2000,
|
||
customClass: 'global-notification',
|
||
})
|
||
return data.data
|
||
} else {
|
||
switch (data.code) {
|
||
case 1:
|
||
if (!data.msg) {
|
||
data.msg = '未知的错误!'
|
||
}
|
||
break
|
||
case -101:
|
||
data.msg = 'token错误!'
|
||
removeStorage('token')
|
||
router.push({ name: 'Login' })
|
||
break
|
||
default:
|
||
break;
|
||
}
|
||
|
||
// 其他业务失败:弹错误提示并拒绝Promise
|
||
ElNotification({
|
||
message: data.msg || '未知的错误!',
|
||
type: 'error',
|
||
duration: 2000,
|
||
customClass: 'global-notification',
|
||
})
|
||
return Promise.reject(data)
|
||
}
|
||
}
|
||
|
||
const responseErrorInterceptor = (error: AxiosError) => {
|
||
// 响应失败,关闭loading
|
||
hideLoading()
|
||
|
||
// 分类处理不同类型的网络错误
|
||
let errMsg = ''
|
||
if (error.message === 'Network Error') {
|
||
errMsg = '网络异常,请检查您的网络连接!'
|
||
} else if (error.code === 'ECONNABORTED') {
|
||
errMsg = `请求超时!请求耗时超过 ${timeout / 1000} 秒`
|
||
} else if (error.response) {
|
||
// 有响应状态码的错误
|
||
const status = error.response.status
|
||
switch (status) {
|
||
case 401:
|
||
errMsg = '登录状态过期,请重新登录!'
|
||
// 可在此处添加跳转到登录页的逻辑
|
||
break
|
||
case 403:
|
||
errMsg = '您没有权限访问该资源!'
|
||
break
|
||
case 404:
|
||
errMsg = '请求的资源不存在!'
|
||
break
|
||
case 500:
|
||
errMsg = '服务器内部错误,请稍后重试!'
|
||
break
|
||
default:
|
||
errMsg = (error.response?.data as ApiResponse)?.msg || `请求失败:${status}`
|
||
}
|
||
} else {
|
||
errMsg = error.message || '服务器错误'
|
||
}
|
||
|
||
ElNotification({
|
||
message: errMsg,
|
||
type: 'error',
|
||
duration: 2000,
|
||
customClass: 'global-notification',
|
||
})
|
||
return Promise.reject(error)
|
||
}
|
||
|
||
service.interceptors.response.use(responseInterceptor, responseErrorInterceptor)
|
||
|
||
// 5. 封装通用请求方法
|
||
export const request = {
|
||
get<T = unknown>(url: string, params?: unknown, config?: InternalAxiosRequestConfig): Promise<T> {
|
||
return service.get<ApiResponse<T>>(url, { params, ...config }).then((res) => {
|
||
return res as T
|
||
})
|
||
},
|
||
post<T = unknown>(url: string, data?: unknown, config?: InternalAxiosRequestConfig): Promise<T> {
|
||
return service.post<ApiResponse<T>>(url, data, config).then((res) => {
|
||
return res as T
|
||
})
|
||
},
|
||
}
|
||
|
||
export default service
|