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 相关文章推荐
javascript网页关闭时提醒效果脚本
Oct 22 Javascript
Textarea与懒惰渲染实现代码
Jan 04 Javascript
jQuery操作Select的Option上下移动及移除添加等等
Nov 18 Javascript
js与jQuery 获取父窗、子窗的iframe
Dec 20 Javascript
JQuery中dataGrid设置行的高度示例代码
Jan 03 Javascript
JavaScipt选取文档元素的方法(推荐)
Aug 05 Javascript
又一款js时钟!transform实现时钟效果
Aug 15 Javascript
jQuery生成假加载动画效果
Dec 01 Javascript
vscode中vue-cli项目es-lint的配置方法
Jul 30 Javascript
JavaScript 中 JSON.parse 函数 和 JSON.stringify 函数
Dec 05 Javascript
React+Redux实现简单的待办事项列表ToDoList
Sep 29 Javascript
在vue中实现某一些路由页面隐藏导航栏的功能操作
Sep 21 Javascript
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程序中的常见漏洞进行攻击(上)
2006/10/09 PHP
php处理斐波那契数列非递归方法
2012/02/04 PHP
PHP中设置时区方法小结
2012/06/03 PHP
php setcookie函数的参数说明及其用法
2014/04/20 PHP
如何使用php实现评委评分器
2015/07/31 PHP
PHP实现爬虫爬取图片代码实例
2021/03/03 PHP
javascript学习基础笔记之DOM对象操作
2011/11/03 Javascript
JavaScript对象属性检查、增加、删除、访问操作实例
2015/07/08 Javascript
javascript实现的闭包简单实例
2015/07/17 Javascript
谈谈JavaScript中function多重理解
2015/08/28 Javascript
Node.js模块封装及使用方法
2016/03/06 Javascript
基于javascript实现文字无缝滚动效果
2016/03/22 Javascript
jQuery过滤特殊字符及JS字符串转为数字
2016/05/26 Javascript
微信小程序 生命周期函数详解
2017/05/24 Javascript
Angular利用trackBy提升性能的方法
2018/01/26 Javascript
js实现动态改变radio状态的方法
2018/02/28 Javascript
详解使用element-ui table组件的筛选功能的一个小坑
2018/11/02 Javascript
qrcode生成二维码微信长按无法识别问题的解决
2019/04/04 Javascript
nodejs读取图片返回给浏览器显示
2019/07/25 NodeJs
详解ES6 扩展运算符的使用与注意事项
2020/11/12 Javascript
JavaScript 防盗链的原理以及破解方法
2020/12/29 Javascript
vue如何使用rem适配
2021/02/06 Vue.js
跟老齐学Python之有容乃大的list(2)
2014/09/15 Python
windows下 兼容Python2和Python3的解决方法
2018/12/05 Python
django drf框架中的user验证以及JWT拓展的介绍
2019/08/12 Python
Pytorch 多维数组运算过程的索引处理方式
2019/12/27 Python
解决pycharm同一目录下无法import其他文件
2020/02/12 Python
Python tcp传输代码实例解析
2020/03/18 Python
基于python实现删除指定文件类型
2020/07/21 Python
澳大利亚小众服装品牌:Maurie & Eve
2018/03/27 全球购物
简历里的自我评价
2014/01/31 职场文书
公务员四风问题对照检查材料整改措施
2014/09/26 职场文书
班级光棍节联谊会策划书
2014/10/10 职场文书
2014年党风廉政建设工作总结
2014/11/19 职场文书
茶楼服务员岗位职责
2015/02/09 职场文书
关于使用Redisson订阅数问题
2022/01/18 Redis