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