创建好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
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) }) */
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
请求结果
第一次请求被拦截,只有第二次成功返回
3.3 operate.ts 方法
主要放置一些 全局参数与方法。
在页面中可以通过 import operate from "@/utils/operate"
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 }