js
复制代码
import Axios, {
type AxiosInstance,
type AxiosError,
type AxiosResponse,
type AxiosRequestConfig
} from 'axios';
import { getStorage } from '@/utils/storage';
import { _closePage } from '@/utils/utils-app';
import { ContentTypeEnum, ResultEnum, RequestEnum } from '@/enums/requestEnum';
import { useAddRequestCount, useRemoveRequestCount } from '@/hooks/useRequestCounter';
import NProgress from './progress';
import qs from 'qs';
// 默认 axios 实例请求配置
const configDefault = {
headers: {
'Content-Type': ContentTypeEnum.JSON,
'config-model': 'INTERNET'
},
timeout: RequestEnum.TIMEOUT,
baseURL: import.meta.env.VITE_BASE_API,
paramsSerializer: {
serialize(params: any) {
return qs.stringify(params, { allowDots: true });
}
},
data: {}
};
class Http {
// 当前实例
private static axiosInstance: AxiosInstance;
// 请求配置
private static axiosConfigDefault: AxiosRequestConfig;
// 请求拦截
private httpInterceptorsRequest(): void {
Http.axiosInstance.interceptors.request.use(
config => {
if (!config.hideLoading) {
useAddRequestCount();
};
const { accessToken } = getStorage('accessToken');
if (accessToken) {
config.headers!['Authorization'] = accessToken;
}
return config;
},
(error: AxiosError) => {
showFailToast(error.message);
return Promise.reject(error);
}
);
}
// 响应拦截
private httpInterceptorsResponse(): void {
Http.axiosInstance.interceptors.response.use(
(response: AxiosResponse) => {
if (!response.config.hideLoading) {
useRemoveRequestCount();
};
// 与后端协定的返回字段
const { code, message } = response.data;
// 判断请求是否成功
const isSuccess = Reflect.has(response.data, 'code') && code === ResultEnum.SUCCESS;
if (isSuccess) {
return response.data;
}
if (code === ResultEnum.EXPIRED) {
showDialog({
title: "权限提示",
message: "页面已失效,请重新进入页面!",
})
.then(() => {
_closePage();
})
} else {
// 处理请求错误
showFailToast(message);
return Promise.reject(response.data);
}
},
(error: AxiosError) => {
if (!error?.config?.hideLoading) {
useRemoveRequestCount();
};
// HTTP 状态码
const status = error.response?.status ?? 0;
const handler = new Map([
[400, '请求错误'],
[401, '未授权,请登录'],
[403, '拒绝访问'],
[404, `请求地址出错: ${error.response?.config?.url}`],
[408, '请求超时'],
[500, '服务器内部错误'],
[501, '服务未实现'],
[502, '网关错误'],
[503, '服务不可用'],
[504, '网关超时'],
[505, 'HTTP版本不受支持'],
[0, '网络连接故障'],
])
// 处理 HTTP 网络错误
const message = handler.get(status) ?? handler.get(0) ?? '';
showFailToast(message);
return Promise.reject(error);
}
);
}
constructor(config: AxiosRequestConfig) {
Http.axiosConfigDefault = config;
Http.axiosInstance = Axios.create(config);
this.httpInterceptorsRequest();
this.httpInterceptorsResponse();
}
// 通用请求函数
public request<T>(paramConfig: AxiosRequestConfig): Promise<T> {
const config = { ...Http.axiosConfigDefault, ...paramConfig };
return new Promise((resolve, reject) => {
Http.axiosInstance
.request(config)
.then((response: any) => {
resolve(response);
})
.catch(error => {
reject(error);
});
});
}
}
export const http = new Http(configDefault);
js
复制代码
import { defineStore } from 'pinia';
import { store } from '@/store';
export const useRequestCounterStore = defineStore({
id: 'request-counter',
state: () => ({
// 请求次数
requestCount: 0,
}),
getters: {
// 是否有请求正在进行
isLoading(): boolean {
return this.requestCount > 0;
}
},
actions: {
// 增加请求计数
addRequestCount() {
this.requestCount += 1;
},
// 减少请求计数
removeRequestCount() {
if (this.requestCount > 0) {
this.requestCount -= 1;
}
},
// 重置计数器(用于清理)
clearRequestCount() {
this.requestCount = 0;
}
}
});
export function useRequestCounterStoreHook() {
return useRequestCounterStore(store);
}
xml
复制代码
<template>
<div class="flex-column-1 hidden-container">
<!-- 适配顶部安全区域 -->
<header class="van-safe-area-top" />
<router-view v-slot="{ Component, route }">
<keep-alive :include="cachedViews">
<component :is="Component" :key="route.path" class="flex-column-1" />
</keep-alive>
</router-view>
<!-- 适配底部安全区域 -->
<footer class="van-safe-area-bottom" />
<!-- 加载中遮罩 -->
<van-overlay :show="isLoading" class="global-loading-overlay">
<div class="flex-center loading-content">
<van-loading type="circular" :size="$getSize(48)" />
</div>
</van-overlay>
</div>
</template>
<script setup lang="ts" name="BasicLayout">
import { useCachedViewStoreHook } from "@/store/modules/cachedView";
import { useRequestCounterStoreHook } from "@/store/modules/requestCounter";
import $getSize from '@/utils/px2vw';
const router = useRouter();
const route = useRoute();
// 缓存页面
const cachedViews = computed(() => {
return useCachedViewStoreHook().cachedViewList;
});
// 是否加载中
const isLoading = computed(() => {
return useRequestCounterStoreHook().isLoading;
});
</script>
<style lang="less" scoped>
.global-loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 9999;
background-color: transparent;
// background-color: rgba(0, 0, 0, .6);
}
.loading-content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 64px;
height: 64px;
background-color: var(--van-toast-background);
border-radius: var(--van-toast-radius);
}
</style>