由于后端接口返回的数据过多,浏览器渲染过慢导致了一个bug
场景及问题描述
分别有开户网点和银行名称两个可搜索的下拉框,当用户选择开户网点后系统会根据选择的开户网点把银行名称回显出来,例如开户网点为中信银行股份有限公司唐山新华东道支行,银行名称自动选择为中信银行。
由于有这个需求,银行名称这个接口需要把所有的银行都查询出来,否则可能会导致出现银行名称查找不到的情况,这也就导致该接口数据量过大,会有成上万条数据。
在浏览器的体现就是点击银行列表名称会有2秒左右的等待时间。
优化方案1(简单):
获取到大量数据后直接截取一小部分展示到页面上,其余的数据由搜索框查询。
优点:简单,工作量小
缺点:万一有用户闲的一个一个找到最底部,可能会找不到。(不会有这么闲的人吧 (ˉ▽ˉ;)... )
下面是示意代码
<template>
<el-select
v-model.trim="form.bankId"
filterable
:filter-method="searchBanks"
style="width: 100%"
:placeholder="`请选择${handleLabelField('bank')}`"
@change="handleChangebankId"
>
<el-option
v-for="item in banks"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
</template>
<script>
export default {
async create() {
const r = await getListBank();
// 保存初始银行列表 防止数据越查越少
this.originBanks = r.data;
this.banks = this.originBanks.slice(0, 200);
},
methods: {
searchBanks(val) {
this.banks = this.originBanks
.filter((bank) => bank.label.indexOf(val) !== -1)
.slice(0, 200);
},
handleChangebankId(val) {
this.form.bankId = val.id;
},
},
};
</script>
优化方案2:
初始展示10条,动态计算用户是否滚动到底部。到底部的时候从data里面再取10条,拼接到列表最后。一次性把数据存到data,由前端实现分页效果。
优点:丝滑体验好,再也不怕很闲的用户了!!!
缺点:工作量大,改起来麻烦 /(ㄒoㄒ)/~~
<template>
<el-select
v-model.trim="form.bankId"
filterable
:filter-method="searchBanks"
v-select-load-more="loadMoreBank"
style="width: 100%"
:placeholder="`请选择${handleLabelField('bank')}`"
@change="handleChangebankId"
>
<el-option
v-for="item in banks"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
</template>
<script>
export default {
async create() {
const r = await getListBank();
// 保存初始银行列表 防止数据越查越少
this.originBanks = r.data;
this.banks = this.originBanks.slice(0, 200);
},
methods: {
searchBanks(val) {
this.bankSearchInput = val;
this.page = 1;
this.loadBank();
},
loadMoreBank() {
this.page++;
this.loadBank();
},
loadBank() {
this.banks = this.originBanks
.filter((bank) => bank.label.indexOf(this.bankSearchInput) !== -1)
.slice(0, 10 * this.page);
},
handleChangebankId(val) {
this.form.bankId = val.id;
},
},
directives: {
"select-load-more": {
// 指令钩子函数会被传入以下参数:
// el:指令所绑定的元素,可以用来直接操作 DOM。
// binding:一个对象,包含以下 property:
// name:指令名,不包括 v- 前缀。
// value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。
// oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
// expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。
// arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。
// modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
// vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
// oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
inserted: function (el, binding) {
// 滚动的容器
const element = el.querySelector(".el-scrollbar__wrap");
element?.addEventListener("scroll", function () {
//说明到底了
if (this.scrollTop + this.offsetHeight >= this.scrollHeight) {
binding.value();
}
});
},
},
},
};
</script>
对scrollHeight offsetHeight等属性陌生的小伙伴可以看下
js中offset、client、scroll距离属性以及获取鼠标坐标总结
啥?你说你不想为了这个写一个自定义指令,那你试试element的无限滚动。
只需要修改一下template的内容即可,可以达到和上面的自定义指令一样的效果。
<template>
<el-select
v-model.trim="form.bankId"
filterable
:filter-method="searchBanks"
style="width: 100%"
:placeholder="`请选择${handleLabelField('bank')}`"
@change="handleChangebankId"
>
<div v-infinite-scroll="loadMoreBank">
<el-option
v-for="item in banks"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</div>
</el-select>
</template>
<script>
export default {
async create() {
const r = await getListBank();
// 保存初始银行列表 防止数据越查越少
this.originBanks = r.data;
this.banks = this.originBanks.slice(0, 200);
},
methods: {
searchBanks(val) {
this.bankSearchInput = val;
this.page = 1;
this.loadBank();
},
loadMoreBank() {
this.page++;
this.loadBank();
},
loadBank() {
this.banks = this.originBanks
.filter((bank) => bank.label.indexOf(this.bankSearchInput) !== -1)
.slice(0, 10 * this.page);
},
handleChangebankId(val) {
this.form.bankId = val.id;
},
},
};
</script>
暂时就介绍这两种方法,如果没有特殊要求推荐使用方案一。如果想要更好的用户体验可以选择方案二。
甚至可以直接采用搜索框的方式,只允许用户输入再选择,默认不展示任何内容。我们产品估计不让。。。
如有错误或者更好的方案欢迎留言!
拜拜ヾ(•ω•`)o