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

部分功能描述

下拉松开可以刷新列表
滚动触底加载分页数据
底部加载失败点击重新加载
加载完毕显示加载完成

使用

项目中
<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>

你可能感兴趣的:(前端)