vue + element-ui的分頁問題
最近比較空閒,公司的後臺就想著把現在的後臺管理系統給改版一下,說是以前的太難看了,用著也不好用,然後給我甩過來一個ant-design-pro的連結,說是他看這個就挺不錯的。
我當時心裡就想著,之前的那個專案混合在你們的java專案裡,跟普通的jsp頁面差不多,一下就是一大堆的css和js檔案,看著我都害怕(好吧,我承認其實我都不敢看),這能載入的快了就奇了怪了。ant-design最初是為react設計的,ant-design-pro自然也是用react了,不得不說人家這個介面看著確實舒服。
對著ant-design-pro的官方文件看了一通,貌似看了跟沒看也差不多???算了,還是直接看程式碼吧,整理了一下思路,大致上是看懂了,除了react + react-router外,狀態管理用的是 dva, redux的非同步問題算是解決了,要不就開始直接寫頁面吧?
等等,我好像漏掉了點什麼?噢,對,先看看打包出來的檔案大小,一打包我的心就涼了,最大的js居然有900多k,ant-design的原始檔是真的大。react我還只是能寫出程式碼,打包優化這個可就有點為難我了。這時的我再想到公司那1m的頻寬,還有這幾個後臺的技術能力,要不然這個技術棧我還是放棄吧?不能指望連 請求頭, CORS稍微高階一點的攜帶cookie, nginx靜態伺服器 都搞不懂的人去給我弄個靜態伺服器,再順便開啟一下gzip吧?算了算了,找找有沒有vue + element-ui的後臺模板,不用太費勁就找到了 ofollow,noindex">vue-element-admin 。
vue-element-admin用著還行,就是介面不太符合我的理想情況,就對著ant-design-pro改造了一點,列表頁大概就是下面這樣了。列表的資料是要分頁的,普通的列表頁只有一個頁面棧,也就是使用者點選位址列的回退位址列時,會返回上一個頁面棧,而不是上一頁的資料,不太符合使用者習慣吧?畢竟傳統的網站都是可以回退到上一頁的,嗯,話不多說,進入正題吧。

第一步:改變位址列
假設列表頁的路徑是 /user/list,分頁相關的引數為 { page: 1, pagesize: 10 }
,從其他頁面跳轉過來的時候,我們的路徑通常是不包含任何引數的,之後的列表資料都是根據該頁面的page和pagesize進行變化的,當未使用keep-alive快取元件時,每次進入列表頁都相當於第一次進入,也就是說每次都只能獲取第一頁的資料。
既然列表資料是用page和pagesize進行變化的,那直接從位址列獲取page和pagesize進行賦值不就好了?那麼是改變位址列的程式碼是直接寫在當前頁面還是 獨立為分頁元件 呢?從複用性方面來說,還是獨立出來的好,畢竟其他頁面可能也會使用到,總不能每次都複製貼上吧,那元件化的意義何在?當然了,也不是說分頁就必須用這個自定義的分頁元件,只推薦在 主頁面(非遮罩層 ,有的頁面會在點選某一行資料時出現遮罩層顯示子列表,此時使用element-ui的分頁元件即可)需要分頁時使用。
當改變位址列的時候,我們是不希望不帶分頁引數的頁面棧存在的,此時用replace直接替換即可。
MyPagination.vue的初始結構為:
<template> <div class = " flex all-center"> <template v-if="total > 0"> <el-pagination :page-size="pagesize" :total="total" :current-page="page" background layout="prev, pager, next, jumper, total" class="my-pagination" @current-change="changePage" /> </template> </div> </template> <script> export default { name: 'MyPagination', props: { total: { type: Number, default: 0, }, page: { type: Number, default: 1, }, pagesize: { type: Number, default: 10, }, totalPages: { type: Number, default: 1, }, }, created() { this.getCurrentPage(); }, methods: { changePage(val) { this.handlePage('push', val, this.pagesize); this.$emit('change', val, this.pagesize); }, getCurrentPage() { var { page, pagesize } = this.$route.query; if (!page || !pagesize) { this.handlePage('replace', page || 1, +pagesize || this.pagesize); return true; } return false; }, handlePage(type, page, pagesize) { this.$router[type]({ path: this.$route.path, query: { ...this.$route.query, page, pagesize }, }); } }, } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .my-pagination { padding-top: 24px; } </style> 複製程式碼
父元件的關鍵程式碼:
<MyPagination :total = "total" :pagesize = "pagesize" :page="page" :totalPages = "totalPages" @change = "changePage" /> methods: { changePage(page, pagesize) { var _page = this.page, _pagesize = this.pagesize; this.page = page; this.pagesize = pagesize; if (page !== _page && pagesize || _pagesize !== pagesize) this.fetchData(); // 非首次進入頁面時再獲取分頁資料,因為在created鉤子中已經獲取過一次了。 }, } 複製程式碼
實現效果: 首次進入該頁面時,如果不含有分頁引數,就會先改變分頁引數,然後再獲取資料,之後點選分頁元件的頁碼也會獲取分頁之後的資料。
第二步: 觀察路由變化
上一步的實現效果乍一看好像沒什麼不對勁的地方,但是如果直接改變位址列的話,顯示的當前頁和當前資料都不會變化。前端路由在頁面的查詢引數(指的是 router的查詢引數 ,可不是普通頁面的查詢引數)變化時,預設是不會重新載入的,除非頁面的key發生變化,這樣是為了儘可能的防止頁面重新渲染,所以就不用key的方式解決了,直接通過vue的watch檢測 $route 的變化,從而改變當前頁和當前資料的顯示問題。
在MyPagination.vue中新增:
watch: { '$route'(to, from) { let { page, pagesize } = to.query; if (!this.getCurrentPage()) { this.$emit('change', +page || 1, +pagesize || 10); } } }, 複製程式碼
第三步: 控制pagesize的大小
在上一步的效果中,當改變位址列的page和pagesize時,列表頁的資料也會隨之變化。既然是根據地址欄的引數變化,那麼新的問題就產生了,
如果使用者輸入的page大於頁面總數呢?
這個時候主要就看後臺怎麼設計了,
- 返回第一頁的資料。
getCurrentPage() { var { page, pagesize } = this.$route.query; /* (totalPages > 0 && (page > totalPages));滿足總頁數大於0且當前頁大於總頁數時,跳轉到第一頁 */ if (!page || !pagesize || (totalPages > 0 && (page > totalPages))) { this.handlePage('replace', page || 1, this.pagesize); return true; } return false; }, 複製程式碼
- 返回最後一頁的資料(我覺得這種操作應該是比較合理的)。
getCurrentPage() { var { page, pagesize } = this.$route.query, MAX_PAGESIZE = this.max, totalPages = this.totalPages; if (!page || !pagesize) { this.handlePage('replace', page || 1, +pagesize || this.pagesize); return true; } else if (totalPages > 0 && (page > totalPages)) { this.handlePage('replace', totalPages, +pagesize); return true; } return false; }, 複製程式碼
替換當前頁面棧,return true的作用是阻止watch中的後續操作,取消本次請求。替換頁面以後,請求遠端資料,更新當前頁和資料的顯示。
- 返回空陣列(可能大多數後臺都是這麼設計的,他們應該沒想過page會大於總頁數吧)。 程式碼與2中的一樣。
上文都是建立在totalPages已確定的情況,如果是首次進入頁面的話情況就會不一樣了。
如果是首次進入頁面的話,totalPages第一次是0,也就是位址列的引數將不會發生變化,這時候就會出現位址列和分頁元件的顯示不一致的情況。這時候可以在分頁元件中watch totalPages的變化。
totalPages(newVal, oldVal) { if (+oldVal === 0 && newVal > 0) { this.handlePage('replace', this.page, +this.pagesize); } } 複製程式碼
如果pagesize過大呢?
pagesize是必須要進行限制的,如果太大的話,後臺查詢資料就會非常慢,也可能會造成壓力。 解決辦法其實也簡單,就是在props增加一個max屬性,然後在getCurrentPage方法中進行限制,程式碼如下:
props: { max: { type: Number, default: 20, }, }, methods: { getCurrentPage() { var { page, pagesize } = this.$route.query, MAX_PAGESIZE = this.max, totalPages = this.totalPages; if (!page || !pagesize) { this.handlePage('replace', page || 1, +pagesize || this.pagesize); return true; } else if (pagesize > MAX_PAGESIZE) { this.handlePage('replace', page, MAX_PAGESIZE); return true; } else if (totalPages > 0 && (page > totalPages)) { this.handlePage('replace', totalPages, +pagesize); return true; } return false; }, }, 複製程式碼
第四步: 優化程式碼
點選分頁元件的頁碼時產生兩次請求
點選分頁元件時,1. 會監聽current-change事件並改變位址列,同時emit change事件至父元件,2. 但是位址列改變後,在watch $route也會emit change事件至父元件,那麼只需要合併emit change事件,即current-change事件中只改變位址列。
changePage(val) { this.handlePage('push', val, this.pagesize); }, 複製程式碼