GVKun编程网logo

Ajax配合vue+element打造个人专属loading(ajax vue)

18

在本文中,我们将为您详细介绍Ajax配合vue+element打造个人专属loading的相关知识,并且为您解答关于ajaxvue的疑问,此外,我们还会提供一些关于ajax同步执行,loading,e

在本文中,我们将为您详细介绍Ajax配合vue+element打造个人专属loading的相关知识,并且为您解答关于ajax vue的疑问,此外,我们还会提供一些关于ajax同步执行,loading,endloading状态控制。、element loading 源码、Element UI- Axios实现全局的loading自动显示、element-ui+vue-cli3.0:el-upload的有用信息。

本文目录一览:

Ajax配合vue+element打造个人专属loading(ajax vue)

Ajax配合vue+element打造个人专属loading(ajax vue)

  最近有使用到element组件中的loading,主要是处理后台传输数据太大,页面这边较长时间处于一个白屏,这里使用了一个loading组件,来进行一个优化,当然这只是视觉层面的一个简单优化,如果不用loading条,用资源懒加载的方式或许更好,这边回归正题,因为我这边是想做成进度条由0加载到100,而elemen的loading组件我看了下给的示例里面只允许加入text,icon,达不到我想要的效果,这边我自己做了一个简单的示例。

分享图片

  这样看起来效果是不是更好,那么如何获取到后台传输的一个进度值呢?下面贴一下我的代码

分享图片

ajax的原理就是通过XmlHttpRequest对象向服务器发送异步请求,从服务器获取数据后,通过javascript进行DOM操作

这里就是先获取到jQuery产生的 XMLHttpRequest对象,为其增加 progress 事件绑定,然后再返回交给ajax使用,通过xhr对象的loaded和total属性就可以获取到进度啦!大功告成

当然我这边是直接使用的element的progress组件,你也可以自定义,甚至可以加一些动画显得更牛逼。

ajax同步执行,loading,endloading状态控制。

ajax同步执行,loading,endloading状态控制。

ajax同步状态,我需要在执行前加载fun a(); loading状态。执行完了endloading,火狐一直都是效果的,而其他浏览器都是没有效果的,怎么扩展使其加载前实行loading 的a();效果,执行完毕后,执行endloading() 了。谢谢。 Deferred()不会用。


var common = {
    ajaxs:
        function(types, urls, getData) {
            var datas;
                $.ajax({
                    type: types,
                    url: urls,
                    datatype: ''json'',
                    async: false,
                    contenttype: ''json'',
                    success: function(datare) {
                        datas = datare;
                    },
                    error: function() {
                        return null;
                    },
                });
            
            return datas;
        }
}


非常感谢你的帮助。


element loading 源码

element loading 源码

本文参考 https://segmentfault.com/a/1190000018535744

loading/index.js

import directive from ''./src/directive'';
import service from ''./src/index'';

export default {
  // 这里为什么有个 install 呢
  // 当你使用单组件单注册的时候就会调用这里了
  // 效果和下面一样,挂载指令,挂载服务
  install (Vue) {
    Vue.use(directive);
    Vue.prototype.$loading = service;
  },
  // 就是上面的 Loading.directive
  directive,
  // 就是上面的 Loading.service
  service
};

loading/src/loading.vue

<template>
  <transition name="el-loading-fade" @after-leave="handleAfterLeave">
    <div
      v-show="visible"
      class="el-loading-mask"
      :style="{ backgroundColor: background || '''' }"
      :class="[customClass, { ''is-fullscreen'': fullscreen }]">
      <div class="el-loading-spinner">
        <svg v-if="!spinner" class="circular" viewBox="25 25 50 50">
          <circle class="path" cx="50" cy="50" r="20" fill="none"/>
        </svg>
        <i v-else :class="spinner"></i>
        <p v-if="text" class="el-loading-text">{{ text }}</p>
      </div>
    </div>
  </transition>
</template>

<script>
  export default {
    data() {
      return {
        // 显示在加载图标下方的加载文案    string
        text: null,
        // 自定义加载图标类名    string
        spinner: null,
        // 遮罩背景色    string
        background: null,
        // 同 v-loading 指令中的 fullscreen 修饰符    boolean
        fullscreen: true,
        // 是否显示
        visible: false,
        // customClass    Loading 的自定义类名    string
        customClass: ''''
      };
    },

    methods: {
      // loading结束后触发
      handleAfterLeave() {
        this.$emit(''after-leave'');
      },
      setText(text) {
        this.text = text;
      }
    }
  };
</script>

loading/src/directive.js

// v-loading 指令解析
import Vue from ''vue'';
import Loading from ''./loading.vue'';
import { addClass, removeClass, getStyle } from ''element-ui/src/utils/dom'';
import { PopupManager } from ''element-ui/src/utils/popup'';
import afterLeave from ''element-ui/src/utils/after-leave'';
const Mask = Vue.extend(Loading);

const loadingDirective = {};
// 还记得 Vue.use() 的使用方法么?若传入的是对象,该对象需要一个 install 属性
loadingDirective.install = Vue => {
  if (Vue.prototype.$isServer) return;
  // 这里处理显示、消失 loading
  const toggleLoading = (el, binding) => {
    if (binding.value) {
      Vue.nextTick(() => {
        if (binding.modifiers.fullscreen) {
          el.originalPosition = getStyle(document.body, ''position'');
          el.originalOverflow = getStyle(document.body, ''overflow'');
          el.maskStyle.zIndex = PopupManager.nextZIndex();

          addClass(el.mask, ''is-fullscreen'');
          insertDom(document.body, el, binding);
        } else {
          removeClass(el.mask, ''is-fullscreen'');

          if (binding.modifiers.body) {
            el.originalPosition = getStyle(document.body, ''position'');

            [''top'', ''left''].forEach(property => {
              const scroll = property === ''top'' ? ''scrollTop'' : ''scrollLeft'';
              el.maskStyle[property] = el.getBoundingClientRect()[property] +
                document.body[scroll] +
                document.documentElement[scroll] -
                parseInt(getStyle(document.body, `margin-${property}`), 10) +
                ''px'';
            });
            [''height'', ''width''].forEach(property => {
              el.maskStyle[property] = el.getBoundingClientRect()[property] + ''px'';
            });

            insertDom(document.body, el, binding);
          } else {
            el.originalPosition = getStyle(el, ''position'');
            insertDom(el, el, binding);
          }
        }
      });
    } else {
      // 不然则将其设为不可见
      // 从上往下读我们是第一次看到 visible 属性
      // 别急,往下看,这个属性可以其实就是单文件 loading.vue 里面的
      // data() { return { visible: false } }
      afterLeave(el.instance, _ => {
        el.domVisible = false;
        const target = binding.modifiers.fullscreen || binding.modifiers.body
          ? document.body
          : el;
        removeClass(target, ''el-loading-parent--relative'');
        removeClass(target, ''el-loading-parent--hidden'');
        el.instance.hiding = false;
      }, 300, true);
      el.instance.visible = false;
      el.instance.hiding = true;
    }
  };
  // 插入dom
  const insertDom = (parent, el, binding) => {

    if (!el.domVisible && getStyle(el, ''display'') !== ''none'' && getStyle(el, ''visibility'') !== ''hidden'') {
      Object.keys(el.maskStyle).forEach(property => {
        el.mask.style[property] = el.maskStyle[property];
      });

      if (el.originalPosition !== ''absolute'' && el.originalPosition !== ''fixed'') {
        addClass(parent, ''el-loading-parent--relative'');
      }
      if (binding.modifiers.fullscreen && binding.modifiers.lock) {
        addClass(parent, ''el-loading-parent--hidden'');
      }
      el.domVisible = true;
      // appendChild 添加的元素若为同一个,则不会重复添加
      // 我们 el.mask 所指的为同一个 dom 元素
      // 因为我们只在 bind 的时候给 el.mask 赋值
      // 并且在组件存在期间,bind 只会调用一次
      parent.appendChild(el.mask);
      Vue.nextTick(() => {
        if (el.instance.hiding) {
          el.instance.$emit(''after-leave'');
        } else {
          // 将 loading 设为可见
          el.instance.visible = true;
        }
      });
      el.domInserted = true;
    }
  };
  // 在此注册 directive 指令
  Vue.directive(''loading'', {
    bind: function (el, binding, vnode) {
      // 创建一个子组件,这里和 new Vue(options) 类似
      // 返回一个组件实例
      const textExr = el.getAttribute(''element-loading-text'');
      const spinnerExr = el.getAttribute(''element-loading-spinner'');
      const backgroundExr = el.getAttribute(''element-loading-background'');
      const customClassExr = el.getAttribute(''element-loading-custom-class'');
      const vm = vnode.context;
      const mask = new Mask({
        el: document.createElement(''div''),
        // 有些人看到这里会迷惑,为什么这个 data 不按照 Vue 官方建议传函数进去呢?
        // 其实这里两者皆可
        // 稍微做一点延展好了,在 Vue 源码里面,data 是延迟求值的
        // 贴一点 Vue 源码上来
        // return function mergedInstanceDataFn() {
        //   let instanceData = typeof childVal === ''function''
        //     ? childVal.call(vm, vm)
        //     : childVal;
        //   let defaultData = typeof parentVal === ''function''
        //     ? parentVal.call(vm, vm)
        //     : parentVal;
        //   if (instanceData) {
        //     return mergeData(instanceData, defaultData)
        //   } else {
        //     return defaultData
        //   }
        // }
        // instanceData 就是我们现在传入的 data: {}
        // defaultData 就是我们 loading.vue 里面的 data() {}
        // 看了这段代码应该就不难理解为什么可以传对象进去了
        data: {
          text: vm && vm[textExr] || textExr,
          spinner: vm && vm[spinnerExr] || spinnerExr,
          background: vm && vm[backgroundExr] || backgroundExr,
          customClass: vm && vm[customClassExr] || customClassExr,
          fullscreen: !!binding.modifiers.fullscreen
        }
      });
      // 将创建的子类挂载到 el 上
      // 在 directive 的文档中建议
      // 应该保证除了 el 之外其他参数(binding、vnode)都是只读的
      el.instance = mask;
      // 挂载 dom
      el.mask = mask.$el;
      el.maskStyle = {};
      // 若 binding 的值为 truthy 运行 toogleLoading
      binding.value && toggleLoading(el, binding);
    },

    update: function (el, binding) {
      el.instance.setText(el.getAttribute(''element-loading-text''));
      if (binding.oldValue !== binding.value) {
        // 若旧不等于新值得时候(一般都是由 true 切换为 false 的时候)
        toggleLoading(el, binding);
      }
    },

    unbind: function (el, binding) {
      if (el.domInserted) {
        // 移除dom
        el.mask &&
          el.mask.parentNode &&
          el.mask.parentNode.removeChild(el.mask);
        toggleLoading(el, { value: false, modifiers: binding.modifiers });
      }
      el.instance && el.instance.$destroy();
    }
  });
};

export default loadingDirective;

loading/src/index.js

// loading服务模式
import Vue from ''vue'';
import loadingVue from ''./loading.vue'';
import { addClass, removeClass, getStyle } from ''element-ui/src/utils/dom'';
import { PopupManager } from ''element-ui/src/utils/popup'';
import afterLeave from ''element-ui/src/utils/after-leave'';
import merge from ''element-ui/src/utils/merge'';

// 和指令模式一样,创建实例构造器
const LoadingConstructor = Vue.extend(loadingVue);

const defaults = {
  text: null,
  fullscreen: true,
  body: false,
  lock: false,
  customClass: ''''
};
// 定义变量,若使用的是全屏 loading 那就要保证全局的 loading 只有一个
let fullscreenLoading;

LoadingConstructor.prototype.originalPosition = '''';
LoadingConstructor.prototype.originalOverflow = '''';

// 这里可以看到和指令模式不同的地方
// 在调用了 close 之后就会移除该元素并销毁组件
LoadingConstructor.prototype.close = function () {
  if (this.fullscreen) {
    fullscreenLoading = undefined;
  }
  afterLeave(this, _ => {
    const target = this.fullscreen || this.body
      ? document.body
      : this.target;
    removeClass(target, ''el-loading-parent--relative'');
    removeClass(target, ''el-loading-parent--hidden'');
    if (this.$el && this.$el.parentNode) {
      this.$el.parentNode.removeChild(this.$el);
    }
    this.$destroy();
  }, 300);
  this.visible = false;
};

const addStyle = (options, parent, instance) => {
  let maskStyle = {};
  if (options.fullscreen) {
    instance.originalPosition = getStyle(document.body, ''position'');
    instance.originalOverflow = getStyle(document.body, ''overflow'');
    maskStyle.zIndex = PopupManager.nextZIndex();
  } else if (options.body) {
    instance.originalPosition = getStyle(document.body, ''position'');
    [''top'', ''left''].forEach(property => {
      let scroll = property === ''top'' ? ''scrollTop'' : ''scrollLeft'';
      maskStyle[property] = options.target.getBoundingClientRect()[property] +
        document.body[scroll] +
        document.documentElement[scroll] +
        ''px'';
    });
    [''height'', ''width''].forEach(property => {
      maskStyle[property] = options.target.getBoundingClientRect()[property] + ''px'';
    });
  } else {
    instance.originalPosition = getStyle(parent, ''position'');
  }
  Object.keys(maskStyle).forEach(property => {
    instance.$el.style[property] = maskStyle[property];
  });
};

const Loading = (options = {}) => {
  if (Vue.prototype.$isServer) return;
  options = merge({}, defaults, options);
  // Loading 需要覆盖的 DOM 节点。可传入一个 DOM 对象或字符串;若传入字符串,则会将其作为参数传入 document.querySelector以获取到对应 DOM 节点
  if (typeof options.target === ''string'') {
    options.target = document.querySelector(options.target);
  }
  options.target = options.target || document.body;
  if (options.target !== document.body) {
    options.fullscreen = false;
  } else {
    options.body = true;
  }
  // 若调用 loading 的时候传入了 fullscreen 并且 fullscreenLoading 不为 falsy
  // fullscreenLoading 只会在下面赋值,并且指向了 loading 实例
  if (options.fullscreen && fullscreenLoading) {
    return fullscreenLoading;
  }

  let parent = options.body ? document.body : options.target;
  // 这里就不用说了吧,和指令中是一样的
  let instance = new LoadingConstructor({
    el: document.createElement(''div''),
    data: options
  });

  addStyle(options, parent, instance);
  if (instance.originalPosition !== ''absolute'' && instance.originalPosition !== ''fixed'') {
    addClass(parent, ''el-loading-parent--relative'');
  }
  if (options.fullscreen && options.lock) {
    addClass(parent, ''el-loading-parent--hidden'');
  }
  // 直接添加元素
  parent.appendChild(instance.$el);
  Vue.nextTick(() => {
    instance.visible = true;
  });
  // 若传入了 fullscreen 参数,则将实例存储
  if (options.fullscreen) {
    // 需要注意的是,以服务的方式调用的全屏 Loading 是单例的:若在前一个全屏 Loading 关闭前再次调用全屏 Loading,并不会创建一个新的 Loading 实例,而是返回现有全屏 Loading 的实例
    fullscreenLoading = instance; 
  }
  return instance;
};

export default Loading;

 

Element UI- Axios实现全局的loading自动显示

Element UI- Axios实现全局的loading自动显示

1、在request.js

import axios from ''axios'';
import { Message,Loading } from ''element-ui'';
import _ from ''lodash'';

//loading对象
let loading;
  
//当前正在请求的数量
let needLoadingRequestCount = 0;

//显示loading
function showLoading(target) {
  // 判断是因为关闭时加了抖动,此时loading对象可能还存在,但needLoadingRequestCount已经变成0.避免这种情况下会重新创建个loading
  if (needLoadingRequestCount === 0 && !loading) {
    loading = Loading.service({
      lock: true,
      text: "Loading...",
      spinner: ''el-icon-loading'',
      background: ''rgba(0, 0, 0, 0.7)''
    });
  }
  needLoadingRequestCount++;
}

//隐藏loading
function hideLoading() {
  needLoadingRequestCount--;
  needLoadingRequestCount = Math.max(needLoadingRequestCount, 0); // 取needLoadingRequestCount和0之间的最大值
  if (needLoadingRequestCount === 0) {
    //关闭loading
    toHideLoading();
  }
}
  
//防抖:将 300ms 间隔内的关闭 loading 便合并为一次。防止连续请求时, loading闪烁的问题。
let toHideLoading = _.debounce(()=>{
      loading.close();
      loading = null;
    }, 300);

export default (
  appName ,
  type,
  {
    needSuccessTip = false,
    needErrorTip = true,
    loading = false,
    loadingText
  } = {}
) => {
  // 创建axios实例
  let baseURL = appName ? `${window.urlConfig[appName]}` : ''''
  const service = axios.create({
    withCredentials: true,
    baseURL: process.env.BASE_URL, // api的base_url
    timeout: `${window.urlConfig.apiTimeout}`,
    headers: {
      token: localStorage.getItem(''token''),
      ''Content-Type'': `application/${
        type === ''form'' ? ''x-www-form-urlencoded'' : ''json''
      };charset=UTF-8`
    }
  })
  //添加请求拦截器
  service.interceptors.request.use(res=> {
    if(loading){
      showLoading();
    }
    return res;
  }, 
  (err) => {
    if(loading){
      hideLoading();
    }
    Message.error(''请求超时!'');
    Promise.reject(error)
  });
  //响应拦截器
  service.interceptors.response.use(
      (res) => {
        if(loading){
          hideLoading();
        }
        return res;
      },
      error => {
        if(loading){
          hideLoading();
        }
        Message({
          message: error.message,
          type: ''error''
        })
        return Promise.reject(error);
      }
  )
  return service
}

2、在api

import request from ''@/request''
export function getExportApi (params) {
  return request(
    '''',
    '''',
    {
      loading: true
    }
  )({
    url: ''xxx,
    method: ''get'',
    params,
    responseType: ''blob''
  })
}

element-ui+vue-cli3.0:el-upload

element-ui+vue-cli3.0:el-upload

最近项目中涉及很多文件上传的地方,然后文件上传又有很多限制。比如文件大小限制,文件个数限制,文件类型限制,文件上传后的列表样式自定义,包括上传进度条等问题。下面是我对element-ui的上传组件的一些改造, 点击查看源码。

我是自己维护了一个列表数据,再对这个列表数据进行一些操作,没用组件自带的。先看看我的组件模版
<template>
  <el-upload:limit="limit"
    :action="action"
    :accept="accept"
    :data="data"
    :multiple="multiple"
    :show-file-list="showFileList"
    :on-exceed="handleExceed"
    :with-credentials="withcredentials"
    :before-upload="handleBeforeUpload"
    :on-progress="handleProgress"
    :on-success="handleSuccess"
    :on-error="handleError">
    <el-button size="small" type="primary">上传</el-button>
  </el-upload>
</template>
limit: 限制文件个数
action:文件的上传地址(这里我没有特别封装axios,直接用默认的)
accept:接受上传的文件类型(字符串)
data:上传时附带的额外参数
multiple:多选(布尔类型,我这里设为true,即可以批量上传)
show-file-list:是否显示文件上传列表
with-credentials:是否携带cookie,布尔类型,true表示携带
这是我设置的一些初始值

initdata.png

下面最重要的就是钩子函数了

methods.png

1、handleExceed是文件超出个数限制时的钩子
private handleExceed(files: any, fileList: any) {
    if (fileList.length > 20) {
      this.$message.error(''最多允许上传20个文件'');
      return false;
    }
  }
2、handleBeforeUpload文件上传前的钩子,可以做一些拦截,return false,则停止上传
private handleBeforeUpload(file: any) {
    // 文件大小限制
    const isLt5M = file.size / 1024 / 1024 < 5;
    if (!isLt5M) {
      this.$message.error(''不得超过5M'');
      return isLt5M;
    }
    // 文件类型限制
    const name = file.name ? file.name : '''';
    const ext = name
      ? name.substr(name.lastIndexOf(''.'') + 1, name.length)
      : true;
    const isExt = this.accept.indexOf(ext) < 0;
    if (isExt) {
      this.$message.error(''请上传正确的格式类型'');
      return !isExt;
    }
    // 大小和类型验证都通过后,给自定义的列表中添加需要的数据
    this.objAddItem(this.tempArr, file);
  }
3、handleProgress文件上传时的钩子,更新进度条的值
private handleProgress(event: any, file: any, fileList: any) {
    this.tempArr.forEach((element: any, index: number) => {
      if (element.uid === file.uid) {
        // 更新这个uid下的进度
        const progress = Math.floor(event.percent);
        // 防止上传完接口还没有返回成功值,所以此处给定progress的最大值为99,成功的钩子中再置为100
        element.progress = progress === 100 ? 99 : progress;
        this.$set(this.tempArr, index, element);
        this.$emit(''changeFileList'', this.tempArr);
      }
    });
  }
4、handleSuccess文件上传成功时的钩子
private handleSuccess(response: any, file: any, fileList: any) {
    this.tempArr.forEach((element: any, index: number) => {
      if (element.uid === file.uid) {
        element.progress = 100;
        // element.url为下载地址,一般后端人员会给你返回
        // 我这边为了做后面的下载,先写死链接供测试
        element.url = ''http://originoo-1.b0.upaiyun.com/freepic/3226433.jpg!freethumb'';
        this.$message.success(''文件上传成功'');
        this.$set(this.tempArr, index, element);
        this.$emit(''changeFileList'', this.tempArr);
      }
    });
    // response是后端接口返回的数据,可以根据接口返回的数据做一些操作
    // 示例
    // const bizCode = response.rspResult.bizCode;
    // switch (bizCode) {
    //   case 200:
    //     this.tempArr.forEach((element: any, index: number) => {
    //       if (element.uid === file.uid) {
    //         element.progress = 100;
    //         element.url = response.data.url; // 这是后端人员给我返回的下载地址
    //         this.$message.success(''文件上传成功'');
    //         this.$set(this.tempArr, index, element);
    //         this.$emit(''changeFileList'', this.tempArr);
    //       }
    //     });
    //     break;
    //   default:
    //     this.tempArr.forEach((element: any, index: number) => {
    //       if (element.uid === file.uid) {
    //         this.tempArr.splice(index, 1); // 上传失败删除该记录
    //         this.$message.error(''文件上传失败'');
    //         this.$emit(''changeFileList'', this.tempArr);
    //       }
    //     });
    //     break;
    // }
  }
5、handleError文件上传失败时的钩子
private handleError(err: any, file: any, fileList: any) {
    this.tempArr.forEach((element: any, index: number) => {
      if (element.uid === file.uid) {
        this.tempArr.splice(index, 1); // 上传失败删除该记录
        this.$message.error(''文件上传失败'');
        this.$emit(''changeFileList'', this.tempArr);
      }
    });
  }
添加数据函数
private objAddItem(tempArr: any[], file: any) {
    const tempObj = {
      uid: file.uid, // uid用于辨别文件
      originalName: file.name, // 列表显示的文件名
      progress: 0, // 进度条
      code: 200, // 上传状态
    };
    tempArr.push(tempObj);
    this.$emit(''changeFileList'', tempArr);
  }
上传的文件下载封装
private downloadFileFun(url: any) {
    const iframe: any = document.createElement(''iframe'') as HTMLIFrameElement;
    iframe.style.display = ''none''; // 防止影响页面
    iframe.style.height = 0; // 防止影响页面
    iframe.src = url;
    document.body.appendChild(iframe); // 这一行必须,iframe挂在到dom树上才会发请求
    // 5分钟之后删除(onload方法对于下载链接不起作用,就先抠脚一下吧)
    setTimeout(() => {
      iframe.remove();
    }, 5 * 60 * 1000);
  }
持续更新......

今天的关于Ajax配合vue+element打造个人专属loadingajax vue的分享已经结束,谢谢您的关注,如果想了解更多关于ajax同步执行,loading,endloading状态控制。、element loading 源码、Element UI- Axios实现全局的loading自动显示、element-ui+vue-cli3.0:el-upload的相关知识,请在本站进行查询。

本文标签: