【Vue組件封裝】之簡單分頁組件的研究與實現

如題,筆者最近在閒餘之時一直在研究vue的常用公共組件的開發,參考的是element和iview組件庫的樣式,另外通過參考github上的Xue-ui組件和自己的一些想法最近新學習製作了一個簡單的Vue分頁組件,下面筆者將詳細介紹這個組件的設計思路。

首先我們可以先看看最終的組件顯示效果:

圖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 lang="scss" 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

發佈了25 篇原創文章 · 獲贊 22 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章