297 lines
6.7 KiB
JavaScript
297 lines
6.7 KiB
JavaScript
/**
|
||
* Axios请求工具
|
||
* 统一处理HTTP请求和响应
|
||
*/
|
||
|
||
import axios from 'axios'
|
||
import { ElMessage, ElLoading } from 'element-plus'
|
||
import { getToken, removeToken } from '@/utils/auth'
|
||
import router from '@/router'
|
||
import globalErrorHandler, { ErrorTypes } from './errorHandler'
|
||
|
||
// 创建axios实例
|
||
const service = axios.create({
|
||
baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
|
||
timeout: 15000, // 请求超时时间
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
}
|
||
})
|
||
|
||
// 存储当前请求的loading实例
|
||
let loadingInstance = null
|
||
// 存储当前请求数量
|
||
let requestCount = 0
|
||
|
||
/**
|
||
* 显示loading
|
||
*/
|
||
const showLoading = () => {
|
||
if (requestCount === 0) {
|
||
loadingInstance = ElLoading.service({
|
||
lock: true,
|
||
text: '加载中...',
|
||
background: 'rgba(0, 0, 0, 0.7)'
|
||
})
|
||
}
|
||
requestCount++
|
||
}
|
||
|
||
/**
|
||
* 隐藏loading
|
||
*/
|
||
const hideLoading = () => {
|
||
requestCount--
|
||
if (requestCount <= 0) {
|
||
requestCount = 0
|
||
if (loadingInstance) {
|
||
loadingInstance.close()
|
||
loadingInstance = null
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 请求拦截器
|
||
*/
|
||
service.interceptors.request.use(
|
||
config => {
|
||
// 显示loading(可选)
|
||
if (config.showLoading !== false) {
|
||
showLoading()
|
||
}
|
||
|
||
// 添加token到请求头
|
||
const token = getToken()
|
||
if (token) {
|
||
config.headers.Authorization = `Bearer ${token}`
|
||
}
|
||
|
||
// 添加请求ID用于追踪
|
||
config.headers['X-Request-ID'] = generateRequestId()
|
||
|
||
// 添加时间戳防止缓存
|
||
if (config.method === 'get') {
|
||
config.params = {
|
||
...config.params,
|
||
_t: Date.now()
|
||
}
|
||
}
|
||
|
||
return config
|
||
},
|
||
error => {
|
||
// 请求错误处理
|
||
hideLoading()
|
||
globalErrorHandler.handleError(error, {
|
||
type: ErrorTypes.NETWORK,
|
||
context: 'request_interceptor',
|
||
showMessage: true
|
||
})
|
||
return Promise.reject(error)
|
||
}
|
||
)
|
||
|
||
/**
|
||
* 响应拦截器
|
||
*/
|
||
service.interceptors.response.use(
|
||
response => {
|
||
// 隐藏loading
|
||
hideLoading()
|
||
|
||
// 获取响应数据
|
||
const res = response.data
|
||
|
||
// 根据后端约定的响应码处理
|
||
if (response.status === 200) {
|
||
// 如果响应中包含code字段,根据code判断
|
||
if (res.code !== undefined) {
|
||
// 成功响应
|
||
if (res.code === 200 || res.code === 0) {
|
||
return res.data || res
|
||
}
|
||
// token过期或无效
|
||
else if (res.code === 401) {
|
||
ElMessage.error('登录已过期,请重新登录')
|
||
removeToken()
|
||
router.push('/login')
|
||
return Promise.reject(new Error('登录已过期'))
|
||
}
|
||
// 权限不足
|
||
else if (res.code === 403) {
|
||
ElMessage.error('权限不足')
|
||
return Promise.reject(new Error('权限不足'))
|
||
}
|
||
// 其他业务错误
|
||
else {
|
||
const message = res.message || '服务器响应错误'
|
||
ElMessage.error(message)
|
||
return Promise.reject(new Error(message))
|
||
}
|
||
}
|
||
// 如果没有code字段,直接返回数据
|
||
else {
|
||
return res
|
||
}
|
||
} else {
|
||
// 处理非200状态码
|
||
return handleErrorResponse(response)
|
||
}
|
||
},
|
||
error => {
|
||
// 隐藏loading
|
||
hideLoading()
|
||
|
||
// 处理响应错误
|
||
if (error.response) {
|
||
// 服务器返回了响应,但状态码不在2xx范围内
|
||
return handleErrorResponse(error.response)
|
||
} else if (error.request) {
|
||
// 请求已发出,但没有收到响应
|
||
globalErrorHandler.handleNetworkError(error, {
|
||
context: 'no_response',
|
||
showMessage: true
|
||
})
|
||
return Promise.reject(error)
|
||
} else {
|
||
// 请求配置出错
|
||
globalErrorHandler.handleError(error, {
|
||
type: ErrorTypes.NETWORK,
|
||
context: 'request_config',
|
||
showMessage: true
|
||
})
|
||
return Promise.reject(error)
|
||
}
|
||
}
|
||
)
|
||
|
||
/**
|
||
* 处理错误响应
|
||
* @param {Object} response - 响应对象
|
||
*/
|
||
function handleErrorResponse(response) {
|
||
const { status, data } = response
|
||
|
||
// 使用全局错误处理器处理API错误
|
||
globalErrorHandler.handleApiError(response, {
|
||
showMessage: true
|
||
})
|
||
|
||
return Promise.reject(new Error(data?.message || `请求失败 (${status})`))
|
||
}
|
||
|
||
/**
|
||
* 生成请求ID
|
||
*/
|
||
function generateRequestId() {
|
||
return Date.now().toString(36) + Math.random().toString(36).substr(2)
|
||
}
|
||
|
||
/**
|
||
* 封装GET请求
|
||
* @param {string} url - 请求地址
|
||
* @param {Object} params - 请求参数
|
||
* @param {Object} config - 请求配置
|
||
*/
|
||
export function get(url, params = {}, config = {}) {
|
||
return service.get(url, {
|
||
params,
|
||
...config
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 封装POST请求
|
||
* @param {string} url - 请求地址
|
||
* @param {Object} data - 请求数据
|
||
* @param {Object} config - 请求配置
|
||
*/
|
||
export function post(url, data = {}, config = {}) {
|
||
return service.post(url, data, config)
|
||
}
|
||
|
||
/**
|
||
* 封装PUT请求
|
||
* @param {string} url - 请求地址
|
||
* @param {Object} data - 请求数据
|
||
* @param {Object} config - 请求配置
|
||
*/
|
||
export function put(url, data = {}, config = {}) {
|
||
return service.put(url, data, config)
|
||
}
|
||
|
||
/**
|
||
* 封装DELETE请求
|
||
* @param {string} url - 请求地址
|
||
* @param {Object} config - 请求配置
|
||
*/
|
||
export function del(url, config = {}) {
|
||
return service.delete(url, config)
|
||
}
|
||
|
||
/**
|
||
* 封装上传文件请求
|
||
* @param {string} url - 请求地址
|
||
* @param {FormData} formData - 表单数据
|
||
* @param {Object} config - 请求配置
|
||
*/
|
||
export function upload(url, formData, config = {}) {
|
||
return service.post(url, formData, {
|
||
headers: {
|
||
'Content-Type': 'multipart/form-data'
|
||
},
|
||
...config
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 封装下载文件请求
|
||
* @param {string} url - 请求地址
|
||
* @param {Object} params - 请求参数
|
||
* @param {string} filename - 下载文件名
|
||
*/
|
||
export function download(url, params = {}, filename = '') {
|
||
return service.get(url, {
|
||
params,
|
||
responseType: 'blob'
|
||
}).then(response => {
|
||
// 创建下载链接
|
||
const blob = new Blob([response])
|
||
const downloadUrl = window.URL.createObjectURL(blob)
|
||
const link = document.createElement('a')
|
||
link.href = downloadUrl
|
||
|
||
// 设置下载文件名
|
||
link.download = filename || `download_${Date.now()}`
|
||
|
||
// 触发下载
|
||
document.body.appendChild(link)
|
||
link.click()
|
||
document.body.removeChild(link)
|
||
|
||
// 释放URL对象
|
||
window.URL.revokeObjectURL(downloadUrl)
|
||
|
||
return response
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 取消请求
|
||
* @param {string} url - 请求地址
|
||
*/
|
||
export function cancelRequest(url) {
|
||
// 这里可以实现取消特定请求的逻辑
|
||
// 例如使用CancelToken或AbortController
|
||
}
|
||
|
||
/**
|
||
* 取消所有请求
|
||
*/
|
||
export function cancelAllRequests() {
|
||
// 这里可以实现取消所有请求的逻辑
|
||
// 例如存储所有请求的CancelToken或AbortController
|
||
}
|
||
|
||
export default service |