import { serverBaseUrl } from '@/common' import axios, { type AxiosRequestConfig, type AxiosResponse, } from 'axios' import * as qs from 'qs' import { useAppUserStore } from '@/common/app/app-user-store.ts' import Utils from '@/common/utils/index.ts' import mime from '@/common/utils/mime.ts' import Evt from '@/common/utils/evt.ts' import { ElMessage } from 'element-plus' /** * HTTP 统一响应结构 */ export interface R { code: number; msg: string; message: any; success: boolean; data: T; headers?: AxiosRequestConfig['headers'] } type AxiosConfig = Pick // const closeUrls = closeUrl.split(',') // type ParamsSerializerType = Extract; /** * Query 参数处理器 * * @param params 参数内容 */ const paramsSerializer = (params: any) => { return qs.stringify(params, {indices: false, allowDots: true}) } /** * 统一错误处理函数 */ /* function errHandler(r?: R) { Toast.error(r?.message ?? '操作失败') } */ const errHandler = Utils.throttle(500, (r?: AxiosResponse, any>) => { console.log('异常处理', r) ElMessage.error(r?.data?.message ?? '服务器错误') }) /** * axios 实例 */ const httpUtil = axios.create({ timeout: 10000, baseURL: serverBaseUrl, headers: { Accept: mime.JSON, }, }) /** * 配置请求拦截器 */ httpUtil.interceptors.request.use( config => { const appUserStore = useAppUserStore() config.headers.Authorization = appUserStore.token config.formSerializer = {indexes: null} return config }, error => { // TODO 请求失败日志 console.error('HTTP 请求发送失败', error) return Promise.reject(error) }, ) /** * 配置响应拦截器 */ httpUtil.interceptors.response.use( response => { // console.log('HTTP 请求结果', response.config.url, response) // vite 代理失败时 响应码为 200 响应内容为空 if (response.config.responseType !== 'json') { return Promise.resolve(response) } if (response.data == null) { response.data = {code: 0, success: true, msg: '无响应内容', message: '无响应内容', data: null, headers: response.headers} } response.data.headers = response.headers if (response.data.code === 0) { return Promise.resolve(response) } else { return Promise.reject(response) } }, error => { console.error('HTTP 请求失败', error) if (error.response != null && error.response.status === 403) { Evt.emit('logout') } if (error.response != null) { error.response.data = {...error.response.data, headers: error.response.headers} } else if (error.request != null) { error.response = { data: {code: 9999, success: false, msg: '网络异常', message: '网络异常', data: null}, } } else { error.response = { data: {code: 5555, success: false, msg: '请求发送失败', message: '请求发送失败', data: null}, } } return Promise.reject(error.response) }, ) /** * GET 请求(JSON) * * @param url 请求地址 * @param params Query 参数 * @param disposeErr 是否处理错误响应,默认-->true */ export function get(url: string, params?: any, disposeErr: boolean = true) { /* if (closeUrls.includes(url)) { return Promise.reject({code: 0, success: true, msg: '', message: '', data: null} as R) } */ return httpUtil.get>(url, {params, paramsSerializer, responseType: 'json'}) .then(({data}) => data) .catch(res => { if (disposeErr) errHandler(res) return Promise.reject(res as T) }) } /** * POST 请求(JSON) * * @param url 请求地址 * @param body Body 参数 * @param params 参数 * @param disposeErr 是否处理错误响应,默认-->true */ export function post(url: string, body?: any, params?: any, disposeErr: boolean = true) { /* if (closeUrls.includes(url)) { return Promise.reject({code: 0, success: true, msg: '', message: '', data: null} as R) } */ return httpUtil.post>(url, body, {responseType: 'json', params}) .then(({data}) => data) .catch(res => { if (disposeErr) errHandler(res) return Promise.reject(res) }) } /** * POST 请求(编码表单) * * @param url 请求地址 * @param body Body 参数 * @param config Axios 配置 * @param disposeErr 是否处理错误响应,默认-->true */ export function postForm(url: string, body: any, config?: AxiosConfig, disposeErr: boolean = true) { return httpUtil.postForm>(url, paramsSerializer(body), { headers: { ...(config?.headers ?? {}), 'Content-Type': mime.FORM, }, params: config?.params, responseType: config?.responseType ?? 'json', }) .then(({data}) => data) .catch(res => { if (disposeErr) errHandler(res) return Promise.reject(res) }) } /** * POST 请求(多部分表单) * * @param url 请求地址 * @param body Body 参数 * @param config Axios 配置 * @param disposeErr 是否处理错误响应,默认-->true */ export function postMltForm(url: string, body: any, config?: AxiosConfig, disposeErr: boolean = true) { return httpUtil.postForm>(url, body, { headers: { ...(config?.headers ?? {}), 'Content-Type': mime.MLT_FORM, }, params: config?.params, responseType: config?.responseType ?? 'json', }) .then(({data}) => data) .catch(res => { if (disposeErr) errHandler(res) return Promise.reject(res) }) } function getFileName(contentDisposition: string) { // 检查content-disposition是否存在 if (!contentDisposition) { return null } // 查找filename=部分 const match = contentDisposition.match(/filename=(.+)/) if (!match || match.length < 2) { return null } // 提取并解码文件名 const fileNameEncoded = match[1].trim() // 移除可能存在的引号 const fileNameWithoutQuotes = fileNameEncoded.replace(/["']/g, '') // 解码URL编码的字符串 return decodeURIComponent(fileNameWithoutQuotes) } export function download(url: string, data?: any, params?: any, defaultName: string = '下载的文件', disposeErr: boolean = true) { return httpUtil.post(url, data, {params, paramsSerializer, responseType: 'arraybuffer'}) .then(res => { const data = res.data if (!data || data.byteLength <= 0) { // 错误提示 return Promise.reject({code: 9999, success: false, msg: '文件获取失败', message: '文件获取失败', data: null, headers: res.headers}) } const contentDisposition = res.headers['Content-Disposition'] ?? res.headers['content-disposition'] const filename = getFileName(contentDisposition) ?? defaultName // 将二进制流转为blob const blob = new Blob([ data ]) return Promise.resolve({code: 0, success: true, msg: '成功', message: '文件获取成功', data: {data: blob, filename}, headers: res.headers}) }) .catch(res => { if (disposeErr) errHandler(res) return Promise.reject(res) }) } export function getFileUrl(path?: string) { if (path == null || path.length <= 0) { return '' } const appUserStore = useAppUserStore() return serverBaseUrl + path + '?authorization=' + appUserStore.token } export default { get, post, postForm, postMltForm, download, getFileUrl, ins: httpUtil, }