需要形容:

  1. 实现一个element款式的选择器
  2. 搜寻
  3. 单个勾销,全副勾销
  4. 暗藏两个以上的已选并显示...

效果图:

坑:

一开始应用element的多选选择器,然而element不反对暗藏已选Tag,应用了伪类款式胜利实现第4个需要。然而发现搜寻基本用不了,被挡住了。最初还是自定义了一个组件。

在交互上,还有很多须要留神的——当点击选择器以外时,须要暗藏选择器;下拉开展时,input须要始终聚焦的。一开始在input中退出blur事件,发现无奈满足这两个交互。最初监听点击事件,判断点击的DOM是否在选择器中,以此管制失焦、聚焦和展现、暗藏下拉。

实现:

<template>  <div>    <el-popover trigger="manual" v-model="visible" popper-class="multiple-search-select-popper">      <div        class="multiple-search-select el-select"        :id="selectId"        slot="reference"        @click="visible = !visible"      >        <div class="el-select__tags">          <span>            <span              v-for="item in selectedList"              :key="item.value + 'abc'"              class="el-tag el-tag--info el-tag--small el-tag--light"            >              <span class="el-select__tags-text">{{ item.label }}</span>              <i @click="selectItem(item)" class="el-tag__close el-icon-close"></i>            </span>          </span>        </div>        <div class="el-input el-input--suffix" :class="{ 'is-focus': selecting }">          <input            tabindex="-1"            type="text"            readonly="readonly"            autocomplete="off"            :placeholder="selectedList.length == 0 ? '反对含糊搜寻,多选,勾销性能' : ''"            class="el-input__inner select-input"            :id="selectId + 'Input'"            style="height: 40px"          />          <span            class="el-input__suffix"            @mouseover="handleHoverIcon(true)"            @mouseleave="handleHoverIcon(false)"            ><span class="el-input__suffix-inner"              ><i                v-show="!showEmptyIcon"                :class="{                  'is-reverse': visible,                  'el-select__caret el-input__icon el-icon-arrow-up': true,                }"              ></i>              <i                v-show="selectedList.length != 0 && showEmptyIcon"                @click="empty"                class="el-select__caret el-input__icon el-icon-circle-close"              ></i></span          ></span>        </div>      </div>      <div class="search-ul-wrap">        <div class="el-input el-input--suffix">          <input            v-model="searchValue"            type="text"            autocomplete="off"            placeholder="搜寻"            class="el-input__inner"          />          <i            v-show="searchValue !== ''"            @click="searchValue = ''"            class="el-select__caret el-input__icon el-icon-circle-close close-icon"          ></i>        </div>        <div class="el-scrollbar">          <div class="el-select-dropdown__wrap el-scrollbar__wrap">            <ul class="el-scrollbar__view el-select-dropdown__list">              <!---->              <li                :class="{                  'el-select-dropdown__item': true,                  selected: values.indexOf(item.value) != -1,                }"                v-for="item in optionList"                @click="selectItem(item)"                :key="item.value + 'abcd'"              >                <span>{{ item.label }}</span>              </li>            </ul>          </div>        </div>      </div>    </el-popover>  </div></template>
<script>export default {  name: 'MultipleSearchSelect',  props: {    //   已选项,[{label:'',value:''}]    selected: {      type: Array,      default: () => [],    },    // 选项,[{label:'',value:''}]    options: {      type: Array,      default: () => [],    },  },  data: () => ({    visible: false, //下拉展现暗藏状态    showEmptyIcon: false, //清空抉择图标的展现暗藏状态    selectedList: [], //本组件的已选项    optionList: [], //本组件的选项    searchValue: '', //搜寻文本    values: [], //纯数组:已选    selectId: Math.ceil(Math.random() * 10000), //本组件惟一ID    selecting: false, //正在抉择状态  }),  watch: {    //   监听搜寻,展现符合条件的选项    searchValue(val) {      let arr = []      this.options.map((item) => {        if (item.label.indexOf(val) != -1 || val == '') {          arr.push(item)        }      })      this.optionList = arr    },    // 依据已选,筛选纯数组    selectedList(val) {      this.values = val.map((item) => {        return item.value      })    },  },  mounted() {    const t = this    // props给data    this.selectedList = this.selected    this.optionList = this.options    // 内部点击事件    const dropdown = document.getElementById(this.selectId)    document.addEventListener('click', function (event) {      var target = event.target      if (!dropdown || !dropdown.childNodes) return      if (target === dropdown.children || dropdown.contains(target)) {        return      }      t.visible = false      document.getElementById(t.selectId + 'Input').blur()      t.selecting = false    })    //下拉器宽度    console.log(document.getElementById(t.selectId + 'Input').offsetWidth)  },  methods: {    handleHoverIcon(sign) {      this.showEmptyIcon = sign ? this.selectedList.length != 0 : sign      if (!this.visible) {        document.getElementById(this.selectId + 'Input').blur()        this.selecting = false      }    },    //全副清空    empty() {      this.selectedList = []    },    // 选单个    selectItem(selectedItem) {      document.getElementById(this.selectId + 'Input').focus()      this.selecting = true      //   扭转已选数组      if (this.values.indexOf(selectedItem.value) === -1) {        this.selectedList.push(selectedItem)      } else {        this.selectedList.splice(this.values.indexOf(selectedItem.value), 1)      }      //如果是搜寻后点击,则触发清空搜索,节俭用户操作(参照element的交互)      if (this.searchValue != '') this.searchValue = ''    },  },}</script>
<style scoped lang="less">.multiple-search-select {  .el-select__tags {    flex-wrap: nowrap;  }  .el-input__inner {    padding-right: 5px;    cursor: pointer;    .el-select__caret {      float: right;      color: #c0c4cc;      font-size: 14px;      -webkit-transition: -webkit-transform 0.3s;      transition: -webkit-transform 0.3s;      -o-transition: transform 0.3s;      transition: transform 0.3s;      transition: transform 0.3s, -webkit-transform 0.3s;      transition: transform 0.3s, -webkit-transform 0.3s;      -webkit-transform: rotateZ(180deg);      -ms-transform: rotate(180deg);      transform: rotateZ(180deg);      cursor: pointer;    }  }  .el-dropdown-menu__item:focus,  .el-dropdown-menu__item:not(.is-disabled):hover {    background-color: #f5f7fa;    color: #606266;  }}.search-ul-wrap {  .el-input__inner {    margin-left: 8px;    margin-right: 8px;    width: 94%;  }}.selected {  color: #409eff;  &:after {    position: absolute;    right: 20px;    font-family: element-icons;    content: '\E6DA';    font-size: 12px;    font-weight: 700;    -webkit-font-smoothing: antialiased;  }}.el-tag.el-tag--info {  margin-right: 2px;  float: left;}.close-icon {  position: absolute;  right: 10px;  top: 0;  color: #c0c4cc;  cursor: pointer;}// 暗藏两个之后的所有Tag,并在第三个Tag之前展现.../deep/ .el-tag.el-tag--info:nth-child(n + 3) {  width: 0;  padding: 0;  height: 0;  margin: 0;  color: rgba(255, 255, 255, 0);  border-color: unset;  background: none;  i {    display: none;  }}/deep/ .el-tag.el-tag--info:nth-child(3) {  position: relative;  &:before {    content: '...';    position: absolute;    left: 4px;    top: -12px;    width: 26px;    height: 24px;    padding: 0 8px;    line-height: 22px;    font-size: 12px;    background-color: #f4f4f5;    color: #909399;    box-sizing: border-box;    border: 1px solid #e9e9eb;    border-radius: 4px;  }}&::-webkit-scrollbar-track-piece {  background: unset;}&::-webkit-scrollbar {  width: 6px;  height: 6px;}&::-webkit-scrollbar-thumb {  transition: 0.3s background-color;  background-color: rgba(144, 147, 153, 0.3);  border-radius: 20px;  width: 6px;  height: 6px;}</style>