<template>
<div class="ion-loading" :class="[modeClass,cssClass]">
<vm-backdrop :isActive="isActive && showBackdrop" :enableBackdropDismiss="false"></vm-backdrop>
<transition :name="transitionClass"
@before-enter="beforeEnter"
@after-enter="afterEnter"
@before-leave="beforeLeave"
@after-leave="afterLeave">
<div class="loading-wrapper" v-show="isActive">
<div v-if="showSpinner" class="loading-spinner">
<vm-spinner :name="spinner"></vm-spinner>
</div>
<div v-if="content" v-html="content" class="loading-content"></div>
</div>
</transition>
</div>
</template>
<script type="text/javascript">
/**
* @component Loading
* @description
*
* ## 弹出层 / Loading组件(大Loading)
*
* Loading也有大小之分
*
* ### 关于大小Loading的问题
*
* Loading的使用场景是这样的: 当用户访问数据/资源加载/需要等待操作的需求下使用, 完整的情况下需要向用户说明我这次Loading是做什么, 比如Loading是提示"**正在请求资源, 请稍后!**", 有时我们并不需提示, 只需要显示菊花图即可, 因为频繁的显示大Loading还是很烦的, 这里需要介绍下Indicator组件, 他是继承Loading组件, 但是只显示菊花图, 而且调用也简单, 不必每次传入参数.
*
* ### 弹出层
*
* - Loading通过present开启, 如果没设置duration, 则需要手动dismiss,
* - Vimo的弹出层都是独立于页面的, 但也不是`body`的直接子元素, 而是挂载在App组件中的. 这样做是有考量的.
* - 组件相应路由切换, 路由切换则自动关闭
*
* ### 用法
*
* - 不传参数则显示菊花
* - 传入string则显示string
* - 参考下面的示例
*
*
* @props {string} [spinner='ios'] - 菊花样式
* @props {string} [content] - 内容
* @props {string} [cssClass] - 自定义样式
* @props {boolean} [showBackdrop=false] - 是否显示黑色背景
* @props {number} [duration] - loading持续时间, 如果为0则无效
* @props {boolean} [dismissOnPageChange=true] - 页面切换是否关闭loading
* @props {string} [mode='ios'] - 模式
*
* @demo #/loading
* @see component:Indicator
* @usage
*
* // 只显示菊花
* spinnerOnly () {
* this.$loading.present();
* setTimeout(() => {
* this.$loading.dismiss().then(() => {
* console.debug('dismiss in promise success!')
* })
* }, 1000)
* },
*
* // 是显示name
* stringOnly () {
* this.$loading.present('只传入了String');
* setTimeout(() => {
* this.$loading.dismiss().then(() => {
* console.debug('dismiss in promise success!')
* })
* }, 1000)
* },
*
* // 普通的
* showDefault () {
* this.$loading.present({
* content: '正在加载, 6000ms后自动关闭...',
* dismissOnPageChange: false, // url变化后关闭loading(默认)
* showBackdrop: true,
* });
* setTimeout(() => {
* this.$loading.dismiss().then(() => {
* console.debug('dismiss in promise success!')
* })
* }, 6000);
* },
*
* */
import { urlChange } from '../../util/util'
import ThemeMixins from '../../themes/theme.mixins'
import VmBackdrop from "../backdrop/backdrop.vue";
import VmSpinner from "../spinner/spinner.vue";
const NOOP = () => {}
export default {
name: 'vm-loading',
mixins: [ThemeMixins],
components: {
VmSpinner,
VmBackdrop
},
props: {
spinner: {
type: String,
default () { return this.$config && this.$config.get('spinner', 'ios') || 'ios' }
},
content: String,
cssClass: String,
showBackdrop: {
type: Boolean,
default: true
},
duration: Number,
dismissOnPageChange: {
type: Boolean,
default () { return true }
}
},
data () {
return {
isActive: false, // 开启状态
enabled: false, // 组件当前是否进入正常状态的标示(正常显示状态 和 正常退出状态)
// promise
presentCallback: NOOP,
dismissCallback: NOOP,
startTime: null,
timer: null,
unreg: null
}
},
computed: {
showSpinner () {
return this.spinner !== 'hide'
},
transitionClass () {
return `loading-${this.mode}`
}
},
methods: {
// -------- private --------
/**
* ActionSheet Animate Hooks
* */
beforeEnter () {
this.$app && this.$app.setEnabled(false, 200)
this.enabled = false
},
afterEnter () {
this.presentCallback()
this.enabled = true
},
beforeLeave () {
this.$app && this.$app.setEnabled(false, 200)
this.enabled = false
},
afterLeave () {
// 删除DOM
this.dismissCallback()
this.$el.remove()
this.enabled = true
},
// -------- public --------
/**
* @function present
* @description
* 打开Loading
* @param {Object} options - 给组件props传参的对象, 这部分在loading.js中定义
* @returns {Promise} 结果返回Promise, 当动画完毕后执行resolved
*/
present () {
this.startTime = new Date().getTime()
this.isActive = true
if (parseInt(this.duration) > 16) {
this.timer && window.clearTimeout(this.timer)
this.timer = window.setTimeout(() => {
this.dismiss()
}, this.duration)
}
return new Promise((resolve) => { this.presentCallback = resolve })
},
/**
* @function dismiss
* @description
* 关闭Loading
* @return {Promise} 结果返回Promise, 当动画完毕后执行resolved
* */
dismiss () {
if (this.isActive) {
this.isActive = false // 动起来
this.timer && window.clearTimeout(this.timer)
this.unreg && this.unreg()
if (!this.enabled) {
this.$nextTick(() => {
this.$el.remove()
this.dismissCallback()
this.enabled = true
})
}
return new Promise((resolve) => { this.dismissCallback = resolve })
} else {
return new Promise((resolve) => { resolve() })
}
}
},
created () {
if (this.dismissOnPageChange) {
this.unreg = urlChange(() => {
this.isActive && this.dismiss()
})
}
}
}
</script>
<style lang="scss">
@import "loading";
@import "loading.ios";
@import "loading.md";
.ion-loading.indicator {
.loading-wrapper {
background: rgba(0, 0, 0, 0.9);
color: #fff;
padding: 13px 13px;
circle, line {
stroke: #fff !important;
}
.loading-spinner {
height: 30px;
width: 30px;
display: flex;
justify-content: center;
align-items: center;
}
}
}
.indicator.reverse {
.loading-wrapper {
background: rgba(256, 256, 256, 0.9);
circle, line {
stroke: #000 !important;
}
}
}
</style>