经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » JavaScript » 查看文章
JS实用技巧实现loading加载示例详解
来源:jb51  时间:2023/3/27 14:48:43  对本文有异议

防抖节流自定义指令

一、问题现象

  操作系统流程时,网速过慢,点击【按钮】,页面没有及时反应;用户感知不到,再次点击按钮,系统流程报错。

二、想法

  控制按钮操作时的频繁接口调用,通过防抖操作进行处理

三、实现

  第一步:封装自定义指令v-debounce

  1. import Vue from 'vue';
  2. //按钮防抖动指令
  3. Vue.directive('debounce', {
  4. inserted(el, binding) {
  5. el.addEventListener('click', () => {
  6. if (!el.disabled) {
  7. el.disabled = true;
  8. setTimeout(() => {
  9. el.disabled = false;
  10. }, binding.value || 3 * 1000); // 三秒之内点击不会触发接口调用
  11. }
  12. });
  13. },
  14. });

  第二步:在main.js文件中引入文件

  1. import '@/utils/directives.js';

  第三步:项目的template中使用

  1. <el-button type="primary" class="change-btns" @click="sendCode()" v-debounce>发送验证码</el-button>

loading加载

考虑到项目本身可能已经进入尾声,再应用自定义指令的话,需要耗费时间,逐一排查按钮操作,费时费力。

一、想法

  想要减少时间,肯定需要统一配置处理,考虑到控制接口的频繁调用和操作频繁等问题,或许通过页面整体不可点击的方式进行处理,那就是接口调用时控制页面加载。

二、实现

  • 首先写一个loading.vue组件
  1. <!--
  2. * @Author: Winter_Bear
  3. * @Date: 2023-03-25 16:18:16
  4. * @LastEditors: zh
  5. * @LastEditTime: 2023-03-25 16:55:18
  6. * @Description: loading组件
  7. -->
  8. <template>
  9. <div v-if="visable" class="loaidng">
  10. <transition name="animation">
  11. <div class="load">
  12. <img alt="" class="img" src="@/assets/image/loading.png" />
  13. </div>
  14. </transition>
  15. </div>
  16. </template>
  17. <script>
  18. export default {
  19. data() {
  20. return {
  21. visable: false,
  22. };
  23. },
  24. };
  25. </script>
  26. <style scoped>
  27. .loaidng {
  28. width: 100% !important;
  29. height: 100% !important;
  30. display: -webkit-flex !important; /* 新版本语法: Chrome 21+ */
  31. display: -webkit-box !important; /* 老版本语法: Safari, iOS, Android browser, older WebKit browsers. */
  32. display: -moz-box !important; /* 老版本语法: Firefox (buggy) */
  33. display: -ms-flexbox !important; /* 混合版本语法: IE 10 */
  34. display: flex !important;
  35. justify-content: center !important;
  36. align-items: center !important;
  37. position: fixed !important;
  38. top: 0 !important;
  39. right: 0 !important;
  40. bottom: 0 !important;
  41. left: 0 !important;
  42. background: rgba(0, 0, 0, 0);
  43. color: #282828;
  44. font-size: 20px;
  45. z-index: 999999;
  46. }
  47. .load {
  48. background-clip: text;
  49. -webkit-position: relative !important;
  50. position: relative !important;
  51. }
  52. .img {
  53. width: 75px;
  54. animation: rotation 5s linear infinite;
  55. animation: rotation 2s linear infinite;
  56. -moz-user-select: -moz-none;
  57. -khtml-user-select: none;
  58. -webkit-user-select: none;
  59. -o-user-select: none;
  60. user-select: none;
  61. }
  62. .no-scroll {
  63. height: 100vh;
  64. }
  65. .no-scroll > * {
  66. position: sticky;
  67. top: 0;
  68. }
  69. @keyframes rotation {
  70. 0% {
  71. -webkit-transform: rotate(0deg);
  72. }
  73. 100% {
  74. -webkit-transform: rotate(360deg);
  75. }
  76. }
  77. </style>
  • 封装loading.js文件
  1. import Loading from './index.vue';
  2. //先创建一个空实例
  3. let instance = null;
  4. let winX = null;
  5. let winY = null;
  6. window.addEventListener('scroll', function () {
  7. if (winX !== null && winY !== null) {
  8. window.scrollTo(winX, winY);
  9. }
  10. });
  11. function disableWindowScroll() {
  12. winX = window.scrollX;
  13. winY = window.scrollY;
  14. }
  15. function enableWindowScroll() {
  16. winX = null;
  17. winY = null;
  18. }
  19. export default {
  20. install(Vue) {
  21. if (!instance) {
  22. //构造器 /子类
  23. let MyLoading = Vue.extend(Loading);
  24. instance = new MyLoading({
  25. //创建一个div,并挂载上去
  26. el: document.createElement('div'),
  27. });
  28. document.body.appendChild(instance.$el);
  29. }
  30. //自定义一些方法,操作loading的显示与隐藏关
  31. let customMethods = {
  32. async start() {
  33. console.log(instance);
  34. instance.visable = true;
  35. disableWindowScroll();
  36. var mo = function (e) {
  37. passive: false;
  38. };
  39. },
  40. finish() {
  41. instance.visable = false;
  42. enableWindowScroll();
  43. var mo = function (e) {
  44. passive: false;
  45. };
  46. },
  47. };
  48. //挂载到自定义方法vue示例上
  49. if (!Vue.$loading) {
  50. Vue.$loading = customMethods;
  51. //挂载到原型上
  52. Vue.prototype.$loading = Vue.$loading;
  53. } else {
  54. console.log('$loading方法已被占用');
  55. }
  56. },
  57. };

3.在main.js中挂载到全局Vue的原型上

  1. import $loading from '@/components/loading/loading.js';
  2. Vue.use($loading);
  • 在request.js接口请求和响应拦截时做处理
  1. import Vue from 'vue';
  2. import axios from 'axios';
  3. import store from '@/store';
  4. import router from '@/router';
  5. import messageup from './resetMessage.js';
  6. import commonService from '@/api/common.js';
  7. import storage from '@/utils/storage';
  8. import { setToken, setRefreshToken } from '@/utils/auth.js';
  9. const service = axios.create({
  10. baseURL: process.env.VUE_APP_appBaseUrl,
  11. // 跨域请求时是否需要使用凭证
  12. withCredentials: false,
  13. // 请求 1000s 超时
  14. timeout: 1000 * 60 * 60,
  15. });
  16. let loadingSum = 0;
  17. let isRefreshing = false; // 标记是否正在刷新 token, 防止多次刷新token
  18. let requests = []; // 存储待重发请求的数组(同时发起多个请求的处理)
  19. // 请求拦截器
  20. service.interceptors.request.use(
  21. (config) => {
  22. loadingSum++;
  23. if (loadingSum == 1) {
  24. Vue.$loading.start();
  25. }
  26. let EnterpriseToken = '';
  27. storage.get('CLIENTID', (data) => {
  28. EnterpriseToken = data;
  29. });
  30. if (EnterpriseToken) {
  31. config.headers.EnterpriseToken = EnterpriseToken;
  32. }
  33. return config;
  34. },
  35. (error) => {
  36. messageup({
  37. message: '服务异常!',
  38. type: 'error',
  39. showClose: true,
  40. duration: 0,
  41. });
  42. return Promise.resolve(error);
  43. },
  44. );
  45. // 响应拦截器
  46. service.interceptors.response.use(
  47. (response) => {
  48. let config = response.config;
  49. let url = response.config.url;
  50. const code = response.data.code;
  51. loadingSum--;
  52. if (loadingSum == 0) {
  53. Vue.$loading.finish();
  54. }
  55. if (['701', '702'].includes(code)) {
  56. storage.removeAll();
  57. router.replace('/sign').catch((err) => err);
  58. messageup({
  59. message: response.data.message,
  60. type: 'error',
  61. });
  62. return;
  63. } else if (code == '801') {
  64. //这部分属于强制登录的逻辑状态处理
  65. if (!isRefreshing) {
  66. loadingSum++;
  67. if (loadingSum == 1) {
  68. Vue.$loading.start();
  69. }
  70. isRefreshing = true;
  71. let getRefreshToken = '';
  72. storage.get('REFCLIENTID', (data) => {
  73. getRefreshToken = data;
  74. });
  75. if (getRefreshToken) {
  76. return new Promise((resolve, reject) => {
  77. let data = {
  78. refreshToken: getRefreshToken,
  79. };
  80. commonService
  81. .refreshToken(data)
  82. .then((res) => {
  83. if (res && res.data && res.data.code == '200') {
  84. const { clientid, refreshid } = res.data.data;
  85. setToken(clientid);
  86. setRefreshToken(refreshid);
  87. config.headers.EnterpriseToken = clientid;
  88. // token 刷新后将数组的方法重新执行
  89. requests.forEach((cb) => cb(clientid));
  90. requests = []; // 重新请求完清空
  91. resolve(service(config));
  92. } else {
  93. requests = [];
  94. storage.removeAll();
  95. router.replace('/sign').catch((err) => err);
  96. }
  97. })
  98. .catch((err) => {
  99. return Promise.reject(err);
  100. })
  101. .finally(() => {
  102. isRefreshing = false;
  103. loadingSum--;
  104. if (loadingSum == 0) {
  105. Vue.$loading.finish();
  106. }
  107. });
  108. });
  109. } else {
  110. loadingSum--;
  111. if (loadingSum == 0) {
  112. Vue.$loading.finish();
  113. }
  114. }
  115. } else {
  116. // 返回未执行 resolve 的 Promise
  117. return new Promise((resolve) => {
  118. // 用函数形式将 resolve 存入,等待刷新后再执行
  119. requests.push((token) => {
  120. config.headers.EnterpriseToken = token;
  121. resolve(service(config));
  122. });
  123. });
  124. }
  125. } else {
  126. return response;
  127. }
  128. },
  129. (error) => {
  130. loadingSum--;
  131. if (loadingSum == 0) {
  132. Vue.$loading.finish();
  133. }
  134. messageup({
  135. message: error.message,
  136. type: 'error',
  137. showClose: true,
  138. duration: 0,
  139. });
  140. return Promise.reject(error);
  141. },
  142. );
  143. export default {
  144. post(url, data = {}, headers = {}) {
  145. return new Promise((resolve, reject) => {
  146. service.post(url, data, headers).then(
  147. (response) => {
  148. resolve(response);
  149. },
  150. (err) => {
  151. reject(err);
  152. },
  153. );
  154. });
  155. },
  156. get(url, params = {}, headers = {}) {
  157. return new Promise((resolve, reject) => {
  158. service
  159. .get(url, {
  160. params: params,
  161. headers: headers,
  162. })
  163. .then((response) => {
  164. resolve(response);
  165. })
  166. .catch((err) => {
  167. reject(err);
  168. });
  169. });
  170. },
  171. when(arry = []) {
  172. if (arry.length <= 1) {
  173. return arry[0];
  174. } else {
  175. let arr = [];
  176. let length = arry.length;
  177. for (let i = 0; i < length; i++) {
  178. arr.push('res' + i);
  179. }
  180. return new Promise((resolve, reject) => {
  181. axios.all(arry).then(
  182. axios.spread((...arr) => {
  183. resolve(arr);
  184. }),
  185. );
  186. });
  187. }
  188. },
  189. };

总结

axios拦截应用loading的时机:

1.请求拦截时统计所有接口请求的次数,逐次累计;接口可能一次调很多个,但是仅首次调用时出现loading,后面再有接口调用时,就不再出现。

2.响应拦截时统一也会统计所有接口请求次数,逐次累减;直到最终接口不再调用时,停止loading效果

3.单纯响应拦截过程中存在累计和累减;801登录态强制更新时也同样存在累计和累减。

以上就是JS实用技巧实现loading加载示例详解的详细内容,更多关于JS loading加载技巧的资料请关注w3xue其它相关文章!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号