vue3+ts Axios封装—重复请求拦截
阅读原文时间:2023年09月04日阅读:1

创建好vue3项目

Axios安装

npm install axios

Element Plus 安装

官网入口:https://element-plus.gitee.io/zh-CN/

npm install element-plus --save

Element 主要用到信息提示 与 全屏加载动画

api 文件夹下 封装 Axios封装 与 请求配置

utils 文件夹下 operate.ts 配置接口地址 与其他全局ts

旧版本地址:https://www.cnblogs.com/lovejielive/p/16363587.html

新版本:主要增加动态控制是否显示加载动画。

是否需要判断重复请求。

优化请求接口配置参数写法。

扩展AxiosRequestConfig 增加自定义参数

declare module 'axios' {
//请求自定义参数
interface AxiosRequestConfig {
// 是否显示加载框
ifLoading?: boolean
// 是否允许重复请求
repeatRequest?: boolean
// 登录 token
isToken?: any;
}
}

3.1重复请求判断

通过配置repeatRequest是否允许重复请求,来开启判断。主要在api.ts中配置。

每一次请求创建一个key,判断是否存在,如存在执行.abort()取消当前请求,

不存在pendingMap中新增一个key。

通过AbortController来进行手动取消。

主要代码

//格式化请求链接
function getRequestKey(config: AxiosRequestConfig) {
const { url, method, data, params } = config,
//字符串化参数
dataStr = JSON.stringify(data) || '',
paramsStr = JSON.stringify(params) || '',
//记得这里一定要处理 每次请求都掉会变化的参数(比如每个请求都携带了时间戳),否则二个请求的key不一样
key = [method, url, dataStr, paramsStr].join("&");
return key;
}

//创建存储 key 的 集合
const pendingMap = new Map()

//是否重复请求key
function setPendingMap(config: AxiosRequestConfig) {
//手动取消
const controller = new AbortController()
config.signal = controller.signal
const key = getRequestKey(config)
//判断是否存在key 存在取消请求 不存在添加
if (pendingMap.has(key)) {
// abort取消请求
pendingMap.get(key).abort()
//删除key
pendingMap.delete(key)
} else {
pendingMap.set(key, controller)
}
}

在接口消息提示时,通过 axios.isCancel(error) 过滤掉已取消的请求,

//拦截掉重复请求的错误,中断promise执行
if (axios.isCancel(error)) return []

3.2 axios完整代码

api文件夹下 创建 request-wrapper.ts Axios封装

/*
* @description: 请求封装
* @Author: Jay
* @Date: 2023-04-11 13:24:41
* @LastEditors: Jay
* @LastEditTime: 2023-09-04 11:49:10
*/

// 导入axios
import axios, { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
// 使用element-ui ElMessage做消息提醒 ElLoading加载
import { ElMessage, ElLoading } from "element-plus";
//请求头
import operate from "@/utils/operate"

//加载配置
let loadingInstance: { close: () => void };
let requestNum = 0;
//加载动画
const addLoading = () => {
// 防止重复弹出
requestNum++;
if (requestNum === 1) {
loadingInstance = ElLoading.service({ fullscreen: true });
}
}
// 关闭 加载动画
const cancelLoading = () => {
requestNum--;
if (requestNum === 0) loadingInstance?.close();
}

//格式化请求链接
function getRequestKey(config: AxiosRequestConfig) {
const { url, method, data, params } = config,
//字符串化参数
dataStr = JSON.stringify(data) || '',
paramsStr = JSON.stringify(params) || '',
//记得这里一定要处理 每次请求都掉会变化的参数(比如每个请求都携带了时间戳),否则二个请求的key不一样
key = [method, url, dataStr, paramsStr].join("&");
return key;
}

//创建存储 key 的 集合
const pendingMap = new Map()

//是否重复请求key
function setPendingMap(config: AxiosRequestConfig) {
//手动取消
const controller = new AbortController()
config.signal = controller.signal
const key = getRequestKey(config)
//判断是否存在key 存在取消请求 不存在添加
if (pendingMap.has(key)) {
// abort取消请求
pendingMap.get(key).abort()
//删除key
pendingMap.delete(key)
} else {
pendingMap.set(key, controller)
}
}

//增加新的请求参数类型
declare module 'axios' {
//请求自定义参数
interface AxiosRequestConfig {
// 是否显示加载框
ifLoading?: boolean
// 是否允许重复请求
repeatRequest?: boolean
// 登录 token
isToken?: any;
}

// 解決 类型“AxiosResponse<any, any>”上不存在属性“code”  
// interface AxiosResponse<T = any> {  
//     // 请求 data 里的一级参数  
//     code: number;  
//     time: string;  
//     msg: string;  
//     data: T;  
// }  

}

//创建axios的一个实例
const axiosInstance: AxiosInstance = axios.create({
//接口统一域名
baseURL: operate.baseUrl(),
//设置超时
timeout: 1000 * 30,
//跨域携带cookie
withCredentials: true,
})

// 添加请求拦截器
axiosInstance.interceptors.request.use(
(config) => {
//加载动画
if (config?.ifLoading) addLoading();
//是否判断重复请求
if (!config.repeatRequest) {
setPendingMap(config)
}

    //判断是否有token 根据自己的需求判断  
    const token = config.isToken  
    console.log("判断是否有token", token)  
    if (token != undefined) {  
        //如果要求携带在参数中  
        config.params = Object.assign({}, config.params, token)  
        // 如果要求携带在请求头中  
        // config.headers = Object.assign({}, config.headers, operate.uploadParameters())  
    }  
    return config  
},  
(error: AxiosError) => {  
    return Promise.reject(error)  
}  

)

// 添加响应拦截器
axiosInstance.interceptors.response.use((response: AxiosResponse) => {
const config = response.config
// 关闭加载 动画
if (config?.ifLoading) cancelLoading();
//是否登录过期
if (response.data.code == 400 || response.data.code == 401) {
ElMessage.error("登录过期,请重新登录")
// //清除登录缓存
// store.commit("LOGOUT")
// //返回首页
// setTimeout(() => {
// router.push("/");
// }, 500);
return
}
// 返回参数
return response.data
})

// 错误处理
axiosInstance.interceptors.response.use(undefined, (error) => {
const config = error.config
// 关闭加载 动画
if (config?.ifLoading) cancelLoading();

//拦截掉重复请求的错误,中断promise执行  
if (axios.isCancel(error)) return \[\]

/\*\*\*\*\* 接收到异常响应的处理开始 \*\*\*\*\*/  
if (error && error.response) {  
    // 1.公共错误处理  
    // 2.根据响应码具体处理  
    switch (error.response.status) {  
        case 400:  
            error.message = '错误请求'  
            break;  
        case 401:  
            error.message = '未授权,请重新登录'  
            break;  
        case 403:  
            error.message = '拒绝访问'  
            break;  
        case 404:  
            error.message = '请求错误,未找到该资源'  
            // window.location.href = "/NotFound"  
            break;  
        case 405:  
            error.message = '请求方法未允许'  
            break;  
        case 408:  
            error.message = '请求超时'  
            break;  
        case 500:  
            error.message = '服务器端出错'  
            break;  
        case 501:  
            error.message = '网络未实现'  
            break;  
        case 502:  
            error.message = '网络错误'  
            break;  
        case 503:  
            error.message = '服务不可用'  
            break;  
        case 504:  
            error.message = '网络超时'  
            break;  
        case 505:  
            error.message = 'http版本不支持该请求'  
            break;  
        default:  
            error.message = \`连接错误${error.response.status}\`  
    }  
} else {  
    // 超时处理  
    if (JSON.stringify(error).includes('timeout')) {  
        error.message = '服务器响应超时,请刷新当前页'  
    } else {  
        error.message = '连接服务器失败'  
    }  
}

//提示  
ElMessage.error(error.message)

/\*\*\*\*\* 处理结束 \*\*\*\*\*/  
return Promise.resolve(error)  

})

export default axiosInstance

request-wrapper.ts

api文件夹下 创建 api.ts 接口配置

/*
* @description: 请求接口 配置
* @Author: Jay
* @Date: 2023-04-11 13:24:41
* @LastEditors: Jay
* @LastEditTime: 2023-09-04 13:30:09
*/

//导入 Axios 请求
import request from '@/utils/request'
//其他配置
import operate from '@/utils/operate';

// 官网接口
export const homePost = (data?: any) => {
return request({
url: '/api/index',
method: 'post',
data,
//登录token
isToken: operate.isToken(),
//加载动画是否启动
ifLoading: true,
//是否允许重复请求
repeatRequest: false,
})
}

/*
请求配置与使用

* 请求 方式
export const 名字 = (data: any) =>
request.post("接口", data, {
直接为空
注:只能配置 AxiosRequestConfig 里有的参数名 可不用配置
});

*使用 方法
*引入
import {
名字
} from "../api/api"
*生命周期中 请求
名字({请求参数}).then((res) => {
console.log(res)
})
*/

api.ts

开始请求

请求结果

第一次请求被拦截,只有第二次成功返回

3.3 operate.ts 方法

主要放置一些 全局参数与方法。

在页面中可以通过 import operate from "@/utils/operate" 导入使用,也可以在main.ts中全局配置。

/*
* @description: 全局js
* @Author: Jay
* @Date: 2023-09-04 13:53:47
* @LastEditors: Jay
* @LastEditTime: 2023-09-04 13:55:44
*/

// vuex 数据
import store from '../store/index'

//接口地址
const baseUrl = () => {
if (process.env.NODE_ENV == "development") {
//开发环境
return "";
} else {
//正式环境
return "";
}
}

//获取用户token
const isToken = () => {
if (store.state.Authorization != '') {
return store.state.Authorization
}
return '';
}

/* eslint-disable */

/*
格式化时间 加上时分秒
num: 后台时间格式
type: 'YY-MM-DD' 年月日 ,'HH-MM-SS' 时分秒 ,不传 年月日时分秒
*/
const happenTime = (num: any, type: string) => {
let date = new Date(num * 1000);
//时间戳为10位需*1000,时间戳为13位的话不需乘1000
let y: any = date.getFullYear();
let MM: any = date.getMonth() + 1;
MM = MM < 10 ? ('0' + MM) : MM; //月补0
let d: any = date.getDate();
d = d < 10 ? ('0' + d) : d; //天补0
let h: any = date.getHours();
h = h < 10 ? ('0' + h) : h; //小时补0
let m: any = date.getMinutes();
m = m < 10 ? ('0' + m) : m; //分钟补0
let s: any = date.getSeconds();
s = s < 10 ? ('0' + s) : s; //秒补0
if (type === 'YY-MM-DD') {
//年月日
return y + '-' + MM + '-' + d;
} else if (type === 'HH-MM-SS') {
//时分秒
return h + ':' + m + ':' + s;
} else {
//全部
return y + '-' + MM + '-' + d + ' ' + h + ':' + m + ':' + s;
}
}
/* eslint-enable */

// 页面回到顶部(滚动效果)
/*
使用方法
//监听滚动事件
window.addEventListener("scroll", proxy.$operate.handleScroll, {
once: true,
});
*/
const handleScroll = () => {
let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
console.log(scrollTop, "scrollTop");
if (scrollTop > 0) {
const timeTop = setInterval(() => {
document.documentElement.scrollTop = document.body.scrollTop = scrollTop -= 50; //一次减50往上滑动
if (scrollTop <= 0) {
clearInterval(timeTop);
}
}, 10); //定时调用函数使其更顺滑
}
};

export default {
baseUrl,
isToken,
happenTime,
handleScroll
}

operate.ts

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章