Vue 2.x折腾记 - (5) 写一个不大靠谱的selectSearch组件

简介: 这个功能在网站中也很常见,下拉选择带搜索,基于之前的typeahead组件的基础上完善


前言


这个功能在网站中也很常见,下拉选择带搜索,基于之前的typeahead组件的基础上完善


效果图



实现逻辑


  • typeahead的实现大同小异,就是选择和传递数据的细节有所改善和重置默认值的思路有所变动
  • indexOf的比对稍微加强了,字母默认全部小写,还有输入的值去除空格


整体来说比typeahead组件更好理解和阅读


你能学到什么?


  • css样式
  • 组件临时增加的状态的处理
  • 数据过滤
  • 一些常用按键的监听实现(ESC,up,down,Enter),失焦等..
  • 过滤数据的默认选中


代码


selectSearch.vue


<template>
  <div class="select-search">
    <div class="select-header" @click="isExpand=!isExpand">
      <input type="text" autocomplete="off" readonly :placeholder="placeholder" :value="placeholderValue" @keydown.down.prevent="selectChildWidthArrowDown" @keydown.up.prevent="selectChildWidthArrowUp" @keydown.enter="selectChildWidthEnter">
      <i class="fzicon " :class="isExpand?'fz-ad-jiantou1':'fz-ad-jiantou'"></i>
    </div>
    <div class="select-body" v-if="isExpand">
      <input type="text" placeholder="关键字" v-model="searchVal" autocomplete="off" @blur="resetDefaultStatus" @keydown.esc="resetDefaultStatus" @keydown.down.prevent="selectChildWidthArrowDown" @keydown.up.prevent="selectChildWidthArrowUp" @keydown.enter="selectChildWidthEnter">
      <transition name="el-fade-in-linear" mode="out-in">
        <div class="typeahead-filter" v-show="typeaheadData">
          <transition-group tag="ul" name="el-fade-in-linear">
            <li v-for="(item,index) in typeaheadData " :key="index" :class="item.active ? 'active':''" @mouseenter="setActiveClass(index)" @mouseleave="setActiveClass(index)" @click="selectChild(index)">
              <a href="javascript:;">
                {{item.text}}
              </a>
            </li>
          </transition-group>
          <p class="noFound" v-show="typeaheadData && typeaheadData.length === 0">未能查询到,请重新输入!</p>
        </div>
      </transition>
    </div>
  </div>
</template>
<script>
  export default {
    name: 'selectSearch',
    data: function () {
      return {
        placeholderValue: '',// 给看到选择内容的
        isExpand: false,
        searchVal: '', // 搜索关键字
        resultVal: '', // 保存搜索到的值
        searchList: [], //保存过滤的结果集
        currentIndex: -1, // 当前默认选中的index,
      }
    },
    computed: {
      typeaheadData () {
        let temp = [];
        if (this.searchVal === '') {
          return this.mapData;
        } else {
          this.currentIndex = -1;  // 重置特殊情况下的索引
          this.mapData.forEach(item => {
            item.active = false; // 取消高亮
            if (item.text.indexOf(this.searchVal.toLowerCase().trim()) !== -1) {
              temp.push(item)
            }
          })
          return temp;
        }
      }
    },
    props: {
      placeholder: {
        type: String,
        default: '--请选择--'
      },
      mapData: {
        type: Array,
        default: function () {
          return [
            {
              text: 'wofsdf',
              value: 0
            },
            {
              text: '我是技术渣1',
              value: 1
            },
            {
              text: '我是技术渣2',
              value: 2
            },
            {
              text: '我是天坑',
              value: 3
            },
            {
              text: '我是天坑,分身乏术',
              value: 4
            },
            {
              text: '我是天坑2,分身乏术',
              value: 5
            },
            {
              text: '我是天坑3,分身乏术',
              value: 6
            }
          ]
        }
      }
    },
    methods: {
      resetDefaultStatus () {
        this.searchVal = '';
        this.resultVal = '';
        this.currentIndex = -1;
        this.typeaheadData.forEach(item => {
          this.$set(item, 'active', false);
        })
      },
      setActiveClass (index) { // 设置样式活动类
        this.mapData.forEach((item, innerIndex) => {
          if (index === innerIndex) {
            this.$set(item, 'active', true);
            this.currentIndex = index;  // 这句话是用来修正index,,就是键盘上下键的索引,不然会跳位
          } else {
            this.$set(item, 'active', false)
          }
        })
      },
      selectChildWidthArrowDown () {
        // 判断index选中子项
        if (this.currentIndex < this.typeaheadData.length) {
          this.currentIndex++;
          this.typeaheadData.forEach((item, index) => {
            this.currentIndex === index ? this.$set(item, 'active', true) : this.$set(item, 'active', false);
          })
        }
      },
      selectChildWidthArrowUp () {
        // 判断index选中子项
        if (this.currentIndex > 0) {
          this.currentIndex--;
          this.typeaheadData.forEach((item, index) => {
            this.currentIndex === index ? this.$set(item, 'active', true) : this.$set(item, 'active', false);
          })
        }
      },
      selectChildWidthEnter () {
        // 若是结果集只有一个,则默认选中
        if (this.typeaheadData.length === 1) {
          this.searchVal = this.typeaheadData[0].text;
          this.resultVal = this.typeaheadData[0].value;
          this.placeholderValue = this.typeaheadData[0].text;
          this.isExpand = false;
          this.$emit('selectValue', { text: this.searchVal, value: this.resultVal })
        } else {
          // 若是搜索的内容完全匹配到项内的内容,则默认选中
          this.typeaheadData.forEach(item => {
            if (this.searchVal === item.text || item.active === true) {
              this.searchVal = item.text;
              this.placeholderValue = item.text;
              this.resultVal = item.value;
              this.isExpand = false;
              this.$emit('selectValue', { text: this.searchVal, value: this.resultVal })
            }
          })
        }
        this.resetDefaultStatus();
      },
      selectChild (index) {
        // 鼠标点击选择子项
        this.mapData.forEach((item, innerIndex) => {
          if (index === innerIndex || item.active) {
            this.searchVal = item.text;
            this.resultVal = item.value;
            this.placeholderValue = item.text;
            this.isExpand = false;
          }
          this.$set(item, 'active', false);
        })
        this.$emit('selectValue', { text: this.searchVal, value: this.resultVal });
        this.resetDefaultStatus();
      },
    }
  }
</script>
<style scoped lang="scss">
  .el-fade-in-linear-enter-active,
  .el-fade-in-linear-leave-active,
  .fade-in-linear-enter-active,
  .fade-in-linear-leave-active {
    transition: opacity .2s linear;
  }
  .el-fade-in-enter,
  .el-fade-in-leave-active,
  .el-fade-in-linear-enter,
  .el-fade-in-linear-leave,
  .el-fade-in-linear-leave-active,
  .fade-in-linear-enter,
  .fade-in-linear-leave,
  .fade-in-linear-leave-active {
    opacity: 0;
  }
  .select-search {
    a {
      color: #333;
      text-decoration: none;
      padding: 5px;
    }
    ul {
      list-style: none;
      padding: 6px 0;
      margin: 0;
      overflow: visible;
      li {
        display: block;
        width: 100%;
        padding: 5px;
        font-size: 14px;
        padding: 8px 10px;
        position: relative;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        color: #48576a;
        height: 36px;
        line-height: 1.5;
        box-sizing: border-box;
        cursor: pointer;
        &.active {
          background-color: #20a0ff;
          a {
            color: #fff;
          }
        }
      }
    }
    .select-header {
      position: relative;
      border-radius: 4px;
      border: 1px solid #bfcbd9;
      outline: 0;
      padding: 0 8px;
      >input {
        border: none;
        -webkit-appearance: none;
        -moz-appearance: none;
        appearance: none;
        width: 100%;
        outline: 0;
        box-sizing: border-box;
        color: #1f2d3d;
        font-size: inherit;
        height: 36px;
        line-height: 1;
      }
      >i {
        transition: all .3s linear;
        display: inline-block;
        position: absolute;
        right: 3%;
        top: 50%;
        transform: translateY(-50%);
      }
    }
    .select-body {
      z-index: 1000;
      border-radius: 2px;
      background-color: #fff;
      box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
      box-sizing: border-box;
      margin: 5px 0;
      padding: 8px;
      >input {
        -webkit-appearance: none;
        -moz-appearance: none;
        appearance: none;
        background-color: #fff;
        background-image: none;
        border-radius: 4px;
        border: 1px solid #bfcbd9;
        box-sizing: border-box;
        color: #1f2d3d;
        font-size: inherit;
        height: 36px;
        line-height: 1;
        outline: 0;
        padding: 3px 10px;
        transition: border-color .2s cubic-bezier(.645, .045, .355, 1);
        width: 100%;
        display: inline-block;
      }
    }
  }
</style>


目录
相关文章
|
2月前
|
缓存 JavaScript UED
Vue3中v-model在处理自定义组件双向数据绑定时有哪些注意事项?
在使用`v-model`处理自定义组件双向数据绑定时,要仔细考虑各种因素,确保数据的准确传递和更新,同时提供良好的用户体验和代码可维护性。通过合理的设计和注意事项的遵循,能够更好地发挥`v-model`的优势,实现高效的双向数据绑定效果。
164 64
|
2月前
|
前端开发 JavaScript 测试技术
Vue3中v-model在处理自定义组件双向数据绑定时,如何避免循环引用?
Web 组件化是一种有效的开发方法,可以提高项目的质量、效率和可维护性。在实际项目中,要结合项目的具体情况,合理应用 Web 组件化的理念和技术,实现项目的成功实施和交付。通过不断地探索和实践,将 Web 组件化的优势充分发挥出来,为前端开发领域的发展做出贡献。
57 8
|
2月前
|
JavaScript
在 Vue 3 中,如何使用 v-model 来处理自定义组件的双向数据绑定?
需要注意的是,在实际开发中,根据具体的业务需求和组件设计,可能需要对上述步骤进行适当的调整和优化,以确保双向数据绑定的正确性和稳定性。同时,深入理解 Vue 3 的响应式机制和组件通信原理,将有助于更好地运用 `v-model` 实现自定义组件的双向数据绑定。
|
2月前
|
存储 JavaScript 开发者
Vue 组件间通信的最佳实践
本文总结了 Vue.js 中组件间通信的多种方法,包括 props、事件、Vuex 状态管理等,帮助开发者选择最适合项目需求的通信方式,提高开发效率和代码可维护性。
|
2月前
|
存储 JavaScript
Vue 组件间如何通信
Vue组件间通信是指在Vue应用中,不同组件之间传递数据和事件的方法。常用的方式有:props、自定义事件、$emit、$attrs、$refs、provide/inject、Vuex等。掌握这些方法可以实现父子组件、兄弟组件及跨级组件间的高效通信。
|
2月前
|
缓存 JavaScript UED
Vue 中实现组件的懒加载
【10月更文挑战第23天】组件的懒加载是 Vue 应用中提高性能的重要手段之一。通过合理运用动态导入、路由配置等方式,可以实现组件的按需加载,减少资源浪费,提高应用的响应速度和用户体验。在实际应用中,需要根据具体情况选择合适的懒加载方式,并结合性能优化的其他措施,以打造更高效、更优质的 Vue 应用。
|
3月前
|
JavaScript 前端开发 测试技术
组件化开发:创建可重用的Vue组件
【10月更文挑战第21天】组件化开发:创建可重用的Vue组件
38 1
|
JavaScript
Vue的非父子组件之间传值
全局事件总线 一种组件间通信的方式,适用于任意组件间通信
|
缓存 JavaScript 前端开发
Vue Props、Slot、v-once、非父子组件间的传值....
Vue Props、Slot、v-once、非父子组件间的传值....
97 0
|
JavaScript
Vue中父子组件传值
先在⽗组件中给⼦组件的⾃定义属性绑定⼀个⽗组件的变量