Vue二次封裝el-select實現(xiàn)下拉滾動加載效果(el-select無限滾動)
前言
平時我們做業(yè)務需求的時候,可能會遇到非常大量的數(shù)據(jù),有時候成百上千條,一般后端都會寫一個分頁的接口,只要我們請求的時候加上頁碼參數(shù)即可。
但是在使用element-ui的el-select下拉菜單組件中,官方?jīng)]有提供相應的方法進行多頁加載。
這時候我們可以實現(xiàn)一個Vue的自定義指令,每當使用el-select滾動到列表底部的時候就請求下一頁數(shù)據(jù),來達到下拉滾動加載更多的目的。
實現(xiàn)自定義指令
首先實現(xiàn)一個el-select下拉加載的自定義指令v-loadmore:
// directives.js import Vue from 'vue' Vue.directive("loadmore", { bind(el, binding, vnode) { const SELECTWRAP = el.querySelector( ".el-select-dropdown .el-select-dropdown__wrap" ); SELECTWRAP.addEventListener("scroll", function () { // scrollTop 這里可能因為瀏覽器縮放存在小數(shù)點的情況,導致了滾動到底部時 // scrollHeight 減去滾動到底部時的scrollTop ,依然大于clientHeight 導致無法請求更多數(shù)據(jù) // 這里將scrollTop向上取整 保證滾到底部時,觸發(fā)調(diào)用 const CONDITION = this.scrollHeight - Math.ceil(this.scrollTop) <= this.clientHeight; // el.scrollTop !== 0 當輸入時,如果搜索結果很少,以至于沒看到滾動條,那么此時的CONDITION計算結果是true,會執(zhí)行bind.value(),此時不應該執(zhí)行,否則搜索結果不匹配 if (CONDITION && this.scrollTop !== 0) { binding.value(); } }); }, });
代碼說明:
document.querySelector:querySelector()
方法僅僅返回匹配指定選擇器的第一個元素。
Element.scrollHeight:
在不使用滾動條的情況下為了適應視口中所用內(nèi)容所需的最小高度(只讀)
警告: 在使用顯示比例縮放的系統(tǒng)上,scrollTop可能會提供一個小數(shù)。
Element.scrollTop:
獲取或設置一個元素的內(nèi)容垂直滾動的像素數(shù)。
Element.clientHeight:
讀取元素的可見高度(只讀)。
如果元素滾動到底,下面等式返回true,沒有則返回false。
// scrollTop 這里可能因為瀏覽器縮放不等于100%時,存在小數(shù)點的情況,導致了滾動到底部時
// scrollHeight 減去滾動到底部時的scrollTop ,依然大于clientHeight 導致沒有觸發(fā)加載事件
// 這里將scrollTop向上取整 保證滾到底部時,觸發(fā)調(diào)用
// 此判斷不準確: element.scrollHeight - element.scrollTop === element.clientHeight
// 使用下面的判斷方式保證
任何縮放都能觸發(fā):
element.scrollHeight - Math.ceil(element.scrollTop) <= element.clientHeight
在項目中全局注冊v-loadmore指令:
// main.js
import directives from './directive.js' Vue.use(directives)
最后在組件el-select中使用該指令:
<template> <el-select v-model="selected" v-loadmore="loadMore"> <el-option v-for="option in options" :label="option.label" :value="option.value" :key="option.value" ></el-option> </el-select> </template> <script> export default { data() { return { selected: "", options: [ { label: "1", value: 1 }, // ... 此處省略多個選項 { label: "到達底部啦", value: 9 } ] }; }, methods: { loadMore() { console.log("more") } } };
使用效果如下:
從效果圖可以看出,每當菜單列表滾動到底部時,指令就會調(diào)用傳入的loadMore函數(shù),控制臺隨即打印出 “more”。
注意事項:
傳入的數(shù)組個數(shù)必須大于或者等于8個選項時才能讓el-select組件出現(xiàn)下拉滾動。
列表里不存在滾動時,無法觸發(fā)傳入指令的函數(shù)。
進行二次封裝
滾動到底部調(diào)用函數(shù)的指令已經(jīng)實現(xiàn)了,下面只要調(diào)用接口,把獲取到下一頁的數(shù)據(jù)拼接到當前的數(shù)據(jù)中即可。
接下來把el-select進行二次封裝,封裝成公用的組件之后,傳入必要的參數(shù)就可以在項目中調(diào)用。
首先新建一個文件load-select.vue:
<template> <el-select :value="value" v-loadmore="loadMore" @focus="focus" v-bind="$attrs" v-on="$listeners"> <el-option v-for="option in data" :label="option[dictLabel]" :value="option[dictValue]" :key="option.value" ></el-option> </el-select> </template> <script> export default { props: { value: { type: String, default: "" }, // 列表數(shù)據(jù) data: { type: Array, default: () => [] }, dictLabel: { type: String, default: "label" }, dictValue: { type: String, default: "value" }, // 調(diào)用頁數(shù)的接口 request: { type: Function, default: () => {} }, page: { type: [Number, String], default: 1 } }, data() { return {}; }, methods: { // 請求下一頁的數(shù)據(jù) loadMore() { this.request({ page: this.page + 1 }) }, // 選中下拉框沒有數(shù)據(jù)時,自動請求第一頁的數(shù)據(jù) focus() { if (!this.data.length) { this.request({page: 1}) } } } }; </script>
在頁面組件中調(diào)用load-select.vue:
<!-- page.vue --> <template> <div class="xxx-page"> <load-select v-model="selected" :data="data" :page="page" :request="getData"></load-select> </div> </template> <script> // 導入該組件 import loadSelect from "@/components/load-select/index"; export default { name: "app", components: { loadSelect }, data() { return { selected: "", page: 1, more: true, data: [] }; }, methods: { // 傳入給load-select組件的函數(shù) getData({ page = 1 } = {}) { // 輸出頁數(shù) console.log(page) // 訪問后端接口API this.requestAPI({ page }).then(res => { this.data = [...this.data, ...res.result] this.page = res.page }); }, // 模擬后端接口的API requestAPI({ page = 1, size = 10 } = {}) { return new Promise(resolve => { let responseData = [] // 假設總共的數(shù)據(jù)有50條 let total = 50; for (let index = 1; index <= size; index++) { // serial:處于第幾個元素,就顯示多少序號 let serial = index + (page - 1) * size if (serial <= 50) { responseData.push({ label: serial, value: serial }); } } // 模擬異步請求,500ms之后返回接口的數(shù)據(jù) setTimeout(() => { resolve({ total, page, size, result: responseData }); }, 500); }); } } }; </script>
代碼解析:
首次點擊下拉框時,會觸發(fā)focus事件請求第一頁的數(shù)據(jù),之后只要每次滾動列表到底部,就會自動請求下一頁的數(shù)據(jù)然后拼接到當前的數(shù)組中。
我們來看看效果:
完美!但是在實際使用的過程中,可能會因為接口還來不及返回數(shù)據(jù),然后列表又向下滾動再次觸發(fā)了請求,結果就是返回了兩份相同的數(shù)據(jù)。
現(xiàn)在把接口的延遲調(diào)到2000ms重現(xiàn)這個場景:
在兩次快速滾動到底部的時候,請求的參數(shù)頁數(shù)都是2,如何解決這個問題?可以在加載函數(shù)中加入一個攔截操作,在接口沒有響應之前,不調(diào)用加載函數(shù),不過這樣做要把getData轉(zhuǎn)換成異步函數(shù)的形式。
首先在load-select.vue中的loadMore()中加入一個攔截操作:
<!-- load-select.vue --> <template> ... </template> <script> // 請求下一頁的數(shù)據(jù) methods: { loadMore() { // 如果 intercept 屬性為 true 則不請求數(shù)據(jù) if (this.loadMore.intercept) { return } this.loadMore.intercept = true this.request({ page: this.page + 1 }).then(() => { // 接口響應之后才把 intercept 設置為 false this.loadMore.intercept = false }) } } </script>
然后在page.vue中的getData()函數(shù)轉(zhuǎn)換成異步函數(shù)的形式:
<template> ... </template> <script> methods: { // 傳入給load-select組件的函數(shù) getData({ page = 1 } = {}) { // 返回 Promise 對象 return new Promise( resolve => { // 訪問后端接口API this.requestAPI({ page }).then(res => { this.data = [...this.data, ...res.result] this.page = res.page resolve() }); }) }, } </script>
現(xiàn)在問題來了:
一般分頁的接口都支持關鍵字的搜索,load-select.vue組件能不能加入關鍵字搜索的功能呢?
關鍵字搜索功能
還好el-select組件支持遠程搜索功能,只要傳入filterable和remote參數(shù),具體的可以查看element-ui的官方文檔。
接下來對load-select.vue進行以下修改:
<!-- load-select.vue --> <template> <el-select :value="value" v-loadmore="loadMore" @focus="focus" filterable remote :filter-method="handleSearch" :loading="loading" clearable v-bind="$attrs" v-on="$listeners" > <el-option v-for="option in data" :label="option[dictLabel]" :value="option[dictValue]" :key="option.value" ></el-option> <!-- 此處加載中的value可以隨便設置,只要不與其他數(shù)據(jù)重復即可 --> <el-option v-if="hasMore" disabled label="加載中..." value="-1"></el-option> </el-select> </template> <script> export default { props: { value: { default: "" }, // 列表數(shù)據(jù) data: { type: Array, default: () => [] }, dictLabel: { type: String, default: "label" }, dictValue: { type: String, default: "value" }, // 調(diào)用頁數(shù)的接口 request: { type: Function, default: () => {} }, // 傳入的頁碼 page: { type: [Number, String], default: 1 }, // 是否還有更多數(shù)據(jù) hasMore: { type: Boolean, default: true } }, data() { return { // 存儲關鍵字用 keyword: "", loading: false }; }, methods: { // 請求下一頁的數(shù)據(jù) loadMore() { // 如果沒有更多數(shù)據(jù),則不請求 if (!this.hasMore) { return } // 如果intercept屬性為true則不請求數(shù)據(jù), if (this.loadMore.intercept) { return } this.loadMore.intercept = true; this.request({ page: this.page + 1, more: true, keyword: this.keyword }).then(() => { this.loadMore.intercept = false }); }, // 選中下拉框沒有數(shù)據(jù)時,自動請求第一頁的數(shù)據(jù) focus() { if (!this.data.length) { this.request({ page: 1 }) } }, // 關鍵字搜索 handleSearch(keyword) { this.keyword = keyword this.loading = true this.request({ page: 1, keyword }).then(() => { this.loading = false }); }, // 刪除選中時,如果請求了關鍵字,則清除關鍵字再請求第一頁的數(shù)據(jù) clear() { if (this.keyword) { this.keyword = "" this.request({ page: 1 }) } } } }; </script>
頁面調(diào)用時,getData()請求函數(shù)需要接收keyword和more參數(shù)并進行相應的處理:
<!-- page.vue --> <template> <div class="xxx-page"> <load-select v-model="selected" :data="data" :page="page" :hasMore="more" :request="getData"></load-select> </div> </template> <script> // 導入該組件 import loadSelect from "@/components/load-select/index"; export default { name: "app", components: { loadSelect }, data() { return { selected: "", page: 1, more: true, data: [] }; }, methods: { // 傳入給load-select組件的函數(shù) getData({ page = 1, more = false, keyword = "" } = {}) { return new Promise(resolve => { // 訪問后端接口API this.requestAPI({ page, keyword }).then(res => { // 如果是加載更多,則合并之前的數(shù)據(jù) if (more) { this.data = [...this.data, ...res.result] } else { this.data = res.result } this.page = res.page; let { total, page, size } = res // 如果為最后一頁,則設置more為false this.more = page * size < total this.page = page resolve() }); }); }, // 模擬后端接口的API requestAPI({ page = 1, size = 10, keyword = "" } = {}) { return new Promise(resolve => { // 如果有 keyword 參數(shù),則返回帶有 keyword 的數(shù)據(jù) if (keyword) { setTimeout(() => { resolve({ total: 3, page: 1, size: 10, result: [ { label: keyword, value: 1 }, { label: keyword + 1, value: 2 }, { label: keyword + 2, value: 3 } ] }) }, 500) return } let responseData = []; // 假設總共的數(shù)據(jù)有50條 let total = 50; for (let index = 1; index <= size; index++) { // serial:處于第幾個元素,就顯示多少序號 let serial = index + (page - 1) * size if (serial <= 50) { responseData.push({ label: serial, value: serial }); } } setTimeout(() => { resolve({ total, page, size, result: responseData }) }, 500) }) } } }; </script>
接下來看看搜索關鍵字的效果:
搜索功能也完成啦!
總結
為了適用于大部分的請求接口,因此在設計這個組件的時候只能把請求與組件剝離開來,易用程度不算太高,不過我們可以適當?shù)貍魅胍恍┖唵伪匾膮?shù)去維持基本地使用。
當然,在項目中遇到某些固定的加載請求時,我們也可以對該組件進行再次封裝,具體可以根據(jù)自身的業(yè)務需求進行修改。
到此這篇關于Vue二次封裝el-select實現(xiàn)下拉滾動加載效果(el-select無限滾動)的文章就介紹到這了,更多相關Vue el-select無限滾動內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Vue.js條件渲染和列表渲染以及Vue中key值的內(nèi)部原理
這篇文章主要介紹了Vue.js條件渲染和列表渲染,以及Vue中key值的內(nèi)部原理,文中有詳細的代碼示例,感興趣的同學可以參考閱讀2023-04-04Vue數(shù)據(jù)更新但頁面沒有更新的多種情況問題及解決
這篇文章主要介紹了Vue數(shù)據(jù)更新但頁面沒有更新的多種情況問題及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07