欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Vue2.0组件实现动态搜索引擎(二)

程序员文章站 2022-04-29 08:53:28
...
接上一篇,完成logo部分我们就要开始整个项目的核心——panel组件。

整个panel组件分为3个部分:关键字输入框,相关搜索建议下拉列表和搜索跳转按钮。

由于我们整个项目是设计成父子组件的架构所以在父组件即panel部分开头需要引入子组件:

HTML:

<logo-select @logoNow="changeSource"></logo-select>


JS:

import logoSelect from './logo-new.vue';

export default {
  components: {
    logoSelect //等价于logoSelect: logoSelect
  }


注意使用相对路径不要照搬。


目前的效果如下。

Vue2.0组件实现动态搜索引擎(二)


接下来做我们的搜索框,这一步比较简单,设置一个input再用v-model做一个双向绑定到data:

<input type="text" v-model="msgInput">
data: function () {
      return {
        msgInput: '',
        ...
      }
}

顺便加上一个“X”span,方便清空输入栏:

<span class="markDelete" @click="clearInput">x</span>


methods: {
      clearInput: function () {
          this.msgInput = '',//清空输入框
          this.results = ''//清空搜索建议数组
      },
      ...
}

这里比较偷懒用了个X字符代替清除按钮,对样式有追求的童鞋可以找个美观的图片,其他不变。


接下来就是核心功能:如何输入关键字就能自动弹出下拉菜单并且给出搜索建议?

由于我们要做的动态搜索建议,必然要使用其他搜索引擎的接口,那么跨域就成了主要技术难点。

翻阅了相关资料,使用jsonp跨域最为方便,Vue使用jsonp就要考虑vue-resource;


如果是第一次使用就要命令行安装:

npm install vue-resource --save


先在main.js引入vue-resource:

var vueResource = require('vue-resource');
Vue.use(vueResource);

然后我们在data里新建一个空数组以便将来存放jsonp获取到的搜索建议,之后便接着input标签写一个ul列表循环数组里的搜索结果。

 

<ul>
    <li v-for="(item, index) in results">
        {{ item }}
    </li>
</ul>

给input设置一个键盘监听事件,触发jsonp:

@keyup="get($event)"

methods: {
      get: function (ev) {
	//左右方向键不触发
        if(ev.keyCode === 38 || ev.keyCode === 40) {
          return;
        }
        //利用jsonp跨域
        this.$http.jsonp('https://sug.so.360.cn/suggest?word=' + this.msgInput
          + '&encodein=utf-8&encodeout=utf-8')
          .then(function(res) {//成功后执行函数
            this.results = res.data.s//将搜索建议赋给预先准备好的数组
          }
        )
      }
}


如此一来,基本的跨域功能就实现了,下一步是解决父子间通信的问题。

子组件向父组件传递信息一般是子组件$emit一个参数,父组件监听这个参数,再执行相应函数:


<logo-select @logoNow="changeSource"></logo-select>


      //监听的子组件函数不用写参数,但调用的函数需要加
      changeSource: function (index) {
        this.logoIndex = index
      }


如此一旦logo改变panel就能够得到logo的index,可以改变回车搜索的链接url:

logoData: [
          {
            name: '360搜索',
            url: 'https://www.so.com/s?ie=utf-8&fr=none&src=360sou_newhome&q='
          },
          {
            name: '百度搜索',
            url: 'https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=0&rsv_idx=1&tn=baidu&wd='
          },
          {
            name: '搜狗搜索',
            url: 'https://www.sogou.com/web?query='
          }


search: function () {
        window.open(this.logoData[this.logoIndex].url + this.msgInput)//进行字符串拼接url和搜索关键字
      }


最后还有一个要点就是模仿主流搜索引擎的上下键选取搜索建议到输入框的功能。

@keydown.up.prevent="selectUp()" @keydown.down="selectDown()" @keydown.enter="search()"//prevent可以阻止input默认事件光标回到最前

selectDown: function () {
        this.now++;//data中设置一个变量记录当前选中的index
        if(this.now == this.results.length) {//到达最下回到顶部
          this.now = 0;
        }
        this.msgInput = this.results[this.now];//改变输入框内容
      },
      selectUp: function () {
        this.now--;
        if(this.now == -1) {//到达最上回到底部
          this.now = this.results.length - 1;
        }
        this.msgInput = this.results[this.now];
      }


上面代码的核心原理跟之前的logo组件部分类似,也是先设置选中背景色,再通过对应index控制输入框的当前内容。


最后贴出完整代码:

main.js:

import Vue from 'vue';
import App from './App1.vue';
import VueRouter from 'vue-router';

var vueResource = require('vue-resource');
Vue.use(vueResource);
Vue.use(VueRouter);

new Vue({
  el: '#app',
  render: h => h(App)
})


App1.vue:

<template>
  <div id="app">
      <panel></panel>
  </div>
</template>

<script>
  import panel from './components/panel-new.vue'

  export default {
    components: {
        panel
    }
  }
</script>


logo-new.vue:

<template>
  <div class="logo">
    <img class="logoNow" :src="imgs[imgState].imgSrc" @click="toggle">
    <div class="triangle" @click="toggle">
      <span></span>
    </div>
    <div class="logoMain">
      <transition name="fade">
        <ul v-show="toggleState" class="listLogo" @mouseleave="leaveList">
          <li v-for="(item, index) in imgs" :class=" index == imgSelected ? 'colorBack' : ''" @click="changeImg(index)"
              @mouseover="changeBackColor(index)">
            <img :src="item.imgSrc">
          </li>
        </ul>
      </transition>
    </div>
  </div>
</template>

<script>
  export default {
    data () {
      return {
        //下拉图片背景初始值
        imgSelected: -1,
        //判断下拉条件
        toggleState: false,
        //界面显示哪张图片
        imgState: 0,
        //图片一类的静态文件,应该放在这个static文件夹下,
        // 这个文件夹下的文件(夹)会按照原本的结构放在网站根目录下
        imgs: [{
          imgSrc: ('.././static/360_logo.png')
        }, {
          imgSrc: ('.././static/baidu_logo.png')
        }, {
          imgSrc: ('.././static/sougou_logo.png')
        }]
      }
    },
    methods: {
      toggle: function () {
        this.toggleState = !this.toggleState,
        //清空上次背景色,
        this.imgSelected = -1
      },
      changeImg: function (index) {
        this.toggleState = !this.toggleState,
        this.imgState = index,
        this.$emit('logoNow', [index])
      },
      changeBackColor: function (index) {
        this.imgSelected = index
      },
      leaveList: function () {
          this.toggleState = false,
          this.imgSelected = -1
      }
    }
  }
</script>


panel-new.vue:

<template>
  <div>
    <logo-select @logoNow="changeSource"></logo-select>
    <div class="inputSelect">
      <input type="text" v-model="msgInput" @keyup="get($event)" @keydown.up.prevent="selectUp()"
             @keydown.down="selectDown()" @keydown.enter="search()">
      <span class="markDelete" @click="clearInput">x</span>
      <button @click="search">搜一下</button>
    </div>
    <div class="inputToggle">
      <!--多个元素/组件的过渡效果用transition-group,每个 <transition-group> 的子节点必须有 独立的key ,动画才能正常工作-->
      <transition-group tag="ul" name="fade" class="ulResult" mode="out-in" v-cloak>
        <li v-for="(item, index) in results" class="liStyle" :class="{selectback:index==now}"
            @mouseover="changeBack(index)" @click="msgChange(index)" :key="item">
          {{ item }}
        </li>
      </transition-group>
    </div>
  </div>
</template>

<script type="text/javascript">
  import logoSelect from './logo-new.vue';

  export default {
    components: {
      logoSelect
    },
    data: function () {
      return {
        msgInput: '',
        results:[],
        now: -1,
        logoIndex: 0,
        logoData: [
          {
            name: '360搜索',
            url: 'https://www.so.com/s?ie=utf-8&fr=none&src=360sou_newhome&q='
          },
          {
            name: '百度搜索',
            url: 'https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=0&rsv_idx=1&tn=baidu&wd='
          },
          {
            name: '搜狗搜索',
            url: 'https://www.sogou.com/web?query='
          }
        ]
      }
    },
    methods: {
      clearInput: function () {
          this.msgInput = '',
          this.results = ''
      },
      get: function (ev) {
        if(ev.keyCode === 38 || ev.keyCode === 40) {
          return;
        }
        //利用jsonp跨域
        this.$http.jsonp('https://sug.so.360.cn/suggest?word=' + this.msgInput
          + '&encodein=utf-8&encodeout=utf-8')
          .then(function(res) {
            this.results = res.data.s,
            this.now = -1
          }
        )
      },
      selectDown: function () {
        this.now++;
        if(this.now == this.results.length) {
          this.now = 0;
        }
        this.msgInput = this.results[this.now];
      },
      selectUp: function () {
        this.now--;
        if(this.now == -1) {
          this.now = this.results.length - 1;
        }
        this.msgInput = this.results[this.now];
      },
      changeBack: function (index) {
        this.now = index
      },
      msgChange: function (index) {
        this.msgInput = this.results[index],
        window.open(this.logoData[this.logoIndex].url + this.msgInput)
      },
      search: function () {
        window.open(this.logoData[this.logoIndex].url + this.msgInput)
      },
      //监听的子组件函数不用写参数,但调用的函数需要加
      changeSource: function (index) {
        this.logoIndex = index
      }
    }
  }
</script>


逻辑功能的代码大致就是这样了,css部分大家可以结合自己喜好自行配置。