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

 前言

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

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

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

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

效果图

实现的功能

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组件功能(非常靠谱),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • Bootstrap学习系列之使用 Bootstrap Typeahead 组件实现百度下拉效果

    UnderScore官网:http://underscorejs.org/ 参考文档:http://www.css88.com/doc/underscore/ 页面代码: @{ ViewBag.Title = "Index"; } <script src="Scripts/bootstrap-typeahead.js"></script> <script src="Scripts/underscore-min.js"

  • 使用Bootstrap typeahead插件实现搜索框自动补全的方法

    这就是贴代码的坏处之一:搜索框快被网友玩儿坏了!!!有故意输入空格的,有输入or 1=1的,有alert的,有html乱入的.......而且好像还在玩儿,随他们去吧,只要开心就好. 在项目中,经常会用到输入框的自动补全功能,就像百度.淘宝等搜索框一样:当用户输入首字母.关键词时,后台会迅速将与此相关的条目返回并显示到前台,以便用户选择,提升用户体验.当然本项目的补全功能和这些大厂的技术是没有可比性的,但用于站内搜索也是绰绰有余了. 接触到的自动补全插件主要有两个:autocomplete和ty

  • Bootstrap3使用typeahead插件实现自动补全功能

    很酷的一个自动补全插件 http://twitter.github.io/typeahead.js 在bootstrap中使用typeahead插件,完成自动补全 相关的文档:https://github.com/twitter/typeahead.js/blob/master/doc/jquery_typeahead.md 数据源: Local:数组 prefectch:json remote等方式 -----------------------------------------------

  • BootStrap Typeahead自动补全插件实例代码

    关键代码如下所示: $('#Sale').typeahead({ ajax: { url: '@Url.Action("../Contract/GetSale")', //timeout: 300, method: 'post', triggerLength: 1, loadingClass: null, preProcess: function (result) { return result; } }, display: "Value", val: "

  • 使用bootstrap typeahead插件实现输入框自动补全之问题及解决办法

    根据网上查找到的 typeahead使用方法,到最后一步时就出错,数据能从数据库读取出来,但在输入框显示提示时,全都显为:underfined.捉摸了半天都发现不了问题出在哪儿.后来在http://blog.64cm.com/post/2014/08/13/%E4%BD%BF%E7%94%A8bootstrap-typeahead%E6%8F%92%E4%BB%B6 上不经意发现这么一句话:"在当前版本的typeahead中,已经不再支持在source属性中直接调用ajax方法获取数据源了.&q

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

     前言 之前那个typeahead写的太早,不满足当前的业务需求. 而且有些瑕疵,还有也不方便传入数据和响应数据.. 于是就推倒了重来,写了个V2的版本 看图,多了一些细节的考虑;精简了实现的逻辑代码 效果图 实现的功能 1: 鼠标点击下拉框之外的区域关闭下拉框 2: 支持键盘上下键选择,支持鼠标选择 3: 支持列表过滤搜索 4: 支持外部传入列表JSON格式的映射 5: 支持placeholder的传入 6: 选中对象的响应(.sync vue2.3的组件通讯的语法糖) 7: 箭头icon的映

  • vxe-table vue table 表格组件功能

    一个功能更加强大的 Vue 表格组件 查看vxe-table 功能点 •基础 •尺寸 •斑马线条纹 •带边框 •单元格样式 •列宽拖动 •流体高度 •固定表头 •固定列 •固定表头和列 •表头分组 •序号 •单选 •多选 •排序 •筛选 •合并行或列 •表尾合计 •导出 CSV •显示/隐藏列 •加载中 •格式化内容 •自定义模板 •快捷菜单 •滚动渲染 •展开行 •树形表格 •可编辑表格 •数据校验 •全键盘操作 •Excel 表格 例子 <template> <div> <

  • VUE实现日历组件功能

    哈哈, 就在昨天笔者刚刚在Github 上发布了一个基于VUE的日历组件.过去做日历都是需要引用 jquery moment 引用 fullCalendar.js 的.几者加起来体积庞大不说,也并不是很好使用在vue这种数据驱动的项目里.所以笔者经过一周的拍脑袋,做了一个十分简陋的版本. 简介 目前只支持月视图,该组件是 .vue 文件的形式.所以,大家在使用的时候 是需要node的咯~~~ 安装 npm install vue-fullcalendar DEMO 针对这个组件, 本人做了一个十

  • Vue.js仿微信聊天窗口展示组件功能

    源码:https://github.com/doterlin/vue-wxChat 演示地址:https://doterlin.github.io/vue-wxChat/ 运行 # install dependencies npm install # serve with hot reload at localhost:8080 npm run dev # build for production with minification npm run build 介绍 支持文本和图片的展示(后续将

  • 基于Vue的移动端图片裁剪组件功能

    最近项目上要做一个车牌识别的功能.本来以为很简单,只需要将图片扔给后台就可以了,但是经测试后识别率只有20-40%.因此产品建议拍摄图片后,可以对图片进行拖拽和缩放,然后裁剪车牌部分上传给后台来提高识别率.刚开始的话还是百度了一下看看有没有现成的组件,但是找来找去都没有找到一个合适的,还好这个功能不是很着急,因此自己周末就在家里研究一下. Demo地址:https://vivialex.github.io/demo/imageClipper/index.html 下载地址:https://git

  • vue裁切预览组件功能的实现步骤

    vue版本裁切工具,包含预览功能 最终效果: qiuyaofan.github.io/vue-crop-de- 源码地址: github.com/qiuyaofan/v- 第一步:先用vue-cli安装脚手架(不会安装的看 vue-cli官网) // 初始化vue-cli vue init webpack my-plugin 第二步:创建文件 新建src/views/validSlideDemo.vue, src/components里新建VueCrop/index.js,VueCrop.vue

  • vue组件定义,全局、局部组件,配合模板及动态组件功能示例

    本文实例讲述了vue组件定义,全局.局部组件,配合模板及动态组件功能.分享给大家供大家参考,具体如下: 一.定义一个组件 定义一个组件: 1. 全局组件 var Aaa=Vue.extend({ template:'<h3>我是标题3</h3>' }); Vue.component('aaa',Aaa); *组件里面放数据: data必须是函数的形式,函数必须返回一个对象(json) 2. 局部组件 放到某个组件内部 var vm=new Vue({ el:'#box', data

  • Vue中添加手机验证码组件功能操作方法

    什么是组件: 组件是Vue.js最强大的功能之一.组件可以扩展HTML元素,封装可重用的代码.在较高层面上,组件是自定义的元素,Vue.js的编译器为它添加特殊功能.在有些情况下,组件也可以是原生HTML元素的形式,以is特性扩展. 写在前面: 今天要实现的功能是在 完善个人信息页面(vue)中添加手机验证码组件,当用户点击 手机选项时,弹出获取验证码组件,完成验证手机的功能: 这里考虑到功能的复用,我把当前弹出手机验证码的操作放在了单独的组件中: <template > <div>

  • 基于cropper.js封装vue实现在线图片裁剪组件功能

    效果图如下所示, github:demo下载 cropper.js github:cropper.js 官网(demo) cropper.js 安装 npm或bower安装 npm install cropper # or bower install cropper clone下载:下载地址 git clone https://github.com/fengyuanchen/cropper.git 引用cropper.js 主要引用cropper.js跟cropper.css两个文件 <scri

  • vue实现按需加载组件及异步组件功能

    说实话,我一开始也不知道什么叫按需加载组件,组件还可以按需加载???后来知道了 学不完啊...没关系,看我的 按需加载组件,或者异步组件,主要是应用了component的 is 属性 template中的代码: 这里的每一个按钮,都要显示不同的组件,所以我让他们使用了同一个方法名 <template slot-scope="scope"> <el-button type="text" size="mini" @click=&qu

随机推荐