Vue二次封裝el-select實(shí)現(xiàn)下拉滾動(dòng)加載效果(el-select無(wú)限滾動(dòng))
前言
平時(shí)我們做業(yè)務(wù)需求的時(shí)候,可能會(huì)遇到非常大量的數(shù)據(jù),有時(shí)候成百上千條,一般后端都會(huì)寫一個(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é)果很少,以至于沒看到滾動(dòng)條,那么此時(shí)的CONDITION計(jì)算結(jié)果是true,會(huì)執(zhí)行bind.value(),此時(shí)不應(yīng)該執(zhí)行,否則搜索結(jié)果不匹配
if (CONDITION && this.scrollTop !== 0) {
binding.value();
}
});
},
});
代碼說明:
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:讀取元素的可見高度(只讀)。
如果元素滾動(dòng)到底,下面等式返回true,沒有則返回false。
// scrollTop 這里可能因?yàn)闉g覽器縮放不等于100%時(shí),存在小數(shù)點(diǎn)的情況,導(dǎo)致了滾動(dòng)到底部時(shí)
// scrollHeight 減去滾動(dòng)到底部時(shí)的scrollTop ,依然大于clientHeight 導(dǎo)致沒有觸發(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 })
},
// 選中下拉框沒有數(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)
// 訪問后端接口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è)總共的數(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í)際使用的過程中,可能會(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è)問題?可以在加載函數(shù)中加入一個(gè)攔截操作,在接口沒有響應(yīng)之前,不調(diào)用加載函數(shù),不過這樣做要把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 => {
// 訪問后端接口API
this.requestAPI({ page }).then(res => {
this.data = [...this.data, ...res.result]
this.page = res.page
resolve()
});
})
},
}
</script>
現(xià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() {
// 如果沒有更多數(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
});
},
// 選中下拉框沒有數(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 => {
// 訪問后端接口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
// 如果為最后一頁(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)求與組件剝離開來(lái),易用程度不算太高,不過我們可以適當(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-04
Vue數(shù)據(jù)更新但頁(yè)面沒有更新的多種情況問題及解決
這篇文章主要介紹了Vue數(shù)據(jù)更新但頁(yè)面沒有更新的多種情況問題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07
vue2.0實(shí)現(xiàn)檢測(cè)無(wú)用的代碼并刪除
這篇文章主要介紹了vue2.0實(shí)現(xiàn)檢測(cè)無(wú)用的代碼并刪除方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07

