270 lines
7.5 KiB
TypeScript
270 lines
7.5 KiB
TypeScript
import {
|
||
downloadBaseUrl,
|
||
serverBaseUrl,
|
||
serverTimeout
|
||
} from '@/common'
|
||
import axios, { AxiosRequestConfig } from 'axios'
|
||
import * as qs from 'qs'
|
||
import Toast from '@/components/toast'
|
||
import { useAppUserStore } from '@/common/app/app-user-store.ts'
|
||
import { throttle } from '@/common/utils/index.ts'
|
||
|
||
/**
|
||
* HTTP 统一响应结构
|
||
*/
|
||
export interface R<T = any> {
|
||
code: number;
|
||
msg: string;
|
||
message: any;
|
||
data: T;
|
||
headers?: AxiosRequestConfig['headers']
|
||
}
|
||
|
||
export enum ContentType {
|
||
FORM = 'application/x-www-form-urlencoded',
|
||
MLT_FORM = 'multipart/form-data',
|
||
JSON = 'application/json',
|
||
BINARY = 'application/octet-stream',
|
||
}
|
||
|
||
|
||
type AxiosConfig = Pick<AxiosRequestConfig, 'headers' | 'params' | 'responseType'>
|
||
|
||
// type ParamsSerializerType = Extract<AxiosRequestConfig['paramsSerializer'], any>;
|
||
|
||
/**
|
||
* 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 = throttle(500, (r?: R) => {
|
||
Toast.error(r?.message ?? '服务器错误')
|
||
})
|
||
/**
|
||
* axios 实例
|
||
*/
|
||
const httpUtil = axios.create({
|
||
timeout: serverTimeout,
|
||
baseURL: serverBaseUrl,
|
||
headers: {
|
||
Accept: ContentType.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, 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.data = {...error.response.data, headers: error.response.headers}
|
||
} else if (error.request != null) {
|
||
error.response = {
|
||
data: {code: 9999, msg: '网络异常', message: '网络异常', data: null},
|
||
}
|
||
} else {
|
||
error.response = {
|
||
data: {code: 5555, msg: '请求发送失败', message: '请求发送失败', data: null},
|
||
}
|
||
}
|
||
return Promise.reject(error.response.data)
|
||
},
|
||
)
|
||
|
||
/**
|
||
* GET 请求
|
||
*
|
||
* @param url 请求地址
|
||
* @param params Query 参数
|
||
* @param disposeErr 是否处理错误响应,默认-->true
|
||
*/
|
||
export function get<T = any>(url: string, params?: any, disposeErr: boolean = true) {
|
||
return httpUtil.get<R<T>>(url, {params, paramsSerializer, 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 post<T>(url: string, body?: any, config?: AxiosConfig, disposeErr: boolean = true) {
|
||
return httpUtil.post<R<T>>(url, body, {...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 postForm<T>(url: string, body: any, config?: AxiosConfig, disposeErr: boolean = true) {
|
||
return httpUtil.postForm<R<T>>(url, paramsSerializer(body),
|
||
{
|
||
headers: {
|
||
...(config?.headers ?? {}),
|
||
'Content-Type': ContentType.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<T>(url: string, body: any, config?: AxiosConfig, disposeErr: boolean = true) {
|
||
return httpUtil.postForm<R<T>>(url, body,
|
||
{
|
||
headers: {
|
||
...(config?.headers ?? {}),
|
||
'Content-Type': ContentType.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, params?: any, disposeErr: boolean = true) {
|
||
return httpUtil.get(url, {params, paramsSerializer, responseType: 'arraybuffer'})
|
||
.then(res => {
|
||
let data = res.data
|
||
if (!data || data.byteLength <= 0) {
|
||
// 错误提示
|
||
return
|
||
}
|
||
const contentDisposition = res.headers['Content-Disposition'] ?? res.headers['content-disposition']
|
||
const fileName = getFileName(contentDisposition) ?? '下载的文件'
|
||
// 将二进制流转为blob
|
||
const blob = new Blob([ data ])
|
||
// 创建新的URL并指向File对象或者Blob对象的地址
|
||
const blobURL = window.URL.createObjectURL(blob)
|
||
// 创建a标签,用于跳转至下载链接
|
||
const tempLink = document.createElement('a')
|
||
tempLink.style.display = 'none'
|
||
tempLink.href = blobURL
|
||
tempLink.setAttribute('download', decodeURI(fileName))
|
||
// 兼容:某些浏览器不支持HTML5的download属性
|
||
if (typeof tempLink.download === 'undefined') {
|
||
tempLink.setAttribute('target', '_blank')
|
||
}
|
||
// 挂载a标签
|
||
document.body.appendChild(tempLink)
|
||
tempLink.click()
|
||
document.body.removeChild(tempLink)
|
||
// 释放blob URL地址
|
||
window.URL.revokeObjectURL(blobURL)
|
||
})
|
||
.catch(res => {
|
||
if (disposeErr) errHandler(res)
|
||
return Promise.reject(res)
|
||
})
|
||
}
|
||
|
||
export function getFileUrl(filename: string) {
|
||
const appUserStore = useAppUserStore()
|
||
return downloadBaseUrl + filename + '?authorization=' + appUserStore.token
|
||
}
|
||
|
||
export default {
|
||
get,
|
||
post,
|
||
postForm,
|
||
postMltForm,
|
||
download,
|
||
getFileUrl,
|
||
}
|