手写分页器组件

主题

数据量过大的时候,前端分页是避免不了的事情,记录一次手写分页器组件(估计我这辈子只会手写这一次)。

首先把静态分页器弄出来。再做js逻辑设计。

<template>
  <div class="pagination">
    <button>上一页</button>
    <button>1</button>
    <button>···</button>

    <button>3</button>
    <button>4</button>
    <button>5</button>
    <button>6</button>
    <button>7</button>

    <button>···</button>
    <button>9</button>
    <button>下一页</button>

    <button style="margin-left: 30px">共 60 条</button>
  </div>
</template>

<script>
export default {
  name: 'Pagination'
}
</script>

<style lang="less" scoped>
.pagination {
  text-align: center;
  button {
    margin: 0 5px;
    background-color: #f4f4f5;
    color: #606266;
    outline: none;
    border-radius: 2px;
    padding: 0 4px;
    vertical-align: top;
    display: inline-block;
    font-size: 13px;
    min-width: 35.5px;
    height: 28px;
    line-height: 28px;
    cursor: pointer;
    box-sizing: border-box;
    text-align: center;
    border: 0;

    &[disabled] {
      color: #c0c4cc;
      cursor: not-allowed;
    }

    &.active {
      cursor: not-allowed;
      background-color: #409eff;
      color: #fff;
    }
  }
}
</style>

视图:

在这里插入图片描述

参数设计

先要搞清楚几件事情,前端做分页我们需要什么东西?这里假设我们做的是一个商城项目。

  • 1.total:商品的总数量
  • 2.pageSize: 每页需要显示的数量
  • 3.curPage:当前页

然后我设计的分页器,中间那一坨,默认显示5个,也就是当前页前后还带有2个页码

搞清楚我们要的东西之后,我们在父组件传入一些写死的参数测试。

父组件使用

 <Pagination :total="30" :curPage="6" :pageSize="10"></Pagination>

子组件接受

  props: {
    // 商品总数量
    total: {
      type: Number,
      default: 0
    },
    // 当前页
    curPage: {
      type: Number,
      default: 1
    },
    // 每页大小
    pageSize: {
      type: Number,
      default: 10
    }
  }

这时候问题就来了,有多少页呢?我们先来推算一下,如果total为90,每页的商品数量有10个,那么页码就有9个,但是如果total是91呢?那么页码就应该有10个,很明显,是总数除以每页数量并向上取了整。所以我们就可以在分页器组件来一个pageTotalNum,由于它是依赖于totalpageSize的,所以我们这里可以把它放到计算属性里面。

  computed: {
      // 页码总数
    pageTotalNum () {
      return Math.ceil(this.total / this.pageSize)
    }
  }

写到这里, 我们来测试一下

父组件传参

 <Pagination :total="300" :curPage="6" :pageSize="10"></Pagination>

上组件代码

<template>
  <div class="pagination">
    <button>上一页</button>
    <button>1</button>
    <button>···</button>

    <button>3</button>
    <button>4</button>
    <button>5</button>
    <button>6</button>
    <button>7</button>

    <button>···</button>
    <button>{{ pageTotalNum }}</button>
    <button>下一页</button>

    <span>共{{ total }}条</span>
  </div>
</template>

<script>
export default {
  name: 'Pagination',
  props: {
    // 商品总数量
    total: {
      type: Number,
      default: 0
    },
    // 当前页
    curPage: {
      type: Number,
      default: 1
    },
    // 每页大小
    pageSize: {
      type: Number,
      default: 10
    }
  },
  computed: {
    pageTotalNum () {
      return Math.ceil(this.total / this.pageSize)
    }
  }
}
</script>

<style lang="less" scoped>
.pagination {
  text-align: center;
  span,
  button {
    margin: 0 5px;
    background-color: #f4f4f5;
    color: #606266;
    outline: none;
    border-radius: 2px;
    padding: 0 4px;
    vertical-align: top;
    display: inline-block;
    font-size: 13px;
    min-width: 35.5px;
    height: 28px;
    line-height: 28px;
    cursor: pointer;
    box-sizing: border-box;
    text-align: center;
    border: 0;

    &[disabled] {
      color: #c0c4cc;
      cursor: not-allowed;
    }

    &.active {
      cursor: not-allowed;
      background-color: #409eff;
      color: #fff;
    }
  }
  span {
    cursor: none !important;
    background-color: unset !important;
  }
}
</style>

视图:

在这里插入图片描述

成功了,实测total为301也是没问题的,这里我就不截图了。

处理边界情况

看似这个分页器组件好像设计的挺好的,从视图上面没看出什么问题,但是还有很多边界情况需要处理。

1.当前页码问题

首先要搞清楚一件事,如果当前页码是5,那么5的前后是要有2个连续的页码的,也就是中间一坨要是3,4,5,6,7,但是问题来了,如果当前页码是2呢,总不可能是0,1,2,3,4,5吧,所以当前页码是有相关限制的,也就是说当当前页码小于3时,就要特殊处理了,因为当前页码等于3,刚好是1,2,3,4,5。

1.1 中间那一坨的起始参数

这时候我们定义一个start参数,用来表示当前页码的前2个页码,也就是说,当前页码是5,那么start就是3,当前页码是4,start就是就是2

这时候就要处理相关逻辑了

    // 当前页码的前2位
    start () {
      // 当前页码小于3,左右不对称,start直接为1
      if (this.curPage < 3) {
        return 1
      } else {
        return this.curPage - 2
      }
    }
1.2 中间那一坨的结束参数

这时候我们定义一个end参数,用来表示当前页码的后2个页码,也就是说,当前页码是5,那么end就是7,当前页码是4,end就是就是6

这时候就要处理相关逻辑了。这个地方略微麻烦一点,比如总页码数现在是8,当前页码是7,end肯定不能为9是为8,当前页码为6,end就是正常的,所以推出end小于总页码数-2才能正常显示。

    end () {
      // 当前页码不能大于总页码-2
      if (this.curPage > this.pageTotalNum - 2) {
        return this.pageTotalNum
      } else {
        return this.curPage + 2
      }
    }

循环遍历中间那一坨按钮

我们现在有中间那一坨按钮的startend参数,就可以把中间那一坨按钮用v-for循环遍历出来了

      <button v-for="(item, index) in end" :key="index">
        {{ item }}
      </button>

视图:

在这里插入图片描述

很明显,遍历是遍历出来了,但是很明显是有问题的,我们中间那一坨按钮明显多了,期望的应该是4,5,6,7,8,----1,2,3应该都是不显示的,这时候就要用到v-if了,判断的逻辑就是我只让start小于等于item的显示,就可以了。但是这里要注意一点,v-ifv-for是不能一起使用的,怎么解决请参考我的这篇文章,

上html代码

   <template v-for="(item, index) in end">
      <button v-if="start <= item" :key="index">
        {{ item }}
      </button>
    </template>

上视图:

在这里插入图片描述

这时候看起来就想当正常了。

边界情况的再处理

1.左边的边界情况

但是现在又来了,最前面那个1跟…肯定不能一直显示,打个比方如果start是1呢?看视图:

在这里插入图片描述

先来考虑左侧…的显示与隐藏,我们想想,当start为3时,1跟3中间有个2,这时候…就应该显示了,当statr为2时,…就应该隐藏了所以上…的显示与隐藏逻辑

  <button v-show="start > 2">···</button>

视图:

在这里插入图片描述

嗯,很符合我们的预期。

在上1的判断逻辑

<button v-show="start > 1">1</button>

视图:

在这里插入图片描述

也是很符合我们的预期的。

2.右边的边界情况处理

我们先来考虑右边的…显示与隐藏,当end等于30的时候,没有…,当end等于29的时候有…

这时候判断逻辑就出来了

  <button v-show="end < pageTotalNum - 1">···</button>

视图也是符合预期的,注意我这里把当前页码修改成了28

在这里插入图片描述

再来控制31的显示与隐藏,想一下,如果end等于31那么31肯定得隐藏了

    <button v-show="end !== pageTotalNum">{{ pageTotalNum }}</button>

注意这里我把当前页码修改成了29让end与总页码数相等

视图也是符合预期的:

在这里插入图片描述

3.上一页下一页按钮的禁用

设想一下:

  • 当前页在第一页,上一页肯定不能点
  • 当前页为最后一页,下一页肯定也不能点

这个地方就比较简单了上判断代码

    <button :disabled="curPage === 1">上一页</button>
	<button :disabled="curPage === pageTotalNum">下一页</button>

到这里,边界情况就结束了。我们开始设计交互逻辑

点击事件的绑定

按钮的事件绑定

点击哪个页码我们在父组件修改页码的值,这个地方我的思路是用子组件发射事件父组件处理页码修改,因为当前页码是从父组件传到子组件的,那么肯定涉及到子组件修改父组件的值了,所以我采取如下的方式,上父组件代码

          <Pagination
            :total="301"
            :cur-page="curPage"
            :page-size="10"
            @current-page=“currentPage”
          ></Pagination>

curPage的值初始值肯定是1,上中间那一坨按钮html代码

    <template v-for="(item, index) in end">
      <button
        v-if="start <= item"
        :key="index"
        @click="changeCurrentPage(item)"
      >
        {{ item }}
      </button>
    </template>

事件处理:

  methods: {
    changeCurrentPage (curPage) {
      this.$emit('current-page', curPage)
    }
  },

经过实际测试,是没问题的。

其他的还有上一页,下一页,第一页,最后一页的按钮逻辑,直接给出模板区域代码

<template>
  <div class="pagination">
    <button :disabled="curPage === 1" @click="changeCurrentPage(curPage - 1)">
      上一页
    </button>
    <button v-show="start > 1" @click="changeCurrentPage(1)">1</button>
    <button v-show="start > 2">···</button>

    <template v-for="(item, index) in end">
      <button
        v-if="start <= item"
        :key="index"
        @click="changeCurrentPage(item)"
      >
        {{ item }}
      </button>
    </template>

    <button v-show="end < pageTotalNum - 1">···</button>
    <button
      v-show="end !== pageTotalNum"
      @click="changeCurrentPage(pageTotalNum)"
    >
      {{ pageTotalNum }}
    </button>
    <button
      :disabled="curPage === pageTotalNum"
      @click="changeCurrentPage(curPage + 1)"
    >
      下一页
    </button>

    <span>共{{ total }}条</span>
  </div>
</template>

激活样式绑定

<template v-for="(item, index) in end">
      <button
        :class="{ active: curPage === index + 1 }"
        v-if="start <= item"
        :key="index"
        @click="changeCurrentPage(item)"
      >
        {{ item }}
      </button>
    </template>

判断一下当前页码与index+1是否相等,注意,页码是从1开始的index是从0开始的,所以index加1

样式我就不给了,各位自己定制。

代码优化

我们考虑一下,用户如果点击当前页,其实没必要在发网络请求调接口渲染了,所以我们可以在点击事件里面优化一下

    changeCurrentPage (curPage) {
        // 用户如果点击的是当前页码,就不要发请求了
      if (this.curPage === curPage) {
        return ''
      }
      this.$emit('current-page', curPage)
    }

完整组件代码

<template>
  <div class="pagination">
    <button :disabled="curPage === 1" @click="changeCurrentPage(curPage - 1)">
      上一页
    </button>
    <button v-show="start > 1" @click="changeCurrentPage(1)">1</button>
    <button v-show="start > 2" style="cursor: unset">···</button>

    <template v-for="(item, index) in end">
      <button
        :class="{ active: curPage === index + 1 }"
        v-if="start <= item"
        :key="index"
        @click="changeCurrentPage(item)"
      >
        {{ item }}
      </button>
    </template>

    <button v-show="end < pageTotalNum - 1" style="cursor: unset">···</button>
    <button
      v-show="end !== pageTotalNum"
      @click="changeCurrentPage(pageTotalNum)"
    >
      {{ pageTotalNum }}
    </button>
    <button
      :disabled="curPage === pageTotalNum"
      @click="changeCurrentPage(curPage + 1)"
    >
      下一页
    </button>

    <span>共{{ total }}条</span>
  </div>
</template>

<script>
export default {
  name: 'Pagination',
  props: {
    // 商品总数量
    total: {
      type: Number,
      default: 0
    },
    // 当前页
    curPage: {
      type: Number,
      default: 1
    },
    // 每页大小
    pageSize: {
      type: Number,
      default: 10
    }
  },
  methods: {
    changeCurrentPage (curPage) {
      if (this.curPage === curPage) {
        return ''
      }
      this.$emit('current-page', curPage)
    }
  },
  computed: {
    // 总页码数
    pageTotalNum () {
      return Math.ceil(this.total / this.pageSize)
    },
    // 当前页码的前2位
    start () {
      // 当前页码小于3,左右不对称,start直接为1
      if (this.curPage < 3) {
        return 1
      } else {
        return this.curPage - 2
      }
    },
    // 当前页码的后2位
    end () {
      // 当前页码不能大于总页码-2
      if (this.curPage > this.pageTotalNum - 2) {
        return this.pageTotalNum
      } else {
        return this.curPage + 2
      }
    }
  }
}
</script>

<style lang="less" scoped>
.pagination {
  text-align: center;
  span,
  button {
    margin: 0 5px;
    background-color: #f4f4f5;
    color: #606266;
    outline: none;
    border-radius: 2px;
    padding: 0 4px;
    vertical-align: top;
    display: inline-block;
    font-size: 13px;
    min-width: 35.5px;
    height: 28px;
    line-height: 28px;
    cursor: pointer;
    box-sizing: border-box;
    text-align: center;
    border: 0;

    &[disabled] {
      color: #c0c4cc;
      cursor: not-allowed;
    }
  }

  span {
    cursor: none !important;
    background-color: unset !important;
  }
  .active {
    background-color: #409eff;
    color: #fff;
    cursor: unset;
  }
}
</style>

总结

这个分页组件可以传3个参数

  • 1.total:商品的总数量

  • 2.pageSize: 每页需要显示的数量

  • 3.curPage:当前页

    页码改变时这个组件能发射一个事件给父组件,事件名: current-page,回调参数,当前点击的页码。

    若有任何问题,欢迎指出~~~~

博客

欢迎访问我的博客www.smartxy.cc

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值