黑马头条项目-day8-搜索模块-上拉功能、历史记录,联想搜索

历史记录功能

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

  • 初始化历史记录数据
data () {
    return {
        // 搜索关键字
        q: '',
        // 历史关键字
        history: JSON.parse(localStorage.getItem('keywords') || '[]')
    }
},
  • 历史记录有数据才显示
<div class="history-box" v-else-if="history.length">
  • 渲染历史数据
<van-cell v-for="(item, index) in history" :key="index">
  <a class="word_btn">{{item}}</a>
  <van-icon @click="delHistory(key)" class="close_btn" slot="right-icon" name="cross"/>
</van-cell>

总结:

  1. 回车后,缓存历史关键字
  2. 进入搜索组件,初始化数据时,直接读取缓存
  3. 动态渲染历史数据
  • 数组去重操作
handleSearch () {
    // 回车触发后:1、保存历史关键字;2、跳转到搜索列表页面
    this.history.unshift(this.q)
    // Set是ES6引入的一个构造函数,存储结构和数组类似,其中不允许放重复数据
    this.history = [...new Set(this.history)]
    // 把history数组放入缓存
    localStorage.setItem('keywords', JSON.stringify(this.history))
}

总结:基于ES6提供Set构造函数进行去重比较方便

注意:Set是ES6引入的一个构造函数,存储结构和数组类似,其中不允许放重复数据

  • 删除历史
deleteSingle (index) {
  // 点击叉号删除对应的历史关键字
  this.history.splice(index, 1)
  // 缓存也要更新
  window.localStorage.setItem(SEARCHKEY, JSON.stringify(this.history))
},
  • 清空历史
<van-icon name="delete" @click="deleteAll"></van-icon>
deleteAll () {
  // 清除所有的历史关键字
  this.history = []
  // 缓存也要清空
  window.localStorage.removeItem('keywords')
},

注意:删除时要删除两个部分:1、data中的数据;2、缓存中的数据

联想搜索功能

目标:实现联想搜索功能

  • 封装搜索接口方法
export const searchList = (q) => {
  return request({
    method: 'get',
    url: 'v1_0/suggestion',
    params: {
      q
    }
  })
}
<van-search @input='keywordList' 
// 联想查询
async keywordList () {
  const ret = await searchList(this.q)
  this.keylist = ret.data.options
},

总结:输入一次关键字,调用一次接口得到关键字匹配的列表数据。

  • 实现函数防抖效果debounce/throttle(节流)

函数防抖的作用:限制任务执行的频率

如果连续两次触发条件的时间间隔超过规定的时间,那么才触发一次任务,如果两次触发条件的间隔小于这个固定时间,那么始终不触发任务。

触发条件:输入一个字符

规定的时间:1秒

任务:调用接口发送请求

// 联想查询
keywordList () {
    // 如下的写法就是函数防抖
    clearTimeout(this.timer)
    if (!this.q.trim()) return
    this.timer = setTimeout(async () => {
        const ret = await searchList(this.q)
        this.keylist = ret.data.options
    }, 1000)
},

总结:

  1. 函数防抖 debounce :连续两次触发条件超过特定时间才会执行一次任务。(关键字搜索、账号重复性验证)
  2. 函数节流 throttle:在固定的时间内,无论触发多少次条件,仅仅执行一次任务。(分页动态加载)
  • 渲染搜索列表结果
<van-cell :key='index' v-for='(item, index) in keylist' icon="search">
  <p v-html='item'></p>
</van-cell>

注意:展示内容需要使用v-html指令

  • 实现联想列表的高亮控制
// 需求:让列表项内容与关键自匹配后高亮(包裹一层span标签)
  this.keylist = ret.data.options.map(item => {
    // const arr = []
    // const arr = new Array()
    // const obj = {}
    // const obj = new Object()
    // const reg = /\d/
    // const reg = new RegExp('\d')
    // 原生js方法replace用于替换整个字符串中的子串
    const reg = new RegExp(this.q, 'ig')
    return item.replace(reg, `<span>${this.q}</span>`)
  })

总结:熟悉基于正则表达式构造函数的用法,以及js的replace方法的用法。

  • 路由跳转传递参数的另一种方式

总结:通过编程式导航跳转路由时,通过?方式将参数拼接到路由路径之后

this.$router.push('/sresult?kw=' + this.q)
// 路由映射
{
    path: '/sresult',
    component: Sresult
}

组件内部如何得到这个问号后的参数?this.$route.query.kw

this.$router.push('/sresult/' + this.q)
// 路由映射
{
    path: '/sresult/:kw',
    component: Sresult
}

组件内部获取这种路由参数的方式:this.$route.params.kw

  • 实现搜索页面的跳转
    • 回车后跳转,参数为输入的关键字
// 实现搜索
handleSearch () {
  // 防止输入空字符串
  // q.trim()是原生js的方法,用于去掉两侧的空格 ''
  if (!this.q.trim()) return
  // 回车触发后:1、保存历史关键字;2、跳转到搜索列表页面
  this.history.unshift(this.q)
  // Set是ES6引入的一个构造函数,存储结构和数组类似,其中不允许放重复数据
  this.history = [...new Set(this.history)]
  // 把history数组放入缓存
  window.localStorage.setItem('keywords', JSON.stringify(this.history))
  // 跳转到搜索列表页面
  // this.$router.push('/sresult?kw=' + this.q)
  // this.$router.push('/sresult/' + this.q)
  this.$router.push({
    // path: 'sresult',
    name: 'myresult',
    query: {
      kw: this.q
    }
  })
}

总结:

  1. 编程式导航的跳转有多种用法
  2. 可以这路由映射中设置name属性,编程式导航可以基于name值跳转
  3. 不同的传参方式属性不同 query/params
  • 点击联想的列表项跳转,参数为列表项内容
// 点击联想条目跳转
handleJump (kw) {
  // kw此时包括span高亮的标签,需要去掉
  const reg = new RegExp(`<span>${this.q}</span>`, 'ig')
  kw = kw.replace(reg, this.q)
  this.$router.push({
    name: 'myresult',
    query: {
      kw: kw
    }
  })
},

总结:点击跳转之前,传递的参数需要去掉span高亮标签。

搜索结果-基本布局

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

  • 搜索列表布局
<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: 92px;
  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: 180px;
    }
    .w100 {
      height: 360px;
      width: 100%;
    }
  }
  .info_box {
    color: #999;
    line-height: 2;
    position: relative;
    span {
      padding-right: 20px;
    }
  }
}

搜索结果-上拉功能

目标:实现搜索上拉功能

  • 封装上拉加载接口
// 根据关键字搜索文章列表
export const searchArticles = (options) => {
  return request({
    method: 'get',
    url: 'v1_0/search',
    // axios传递get请求参数本来就是使用params
    params: {
      // 当前页码
      page: options.pagenum,
      // 每页的条数
      per_page: options.pagesize,
      // 查询的关键字
      q: options.kw
    }
  })
}
  • 列表相关数据
data () {
  return {
    loading: false,
    finished: false,
    // 搜索参数
    queryData: {
      page: 1,
      perPage: 10,
      // 通过路由参数初始化数据
      q: this.$route.query.q
    },
    // 搜索结果
    list: [],
    // 列表总数
    total: 0
  }
},
    
methods: {
  async searchArticles () {
    // 根据关键字搜索文章列表结果
    const [err, ret] = await searchArticles(this.queryData)
    if (err) {
      this.$toast.fail('查询文章失败')
    } else {
      // 获取文章的列表数据和总数
      this.list = ret.data.results
      this.total = ret.data.total_count
    }
  }
},
  • 触发上拉加载更多动作
  methods: {
    async onl oad () {
      // 页面加载时,触发一次,如果不够一屏,再次调用一次
      const ret = await searchArticles(this.queryData)
      this.list.push(...ret.data.results)
      this.total = ret.data.total_count
      // 结束这次的加载
      this.loading = false
      // 判断加载完成的状态
      if (this.list.length >= this.total) {
        // 没有更多数据需要加载
        this.finished = true
      } else {
        // 还有更多数据可以加载:页码累加
        this.queryData.pagenum += 1
      }
    }
  },
  created () {
    // 获取路由参数中的查询关键字
    this.queryData.kw = this.$route.query.kw
  }
  • 渲染列表内容
<van-cell 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">
      <van-image :key='index' v-for='(img, index) in item.cover.images' :class="[{w33: item.cover.type===3}, {w100: item.cover.type===1}]" fit="cover" :src="img" />
    </div>
    <div class="info_box">
      <span>{{item.aut_name}}</span>
      <span>{{item.comm_count}}评论</span>
      <span>{{item.pubdate|formatTime}}</span>
    </div>
  </div>
</van-cell>

总结:

  1. 基于pagenum和pagesize的分页逻辑
  2. van-list组件的基本使用
  3. 类名的动态绑定的用法

总结

  • 缓存搜索的历史关键字:本地存储API
  • 数组去重:基于ES6引入的新的构造函数Set
  • 删除历史关键字:单个删除和全部删除(本地存储API)
  • 防止输入空字符:原生js的trim方法的用法
  • 函数防抖的业务流程(※);对比函数节流
    • 概念;应用场景;代码实现
  • 关键字匹配基本功能:调用接口;填充页面
  • 列表项目的高亮控制:正则的构造函数用法(好处:支持变量)(※)
  • 路由传递参数的用法(基于问号传递参数)(※)
  • 编程式导航路由跳转,路由映射支持name,也可以用name跳转(※)
    • 注意传递参数:query/params
    • name的用法
  • 基于pagenum和pagesize参数的分页:基于时间的分页
上一篇:介绍Python中的函数参数的可变参数和关键字参数?


下一篇:educoder第1关:学习-Python函数之函数参数