vue3+ts Axios封装—重复请求拦截

x33g5p2x  于3个月前 转载在 iOS  
字(8.0k)|赞(0)|评价(0)|浏览(109)

创建好vue3项目

1.安装Axios与Element Plus

Axios安装

npm install axios

Element Plus 安装

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

npm install element-plus --save

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

2.在src 目录下创建 api 文件夹和 utils 文件夹

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

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

3.Axios封装

旧版本地址: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 15:04:28
 */

//导入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();
}

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

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

//是否重复请求key
functionsetPendingMap(config: AxiosRequestConfig) {
    //手动取消请求
    const controller = newAbortController()
    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, operate.uploadParameters())
            //如果要求携带在请求头中
            //config.headers = Object.assign({}, config.headers, operate.uploadParameters())
}
        returnconfig
    },
    (error: AxiosError) =>{
        returnPromise.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}
    //返回参数
    returnresponse.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)

    /***** 处理结束 *****/
    returnPromise.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) =>{
    returnrequest({
        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

开始请求
<script lang="ts" setup>
import { onMounted } from "vue";
import { homePost } from "@/api/api";

//生命周期
onMounted(() => {
  homePost().then((res) => {
    console.log("第一次", res);
  });
  homePost().then((res) => {
    console.log("第二次", res);
  });
});
</script>

请求结果

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

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 != '') {
        returnstore.state.Authorization
    }
    return '';
}

//上传请求头 登录验证
const uploadParameters = () =>{
    return { 'token': isToken() }
    //return { 'Authori-zation': 'Bearer ' + isToken() }
}

/*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,
    uploadParameters,
    happenTime,
    handleScroll
}

operate.ts

本文来自博客园,作者:虚乄,转载请注明原文链接:https://www.cnblogs.com/lovejielive/p/17676856.html

相关文章