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

简介: typeahead在网站中的应用很多..今天跟着我来写一个不大靠谱的typeahead;你能学到什么?


前言


typeahead在网站中的应用很多..今天跟着我来写一个不大靠谱的typeahead;

你能学到什么?


  • 自定义事件
  • 遍历的思想
  • 功能细节的考虑


一切都挺不靠谱的,可完善的地方很多.废话不多说,看效果图


效果图


有哪些功能点?


  • 粗糙的模糊搜索 - 借助indexOf
  • ESCblur事件清除输入框,没有找到匹配的情况下
  • Enter默认在找到只剩下一个情况下选中
  • 方向盘的上下(已经阻止光标的移动)选中子项,回车选中
  • 鼠标点击选择子项
  • 搜索框清空情况下默认不触发自定义事件值的返回
  • 鼠标移动+键盘方向键移动位置的同步
  • placeholder及遍历数据data支持外部传入,也就是绑定props;前者字符串,后者数组对象


代码


typeahead.vue


<template>
  <div class="typeahead">
    <div class="typeahead-header">
      <input type="text" v-model="searchVal" @input="filterList(searchVal)" ref="input" :placeholder="placeholder" @keydown.down.prevent="selectChildWidthArrowDown" @keydown.up.prevent="selectChildWidthArrowUp" @keydown.enter="selectChildWidthEnter" @blur="ifNotFoundClear" @keydown.esc="ifNotFoundClear" autocomplete="off">
    </div>
    <transition name="el-fade-in-linear" mode="out-in">
      <div class="typeahead-content" v-show="searchVal && searchVal.length">
        <transition-group tag="ul" name="el-fade-in-linear" v-show="searchList && searchList.length > 0  && isExpand ">
          <li v-for="(item,index) in searchList" :key="index" :class="item.active ? 'active':''" @click="selectChild(index)" @mouseenter="setActiveClass(index)" @mouseleave="setActiveClass(index)">
            <a href="javascript:;">
              {{item.text}}
            </a>
          </li>
        </transition-group>
        <p class="noFound" v-show="searchList && searchList.length === 0">未能查询到,请重新输入!</p>
      </div>
    </transition>
  </div>
</template>
<script>
  export default {
    name: 'typeahead',
    data: function () {
      return {
        searchVal: '', // 搜索关键字
        resultVal: '', // 保存搜索到的值
        searchList: [], //保存过滤的结果集
        currentIndex: -1, // 当前默认选中的index,
        isExpand: false
      }
    },
    computed: {
      typeaheadData () {
        return this.mapData;
      }
    },
    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: '2'
            },
            {
              text: '我是天坑,分身乏术',
              value: '3'
            },
            {
              text: '我是天坑2,分身乏术',
              value: '3'
            },
            {
              text: '我是天坑3,分身乏术',
              value: '3'
            }
          ]
        }
      }
    },
    methods: {
      filterList (searchVal) { // 过滤数组
        if (this.searchVal === '') {
          this.ifNotFoundClear();
        } else {
          this.searchList = []; // 清空列表
          let tempArr = []; // 一个临时数组
          this.currentIndex = -1; // 重置index
          this.typeaheadData && this.typeaheadData.forEach(item => {
            if (item.text.indexOf(searchVal.trim()) !== -1) {
              tempArr.push(item);
            }
          });
          this.searchList = tempArr; // 为什么要一个临时数组,不然每次push都会触发视图更新..数据量一大....
          this.isExpand = true;
        }
      },
      ifNotFoundClear () { // 若是结果集长度为0就证明没有找到...赋值为空
        if (this.searchList.length === 0) {
          this.searchVal = '';
          this.isExpand = false;
        }
        this.searchList.forEach(item => {
          item.active = false;
        })
      },
      selectChild (index) {
        // 鼠标点击选择子项
        this.searchList.forEach((item, innerIndex) => {
          if (index === innerIndex || item.active) {
            this.searchVal = item.text;
            this.resultVal = item.value;
            this.isExpand = false;
          }
          this.$set(item, 'active', false);
        })
        this.$emit('selectValue', { text: this.searchVal, value: this.resultVal });
      },
      selectChildWidthArrowDown () {
        // 判断index选中子项
        if (this.currentIndex < this.searchList.length) {
          this.currentIndex++;
          this.searchList.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.searchList.forEach((item, index) => {
            this.currentIndex === index ? this.$set(item, 'active', true) : this.$set(item, 'active', false);
          })
        }
      },
      selectChildWidthEnter () {
        // 若是结果集只有一个,则默认选中
        if (this.searchList.length === 1) {
          this.searchVal = this.searchList[0].text;
          this.resultVal = this.searchList[0].value;
          this.isExpand = false;
          this.$emit('selectValue', { text: this.searchVal, value: this.resultVal })
        } else {
          // 若是搜索的内容完全匹配到项内的内容,则默认选中
          this.searchList.forEach(item => {
            if (this.searchVal === item.text || item.active === true) {
              this.searchVal = item.text;
              this.resultVal = item.value;
              this.isExpand = false;
              this.$emit('selectValue', { text: this.searchVal, value: this.resultVal })
            }
          })
        }
      },
      setActiveClass (index) {
        this.searchList.forEach((item, innerIndex) => {
          if (index === innerIndex) {
            this.$set(item, 'active', true);
            this.currentIndex = index;  // 这句话是用来修正index,,就是键盘上下键的索引,不然会跳位
          } else {
            this.$set(item, 'active', false)
          }
        })
      }
    }
  }
</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;
  }
  .typeahead {
    position: relative;
    background-color: #fff;
    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;
          }
        }
      }
    }
    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;
    }
    .typeahead-header,
    .typeahead-content {
      width: 100%;
    }
    .typeahead-content {
      position: absolute;
      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;
    }
    .noFound {
      text-align: center;
    }
  }
</style>


目录
相关文章
|
3月前
|
JavaScript
Vue中如何实现兄弟组件之间的通信
在Vue中,兄弟组件可通过父组件中转、事件总线、Vuex/Pinia或provide/inject实现通信。小型项目推荐父组件中转或事件总线,大型项目建议使用Pinia等状态管理工具,确保数据流清晰可控,避免内存泄漏。
311 2
|
6月前
|
人工智能 JavaScript 算法
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
786 0
|
6月前
|
JavaScript UED
用组件懒加载优化Vue应用性能
用组件懒加载优化Vue应用性能
|
6月前
|
JavaScript 前端开发 UED
Vue 表情包输入组件的实现代码:支持自定义表情库、快捷键发送和输入框联动的聊天表情解决方案
本文详细介绍了在 Vue 项目中实现一个功能完善、交互友好的表情包输入组件的方法,并提供了具体的应用实例。组件设计包含表情分类展示、响应式布局、与输入框的交互及样式定制等功能。通过核心技术实现,如将表情插入输入框光标位置和点击外部关闭选择器,确保用户体验流畅。同时探讨了性能优化策略,如懒加载和虚拟滚动,以及扩展性方案,如自定义主题和国际化支持。最终,展示了如何在聊天界面中集成该组件,为用户提供丰富的表情输入体验。
488 8
|
6月前
|
JavaScript 前端开发 UED
Vue 表情包输入组件实现代码及详细开发流程解析
这是一篇关于 Vue 表情包输入组件的使用方法与封装指南的文章。通过安装依赖、全局注册和局部使用,可以快速集成表情包功能到 Vue 项目中。文章还详细介绍了组件的封装实现、高级配置(如自定义表情列表、主题定制、动画效果和懒加载)以及完整集成示例。开发者可根据需求扩展功能,例如 GIF 搜索或自定义表情上传,提升用户体验。资源链接提供进一步学习材料。
276 1
|
6月前
|
JavaScript 前端开发 UED
Vue 项目中如何自定义实用的进度条组件
本文介绍了如何使用Vue.js创建一个灵活多样的自定义进度条组件。该组件可接受进度段数据数组作为输入,动态渲染进度段,支持动画效果和内容展示。当进度超出总长时,超出部分将以红色填充。文章详细描述了组件的设计目标、实现步骤(包括props定义、宽度计算、模板渲染、动画处理及超出部分的显示),并提供了使用示例。通过此组件,开发者可根据项目需求灵活展示进度情况,优化用户体验。资源地址:[https://pan.quark.cn/s/35324205c62b](https://pan.quark.cn/s/35324205c62b)。
252 0
|
8月前
|
存储 JavaScript 前端开发
基于 ant-design-vue 和 Vue 3 封装的功能强大的表格组件
VTable 是一个基于 ant-design-vue 和 Vue 3 的多功能表格组件,支持列自定义、排序、本地化存储、行选择等功能。它继承了 Ant-Design-Vue Table 的所有特性并加以扩展,提供开箱即用的高性能体验。示例包括基础表格、可选择表格和自定义列渲染等。
624 6
|
JavaScript
Vue的非父子组件之间传值
全局事件总线 一种组件间通信的方式,适用于任意组件间通信
210 0
|
缓存 JavaScript 前端开发
Vue Props、Slot、v-once、非父子组件间的传值....
Vue Props、Slot、v-once、非父子组件间的传值....
188 0
|
JavaScript
Vue中父子组件传值
先在⽗组件中给⼦组件的⾃定义属性绑定⼀个⽗组件的变量
186 0