my-card-list/src/CardList.vue

<template>
  <div class="my-card-list">
    <!-- 列表内容 -->
    <div v-if="list.length && !this.currentLoading" class="my-card-list__wrapper">
      <el-row v-bind="rowProps">
        <el-col v-for="(item, index) in list"
                :key="index"
                v-bind="colProps"
                :style="colStyle">
          <slot
            v-bind="{item, index, page:currentPage,pageSize:currentPageSize,total:currentTotal,columns:currentColumn}">
            {{item}}
          </slot>
        </el-col>
      </el-row>
    </div>

    <!-- 加载中提示 -->
    <div v-if="this.currentLoading" class="my-card-list__loading">
      <slot name="loading">正在努力加载数据...</slot>
    </div>

    <!-- 无数据提示 -->
    <div v-if="!this.currentLoading && !list.length" class="my-card-list__empty">
      <slot name="empty">暂无数据</slot>
    </div>

    <!-- 列表底部自定义内容 -->
    <div v-if="$slots.append" class="my-card-list__append">
      <slot name="append"></slot>
    </div>

    <!-- 分页 -->
    <Pagination v-if="pager && currentTotal"
                class="my-card-list__pager"
                ref="pager"
                v-bind="pagerProps"
                :total="currentTotal"
                :current-page="currentPage"
                @size-change="handlePageSizeChange"
                @current-change="handlePageChange"></Pagination>
  </div>
</template>

<script>
  /**
   * 卡片列表组件
   * @module $ui/components/my-card-list
   */
  // import responsive, {responsiveArray} from '$ui/utils/responsive'
  import responsiveCol from '$ui/utils/responsive-col'
  import {Pagination} from 'element-ui'
  // 分页组件默认配置
  const defaultPagerProps = {
    align: 'center',
    background: true,
    layout: 'prev, pager, next'
  }

  /**
   * 插槽
   * @member slots
   * @property {string} default 作用域插槽,定义卡片,参数:item 卡片数据,index 索引位置
   * @property {string} empty 无数据时显示内容
   * @property {string} loading 定义加载中提示
   * @property {string} append 列表底部自定义内容
   */
  export default {
    name: 'MyCardList',
    mixins: [responsiveCol],
    components: {
      Pagination
    },
    /**
     * 属性参数
     * @member props
     * @property {Array} [data] 列表数据数组
     * @property {Function} [loader] 数据加载函数,loader 优先 data,函数必须要返回Promise,需要回调 {list, total}
     * @property {number|object} [columns] 显示列数,支持响应式对象设置 {xxl,xl,lg,md,sm,xs}
     * @property {boolean} [listenEl] 监听$el元素宽度实现响应布局(默认为false)
     * @property {boolean|object} [pager] 分页配置,如果不设置,不开启分页功能
     * @property {number} [page=1] 初始页码, 从1开始
     * @property {number} [pageSize=12] 每页显示几条
     * @property {number} [total=0] 数据记录数,开启分页才有效
     * @property {boolean} [auto] 初始化完成后调用loader
     * @property {number} [gutter=12] 栅格间隔
     * @property {boolean} [loading] 显示loading
     */
    props: {
      // 数据
      data: Array,
      // 数据加载函数,loader 优先 data,函数必须要返回Promise,回调{list, total}
      loader: Function,
       
      // 初始页码, 从1开始
      page: {
        type: Number,
        default: 1
      },

      // 每页显示几条
      pageSize: {
        type: Number,
        default: 12
      },

      // 记录数
      total: {
        type: Number,
        default: 0
      },

      // 分页配置
      pager: {
        type: [Boolean, Object]
      },
      // 初始化完成后调用loader
      auto: {
        type: Boolean,
        default: true
      },
      // 栅格间隔
      gutter: {
        type: Number,
        default: 12
      },
      loading: Boolean
    },
    data() {
      return {
        list: [], 
        currentPage: this.page,
        currentTotal: this.total,
        currentPageSize: this.pageSize,
        currentLoading: this.loading
      }
    },
    computed: {
      rowProps() {
        return {
          gutter: this.gutter
        }
      },
      colProps() {
        return {
          span: Math.floor(24 / this.currentColumn)
        }
      },
      colStyle() {
        return {
          paddingBottom: `${this.gutter}px`
        }
      },
      pagerProps() {
        return {
          ...defaultPagerProps,
          ...this.pager,
          pageSize: this.pageSize,
          total: this.total,
          currentPage: this.page
        }
      }
    },
    watch: {
      data: {
        immediate: true,
        handler(val = []) {
          this.list = Object.freeze(val.slice(0))
        }
      },
      loading: {
        immediate: true,
        handler(val) {
          this.currentLoading = val
        }
      }, 
      pagerProps: {
        immediate: true,
        handler(props) {
          this.currentPage = props.currentPage
          this.currentTotal = props.total
          this.currentPageSize = props.pageSize
          this.auto && this.$nextTick(this.load)
        }
      }
    },
    methods: {
      handlePageChange(page) {
        this.currentPage = page
        /**
         * 分页页码变化时触发
         * @event page-change
         * @param {number} page 页码
         * @param {number} pageSize 页大小
         */
        this.$emit('page-change', page, this.currentPageSize)
        this.load()
      },
      handlePageSizeChange(size) {
        this.currentPage = 1;
        this.currentPageSize = size
        /**
         * 页大小变化时触发
         * @event size-change
         * @param {number} pageSize 页大小
         */
        this.$emit('size-change', size)
        this.load()
      },
      load() {
        if (!this.loader) return

        this.currentLoading = true
        this.loader(this.currentPage, this.currentPageSize).then(res => {
          this.list = res.list
          this.currentTotal = res.total || 0
          this.$emit('success', res)
        }).catch(e => {
          this.$emit('error', e)
        }).finally(r => {
          this.currentLoading = false
        })

      },
      /**
       * 刷新列表
       * @method refresh
       * @param {number} [page] 刷新的页码
       */
      refresh(page) {
        this.currentPage = page || this.currentPage
        this.load()
      }
    }
  }
</script>