项目需求:由于表单中的一个el-select中的数据过多,下拉框用户体验不好,因此需要做成有懒加载且分页的下拉框。后又因为该功能需要多选,并且打开表单要能够正确回显。这些需求加起来,导致问题变得复杂起来。我在网上也看了许多文章,没有找到好的解决方案。因此我将自己的记录下来,还望各位指点!!!
(本项目请求后端需要传id,page,size,name)(name的格式为JSON形式的[id:1,name:’’],[id:2,name:’’])
1、在需要使用懒加载的el-select加上我们的自定义指令(v-el-select-loadmore实现懒加载)
<el-select
ref="select"
v-model="form.allBreederId"
placeholder="请输入"
filterable
multiple
v-el-select-loadmore="loadmore" //自定义指令v-el-select-loadmore实现懒加载
:filter-method="brandKeyChange" //自定义搜索方法brandKeyChange()
style="width: 300px"
@focus="setMinWidth"
class="filter-item"
@change="changeAllBreeder">
<el-option
v-for="item in options"
:label="item.label"
:value="item.value"
:key="item.id"
</el-option>
</el-select>
2、自定义的懒加载指令loadmore
created() {},
directives: {
'el-select-loadmore': {
bind(el, binding) {
// 获取element-ui定义好的scroll盒子
const SELECTWRAP_DOM = el.querySelector('.el-select-dropdown .el-select-dropdown__wrap');
SELECTWRAP_DOM.addEventListener('scroll', function () {
/**
* scrollHeight 获取元素内容高度(只读)
* scrollTop 获取或者设置元素的偏移值,常用于, 计算滚动条的位置, 当一个元素的容器没有产生垂直方向的滚动条, 那它的scrollTop的值默认为0.
* clientHeight 读取元素的可见高度(只读)
* 如果元素滚动到底, 下面等式返回true, 没有则返回false:
* ele.scrollHeight - ele.scrollTop === ele.clientHeight;
*/
const condition = this.scrollHeight - this.scrollTop <= this.clientHeight;
if (condition && this.scrollTop > 0) {
binding.value();
}
});
}
},
},
3、其他相关数据及方法实现
data() {
return {
formData: {
page: 0,
size: 10
}, //由于分页的需求,这是提交时给后端传的数据。page:页数,size:每页大小
inputKey: '', //select模糊搜索输入框输入的数据
personPages: null, //总页数
changeAllBreeders: [], //点击下拉框的选中值
chosedItem: [],
step: 0 //区分该表单是在有编辑情况下点击的提交还是仅仅打开表单后点击的提交(0为后种情况)
}
},
methods: {
//下拉框目前的选中值
changeAllBreeder(params) {
this.step = 1 //说明该表单有变动
this.changeAllBreeders = params //获取目前的选中值
},
//自定义懒加载方法
loadmore() {
//鼠标滑动到底部时page+1,并判断如果滑到最底部page不能再继续++
this.formData.page++;
if (this.formData.page >= this.personPages) {
this.formData.page = this.formData.page - 1
}
//getList()为调用后端接口方法
this.getList(this.formData);
},
//getList()为调用后端接口方法
getList(formData) {
//判断select框里是否有模糊查询手动输入的值,有的话带上值一起传给后端
if (formData.inputKey == null || formData.inputKey == '') {
this.data = {
page: formData.page,
size: formData.size
}
} else {
this.data = {
page: formData.page,
size: formData.size,
name: formData.inputKey
}
}
const _this = this;
//getUnit()调用接口的方法,大家自己根据情况修改
getUnit(this.data).then(data => {
this.personPages = data.pages //总页数
let res = data.records //数据
//过滤数据
_this.chosedItem.forEach(chosed => {
res.find((e, index) => {
if (e && e.id == chosed.value) {
res.splice(index, 1);
}
})
})
res = JSON.parse(JSON.stringify(res).replaceAll('id', 'value').replaceAll('nickName', 'label'))
console.log(this.options, ' this.options');
//过滤数据
//将下拉框中为了正确回显设置的假数据与后端不断传回来的真数据作对比,如果重复,则删除真数据,保证key是唯一
_this.options.forEach(chosed => {
res.find((e, index) => {
if (e && e.value == chosed.value) {
res.splice(index, 1);
}
})
})
//当前下拉框加入过滤后的数据
this.options.push(...res)
});
},
// 自定义的搜索方法
brandKeyChange: function (inputKey) {
//inputKey为当前输入的数据
this.inputKey = inputKey
this.formData = {
page: 0,
size: 10,
inputKey: this.inputKey
},
this.getList(this.formData);
},
//当 input 获得焦点时触发(原因:为了刚打开表单时能够正确回显,所以做了一些假数据。但是真正点击select看到下拉框中的数据应该为真数据。所以需要在第一次点击select框的时候调一次接口,拿到数据)
setMinWidth(val) {
this.formData = {
page: 0,
size: 10
},
this.getList(this.formData);
},
// 编辑前([CRUD.HOOK.beforeToEdit]是本项目仿elementUI 封的一个生命周期方法,本方法用于打开表单前的操作)
[CRUD.HOOK.beforeToEdit](crud, form) {
// 将JSON形式的name数据处理成正常数组放在下拉框options中
this.chosedItem = JSON.parse(form.allBreederName)
this.chosedItem.forEach(item => {
item.value = item.id;
item.label = item.name;
delete item.id;
delete item.name;
})
this.options = [...this.chosedItem]
},
// 提交前做的操作
[CRUD.HOOK.beforeSubmit](crud, form) {
const AllBre = [] //最后处理好的id数组
const AllBreName = [] //最后处理好的JSON形式的name数组
//情况一:没有获取表单焦点直接又点击提交,此时要传给后端的下拉框的值仍为后端返回来的值。重新处理好交给后端就行
if (this.step === 0) {
this.chosedItem.filter(e => {
AllBre.push(e.value)
AllBreName.push({
id: this.options[i].value,
name: this.options[i].label
})
})
crud.form.allBreederId = AllBre
crud.form.allBreederName = JSON.stringify(AllBreName)
} else {
//情况二:有修改后点击的提交
this.changeAllBreeders.filter(e => {
AllBre.push(e)
})
for (let i = 0; i < this.options.length; i++) {
for (let j = 0; j < AllBre.length; j++) {
if (this.options[i].value == AllBre[j]) {
AllBreName.push({
id: this.options[i].value,
name: this.options[i].label
})
}
}
}
crud.form.allBreederId = AllBre //id
crud.form.allBreederName = JSON.stringify(AllBreName) //name
}
},
}
总结
该需求中的难点
一是后端传了一个id,另外给的id,name形式的值还是JSON形式,传和调都需要处理,就很繁琐。
二是在懒加载的情况下,要求多选回显。我看了很多文章,都没有很好的解决这个问题。最后我是绕了一圈,实现思路是先通过后端返回的所有数据做一个假的数组放在下拉框中,保证表单打开正确回显内容,而不是id号。接着在点击输入框的时候调用接口,获取正确数据,与假数据做比对,剔除重复数据后放在下拉框中。这样就能保证既正确回显未加载数据又能保证id不重复,安全进行后续的删除多选及提交操作!!!!
完美解决!