import axios from 'axios' import qs from 'query-string' import type { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios' import { useUserStore } from '@/stores' import { getToken } from '@/utils/auth' import modalErrorWrapper from '@/utils/modal-error-wrapper' import messageErrorWrapper from '@/utils/message-error-wrapper' import notificationErrorWrapper from '@/utils/notification-error-wrapper' import router from '@/router' interface ICodeMessage { [propName: number]: string } const StatusCodeMessage: ICodeMessage = { 200: '服务器成功返回请求的数据', 201: '新建或修改数据成功。', 202: '一个请求已经进入后台排队(异步任务)', 204: '删除数据成功', 400: '请求错误(400)', 401: '未授权,请重新登录(401)', 403: '拒绝访问(403)', 404: '请求出错(404)', 408: '请求超时(408)', 500: '服务器错误(500)', 501: '服务未实现(501)', 502: '网络错误(502)', 503: '服务不可用(503)', 504: '网络超时(504)', } const http: AxiosInstance = axios.create({ baseURL: import.meta.env.VITE_API_PREFIX ?? import.meta.env.VITE_API_BASE_URL, timeout: 30 * 1000, }) const handleError = (msg: string) => { if (msg.length >= 15) { return notificationErrorWrapper({ content: msg || '服务器端错误', duration: 5 * 1000, }) } return messageErrorWrapper({ content: msg || '服务器端错误', duration: 5 * 1000, }) } // 请求拦截器 http.interceptors.request.use( (config: AxiosRequestConfig) => { const token = getToken() if (token) { if (!config.headers) { config.headers = {} } config.headers.Authorization = `${token}` } return config }, (error) => Promise.reject(error), ) // 响应拦截器 http.interceptors.response.use( (response: AxiosResponse) => { const { data } = response // 兼容返回数据中使用rows字段的情况 if (data && data.rows !== undefined && data.data === undefined) { data.data = data.rows } // 兼容不同的API响应结构 const { success, code, msg } = data // 检查响应类型是否是blob if (response.request.responseType === 'blob') { const contentType = data.type if (contentType.startsWith('application/json')) { const reader = new FileReader() reader.readAsText(data) reader.onload = () => { try { const result = JSON.parse(reader.result as string) const isSuccess = result.success !== undefined ? result.success : (result.code === 200 || result.code === '200') if (!isSuccess) { handleError(result.msg || '操作失败') } } catch (error) { handleError('解析响应数据失败') } } return Promise.reject(msg) } else { return response } } // 判断请求是否成功:明确的success字段为true,或者code为200都视为成功 const isSuccess = success !== undefined ? success : (code === 200 || code === '200') if (isSuccess) { return response } // Token 失效 if (code === '401' && response.config.url !== '/auth/user/info') { modalErrorWrapper({ title: '提示', content: msg, maskClosable: false, escToClose: false, okText: '重新登录', async onOk() { const userStore = useUserStore() await userStore.logoutCallBack() await router.replace('/login') }, }) } else { handleError(msg) } return Promise.reject(new Error(msg || '服务器端错误')) }, (error: AxiosError) => { if (!error.response) { handleError('网络连接失败,请检查您的网络') return Promise.reject(error) } const status = error.response?.status const errorMsg = StatusCodeMessage[status] || '服务器暂时未响应,请刷新页面并重试。若无法解决,请联系管理员' handleError(errorMsg) return Promise.reject(error) }, ) const request = async (config: AxiosRequestConfig): Promise> => { return http.request(config) .then((res: AxiosResponse) => { // 处理返回数据结构,兼容rows和data字段 const responseData = res.data // 如果返回的数据中有rows字段但没有data字段,将rows赋值给data if (responseData.rows !== undefined && responseData.data === undefined) { responseData.data = responseData.rows } // 如果返回的code是200但没有设置success字段,将success设置为true if ((responseData.code === 200 || responseData.code === '200') && responseData.success === undefined) { responseData.success = true } return responseData }) .catch((err: { msg: string }) => Promise.reject(err)) } const requestNative = async (config: AxiosRequestConfig): Promise => { return http.request(config) .then((res: AxiosResponse) => res) .catch((err: { msg: string }) => Promise.reject(err)) } // 完全绕过拦截器的请求方法 const requestRaw = async (config: AxiosRequestConfig): Promise => { // 创建一个新的axios实例,不包含拦截器 const rawAxios = axios.create({ baseURL: import.meta.env.VITE_API_PREFIX ?? import.meta.env.VITE_API_BASE_URL, timeout: 30 * 1000, }) // 只添加请求拦截器来设置token,不添加响应拦截器 rawAxios.interceptors.request.use( (config: AxiosRequestConfig) => { const token = getToken() if (token) { if (!config.headers) { config.headers = {} } config.headers.Authorization = `${token}` } return config }, (error) => Promise.reject(error), ) return rawAxios.request(config) } const createRequest = (method: string) => { return (url: string, params?: object, config?: AxiosRequestConfig): Promise> => { return request({ method, url, [method === 'get' ? 'params' : 'data']: params, ...(method === 'get' ? { paramsSerializer: (obj) => qs.stringify(obj), } : {}), ...config, }) } } const download = (url: string, params?: object, config?: AxiosRequestConfig): Promise => { return requestNative({ method: 'get', url, responseType: 'blob', params, paramsSerializer: (obj) => qs.stringify(obj), ...config, }) } export default { get: createRequest('get'), post: createRequest('post'), put: createRequest('put'), patch: createRequest('patch'), del: createRequest('delete'), request, requestNative, requestRaw, download, }