第九集: 从零开始实现一套pc端vue的ui组件库( 分页器组件 )

第九集: 从零开始实现( 分页器组件 )

本集定位:
分页器这个组件也算是个老朋友了, 还记得刚学js的时候, 写个分页器要300行代码,要是能穿越回去, 我得好好教教我本身设计模式😹.css

随着如今手机地位的提高, 大部分人上网的时间都用在了手机上, pc端的确是少了不少不少, 而分页器这种类型的组件, 真的并不很适合手机, 已经不符合人类的操做体验了, 人们如今都是划划划或拉拉拉的动做驱动翻页, 分页器其实要配合鼠标才能有很好的感觉, 人的手指点击并不精准, 并且点击的时候还会遮盖住视野, 反正本人更推荐pc端使用分页器来跳转, 移动端可不要这么玩, 多想点让用户更舒服的操做吧.vue

本次编写参考了饥人谷的视频, 同时也看了element的源码, 但最终仍是按我本身的想法构建了这个组件.webpack

  1. 增长了本身的一些理解, 实现方式与想法挺有趣的, 毕竟代码这种东西不是一成不变的, 更多的玩法才能使编程更有生命力,
  2. 去掉了好比说一个输入框, 能够跳转到固定页数, 这个功能我去年作了半年的后台管理系统, 一次也没用上.
  3. 没有去想传统组件同样, 用户传入总条数, 而后我来给分红对应的页数, 而是采用用户本身传入分红了多少页.
  4. 本次激活的页面会以v-model的形式与用户进行交互, 也就是说这个变量是双向的, 上面说去掉的两个功能就很是好实现了, 但本次不会作, 毕竟实践经验告诉我, 作了真没啥用

一. 基本结构
这个结构是参考的别人的源码... 还有挺好的, 虽然dom写的有点丑, 可是逻辑清晰, 易维护.git

老样子
vue-cc-ui/src/components/Pagination/index.jsgithub

import Pagination from './main/pagination.vue';

Pagination.install = function(Vue) {
    Vue.component(Pagination.name, Pagination);
  };

export default Pagination;

躯壳web

  1. 单独写了 第一位与最后一位, 默认就是这两位要一直展现给用户选择
  2. ...这种标志也是单独写了, 毕竟首尾都直接写了, 那他也能够这样操做
  3. 左右两边的两个按钮容许用户插入本身的代码
  4. 这个结构的好处就是把问题具体化了, 不用考虑其余的, 当前核心问题就是如何求出中间for循环的数据,也就是本集的重点了.
<template>
  <div class='cc-pagination'>
    <button class="btn-prev">
      <slot name='previous'> &lt; </slot>
    </button>
    <ul class='cc-pagination__box'>
      <li>1</li>
      <li>··</li>
      <li ....>{{item}}</li>
      <li>··</li>
      <li v-if="总页数!== 1">{{总页数}}</li>
    </ul>
    <button class="btn-prev">
      <slot name="next"> &gt; </slot>
    </button>
  </div>
</template>

先展现一下基本的样子
图片描述vue-router

css 方面vuex

@include b(pagination) {
    cursor: pointer;
    color: #606266; // 这个颜色很柔和的黑
    align-items: center;
    display: inline-flex;
    justify-content: center;
    .btn-prev { // 按钮去掉默认样式
        border: none;
        outline: none;
        background-color: transparent;
        &:hover { // 这个nomal是个柔和的蓝色
            color: $--color-nomal
        }
    }
}

二. 功能的定义 编程

  1. pageTotal: 总的页数, 就是说比这波数据分红700页显示, 那就传进来700,
  2. pageSize: 最多显示多少个分页标示, 好比说 传入了3, pageTotal传入了6, 那就是
    1,2 ... 6 页面上只显示这三个数. 通过不少次实验, 这个数最小也要传5, 否则体验会不好,最大能够传无限, 朋友们有机会能够本身试试.
  3. value: 实现v-model的基本元素
  4. validator: 这个函数是子组件接收参数时的校验函数, 这里不能修改参数, 他只负责告诉用户传的对不对就行了, 不要有太多功能, 逻辑分散的话很差维护.
  5. 下面的代码出现了三个重复的函数, 那么 必需要封装一个共用的工具函数了
pageSize: {
      type: Number,
      default: 5,
      validator: function(value){
      if (value < min || value !== ~~value) {
        throw new Error(`最小为5的整数`);
       }
       return true;
      }
    },
    value: {
      // 选中页
      type: Number,
      required: true,
      validator: function(value){
      if (value < min || value !== ~~value) {
        throw new Error(`最小为1的整数`);
       }
       return true;
      }
    },
    pageTotal: {
      // 总数
      type: Number,
      default: 1,
      required: true,
      validator: function(value){
      if (value < 1 || value !== ~~value) {
        throw new Error(`最小为1的整数`);
       }
       return true;
      }
    }

抽离工具函数
vue-ui/my/vue-cc-ui/src/assets/js/utils.js设计模式

// inspect单词就是检测的意思, 暂时业务只须要传入一个最小值;
export function inspect(min) {
// 返回一个函数做为真正的校验函数
  return function(value) {
// 小于这个最小值或不是整数的都要抛错
// ~~这个位运算符的写法的意思就是取整, 取整以后与没取整相等, 固然就不是浮点数
// ~运算符是对位求反,1变0,0变1,也就是求二进制的反码
    if (value < min || value !== ~~value) {
      throw new Error(`最小为${min}的整数`);
    }
    return true;
  };
}

通过抽离, 我这里就能够化简了, 清爽了不少

pageSize: {
      type: Number,
      default: 5,
      validator: inspect(5)
    },
    value: {
      type: Number,
      required: true,
      validator: inspect(1)
    },
    pageTotal: {
      type: Number,
      default: 1,
      required: true,
      validator: inspect(1)
    }

三. 完善页码的展现(重点)
逐一分析:

  1. 前面说了, 首尾页码已经直接写上了, 因此好比用户定义的pageSize为5 那么我就要取出中间的3个,
    好比用户当前在 第6页, 总页数 12页, 那么 1,...,5,6,7,...,12 中间的567就是我要获取的目标
  2. 本次选择用计算属性来作, 能够监控v-model的实时变化.名为showPages, 供li去循环展现;
  3. 兼容value值出现错误的状况
  4. 这种作法确定是有偏移的, 好比说 用户输入了pageSize为4, 会出现两种状况让你选择
    1,...,5,6,...,12, 1,...,6,7,...,12 在6被激活时, 究竟是要5,仍是7这个不必纠结, 随便写一个就行了, 由于我纠结了一下感受没意义😖;
  5. 作法思路, 拿到当前要激活的页码value, 而后向他左右延伸, 好比拿到value是 6, 那么左右就是5,7, 这样不断的遍历拿值, 最终在规定数量内, 而且不要触及边界条件.
showPages() {
// 习惯性的定义返回的变量
      let result = [],
      // 拿到所需的变量
        value = this.value,
        pageTotal = this.pageTotal,
        // 由于要去掉头尾, 因此-2
        pageSize = this.pageSize - 2;
      // 防止用户输入错误引发的混乱, 好比用户的缓存, 要返给用户, 让用户去处理, 由于极可能v-model出现死循环
      if (value > pageTotal) {
      // 友好的触发一个错误事件
        this.$emit("error", value, pageTotal);
        value = pageTotal;
      }
      // 若是被激活的页面在1与end之间, 则把value放入数组, 否则的话会出现重复值
      if (value > 1 && value < pageTotal) result.push(value);
      // 双管齐下, 求出当前激活的页码左右的数据
      for (let i = 1; i <= pageSize; i++) {
      // 加法, 因此检测小于总数就行
        if (value + i < pageTotal) {
          result.push(value + i);
          // 随时甄别是否已经符合条件, 取值已够就退出;
          if (result.length >= pageSize) break;
        }
      // 减法, 只要检测大于1就行
        if (value - i > 1) {
          result.unshift(value - i);
          if (result.length >= pageSize) break;
        }
      }
      return result;
    },

上面的li标签 放心遍历了

<li v-for="item in showPages"
          :key='item'
          :class="{'is-active':value === item}">{{item}}</li>

四. 定义事件

说了这么多, 结构已经作好了, 那么就须要事件的驱动了;

  1. 这个事件负责通知父级改变值, 同时会作相应的校验;
  2. 参数为当前想要激活哪一页
  3. 每次事件都通知父级会有重复的激活, 因此这个方法里面会把想要激活的页码与当前激活的页码进行比较, 放在抛出的事件的第二个参数里面, 用户只要判断isNoChange的真伪就知道是否要请求新数据了, 用户还能够根据这个提示用户"您已在xx页"
handlClick(page) {
      if (page < 1) page = 1;
      if (page > this.pageTotal) {
        page = this.pageTotal;
      }
      let isNoChange = this.value === page;
      this.$emit("input", page);
      // 当前值, 与当前值相比是否有变化
      this.$emit("onChange", page, isNoChange);
    }
  1. 前进后退直接+1-1就好了
  2. ...按钮要作一下处理, 由于涉及到前进与后退的加减,
  3. ...按钮的点击我设计为跳转到一个当前正好看不到的页面, 当前点不到的就行
  4. 分为两个函数来处理
// 左侧的...
    previous() {
      // 左侧未显示的第一个
      let page = this.showPages[0];
      this.handlClick(page - 1);
    },
    // 右侧的...
    next() {
    // 右侧未显示的第一个
      let len = this.showPages.length,
        page = this.showPages[len - 1] + 1;
      this.handlClick(page + 1);
    },

把时间放到dom上吧

<template>
  <div class='cc-pagination'>
    <button class="btn-prev"
            @click="handlClick(value-1)"
            // 到头了要提示用户, 显示出禁止点击的样式
            :style="{'cursor': (value === 1)?'not-allowed':'pointer'}">
      <slot name='previous'> &lt; </slot>
    </button>
    <ul class='cc-pagination__box'>
      <li @click="handlClick(1)"
          :class="{'is-active':value === 1}">1</li>
      <li v-if='showLeft'
          @click="previous">··</li>
      <li v-for="item in showPages"
          :key='item'
          @click="handlClick(item)"
          :class="{'is-active':value === item}">{{item}}</li>
      <li v-if='showRight'
          @click="next">··</li>
      <li v-if="pageTotal !== 1"
          @click="handlClick(pageTotal)"
          :class="{'is-active':value === pageTotal}">{{pageTotal}}</li>
    </ul>
    <button class="btn-prev"
            @click="handlClick(value+1)"
            // 到头了要提示用户, 显示出禁止点击的样式
            :style="{'cursor': (value === pageTotal)? 'not-allowed':'pointer'}">
      <slot name="next"> &gt; </slot>
    </button>
  </div>
</template>

为了判断左右的 ...是否显示, 咱们也要抽离出判断的逻辑
好比说中间的那个数组两边的元素链接上了, 就不显示.. 不然出现.

showLeft() {
      let { showPages, pageTotal, pageSize } = this;
      // 左边不是2, 而且pageTotal超出规定数才显示, 否则1 ... ... 2 很尴尬
      return showPages[0] !== 2 && pageTotal > pageSize;
    },
    showRight() {
      let { showPages, pageTotal, pageSize } = this,
        len = showPages.length;
      return showPages[len - 1] !== pageTotal - 1 && pageTotal > pageSize;
    }

至此, 功能性的东西才告一段落

五. 丰富样式与效果

  1. 用户能够传background 以激活背景色效果, 看对比图

图片描述
图片描述

  1. 对比一下大量数据的美, 😃哈哈哈, 这个组件的特点就是能够无限多

图片描述
图片描述

代码实现一下

// background是关键字, 尤为涉及到css 不要直接使用
// js里面为了方便用户, 能够适当使用
<ul class='cc-pagination__box'
        :class="{'ground-box':background}">

css
单独抽离出ground样式, 为之后的扩展作准备

.ground {
        background-color: #f4f4f5;
        ;
        border-radius: 4px;
    
        &:hover {
            background-color: $--color-nomal;
            color: white;
        }
    }
    .ground-box { // 背景色是关键字
        &>li {
            @extend .ground;
        }
        &>.is-active{
            background-color: $--color-nomal;
            color: white;
        }
    }

hideOne 属性, 开启只有一页的时候不显示组件

// 最外层的父级定义就行了
 <div class='cc-pagination'
       v-if='!(hideOne && pageTotal === 1)'>

total: 开启左侧显示条数模式
我作的与别人不一样, 你传了我就显示, 没传就无所谓, 没有附加的功能

<p v-if="total"
       class="total-number">总共 <span> {{total}}</span> 条</p>

图片描述

麻雀虽小, 五脏俱全, 作这个也花费了半天的时间, 测出好多问题, 都一一改进了.

end
另外最近计划作一个vue,vuex, vue-router, webpack 原理解析的系列,也是一点点从零开始, 期待你们继续一块儿学习,一块儿进步, 实现自我价值!!
下一集准备聊聊 计数器...上期就这么说的😓;
更多好玩的效果请关注我的博客:连接描述
github地址:连接描述

相关文章
相关标签/搜索