See the Pen
Vue3前端分頁 by 喇賽人 (@weber87na )
on CodePen .
功能分析 & 實作 分頁應該是個每隔一兩年就會跑出來的問題 , 懶一點就用套件 , 不然自幹還是要想一陣子 剛好遇到之前修復 angularjs 滿滿 bug 的分頁 , 就順便整理下寫成 vue3 大概會需要以下幾個東西
主要資料源
=> 反正就某個地方的 json
array閹割過的主要資料源
=> 實際上看到 table
or list
的應該是他每頁裡面顯示的資料筆數
=> 通常就是 5
or 10
總頁數
=> ceil(主要資料源的數量 / 每頁裡面顯示的資料筆數)
總頁碼的 array
=> 大概長這樣 [1 , 2 , 3 , 4 , 5 , 6 , 7 , 8]顯示的頁碼 array
=> 預期 5
頁的話大概長這樣 [1 , 2 , 3 , 4 , 5]目前選到的頁碼
跳任意頁
第一頁
上一頁
下一頁
最後一頁
html & css 我這裡有套 bootstrap 5 , 大致上分為兩個部分 , table
裡面擺閹割過的主要資料源顯示資料 , nav
裡則是分頁的頁碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 <div id ="app" > <table class ="table table-striped table-hover" > <thead > <tr > <th scope ="col" > id</th > <th scope ="col" > name</th > <th scope ="col" > color</th > </tr > </thead > <tbody > <tr v-for ="flower in displayFlowers" > <td > {{ flower.id }}</td > <td > {{ flower.name }}</td > <td > {{ flower.color }}</td > </tr > </tbody > </table > <nav aria-label ="..." > <ul class ="pagination" > <li class ="page-item" @click ="gotoFirstPage()" > <a class ="page-link" href ="javascript:;" > First</a > </li > <li class ="page-item" @click ="gotoPrevPage()" > <a class ="page-link" href ="javascript:;" > Previous</a > </li > <li class ="page-item" :class ="{active:isActive(page)}" v-for ="page in displayPageArray" @click ="gotoPage(page)" > <a class ="page-link" href ="javascript:;" > {{ page }}</a > </li > <li class ="page-item" @click ="gotoNextPage()" > <a class ="page-link" href ="javascript:;" > Next</a > </li > <li class ="page-item" @click ="gotoLastPage()" > <a class ="page-link" href ="javascript:;" > Last</a > </li > </ul > </nav > </div >
css 我只擺個居中沒啥好講的
1 2 3 4 5 6 body { display: flex; justify-content: center; align-items: center; }
js 首先用 ref
定義一個 flowers
的主要資料源
1 2 3 4 5 6 7 let flowers = ref([ { id : 1 , name : '玫瑰' , color : 'red' }, { id : 2 , name : '太陽花' , color : 'yellow' }, { id : 3 , name : '向日葵' , color : 'yellow' }, { id : 4 , name : '薰衣草' , color : 'purple' }, { id : 5 , name : '鬱金香' , color : 'pink' }, ...
接著算出總頁數
1 let totalPageSize = Math .ceil(flowers.value.length / pageRows)
然後用 slice
函數 , 類似 linq
裡面的 skip
+ take
功能 假設有個資料源 [1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10]
slice(0 , 5)
=> [1 , 2 , 3 , 4 , 5]
slice(5 , 10)
=> [6 , 7 , 8 , 9 , 10]
pageRows
表示 每頁裡面顯示的資料筆數
currentPage
目前選到第幾頁 , 起始為 1
要注意 slice
則由 0
開始計算
假設第一頁取 5 * 1 - 5
及 5 * 1
假設第二頁取 5 * 2 - 5
及 5 * 2
所以可以快速得出分頁結果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 let displayFlowers = computed( () => { if (flowers.value.length > 5 ) { return normalSize(); } else { return flowers.value } } ) function normalSize ( ) { return flowers.value.slice( pageRows * currentPage.value - pageRows, pageRows * currentPage.value ) }
接著定義出 目前選到的頁碼
跳任意頁
等函數 , 另外還有個 isActive
這個是拿來套用目前選到頁碼樣式的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 let currentPage = ref(1) function gotoPage(page) { currentPage.value = page } function gotoLastPage() { currentPage.value = totalPageSize } function gotoFirstPage() { currentPage.value = 1 } function gotoPrevPage() { if (currentPage.value > 1) currentPage.value = currentPage.value - 1 } function gotoNextPage() { if (currentPage.value < totalPageSize) currentPage.value = currentPage.value + 1 } function isActive(page) { return currentPage.value === page }
接著就是最核心的地方 , 計算頁碼 先 loop 總頁碼然後 push 到 array 裡面 如果分頁數量小於 5 就直接 return , 因為算法失去意義
如果分頁數量大於 5 的話 , 先看看目前選到的頁碼是否小於 3 , 是的話直接傳 [1, 2, 3, 4, 5]
接著處理選到尾端的狀況 , 如果已經滑到尾部 , 則直接回傳由最末端的五個數值 [4 , 5 , 6 , 7 , 8]
其他則表示正常狀況 , 將目前選到的頁作為中心值依序增減直到符合預計的分頁大小即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 function getPages ( ) { let result = [] for (let i = 1 ; i <= totalPageSize; i++) { result.push(i) } return result } let displayPageSize = 5 function getDisplayPages ( ) { let pages = getPages() let result = []; if (pages.length <= 5 ) { return pages } if (currentPage.value < 3 ) { result = [1 , 2 , 3 , 4 , 5 ] return result } if (currentPage.value >= pages.length - 2 ) { result.push(pages.length - 4 ) result.push(pages.length - 3 ) result.push(pages.length - 2 ) result.push(pages.length - 1 ) result.push(pages.length) return result } if (currentPage.value >= 3 ) { result.push(currentPage.value - 2 ) result.push(currentPage.value - 1 ) result.push(currentPage.value) result.push(currentPage.value + 1 ) result.push(currentPage.value + 2 ) return result; } }
貓毛 bug 修復 不過做到這裡如果遇到很 貓毛
的人會認為是 bug
有一點點不完美 , 因為我的算法讓 active & focus
有可能是不一致的情形 原本的動作假設是點第 4 頁的話 , 會 active
第 4 頁 , 但是會 focus
在第 5 頁 要完美的話可以用 mousedown
mouseup
click
這三個事件搭配 依靠事件的順序特性來解決 , 他們順序是這樣 mouseDown
=> mouseUp
=> mouseClick
不過在此 mouseUp
用不到
先宣告一個 ref=pageItems
特別注意因為是 loop
蓋出的 element
所以這裡會是一個 array
接著當呼叫 mouseDown
時先用 event.preventDefault()
消除 focus
, 呼叫 gotoPage(page)
接著切換頁碼 然後呼叫 mouseClick
這時候取得 ref array
裡面的 active element
這時候拿到 li
粗暴的用 querySelector('a')
直接得到 a
並且 focus
如果把 let activeEle = pageItems.value.find(x => x.classList.contains('active'))
插在 mouseClick
的話 會拿到一開始點選的 active
元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 const pageItems = ref(null );function mouseDown (page ) { console .log('mouseDown' ) event.preventDefault() gotoPage(page) } function mouseUp (event ) { console .log('mouseUp' ) } const pageItems = ref(null );function mouseClick (event, page ) { let activeEle = pageItems.value.find(x => x.classList.contains('active' )) let tag = activeEle.querySelector('a' ) tag.focus() } function gotoPage (page ) { currentPage.value = page }
html 調整如下
1 2 3 4 5 6 7 8 9 10 <li class ="page-item" :class ="{active:isActive(page)}" @mousedown ="mouseDown(page)" @mouseup ="mouseUp($event)" @click ="mouseClick($event , page)" ref ="pageItems" v-for ="page in displayPageArray" > <a class ="page-link" href ="javascript:;" > {{ page }}</a > </li >
drag & drop 順便玩玩 drag & drop 功能 , 因為寫過 N 次了所以也不難 XD 這裡有個重點就是怎麼改變 drag & drop 的 index , 我的邏輯是可以交換兩個元素的位置 但是也有種把 drag 之後的元素往前推方法 , 可以參考這裡 就是用這種方法
html
1 2 3 4 5 6 7 8 9 10 <tr v-for="flower in displayFlowers" draggable="true" @dragstart="onDrag($event, flower)" @drop="onDrop($event, flower)" @dragover.prevent @dragenter.preven> <td>{{ flower.id }}</td> <td>{{ flower.name }}</td> <td>{{ flower.color }}</td> </tr>
js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 function onDrag(event, flower) { event.dataTransfer.dropEffect = 'move' event.dataTransfer.effectAllowed = 'move' event.dataTransfer.setData('id', flower.id) } // 交換式 // function onDrop(event, flower) { // let dragId = parseInt(event.dataTransfer.getData('id')) // let dropId = flower.id // let dragIndex = flowers.value.findIndex(x => x.id === dragId) // let dropIndex = flowers.value.findIndex(x => x.id === dropId) // let temp = flowers.value[dragIndex] // flowers.value[dragIndex] = flowers.value[dropIndex] // flowers.value[dropIndex] = temp // } //drag 之後的元素往前推 function onDrop(event, flower) { let dragId = parseInt(event.dataTransfer.getData('id')) let dropId = flower.id let dragFlower = flowers.value.find(x => x.id === dragId) let dragIndex = flowers.value.findIndex(x => x.id === dragId) let dropIndex = flowers.value.findIndex(x => x.id === dropId) flowers.value.splice(dragIndex, 1); flowers.value.splice(dropIndex, 0, dragFlower); }