【Vue】el-select 数据过多替代方案

发布时间 2023-07-05 00:30:56作者: emdzz

一、需求问题:

一开始就考虑使用简单el-select选取数据,但是后面数据量增多,

超过一千条开始,组件会很卡不好用,第二个是接口也慢了

数据量多的话是有一个filterable做支持了,可以输入关键字进行匹配检索

但是不能解决卡顿的问题,接口还是比较慢

 

二、替代方案:

然后改用了el-autocomplete组件,自动补全,在官网上是没有这个文档说明的

代码示例:

<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 ...
    })
  }
}

 

@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', '')
      })
    }
  }
},

  

其他补充参考:

https://zhuanlan.zhihu.com/p/395688018