第九集: 從零開始實現一套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地址:連接描述

相關文章
相關標籤/搜索