【Vue】el-select 数据过多替代方案
一、需求问题:
一开始就考虑使用简单el-select选取数据,但是后面数据量增多,
超过一千条开始,组件会很卡不好用,第二个是接口也慢了
数据量多的话是有一个filterable做支持了,可以输入关键字进行匹配检索
但是不能解决卡顿的问题,接口还是比较慢
二、替代方案:
然后改用了el-autocomplete组件,自动补全,在官网上是没有这个文档说明的
后来发现在el-input文档里面,el-input没有把这个组件拆出来说明。。。。
代码示例:
<el-form-item label="往来对象" prop="biRoObj" :style="commonStyle"> <el-autocomplete v-model.trim="form.biRoObjName" :fetch-suggestions="fetchArList" style="width: 100%;" :popper-append-to-body="false" :trigger-on-focus="false" placeholder="请先选择往来对象类型" @select="handleArSelect($event)" /> </el-form-item>
重要的几个参数:
v-model 等同el-input的一样就是输入值
fetch-suggestions 传递一个方法
@select 在选中选项时触发该事件, 传递一个事件方法
TriggerOnFocus 是否聚焦输入时触发搜索,建议关闭,默认输入完成时触发
FetchSuggestion用法:
/** * fetchSuggestion * 字面意思就是拉取建议选项,在输入关键字时触发该方法 * @param queryString 关键字,等同v-model值 * @param callBack 回调方法, 入参建议集合 [{ id: xx, value: xxx }] */ async fetchArList(queryString, callBack) { /* 1、没有关键字时,返回空建议 */ if (!queryString) return callBack([{ id: '', value: '无结果' }]) /* 2、调用接口获取建议集合 */ await { data: arList } = await getArList({}) /* 3、如果没有建议,同理 */ if (arList.length === 0) return callBack([{ id: '', value: '无结果' }]) /* 4、做转换适配组件的属性要求,id和value */ const mapList = arList.map(x => { return { ... x, id: x.id, value: x.name } }) return callBack(mapList) },
这里我是用promise的ES8语法同步化了一下
为了简洁语法就直接early return回调函数了
实际上是这样:
fetchArList(queryString, callBack) { if (!queryString) { callBack([{ id: '', value: '无结果' }]) // todo ... } else { getArList({}).then(res => { const arList = res.data if (arList.length === 0) { callBack([{ id: '', value: '无结果' }]) } else { const mapList = arList.map(x => { return { ... x, id: x.id, value: x.name } }) callBack(mapList) } }).catch(err => { console.error(err) // todo error ... }) } }
后台接口(Java):
这里有一个限制值,考虑到有可能用户输入的关键字还是会检索很多记录
就限制记录结果,迫使用户找不到后,提供更多关键字进行查找
这里我是按参数设置,如果不提供参数,默认50个以内
/** * @author cloud9 * @date 2023/6/30 11:48 * @description 下拉集合检索用 * @params [dto] * @return java.util.List */ @PostMapping("/list/forFetch") public List<SysArEmployeeDTO> getSysArCustomerListForFetch(@RequestBody SysArEmployeeDTO dto) { String lastLimit = "LIMIT " + (Objects.nonNull(dto.getPage()) ? dto.getPage().getSize() : 50); return sysArEmployeeService .lambdaQuery() .select(SysArEmployeeDTO::getId, SysArEmployeeDTO::getEmCode, SysArEmployeeDTO::getEmName) .and(wq -> wq.like(StringUtils.isNotBlank(dto.getEmName()), SysArEmployeeDTO::getEmCode, dto.getEmName()) .or().like(StringUtils.isNotBlank(dto.getEmName()), SysArEmployeeDTO::getEmName, dto.getEmName())) .orderByDesc(SysArEmployeeDTO::getEmCode) .last(lastLimit) .list(); }
@select事件的处理:
1、事件方法会传递一个事件对象入参,该对象就是建议集合的元素,你放了什么属性,这个对象就能拿什么属性
2、除了赋值输入值以外,因为传递到后台存的是id值,所以需要把id赋值好,form校验的是id,所以两者的处理要同步
3、可以使用直接赋值,但是我没试过,可能是考虑组件有可能不监听数据变化,就用$set赋值刷新组件
4、这个事件等同el-select的@change事件,所以原来的业务逻辑@change有回调处理的时候,可以完全平滑迁移过来
handleArSelect(event) { this.$set(this.form, 'biRoObj', event.id) this.$set(this.form, 'biRoObjName', event.value) }
数据回显问题:
回显时保持和输入值一致,查询的时候带上这个值返回到页面就可以了
这个组件和el-select一样,在定义的form表单对象中,一定要写默认值
不然就会无法输入,不能选择(踩过的坑)
三、校验补充:
因为不是像el-select那种change事件,它是自己输入的
在写完上面的例子后可以发现,我们清空了输入值,是不会触发表单校验的
因为id值没有被清空,所以我们需要通过使用watch监听变化来更新id值:
watch: { 'form.biRoObjName': { immediate: true, handler() { if (!this.form.biRoObjName) this.form.biRoObj = '' } } }
这里不关心新值和旧值的对比处理,就不接参了(oldVal, newVal)
判断label值是否存在,不存在时同步更新id值,这样就能触发表单校验了
四、下拉样式调整:
这个下拉框的宽度是默认跟随input组件宽度的
在特殊情况下,建议选项的展示文本远远超出了组件宽度,默认下是省略的
所以需要强制调整宽度,展示更多文本内容
/* el-autocomplete组件 下拉框 设置 */ /deep/ .el-popper[x-placement^=bottom]{ width:400px !important; text-align: left; }
五、复杂业务的情况:
上面的情况是表单的单个属性的一个问题,在这里要处理的是一个集合的情况
<el-table-column prop="adTypeName" min-width="130px" align="center" label="归属项目" class-name="full-width"> <template slot-scope="sc"> <el-form-item :prop=" `tableData.${sc.$index}.adType`" :rules="rules.adType" :style="commonStyle"> <el-autocomplete v-model.trim="sc.row.prName" :disabled="projectLockFlag || !!sc.row.adServIdent" :fetch-suggestions="fetchPrList" style="width: 100%;" :popper-append-to-body="false" :trigger-on-focus="false" placeholder="输入归属项目" @select="handlePrSelect($event, sc.$index, sc.row)" /> </el-form-item> </template> </el-table-column>
首先是@Select事件的接参问题:
因为要处理集合的任意元素,方法需要知道元素,下标值,以及传递的值
1、可以用$event对象表示
@select="handlePrSelect($event, sc.$index, sc.row)"
2、使用箭头函数传递:
@select="(event) => handlePrSelect(event, sc.$index, sc.row)"
赋值时:
handlePrSelect(event, idx, row) { this.$set(this.form.tableData[idx], 'adType', event.id) this.$set(this.form.tableData[idx], 'prName', event.inName) }
集合校验的问题:
因为集合不能监听每个元素,只能监听集合本身
注意这里要声明 deep: true,才会监听元素内部变化
不声明时,watch只关注集合的长度变化
watch: { 'form.tableData': { deep: true, immediate: true, handler() { this.form.tableData.forEach((el, idx, arr) => { if (!el.prName) this.$set(arr[idx], 'adType', '') }) } } },
六、Clearable不失焦的BUG:
参考方案:
https://zhuanlan.zhihu.com/p/395688018
每个autoComplete基本上都需要这么做
所以我干脆注册到原型对象里面,方便全局调用
import Vue from 'vue' /** * 修复el-autocomplete(clearable)清空后不能触发检索的问题 * 触发后手动调用失焦 */ Vue.prototype.$clearForElAutoComplete = () => { document.activeElement.blur() }
在组件设置方法:
<el-col :span="8"> <el-form-item label="往来对象" prop="apReObj" :style="commonStyle"> <el-autocomplete v-model.trim="form.apReObjName" clearable :fetch-suggestions="fetchArList" style="width: 100%;" :popper-append-to-body="false" :trigger-on-focus="true" placeholder="请先选择往来对象类型" @clear="$clearForElAutoComplete" @select="handleArSelect($event)" /> </el-form-item> </el-col>
2023年11月27日 21:34更新
功能思路:
最近开发的项目,用户反馈,检索的内容过多,可以拆分关键字,按多个关键字更精准定位内容
我想到一定有个分隔符,分割开每个关键词, 可以是空格、逗号、中文逗号
可以先替换成统一符号后转换成集合传给后台转换
SQL语句:
MySQL多关键字查询语句:
select * from user_info where address regexp '花园路|幸福路|安康路';