在实际开发过程中如果没有封装axios会导致项目的难维护及代码的冗余性,所以封装请求就成了一个合格前端的必须技能。
request.ts文件代码
因为AxiosRequestConfig中没有一个控制loading的参数,所以我将AxiosRequestConfig中的参数继承到另一个NewAxiosRequestConfig中
//重写AxiosRequestConfig
interface NewAxiosRequestConfig extends AxiosRequestConfig {
isLoading?:Boolean,
headers?:any
}
创建axios实例
const service = axios.create({
// axios中请求配置有baseURL选项,表示请求URL公共部分
baseURL: import.meta.env.VITE_APP_BASE_API,
// 超时
timeout: 60000,
headers: {
'Content-Type': 'application/json;charset=utf-8',
},
});
创建一个loading的number值loadingCount,通过loadingCount来控制loading的显示
const loadingCount = ref<number>(0)
request拦截器
service.interceptors.request.use(
(config : NewAxiosRequestConfig) => {
//将请求存储到pinia中
const controller = new AbortController()
config.signal = controller.signal
const requestStore : any = useRequestStore()
requestStore.getRequestsLit(controller)
// 是否需要设置 token
const isToken = (config.headers || {}).isToken === false;
//显示loading
if(config.isLoading){
loadingCount.value++
}
if(loadingCount.value > 0 ){
//默认不显示loading,当config.islLoading为true时显示loading
downloadLoadingInstance = ElLoading.service({
lock: true,
text: '加载中',
background: 'rgba(0, 0, 0, 0.7)',
});
}
// 是否需要防止数据重复提交
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false;
if (getToken() && !isToken && config.headers) {
config.headers['token'] = getToken(); // 让每个请求携带自定义token 请根据实际情况自行修改
}
// get请求映射params参数
if (config.method === 'get' && config.params) {
let url = config.url + '?' + tansParams(config.params);
url = url.slice(0, -1);
config.params = {};
config.url = url;
}
if(config.method === 'post' && config.params){
let url = config.url + '?' + tansParams(config.params);
url = url.slice(0, -1);
config.params = {};
config.url = url;
}
if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
const requestObj = {
url: config.url,
data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
time: new Date().getTime(),
};
const sessionObj = cache.session.getJSON('sessionObj');
if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
cache.session.setJSON('sessionObj', requestObj);
} else {
const s_url = sessionObj.url; // 请求地址
const s_data = sessionObj.data; // 请求数据
const s_time = sessionObj.time; // 请求时间
const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交
if (
s_data === requestObj.data &&
requestObj.time - s_time < interval &&
s_url === requestObj.url
) {
// const message = '数据正在处理,请勿重复提交';
// console.warn(`[${s_url}]: ` + message);
// return Promise.reject(new Error(message));
} else {
cache.session.setJSON('sessionObj', requestObj);
}
}
}
return config;
},
error => {
console.log(error);
Promise.reject(error);
}
);
关闭loading
const loadingColse = () => {
if(loadingCount.value > 0){
downloadLoadingInstance.close()
loadingCount.value--
}
}
响应拦截器
service.interceptors.response.use(
res => {
// 未设置状态码则默认成功状态
const code = res.data.code || 200;
// 获取错误信息
const msg = errorCode[code] || res.data.msg || errorCode['default'];
// 二进制数据则直接返回
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
loadingColse()
return res.data;
}
if (code === 401) {
loadingColse()
if (!isRelogin.show) {
isRelogin.show = true;
ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
isRelogin.show = false;
//调用退出登录的方法
})
.catch(() => {
isRelogin.show = false;
});
}
return Promise.reject('无效的会话,或者会话已过期,请重新登录。');
} else if (code === 500) {
loadingColse()
ElMessage({
message: msg,
type: 'error',
});
return Promise.reject(new Error(msg));
} else if (code === 601) {
loadingColse()
ElMessage({
message: msg,
type: 'warning',
});
return Promise.reject(new Error(msg));
} else if (code !== 200) {
loadingColse()
ElNotification.error({
title: msg,
});
return Promise.reject('error');
} else {
loadingColse()
return Promise.resolve(res.data);
}
},
error => {
console.log('err' + error);
let { message } = error;
if (message === 'Network Error') {
message = '后端接口连接异常';
} else if (message.includes('timeout')) {
message = '系统接口请求超时';
} else if (message.includes('Request failed with status code')) {
message = '系统接口' + message.substr(message.length - 3) + '异常';
}
loadingColse()
//message为canceled时就是取消当前请求
if(message != 'canceled'){
ElMessage({
message: message,
type: 'error',
duration: 5 * 1000,
});
}
return Promise.reject(error);
}
);
pinia中创建requests用来后续跳转页面清除请求
import { defineStore } from 'pinia';
const useRequestStore = defineStore('requests',{
state: (): {
requests:any
} => ({
requests:[]
}),
actions: {
getRequestsLit(state:any) {
this.requests.push(state)
}
}
})
export default useRequestStore;
封装requestAbort 方法,在router.beforeEach中调用
const requestAbort = () => {
//当页面切换时清除上个页面未完成的请求
const requestStore = useRequestStore()
requestStore.requests.forEach((controller:any) => controller.abort());
requestStore.requests = []
}
实际应用
export function getUserInfo(data: any) {
return request({
url: `/user/info`,
method: 'post',
data:data,
isLoading:true
});
}