如何解决多个el-select共用备选列表问题

7,418 阅读5分钟

用过element-ui的同学都知道el-select有搜索功能,通过给el-select添加filterable属性即可实现搜索功能。同时,也可以通过传入一个filter-method(Function)来实现自定义搜索功能。那么接下来,我们可以想一下下面的几个简单需求的实现?需求如下:

  1. 单个下拉框实现搜索功能;
  2. 两个下拉框共用同一个搜索列表时,如何实现;
  3. 通过循环遍历出来的下拉框,共用同一个搜索列表,又该如何实现呢?

单个下拉框实现搜索功能

首先针对第一种情况,用户不仅可以通过label进行搜索,同时也可以通过price搜索。此时,我们可以通过el-optionlabel值来实现,即:

默认情况下,el-select 会找出所有label属性包含输入值的选项

而再复杂点,需求规定label不显示price,并且也可以通过price进行搜索的时候,默认情况下就不起作用来, 但是我们依旧可以通过filter-method实现。代码如下

<template>
  <el-select 
    v-model="value"
    filterable
    :filter-method="handleFilter" 
    placeholder="请选择">
    <el-option
      v-for="item in options"
      :key="item.value"
      :label="item.label"
      :value="item.value">
    </el-option>
  </el-select>
</template>

<script>
  export default {
    data() {
      return {
        originList: [{
          value: '选项1',
          label: '黄金糕',
          price: '10元',
        }, {
          value: '选项2',
          label: '双皮奶',
          price: '20元',
        }, {
          value: '选项3',
          label: '蚵仔煎',
          price: '30元',
        }],
        options: [],
        value: ''
      }
    },
    methods: {
      handleFilter (letter) {
        if (!letter) {
          this.options = this.originList
          return
        }
        this.options = this.originList.filter(v => {
          return v.label.includes(letter) || v.price.includes(letter)
        })
      }
    },
    mounted () {
      this.options = this.originList
    }
  }
</script>

两个下拉框共用同一个搜索列表

对于两个下拉框在共用同一个搜索列表时,同时还保持同一个筛选结果列表,此时便会有问题。请看下面代码:

<template>
  <el-select 
    v-model="value1"
    filterable
    :filter-method="handleFilter" 
    placeholder="请选择">
    <el-option
      v-for="item in options"
      :key="item.value"
      :label="item.label"
      :value="item.value">
    </el-option>
  </el-select>
  
  <el-select 
    v-model="value2"
    filterable
    :filter-method="handleFilter" 
    placeholder="请选择">
    <el-option
      v-for="item in options"
      :key="item.value"
      :label="item.label"
      :value="item.value">
    </el-option>
  </el-select>
</template>

由于筛选结果共用options,则在options发送变化时,另一个下拉框因为没有匹配上value值,则默认显示value值做为label值,这样就造成了不好的影响。而此时需要把options分开来写,通过options1options2来分别对应筛选结果列表。

Tips:当然,如果需求的value和label正好一样,那我们依旧可以共用同一个筛选结果列表,因为在页面显示方面,用户是无感知的。

通过循环遍历出来的下拉框,共用同一个搜索列表

那么,接下来在循环共用的情况下,又该如何实现呢?根据上面的经验,我们可能会有这样的想法:通过遍历的数组增加对应的筛选列表的数组做为一一对应,当然这样也是可以实现的。但是,如何遍历的数组项很多,且原始搜索列表的项又是很多,将会造成一定的不必要的影响。假如具体需求如下:

  • 多次航班分别对应入境机场和出境机场;
  • 出入境的机场列表是同一个备选列表;
  • 备选列表以分组的形式展示(机场列表格式如下)。
机场列表 = [
    {
        title: '热门机场',
        airportList: [
            {
                name: '北京首都国际机场',
                city: '北京'
            },
            {
                name: '萧山机场',
                city: '杭州'
            }
            ...
        ]
    },
    {
        title: 'A',
        airportList: [
            {
                name: '澳门国际机场',
                city: '澳门'
            }
            ...
        ]
    }
]

不妨大家可以先想一下,这个需求该如何实现呢?首先讲一下我自己的思路:

既然是共用同一个备选列表,而用户同时只能操作一个下拉框进行搜索,那我们是不是可以动态变换各个下拉框对应的筛选列表呢?

  1. 定义两个类型变量,记录当前下拉框索引和出入境区分;
  2. 定义两个筛选列表变量,记录最原始机场列表和当前下拉框筛选结果列表。

代码如下:

<el-select
   v-model="item.val1"
   filterable
   @focus="handleFocus(index, 'entry')"
   :filter-method="handleFilter"
   placeholder="请选择">
  <el-option-group
    v-for="group in cIndex === index && type === 'entry' ? options : originList"
    :key="group.title"
    :label="group.title">
    ......
  </el-option-group>
</el-select>

<!-- 两个关键点 -->
1. 下拉框触发焦点时,通过 focus 事件,保存当前下拉框的类型变量,即
handleFocus (index, type) {
  // 当前筛选列表需要重新赋值,以保证不会显示上一个下拉框筛选的结果列表
  this.options = this.originList
  this.type = type
  this.cIndex = index
}
2. 备选列表通过 cIndex 和 type,动态切换,以保证当前筛选框的筛选操作,不会影响其他下拉框的显示内容

通过动态切换备选列表的方案解决了多个下拉框公用同一条筛选列表的问题,但是在机场列表为100条以上的情况下,会发现有个问题:

在筛选的过程中,el-selectquery参数会瞬间变为当前value值,而因为筛选结果列表未包含当前value对应的label,显示出现问题,具体原因还未找到,不过猜想:filter-method是否是通过DOM的复用来减少DOM的渲染,以致option-group循环DOM出现了问题。

不过目前的解决方案是:通过el-select提供的remote远程搜索方式来替代filter-method全部源码即例子

总结

  1. 若下拉框的labelvalue值对应一样的情况,我们完全可以用同一个备选列表进行筛选操作;
  2. 一般情况下,labelvalue值是不一样,此时,我们可以尝试动态切换备选列表进行实现;

若读者有更好的解决方案,欢迎提供!