Files
it/frontend/src/utils/request.js
XCool f25b0307db
Some checks failed
CI/CD Pipeline / 测试 (18.x) (push) Has been cancelled
CI/CD Pipeline / 测试 (20.x) (push) Has been cancelled
CI/CD Pipeline / 安全检查 (push) Has been cancelled
CI/CD Pipeline / 部署 (push) Has been cancelled
CI/CD Pipeline / 通知 (push) Has been cancelled
初始化
2025-11-03 19:47:36 +08:00

297 lines
6.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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