防抖节流自定义指令
一、问题现象
操作系统流程时,网速过慢,点击【按钮】,页面没有及时反应;用户感知不到,再次点击按钮,系统流程报错。
二、想法
控制按钮操作时的频繁接口调用,通过防抖操作进行处理
三、实现
第一步:封装自定义指令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加载
考虑到项目本身可能已经进入尾声,再应用自定义指令的话,需要耗费时间,逐一排查按钮操作,费时费力。
一、想法
想要减少时间,肯定需要统一配置处理,考虑到控制接口的频繁调用和操作频繁等问题,或许通过页面整体不可点击的方式进行处理,那就是接口调用时控制页面加载。
二、实现
- <!--
- * @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>
- 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);
- 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加载技巧的资料请关注w3xue其它相关文章!