# 搜索模块

# 搜索组件基本布局

目标:实现搜索组件基本布局

  • 点击搜索按钮跳转路由
<van-nav-bar fixed title="黑马头条" right-text="搜索" @click-right="$router.push('/search')" />
  • 组件基本布局
<template>
  <div>
    <!-- 导航栏 -->
    <van-nav-bar title="搜索中心" left-arrow @click-left="$router.back()" />
    <!-- 搜索框 -->
    <van-search v-model.trim="q" placeholder="请输入搜索关键词" shape="round"/>
  </div>
</template>
<script>
export default {
  name: 'Search',
  data () {
    return {
      // 搜索关键字
      q: ''
    }
  }
}
</script>

# 完善搜索列表布局

目标:完善搜索列表布局

  • 搜索历史布局
<!-- 搜索历史 -->
<div class="history-box">
  <div class="head">
    <span>历史记录</span>
    <van-icon name="delete"></van-icon>
  </div>
  <van-cell-group>
    <van-cell>
      <a class="word_btn">电脑</a>
      <van-icon class="close_btn" slot="right-icon" name="cross"/>
    </van-cell>
  </van-cell-group>
</div>
  • 搜索历史样式
.history-box {
  padding: 0 20px;
  .head{
    line-height: 36px;
    color: #999;
    .van-icon{
      font-size: 16px;
      float: right;
      margin-top: 10px;;
    }
  }
  .van-cell{
    padding: 10px 0;
  }
  .word_btn{
    color:#3296fa;
  }
  .close_btn{
    margin-top:5px;
    color: #999;
  }
}
  • 关键字联想列表
<!-- 联想列表 -->
<van-cell-group class="suggest-box">
  <van-cell icon="search"><p><span>j</span>ava</p></van-cell>
</van-cell-group>
  • 关键字联想列表样式
.suggest-box{
  /deep/ .van-cell{
    padding: 10px 20px;
    color: #999;
    p{
      span{
        color: red;
      }
    }
  }
}
  • 控制联想列表和搜索历史的切换
<!-- 联想列表 -->
<van-cell-group class="suggest-box" v-show='!q'>
  <van-cell icon="search"><p><span>j</span>ava</p></van-cell>
</van-cell-group>
<!-- 搜索历史 -->
<div class="history-box" v-show='q'>
  <div class="head">
    <span>历史记录</span>
    <van-icon name="delete"></van-icon>
  </div>
  <van-cell-group>
    <van-cell>
      <a class="word_btn">电脑</a>
      <van-icon class="close_btn" slot="right-icon" name="cross"/>
    </van-cell>
  </van-cell-group>
</div>

# 历史记录功能

目标:实现搜索历史记录功能

  • 初始化历史记录数据
const SEARCHKEY = 'hm-taotiao-search-123'
history: JSON.parse(window.localStorage.getItem(SEARCHKEY) || '[]')
  • 历史记录有数据才显示
<div class="history-box" v-show="history.length&&!q">
  • 渲染历史数据
<van-cell v-for="(item, index) in history" :key="index">
  <a @click="toSearch(key)" class="word_btn">{{item}}</a>
  <van-icon @click="delHistory(key)" class="close_btn" slot="right-icon" name="cross"/>
</van-cell>
  • 删除历史
deleteHistory (index) {
  // 点击叉号删除对应的历史关键字
  this.history.splice(index, 1)
  // 缓存也要更新
  window.localStorage.setItem(SEARCH_KEY, JSON.stringify(this.history))
},
  • 清空历史
<van-icon name="delete" @click="clearHistory()"></van-icon>
clearHistory () {
  // 清除所有的历史关键字
  this.history = []
  // 缓存也要清空
  window.localStorage.removeItem(SEARCH_KEY)
},
  • 实现搜索功能
<van-search v-model.trim="q" placeholder="请输入搜索关键词" shape="round" @search="onSearch" />
onSearch (key) {
    // 把搜索的历史关键字进行存储(把新输入的关键字放到前面)
    this.history.unshift(this.q)
    // 对数组进行去重操作(Set结构类似数组,但是内部自动去重)
    const set = new Set(this.history)
    // 把去重之后的结果再转换为数组
    // this.history = Array.from(set)
    this.history = [...set]
    // 把数据保存到缓存
    localStorage.setItem(SEARCHKEY, JSON.stringify(this.history))
    // 跳转搜索结果
    // this.$router.push('/result?kw=' + this.q)
    this.$router.push('/result/' + this.q)
},

# 联想搜索功能

目标:实现联想搜索功能

  • 封装搜索接口方法
export const suggest = (q) => {
  return request({
    method: 'get',
    url: 'app/v1_0/suggestion',
    params: {
      q
    }
  })
}
  • 监听关键字变化
searchList () {
  // 根据输入的关键字查询匹配的数据列表
  if (!this.q) return
  try {
    window.clearTimeout(this.timer)
    this.timer = window.setTimeout(async () => {
      const ret = await suggest(this.q)
      this.list = ret.data.options
    }, 300)
  } catch (e) {
    console.log(e)
    this.$toast.fail('查询失败!')
  }
},
  • 渲染搜索列表结果
<van-cell :key='index' v-for='(item, index) in list' icon="search">
  <p v-html='item'></p>
</van-cell>
  • 实现搜索页面的跳转
    • 回车后跳转,参数为输入的关键字
    • 点击联想的列表跳转,参数为列表项内容
<van-cell :key='index' v-for='(item, index) in list' icon="search">
   <p @click='handleJump(item)' v-html='item'></p>
</van-cell>
handleJump (item) {
    // 需要在跳转之前,把列表项内容还原回原始数据
    const reg = new RegExp('<span>' + this.q + '</span>', 'ig')
    this.$router.push('/result/' + item.replace(reg, this.q))
},

# 搜索结果-基本布局

目标:实现搜索列表页面基本布局

  • 搜索列表布局
<div class="container">
  <!-- 导航固定定位 fixed -->
  <van-nav-bar fixed title="搜索结果" left-arrow @click-left="$router.back()" />
  <!-- 文章列表 -->
  <van-list v-model="loading" :finished="finished" finished-text="没有更多了">
    <van-cell-group>
      <van-cell>
        <div class="article_item">
          <h3 class="van-ellipsis">PullRefresh下拉刷新PullRefresh下拉刷新下拉刷新下拉刷新</h3>
          <div class="img_box">
            <van-image class="w33" fit="cover" src="https://img.yzcdn.cn/vant/cat.jpeg" />
            <van-image class="w33" fit="cover" src="https://img.yzcdn.cn/vant/cat.jpeg" />
            <van-image class="w33" fit="cover" src="https://img.yzcdn.cn/vant/cat.jpeg" />
          </div>
          <div class="img_box">
            <van-image class="w100" fit="cover" src="https://img.yzcdn.cn/vant/cat.jpeg" />
          </div>
          <div class="info_box">
            <span>你像一阵风</span>
            <span>8评论</span>
            <span>10分钟前</span>
          </div>
        </div>
      </van-cell>
    </van-cell-group>
  </van-list>
</div>
  • 搜索结果列表样式
.container {
  padding-top: 46px;
  height: 100%;
  overflow-y: auto;
  box-sizing: border-box;
}
.article_item {
  h3 {
    font-weight: normal;
    line-height: 2;
  }
  .img_box {
    display: flex;
    justify-content: space-between;
    .w33 {
      width: 33%;
      height: 90px;
    }
    .w100 {
      height: 180px;
      width: 100%;
    }
  }
  .info_box {
    color: #999;
    line-height: 2;
    position: relative;
    span {
      padding-right: 10px;
    }
  }
}

# 搜索结果-上拉功能

目标:实现搜索上拉功能

  • 封装上拉加载接口
// 根据关键字搜索文章列表
export const searchArticles = (options) => {
  return request({
    method: 'get',
    url: 'app/v1_0/search',
    params: {
      // 当前的页码
      page: options.page,
      // 每页的条数
      per_page: options.perPage,
      // 搜索的关键字
      q: options.q
    }
  })
}

  • 列表相关数据
import { searchArticles } from '@/api/channel.js'

export default {
  name: 'Result',
  props: ['kw'],
  data () {
    return {
      // 单次加载数据的状态
      loading: false,
      // 所有数据加载完成的标准
      finished: false,
      // 加载的数据列表
      list: [],
      // 查询参数
      filterParams: {
        page: 1,
        per_page: 10,
        q: ''
      },
      // 列表总数
      total: 0
    }
  },
  methods: {
    async onLoad () {
      // 分页加载数据
      try {
        const ret = await searchArticles(this.filterParams)
        this.list.push(...ret.data.results)
        this.total = ret.data.total_count
        this.loading = false
        // 加载完成一页数据后,页码需要累加
        this.filterParams.page += 1
        // 判断列表加载完成的标志
        if (this.list.length >= this.total) {
          // 没有更多数据了
          this.finished = true
        }
      } catch (e) {
        console.log(e)
        this.$toast.fail('加载数据失败!')
      }
    }
  },
  created () {
    // console.log(this.$route.query.kw)
    this.filterParams.q = this.kw
  }
}
  • 渲染列表内容
<!-- 文章列表 -->
<van-list @load="onLoad" v-model="loading" :finished="finished" finished-text="没有更多了">
  <van-cell-group>
    <van-cell :to="{name:'article',params:{id:item.art_id.toString()}}" v-for="item in list" :key="item.art_id.toString()">
      <div class="article_item">
        <h3 class="van-ellipsis">{{item.title}}</h3>
        <div class="img_box" v-if="item.cover.type===3">
          <van-image class="w33" fit="cover" :src="item.cover.images[0]" />
          <van-image class="w33" fit="cover" :src="item.cover.images[1]" />
          <van-image class="w33" fit="cover" :src="item.cover.images[2]" />
        </div>
        <div class="img_box" v-if="item.cover.type===1">
          <van-image class="w100" fit="cover" :src="item.cover.images[0]" />
        </div>
        <div class="info_box">
          <span>{{item.aut_name}}</span>
          <span>{{item.comm_count}}评论</span>
          <span>{{item.pubdate|relativeTime}}</span>
        </div>
      </div>
    </van-cell>
  </van-cell-group>
</van-list>