无限滚动组件封装(vue+vant)

javascript 复制代码
<script>
import { ref, getCurrentInstance } from '@vue/composition-api'
import { useInfiniteScroll } from '@vueuse/core'
import { PullRefresh } from 'vant'
import styled from 'vue-styled-components'
const StyledContainer = styled('div', { customStyle: {} })`
  overflow:auto;

  .no-more .el-divider__text{
    background-color: #F2F3F5;
  }

  .loading-more .el-loading-spinner{
    color:#3C77FE !important;
  }

  .data-list-empty{
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%,-50%);
    font-size: 12px;
    color: #b2b2b2;
    text-align: center;

    .data-list-empty_img{
      width: 90px;
      height: 90px;
      margin-bottom: 20px;
      background: url(${feConfig.DEFAULT_DATA_EMPTY_IMG}) 50% no-repeat;
      background-size: cover;
    }

    >span{
      font-size: 13px;
      font-weight: 400;
      color: #BAC1CC;
      line-height: 19px;
    }
  }

  ${props => props.customStyle || ''}
`
export default {
  name: 'InfiniteScroll',
  props: {
    isDeleteMode: Boolean,
    enableInfiniteScroll: {
      type: Boolean,
      default: true,
    },
    enablePullRefresh: Boolean,
    load: Function,
    pageSize: {
      type: Number,
      default: 10,
    },
    hideLoading: Boolean,
  },
  data () {
    return {
    }
  },
  setup (props, context) {
    const dataContainer = ref(null)
    const dataList = ref([])
    const currentPage = ref(1)
    const noMore = ref(false)
    const pullRefreshLoading = ref(false)
    const loading = ref(false)
    const loadMoreLoading = ref(false)
    const { proxy } = getCurrentInstance()
    const getTableData = async (option = {}) => {
      if (proxy._isDestroyed) {
        return
      }
      // 获取数据
      let start = new Date().getTime()
      if (!option.loadMore) {
        if (!props.hideLoading && !pullRefreshLoading.value) { // 下拉刷新
          loading.value = true
        }
        dataList.value = []
        currentPage.value = 1
        noMore.value = false
      } else {
        loadMoreLoading.value = true
      }
      let ret = await props.load(currentPage.value, props.pageSize)
      let diff = (new Date().getTime() - start)
      if (loading.value) {
        if (diff < 200) {
          setTimeout(() => {
            loading.value = false
          }, 200)
        } else {
          loading.value = false
        }
      }
      pullRefreshLoading.value = false
      loadMoreLoading.value = false
      if (ret) {
        pushNewData(ret, option.loadMore)
      }
    }
    //
    const pushNewData = (newData, loadMore) => {
      if (newData.length < props.pageSize) {
        noMore.value = true
      }
      dataList.value.push(...newData)
    }
    const manualLoadMore = () => {
      if (loading.value || loadMoreLoading.value) {
        return
      }
      currentPage.value++
      getTableData({ loadMore: true })
    }
    if (props.enableInfiniteScroll) {
      useInfiniteScroll(
        dataContainer,
        () => {
          if (noMore.value || loading.value || loadMoreLoading.value) {
            return
          }
          currentPage.value++
          getTableData({ loadMore: true })
        },
        { distance: 150 },
      )
    }
    return {
      currentPage,
      dataList,
      dataContainer,
      noMore,
      loading,
      getTableData,
      manualLoadMore,
      pushNewData,
      loadMoreLoading,
      pullRefreshLoading,
    }
  },
  created () {
    this.init()
  },
  activated () {
    if (this.scrollTop) {
      this.$el.scrollTop = this.scrollTop
    }
  },
  methods: {
    init () {
      this.getTableData()
    },
    getCustomStyle () {
      return ''
    },
    getDataContent () {
      return this.dataList.map((data, index) => {
        return <div>{index}</div>
      })
    },
    getLoadMoreContent () {
      return (
        <div style="text-align:center;">
          <el-button type="text" on-click={this.manualLoadMore}>
            加载更多
          </el-button>
        </div>
      )
    },
    getLoadMoreLoadingContent () {
      return (
        <p
          style="margin-top:30px;"
          class="loading-more"
          v-loading={this.loadMoreLoading}
          element-loading-text="拼命加载中"
          element-loading-spinner="el-icon-loading"/>
      )
    },
    getEmptyContent () {
      return (
        <div class="data-list-empty">
          <div class="data-list-empty_img"/>
          <span>暂无数据</span>
        </div>
      )
    },
    getNoMoreContent () {
      return (
        <div class="el-divider el-divider--horizontal no-more">
          <div class="el-divider__text is-center">我是有底线的</div>
        </div>
      )
    },
    getSkeleton () {
      return (
        <el-skeleton animated loading={true} rows={6} />
      )
    },
    getDataContainerEl () {
      let dataContainer = this.$refs.dataContainer
      if (dataContainer) {
        return dataContainer.$el
      }
      return null
    },
  },
  render () {
    let { currentPage, noMore, loadMoreLoading, loading, dataList, enablePullRefresh } = this
    const dataContainer = (
      <StyledContainer
        ref="dataContainer"
        class={['data-container', !loading && currentPage === 1 && dataList.length === 0 ? 'is-empty' : '']}
        customStyle={this.getCustomStyle()}>
        {!loading ? this.getDataContent() : null}
        {!loading && currentPage === 1 && dataList.length === 0 ? this.getEmptyContent() : null}
        {!loadMoreLoading && !loading && !noMore && !this.enableInfiniteScroll ? this.getLoadMoreContent() : null}
        {noMore && currentPage > 1 ? this.getNoMoreContent() : null}
        {loadMoreLoading ? this.getLoadMoreLoadingContent() : null}
        {!loadMoreLoading && loading ? this.getSkeleton() : null}
      </StyledContainer>
    )
    if (!enablePullRefresh) {
      return dataContainer
    }
    return (
      <PullRefresh
        v-model={this.pullRefreshLoading}
        on-refresh={this.getTableData}
        class="pull-refresh-container">
        {dataContainer}
      </PullRefresh>
    )
  },
}
</script>

应用:

继承该组件,复写渲染方法。

相关推荐
默_笙11 小时前
⛵ 我用 React + TS 做了个"调色盘",顺便学会了企业级项目的目录架构
前端·javascript
预知同行11 小时前
从 MCP 到 CLI:AI Agent 工具链的架构演进与实战抉择
前端·面试
何智超11 小时前
AI 微前端性能优化之旅(中):重启
前端·vibecoding
渣波11 小时前
拒绝 Redux 样板代码:Zustand 核心原理与实战进阶指南(基础、异步与切片模式)
前端·javascript
Hamm11 小时前
前端转Java+AI全栈?那我推荐几个实战开源项目给你
前端·后端·全栈
饼干哥哥12 小时前
十年数据分析经验,被我打包成了4个跨境VOC Skill
前端·数据分析·数据可视化
东方小月12 小时前
从零开发一个 Coding Agent(六):实现一个可脚本化的 Faux Provider
前端·人工智能
程序员黑豆12 小时前
什么是JDK以及JDK都由哪些部分组成呢
java·前端·ai编程
妙码生花12 小时前
让各大 AI 整理出来的前端工程命名最简实践(Vue + TS)
前端·javascript·vue.js
郭wes代码13 小时前
纯 CSS+JS 实战:手搓一张支持自定义的通用金色电子奖状(万字详细拆解)
前端·javascript·css