基于Vue的简单通用分页组件

分页组件是每一个系统里必不可少的一个组件,分页组件分为两部分。第一部分是模版部分,用于显示当前分页组件的状态,例如正在获取数据、没有数据、没有下一页等等;第二部分是分页数据对象,用于封装一个分页组件的属性和方法,例如获取数据的 url、当前第几页(page)、每次加载条数(count)、一共有多少页(totalPage)等等,方法可能会有上一页、下一页、处理数据等等。

分页数据对象

import base from '@/api/base'

export default class Pagination {
  constructor({ url, processFunc, processExt, count = 10, isMock = false }) {
    // 数据访问地址
    this.url = url
    // 数据集合
    this.list = []
    // 第几页
    this.page = 1
    // 一共几页
    this.totalPage = 1
    // 加载数据条数
    this.count = count
    // 数据处理函数
    this.processFunc = processFunc
    // 错误处理函数
    this.processExt = processExt
    // 正在加载中
    this.loading = false
    // 参数
    this.params = {}
    // 是否底部
    this.reachBottom = false
    // 是否为空
    this.empty = true
    // 是否需要清除
    this.toClear = false
    // 是否为mock数据
    this.isMock = isMock
  }

  /**
   * 加载下一页数据
   */
  async next(args) {
    if (this.loading) {
      // console.warn('page loading!')
      return this
    }
    const param = {
      pageNo: this.page,
      pageSize: this.count
    }
    // 附加参数
    this.loading = true
    try {
      Object.assign(param, args)
      let res
      let data
      try {
        res = await base.get(this.url, param)
        data = res.data
      } catch (e) {
        if (typeof this.processExt === 'function') {
          data = this.processExt(e)
        } else {
          throw new Error(e)
        }
      }
      // 底部判断
      if (data === null || data.length < 1) {
        if (this.toClear) {
          this.clear()
        } else {
          this.reachBottom = true
        }
        return this
      }
      this.empty = false
      // 处理数据
      this._processData(data)
      // 设置数据
      if (this.toClear) {
        this.list = data
        this.toClear = false
      } else {
        this.list = this.list.concat(data)
      }
      ++this.page
      this.totalPage = res.page.totalPages
      if (
        (res.page && res.page.page === res.page.totalPages) ||
        data.length < this.count
      ) {
        this.reachBottom = true
      }
      return this
    } finally {
      this.loading = false
    }
  }

  /**
   * 恢复到第一页
   */
  reset() {
    this.empty = true
    this.toClear = true
    this.page = 1
    this.reachBottom = false
  }

  clear() {
    this.toClear = false
    this.page = 1
    this.list = []
  }

  /**
   * 处理数据(私有)
   */
  _processData(data) {
    if (this.processFunc) {
      for (let i in data) {
        const result = this.processFunc(data[i])
        if (result) {
          data[i] = result
        }
      }
    }
  }
}

分页模版

<template>
  <div class="z-page-stat">
    <p v-show="page.loading" class="page-loading">
      <span class="ign-loading"></span>
    </p>
    <div
      class="u-more-btn"
      v-show="showLoadMore && !page.reachBottom && !page.loading && !page.empty"
      @click="$emit('nextPage')"
    >
      <span>查看更多</span>
    </div>
    <p class="reach-btm" v-show="showBtmTx && !page.empty && page.reachBottom">
      到底了~
    </p>
    <div class="page-empty" v-show="!page.loading && page.empty">
      <div class="empty-inner">
        <div class="img-bg" v-if="emptyImg">
          <img
            v-if="!emptyImg || emptyImg == 1"
            src="../../img/empty-page.png"
            alt=""
          />
        </div>

        <p class="tx">{{emptyText}}</p>
        <div class="empty-ctn">
          <slot name="empty"></slot>
        </div>
      </div>
    </div>
    <slot name="other"></slot>
  </div>
</template>

<script>
  export default {
    name: 'pageStatus',
    data() {
      return {}
    },
    props: {
      page: {},
      emptyImg: {},
      emptyText: {
        type: String,
        default: '暂时没有数据'
      },
      showLoadMore: {
        // 是否显示加载更多按钮
        type: Boolean,
        default: false
      },
      showBtmTx: {
        // 到底了文字要不要显示
        type: Boolean,
        default: true
      }
    },
    components: {},
    created: function() {},
    mounted: function() {},
    methods: {}
  }
</script>

<style lang="stylus" rel="stylesheet/stylus">
  .z-page-stat
    text-align center
    letter-spacing 2px
    color #757575
    line-height 60px
    .page-loading
      .ign-loading
        border-radius 100%
        margin 16px 0
        animation-fill-mode both
        border 2px solid #e8473f /* no */
        border-bottom-color transparent
        height 25px /* no */
        width 25px /* no */
        background transparent !important
        display inline-block
        animation rotate 1s 0s linear infinite
    .page-empty
      position absolute
      left 0
      width 100%
      top 50%
      transform translate(0, -50%)
      .empty-inner
        width 320px
        margin 0 auto
      .img-bg
        position relative
        display inline-block
        width 254px
        height 254px
        background #d6d6d6
        border-radius 50%
        margin-bottom 20px
      img
        width 94px
        margin-top 28px
      .tx
        color #8c8c8c
    .empty-ctn
      .u-btn
        margin-top 90px
        margin-left 20px
        border 2px solid #464646 /* no */
        box-shadow none
        width 168px
        height 62px
        line-height 62px
</style>

使用组件

<template>
  <div>
    <div class="card-content" v-for="act in page.list" :key="act.id">
      <p>
        {{act.title}}
      </p>
    </div>
    <p-status :page="page"></p-status>
  </div>
</template>

<script>
  import { mainList } from '@/api/activity'
  import PageStatus from 'comps/pageStatus.vue'

  export default {
    data() {
      return {
        page: mainList()
      }
    },
    mixins: [appPage],
    computed: {},
    created: async function() {
      this.page.next({
        /*传参*/
      })
    },
    components: { 'p-status': PageStatus }
  }
</script>
// @/api/activity
import Pagination from '@/utils/Pagination'

/**
 * 列表
 */
export function mainList() {
  const url = `/activity/activity/list.do`
  return new Pagination({
    url: url
  })
}
posted @ 2019-03-01 15:01  .Juliana  阅读(1004)  评论(0编辑  收藏  举报