|
@@ -0,0 +1,145 @@
|
|
|
|
+import qs from 'qs'
|
|
|
|
+import router from '@/router'
|
|
|
|
+import axios, {AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig} from 'axios'
|
|
|
|
+import {ElMessage} from 'element-plus'
|
|
|
|
+import {showFullScreenLoading, tryHideFullScreenLoading} from '@/utils/fullScreen'
|
|
|
|
+import {getToken} from '@/utils/index'
|
|
|
|
+
|
|
|
|
+interface CustomAxiosRequestConfig extends InternalAxiosRequestConfig {
|
|
|
|
+ loading?: boolean
|
|
|
|
+ cancel?: boolean
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const LOGIN_URL = import.meta.env.VITE_LOGIN_URL
|
|
|
|
+
|
|
|
|
+const config = {
|
|
|
|
+ // 默认地址请求地址,可在 .env.** 文件中修改
|
|
|
|
+ baseURL: import.meta.env.VITE_APP_BASE_API,
|
|
|
|
+ // 设置超时时间
|
|
|
|
+ timeout: 30000,
|
|
|
|
+ // 跨域时候允许携带凭证
|
|
|
|
+ withCredentials: true
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 声明一个 Map 用于存储每个请求的标识 和 取消函数
|
|
|
|
+const pendingMap = new Map<string, AbortController>()
|
|
|
|
+
|
|
|
|
+class RequestHttp {
|
|
|
|
+ service: AxiosInstance
|
|
|
|
+
|
|
|
|
+ public constructor(config: AxiosRequestConfig) {
|
|
|
|
+ this.service = axios.create(config)
|
|
|
|
+
|
|
|
|
+ // 请求拦截器
|
|
|
|
+ this.service.interceptors.request.use(
|
|
|
|
+ (config: CustomAxiosRequestConfig) => {
|
|
|
|
+ // 重复请求不需要取消,在 api 服务中通过指定的第三个参数: { cancel: false } 来控制
|
|
|
|
+ config.cancel ??= false
|
|
|
|
+ config.cancel && addPending(config)
|
|
|
|
+ // 当前请求不需要显示 loading,在 api 服务中通过指定的第三个参数: { loading: false } 来控制
|
|
|
|
+ config.loading ??= true
|
|
|
|
+ config.loading && showFullScreenLoading()
|
|
|
|
+ config.headers.set('Authorization', getToken())
|
|
|
|
+ return config
|
|
|
|
+ },
|
|
|
|
+ (error: AxiosError) => {
|
|
|
|
+ return Promise.reject(error)
|
|
|
|
+ }
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+ // 响应拦截器
|
|
|
|
+ this.service.interceptors.response.use(
|
|
|
|
+ (response: AxiosResponse & { config: CustomAxiosRequestConfig }) => {
|
|
|
|
+ const { data, config } = response
|
|
|
|
+ removePending(config)
|
|
|
|
+ config.loading && tryHideFullScreenLoading()
|
|
|
|
+
|
|
|
|
+ const blob = new Blob([data], { type: "application/ms-excel" });
|
|
|
|
+ const url = window.URL.createObjectURL(blob);
|
|
|
|
+ const a = document.createElement('a');
|
|
|
|
+ a.style.display = 'none';
|
|
|
|
+ a.href = url;
|
|
|
|
+ a.download = decodeURIComponent(response.headers['download-filename']) || '文件.xlsx';
|
|
|
|
+ document.body.appendChild(a);
|
|
|
|
+ a.click();
|
|
|
|
+ window.URL.revokeObjectURL(url);
|
|
|
|
+ return data
|
|
|
|
+ },
|
|
|
|
+ (error: AxiosError) => {
|
|
|
|
+ const { response } = error
|
|
|
|
+ tryHideFullScreenLoading()
|
|
|
|
+ // 请求超时 && 网络错误单独判断,没有 response
|
|
|
|
+ if (error.message.indexOf('timeout') !== -1) ElMessage.error('请求超时,请您稍后重试')
|
|
|
|
+ if (error.message.indexOf('Network Error') !== -1) ElMessage.error('网络错误,请您稍后重试')
|
|
|
|
+ // 根据服务器响应的错误状态码,做不同的处理
|
|
|
|
+ if (response) {
|
|
|
|
+ switch (response.status) {
|
|
|
|
+ case 400:
|
|
|
|
+ ElMessage.error('请求失败')
|
|
|
|
+ break
|
|
|
|
+ case 404:
|
|
|
|
+ ElMessage.error('你所访问的资源不存在')
|
|
|
|
+ break
|
|
|
|
+ case 500:
|
|
|
|
+ ElMessage.error('服务异常')
|
|
|
|
+ break
|
|
|
|
+ default:
|
|
|
|
+ ElMessage.error('请求失败')
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ // 服务器结果都没有返回(可能服务器错误可能客户端断网),断网处理:可以跳转到断网页面
|
|
|
|
+ if (!window.navigator.onLine) router.replace('/500').then()
|
|
|
|
+ return Promise.reject(error)
|
|
|
|
+ }
|
|
|
|
+ )
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ get<T>(url: string, params?: object, _object = {}): Promise<T> {
|
|
|
|
+ return this.service.get(url, { params,..._object, responseType: "blob" })
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ post<T>(url: string, data?: object | string, _object = {}): Promise<T> {
|
|
|
|
+ return this.service.post(url, data, {..._object, responseType: "blob"})
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ put<T>(url: string, data?: object | string, _object = {}): Promise<T> {
|
|
|
|
+ return this.service.put(url, data, {..._object, responseType: "blob"})
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ delete<T>(url: string, params?: object, _object = {}): Promise<T> {
|
|
|
|
+ return this.service.delete(url, { params, ..._object, responseType: "blob" })
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 序列化参数
|
|
|
|
+function getPendingUrl(config: CustomAxiosRequestConfig) {
|
|
|
|
+ return [config.method, config.url, sortString(config.data), sortString(config.params)].join('&')
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 序列化参数,确保对象属性顺序一致
|
|
|
|
+function sortString(obj: any) {
|
|
|
|
+ return qs.stringify(obj, { arrayFormat: 'repeat', sort: (a, b) => a.localeCompare(b) })
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 添加请求
|
|
|
|
+function addPending(config: CustomAxiosRequestConfig): void {
|
|
|
|
+ // 在请求开始前,对之前的请求做检查取消操作
|
|
|
|
+ removePending(config)
|
|
|
|
+ const url = getPendingUrl(config)
|
|
|
|
+ const controller = new AbortController()
|
|
|
|
+ config.signal = controller.signal
|
|
|
|
+ pendingMap.set(url, controller)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 移除请求
|
|
|
|
+function removePending(config: CustomAxiosRequestConfig): void {
|
|
|
|
+ const url = getPendingUrl(config)
|
|
|
|
+ // 如果在 pending 中存在当前请求标识,需要取消当前请求并删除条目
|
|
|
|
+ const controller = pendingMap.get(url)
|
|
|
|
+ if (controller) {
|
|
|
|
+ controller.abort()
|
|
|
|
+ pendingMap.delete(url)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+export default new RequestHttp(config)
|