一个指令解决element-ui中el-select无限滚动的所有问题(节流、远程搜索、回显)

首先是全局指令:

/**
 * el-select加载事件(可用于做select的无限滚动)
 * 使用方法(两种方法,推荐第2种):
 * 1. 查询方法自定义(远程搜索也需要自定义):
 *    v-load-more.method="function(){}"
 * 2. 传入url就可实现无限滚动和远程搜索,要求接口分页参数为“pageSize”和“pageNo”:
 *    v-load-more="{url:'operationSupport_qualification/ColBase/findAllByPage', options: relColIdOptions}"
 *    url:(必传)后端接口地址
 *    options:(必传)el-option中用来v-for的对象数组
 *    modelField: (必传)用于回显时查询用的字段以及返回时对应key的字段,这里两个字段合成一个了,如果想分开可自行调整
 *    pageSize:(非必传)每次加载的条数,不传则默认10
 *    model: (非必传)用于回显时查询用的字段值,填了这一项才能回显,这一项和el-select的v-model相等,要求后端接口支持
 *           回显还有另一种用法,就是给options设置初始值,初始值内容为需要回显的内容
 *    searchField:(非必传)远程搜索用的字段,不传则不能实现远程搜索;
 *                要求接口支持和el-select开启搜索(filterable,并重置自定义搜索方法(:filter-method="()=>{}"))
 */
Vue.directive('loadMore', {
  bind(el, binding) {
    // 如果有method由调用方实现,没有则在这里实现加载和远程搜索的功能
    if (binding.modifiers.method) {
      // 节流
      let timer
      // 滚动监听
      el.querySelector('.el-select-dropdown .el-select-dropdown__wrap').addEventListener('scroll', function() {
        const condition = this.scrollHeight - this.scrollTop <= this.clientHeight + 100
        if (!timer && condition) {
          // 滚动加载(调用自定义的加载方法)
          binding.value()
          timer = setTimeout(() => {
            clearTimeout(timer)
            timer = null
          }, 500)
        }
      })
    } else {
      // 传入的对象
      let value = binding.value
      // 节流
      let timer
      // 无搜索内容变量
      let pageNo = 1
      let pages = 1
      // 远程搜索内容变量
      let searchPageNo = 1
      let searchPages = 1
      // 每次加载的条数
      let pageSize = isNaN(value.pageSize) ? 10 : parseInt(value.pageSize)
      // 远程搜索变量
      let searchField = value.searchField
      // 接口地址
      let url = value.url
      // 下拉数组,这个options在本方法中必须永远指向value.options,否则整个功能都将失效
      let options = value.options
      // 无搜索拷贝数组,此处是为了在加载的基础上加一些默认的下拉项
      let optionsCopy = JSON.parse(JSON.stringify(value.options))
      // 远程搜索拷贝数组
      let optionsSearch = []
      // 远程搜索内容
      let searchValue = ''
      // 加载逻辑
      const loadOptions = (searchField, search) => {
        let params = {
          pageSize: pageSize
        }
        // 这里不能改变options的指向,否则会使整个功能失效(不能用options = [])
        options.length = 0
        // 判断是否为远程搜索,true-是
        if (searchField && search) {
          // 当到最大页数时不再查询
          if (searchPages >= searchPageNo) {
            params.pageNo = searchPageNo++
            params[searchField] = search
            // todo 请求方法是我们自己封装的,需要换成自己项目的方法
            api[url](params).then(res => {
              if (res) {
                searchPages = Math.ceil(res.data.total / pageSize)
                optionsSearch = optionsSearch.concat(res.data.data)
                dataProcessing(optionsSearch)
              }
            })
          }
        } else {
          // 当到最大页数时不再查询
          if (pages >= pageNo) {
            params.pageNo = pageNo++
            // todo 请求方法是我们自己封装的,需要换成自己项目的方法
            api[url](params).then(res => {
              if (res) {
                pages = Math.ceil(res.data.total / pageSize)
                optionsCopy = optionsCopy.concat(res.data.data)
                dataProcessing(optionsCopy)
              }
            })
          }
        }
      }
      // 返回数据处理
      let dataProcessing = (optionsCopy) => {
        // 这里不能改变options的指向,否则会使整个功能失效
        optionsCopy.forEach(item => {
          let check = options.find(t => {
            return t[value.modelField] === item[value.modelField]
          })
          if (!check) {
            options.push(item)
          }
        })
      }
      // 首次加载
      loadOptions()
      // 判断是否需要回显
      if (value.model && value.modelField) {
        // 回显方法
        let echo = (model, modelField) => {
          let params = {}
          params[modelField] = model
          // todo 请求方法是我们自己封装的,需要换成自己项目的方法
          api[url](params).then(res => {
            if (res) {
              optionsCopy = optionsCopy.concat(res.data.data)
              dataProcessing(optionsCopy)
            }
          })
        }
        if (optionsCopy.length > 0) {
          let check = optionsCopy.find((item) => {
            return item[value.modelField] === value.model
          })
          if (check) {
            echo(value.model, value.modelField)
          }
        } else {
          echo(value.model, value.modelField)
        }
      }
      // 滚动监听(无限滚动)
      el.querySelector('.el-select-dropdown .el-select-dropdown__wrap').addEventListener('scroll', function() {
        const condition = this.scrollHeight - this.scrollTop <= this.clientHeight + 100
        if (!timer && condition) {
          // 滚动加载
          loadOptions(searchField, searchValue)
          timer = setTimeout(() => {
            clearTimeout(timer)
            timer = null
          }, 200)
        }
      })
      // 输入监听(远程搜索)
      if (searchField) {
        el.getElementsByTagName('input')[0].addEventListener('input', function() {
          if (this.value) {
            searchPageNo = 1
            searchPages = 1
            optionsSearch = []
            searchValue = this.value
            loadOptions(searchField, searchValue)
          } else {
            searchValue = ''
            dataProcessing(optionsCopy)
          }
        })
      }
    }
  }
})

方法一(不推荐):

(方法一只实现了无限滚动,如果需要其他的,可以参考方法二,这里不推荐是因为用到的代码还是会很多,方法都需要自己去定义)

<el-select v-model="id"
           filterable
           placeholder="请选择关联人员"
           v-load-more.method="load"
           style="width: 100%">
  <el-option
    v-for="item in options"
    :key="item.id"
    :label="item.name"
    :value="item.id">
  </el-option>
</el-select>


data() {
  return {
    options:[],
    pages: 1,
    pageNo: 1,
    pageSize: 10,
    id: ''
  }
}


methods: {
  load() {
    if (this.pages >= this.pageNo) {
      // todo 这里的请求是我们项目自己封装的,需要换成你们自己的请求方法
      this.$api['url']({
        pageSize: this.pageSize,
        pageNo: this.pageNo++
      }).then(res => {
        if (res) {
          this.pages = Math.ceil(res.data.total / this.pageSize)
          this.options.push(...res.data.data)
        }
      })
    }
  }
}


mounted() {
	this.load()
}

方法二(推荐):

<el-select v-model="id"
           filterable
           :filter-method="()=>{}"
           v-load-more="{
             url:'url',
             options: options,
             model: id,
             modelField: 'id',
             searchField: 'name'}"
           style="width: 100%">
  <el-option
    v-for="item in options"
    :key="item.id"
    :label="item.name"
    :value="item.id">
  </el-option>
</el-select>

data() {
  return {
    options:[],
    id: ''
  }
}