你可能需要一个带历史记录功能的input输入框

997 阅读4分钟

前言

我们最近有个需求,有个输入框可以查看历史功能,具体的需求如下:

  1. 点击输入框自动展示历史项
  2. 输入自动匹配已有历史项,没有就不展示下拉框
  3. 用户没有历史记录就不展示下拉框
  4. 删除输入的文本或者清空输入框后要显示全部历史记录

我这边实现了两种方案,都是基于elementui的组件库的(毕竟人家有现成没必要手写了吧~~,主打一个拿来主义)

el-input+el-dropdown

最简单的方案就是这个,但是呢,从我们的需求出发,其中需要注意的点就是控制el-dropdown的展开时机 代码如下:

image.png

computed:{
//options是你的历史记录列表
optionsfilter(){
if(this.editTxt){
return this.options.filter(item=>item.includes(this.editTxt))
}else
{
return this.options
}
}

//点击选项时候记得赋值给editTxt
handleCommand(item){
this.editTxt=item
}

这就实现了一个带历史记录的输入框(将就着看吧,就是这个意思了==)

IMG_0047.GIF

但是,只要细测,因为使用的是el-dropdown所以会出现一些其他问题

  1. 清除输入框,无法显示全部历史记录
  2. 在没有历史记录时候依然会存在一个空的dropdown列表。这个可以通过disabled掉整个dropdown来解决,如红框所示

image.png

  1. 在输入历史记录不存在的选项时并通过键盘删除时无法显示全部的历史记录列表,必须再次点击输入框才能触发dropdown的下拉列表

IMG_0057.GIF

el-autocomplete

说实话要不是有大佬提示,我在elementuiV2.15上都没看到这个组件, 我在使用时候压根都不知道有这个,这个库还是玩的不够熟练

image.png

马上试一下

image.png

说下我遇到的问题

  1. 这个传参必须是对象数组,否则会出现选不中下拉框的值,只能手动输入才会显示在输入框, 如果你是普通数组, 你需要select方法赋值给你绑定的值才行
  2. 遇到和el-dropdown一样的问题,输入没有的值再清空无法显示完整记录,我还在清空时候获取了焦点,也触发不到这个下拉框

image.png

现象如下:

IMG_0142.GIF

总之一句话,dropdown的触发是需要点击input框的,无法实时根据输入框的变化来展示下拉框

这样测试就开始给我提bug了,为了实现流畅的显示下拉列表,我觉得很符合的就是el-select的那个功能,在取消后能直接显示下拉框,但是el-select是选择框,在没有选项时候也会显示下拉框,创建条目又没有删除按钮,总之就是各种功能上无法完美匹配, 所以我决定手搓一个,因为select的下拉框用起来很顺畅包括样式和滚动条这些都一定和项目匹配,所以我直接看了elementui的select的源码,其中下拉框的组件使用的是select-menu,所以我直接在项目中使用这个组件

<template>
  <div class="historyinput" v-clickoutside="handleClose">
    <el-input
      v-model="value"
      ref="reference"
      :disabled='disabled'
     
      @focus="handleFocus"
      clearable
      @clear="clearinput"
      v-bind="$attrs"
    />

    <transition name="el-zoom-in-top" @after-leave="doDestroy">
      <el-select-menu
        ref="popper"
        :append-to-body="true"
        v-show="visible && showNewOption.length"
      >
        <div class="el-scrollbar">
          <div class="el-select-dropdown_wrap el-scrollbar_wrap">
            <ul
              class="el-scrollbar_view el-select-dropdown_list"
              ref="scrollbar"
            >
              <li
                v-for="(item, index) in showNewOption"
                :key="index"
              
                @click="handleOptionSelect(item)"
                class="el-select-dropdown_item"
                :class="{
                  'is-disabled': disabled,

             
                }"
              >
                <slot>
                  <span>{{ item }}</span>
                </slot>
              </li>
            </ul>
          </div>
        </div>
      </el-select-menu>
    </transition>
  </div>
  <template>
    <script>
      import ElSelectMenu from 'element-ui/packages/select/src/select-dropdown.vue'
      import Clickoutside from 'element-ui/src/utils/clickoutside';
       export default {

      directives: { Clickoutside },

      model: {
      prop: 'inputvalue',
      event: 'setvalue'

      },
      props:{
      
      disabled:{
      default:false,
       type:Boolean
      },
     //输入值
      inputvalue:{

      type: String, 
      default:''
        },
//历史记录选项
      options:{ 
        default:()=>[], 
        type:Array}
    },

      data{
      return{
      visible: false,
      softFocus: false,
      inputwidth: 0, //一定要有,elementui内部ElSelectMenu使用这个
      hoverIndex:0,
      }},
      components:
      {
      ElSelectMenu,
      },
      computed:{
      value: {
      get (){
      return this.inputvalue
      },

      set (val){

      this.$emit('setvalue',val)
      }},


      isObject() {

      return Object.prototype.toString.call(this.value).toLowerCase()==='[object object]'
      },
     //筛选
      showNewOption() {
      if(this.value.length){
      let hasExistingOption = this.options.filter(option => option.includes(this.vaule)
      return hasExistingOption

      }else{

      return this.options

      }
      }
      },
      mounted(){

      window.addEventListener('resize', this.handleResize)

      this.$nextTick(() => {

      const reference = this.$refs.reference;

      if (reference && reference.$el) {

      this.inputwidth = reference.$el.getBoundingClientRect().width;}
      })
      },
      beforeDestroy(){

      window.removeEventListener('resize', this.handleResize)
      },


      methods: {

      clearinput (){

      this.$nextTick(()=this.$refs.reference.focus())

      },
      resetInputwidth() {

      this.inputwidth=this.$refs.reference.$el.getBoundingClientRect().width
      },
    
      handleClose(event) { 
      this.visible = false;
      },
      handleResize() {

      this.resetInputWidth();
      },
      doDestroy() {
        this.$refs.popper && this.$refs.popper.doDestroy();
      },
      handleOptionSelect(val){
      this.value=val
      this.visible = false;
     
      },
      handleFocus(event) {
    
      this.visible=true},
      },
      }
    </script>
</template>

父组件中使用:

image.png 相信你也看到了,这里面用到一个自定义指令,这其实是用来当鼠标点击外空白区域后关闭下拉框的,之前没用这个我使用的是blur事件,当鼠标失去焦点后就关闭,但是这样在使用过程中会出现无法选中值就直接关闭弹框, 因为点击事件和鼠标失焦存在时序上问题,其实elementui内部是也是没有在blur事件中去关闭弹框,通过一个softFocus的标志位在控制。

现在看着是不是就丝滑了很多~~~

IMG_0058.GIF

总结

没啥总结,谢谢elementui给我们开发带来的各种便利,唯一推荐大家的就是看看elementui的源码,是非常优秀的组件库,不管是整个库的工程化还是各个子组件间的通信都是很有学习价值的