【笔记】实战mpvue2.0多端小程序框架——搜索模块(下)


搜索开发 | 「小慕读书」官网


一、搜索模块组件集成

清除src\pages\search\search.vue页面之前的测试代码,修改为:

<template>
  <div>
    <SearchBar
      :focus="searchFocus"
      @onChange="onChange"
    />
    <TagGroup
      header-text="热门搜索"
      btn-text="换一批"
      :value="hotSearch"
      @onBtnClick="changeHotSearch"
      @onTagClick="showBookDetail"
      v-if="hotSearch.length > 0 && !showList"
    />
    <TagGroup
      header-text="历史搜索"
      btn-text="清空"
      :value="historySearch"
      @onBtnClick="clearHistorySearch"
      @onTagClick="searchKeyWord"
      v-if="historySearch.length > 0 && !showList"
    />
    <SearchList
      :data="searchList"
      v-if="showList"
    />
  </div>
</template>

<script>

import SearchBar from '../../components/home/SearchBar'
import SearchList from '../../components/search/SearchList'
export default {
  components: {SearchList, SearchBar},
  computed: {
    showList() {
      const keys = Object.keys(this.searchList)
      return keys.length > 0
    }
  },
  data() {
    return {
      hotSearch: [],
      historySearch: [],
      searchList: {},
      searchFocus: true
    }
  },
  methods: {
    changeHotSearch() {
      console.log('change hot search')
    },
    showBookDetail() {
      console.log('showBookDetail')
    },
    clearHistorySearch() {
      console.log('clearHistorySearch')
    },
    searchKeyWord() {
      console.log('searchKeyWord')
    },
    onChange(keyword) {
      console.log(keyword)
    }
  }
}
</script>

<style lang="scss" scoped>
</style>

这里的显示与否取决于内容是否为空:

  • 数组:Array.length > 0
  • 对象:Object.keys.length > 0

二、搜索模块API对接

src\api\index.js中新增搜索接口调用方法

export function search(params) {
  return get(`${API_URL}/book/search`, params)
}

修改src\components\search\SearchList.vue:

<template>
  <div class="search-list-wrapper">
    <SearchItem
      icon="apps-o"
      :title="category"
      sub-title="Category"
      @onClick="showList(category.title, 'category')"
      v-if="category"
    />
    <SearchItem
      icon="user-o"
      :title="author"
      sub-title="Author"
      @onClick="showList(author.title, 'author')"
      v-if="author"
    />
    <SearchItem
      icon="newspaper-o"
      :title="publisher"
      sub-title="Publisher"
      @onClick="showList(publisher.title, 'publisher')"
      v-if="publisher"
    />
    <SearchTable :data="data.book" @onClick="onBookClick"/>
  </div>
</template>

<script>
import SearchItem from './SearchItem'
import SearchTable from './SearchTable'
export default {
  components: {SearchTable, SearchItem},
  props: {
    data: Object
  },
  computed: {
    category() {
      if (this.data && this.data.category && this.data.category.length > 0) {
        return this.data.category[0].categoryText
      } else {
        return null
      }
    },
    author() {
      if (this.data && this.data.author && this.data.author.length > 0) {
        return this.data.author[0].author
      } else {
        return null
      }
    },
    publisher() {
      if (this.data && this.data.publisher && this.data.publisher.length > 0) {
        return this.data.publisher[0].publisher
      } else {
        return null
      }
    }
  },
  methods: {
    showList(text, key) {
      console.log(text, key)
    },
    onBookClick(book) {
      console.log(book)
    }
  }
}
</script>

<style lang="scss" scoped>
</style>

修改src\components\search\SearchItem.vue的部分样式:

.search-item-info {
  width: 80%;
  ...
  .search-item-title {
    width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    ...
  }
  ...

修改src\pages\search\search.vue:

  • 修改onChange方法并新增onSearch方法
    onChange(keyword) {
      if (!keyword || keyword.trim().length === 0) {
        this.searchList = {}
        return
      }
      this.onSearch(keyword)
    },
    onSearch(keyword) {
      search({
        keyword, openId: this.openId
      }).then(res => {
        this.searchList = res.data.data
      })
    }
  • 新增data:openId: ''并通过本地缓存获取:
  mounted() {
    this.openId = getStorageSync('openId')
  },

预览:
在这里插入图片描述

三、热门搜索API对接

在src\pages\search\search.vue为SearchBar新增onClear,用来在点击清除按钮时进行数据清除:

<SearchBar
  :focus="searchFocus"
  @onChange="onChange"
  @onClear="onClear"
/>
    onClear() {
      this.searchList = {}
    }

src\api\index.js中新增热门搜索接口调用方法

export function hotSearch(params) {
  return get(`${API_URL}/book/hot-search`, params)
}

修改src\pages\index\index.vue中跳转搜索页面的事件,使在跳转搜索页面时携带参数hotSearch:

onSearchBarClick() {
  this.$router.push({
    path: '/pages/search/main',
    query: {
      hotSearch: this.hotSearch
    }
  })
},

src\pages\search\search.vue页面加载时调用hotSearch接口方法,并拿到首页传来的hotSearch赋给搜索页面新增的datahotSearchKeyword

  data() {
    return {
      hotSearch: [],
      historySearch: [],
      hotSearchKeyword: '',
      searchList: {},
      searchFocus: true,
      openId: ''
    }
  },
  mounted() {
    this.openId = getStorageSync('openId')
    hotSearch().then(res => {
      this.hotSearch = res.data.data
    })
    this.hotSearchKeyword = this.$route.query.hotSearch
  },

在计算属性中创建hotSearchArray方法来获取热门搜索标题,并在中调用

hotSearchArray() {
  const _hotSearch = []
  this.hotSearch.forEach(obj => _hotSearch.push(obj.title))
  return _hotSearch
}

修改src\components\base\TagGroup.vue的样式

<style lang="scss" scoped>
  .tag-group-wrapper {
  	...
    width: 100%;
    padding-bottom: 10px;
    ...
    .tag-goup {
      width: 100%;
      box-sizing: border-box;
      ...
      .tag-group-inner {
        max-width: 100%;
        box-sizing: border-box;
        ...
      }
    }
  }
</style>

修改src\components\base\Tag.vue的样式:

<style lang="scss" scoped>
  .tag-wrapper {
    width: 100%;
    box-sizing: border-box;
    ...
    .tag {
      width: 100%;
      ...
    }
  }
</style>

这样热门搜索就可以正常显示了
在这里插入图片描述
修改showBookDetail方法

showBookDetail(text, index) {
  console.log('showBookDetail', index, text)
},

四、历史搜索和热搜更新功能开发

在src\pages\search\search.vue中新增onConfirm方法:

    onConfirm(keyword) {
      // 1.判断是否有搜索关键词
      if (!keyword || keyword.trim().length === 0) {
        // 如果没有,则获取热门搜索词,通过热门搜索词发起请求
        keyword = this.hotSearchKeyword
        this.$refs.searchBar.setValue(keyword)
      } else {
        // 如果有,使用搜索关键词发起请求
      }
      this.onSearch(keyword)
      // 2.将搜索结果写入历史搜索
      if (!this.historySearch.includes(keyword)) {
        this.historySearch.push(keyword)
        // 将历史搜索写入缓存
        setStorageSync(KEY_HISTORY_SEARCH, this.historySearch)
      }
      // 3.将搜索框失去焦点
      this.searchFocus = false
    }

清空历史记录:

    clearHistorySearch() {
      this.historySearch = []
      setStorageSync(KEY_HISTORY_SEARCH, [])
    },

在搜索页面加载时从缓存中拿到历史搜索记录:

  mounted() {
    this.openId = getStorageSync('openId')
    hotSearch().then(res => {
      // 从接口中拿到热门搜索关键词内容,并赋值本页面
      this.hotSearch = res.data.data
    })
    // 将首页传来的参数(热门搜索关键词)赋给本页面的热门搜索关键词,完成参数传递
    this.hotSearchKeyword = this.$route.query.hotSearch
    // 若缓存中有历史搜索记录,页面渲染完毕就拿到,否则置空
    this.historySearch = getStorageSync(KEY_HISTORY_SEARCH) || []
  },

为了方便,可以定义一个historySearch常量,然后在需要的地方引用:

const KEY_HISTORY_SEARCH = 'historySearch'

换一批其实也就是重新从hotSearch接口拿一遍数据:

hotSearch().then(res => {
  // 从接口中拿到热门搜索关键词内容,并赋值本页面
  this.hotSearch = res.data.data
})

点击历史搜索的tag触发searchKeyWord事件:

searchKeyWord(text) {
  this.$refs.searchBar.setValue(text)
  this.onSearch(text)
},

预览:
在这里插入图片描述

五、触底自动刷新功能开发

页面滑动时搜索框失去焦点(调用的是一个页面的生命周期函数):

  onPageScroll() {
    if (this.searchFocus) {
      this.searchFocus = false
    }
  },

新增datapage: 1,在onSearch给search接口传参时传入:

onSearch(keyword) {
  search({
    keyword, openId: this.openId, page: this.page
  }).then(res => {
    this.searchList = res.data.data
  })
},

在src\api\wechat.js中新增

export function showToast(title) {
  mpvue.showToast({
    title,
    duration: 2000
  })
}

到达页面底部触发页面周期函数onReachBottom:

  onReachBottom() {
    if (this.showList) {
      // 再加载20条数据,即一页数据
      this.page++
      // 拿到搜索关键词
      const searchWord = this.$refs.searchBar.getValue()
      // 这时就不能直接调用onSearch方法,否则整个页面都会加载新的一页数据来替换掉原有页数据
      search({
        keyword: searchWord,
        openId: this.openId,
        page: this.page
      }).then(res => {
        // 通过解构的方式拿到数据
        const { book } = res.data.data
        if (book && book.length > 0) {
          // 这里使用拓展运算符可以把book数组里面的元素散着push进去而不是整个数组push进去
          this.searchList.book.push(...book)
        } else {
          showToast('没有更多数据了')
        }
      })
    }
  },

记得要在关键词改变时初始化page为1

    onChange(keyword) {
      ...
      this.page = 1
      this.searchFocus = true	// 为了防止焦点丢失,建议加上
      ...
    },

在mounted中也是如此:

  mounted() {
    ...
    this.page = 1
    ...
  },
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序边界

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值