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


    创建好vue3项目

    1.安装Axios与Element Plus

    Axios安装

    1
    npm install axios

    Element Plus 安装

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

    1
    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 增加自定义参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    declare module 'axios' {
        //请求自定义参数
        interface AxiosRequestConfig {
            // 是否显示加载框
            ifLoading?: boolean
            // 是否允许重复请求
            repeatRequest?: boolean
            // 登录 token
            isToken?: any;
        }
    }

    3.1重复请求判断

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

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

    不存在pendingMap中新增一个key。

    通过AbortController来进行手动取消。

    主要代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    //格式化请求链接
    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) 过滤掉已取消的请求,

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

    3.2 axios完整代码

    api文件夹下 创建 request-wrapper.ts Axios封装
    copy
    /*
     * @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();
    }
    
    //格式化请求链接
    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”上不存在属性“code”
        // interface AxiosResponse {
        //     // 请求 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())
            }
            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 接口配置
    copy
    /*
     * @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
    开始请求
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    请求结果

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

    3.3 operate.ts 方法

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

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

    copy
    /*
     * @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 '';
    }
    
    //上传请求头 登录验证
    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

     

     

     

  • 相关阅读:
    【开源项目】个人如何在开源社区中成长?
    C#中在.NET 7.0控制台应用使用ADO.NET的方法
    怒刷LeetCode的第26天(Java版)
    SpringBoot整合Kafka (一)
    Kinodynamic RRT-connect(Rapidly-exploring Random Tree-Connect)算法例子
    RS笔记:深度推荐模型之SIM(基于搜索的超长行为序列上的用户长期兴趣建模)[CIKM 2020, 阿里妈妈广告团队]
    SDXL v1.0 多种风格提示词集锦
    Java 枚举数据转Map 查找 筛选
    司机调度问题
    2022暑假牛客多校2(G/K/D)
  • 原文地址:https://www.cnblogs.com/lovejielive/p/17676856.html