防抖节流自定义指令
一、问题现象
二、想法
三、实现
loading加载
一、想法
二、实现
总结
防抖节流自定义指令 一、问题现象操作系统流程时,网速过慢,点击【按钮】,页面没有及时反应;用户感知不到,再次点击按钮,系统流程报错。
二、想法控制按钮操作时的频繁接口调用,通过防抖操作进行处理
三、实现第一步:封装自定义指令v-debounce
import Vue from 'vue';
//按钮防抖动指令
Vue.directive('debounce', {
inserted(el, binding) {
el.addEventListener('click', () => {
if (!el.disabled) {
el.disabled = true;
setTimeout(() => {
el.disabled = false;
}, binding.value || 3 * 1000); // 三秒之内点击不会触发接口调用
}
});
},
});
第二步:在main.js文件中引入文件
import '@/utils/directives.js';
第三步:项目的template中使用
<el-button type="primary" class="change-btns" @click="sendCode()" v-debounce>发送验证码</el-button>
loading加载
考虑到项目本身可能已经进入尾声,再应用自定义指令的话,需要耗费时间,逐一排查按钮操作,费时费力。
一、想法想要减少时间,肯定需要统一配置处理,考虑到控制接口的频繁调用和操作频繁等问题,或许通过页面整体不可点击的方式进行处理,那就是接口调用时控制页面加载。
二、实现首先写一个loading.vue组件
<!--
* @Author: Winter_Bear
* @Date: 2023-03-25 16:18:16
* @LastEditors: zh
* @LastEditTime: 2023-03-25 16:55:18
* @Description: loading组件
-->
<template>
<div v-if="visable" class="loaidng">
<transition name="animation">
<div class="load">
<img alt="" class="img" src="@/assets/image/loading.png" />
</div>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
visable: false,
};
},
};
</script>
<style scoped>
.loaidng {
width: 100% !important;
height: 100% !important;
display: -webkit-flex !important; /* 新版本语法: Chrome 21+ */
display: -webkit-box !important; /* 老版本语法: Safari, iOS, Android browser, older WebKit browsers. */
display: -moz-box !important; /* 老版本语法: Firefox (buggy) */
display: -ms-flexbox !important; /* 混合版本语法: IE 10 */
display: flex !important;
justify-content: center !important;
align-items: center !important;
position: fixed !important;
top: 0 !important;
right: 0 !important;
bottom: 0 !important;
left: 0 !important;
background: rgba(0, 0, 0, 0);
color: #282828;
font-size: 20px;
z-index: 999999;
}
.load {
background-clip: text;
-webkit-position: relative !important;
position: relative !important;
}
.img {
width: 75px;
animation: rotation 5s linear infinite;
animation: rotation 2s linear infinite;
-moz-user-select: -moz-none;
-khtml-user-select: none;
-webkit-user-select: none;
-o-user-select: none;
user-select: none;
}
.no-scroll {
height: 100vh;
}
.no-scroll > * {
position: sticky;
top: 0;
}
@keyframes rotation {
0% {
-webkit-transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
}
}
</style>
封装loading.js文件
import Loading from './index.vue';
//先创建一个空实例
let instance = null;
let winX = null;
let winY = null;
window.addEventListener('scroll', function () {
if (winX !== null && winY !== null) {
window.scrollTo(winX, winY);
}
});
function disableWindowScroll() {
winX = window.scrollX;
winY = window.scrollY;
}
function enableWindowScroll() {
winX = null;
winY = null;
}
export default {
install(Vue) {
if (!instance) {
//构造器 /子类
let MyLoading = Vue.extend(Loading);
instance = new MyLoading({
//创建一个div,并挂载上去
el: document.createElement('div'),
});
document.body.appendChild(instance.$el);
}
//自定义一些方法,操作loading的显示与隐藏关
let customMethods = {
async start() {
console.log(instance);
instance.visable = true;
disableWindowScroll();
var mo = function (e) {
passive: false;
};
},
finish() {
instance.visable = false;
enableWindowScroll();
var mo = function (e) {
passive: false;
};
},
};
//挂载到自定义方法vue示例上
if (!Vue.$loading) {
Vue.$loading = customMethods;
//挂载到原型上
Vue.prototype.$loading = Vue.$loading;
} else {
console.log('$loading方法已被占用');
}
},
};
3.在main.js中挂载到全局Vue的原型上
import $loading from '@/components/loading/loading.js';
Vue.use($loading);
在request.js接口请求和响应拦截时做处理
import Vue from 'vue';
import axios from 'axios';
import store from '@/store';
import router from '@/router';
import messageup from './resetMessage.js';
import commonService from '@/api/common.js';
import storage from '@/utils/storage';
import { setToken, setRefreshToken } from '@/utils/auth.js';
const service = axios.create({
baseURL: process.env.VUE_APP_appBaseUrl,
// 跨域请求时是否需要使用凭证
withCredentials: false,
// 请求 1000s 超时
timeout: 1000 * 60 * 60,
});
let loadingSum = 0;
let isRefreshing = false; // 标记是否正在刷新 token, 防止多次刷新token
let requests = []; // 存储待重发请求的数组(同时发起多个请求的处理)
// 请求拦截器
service.interceptors.request.use(
(config) => {
loadingSum++;
if (loadingSum == 1) {
Vue.$loading.start();
}
let EnterpriseToken = '';
storage.get('CLIENTID', (data) => {
EnterpriseToken = data;
});
if (EnterpriseToken) {
config.headers.EnterpriseToken = EnterpriseToken;
}
return config;
},
(error) => {
messageup({
message: '服务异常!',
type: 'error',
showClose: true,
duration: 0,
});
return Promise.resolve(error);
},
);
// 响应拦截器
service.interceptors.response.use(
(response) => {
let config = response.config;
let url = response.config.url;
const code = response.data.code;
loadingSum--;
if (loadingSum == 0) {
Vue.$loading.finish();
}
if (['701', '702'].includes(code)) {
storage.removeAll();
router.replace('/sign').catch((err) => err);
messageup({
message: response.data.message,
type: 'error',
});
return;
} else if (code == '801') {
//这部分属于强制登录的逻辑状态处理
if (!isRefreshing) {
loadingSum++;
if (loadingSum == 1) {
Vue.$loading.start();
}
isRefreshing = true;
let getRefreshToken = '';
storage.get('REFCLIENTID', (data) => {
getRefreshToken = data;
});
if (getRefreshToken) {
return new Promise((resolve, reject) => {
let data = {
refreshToken: getRefreshToken,
};
commonService
.refreshToken(data)
.then((res) => {
if (res && res.data && res.data.code == '200') {
const { clientid, refreshid } = res.data.data;
setToken(clientid);
setRefreshToken(refreshid);
config.headers.EnterpriseToken = clientid;
// token 刷新后将数组的方法重新执行
requests.forEach((cb) => cb(clientid));
requests = []; // 重新请求完清空
resolve(service(config));
} else {
requests = [];
storage.removeAll();
router.replace('/sign').catch((err) => err);
}
})
.catch((err) => {
return Promise.reject(err);
})
.finally(() => {
isRefreshing = false;
loadingSum--;
if (loadingSum == 0) {
Vue.$loading.finish();
}
});
});
} else {
loadingSum--;
if (loadingSum == 0) {
Vue.$loading.finish();
}
}
} else {
// 返回未执行 resolve 的 Promise
return new Promise((resolve) => {
// 用函数形式将 resolve 存入,等待刷新后再执行
requests.push((token) => {
config.headers.EnterpriseToken = token;
resolve(service(config));
});
});
}
} else {
return response;
}
},
(error) => {
loadingSum--;
if (loadingSum == 0) {
Vue.$loading.finish();
}
messageup({
message: error.message,
type: 'error',
showClose: true,
duration: 0,
});
return Promise.reject(error);
},
);
export default {
post(url, data = {}, headers = {}) {
return new Promise((resolve, reject) => {
service.post(url, data, headers).then(
(response) => {
resolve(response);
},
(err) => {
reject(err);
},
);
});
},
get(url, params = {}, headers = {}) {
return new Promise((resolve, reject) => {
service
.get(url, {
params: params,
headers: headers,
})
.then((response) => {
resolve(response);
})
.catch((err) => {
reject(err);
});
});
},
when(arry = []) {
if (arry.length <= 1) {
return arry[0];
} else {
let arr = [];
let length = arry.length;
for (let i = 0; i < length; i++) {
arr.push('res' + i);
}
return new Promise((resolve, reject) => {
axios.all(arry).then(
axios.spread((...arr) => {
resolve(arr);
}),
);
});
}
},
};
总结
axios拦截应用loading的时机:
1.请求拦截时统计所有接口请求的次数,逐次累计;接口可能一次调很多个,但是仅首次调用时出现loading,后面再有接口调用时,就不再出现。
2.响应拦截时统一也会统计所有接口请求次数,逐次累减;直到最终接口不再调用时,停止loading效果。
3.单纯响应拦截过程中存在累计和累减;801登录态强制更新时也同样存在累计和累减。
以上就是JS实用技巧实现loading加载示例详解的详细内容,更多关于JS loading加载技巧的资料请关注软件开发网其它相关文章!