实现一个移动端无限滚动+下拉刷新列表组件

部分功能描述 【实现一个移动端无限滚动+下拉刷新列表组件】下拉松开可以刷新列表
滚动触底加载分页数据
底部加载失败点击重新加载
加载完毕显示加载完成
使用 项目中

> 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 = https://www.it610.com/article/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') } } lang="less" scoped> @w:20rem; .list{ position: absolute; top: 0; left:0; bottom: 0; right: 0; }

组件
> 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() } } } } 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; } }

    推荐阅读