手摸手,封装一个vue分页组件

小讴
关注前端小讴,阅读更多原创技术文章

【组件化】是每一个前端工程师的必备技能,诚然我们将element、iview、vant等UI组件库运用得娴熟自如,实际开发中还是经常需要封装更适合的业务组件,既帮助快速开发、又让代码简洁明了、还能锻炼我们的组件化能力。

梳理思路

node.js官网的分页组件为例,假设我们要实现下面这样的分页:
最终截图.png
最终截图2.png
最终截图3.png
最终截图4.png

从产品角度梳理思路后,要实现的分页组件有以下特点:
1.当前页页码颜色变化
2.最多显示5个页码,不足5个显示实际数量
3.总页数超过5个时:总页码-当前页>2,则末尾显示省略号;当前页>2,则开头显示省略号
4.总页数超过5个时:当前页为最后1页,则一共显示3个页码;当前页为倒数第二页,则一共显示4个页码;其余均显示5个页码
5.总页数超过5个、且同时满足总页码-当前页>2当前页>2时,当前页总是显示在5个页码的最中间
6.点击最左、最右侧箭头,分别跳转到第1、最后1页

基本结构

先写一个静态的结构:

<template>
  <div class="page">
    <span class="page-block center">{{'«'}}</span>
    <span v-if="pageList[2]-2>1"
          class="page-block center">...</span>
    <span v-for="pageNum in pageList"
          :key="pageNum"
          class="page-block center"
          :style="{color:pageNum===page?'#80bd01':'#778087'}">{{pageNum}}</span>
    <span v-if="pageMax-pageList[2]>2"
          class="page-block center">...</span>
    <span class="page-block center">{{'»'}}</span>
  </div>
</template>

<script>
export default {
  data () {
    return {
      pageList: [1,2,3,4,5], // 页码列表
      page: 1, // 当前页码
      pageMax: 7 // 最大页数
    };
  }
};
</script>

<style rel="stylesheet/scss" lang="scss" scoped>
.page {
  padding: 10px;
  background-color: #fff;

  &-block {
    display: inline-block;
    width: 30px;
    height: 28px;
    padding: 4px 8px;
    font-size: 0.8em;
    line-height: 18px;
    border: 1px solid #ddd;
    border-right: none;
    box-sizing: border-box;

    &:first-child {
      width: 34px;
      border-top-left-radius: 4px;
      border-bottom-left-radius: 4px;
    }

    &:last-child {
      width: 34px;
      border-right: 1px solid #ddd;
      border-top-right-radius: 4px;
      border-bottom-right-radius: 4px;
    }
  }
}
</style>

在父组件引用该组件:

<template>
  <pagination></pagination>
</template>

<script>
import Pagination from "@/components/pagination";
export default {
  components: { Pagination }
}
</script>

此时效果:将看到一个标准的、大于5页的静态分页:
最终截图.png

props接收值

组件的某些值应该是父组件传递过来的,通过计算后挂载

props: {
  // 内容总数
  total: {
    type: Number,
    default: 0
  },
  // 每页数量
  limit: {
    type: Number,
    default: 10
  },
  // 当前页码
  page: {
    type: Number,
    default: 1
  },
},
data () {
  return {
    pageList: [] // 页码列表
  };
},
computed: {
  // 最大页数
  pageMax () {
    return Math.ceil(this.total / this.limit);
  },
},
onLoad () { // 我的框架是mpvue,vue用created
  this.initData();
},
methods: {
  // 生成pageList页码列表
  initData () {
    this.pageList = []; // 清空页码
    var i = 1;
    do {
      this.pageList.push(i);
      i++;
    } while (i <= this.pageMax);
    this.pageList.length > 5 && (this.pageList = this.pageList.slice(0, 5)); // 最多显示5页
  }
}

父组件传递这些值:

<pagination :total="dataList.length"
            :limit="limit"
            :page="page"></pagination>
data () {
  return {
    dataList: [], // 列表
    page: 1, // 当前页码
    limit: 20, // 每页数量
  };
},
onLoad () {
  this.getData();
},
methods: {
  getData () {
     // 动态获取列表 - 以后端分页为例
     this.dataList = (await getDataList(this.limit, this.page)).data;
  }
}

此时效果:将根据父组件——也就是使用该组件的页面——的真实数据,渲染分页组件。页码数量大于5不大于5组件内容不同,且当前页码为首页

event事件回调

组件中追加事件,回调给父组件(调用的组件):

<template>
  <div class="page">
    <span class="page-block center"
          @click="pageChange(1)">{{'«'}}</span>
    <span v-if="pageList[2]-2>1"
          class="page-block center">...</span>
    <span v-for="pageNum in pageList"
          :key="pageNum"
          class="page-block center"
          :style="{color:pageNum===page?'#80bd01':'#778087'}"
          @click="pageChange(pageNum)">{{pageNum}}</span>
    <span v-if="pageMax-pageList[2]>2"
          class="page-block center">...</span>
    <span class="page-block center"
          @click="pageChange(pageMax)">{{'»'}}</span>
  </div>
</template>
methods: {
  // 子组件事件回调:分页
  pageChange (page) {
    this.$emit("page-change", page);
  },
}

父组件执行子组件回调过来的事件:

<pagination :total="dataList.length"
            :limit="limit"
            :page="page"
            @page-change="pageChange"></pagination>
methods: {
  // 分页
  pageChange (page) {
    this.page = page;
    this.getData();
  },
}

此时效果:点击页码颜色会发生变化,父组件接收到子组件的回调事件,返回一个值page——即当前页码,可根据页码做分页的内容渲染;点击最左、最右侧箭头,分别跳转到第1、最后1页

watch页码变化

由于不同页码要渲染的组件内容不同,因此需要监听页码变化,刷新组件内容;同时total总数更新时,重新加载组件

watch: {
  // 监听页码变化 -> 页码列表更新
  page (val) {
    if (val <= 3) {
      this.pageList = [];
      var i = 1;
      do {
        this.pageList.push(i);
        i++;
      } while (i <= this.pageMax);
      this.pageList.length > 5 && (this.pageList = this.pageList.slice(0, 5)); // 最多显示5页
    } else if (val === this.pageMax) {
      this.pageList = [val - 2, val - 1, val];
    } else if (val === this.pageMax - 1) {
      this.pageList = [val - 2, val - 1, val, val + 1];
    } else {
      this.pageList = [val - 2, val - 1, val, val + 1, val + 2];
    }
  },
  // 监听页码变化 -> 总数更新
  total (val) {
    this.initData();
  }
}

此时效果:组件根据页码变化数量变化,渲染出不同的内容,至此该分页组件全部内容完成

扩展

可以像element和iview那样,为组件追加当前页显示数量下拉框,也可以由父组件决定子组件的样式布局,页码按钮的数量(本例为最大5个)。。。总体思路不变,即:
1.子组件接收值并根据值而改变渲染
2.子组件追加事件,回调给父组件
3.根据需要追加监听

完整代码

<template>
  <div class="page">
    <span class="page-block center"
          @click="pageChange(1)">{{'«'}}</span>
    <span v-if="pageList[2]-2>1"
          class="page-block center">...</span>
    <span v-for="pageNum in pageList"
          :key="pageNum"
          class="page-block center"
          :style="{color:pageNum===page?'#80bd01':'#778087'}"
          @click="pageChange(pageNum)">{{pageNum}}</span>
    <span v-if="pageMax-pageList[2]>2"
          class="page-block center">...</span>
    <span class="page-block center"
          @click="pageChange(pageMax)">{{'»'}}</span>
  </div>
</template>

<script>
export default {
  props: {
    // 内容总数
    total: {
      type: Number,
      default: 0
    },
    // 每页数量
    limit: {
      type: Number,
      default: 10
    },
    // 当前页码
    page: {
      type: Number,
      default: 1
    },
  },
  data () {
    return {
      pageList: [] // 页码列表
    };
  },
  computed: {
    // 最大页数
    pageMax () {
      return Math.ceil(this.total / this.limit);
    },
  },
  onLoad () {
    this.initData();
  },
  methods: {
    initData () {
      this.pageList = []; // 清空页码
      var i = 1;
      do {
        this.pageList.push(i);
        i++;
      } while (i <= this.pageMax);
      this.pageList.length > 5 && // 最多显示5页
        (this.pageList = this.pageList.slice(0, 5));
    },
    // 子组件事件回调:分页
    pageChange (pageCurrent) {
      this.$emit("page-change", pageCurrent);
    },
  },
  watch: {
    // 监听页码变化 -> 页码列表更新
    page (val) {
      if (val <= 3) {
        this.pageList = [];
        var i = 1;
        do {
          this.pageList.push(i);
          i++;
        } while (i <= this.pageMax);
        this.pageList.length > 5 && // 最多显示5页
          (this.pageList = this.pageList.slice(0, 5));
      } else if (val === this.pageMax) {
        this.pageList = [val - 2, val - 1, val];
      } else if (val === this.pageMax - 1) {
        this.pageList = [val - 2, val - 1, val, val + 1];
      } else {
        this.pageList = [val - 2, val - 1, val, val + 1, val + 2];
      }
    },
    // 监听页码变化 -> 总数更新
    total (val) {
      this.initData();
    }
  }
};
</script>

<style rel="stylesheet/scss" lang="scss" scoped>
.page {
  padding: 10px;
  background-color: #fff;

  &-block {
    display: inline-block;
    width: 30px;
    height: 28px;
    padding: 4px 8px;
    font-size: 0.8em;
    line-height: 18px;
    border: 1px solid #ddd;
    border-right: none;
    box-sizing: border-box;

    &:first-child {
      width: 34px;
      border-top-left-radius: 4px;
      border-bottom-left-radius: 4px;
    }

    &:last-child {
      width: 34px;
      border-right: 1px solid #ddd;
      border-top-right-radius: 4px;
      border-bottom-right-radius: 4px;
    }
  }
}
</style>

api

props:
属性说明类型默认值
total内容总数Number0
limit每页数量Number10
page当前页码Number1
events:
事件名说明返回值
page-change页码改变的回调,返回改变后的页码当前页码
阅读 389

从前做策划、半路出家的前端程序猿,爱吉他爱户外爱篮球更爱写代码,努力为了更好的生活。大家可添加我微信(powerful_simon),互相交流互相学习,一起弹琴唱歌打球爬山。同时还望前辈们多多指点,多多帮忙内推~

170 声望
6 粉丝
0 条评论
你知道吗?

从前做策划、半路出家的前端程序猿,爱吉他爱户外爱篮球更爱写代码,努力为了更好的生活。大家可添加我微信(powerful_simon),互相交流互相学习,一起弹琴唱歌打球爬山。同时还望前辈们多多指点,多多帮忙内推~

170 声望
6 粉丝
宣传栏