Vue实现typeahead组件功能(非常靠谱)


Posted in Javascript onAugust 26, 2017

 前言

之前那个typeahead写的太早,不满足当前的业务需求。

而且有些瑕疵,还有也不方便传入数据和响应数据..

于是就推倒了重来,写了个V2的版本

看图,多了一些细节的考虑;精简了实现的逻辑代码

效果图

Vue实现typeahead组件功能(非常靠谱)

实现的功能

1: 鼠标点击下拉框之外的区域关闭下拉框

2: 支持键盘上下键选择,支持鼠标选择

3: 支持列表过滤搜索

4: 支持外部传入列表JSON格式的映射

5: 支持placeholder的传入

6: 选中对象的响应(.sync vue2.3的组件通讯的语法糖)

7: 箭头icon的映射,感觉作用不大,移除了

用法

<select-search 
style="max-width:195px" 
placeholder="请选择广告主" 
:asyncData.sync="adHostData" 
:mapData="adHostDataList" 
:mapDataFormat="{label:'userName',value:'userId'}">
</select-search>
  • asyncData:响应的数据,也就是选中的..回来是一个对象
  • mapData : 搜索的列表数据,肯定是外部传入了…
  • mapData : 列表值映射

代码

selectSearch.vue

<template>
 <div class="select-search" v-if="typeaheadData" ref="selectSearch" @click.native="showHideMenu($event)">
  <div class="select-header">
   <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 && typeaheadData">
   <input type="text" placeholder="关键字" v-model="searchVal" autocomplete="off" @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">
     <transition-group tag="ul" name="el-fade-in-linear" v-show="typeaheadData.length>0">
      <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:;" rel="external nofollow" >
        {{item[mapDataFormat.label]}}
       </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: {
   mapFormatData () { // 外部有传入格式的时候映射mapData
    return this.mapData.map(item => {
     item[this.mapDataFormat.value] = item[this.mapDataFormat.value];
     return item;
    });
   },
   typeaheadData () {
    let temp = [];
    if (this.searchVal && this.searchVal === '') {
     return this.mapFormatData;
    } else {
     this.currentIndex = -1; // 重置特殊情况下的索引
     this.mapFormatData.map(item => {
      if (item[this.mapDataFormat.label].indexOf(this.searchVal.toLowerCase().trim()) !== -1) {
       temp.push(item)
      }
      return item;
     })
     return temp;
    }
   }
  },
  props: {
   placeholder: {
    type: String,
    default: '--请选择--'
   },
   emptyText: {
    type: String,
    default: '暂无数据'
   },
   mapData: { // 外部传入的列表数据
    type: Array,
    default: function () {
     return []
    }
   },
   mapDataFormat: { // 映射传入数据的格式
    type: Object,
    default: function () {
     return {
      label: 'text',
      value: 'value',
      extraText: 'extraText'
     }
    }
   },
   asyncData: { // 实时响应的值
    type: [Object, String],
    default: function () {
     return {}
    }
   }
  },
  methods: {
   showHideMenu (e) { // 点击其他区域关闭下拉列表
    if (e) {
     if (this.$refs.selectSearch && this.$refs.selectSearch.contains(e.target)) {
      this.isExpand = true;
     } else {
      this.isExpand = false;
     }
    }
   },
   resetDefaultStatus () { // 清除所有选中状态
    this.searchVal = '';
    this.currentIndex = -1;
    this.typeaheadData.map(item => {
     this.$delete(item, 'active');
    })
   },
   setActiveClass (index) { // 设置样式活动类
    this.typeaheadData.map((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.map((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.map((item, index) => {
      this.currentIndex === index ? this.$set(item, 'active', true) : this.$set(item, 'active', false);
     })
    }
   },
   selectChildWidthEnter () {
    // 若是结果集只有一个,则默认选中
    if (this.typeaheadData.length === 1) {
     this.$emit('update:asyncData', this.typeaheadData[0]); // emit响应的值
     this.placeholderValue = this.typeaheadData[0][this.mapDataFormat.label];
    } else {
     // 若是搜索的内容完全匹配到项内的内容,则默认选中
     this.typeaheadData.map(item => {
      if (this.searchVal === item[this.mapDataFormat.label] || item.active === true) {
       this.$emit('update:asyncData', item); // emit响应的值
       this.placeholderValue = item[this.mapDataFormat.label];
      }
     })
    }
    this.isExpand = false;
   },
   selectChild (index) {
    // 鼠标点击选择子项
    this.typeaheadData.map((item, innerIndex) => {
     if (index === innerIndex || item.active) {
      this.placeholderValue = item[this.mapDataFormat.label];
      this.$emit('update:asyncData', item); // emit响应的值
     }
    });
    this.isExpand = false;
   },
  },
  mounted () {
   window.addEventListener('click', this.showHideMenu);
  },
  beforeDestroy () {
   window.removeEventListener('click', this.showHideMenu);
  },
  watch: {
   'isExpand' (newValue) {
    if (newValue === false) {
     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;
 }
 .noFound {
  text-align: center;
 }
 .select-search {
  position: relative;
  z-index: 1000;
  a {
   color: #333;
   text-decoration: none;
   padding: 5px;
  }
  ul {
   list-style: none;
   padding: 6px 0;
   margin: 0;
   max-height: 200px;
   overflow-x: hidden;
   overflow-y: auto;
   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 {
   position: absolute;
   border-radius: 2px;
   background-color: #fff;
   box-sizing: border-box;
   margin: 5px 0;
   padding: 8px;
   width: 100%;
   box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
   >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;
    &:focus {
     outline: 0;
     border-color: #20a0ff;
    }
   }
  }
 }
</style>

总结

以上所述是小编给大家介绍的Vue实现typeahead组件功能(非常靠谱),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
js小技巧--自动隐藏红叉叉
Aug 13 Javascript
按给定几率进行随机抽取的js代码
Dec 28 Javascript
完美兼容各大浏览器获取HTTP_REFERER方法总结
Jun 24 Javascript
JS实现仿QQ效果的三级竖向菜单
Sep 25 Javascript
jQuery实现的鼠标经过时变宽的效果(附demo源码)
Apr 28 Javascript
Bootstrap前端开发案例一
Jun 17 Javascript
JS实现自动阅读单词(有道单词本添加功能)
Nov 14 Javascript
移动端触摸滑动插件swiper使用方法详解
Aug 11 Javascript
通过jquery toggleClass()属性制作文章段落更改背景颜色
May 21 jQuery
vue-cli3跨域配置的简单方法
Sep 06 Javascript
Vue 实现v-for循环的时候更改 class的样式名称
Jul 17 Javascript
一篇文章告诉你如何实现Vue前端分页和后端分页
Feb 18 Vue.js
vue下跨域设置的相关介绍
Aug 26 #Javascript
客户端(vue框架)与服务器(koa框架)通信及服务器跨域配置详解
Aug 26 #Javascript
微信小程序实现顶部普通选项卡效果(非swiper)
Jun 19 #Javascript
微信小程序实现顶部选项卡(swiper)
Jun 19 #Javascript
js实现本地时间同步功能
Aug 26 #Javascript
基于jQuery实现图片推拉门动画效果的两种方法
Aug 26 #jQuery
Javascript快速实现浏览器系统通知
Aug 26 #Javascript
You might like
PHP中函数内引用全局变量的方法
2008/10/20 PHP
php IP及IP段进行访问限制的代码
2008/12/17 PHP
php中函数的形参与实参的问题说明
2010/09/01 PHP
PHP中的output_buffering详细介绍
2014/09/27 PHP
PHP中实现crontab代码分享
2015/03/26 PHP
彻底搞懂PHP 变量结构体
2017/10/11 PHP
js获取键盘按键响应事件(兼容各浏览器)
2013/05/16 Javascript
JQuery调webservice实现邮箱验证(检测是否可用)
2013/05/21 Javascript
jquery验证手机号码、邮箱格式是否正确示例代码
2013/07/28 Javascript
js动态添加删除,后台取数据(示例代码)
2013/11/25 Javascript
项目中常用的JS方法整理
2015/01/30 Javascript
微信小程序 石头剪刀布实例代码
2017/01/04 Javascript
ES6新增数据结构WeakSet的用法详解
2017/08/07 Javascript
微信小程序使用input组件实现密码框功能【附源码下载】
2017/12/11 Javascript
Angular实现的简单查询天气预报功能示例
2017/12/27 Javascript
VUE2.0+ElementUI2.0表格el-table循环动态列渲染的写法详解
2018/11/30 Javascript
Python的迭代器和生成器使用实例
2015/01/14 Python
Python实现文件内容批量追加的方法示例
2017/08/29 Python
Python简单实现自动删除目录下空文件夹的方法
2017/08/29 Python
面向初学者的Python编辑器Mu
2018/10/08 Python
Python3监控windows,linux系统的CPU、硬盘、内存使用率和各个端口的开启情况详细代码实例
2020/03/18 Python
Css3+Js制作漂亮时钟(附源码)
2013/04/24 HTML / CSS
HTML5 本地存储实现购物车功能
2017/09/07 HTML / CSS
毕业自荐书
2013/12/09 职场文书
关于保护环境的建议书
2014/05/13 职场文书
村居抓节水倡议书
2014/05/19 职场文书
小学运动会报道稿
2014/10/04 职场文书
安全承诺书
2015/01/19 职场文书
房屋认购协议书
2015/01/29 职场文书
自主招生专家推荐信
2015/03/26 职场文书
保护动物的宣传语
2015/07/13 职场文书
酒店宣传语大全
2015/07/13 职场文书
如何写新闻稿
2015/07/18 职场文书
python 统计代码耗时的几种方法分享
2021/04/02 Python
Redis Cluster 集群搭建你会吗
2021/08/04 Redis
使用JS前端技术实现静态图片局部流动效果
2022/08/05 Javascript