Vue3 + Ts + Vite 封装一套企业级axiso全流程

news/2024/5/19 10:15:17 标签: vue.js, 前端, javascript, vite, ts

9a69fede8b2044a79dd834e3e48f20b4.png前期回顾f8e3cc1a0f694ac2b665ca2ad14c49d7.png 

从零搭建 Vue3 + VIte + Ts 项目 —— 并集成eslint 、prettier、stylelint、husky、lint-staged、pinia、axios、loding、动态路由…_彩色之外的博客-CSDN博客

   实现功能:

  1. 以下功能经过测试,亲测 可跑,复制即可运行,原项目:Vite + Ts + Vue3 - template --- 模板: 🎉🎉🔥 Vite + Vue3 + Ts + router + Vuex + axios + eslint 、prettier、stylelint、husky、gitCommit--- 集成多种组件、Hooks支持开封即用,严格的代码质量检验、祝您轻松上大分😷🤺🤺🤺 【动态路由、特效、N个组件、N个自定义指令...】

  2. 取消重复请求:完全相同的接口在上一个pending状态时,自动取消下一个请求

  3. 请求失败自动重试: 接口请求后台异常时候, 自动重新发起多次请求, 直到达到所设次数

  4. 请求接口数据缓存: 接口在设定时间内不会向后台获取数据, 而是直接拿本地缓存

  5. 父页面单独取消当前请求、并发取消指定请求

  6. 父页面取消所有请求

  7. 更多功能根据你的需求自定制

目录

vite.config.ts%20%E5%9F%BA%E5%9C%B0%E5%9D%80%EF%BC%9A%C2%A0-toc" style="margin-left:0px;">  🌍 第一 配置 vite.config.ts 基地址: 

  🤖 第二 配置环境变量:

ts%E7%B1%BB%E5%9E%8B-toc" style="margin-left:0px;"> 🛹 第三  配置ts类型

 🪂 第四 封装本地存储

 🎋 第五 封装axios: 

 👀 第六 页面使用:


vite.config.ts%20%E5%9F%BA%E5%9C%B0%E5%9D%80%EF%BC%9A%C2%A0">🌍 第一 配置 vite.config.ts 基地址: 

  🤖 第二 配置环境变量:

ts%E7%B1%BB%E5%9E%8B">🛹 第三  配置ts类型

src/type/axiso.d.ts

javascript">/* eslint-disable */
import * as axios from "axios";

// 扩展 axios 数据返回类型,可自行扩展
declare module "axios" {
  export interface AxiosResponse<T = any> {
    code: number;
    data: T;
    message: string;
    type?: string;
    /* 
		[key: string]: T; 这段代码是定义了一个索引签名,它表示可以使用任意字符串作为key,并且对应的值的类型是T。
		索引签名允许在对象中使用动态的属性,也就是说,在定义AxiosResponse接口时,除了预定义的code、data、message属性,还可以添
		加其他任意属性,且属性的值的类型是T。
	*/
    [key: string]: T;
  }
  export interface AxiosRequestConfig<T = any> {
    retry?: number;
    retryDelay?: number;
    cache?: boolean;
    cacheTimestamp?: number;
    [key: string]: T;
  }
  export interface AxiosError<T = any> {
    config: AxiosRequestConfig<T>;
    code?: string;
    request?: any;
    response?: AxiosResponse<T>;
    isAxiosError: boolean;
    retry?: number;
    retryDelay?: number;
    retryCount: number;
    cache?: boolean;
    cacheTimestamp?: number;
    [key: string]: T;
  }

  export interface CancelTokenSource<T = any> {
    token: CancelToken;
    cancel: Canceler;
    isFinished?: boolean;
    [key: string]: T;
  }
}

 🪂 第四 封装本地存储

javascript">/**
 * window.localStorage 浏览器永久缓存
 * @method set 设置永久缓存
 * @method get 获取永久缓存
 * @method remove 移除永久缓存
 * @method clear 移除全部永久缓存
 */
export const Local = {
	// 设置永久缓存
	set(key: string, val: any) {
		window.localStorage.setItem(key, JSON.stringify(val));
	},
	// 获取永久缓存
	get(key: string) {
		let json = <string>window.localStorage.getItem(key);
        // !null为true
		if (!json) return null;
		return JSON.parse(json);
	},
	// 移除永久缓存
	remove(key: string) {
		window.localStorage.removeItem(key);
	},
	// 移除全部永久缓存
	clear() {
		window.localStorage.clear();
	},
};

/**
 * window.sessionStorage 浏览器临时缓存
 * @method set 设置临时缓存
 * @method get 获取临时缓存
 * @method remove 移除临时缓存
 * @method clear 移除全部临时缓存
 */
export const Session = {
	// 设置临时缓存
	set(key: string, val: any) {
		window.sessionStorage.setItem(key, JSON.stringify(val));
	},
	// 获取临时缓存
	get(key: string) {
		let json = <string>window.sessionStorage.getItem(key);
		if (!json) return null;
		return JSON.parse(json);
	},
	// 移除临时缓存
	remove(key: string) {
		window.sessionStorage.removeItem(key);
	},
	// 移除全部临时缓存
	clear() {
		window.sessionStorage.clear();
	},
};

  🎋 第五 封装axios: 

新建 \src\api 文件夹,里面有三个ts文件,request.ts 封装axios统一请求,requestMethod.ts   封装的是请求方法,api.ts 封装的是api接口,方便统一管理不至于api接口分散项目各处造成不易维护。

src\api\request.ts

第一种:

刷新页面或者第一次调用 不管是否有缓存,都会调用一次真正接口,之后会判断本地数据

javascript">import axios, {
	AxiosError,
	AxiosInstance,
	AxiosRequestConfig,
	CancelTokenSource,
} from "axios";
/* 
	1. 取消重复请求:完全相同的接口在上一个pending状态时,自动取消下一个请求 
	2. 请求失败自动重试: 接口请求后台异常时候, 自动重新发起多次请求, 直到达到所设次数 
	3. 请求接口数据缓存: 接口在设定时间内不会向后台获取数据, 而是直接拿本地缓存
	4. 父页面单独取消当前请求
	5. 父页面取消所有请求
*/

// 导入 element-plus 中的消息和弹框组件
import { ElMessage, ElMessageBox } from "element-plus";
// 导入 storage.ts 中的 Session 对象
import { Session } from "/@/utils/storage";
// 导入 main.ts 中的 app 对象 用于Loading组件的显示和隐藏
import app from "/@/main";

// 创建 Axios 实例
const service: AxiosInstance = axios.create({
	baseURL: import.meta.env.VITE_API_URL, // 设置基础 URL
	timeout: 5000, // 设置超时时间
	headers: { "Content-Type": "application/json" }, // 设置请求头
});

// handlerRequest Start --------------------------------------------------------------------------

// cacheTimestamp用于判断是否存在相同缓存的请求
let cacheTimestampFlag = 0;
// requestKey用于缓存接口函数 判断是否存在相同的请求
let requestKey = "";
// 创建一个存储请求的Map对象
const pendingRequests: Map<string, CancelTokenSource> = new Map();
// 取消重复请求的方法
const cancelDuplicateRequest = (config: AxiosRequestConfig): void => {
	// 生成请求的唯一标识
	requestKey = `${config.method}-${config.url}`;

	// 如果已经存在该请求,则取消该请求
	if (pendingRequests.has(requestKey)) {
		const cancelToken = pendingRequests.get(requestKey);
		cancelToken?.cancel(
			"进行中的重复请求被拦截,请您等待当前请求完成后再发起请求"
		);
	}
	// 生成一个取消请求的标识
	const cancelToken = axios.CancelToken.source();
	// 将该请求保存到 pendingRequests 中
	pendingRequests.set(requestKey, cancelToken);
	// 设置取消请求的标识
	config.cancelToken = cancelToken.token;
	// 设置缓存时间
	if (config.cacheTimestamp) {
		cacheTimestampFlag = eval(`1000 * 60 * ${config.cacheTimestamp}`);
	}
	// 如果本地有缓存数据,直接返回缓存数据,不经过请求拦截
	/* 
	当Promise被拒绝并且这个拒绝状态没有被处理(即没有调用.catch()方法)时,	会在控制台打印出一个未捕获的Promise拒绝警告。
	如果你不希望在控制台看到这个警告,你需要确保每个Promise都有一个.catch()方法来处理拒绝状态
	*/
	if (config.cache)
		requestIsCache()
			.then(() => {})
			.catch(() => {});
};
// 缓存接口函数 - 注意发起请求判断是否存在相同url需要再组件中调用此函数,不会进过这里的请求拦截器
async function requestIsCache(): Promise<any> {
	// 获取本地存储的所有键
	const keys = Object.keys(sessionStorage);
	if (requestKey && keys.includes(requestKey)) {
		// 停留时间 > 缓存时间阈值
		const isCache =
			Date.now() - Session.get(requestKey)?.cacheTimestamp > cacheTimestampFlag;
		// console.log('是否有key', keys.includes(requestKey));
		// console.log('停留时间', Date.now() - Session.get(requestKey)?.cacheTimestamp);
		// console.log('判断阈值', cacheTimestampFlag);

		// 如果包含 requestKey 并且 缓存未过期
		if (keys.includes(requestKey) && !isCache) {
			// 直接返回本地缓存数据
			const cacheData = Session.get(requestKey);
			return Promise.resolve(cacheData);
		} else {
			// 清除缓存
			Session.remove(requestKey);
			return Promise.reject("接口开启了catch,不存在缓存 或 缓存已过期");
		}
	} else {
		// 如果第一次直接在父组件调用此函数,会进入这里,因为不走请求拦截器没有requestKey
		// 之后在组件中调用此函数,会进入上面的判断,因为走请求拦截器有了requestKey。
		// 当你想要刷新页面不管是否有缓存,都重新请求接口,可以在组件中调用此函数,前提是当前接口
		// 是第一次请求,比如你的页面中页面加载会自动调一次当前接口,这个时候你在刷新是不会重新请求接口的
		// 因为当前接口走了请求拦截器,有了requestKey,所以会进入上面的判断,直接返回缓存数据
		// 总结:只有是第一次调用当前接口,才会进入这里,之后在组件中调用此函数,都会进入上面的判断
		return Promise.reject("本地不存在缓存");
	}
}

/**
 * 错误提示方法
 * @method tipError
 * @param { string }  value 提示内容
 * @param { string }  title 提示标题
 * @description 重复登录、token过期、
 * @author zk
 * @createDate 2023/08/14 14:25:52
 * @lastFixDate 2023/08/14 14:25:52
 */
const tipError = (value: string, title: string) => {
  ElMessageBox.alert(value, title, {
    confirmButtonText: "重新登录",
    type: "warning",
  }).then(() => {
    Session.clear(); // 清除临时缓存
    // 清除cookie
    document.cookie.split(";").forEach(function (c) {
      document.cookie = c
        .replace(/^ +/, "")
        .replace(/=.*/, "=;expires=" + new Date().toUTCString() + ";path=/");
    });
    window.location.reload(); // 刷新页面
  });
};
// 请求失败自动重试的方法
const retryFailedRequest = async (error: AxiosError): Promise<any> => {
	const config = error;
	// 如果没有设置重试次数 或 已经达到最大重试次数,则直接返回错误
	if (!config || !config.retry || config.retryCount >= config.retry) {
		return Promise.reject(config);
	}
	// 设置重试次数关闭阈值
	config.retryCount = config.retryCount || 0;
	// 重试次数自增
	config.retryCount += 1;
	// 设置重试延时
	const delay = config.retryDelay || 1000;
	// 延时处理
	await new Promise<void>((resolve) => {
		setTimeout(() => resolve(), delay);
	});
	// console.log('🤺🤺  🚀 ==>:', service(config));
	return await service(config);
};
// handlerRequest End --------------------------------------------------------------------------

// Axios 的请求拦截器期望返回一个配置对象,而不是响应对象。如果你试图返回一个响应对象,Axios 将会抛出一个错误。
service.interceptors.request.use(
	async (config: AxiosRequestConfig) => {
		// 在发送请求之前做些什么?
		const token = Session.get("token");
		if (token) config.headers!["token"] = token; // 在请求头中添加 token
		// 取消重复请求
		cancelDuplicateRequest(config);

		app.config.globalProperties.$smallLoading.showLoading();
		return config;
	},
	async (error) => {
		// 对请求错误做些什么?
		app.config.globalProperties.$smallLoading.hideLoading();
		// 请求失败重试
		await retryFailedRequest(error);
		return Promise.reject(error);
	}
);

// 响应拦截器
service.interceptors.response.use(
	(response) => {
	    // 对响应数据做点什么? 这里只返回成功响应http数据!
    const {
      config,
      data,
      data: { code: codes },
    } = response;
    // http状态是200 但是code不是200 返回数据是错误的需要return
    if (codes !== 200) {
      // 如果后台返回的错误码为 100010016 重复登录、100010011 token过期、100010012 token可能被篡改
      // if (codes === 100010016 || codes === 100010011 || codes === 100010012) {
      app.config.globalProperties.$smallLoading.hideLoading();
      return tipError(data.message, "错误提示");
      // }
    }
		// 给 pendingRequests 标记一个isFinished为true 请求完成的标识
		const responseKey = `${config.method}-${config.url}`;
		const request = pendingRequests.get(responseKey);
		if (request && request.token) {
			pendingRequests.set(responseKey, { ...request, isFinished: true });
		}

		app.config.globalProperties.$smallLoading.hideLoading();

		// 判断是否有缓存
		if (config.cache) {
			const cachedResponse = Session.get(responseKey);
			if (cachedResponse) {
				return cachedResponse;
			} else {
				// 接口有 cache 参数,且缓存不存在,则缓存接口数据,并插入当前时间戳
				data.cacheTimestamp = new Date().getTime();
				Session.set(responseKey, data);
				return data;
			}
		} else {
			return data;
		}
	},
	(error) => {
		// 对响应错误数据做点什么?这里只显示错误消息!
		app.config.globalProperties.$smallLoading.hideLoading();
		/* 
			axios.isCancel(error) 是 Axios 库中的一个方法,用于判断一个错误对象是否是由于请求取消导致的。
			当使用 axios.CancelToken 取消请求时,会抛出一个带有一个 message 属性的错误对象。
			axios.isCancel(error) 的作用就是判断这个错误对象的类型,如果是由请求取消导致的错误,则返回 true,否则返回 false。
		    console.log('打印cancelToken.cancel('xxx')传入来的值', error.message);
	    */
		if (axios.isCancel(error)) {
			// 只提示请求取消有主动填写的消息 如:cancelToken.cancel('xxx')
			if (error.message !== "canceled")
				ElMessage.error(requestKey + " 🤖 " + error.message);
		} else {
			// 响应失败重试
			retryFailedRequest(error);
			// 不是由请求取消导致的错误
			let errorMessage; // 错误提示变量
			let statusData = error.response?.data; // 错误data数据
			const describeForNameMap = [
				[
					() => error.message.indexOf("timeout") !== -1,
					() => (errorMessage = "网络超时 🤖"),
				],
				[
					() => error.message === "Network Error",
					() => (errorMessage = "网络连接错误 🤪"),
				],
				// 否则 显示错误消息,这里要根据后台返回的数据结构来定
				[() => statusData, () => (errorMessage = statusData.message)],
			];
			// 获取符合条件的子数组
			const getDescribe = describeForNameMap.find((item) => item[0]());
			// 执行子数组中的函数
			getDescribe && getDescribe[1]();
			ElMessage.error(errorMessage); // 显示错误消息
		}
	}
);
// 取消全部请求的方法
export const cancelAllRequest = (): void => {
	// 创建一个标记 是否取消成功,初始值为false
	let hasCancelled = false;

	// 遍历所有待处理的请求
	pendingRequests.forEach((value) => {
		// 如果请求还没有完成
		if (!value.isFinished) {
			// 取消请求
			value.cancel();
			// 将标记设为true
			hasCancelled = true;
		}
	});

	// 清空待处理请求的集合
	pendingRequests.clear();

	// 至少取消了一个请求,显示提示,防止都是成功请求点击取消按钮时也提示
	if (hasCancelled) {
		ElMessage.success("成功取消全部请求");
	}
};

// 取消当前请求的方法
export const cancelCurrentRequest = (
	payloadCurrentKey: string = requestKey
): void => {
	// 遍历所有待处理的请求
	pendingRequests.forEach((value, key) => {
		if (key === payloadCurrentKey && !value.isFinished) {
			value.cancel();
			pendingRequests.delete(key);
			ElMessage.success("成功取消当前请求");
		}
	});
};

// 导出 service将其命名为axios , requestIsCache 用于判断是否有缓存
export { service as axios, requestIsCache };

第二种:
不论是否刷新还是第一次调用精准根据本地缓存判断是否来调用本地缓存数据

javascript">import axios, {
  AxiosInstance,
  AxiosRequestConfig,
  CancelTokenSource,
  AxiosError,
} from "axios";
/* 
	1. 取消重复请求:完全相同的接口在上一个pending状态时,自动取消下一个请求 
	2. 请求失败自动重试: 接口请求后台异常时候, 自动重新发起多次请求, 直到达到所设次数 
	3. 请求接口数据缓存: 接口在设定时间内不会向后台获取数据, 而是直接拿本地缓存
	4. 父页面单独取消当前请求
	5. 父页面取消所有请求
*/

// 导入 element-plus 中的消息和弹框组件
import { ElMessage, ElMessageBox } from "element-plus";
// 导入 storage.ts 中的 Session 对象
import { Session } from "@/utils/storage";
// 导入 main.ts 中的 app 对象 用于Loading组件的显示和隐藏
import app from "@/main";

// 创建 Axios 实例
const service: AxiosInstance = axios.create({
  baseURL: import.meta.env.VITE_API_URL, // 设置基础 URL
  timeout: 5000, // 设置超时时间
  headers: { "Content-Type": "application/json" }, // 设置请求头
});

// handlerRequest Start --------------------------------------------------------------------------
// requestKey用于缓存接口函数 判断是否存在相同的请求
let requestKey = "";
// 创建一个存储请求的Map对象
const pendingRequests: Map<string, CancelTokenSource> = new Map();
// 取消重复请求的方法
const cancelDuplicateRequest = (config: AxiosRequestConfig): void => {
  // 生成请求的唯一标识
  requestKey = `${config.method}-${config.url}`;
  // 如果已经存在该请求,则取消该请求
  if (pendingRequests.has(requestKey)) {
    const cancelToken = pendingRequests.get(requestKey);
    cancelToken?.cancel(
      "进行中的重复请求被拦截,请您等待当前请求完成后再发起请求"
    );
  }
  // 生成一个取消请求的标识
  const cancelToken = axios.CancelToken.source();
  // 将该请求保存到 pendingRequests 中
  pendingRequests.set(requestKey, cancelToken);
  // 设置取消请求的标识
  config.cancelToken = cancelToken.token;
};
type cacheTimestamp = 1 | 2 | 3 | 4 | 5;
/**
 * 接口缓存
 * @method requestIsCache
 * @param { string } payloadUrl 请求方法-api地址 require
 * @param { number } responseConfigCacheFlag 1-5(分钟) 缓存阈值 require
 * @author zk
 * @createDate 2023/08/14 14:19:52
 * @lastFixDate 2023/08/14 14:19:52
 */
function requestIsCache(
  payloadUrl: string,
  responseConfigCacheFlag: cacheTimestamp
): Promise<any> {
  // 获取本地存储的所有键
  const keys = Object.keys(sessionStorage);

  // 如果本地存储有数据就判断 是否存在相同的请求和缓存是否过期
  if (keys.length > 0) {
    // 停留时间 > 缓存时间阈值
    const isCache =
      Date.now() - Session.get(payloadUrl)?.cacheTimestamp >
      1000 * 60 * responseConfigCacheFlag;
    // console.log(
    // 	"停留时间",
    // 	Date.now() - Session.get(payloadUrl)?.cacheTimestamp
    // );
    // console.log("判断阈值", 1000 * 60 * responseConfigCacheFlag);
    // console.log("是否有key", keys.includes(payloadUrl));
    // console.log("是否过期 ==>:", isCache); // 过期 true 未过期 false
    // 如果包含 payloadUrl 并且 缓存未过期
    if (keys.includes(payloadUrl) && !isCache) {
      // 直接返回本地缓存数据
      const cacheData = Session.get(payloadUrl);
      return Promise.resolve(cacheData);
    } else {
      // 清除缓存
      Session.remove(payloadUrl);
      return Promise.reject("本地不存在当前接口缓存或者缓存已过期");
    }
  } else {
    return Promise.reject("本地不存在缓存");
  }
}

/**
 * 错误提示方法
 * @method tipError
 * @param { string }  value 提示内容
 * @param { string }  title 提示标题
 * @description 重复登录、token过期、
 * @author zk
 * @createDate 2023/08/14 14:25:52
 * @lastFixDate 2023/08/14 14:25:52
 */
const tipError = (value: string, title: string) => {
  ElMessageBox.alert(value, title, {
    confirmButtonText: "重新登录",
    type: "warning",
  }).then(() => {
    Session.clear(); // 清除临时缓存
    // 清除cookie
    document.cookie.split(";").forEach(function (c) {
      document.cookie = c
        .replace(/^ +/, "")
        .replace(/=.*/, "=;expires=" + new Date().toUTCString() + ";path=/");
    });
    window.location.reload(); // 刷新页面
  });
};

// 请求失败自动重试的方法 请求失败时、响应失败时、重试请求会触发
const retryFailedRequest = async (error: AxiosError): Promise<any> => {
  const config = error;
  // 如果没有设置重试次数 或 已经达到最大重试次数,则直接返回错误
  if (!config || !config.retry || config.retryCount >= config.retry) {
    return Promise.reject(config);
  }
  // 设置重试次数关闭阈值
  config.retryCount = config.retryCount || 0;
  // 重试次数自增
  config.retryCount += 1;
  // 设置重试延时
  const delay = config.retryDelay || 1000;
  // 延时处理
  await new Promise<void>((resolve) => {
    setTimeout(() => resolve(), delay);
  });
  // 注意 需要将请求设置为异步,如果同步会发出多个
  // 使用 await 关键字等待 service(config) 完成,然后再返回结果。这样,每次重试都会等待上一次请求完成
  return await service(config);
};
// handlerRequest End --------------------------------------------------------------------------

// Axios 的请求拦截器期望返回一个配置对象,而不是响应对象。如果你试图返回一个响应对象,Axios 将会抛出一个错误。
service.interceptors.request.use(
  (config) => {
    // 在发送请求之前做些什么?
    const token = Session.get("token");
    if (token) config.headers!["token"] = token; // 在请求头中添加 token
    // 取消重复请求
    cancelDuplicateRequest(config);
    // retryFailedRequest(config); //如果要测试重发请求,可以打开这个注释,关闭取消重复请求
    app.config.globalProperties.$smallLoading.showLoading();
    return config;
  },
  async (error) => {
    // 对请求错误做些什么?
    app.config.globalProperties.$smallLoading.hideLoading();
    // 请求失败重试
    await retryFailedRequest(error);
    return Promise.reject(error);
  }
);

// 响应拦截器
service.interceptors.response.use(
  (response) => {
    // 对响应数据做点什么? 这里只返回成功响应http数据!
    const {
      config,
      data,
      data: { code: codes },
    } = response;
    // http状态是200 但是code不是200 返回数据是错误的需要return
    if (codes !== 200) {
      // 如果后台返回的错误码为 100010016 重复登录、100010011 token过期、100010012 token可能被篡改
      // if (codes === 100010016 || codes === 100010011 || codes === 100010012) {
      app.config.globalProperties.$smallLoading.hideLoading();
      return tipError(data.message, "错误提示");
      // }
    }
    // 给 pendingRequests 标记一个isFinished为true 请求完成的标识
    const responseKey = `${config.method}-${config.url}`;
    const request = pendingRequests.get(responseKey);
    if (request && request.token) {
      pendingRequests.set(responseKey, { ...request, isFinished: true });
    }

    app.config.globalProperties.$smallLoading.hideLoading();

    // 判断是否有缓存
    if (config.cache) {
      const cachedResponse = Session.get(responseKey);
      if (cachedResponse) {
        return cachedResponse;
      } else {
        // 接口有 cache 参数,且缓存不存在,则缓存接口数据,并插入当前时间戳
        data.cacheTimestamp = new Date().getTime();
        Session.set(responseKey, data);
        return data;
      }
    } else {
      return data;
    }
  },
  (error) => {
    // 对响应错误数据做点什么?这里只显示错误消息!
    app.config.globalProperties.$smallLoading.hideLoading();
    /* 
			axios.isCancel(error) 是 Axios 库中的一个方法,用于判断一个错误对象是否是由于请求取消导致的。
			当使用 axios.CancelToken 取消请求时,会抛出一个带有一个 message 属性的错误对象。
			axios.isCancel(error) 的作用就是判断这个错误对象的类型,如果是由请求取消导致的错误,则返回 true,否则返回 false。
		    console.log('打印cancelToken.cancel('xxx')传入来的值', error.message);
	    */
    if (axios.isCancel(error)) {
      // 只提示请求取消有主动填写的消息 如:cancelToken.cancel('xxx')
      if (error.message !== "canceled")
        ElMessage.error(" 🤖 " + error.message + "---" + requestKey);
    } else {
      // 响应失败重试
      retryFailedRequest(error);
      // 不是由请求取消导致的错误
      let errorMessage; // 错误提示变量
      const statusData = error.response?.data; // 错误data数据
      const describeForNameMap = [
        [
          () => error.message.indexOf("timeout") !== -1,
          () => (errorMessage = "网络超时 🤖"),
        ],
        [
          () => error.message === "Network Error",
          () => (errorMessage = "网络连接错误 🤪"),
        ],

        // 否则 显示错误消息,这里要根据后台返回的数据结构来定
        [() => statusData, () => (errorMessage = statusData.message)],
      ];
      // 获取符合条件的子数组
      const getDescribe = describeForNameMap.find((item) => item[0]());
      // 执行子数组中的函数
      getDescribe && getDescribe[1]();
      ElMessage.error(errorMessage); // 显示错误消息
    }
  }
);
// 取消全部请求的方法
export const cancelAllRequest = (): void => {
  // 创建一个标记 是否取消成功,初始值为false
  let hasCancelled = false;

  // 遍历所有待处理的请求
  pendingRequests.forEach((value) => {
    // 如果请求还没有完成
    if (!value.isFinished) {
      // 取消请求
      value.cancel();
      // 将标记设为true
      hasCancelled = true;
    }
  });

  // 清空待处理请求的集合
  pendingRequests.clear();

  // 至少取消了一个请求,显示提示,防止都是成功请求点击取消按钮时也提示
  if (hasCancelled) {
    ElMessage.success("成功取消全部请求");
  }
};

// 取消当前请求的方法
export const cancelCurrentRequest = (
  payloadCurrentKey: string = requestKey
): void => {
  // 遍历所有待处理的请求
  pendingRequests.forEach((value, key) => {
    // 传过来key和请求的key相同,且请求还没有完成
    if (key === payloadCurrentKey && !value.isFinished) {
      value.cancel();
      pendingRequests.delete(key);
      ElMessage.success("成功取消当前请求");
    }
  });
};

// 导出 service将其命名为serviceAxios , requestIsCache 用于判断是否有缓存
export { service as serviceAxios, requestIsCache };

src\api\requestMethod.ts

对应封装axios第一种:
 

javascript">import { axios } from "./request";
// post使用data接受参数,get使用params接受参数
// 如果是post请求,但是参数是在url上的,那么就要使用params接受参数,否则使用data接受参数
// put 也相当与post请求,如果报参数错误,就是接受参数的请求体错了post/put用data,get请求用params
type method = "GET" | "POST" | "PUT" | "DELETE";
// 规定缓存时间戳的类型只能 1 - 5 分钟
type cacheTimestamp = 1 | 2 | 3 | 4 | 5;

/**
 * @name request 配置
 * @param { string } - method 请求方法
 * @param { string } - url   请求地址
 * @param { object } - data/params 请求参数
 * @param { number } - retry  重试次数
 * @param { number } - retryDelay 重试延迟
 * @param { boolean } -  cache 是否缓存
 * @param { number }  - cacheTimestamp 缓存过期 1-5分钟
 * @createDate 2023/08/09 13:12:08
 * @lastFixDate 2023/08/09 13:12:08
 */
interface requestConfig<T = any> {
	method: method;
	url: string;
	data?: T;
	params?: T;
	retry?: number;
	retryDelay?: number;
	cache?: boolean;
	cacheTimestamp?: cacheTimestamp;
}

function request({
	method = "GET",
	url,
	data = {},
	params = {},
	retry,
	retryDelay,
	cache,
	cacheTimestamp = 1,
}: requestConfig<T>) {
	return axios({
		method,
		url,
		data,
		params,
		retry,
		retryDelay,
		cache,
		cacheTimestamp,
	});
}

export default request;

第二种 

javascript">import { serviceAxios } from "./serves";
// post使用data接受参数,get使用params接受参数
// 如果是post请求,但是参数是在url上的,那么就要使用params接受参数,否则使用data接受参数
// put 也相当与post请求,如果报参数错误,就是接受参数的请求体错了post/put用data,get请求用params
type Method = "GET" | "POST" | "PUT" | "DELETE";

interface requestConfig {
  method: Method;
  url: string;
  data?: object;
  params?: object;
  retry?: number;
  retryDelay?: number;
  cache?: boolean;
}
/**
 * @name request 配置
 * @param { string }  method 请求方法
 * @param { string }  url   请求地址
 * @param { object }  data/params 请求参数
 * @param { number }  retry  重试次数
 * @param { number }  retryDelay 重试延迟
 * @param { boolean }   cache 是否缓存
 * @createDate 2023/08/09 13:12:08
 * @lastFixDate 2023/08/09 13:12:08
 */
function request({
  method = "GET",
  url,
  data = {},
  params = {},
  retry,
  retryDelay,
  cache,
}: requestConfig) {
  return serviceAxios({
    method,
    url,
    data,
    params,
    retry,
    retryDelay,
    cache,
  });
}

export default request;

src\api/auth-manage/menu.ts

javascript">// 导入axios实例中的AxiosResponse泛型接口
import { AxiosResponse } from "axios";
//导入封装的axios请求方法
import request from "-/requestMethod";

//  如果是get请求不需要写method,post请求使用data请求体 默认封装的get
// post示例
//  export const login = (data) => request({ method: "post", url: '/login', data: data });

// get示例
//  export const getUserList = (params) => request({ url: '/users', params });

// put示例
//     export const getEdit = (data) => request({
//      method: "put",
//      data,
//      url: "users/" + data.uid + "/state/" + data.type,
//    })

export const history = (): Promise<AxiosResponse<any, any>> =>
	request({
		method: "GET",
		url: "common/history",
		cache: true,
		retry: 3,
	});

/**
 * @name chat接口
 * @param { object }  params 请求参数
 * @description 接口缓存、重试3次(请求、响应失败时)
 * @author zk
 * @createDate 2023/08/14 14:57:20
 * @lastFixDate 2023/08/14 14:57:20
 */
export const chat = (params: object): Promise<AxiosResponse<any, any>> =>
	request({
		method: "GET",
		url: "ai/chat",
		cache: true,
		retry: 3,
		params,
	});

👀 第六 页面使用:

javascript"><script setup lang="ts">
/*
 requestIsCache - 判断请求是否开启了缓存
 cancelAllRequest - 取消所有请求
 cancelCurrentRequest - 取消当前请求
*/
import {
	requestIsCache,
	cancelAllRequest,
	cancelCurrentRequest,
} from "-/request";
import { AxiosResponse } from "axios";
import { ElMessage } from "element-plus";
import { chat, history } from "-/login";

// 封装请求错误提示 http状态是200 但是code不是200 返回数据是错误的需要处理
function tipError<T extends AxiosResponse>(res: T) {
	if (res.code !== 200) {
		ElMessage.error(res.msg);
		return;
	}
}

// 发起 chat
const getA = async () => {
	// 缓存函数,如果在接口开启了cache: true,需要在请求前调用此函数
	await requestIsCache("get-ai/chat", 1)
		.then((res) => {
			if (!res) return;
			tipError(res);
			ElMessage.success("✈️ 本地数据请求成功----" + res.result.displayText);
		})
		.catch(() => {
			// 真正接口
			chat({ text: "张坤" }).then((res) => {
				if (!res) return;
				tipError(res);
				ElMessage.success("🤖 接口数据-----" + res.result.displayText);
			});
		});
};

// 取消 chat
const cancelA = () => {
	// 在适当的时机调用取消请求(例如点击取消按钮),不传参数默认取消最后一条请求
	cancelCurrentRequest("get-ai/chat");
};

// 发起 history
const getB = async () => {
	await history().then((res) => {
		if (!res) return;
		tipError(res);
		ElMessage.success("🤖 接口数据" + res.msg);
	});
};

// 取消 history
const cancelB = () => {
	cancelCurrentRequest();
};

// 取消所有请求
function cancelAll() {
	cancelAllRequest();
}
</script>

<template>
	<div>
		<!-- 发起 -->
		<el-button type="primary" @click="getA">发起A</el-button>
		<!-- 取消 -->
		<el-button type="danger" @click="cancelA">取消A</el-button>
		<!-- 发起 -->
		<el-button type="primary" @click="getB">发起B</el-button>
		<!-- 取消 -->
		<el-button type="danger" @click="cancelB">取消B</el-button>
		<el-button type="danger" @click="cancelAll">取消所有请求</el-button>
	</div>
</template>

全文结束,所有代码都在文中,最上面的链接中也有原项目

7730e2bd39d64179909767e1967da702.jpeg

 _______________________________  期待再见  _______________________________ 


http://www.niftyadmin.cn/n/4942814.html

相关文章

Intel 12代酷睿集体大降价!三折太离谱了

之前有德国媒体报道称&#xff0c;Intel 12/13代酷睿以及即将发布的14代酷睿&#xff0c;将会全面涨价。 没想到&#xff0c;12代酷睿大降价了&#xff0c;幅度相当不可思议&#xff0c;不过至少目前仅限美国市场&#xff0c;新蛋、亚马逊、MicroCenter等大型零售商集体行动。 …

NuGet控制台命令初步使用

查看所有安装的包&#xff0c; 查找包&#xff0c;提示Nuget版本低&#xff1b;安装一个更高版本&#xff1b; 查看所有安装的包&#xff0c; 查找名字包含某字符串的包&#xff0c; 查找名字包含某字符串的包&#xff0c; 安装&#xff0c;使用-version指定版本&#xff0c;可…

Flink 流式读写文件、文件夹

文章目录 一、flink 流式读取文件夹、文件二、flink 写入文件系统——StreamFileSink三、查看完整代码 一、flink 流式读取文件夹、文件 Apache Flink针对文件系统实现了一个可重置的source连接器&#xff0c;将文件看作流来读取数据。如下面的例子所示&#xff1a; StreamExe…

关于consul的下载方法

linux下 sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo sudo yum -y install consulwindow下 https://developer.hashicorp.com/consul/downloads 然后把里面的exe文件放在gopath下就行了 验证…

WSL2 ubuntu子系统换源

文章目录 1.直接编辑/etc/apt/sources.list文件&#xff08;需要使用 sudo&#xff09;:2.将文件中的内容删除&#xff0c;将以下对应版本镜像源复制到里面。ubuntu的镜像源 3.更新 1.直接编辑/etc/apt/sources.list文件&#xff08;需要使用 sudo&#xff09;: 将原文件做备份…

【Vue-Router】路由元信息

路由元信息&#xff08;Route Meta Information&#xff09;是在路由配置中为每个路由定义的一组自定义数据。这些数据可以包含任何你希望在路由中传递和使用的信息&#xff0c;比如权限、页面标题、布局设置等。Vue Router 允许你在路由配置中定义元信息&#xff0c;然后在组件…

OCR相关模块——版面分析技术、表格文本识别

OCR相关模块——版面分析技术、表格文本识别 版面分析技术表格识别技术 版面分析技术 版面分析模型&#xff1a;飞桨用到了yolov2检测模型&#xff0c;对文档图片中的文本、表格、图片、标题与列表区域进行检测。当前主流是用分割做。 表格识别技术 参考博文

系统架构设计师考试---论文写作应试技巧

软考论⽂写作⽅法及规范在考前需要了解清楚,这⾥就为⼤家详细介绍了软考论⽂的写作⽅法、写作规范及写作技巧。 ⾼级软考考试中⼀共有三科,下午考试可以说是重中之重,⽽对于⼤部分考⽣⽽⾔,论⽂是⾼级软考⾥⾯最难的,对⽆相关⼯作经验、⾮科班、⽆项⽬经验的“三⽆”考⽣来…