前期回顾
从零搭建 Vue3 + VIte + Ts 项目 —— 并集成eslint 、prettier、stylelint、husky、lint-staged、pinia、axios、loding、动态路由…_彩色之外的博客-CSDN博客
实现功能:
以下功能经过测试,亲测 可跑,复制即可运行,原项目:Vite + Ts + Vue3 - template --- 模板: 🎉🎉🔥 Vite + Vue3 + Ts + router + Vuex + axios + eslint 、prettier、stylelint、husky、gitCommit--- 集成多种组件、Hooks支持开封即用,严格的代码质量检验、祝您轻松上大分😷🤺🤺🤺 【动态路由、特效、N个组件、N个自定义指令...】
取消重复请求:完全相同的接口在上一个pending状态时,自动取消下一个请求
请求失败自动重试: 接口请求后台异常时候, 自动重新发起多次请求, 直到达到所设次数
请求接口数据缓存: 接口在设定时间内不会向后台获取数据, 而是直接拿本地缓存
父页面单独取消当前请求、并发取消指定请求
父页面取消所有请求
更多功能根据你的需求自定制
目录
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>
全文结束,所有代码都在文中,最上面的链接中也有原项目
_______________________________ 期待再见 _______________________________