如題,筆者最近在閑餘之時一直在研究vue的常用公共元件的開發,參考的是element和iview元件庫的樣式,另外通過參考github上的Xue-ui元件和自己的一些想法最近新學習制作了一個簡單的Vue分頁元件,下面筆者将詳細介紹這個元件的設計思路。
首先我們可以先看看最終的元件顯示效果:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL9kFRPVzYq5EeVRVTzUkMMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL2gjMzQjM0QTM0AjMxkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
圖1:簡單的分頁元件
如上圖,這裡大緻展示了我們要開發的分頁元件的具體樣式,而之是以說是簡單的分頁元件是因為這個元件既沒有帶跳轉到指定頁面的功能,也沒有控制每頁顯示的資料條數的功能,以element元件為例,一個較為完整的分頁元件是這樣的:
圖2:完整的分頁元件
當然這樣的分頁元件就比較複雜一些了,因為它還涉及到了下拉選框元件、輸入框元件的使用。話說回來,我們今天要實作的隻是圖1。開發公共元件,俗稱造輪子,其實開發界一直不鼓勵重複造輪子,但是研究輪子是怎麼造出來的,了解輪子的制作原理還是很重要的。
設計思路
首先,我們把要設計開發的分頁元件命名為Pager元件。我們根據圖1的最終元件顯示圖,想象出具體的HTML構架。我們發現這裡有兩種樣式,一種是頁面數字帶背景色的,另一類是簡易版的隻是單純的顯示頁面數字,沒有背景色。而實作了帶背景色的分頁按鈕的Pager,簡易版的自然而然也就可以實作了。對于這種一排過去含有若幹個按鈕的頁面,我們可以把每一個按鈕當成一個div,div中顯示具體的分頁頁碼或者“...”。當然,更主流的做法是把這些按鈕看成是ul标簽下的li,好比是我們當初的開發網頁時設計的導航欄一樣。另外這裡的最左和最右邊分頁有兩個箭頭符号“<” ">"用來表示上一頁和下一頁。這裡如果要偷懶的話可以直接用大于小于号來展示,當然如果要做的好一些的話就用svg類型的圖檔來顯示。好了,說到這裡其實pager元件的基本HTML架構我們已經了解了,而css的寫法也不複雜,唯一需要注意的是這裡有兩套樣式的展示,需要通過元件的屬性來切換樣式的顯示。
在說完了HTML和CSS之後我們來說最複雜的JS部分。首先是Pager元件需要的屬性值。那麼對于一個分頁元件首先是父元件在調用它是需要傳入的Props,對于我們需要實作的簡單分頁元件來說,隻要總頁數和目前所在頁碼是必須要傳的,另外還有一個控制樣式變化的屬性我們可以選擇性的傳遞。主要屬性就是這些了,接下來就是怎麼利用這些屬性來展示具體的頁碼了,下面我們來看一下完整的代碼:
<template>
<ul class="c-pager" v-show="!singleHide || total !== 1" :class="{simple}">
<li class="num" :class="{disabled:current===1}" @click="onSkip(-1)">
<c-icon name="arrow" class="arrow-left"></c-icon>
</li>
<li v-for="(page, index) in pages" :key="index" class="num" :class="{active:page===current, seprator:page==='...'}" @click="onClickPage(page)">
<template v-if="page==='...'">
<c-icon name="dot"></c-icon>
</template>
<template v-else>
{{page}}
</template>
</li>
<li class="num" :class="{disabled:current===total}" @click="onSkip(1)">
<c-icon name="arrow" class="arrow-right"></c-icon>
</li>
</ul>
</template>
<script>
import cIcon from '@/components/basic/icon/Icon.vue'
export default {
name: 'cPager',
components: {
cIcon
},
props: {
total: {
type: Number,
required: true
},
current: {
type: Number,
required: true
},
singleHide: {
type: Boolean,
default: true
},
simple: {
type: Boolean,
default: false
}
},
computed: {
pages () {
let array = [1, this.total, this.current, this.current - 1, this.current - 2, this.current + 1, this.current + 2]
if (this.current <= 4) {
array = [1, 2, 3, 4, 5, 6, 7, this.current + 1, this.current + 2, this.total]
}
if (this.current >= this.total - 3) {
array = [1, this.total, this.current, this.total - 1, this.total - 2, this.total - 3, this.total - 4, this.total - 5, this.total - 6]
}
array = this.unique(array.sort((a, b) => a - b))
let pages = array.reduce((prev, current, index, array) => {
prev.push(current)
let length = prev.length
if (prev[length - 2] && current - prev[length - 2] > 1) {
prev.splice(prev.length - 1, 0, '...')
}
return prev
}, [])
pages = pages.filter(n => (n >= 1 && n <= this.total) || n === '...')
return pages
}
},
methods: {
unique (arr) {
let newArray = []
arr.forEach(n => {
if (newArray.indexOf(n) === -1) {
newArray.push(n)
}
})
return newArray
},
onClickPage (page) {
if (page !== '...') {
this.$emit('update:current', page)
}
},
onSkip (num) {
if (num === -1 && this.current > 1) {
this.$emit('update:current', this.current - 1)
}
if (num === 1 && this.current < this.total) {
this.$emit('update:current', this.current + 1)
}
}
}
}
</script>
<style scoped>
@import '@/scss/baseColor.scss';
.c-pager {
font-size: 14px;
font-weight: 600;
display: flex;
justify-content: flex-start;
align-items: center;
line-height: 30px;
user-select: none;
height: 30px;
.arrow-left {
font-size: 10px;
transform: rotateZ(180deg);
}
.arrow-right {
font-size: 10px;
}
> .num {
min-width: 35px;
height: 100%;
background: $bg;
cursor: pointer;
padding: 2px 0;
display: flex;
justify-content: center;
align-items: center;
&:not(:first-child) {
margin-left: 4px;
}
&:hover:not(.seprator) {
color: $p;
}
&.active {
background: $p;
color: #fff;
cursor: default;
&:hover {
color: #fff;
}
}
&.seprator {
cursor: default;
}
&.disabled {
color: $disabled;
cursor: not-allowed;
&:hover {
color: $disabled;
}
}
}
&.simple {
> .num {
background: none;
color: $main;
&:hover:not(.seprator) {
color: $p;
}
&.active {
color: $p;
cursor: default;
}
&.disabled {
color: $disabled;
cursor: not-allowed;
&:hover {
color: $disabled;
}
}
}
}
}
</style>
代碼解讀:
通過代碼我們可以發現Pager元件的實作還是比較簡單的,大部分地方都比較好懂,這裡主要是利用了pages這個計算屬性來存儲目前頁面需要顯示的頁碼值,另外的兩個li則是展示“上一頁”和“下一頁”這兩個按鈕。關于pages的計算:pages主要有三種不同的顯示形态,這裡我們設目前頁碼值為current,總頁碼數為total
① 一般顯示狀态:
頁碼數較多時
頁碼數較少時
② current比較小時左側頁碼完整顯示不帶“...”
③ current比較大時(隻比total小一點)右側頁碼完整顯示不帶“...”
在不考慮“...”按鈕的展示的情況下我們根據以上三種情況可以得出pages分為為
① let array = [1, this.total, this.current, this.current - 1, this.current - 2, this.current + 1, this.current + 2]
② array = [1, 2, 3, 4, 5, 6, 7, this.current + 1, this.current + 2, this.total]
③ array = [1, this.total, this.current, this.total - 1, this.total - 2, this.total - 3, this.total - 4, this.total - 5, this.total - 6]
當然上述的array數組頁碼數可能會存在重複的情況,是以我們會在之後的進行中為array去重,這裡利用ES6的set去重最為簡潔明快,當然自己寫一個去重函數也是可以的。 之後我們需要給pages加上"...",也就是以下代碼:
let pages = array.reduce((prev, current, index, array) => {
prev.push(current)
let length = prev.length
if (prev[length - 2] && current - prev[length - 2] > 1) {
prev.splice(prev.length - 1, 0, '...')
}
return prev
}, [])
這裡利用了一個reduce函數為pages加上"...",這裡用到了reduce函數來作為篩選還是比較巧妙的,關于reduce的具體用法我們可以參考這篇文章 https://www.jianshu.com/p/541b84c9df90,reduce函數在這裡的主要作用是歸并,首先把initialValue參數置為空數組[ ],也就是prev的初始值,之後依次将pages數組中的元素周遊放入prev中,當出現數組長度大于等于2之後,我們需要判斷prev數組的最後一個元素與倒數第二個元素之間的內插補點是不是大于1,如果大于1則說明兩個元素之間還可以存在其他數字,此時我們将“...”符号插入到兩個元素中間,用以表示省略顯示了部分數字。最後我們再篩選一下數組元素,去掉不滿足條件的元素:
pages = pages.filter(n => (n >= 1 && n <= this.total) || n === '...')
這樣一來便得到了最關鍵的pages數組。
另外一個比較關鍵的點是跳轉頁面觸發的函數,由于跳轉頁面需要改變的是current的值,而current是由父元件傳入到Pager元件中的,是以當需要更新current值時,不能在Pager元件中更新,而應該使用$emit函數将current的變化通知到父元件中:
this.$emit('update:current', this.current + 1)
這裡将emit的觸發事件寫成‘update:current’是友善在父元件中直接用.sync符進行綁定current,代碼如下:
<c-pager :total="50" :current.sync="current2"></c-pager>
其相當于是:
<c-pager :total="50" :current="current2" @update:current="val => current2 = val"></c-pager>
這樣便将current的變化通知到了父元件,然後由父元件來修改current2的值。
總結:
開發公共元件本身并不難,但是在沒有相關知識儲備和經驗的情況下可能無從下手,而在實際的生産活動中我們一向不鼓勵重複造輪子,但是了解輪子的建構原理很重要,它不僅能提升我們的工作效率,也加深了我們對語言和架構的認識。分頁元件在元件開發中屬于比較簡單的一類元件了,就像我們上面做的這個分頁元件一樣,其實思路并不複雜,但是需要大量的基礎知識儲備。另外我們還可以考慮在原有的元件基礎上加入跳轉頁碼和選擇每頁顯示條數的功能。
參考:https://github.com/BlameDeng/xue-ui