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

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

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

梳理思路

node.js官网的分页组件为例,假设我们要实现下面这样的分页:
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
从产品角度梳理思路后,要实现的分页组件有以下特点:
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页的静态分页:
在这里插入图片描述

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页码改变的回调,返回改变后的页码当前页码
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值