<template>
|
<div class="load-refresh">
|
<!-- 刷新动画 -->
|
<div class="refresh hollow-dots-spinner">
|
<template v-if="hollowDotSpinner">
|
<div class="dot" :style="[{ animationPlayState: playState }]"></div>
|
<div class="dot" :style="[{ animationPlayState: playState }]"></div>
|
<div class="dot" :style="[{ animationPlayState: playState }]"></div>
|
</template>
|
</div>
|
<div class="cover-container" :style="[
|
{
|
background: backgroundCover,
|
//transform: coverTransform,
|
transition: coverTransition
|
}
|
]" @touchstart="coverTouchstart" @touchmove="coverTouchmove" @touchend="coverTouchend">
|
<scroll-view v-if="!isEmpty" scroll-y :show-scrollbar="false" class="list" @scroll="scrollTop"
|
@scrolltolower="loadMore" :style="getHeight">
|
<!-- 数据集插槽 -->
|
<slot name="content-list"></slot>
|
<!-- 上拉加载 -->
|
<div class="load-more">{{loadText}}</div>
|
</scroll-view>
|
<div class="empty-row pdt" :style="{background:emptyBack||'#fff'}" v-else>
|
<div class="">
|
<image :src="emptyImg||'/static/images/empy.png'" mode="" class="empty-img"></image>
|
<div class="empty-text">{{emptyText}}</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<script>
|
export default {
|
name: 'loadRefresh',
|
props: {
|
dataList: {
|
type: Array,
|
defaule: () => {
|
return []
|
}
|
},
|
showEmptyImg: {
|
type: Boolean,
|
defaule: true
|
},
|
isRefresh: {
|
type: Boolean,
|
defaule: true
|
},
|
refreshTime: {
|
type: Number,
|
default: 800
|
},
|
emptyImg: {
|
type: String,
|
default: ''
|
},
|
emptyBack: {
|
type: String,
|
default: ''
|
},
|
heightReduce: {
|
type: Number,
|
default: 0
|
},
|
backgroundCover: {
|
type: String,
|
default: '#fff'
|
},
|
pageNo: {
|
type: Number,
|
default: 0
|
},
|
totalPageNo: {
|
type: Number,
|
default: 0
|
},
|
emptyText: {
|
type: String,
|
default: ''
|
}
|
},
|
data () {
|
return {
|
startY: 0,
|
moveY: 0,
|
hasMore: true,
|
moving: false,
|
refreshStatus: false,
|
loading: false,
|
isEmpty: false,
|
coverTransform: 'translateY(0px)',
|
coverTransition: '0s',
|
playState: 'paused', // 动画的状态 暂停/开始
|
hollowDotSpinner: true
|
}
|
},
|
computed: {
|
|
// 计算组件所占屏幕高度
|
getHeight () {
|
const height = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
|
return `height: ${height}px;`
|
},
|
// 判断loadText
|
// 可以根据需求自定义
|
loadText () {
|
const { pageNo, totalPageNo, loading } = this
|
if (loading) {
|
return '加载中...'
|
} else if (pageNo < totalPageNo) {
|
return '上拉加载更多'
|
} else {
|
return '没有更多内容'
|
}
|
}
|
},
|
watch: {
|
// 监听refresh值 避免多次触发@refresh
|
refreshStatus (val) {
|
if (val) {
|
this.$emit('refresh')
|
}
|
},
|
dataList: {
|
immediate: true,
|
handler (value) {
|
this.isEmpty = !value || !value.length
|
}
|
}
|
},
|
methods: {
|
scrollTop (res) {
|
this.$emit('scrollTop', res.detail.scrollTop)
|
},
|
// 根据pageNo和totalPageNo的值来判断 是否触发@loadMore
|
loadMore () {
|
const { pageNo, totalPageNo } = this
|
if (pageNo < totalPageNo) {
|
this.loading = true
|
this.$emit('loadMore')
|
} else {
|
this.loading = false
|
}
|
},
|
// 单次加载结束
|
loadOver () {
|
this.loading = false
|
},
|
// 回弹效果
|
coverTouchstart (e) {
|
this.$emit('touchstarts', e)
|
if (!this.isRefresh) {
|
return
|
}
|
this.coverTransition = 'transform .1s linear'
|
this.startY = e.touches[0].clientY
|
},
|
coverTouchmove (e) {
|
this.$emit('touchmoves', e)
|
if (!this.isRefresh || this.refreshStatus) {
|
return
|
}
|
this.moveY = e.touches[0].clientY
|
const moveDistance = this.moveY - this.startY
|
if (moveDistance < 0) {
|
this.moving = false
|
return
|
}
|
this.moving = true
|
if (moveDistance >= 60) {
|
this.hollowDotSpinner = true
|
this.refreshStatus = true
|
this.coverTransform = 'translateY(60px)'
|
this.playState = 'running'
|
}
|
},
|
coverTouchend (e) {
|
this.$emit('touchends', e)
|
if (!(this.isRefresh && this.refreshStatus)) {
|
return
|
}
|
setTimeout(() => {
|
if (this.moving === false) {
|
return
|
}
|
this.moving = false
|
this.refreshStatus = false
|
this.coverTransition = 'transform 0.3s cubic-bezier(.21,1.93,.53,.64)'
|
this.coverTransform = 'translateY(0px)'
|
this.playState = 'paused'
|
this.hollowDotSpinner = false
|
}, this.refreshTime)
|
},
|
runRefresh () {
|
// 开始
|
this.refreshStatus = true
|
this.hollowDotSpinner = true
|
this.coverTransition = 'transform .1s linear'
|
this.coverTransform = 'translateY(60px)'
|
this.playState = 'running'
|
// 结束
|
setTimeout(() => {
|
this.refreshStatus = false
|
this.coverTransition = 'transform 0.3s cubic-bezier(.21,1.93,.53,.64)'
|
this.coverTransform = 'translateY(0px)'
|
this.playState = 'paused'
|
this.hollowDotSpinner = false
|
}, this.refreshTime)
|
}
|
}
|
}
|
</script>
|
|
<style lang="scss" scoped>
|
.load-refresh {
|
margin: 0;
|
padding: 0;
|
width: 100%;
|
height: 100%;
|
/deep/ .cover-container {
|
background: #f2f2f2 !important;
|
}
|
.refresh {
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
height: 120px;
|
width: 100%;
|
}
|
|
.empty-row {
|
height: 100%;
|
display: flex;
|
justify-content: center;
|
|
.empty-img {
|
width: 424px;
|
height: 315px;
|
}
|
.empty-text {
|
text-align: center;
|
color: #cecece;
|
font-size: 32px;
|
font-weight: 400;
|
}
|
}
|
.cover-container {
|
height: 100%;
|
}
|
.hollow-dots-spinner,
|
.hollow-dots-spinner * {
|
box-sizing: border-box;
|
}
|
|
.hollow-dots-spinner {
|
height: 100px;
|
width: 100%;
|
}
|
|
.hollow-dots-spinner .dot {
|
width: 15px;
|
height: 15px;
|
margin: 0 calc(15px / 2);
|
background: #eb0f17;
|
// border: calc(15px / 5) solid #4CD964;
|
border-radius: 50%;
|
float: left;
|
transform: scale(0);
|
animation: hollow-dots-spinner-animation 1000ms ease infinite 0ms;
|
}
|
|
.hollow-dots-spinner .dot:nth-child(1) {
|
animation-delay: calc(300ms * 1);
|
}
|
|
.hollow-dots-spinner .dot:nth-child(2) {
|
animation-delay: calc(300ms * 2);
|
}
|
|
.hollow-dots-spinner .dot:nth-child(3) {
|
animation-delay: calc(300ms * 3);
|
}
|
|
@keyframes hollow-dots-spinner-animation {
|
50% {
|
transform: scale(1);
|
opacity: 1;
|
}
|
100% {
|
opacity: 0;
|
}
|
}
|
.cover-container {
|
width: 100%;
|
margin-top: -100px;
|
padding-top: 2px;
|
.list {
|
width: 100%;
|
.load-more {
|
font-size: 20px;
|
text-align: center;
|
color: #aaaaaa;
|
padding: 16px;
|
}
|
}
|
}
|
}
|
</style>
|