下拉松开可以刷新列表
滚动触底加载分页数据
底部加载失败点击重新加载
加载完毕显示加载完成
<template>
<div class="page">
<div class="list">
<scrollBox ref="result"
v-else-if="showFlag===0"
:noMore='noMoreData'
:onInfinite='onInfinite'
:onRefresh='onRefresh'
:hidefooter='hidefooter'>
<div>
<div class="card" v-for="(item, i) in dataList" :key="i">div>
div>
scrollBox>
div>
div>
template>
<script>
import scrollBox from '../components/scrollBox'
export default {
components: { scrollBox },
data () {
return {
showFlag: 0,
noMoreData: false,
dataList: [],
fetchData: {
pageNum: 0,
pageSize: 10,
searchName: ''
}
}
},
computed: {
hidefooter () {
return this.dataList.length > 0
}
},
methods: {
getList (data, type, fn) {
if (type === 'init' || type === 'search') loading.start()
this.httpRequest({
method: 'POST',
url: '',
data: data
}).then(res => {
loading.end()
fn && fn()
if (res.data.code === '000') {
switch (type) {
let resultList = res.data.data.resultList
let totalNum = res.data.data.totalNum
case 'init':
this.dataList = resultList
this.notFound = this.dataList.length === 0 ? 1 : 0
if (this.dataList.length > 0) {
this.showFlag = 0
}
break
case 'refresh' :
case 'search':
this.dataList = resultList
this.notFound = 0
this.showFlag = this.dataList.length === 0 ? 1 : 0
if (this.dataList.length > 0) {
this.showFlag = 0
}
break
case 'infinite':
this.dataList = this.dataList.concat(resultList)
break
}
if (this.dataList.length && this.dataList.length < +totalNum) {
this.noMoreData = false // 显示加载中
} else {
this.noMoreData = true // 显示加载完成
}
} else if (res.data.code) {
this.fetchData.pageNum--
fn && fn(true)
this.showFlag = 1
} else {
this.fetchData.pageNum--
fn && fn(true)
this.showFlag = 2
}
}).catch(e => {
console.warn(e)
})
},
onInfinite (done) { // 滚动至底部触发加载
this.fetchData.pageNum++
this.getList(this.fetchData, 'infinite', done)
},
onRefresh (done) { // 松开刷新
this.fetchData.pageNum = 0
this.getList(this.fetchData, 'refresh', done)
}
},
mounted () {
this.getList(this.fetchData, 'init')
}
}
script>
<style lang="less" scoped>
@w:20rem;
.list{
position: absolute;
top: 0;
left:0;
bottom: 0;
right: 0;
}
style>
<template lang="html">
<div class="yo-scroll"
:class="{'down':(state===0),'up':(state==1),refresh:(state===2),touch:touching}"
@touchstart="touchStart($event)"
@touchmove="touchMove($event)"
@touchend="touchEnd($event)"
@scroll="(onInfinite || infiniteLoading) ? onScroll($event) : undefined">
<div><slot name='theader'>slot>div>
<section class="inner" :style="{ transform: 'translate3d(0, ' + top + 'px, 0)' }">
<header class="pull-refresh" v-if='enableRefresh'>
<slot name="pull-refresh">
<div class="up-tip">松开更新div>
<div class="refresh-tip isLoading">
<img src="../../../assets/loading.gif" /><span>正在更新span>
div>
slot>
header>
<slot>slot>
<footer class="load-more" v-if='enableInfinite' v-show='hidefooter'>
<slot name="load-more">
<div v-if='infiniteError' class='isLoadded' @click='infiniteErrorClick'>
<span>span><div>加载失败,点击重试div><span>span>
div>
<div v-else>
<div v-if='innerNoMore' class='isLoadded'>
<span>span><div>加载完成div><span>span>
div>
<div v-else class="isLoading"><img src="../../../assets/loading.gif" /><span>正在加载span>div>
div>
slot>
footer>
section>
div>
template>
<script>
export default {
props: {
offset: {
type: Number,
default: 40
},
enableInfinite: {
type: Boolean,
default: true
},
enableRefresh: {
type: Boolean,
default: true
},
onRefresh: {
type: Function,
default () {
return () => {}
}
},
onInfinite: {
type: Function,
default () {
return () => {}
}
},
noMore: {
type: Boolean,
default: false
},
hidefooter: {
type: Boolean,
default: true
}
},
data () {
return {
top: 0,
state: 0,
startY: 0,
touching: false,
infiniteLoading: false,
infiniteError: false,
timeStamp: new Date().getTime(),
removeTop: 0
}
},
computed: {
innerNoMore: {
get () {
return this.noMore
},
set (val) {
this.$emit('update:noMore', val)
}
}
},
watch: {
removeTop: {
handler () {
this.$emit('scrollTop', this.$el.scrollTop)
}
}
},
methods: {
touchStart (e) {
this.startY = e.targetTouches[0].pageY
this.removeTop = this.$el.scrollTop
this.startScroll = this.$el.scrollTop || 0
this.touching = true
this.timeStamp = new Date().getTime()
},
touchMove (e) {
if (!this.enableRefresh || this.$el.scrollTop > 0 || !this.touching) {
return
}
let diff = e.targetTouches[0].pageY - this.startY - this.startScroll
if (diff > 0) e.preventDefault()
this.top = Math.pow(diff, 0.8) + (this.state === 2 ? this.offset : 0)
if (this.state === 2) { // in refreshing
return
}
if (this.top >= this.offset) {
this.state = 1
} else {
this.state = 0
}
},
touchEnd (e) {
if (!this.enableRefresh) return
this.touching = false
if (new Date().getTime() - this.timeStamp <= 50) {
return
}
if (this.state === 2) { // in refreshing
this.state = 2
this.top = this.offset
return
}
if (this.top >= this.offset) { // do refresh
this.refresh()
} else { // cancel refresh
this.state = 0
this.top = 0
}
},
refresh () {
this.state = 2
this.top = this.offset
this.innerNoMore = false
this.infiniteError = false
// this.onRefresh(this.refreshDone)
this.onRefresh(this.refreshDone)
},
refreshDone () {
this.state = 0
this.top = 0
},
infinite () {
if (this.infiniteLoading === false) {
this.infiniteLoading = true
this.onInfinite(this.infiniteDone)
}
},
infiniteErrorClick () {
this.infiniteLoading = true
this.infiniteError = false
this.onInfinite(this.infiniteDone)
},
infiniteDone (flag = false) {
this.infiniteLoading = false
this.infiniteError = flag
},
onScroll (e) {
if (this.innerNoMore) {
return
}
if (this.infiniteError) {
return
}
if (this.scrollFlag) {
if (!this.enableInfinite || this.infiniteLoading) {
return
}
}
let outerHeight = this.$el.clientHeight
let innerHeight = this.$el.querySelector('.inner').clientHeight
let scrollTop = this.$el.scrollTop
let ptrHeight = this.onRefresh && this.enableRefresh ? this.$el.querySelector('.pull-refresh').clientHeight : 0
let infiniteHeight = this.$el.querySelector('.load-more').clientHeight
let bottom = innerHeight - outerHeight - scrollTop - ptrHeight
if (bottom <= infiniteHeight) {
this.infinite()
}
}
}
}
script>
<style lang='less' scoped>
@w:20rem;
.yo-scroll {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: auto;
-webkit-overflow-scrolling: touch;
display: flex;
flex-direction: column;
color: #c3c3c3;
}
.yo-scroll .inner {
position: relative;
width: 100%;
transition-duration: 300ms;
}
.yo-scroll .pull-refresh {
position: relative;
left: 0;
top: 0;
width: 100%;
line-height: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 17/@w;
}
.yo-scroll.touch .inner {
transition-duration: 0ms;
}
.yo-scroll.down .down-tip {
display: block;
height: 0;
}
.yo-scroll.up .up-tip {
display: block;
}
.yo-scroll.refresh .refresh-tip {
display: block;
}
.yo-scroll .down-tip,
.yo-scroll .refresh-tip,
.yo-scroll .up-tip {
display: none;
}
.isLoading {
text-align: center;
font-size: 17/@w;
line-height: 34/@w;
display: flex;
align-items: center;
justify-content: center;
img {
height: 15/@w;
margin-right: 20/@w;
vertical-align: middle
}
}
.isLoadded {
padding: 15/@w 0;
text-align: center;
font-size: 17/@w;
color: #c3c3c3;
display: flex;
line-height: 30/@w;
align-items: center;
width: 65%;
margin: 0 auto;
span {
flex-grow: 1;
height: 1px;
background-color: #e3e3e3;
}
div{
white-space: nowrap;
padding: 0 1em;
flex-shrink: 0;
}
}
style>