全网整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:400-708-3566

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


# vue  # typeahead组件  # BootStrap Typeahead自动补全插件实例代码  # Bootstrap学习系列之使用 Bootstrap Typeahead 组件实现百度下拉效果  # Bootstrap3使用typeahead插件实现自动补全功能  # 使用bootstrap typeahead插件实现输入框自动补全之问题及解决办法  # 使用Bootstrap typeahead插件实现搜索框自动补全的方法  # 请选择  # 小编  # 鼠标点击  # 是一个  # 也不  # 下拉框  # 暂无  # 鼠标  # 在此  # 这句话  # 给大家  # 写了  # 只有一个  # 倒了  # 所述  # 重来  # 给我留言  # 看图  # 太早  # 感谢大家 


相关文章: 建站之星在线版空间:自助建站+智能模板一键生成方案  北京专业网站制作设计师招聘,北京白云观官方网站?  台州网站建设制作公司,浙江手机无犯罪记录证明怎么开?  建站之星如何助力网站排名飙升?揭秘高效技巧  如何在IIS管理器中快速创建并配置网站?  建站主机空间推荐 高性价比配置与快速部署方案解析  小建面朝正北,A点实际方位是否存在偏差?  广州网站制作的公司,现在专门做网站的公司有没有哪几家是比较好的,性价比高,模板也多的?  Android使用GridView实现日历的简单功能  如何快速建站并高效导出源代码?  英语简历制作免费网站推荐,如何将简历翻译成英文?  深圳网站制作案例,网页的相关名词有哪些?  如何用好域名打造高点击率的自主建站?  开心动漫网站制作软件下载,十分开心动画为何停播?  如何用美橙互联一键搭建多站合一网站?  如何在建站宝盒中设置产品搜索功能?  红河网站制作公司,红河事业单位身份证如何上传?  如何获取上海专业网站定制建站电话?  如何快速搭建高效简练网站?  番禺网站制作公司哪家值得合作,番禺图书馆新馆开放了吗?  学校为何禁止电信移动建设网站?  制作网站的过程怎么写,用凡科建站如何制作自己的网站?  微信网站制作公司有哪些,民生银行办理公司开户怎么在微信网页上查询进度?  建站之星如何快速更换网站模板?  建站之星代理如何获取技术支持?  微信小程序 input输入框控件详解及实例(多种示例)  唐山网站制作公司有哪些,唐山找工作哪个网站最靠谱?  如何在Windows虚拟主机上快速搭建网站?  一键制作网站软件下载安装,一键自动采集网页文档制作步骤?  香港服务器网站测试全流程:性能评估、SEO加载与移动适配优化  建站之星各版本价格是多少?  黑客如何通过漏洞一步步攻陷网站服务器?  建站之星安全性能如何?防护体系能否抵御黑客入侵?  公司网站制作费用多少,为公司建立一个网站需要哪些费用?  制作网站哪家好,cc、.co、.cm哪个域名更适合做网站?  股票网站制作软件,网上股票怎么开户?  如何在新浪SAE免费搭建个人博客?  建站VPS推荐:2025年高性能服务器配置指南  专业网站制作服务公司,有哪些网站可以免费发布招聘信息?  TestNG的testng.xml配置文件怎么写  活动邀请函制作网站有哪些,活动邀请函文案?  内部网站制作流程,如何建立公司内部网站?  如何通过cPanel快速搭建网站?  如何选择美橙互联多站合一建站方案?  如何在IIS中配置站点IP、端口及主机头?  C#如何在一个XML文件中查找并替换文本内容  深圳企业网站制作设计,在深圳如何网上全流程注册公司?  Python多线程使用规范_线程安全解析【教程】  建站OpenVZ教程与优化策略:配置指南与性能提升  家庭建站与云服务器建站,如何选择更优? 

您的项目需求

*请认真填写需求信息,我们会在24小时内与您取得联系。