用vue实现一个搜索框组件

一、需求描述

实现一个搜索框:
1、输入关键字,按键抬起后可以实现查询功能;
2、下拉列表展示搜索结果,搜索结果中关键字要用特殊颜色突出显示,默认选中搜索结果的第一项;
3、可以按上下键切换选中项,按回车输出选中项;
4、单击组件之外的地方可以收起下拉列表;

二、分析

用到的插件 pinyin-match
支持拼音全拼、简拼和汉字模糊匹配。
调用match方法,匹配成功返回数组([匹配到第一个字符的下标,匹配到最后一个字符的下标]),失败返回false。

三、代码实现

<template>
  <div class="search-wrapper" @click.stop>
    <div class="search-input" :class="{'is-focus':isFocus}">
      <!-- 搜索输入框 -->
      <input type="text"
            autocomplete="off"
            autocorrect="off"
            autocapitalize="off"
            autofocus
            v-model="value"
            ref='searchInput'
            spellcheck="false"
            placeholder="搜索..."
            @click="changeInput"
            @keyup="clickSearch"
            @keydown.down.prevent="navigateOptions('next')"
            @keydown.up.prevent="navigateOptions('prev')">

      <!-- 搜索放大镜图标 -->
      <div class="search-icons">
        <i class="el-icon-close" v-show="value" @click="clearSearchValue"></i>    
        <i class="el-icon-search" v-show="!value" @click="clickSearch"></i>
      </div>      
    </div>
    <!-- 搜索下拉列表 -->
    <div class="search-list">
      <div class="search-result" ref="ul">
        <ul v-for="item in result">            
          <li class="select-li" :class="{'is-selected': item.isSelected}" @click="clickResultItem(item)">
            <img class="user-avatar" src="../../images/avatar.png">
            <div class="user-name">
              <span>{{item.keyword[0]}}<span class="keyword">{{item.keyword[1]}}</span>{{item.keyword[2]}}</span>                        
            </div>
          </li>
        </ul>

        <!-- 查询结果为空时候 -->
        <div class="result-null" v-if="value && result.length == 0">
           没有结果 
        </div>         
      </div>
    </div>
  </div>
</template>
<script>
const PinyinMatch = require('pinyin-match');
export default {
  name: 'SearchInput',
  data () {
    return {
      isFocus: false,//搜索框是否获取了焦点
      value: '',//输入的搜索关键字
      result : [],//全部搜索结果
      curIndex: 0,//选中的搜索结果的下标
      selectItem: {},//选中的搜索结果
      userList: [
        {name:'安其拉',id:'a1'},
        {name:'安琪',id:'a2'},
        {name:'陈仙仙', id:'c1' },
        {name:'成小龙', id:'c2' },
        {name:'程野', id:'c3' },
        {name:'丁小明', id:'d1' },
        {name:'丁小龙', id:'d2' },
        {name:'丁小野', id:'d3' },
        {name:'冯小明', id:'f1' },
        {name:'冯小龙', id:'f2' },
        {name:'冯小野', id:'f3' },
        {name:'高小明', id:'g1' },
        {name:'高小龙', id:'g2' },
        {name:'高小野', id:'g3' },
        {name:'高小阳', id:'g4' },
        {name:'郭小名', id:'g5' },
        {name:'黄小明', id:'h1' },
        {name:'黄小龙', id:'h2' },
        {name:'黄小野', id:'h3' },
        {name:'郝小阳', id:'h4' },
        {name:'郝小名', id:'h5' },
        {name:'李小明',id:'l1'},
        {name:'李小龙',id:'l2'},
        {name:'李小野',id:'l3'},
        {name:'李小阳',id:'l4'},
        {name:'李小一',id:'l5'},
        {name:'李小二',id:'l6'},
        {name:'李小三',id:'l7'},
        {name:'李小四',id:'l8'},
        {name:'李小五',id:'l9'},
      ] 
    }
  },
  mounted () {   
    let that = this;   
    document.body.addEventListener('click', () => {//单击组件之外时收起下拉列表
      that.isFocus = false;
      that.value = "";
    }, false);
  },
  methods: {
    changeInput() {//单击搜索框时获取焦点
      this.isFocus = true;  
    },
    clearSearchValue () {//清空输入的查询条件
      if (this.value) {
        this.value = "";//搜索关键字
        this.result = [];//全部搜索结果
        this.curIndex = 0;//选中的搜索结果的下标
        this.selectItem = {};//选中的搜索结果
        this.$refs.searchInput.focus();//输入查询条件清空后获取焦点
      }
    },
    clickSearch () {//查询
      if (event.code == "ArrowDown" || event.code == "ArrowUp") {//上下键
        return;
      }       
      if (event.code == "Enter" && this.selectItem) {
        this.clickResultItem(this.selectItem);//有搜索结果时按下enter直接选中第一项
        return;
      }
      this.result = [];//全部搜索结果
      this.curIndex = 0;//选中的搜索结果的下标
      this.selectItem = {};//选中的搜索结果
      if (this.value.trim()) {                                 
        for (let i = 0; i < this.userList.length; i++) {//根据所有人员名字匹配
          let name = this.userList[i].name;
          let search = PinyinMatch.match(name, this.value);          
          if (search && search.length > 0) {               
            this.userList[i].keyword = this.getKeyWord(name, search);
            this.userList[i].isSelected = false;            
            this.result.push(this.userList[i]);         
          }
        }
        if (this.result && this.result[0]) {
          this.result[0].isSelected = true;
          this.curIndex = 0;//选中的搜索结果的下标
          this.selectItem = this.result[0];//选中的搜索结果
        }       
      } else {
        this.$refs.searchInput.focus();//没有输入查询条件焦点不应该失去
      }
    },
    getKeyWord (name, search) {//关键字
      let keyword = [];
      keyword[0] = name.substring(0, search[0]);
      if (search[0] == search[1]) {
        keyword[1] = name.charAt(search[0]);//匹配到的作为关键字
        keyword[2] = name.substring(search[0] + 1);
      } else {
        keyword[1] = name.substring(search[0], search[1] + 1);//匹配到的作为关键字
        keyword[2] = name.substring(search[1] + 1);
      }
      return keyword;
    },             
    clickResultItem (data) {//单击下拉列表中的选项    
      alert('您选择了' + data.name);
    },
    navigateOptions (direction) { //搜索结果上下键选择
      let resultLength = this.result.length;
      if (resultLength == 0 || resultLength == 1 ) {
        return;
      }
      let lastIndex = this.curIndex;
      if (direction === 'next') {//向下      
        this.curIndex++;   
        if (this.curIndex === resultLength) {
          this.curIndex = 0;
        }       
      }
      if (direction === 'prev') {//向上
        this.curIndex--;
        if (this.curIndex < 0) {        
          this.curIndex = resultLength - 1;
        }
      }
      if (lastIndex < resultLength) {        
        this.result[lastIndex].isSelected = false;
      }
      if (-1 < this.curIndex < resultLength) {
        this.selectItem = this.result[this.curIndex];
        this.selectItem.isSelected = true;    

        // this.result[this.curIndex].isSelected = true; 
        this.$set(this.result,this.curIndex,this.selectItem)     
      }
      this.resetScrollTop();
    },
    resetScrollTop () { //设置滚动条的位置
      let maxShowCount = this.$refs.ul.clientHeight / 50;
      let diff = this.curIndex - maxShowCount;       
      diff += 4;      
      this.$refs.ul.scrollTop = diff * (50) + 60;
    },
  }
}
</script>
<style src="./index.scss" lang="scss"></style>

index.scss

.search-wrapper {
  background-color: rgb(235, 148, 148);
  height: 580px;
  width:325px; 
  overflow: hidden;
  border-radius: 3px;
  font-family: 'Microsoft YaHei';
  box-sizing: border-box;
  padding: 10px;
  .search-input{
    position: relative;
    input {
      width: 100%;
      height: 35px;
      box-sizing: border-box;
      border: 0;
      border-radius: 3px;
      margin: 0;
      padding: 0px 40px 0px 10px;
      outline: 0;
      font-size: 14px;
      color: #E5EAEE;
      // background-color: #209df7;
      background-color: #fff;
    }
    input::-webkit-input-placeholder {
      color: #E5EAEE;
    }
    .search-icons {
      position: absolute;
      top: 7px;
      right: 10px;
      color: #209df7;
      font-size: 17px;
    }
  }
  .is-focus {
    overflow: visible !important;
    input {
      color: #222 !important;
      // border: 1px solid #fff !important;
      // background-color: #fff !important;
      box-shadow: inset 0 1px 3px rgba(0, 0, 0, .2), 0 1px 15px hsla(0, 0%, 100%, .3) !important;
    }
    .search-icons {
      .el-icon-close{
        color: #111;
        background-color: #d8d8d8;
        border-radius: 50%;
        padding: 2px;
        box-sizing: border-box;
        font-size: 15px;
        cursor: pointer;
      }
      .el-icon-close:hover{
        color: #fff;
        background-color: #209df7;
      }
    }
  }
  .search-list{
    height: 480px;
    margin-top: 8px;
    border: 0 none;    
    border-radius: 3px;
    box-shadow: 0 0 6px rgba(0, 0, 0, .15);   
    background-color: #fff;    
    .search-result {
      height: 100%;
      overflow-x: hidden;
      overflow-y: auto;
      ul, li{
        margin: 0px;
        padding: 0px;
      }
      li{
        list-style-type: none;
      }
      .select-li {
        cursor: pointer;
        padding: 8px 10px;
        overflow: hidden;
        border-bottom: 1px solid #ebebeb;
        .user-avatar {
          border-radius: 50%;
          float: left;
          width: 40px;
          height: 40px;
          background-size: cover;
        }
        .user-name {
          width: 200px;
          float: left;
          margin-left: 15px;
          line-height: 40px;
          .keyword {
            color: #008cee;
          }
        } 
      }
      .select-li:hover{
        background-color: #e5f0fa;
      }
      .is-selected {
        background-color: rgba(140, 197, 255, 0.46)
      }
    }
    .result-null {
      margin-top: 100px;
      text-align: center;
      font-size: 20px;
    }
  }
}

::-webkit-scrollbar {
  width: 6px;
  height: 6px;
}
/*滑动轨道*/     
::-webkit-scrollbar-track-piece {
  background-color: transparent;
  -webkit-border-radius: 6px;
}  
// 下面是滑块
/*竖向滚动条*/      
::-webkit-scrollbar-thumb:vertical {
  height: 5px;
  background-color: rgb(161, 161, 161);
  -webkit-border-radius: 6px;
}
/*横向滚动条*/          
::-webkit-scrollbar-thumb:horizontal {
  width: 5px;
  background-color: rgb(143, 144, 145);
  -webkit-border-radius: 6px;
}

三、效果

在这里插入图片描述
以上就实现了一个简单的搜索框。

下面对搜索过程进行简单的优化:

在data中添加 timer:null
将搜索过程放入 setTimeout 函数中,按键抬起半秒后再去进行搜索。

    clickSearch () {//查询
      if (event.code == "ArrowDown" || event.code == "ArrowUp") {//上下键
        return;
      }       
      if (event.code == "Enter" && this.selectItem) {
        this.clickResultItem(this.selectItem);//有搜索结果时按下enter直接选中第一项
        return;
      }
      this.result = [];//全部搜索结果
      this.curIndex = 0;//选中的搜索结果的下标
      this.selectItem = {};//选中的搜索结果
      if (this.value.trim()) {     
        clearTimeout(this.timer);
        this.timer = setTimeout(function () {
	        for (let i = 0; i < this.userList.length; i++) {//根据所有人员名字匹配
	          let name = this.userList[i].name;
	          let search = PinyinMatch.match(name, this.value);          
	          if (search && search.length > 0) {               
	            this.userList[i].keyword = this.getKeyWord(name, search);
	            this.userList[i].isSelected = false;            
	            this.result.push(this.userList[i]);         
	          }
	        }
	        if (this.result && this.result[0]) {
	          this.result[0].isSelected = true;
	          this.curIndex = 0;//选中的搜索结果的下标
	          this.selectItem = this.result[0];//选中的搜索结果
	        }         
      	}, 500)                                        
      } else {
        this.$refs.searchInput.focus();//没有输入查询条件焦点不应该失去
      }
    },
  • 10
    点赞
  • 75
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值