在本文中,我们将为您详细介绍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同步执行,loading,endloading状态控制。
- element loading 源码
- Element UI- Axios实现全局的loading自动显示
- element-ui+vue-cli3.0:el-upload
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同步状态,我需要在执行前加载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 源码
本文参考 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自动显示
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的上传组件的一些改造, 点击查看源码。
我是自己维护了一个列表数据,再对这个列表数据进行一些操作,没用组件自带的。先看看我的组件模版
<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表示携带
这是我设置的一些初始值
下面最重要的就是钩子函数了
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打造个人专属loading和ajax vue的分享已经结束,谢谢您的关注,如果想了解更多关于ajax同步执行,loading,endloading状态控制。、element loading 源码、Element UI- Axios实现全局的loading自动显示、element-ui+vue-cli3.0:el-upload的相关知识,请在本站进行查询。
本文标签: