>  기사  >  위챗 애플릿  >  그 당시 WeChat 애플릿은 NetEase Cloud Music의 실시간 검색 기능을 모방했습니다.

그 당시 WeChat 애플릿은 NetEase Cloud Music의 실시간 검색 기능을 모방했습니다.

coldplay.xixi
coldplay.xixi앞으로
2020-09-14 13:18:363914검색

그 당시 WeChat 애플릿은 NetEase Cloud Music의 실시간 검색 기능을 모방했습니다.

관련 학습 추천: WeChat 미니 프로그램 튜토리얼

머리말

얼마 전 친구가 NetEase 클라우드 뮤직 미니 프로그램의 음악 재생 기능을 자세히 소개해줬는데, 제가 먼저 배웠어요. -end novice 한동안 일이 너무 바빠서 실시간 검색 내용을 제때 작성하지 못해 여러분께 공유해드리지 못했습니다. (사실 코드나 함수는 이미 거의 예전에 작성해두었던 내용입니다.) 그래서 오늘은 말씀드리겠습니다. 내 작업에 대한 세부 사항과 세부 사항을 최적화하세요.

검색 기능은 매우 일반적이며 여러 곳에서 사용할 수 있습니다. 동시에 유용한 내용을 공유할 수 있으면 좋겠고, 부족한 부분이 있으면 지적해 주시길 바랍니다. 수정했습니다. Xiaobai님 덕분입니다!

입력 상자에 값을 입력하는 것부터 제안 검색, 결과 검색, 마지막으로 노래 재생으로 이동까지 실시간 검색 기능에서도 API 인터페이스를 사용해야 합니다. 값을 전달하는 것이 중요하며 동시에 다양한 기능 아래에는 다양한 컨테이너 상자의 숨기기 및 표시와 검색과 관련된 일부 세부 정보 및 최적화가 나와 있습니다. 살펴보자!

인터페이스 미리보기

그 당시 WeChat 애플릿은 NetEase Cloud Music의 실시간 검색 기능을 모방했습니다.

인터페이스 분석

헤드 검색창: 왼쪽에 돌아가기 화살표, 가운데에 입력 상자, 오른쪽에 가수 순위 페이지 점프가 숨겨져 있습니다. 입력할 때만 해당 값이 나중에 표시됩니다.

히스토리 레코드를 내려갈 때 여기의 각 검색 레코드 값은 등거리 분포의 작은 조각인 것처럼 검색 값은 이 작은 조각의 길이만큼 길며 여기서 사용되는 것은 display: flex;flex -wrap: Wrap;, 이 인터페이스 스타일에 관심이 있는 친구들은 나중에 전체 코드를 살펴볼 수 있습니다. display: flex;flex-wrap: wrap;,对这个界面样式感兴趣的小伙伴可以待会看看全部代码。

接下来是热搜榜,这里没有太多讲究,就是发起接口请求数据,把数据埋进去显示出来就行了。

搜索建议会在输入结束后才会出现,并且是很立体的一块覆盖在整个页面上,用box-shadow: 1px 1px 5px #888888达到立体效果,z-index起到覆盖的效果。

搜索结果会在点击搜索建议中的某一条或者点击搜索历史或者热搜才出现,同时界面上其他所有的容器快都会隐藏起来,这里其实就是一个容器框的隐藏与出现的小细节了,待会在功能中我们会详细讲到。这里我们先讲一下组件(容器)如何进行隐藏与显示,以免下面的功能中看到这几项内容蒙圈

几个容器的头部展示

<!-- 点击×可以清空正在输入 -->
<image class="{{showClean ? &#39;header_view_hide&#39; : &#39;clean-pic&#39;}}" src="../../image/search_delete.png" bindtap="clearInput" />复制代码
<!-- 搜索建议 -->
<view class="{{showSongResult ? &#39;search_suggest&#39; : &#39;header_view_hide&#39;}}">复制代码
<!-- 搜索结果 -->
<view class="{{showSearchResult ? &#39;header_view_hide&#39; : &#39;search_result_songs&#39;}}">复制代码
<!-- 搜索历史 -->
<view class="{{showView?&#39;option&#39;:&#39;header_view_hide&#39;}}">复制代码
<!-- 热搜榜 -->
<view class="{{showView?&#39;option&#39;:&#39;header_view_hide&#39;}}">复制代码

解析:这里只放了这几块容器的头部的内容,在data数据源中分别放了showClean,showSongResult,showSearchResult,showView, 为true 则这几块容器默认为:(冒号)前面的样式,为false则默认为:(冒号)后面的样式;header_view_hide样式设置为display: none;,即隐藏不显示;所以当在某一个方法中可以去改变showClean,showSongResult,showSearchResult,showViewtrue还是false可以让这几块容器分别为显示或是隐藏。

接口封装

接口封装在上一篇我的小伙伴已经讲的十分清晰了,我们这里不再多去讲解了,同样现在用到的功能也不只是光调接口请求数据那么简单了,我们需要传值给接口,让接口收到值后再给我们返回相应的数据;在搜索界面我们用到的是搜索建议以及搜索结果的接口。热搜榜我们暂时只使用最基础的wx.request直接获取数据

api.js

const API = {
    getSearchSuggest: (data) => request(GET, `/search/suggest`, data),  // 搜索建议接口
    getSearchResult: (data) => request(GET, `/search`, data),  // 搜索结果接口
}复制代码

实时搜索功能:

1.数据源分析

一个搜索功能我们设计到的数据会有很多,可以细列一下:输入的值inputValue,在输入时获取;热搜榜数据hots,热搜接口获取;搜索关键词searchKey,本身就是输入框的值,用来传递给搜索建议作为搜索关键词;searchSuggest,搜索建议接口拿到搜索关键词后返回的的数据(搜索建议);搜索结果searchResult,当点击搜索建议中的某一条,该值将填入搜索框,此时搜索关键词searchKey将变为该值又传递给搜索结果接口,并返回数据放入searchResult;最后是搜索历史history,每当进行一次搜索,将原本输入框的值放到history

다음 단계는 핫 검색 목록입니다. 여기서는 크게 강조하지 않고 데이터를 요청하고 데이터를 묻어 표시하는 인터페이스를 시작합니다. 🎜🎜입력이 완료된 후 검색 제안이 나타나며 매우 입체적이며 전체 페이지를 덮습니다. 입체적인 효과를 얻으려면 box-shadow: 1px 1px 5px #888888을 사용하세요. , z -index는 덮어쓰기 효과가 있습니다. 🎜🎜검색 제안 중 하나를 클릭하거나 검색 기록 또는 핫 검색을 클릭하면 검색 결과가 나타납니다. 동시에 인터페이스의 다른 모든 컨테이너는 실제로 숨겨진 세부 사항입니다. 그리고 컨테이너 박스의 모습에 대해서는 나중에 함수에서 자세히 다루겠습니다. 여기서는 먼저 다음 함수에서 이러한 항목이 표시되지 않도록 구성 요소(컨테이너)를 숨기고 표시하는 방법에 대해 설명합니다. 🎜🎜🎜여러 컨테이너의 헤더 표시🎜
data: {
    inputValue: null,//输入框输入的值
    history: [], //搜索历史存放数组
    searchSuggest: [], //搜索建议
    showView: true,//组件的显示与隐藏
    showSongResult: true,
    searchResult: [],//搜索结果
    searchKey: [],
    showSearchResult: true,
    showClean: true,
    hots: [] //热门搜索
 }复制代码
onLoad: function (options) {
    wx.request({
      url: 'http://neteasecloudmusicapi.zhaoboy.com/search/hot/detail',
      header: { "Content-Type": "application/json" },
      success: (res) => {  // console.log(res)
        this.setData({
          hots: res.data.result.hots })
      }
    })
  },复制代码
<input focus=&#39;true&#39; type="text" class="weui-search-bar__input" placeholder="大家都在搜 " placeholder-style="color:#eaeaea" value=&#39;{{inputValue}}&#39; bindinput="getSearchKey" bindblur="routeSearchResPage" bindconfirm="searchOver" />复制代码
getSearchKey: function (e) {
    // console.log(e.detail) //打印出输入框的值
    if (e.detail.cursor != this.data.cursor) { //实时获取输入框的值
      this.setData({
        showSongResult: true,
        searchKey: e.detail.value })
      this.searchSuggest();
    }
    if (e.detail.value) { // 当input框有值时,才显示清除按钮'x'
      this.setData({
        showClean: false  // 出现清除按钮 })
    }
    if(e.detail.cursor === 0){
      this.setData({  // 当输入框没有值时,即没有输入时,隐藏搜索建议界面,返回到最开始的状态
        showSongResult: false })
      return
    }
  }复制代码
 clearInput: function (e) {
    // console.log(e)  
    this.setData({
      inputValue: '',  // 将输入框的值为空
      showSongResult: false,  // 隐藏搜索建议栏
      showClean: true // 隐藏清除按钮 (不加此项会出现清除输入框内容后清除按钮不消失的bug)
    })
  },复制代码
🎜분석: 여기에는 이 몇 가지 조각만 배치됩니다. 컨테이너 헤더의 data 데이터 소스에 각각 showClean, showSongResult, showSearchResult, showView가 포함됩니다. 컨테이너는 :(콜론) 앞의 스타일을 기본값으로 설정하고, false인 경우 :(콜론) 뒤의 스타일을 기본값으로 설정합니다. > 스타일은 display: none;으로 설정됩니다. 즉, 숨겨지고 표시되지 않습니다. 따라서 특정 메서드에서는 showClean, showSongResult, showSearchResult, showView를 true 또는 false를 사용하면 이러한 컨테이너를 각각 표시하거나 숨길 수 있습니다. 🎜🎜

인터페이스 캡슐화🎜🎜인터페이스 캡슐화는 이전 기사에서 내 친구가 매우 명확하게 설명했습니다. 여기서는 동일한 기능이 사용됩니다. 데이터를 요청하기 위해 인터페이스를 조정하는 것만이 아니라 인터페이스에 값을 전달하고 인터페이스가 값을 받은 후 해당 데이터를 반환하도록 해야 합니다. 검색 결과. 핫 검색 목록의 경우 가장 기본적인 wx.request만 사용하여 실시간으로 데이터🎜🎜🎜api.js🎜
back: function () {
    wx: wx.navigateBack({  // 关闭当前页面,返回上一页面或多级页面
      delta: 0   // 返回的页面数,如果 delta 大于现有页面数,则返回到首页
     });
  }复制代码
🎜

를 얻습니다. 검색 기능: 🎜1. 데이터 소스 분석🎜많은 양의 데이터로 검색 기능을 설계해 보겠습니다. 입력 값 inputValue, 입력 중에 획득; 핫 검색 인터페이스를 통해 획득된 핫 검색 목록 데이터 searchKey, 그 자체가 사용된 입력 상자의 값입니다. searchSuggest, 검색 키워드 검색 결과 searchResult를 가져온 후 검색 제안 인터페이스에서 반환된 데이터(검색 제안)를 검색 제안에 전달합니다. 의 추천 검색어를 클릭하면 이 값이 검색창에 채워지며, 이때 검색 키워드 searchKey가 이 값이 되어 검색 결과 인터페이스에 전달되고 반환되는 데이터는 다음과 같습니다. searchResult에 넣습니다. 마지막으로 검색 기록 기록을 입력합니다. 검색이 수행될 때마다 원래 입력 상자의 값이 기록에 입력됩니다. 데이터 소스. 다른 데이터 소스의 경우 구성 요소 숨김 및 표시, 즉 어떤 상황에서 각 조각의 컨테이너 프레임이 숨겨지고 어떤 상황에서 표시되는지가 관련됩니다. 🎜

数据源展示

data: {
    inputValue: null,//输入框输入的值
    history: [], //搜索历史存放数组
    searchSuggest: [], //搜索建议
    showView: true,//组件的显示与隐藏
    showSongResult: true,
    searchResult: [],//搜索结果
    searchKey: [],
    showSearchResult: true,
    showClean: true,
    hots: [] //热门搜索
 }复制代码

2.获取热搜榜

这里我们直接在页面的初始数据中调用接口,直接获取到数据使用

onLoad: function (options) {
    wx.request({
      url: 'http://neteasecloudmusicapi.zhaoboy.com/search/hot/detail',
      header: { "Content-Type": "application/json" },
      success: (res) => {  // console.log(res)
        this.setData({
          hots: res.data.result.hots })
      }
    })
  },复制代码

3.获取input文本

前面已将讲过,搜索建议和结果的接口并没有直接的获取方式,需要我们进行传值,所以首先我们需要获取到输入框的值

input框内容分析

<input focus=&#39;true&#39; type="text" class="weui-search-bar__input" placeholder="大家都在搜 " placeholder-style="color:#eaeaea" value=&#39;{{inputValue}}&#39; bindinput="getSearchKey" bindblur="routeSearchResPage" bindconfirm="searchOver" />复制代码

小程序中关于input输入框的相关属性大家可以去详细了解一下;placeholder为输入框为空时占位符,即还没输入前输入框显示的内容,placeholder-style可以去设置placeholder的样式;value是输入框的初始内容,即自己在输入框输入的内容,我们在这里直接将输入的内容value直接作为了data数据源中inputValue的内容;bindinput是在键盘输入时触发,即我们一进行打字,就能触发我们的自定义事件getSearchKey,并且会返还相应数据;bindblur在输入框失去焦点时触发,进行搜索功能时,需要在搜索框输值,此时焦点一直在输入框,当点击输入框以外的地方即输入框失去焦点,同时触发routeSearchResPage事件,还会返回相应的数据,在下面功能中会讲到;bindconfirm在点击完成按钮时触发,这里绑定一个searchOver,用来隐藏组件(容器块),再次触发搜索功能,在下面的功能中也会讲到。

获取input文本

getSearchKey: function (e) {
    // console.log(e.detail) //打印出输入框的值
    if (e.detail.cursor != this.data.cursor) { //实时获取输入框的值
      this.setData({
        showSongResult: true,
        searchKey: e.detail.value })
      this.searchSuggest();
    }
    if (e.detail.value) { // 当input框有值时,才显示清除按钮'x'
      this.setData({
        showClean: false  // 出现清除按钮 })
    }
    if(e.detail.cursor === 0){
      this.setData({  // 当输入框没有值时,即没有输入时,隐藏搜索建议界面,返回到最开始的状态
        showSongResult: false })
      return
    }
  }复制代码

bindinput本身是会返回数据,在代码运行时,可以打印出来先看看; e.detail.value即为输入框的值,将它赋值给searchKey; 查看打印数据e:

그 당시 WeChat 애플릿은 NetEase Cloud Music의 실시간 검색 기능을 모방했습니다.

解析:

疑惑的小伙伴可以将代码运行,打印出以上设计的几个数据进行分析

①当此时输入框的值和bindinput返回的输入框的值时一样的,就将输入框的值赋给搜索关键词searchKey,此时显示搜索建议栏(showSongResult写在wxml当中,用来控制该容器是否展示,可以看到最后面发的整个界面的wxml中的详情);同时searchSuggest事件(方法)生效。

②当输入框没值时,清除按钮x是不会显示的,只有当输入框有值是才会出现清除按钮x

③当输入框没有值时,隐藏搜索建议栏,其实本身我们最开始进入这个页面时,输入框是没值的,搜索建议栏也是不展示的,为没进行输入就没有数据;但是当我们输入内容后,出现搜索建议,此时我们点击清除按钮,输入框的内容没了,但是搜索建议还停留在之前的状态,所以这里我们优化一下,让showSongResultfalse,即一清空输入框内容,隐藏掉搜索建议栏。另外我们为什么要return呢?这里还有一个bug,当清除输入框内容后,再输入发现已经不再具备搜索功能了,所以需要return回到初始的状态,就能重新进行输入并且搜索。同时当输入框为空时进行搜索功能还会报错,这也是一个bug,所以有了return即使空值搜索也会立马回到初始状态,解决了空值搜索报错的bug

4.搜索框其他功能

  • 清空输入框内容

     clearInput: function (e) {
        // console.log(e)  
        this.setData({
          inputValue: '',  // 将输入框的值为空
          showSongResult: false,  // 隐藏搜索建议栏
          showClean: true // 隐藏清除按钮 (不加此项会出现清除输入框内容后清除按钮不消失的bug)
        })
      },复制代码

    点击清除按钮,就让inputValue值为空,即输入框的内容为空,达到清除文本的效果;在获取输入框文本那里我们也提到了清除按钮,也提到输入框文本清空时,之前的搜索建议栏还会留下,所以这里我们让showSongResultfalse,使得搜索建议栏隐藏。清除文本的同时再隐藏掉清除按钮。

  • 取消搜索返回上页

    back: function () {
        wx: wx.navigateBack({  // 关闭当前页面,返回上一页面或多级页面
          delta: 0   // 返回的页面数,如果 delta 大于现有页面数,则返回到首页
         });
      }复制代码

    这里用到的小程序自带的返回页面的功能,当给delta值为0即回到上一个页面。(可去文档查看详情)

  • 跳转歌手排行榜

    singerPage: function () {
        wx.navigateTo({  // 保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面
          url: `../singer/singer` // 要跳转去的界面
        })
      },复制代码

    在微信官方文档可以查看到navigateTo的功能及其属性,这里不多讲。

5.搜索建议

 searchSuggest() {
    $api. getSearchSuggest({ keywords: this.data.searchKey, type: 'mobile' }).then(res => {
      //请求成功 
      // console.log(res);  // 打印出返回数据进行查看
      if(res.statusCode === 200){
        this.setData({
          searchSuggest: res.data.result.allMatch  // 将返回数据里的歌名传给搜索建议
        })
       }
    })
    .catch(err => {  // 请求失败
      console.log('错误')   })
  }复制代码

解析:开始我们将接口进行了封装,在上一篇讲播放的文章中我的小伙伴已经把接口跟封装讲的很仔细了,这里我们就不在讲这个了,就分析我们的接口。searchKey作为搜索关键词需要传递给接口,在前面的getSearchKey方法中,我们已经讲输入框的内容传给了searchKey作为它的值;所以此时我们拿到有值的searchKey传递给接口,让接口返回相关数据,返回的数据中的res.data.result.allMatch就是从搜索关键词返回的搜索建议里的所有歌名,在将这些数据放到searchSuggest数据源中,这样在wxml埋好的空就能拿到数据,将搜索建议栏显示出。

6.搜索结果

  • 搜索建议内的歌曲点击事件
    // 看看 wxml中的点击事件展示
    // <view wx:for="{{searchSuggest}}" wx:key="index" class=&#39;search_result&#39; data-value=&#39;{{item.keyword}} &#39; bindtap=&#39;fill_value&#39;>
    // js如下:
    fill_value: function (e) {   // 点击搜索建议,热门搜索值或搜索历史,填入搜索框
        // console.log(e.currentTarget.dataset.value)  // 打印`e`中的数据->点击的值
        this.setData({
          searchKey: e.currentTarget.dataset.value, // 点击时把值给searchKey进行搜索
          inputValue: e.currentTarget.dataset.value, // 在输入框显示内容
          showSongResult: false, // 给false值,隐藏搜索建议页面
          showClean: false // 显示清除按钮 (不加此项,会出现点击后输入框有值但不显示清除按钮的bug)
        })
        this.searchResult();  // 执行搜索功能
      },复制代码

    解析:首先点击事件可以携带额外信息,如 id, dataset, touches;返回参数eventevent本身会有一个currentTarget属性;这里解释一下data-value='{{item.keyword}}=>data就是dataset;item.keyword是搜索建议完成之后返回的数据赋值给searchSuggest里面的某个数据;当一点击搜索建议里面的某一个歌名时,此歌名即为此时的item.keyword,并将该值存入点击事件的参数event内的dataset。大家也可操作一波打印出来看看结果,currentTarget.dataset.value就是我们点击的那个歌曲名字。所以一点击搜索建议中的某个歌名或者搜索历史以及热搜榜单中的某个歌名时,点击事件生效,返回这样该歌曲名称,并将该值给到此时的searchKeyinputValue,此时输入框的值会变成该值,搜索结果的关键词的值也会变成该值;同时this.searchResult()可让此时执行搜索结果功能。showSongResult: false这里还将搜索建议栏给隐藏了。增加showClean: false是为了解决点击后输入框有值但不显示清除按钮的bug查看打印数据e:

    그 당시 WeChat 애플릿은 NetEase Cloud Music의 실시간 검색 기능을 모방했습니다.
  • 返回搜索结果
    searchResult: function () {
        // console.log(this.data.searchKey)  // 打印此时的搜索关键词
        $api.getSearchResult({ keywords: this.data.searchKey, type: 1, limit: 100, offset: 2 }).then(res => {
          // 请求成功
          if (res.statusCode === 200) {
            // console.log(res)  // 打印返回数据
            this.setData({
              searchResult: res.data.result.songs, // 将搜索出的歌曲名称给到搜索结果
              showSearchResult: false, // 显示搜索结果栏
              showView: false,  // 隐藏搜索历史栏和热搜榜单栏
            });
          }
        })
        .catch(ree => {
          //请求失败
        })
      },复制代码

    解析:上面的歌曲名称点击同时触发了搜索结果的功能,将点击后的新的keywords传递给了搜索结果的接口,接口请求后返回给我们数据,数据中的res.data.result.songs为搜索到的歌曲,此时将它赋值给到searchResult,这样搜索结果栏中会拿到数据,并且showSearchResult: false让搜索结果栏显示出来;这里还做了搜索历史栏和热搜栏的隐藏功能注:搜索结果和搜索建议都需要将搜索关键词传递给接口,不清楚的小伙伴可以去查看接口文档研究一下:https://binaryify.github.io/NeteaseCloudMusicApi/#/

  • 搜索完成后的优化
      searchOver: function () { // 搜索结果完成后(再次点击输入框)
       this.setData({
         showSongResult: false  // 搜索建议这块容器消失
       })
       this.searchResult();  // 执行搜索结果
     },复制代码

    解析:前面我们讲到过, searchOver是绑定在input框中的bindconfirm事件,即点击完成按钮时触发。当我们搜索完成之后,界面上还有搜索栏以及搜索结果的显示,此时我们再次点击输入框,可以进行清除文本,同时我们还需要增加一个功能,即在此种情况下,我们还可以进行再次输入并且返回搜索建议以及点击搜索建议中的歌曲时再次执行搜索结果功能。

7.搜索历史

  • input失去焦点
    routeSearchResPage: function (e) {  
        // console.log(this.data.searchKey)  // 打印此时的搜索关键词
        // console.log(this.data.searchKey.length)  
        if (this.data.searchKey.length > 0) {  // 当搜索框有值的情况下才把搜索值存放到历史中,避免将空值存入历史记录
          let history = wx.getStorageSync("history") || [];  // 从本地缓存中同步获取指定 key 对应的内容,key指定为history
          // console.log(history);
          history = history.filter(item => item !== this.data.searchKey)  // 历史去重
          history.unshift(this.data.searchKey)  // 排序传入
          wx.setStorageSync("history", history);
        }
      }复制代码

    解析:之前讲过routeSearchResPage事件时放在input框中的,输入框失去焦点时触发,即不在输入框内进行输入,点击输入框以外的内容时触发。当输入完成时会出现搜索建议,此时焦点还在输入框,当我们点击搜索建议中的某一天时,输入框即失去焦点,此时该事件触发。失去焦点函数是在搜索建议事件后发生,此时的搜索关键词为搜索建议的搜索关键词,前面也讲到过,这个搜索关键词就是我们在输入框输入的文本内容,所以将此时的搜索关键词赋值给搜索历史history注:关于搜索历史,我们这里增加了一个判断,即当搜索关键词不为空时,才会拿到搜索关键词给到搜索历史里面,否则,每一次不输入值也去点击输入框以外,会将一个空值传给搜索历史,导致搜索历史中会有空值得显示,这也是一个`bug得解决。同时还进一步将代码进行优化,用到filter达到历史去重得效果,即判断新拿到得搜索关键词是否与已有得搜索历史中的搜索关键词相同,同则过滤掉先前的那个,并使用到unshift向数组开头增加这个作为新的历史记录。

  • 历史缓存
    onShow: function () {  //每次显示变动就去获取缓存,给history,并for出来。
      // console.log('a')
      this.setData({
        history: wx.getStorageSync("history") || []
      })
    }复制代码

    解析:虽然上一步将拿到的搜索记录存入到了搜索历史,但是还不能显示出来,让数据源拿到数据,这里要做一个历史缓存的操作。onShow为监听页面显示,每次在搜素建议功能后进行点击歌名出现搜索结果栏时触发,此时将上一步拿到的historygetStorageSync进行本地缓存,使得在刷新或者跳转时,不会讲搜索历史丢失,一直保存下来。

  • 删除历史
    clearHistory: function () {  // 清空page对象data的history数组 重置缓存为[](空)
        const that = this;
        wx.showModal({
          content: '确认清空全部历史记录',
          cancelColor: '#DE655C',
          confirmColor: '#DE655C',
          success(res) {
            if (res.confirm) { // 点击确认
              that.setData({
                history: []
              })
              wx.setStorageSync("history", []) //把空数组给history,即清空历史记录
            } else if (res.cancel) {
            }
          }
        })
      }复制代码

    解析:showModal() 方法用于显示对话窗,当点击删除按钮时触发,显示出确认清空全部历史记录的窗口,并有两个点击按钮:确认取消;当点击确认时,将history数组中的内容重置为空,即达到清空搜索历史中的数据的功能;同时也需要将此时没有数据的的搜索历史进行缓存。点击取消,提示窗消失,界面不会发生任何变化。

8.歌曲跳转播放播放

  • 传值跳转播放界面
    // 先来看看handlePlayAudio绑定的地方
    // <view wx:for="{{searchResult}}" wx:key="index" class=&#39;search_result_song_item&#39; data-id="{{item.id}}" bindtap=&#39;handlePlayAudio&#39;>
    // 以下为js:
    handlePlayAudio: function (e) { //event 对象,自带,点击事件后触发,event有type,target,timeStamp,currentTarget属性
      // console.log(e)   // 打印出返回参数内容
      const musicId = e.currentTarget.dataset.id; //获取到event里面的歌曲id赋值给musicId
      wx.navigateTo({                       //获取到musicId带着完整url后跳转到play页面
        url: `../play/play?musicId=${musicId}`  // 跳转到已经传值完成的歌曲播放界面
      })
    }复制代码

    解析:handlePlayAudio绑定在每天搜索结果上,即点击搜索建议后完成搜索结果功能显示出搜索结果栏,点击每一天搜索结果都可以触发handlePlayAudio。前面也讲到过bindtap是带有参数返回,携带额外信息dataset,event本身会有一个currentTarget属性,data-id="{{item.id}}"的作用跟上面的搜索建议内的歌曲点击事件是同样的效果,item.id为执行搜索结果时接口返回给searchResult的数据,也就是搜索结果中每首歌曲各自对应的id。当点击搜索结果内的某一首歌,即将这首歌的id传给event中的dataset,数据名为dataset里的id。此时我们定义一个musicId,将event里面的歌曲id赋值给musicId,用 wx.navigateTo跳转到播放界面,同时将musicId作为播放请求接口需要的传入数据。 查看打印数据e:

    그 당시 WeChat 애플릿은 NetEase Cloud Music의 실시간 검색 기능을 모방했습니다.

9.search功能源码分享

wxml

<nav-bar></nav-bar>
<view class="wrapper">
    <!-- 上部整个搜索框 -->
    <view class="weui-search-bar">
        <!-- 返回箭头按钮 -->
        <view class="weui-search-bar__cancel-btn" bindtap="back">
            <image class="return-pic" src="../../image/search_return.png" bindtap="cancel" />
        </view>
        <!-- 搜索栏 -->
        <view class="weui-search-bar__form">
            <view class="weui-search-bar__box">
                <input focus=&#39;true&#39; type="text" class="weui-search-bar__input" placeholder="大家都在搜 " placeholder-style="color:#eaeaea" value=&#39;{{inputValue}}&#39; bindinput="getSearchKey" bindblur="routeSearchResPage" bindconfirm="searchOver" />
            </view>
            <!-- 点击×可以清空正在输入 -->
            <view class="clean-bar">
                <image class="{{showClean ? &#39;header_view_hide&#39; : &#39;clean-pic&#39;}}" src="../../image/search_delete.png" bindtap="clearInput" />
            </view>
        </view>
        <!-- 跳转歌手分类界面 -->
        <view class="songer">
            <image class="songer-pic" src="../../image/search_songner.png" bindtap="singerPage" />
        </view>
    </view>
    <!-- 搜索建议 -->
    <view class="{{showSongResult ? &#39;search_suggest&#39; : &#39;header_view_hide&#39;}}">
        <view wx:for="{{searchSuggest}}" wx:key="index" class=&#39;search_result&#39; data-value=&#39;{{item.keyword}} &#39; bindtap=&#39;fill_value&#39;>
            <image class="search-pic" src="../../image/search_search.png"></image>
            <view class="search_suggest_name">{{item.keyword}}</view>
        </view>
    </view>
    <!-- 搜索结果 -->
    <view class="{{showSearchResult ? &#39;header_view_hide&#39; : &#39;search_result_songs&#39;}}">
        <view class="search-title">
            <text class="songTitle">单曲</text>
            <view class="openBox">
                <image class="openTap" src="../../image/search_openTap.png" />
                <text class="openDes">播放全部</text>
            </view>
        </view>
        <view wx:for="{{searchResult}}" wx:key="index" class=&#39;search_result_song_item&#39; data-id="{{item.id}}" bindtap=&#39;handlePlayAudio&#39;>
            <view class=&#39;search_result_song_song_name&#39;>{{item.name}}</view>
            <view class=&#39;search_result_song_song_art-album&#39;>
                {{item.artists[0].name}} - {{item.album.name}}
            </view>
            <image class="broadcast" src="../../image/search_nav-open.png" />
            <image class="navigation" src="../../image/mine_lan.png" />
        </view>
    </view>
    <!-- 搜索历史 -->
    <view class="{{showView?&#39;option&#39;:&#39;header_view_hide&#39;}}">
        <view class="history">
            <view class="history-wrapper">
                <text class="history-name">历史记录</text>
                <image bindtap="clearHistory" class="history-delete" src="../../image/search_del.png" />
            </view>
            <view class="allhistory">
                <view class="allhistorybox" wx:for="{{history}}" wx:key="index" data-value=&#39;{{item}}&#39; data-index="{{index}}" bindtap="fill_value">
                    <text class="historyname">{{item}}</text>
                </view>
            </view>
        </view>
    </view>
    <!-- 热搜榜 -->
    <view class="{{showView?&#39;option&#39;:&#39;header_view_hide&#39;}}">
        <view class="ranking">
            <text class="ranking-name">热搜榜</text>
        </view>
        <view class="rankingList">
            <view class="rankingList-box" wx:for="{{hots}}" wx:key="index">
                <view wx:if="{{index <= 2}}">
                    <text class="rankingList-num" style="color:red">{{index+1}}</text>
                    <view class="song">
                        <text class="rankigList-songname" style="color:black;font-weight:600" data-value="{{item.first}}" bindtap=&#39;fill_value&#39;>
                            {{item.first}}
                        </text>
                        <block wx:for="{{detail}}" wx:key="index">
                            <text class="rankigList-hotsong" style="color:red">{{item.hot}}</text>
                        </block>
                    </view>
                </view>
                <view wx:if="{{index > 2}}">
                    <text class="rankingList-num">{{index+1}}</text>
                    <view class="othersong">
                        <text class="rankigList-songname" data-value="{{item.first}}" bindtap=&#39;fill_value&#39;>
                            {{item.first}}
                        </text>
                    </view>
                </view>
            </view>
        </view>
    </view>
</view>复制代码

wxss

  /* pages/search/search.wxss */
.weui-search-bar{
    position:relative;
    /* padding:8px; */
    display:flex;
    box-sizing:border-box;
    /* background-color:#EDEDED; */
    -webkit-text-size-adjust:100%;
    align-items:center
}
.weui-icon-search{
    margin-right:8px;font-size:14px;vertical-align:top;margin-top:.64em;
    height:1em;line-height:1em
}
.weui-icon-search_in-box{
    position:absolute;left:12px;top:50%;margin-top:-8px
}
.weui-search-bar__text{
    display:inline-block;font-size:14px;vertical-align:top
}
.weui-search-bar__form{
    position:relative;
    /* flex:auto;
    border-radius:4px;
    background:#FFFFFF */
    border-bottom: 1px solid #000;
    margin-left: 30rpx;
    width: 400rpx;
    padding-right: 80rpx;
}
.weui-search-bar__box{
    position:relative;
    padding-right: 80rpx;
    box-sizing:border-box;
    z-index:1;
}
.weui-search-bar__input{
    height:32px;line-height:32px;font-size:14px;caret-color:#07C160
}
.weui-icon-clear{
    position:absolute;top:0;right:0;bottom:0;padding:0 12px;font-size:0
}
.weui-icon-clear:after{
    content:"";height:100%;vertical-align:middle;display:inline-block;width:0;overflow:hidden
}
.weui-search-bar__label{
    position:absolute;top:0;right:0;bottom:0;left:0;z-index:2;border-radius:4px;
    text-align:center;color:rgba(0,0,0,0.5);background:#FFFFFF;line-height:32px
}
.weui-search-bar__cancel-btn{
    margin-left:8px;line-height:32px;color:#576B95;white-space:nowrap
}
.clean-bar {
    /* width: 20rpx;
    height: 20rpx; */
}
.clean-pic {
    width: 20rpx;
    height: 20rpx;
    float: right;
    position: absolute;
     margin-top: -30rpx; 
     margin-left: 450rpx;
}
.return-pic {
    width: 60rpx;
    height: 60rpx;
    margin-left: 20rpx;
}
.songer-pic{
    width: 60rpx;
    height: 60rpx;
    margin-left: 40rpx;
}
.wrapper {
    width: 100%;
    height: 100%;
    position: relative;
    z-index: 1;
}
.poster {
    width: 670rpx;
    height: 100rpx;
    margin-top: 40rpx;
    margin-left: 40rpx;
}
.postername {
    font-size: 15rpx;
    position: absolute;
    margin-top: 10rpx;
    margin-left: 10rpx;
}
.poster-outside {
    border-radius: 10rpx;
    background-color: slategrey;
}
.poster-pic0 {
    width: 80rpx;
    height: 80rpx;
    margin-top: 10rpx;
}
.test-title {
    position: absolute;
    font-size: 30rpx;
    line-height: 100rpx;
    margin-left: 20rpx;
    color: red;
}
.test-age {
    position: absolute;
    font-size: 30rpx;
    line-height: 100rpx;
    margin-left: 80rpx;
}
.test-red {
    position: absolute;
    font-size: 30rpx;
    line-height: 100rpx;
    margin-left: 270rpx;
    color: red;
}
.test-black {
    position: absolute;
    font-size: 30rpx;
    line-height: 100rpx;
    margin-left: 400rpx;
}
.poster-pic1 {
    width: 80rpx;
    height: 80rpx;
    margin-left: 510rpx;
}
.history {
    margin: 50rpx 0 0 40rpx;
}
.history-name {
    font-size: 28rpx;
    font-weight: 550;
}
.history-delete {
    width: 50rpx;
    height: 50rpx;
    position: absolute;
    margin-left: 510rpx;
}
.allhistory {
    display: flex;
    flex-wrap: wrap;
}
.allhistorybox {
    margin: 30rpx 20rpx 0 0;
    background-color: dimgray;
    border-radius: 10rpx;
}
.historyname {
    font-size: 28rpx;
    margin: 20rpx 20rpx 20rpx 20rpx;
}
.ranking {
    margin-left: 40rpx;
    margin-top: 100rpx;
}
.ranking-name {
    font-size: 28rpx;
    color: black;
    font-weight: 550;
}
.rankingList {
    margin-left: 50rpx;
    margin-top: 30rpx;
}
.rankingList-box {
    width: 100%;
    height: 80rpx;
    margin: 0 0 30rpx 0;
}
.rankingList-num {
    line-height: 80rpx;
    align-content: center;
}
.song {
    margin: -100rpx 0 0 30rpx;
    display: flex;
    flex-wrap: wrap;
}
.othersong {
    margin-top: -100rpx;
    margin-left: 70rpx;
}
.rankigList-songname {
    font-size: 30rpx;
    margin-left: 40rpx;
}
.rankigList-hotsong {
    font-size: 25rpx;
    font-weight: 550;
    margin-top: 45rpx;
    margin-left: 20rpx;
}
.rankigList-hotnum {
    float: right;
    position: absolute;
    line-height: 80rpx;
    margin-left: 600rpx;
    font-size: 20rpx;
    color: darkgrey;
}
.rankingList-songdes {
    font-size: 22rpx;
    color: darkgrey;
    position: absolute;
    margin-left: 60rpx;
    margin-top: -30rpx;
}
.search_suggest{
    width:570rpx;
    margin-left: 40rpx;
    position: absolute;
    z-index: 2;
    background: #fff;
    box-shadow: 1px 1px 5px #888888;
    margin-top: 20rpx;
}
.header_view_hide{
    display: none;
  }
.search-pic {
      width: 50rpx;
      height: 50rpx;
     margin-top: 25rpx;
     margin-left: 20rpx;
}
.search-title {
    color: #000;
    margin-left: 15rpx;
    margin-bottom: 30rpx;
}
.songTitle {
    font-size: 30rpx;
    font-weight: 700;
}
.openBox {
    float: right;
    border-radius: 30rpx;
    margin-right: 30rpx;
    border-radius: 30rpx;
    border-bottom: 1px solid #eaeaea;
}
.openTap {
    width: 30rpx;
    height: 30rpx;
    position: absolute;
    margin: 6rpx 10rpx 0rpx 20rpx;
}
.openDes {
    font-size: 25rpx;
    color: rgba(0,0,0,0.5);
    margin-right: 20rpx;
    margin-left: 58rpx;
}
.broadcast {
    width: 20px;
    height: 20px;
    display: inline-block;
    overflow: hidden;
    float: right;
    margin-top: -70rpx;
    margin-left: -120rpx;
    margin-right: 80rpx;
}
.navigation {
    width: 20px;
    height: 20px;
    display: inline-block;
    overflow: hidden;
    float: right;
    margin-top: -70rpx;
    margin-right: 20rpx;
}
  .search_result{
    /* display: block;
    font-size: 14px;
    color: #000000;
    padding: 15rpx;
    margin: 15rpx; */
    /* border-bottom: 1px solid #eaeaea; */
    /* float: right; */
    /* margin-left: -450rpx; */
    width: 570rpx;    
    height: 100rpx;
    border-bottom: 1px solid #eaeaea;
  }
  .search_suggest_name {
    display: block;
    float: right;
    position: absolute;
    margin-left: 85rpx;
    margin-top: -46rpx;
    font-size: 14px;
    color: darkgrey;
    /* padding: 15rpx;
    margin: 15rpx; */
  }
  .search_result_songs{
    margin-top: 10rpx;
    width: 100%;
    height: 100%;
    margin-left: 15rpx;
  }
  .search_result_song_item{
     display: block;
     margin: 15rpx;
     border-bottom: 1px solid #EDEEF0;
  }
  .search_result_song_song_name{
    font-size: 15px;
    color: #000000;
    margin-bottom: 15rpx;
  }
  .search_result_song_song_art-album{
    font-size: 11px;
    color: #000000;
    font-weight:lighter;
    margin-bottom: 5rpx;
  }复制代码

js

// pages/search/search.js
// const API = require('../../utils/req')
const $api = require('../../utils/api.js').API;
const app = getApp();
Page({
  data: {
    inputValue: null,//输入框输入的值
    history: [], //搜索历史存放数组
    searchSuggest: [], //搜索建议
    showView: true,//组件的显示与隐藏
    showSongResult: true,
    searchResult: [],//搜索结果
    searchKey: [],
    showSearchResult: true,
    showClean: true,
    hots: [], //热门搜索
    detail: [
      {
        hot: 'HOT'
      }
    ],
  },
  onLoad: function (options) {
    wx.request({
      url: 'http://neteasecloudmusicapi.zhaoboy.com/search/hot/detail',
      data: {
      },
      header: {
        "Content-Type": "application/json"
      },
      success: (res) => {
        // console.log(res)
        this.setData({
          hots: res.data.result.hots
        })
      }
    })
  },
  // 点x将输入框的内容清空
  clearInput: function (e) {
    // console.log(e)
    this.setData({
      inputValue: '',
      showSongResult: false,
      showClean: true // 隐藏清除按钮
    })
  },
  //实现直接返回返回上一页的功能,退出搜索界面
  back: function () {
    wx: wx.navigateBack({
      delta: 0
    });
  },
  // 跳转到歌手排行界面
  singerPage: function () {
    // console.log('a')
    wx.navigateTo({
      url: `../singer/singer`
    })
  },
  //获取input文本并且实时搜索
  getSearchKey: function (e) {
    if(e.detail.cursor === 0){
      this.setData({
        showSongResult: false
      })
      return
    }
    // console.log(e.detail) //打印出输入框的值
    if (e.detail.cursor != this.data.cursor) { //实时获取输入框的值
      this.setData({
        showSongResult: true,
        searchKey: e.detail.value
      })
      this.searchSuggest();
    }
    if (e.detail.value) { // 当input框有值时,才显示清除按钮'x'
      this.setData({
        showClean: false  // 出现清除按钮
      })
    }
  },
  // 搜索建议
  searchSuggest() {
    $api. getSearchSuggest({ keywords: this.data.searchKey, type: 'mobile' }).then(res => {
      //请求成功 
      // console.log(res);
      if(res.statusCode === 200){
        this.setData({
          searchSuggest: res.data.result.allMatch 
        })
       }
    })
    .catch(err => {
      //请求失败
      console.log('错误')
    })
  },
  // 搜索结果
  searchResult: function () {
    // console.log(this.data.searchKey)
    $api.getSearchResult({ keywords: this.data.searchKey, type: 1, limit: 100, offset: 2 }).then(res => {
      // 请求成功
      if (res.statusCode === 200) {
        // console.log(res)
        this.setData({
          searchResult: res.data.result.songs,
          showSearchResult: false,
          showView: false,
        });
      }
    })
    .catch(ree => {
      //请求失败
    })
  },
  handlePlayAudio: function (e) { //event 对象,自带,点击事件后触发,event有type,target,timeStamp,currentTarget属性
    // console.log(e)
    const musicId = e.currentTarget.dataset.id; //获取到event里面的歌曲id赋值给musicId
    wx.navigateTo({                                 //获取到musicId带着完整url后跳转到play页面
      url: `../play/play?musicId=${musicId}`
    })
  },
  // input失去焦点函数
  routeSearchResPage: function (e) {
    // console.log(e)
    // console.log(e.detail.value)
    // console.log(this.data.searchKey)
    // console.log(this.data.searchKey.length)  
    if (this.data.searchKey.length > 0) {  // 当搜索框有值的情况下才把搜索值存放到历史中,避免将空值存入历史记录
      let history = wx.getStorageSync("history") || [];
      // console.log(history);
      history = history.filter(item => item !== this.data.searchKey)  // 历史去重
      history.unshift(this.data.searchKey)
      wx.setStorageSync("history", history);
    }  
  },
  // 清空page对象data的history数组 重置缓存为[](空)
  clearHistory: function () {
    const that = this;
    wx.showModal({
      content: '确认清空全部历史记录',
      cancelColor: '#DE655C',
      confirmColor: '#DE655C',
      success(res) {
        if (res.confirm) {
          that.setData({
            history: []
          })
          wx.setStorageSync("history", []) //把空数组给history,即清空历史记录
        } else if (res.cancel) {
        }
      }
    })
  },
    // 搜索结果完成后(再次点击输入框)
  searchOver: function () {
    this.searchSuggest();  // 执行搜索结果
    this.searchResult()
  },
  // 点击热门搜索值或搜索历史,填入搜索框
  fill_value: function (e) {
    console.log(e)
    // console.log(this.data.history)
    // console.log(e.currentTarget.dataset.value)
    this.setData({
      searchKey: e.currentTarget.dataset.value,//点击=把值给searchKey,让他去搜索
      inputValue: e.currentTarget.dataset.value,//在输入框显示内容
      showSongResult: false, //给false值,隐藏搜索建议页面
      showClean: false // 显示 清除按钮
    })
    this.searchResult(); //执行搜索功能
  },
  /**
   * 生命周期函数--监听页面显示
   */
  //每次显示变动就去获取缓存,给history,并for出来。
  onShow: function () {
    // console.log('a')
    this.setData({
      history: wx.getStorageSync("history") || []
    })
  },
})复制代码

api.js

const app = getApp();
// method(HTTP 请求方法),网易云API提供get和post两种请求方式
const GET = 'GET';
const POST = 'POST';
// 定义全局常量baseUrl用来存储前缀
const baseURL = 'http://neteasecloudmusicapi.zhaoboy.com';
function request(method, url, data) {
  return new Promise(function (resolve, reject) {
    let header = {
      'content-type': 'application/json',
      'cookie': app.globalData.login_token
    };
    wx.request({
      url: baseURL + url,
      method: method,
      data: method === POST ? JSON.stringify(data) : data,
      header: header,
      success(res) {
        //请求成功
        //判断状态码---errCode状态根据后端定义来判断
        if (res.data.code == 200) {  //请求成功
          resolve(res);
        } else {
          //其他异常
          reject('运行时错误,请稍后再试');
        }
      },
      fail(err) {
        //请求失败
        reject(err)
      }
    })
  })
}
const API = {
  getSearchSuggest: (data) => request(GET, `/search/suggest`, data),  // 搜索建议接口
  getSearchResult: (data) => request(GET, `/search`, data),  // 搜索结果接口
};
module.exports = {
  API: API
}复制代码

总结

其实一点一点的捋清楚会发现也不是很难操作,首先思路要清晰,知道每一个功能是什么作用,同时在调试是时候去发现一些bug,再去对代码进行优化。关于搜索这个功能用处广泛,希望本次的分享能给大家带来一点用处。

相关学习推荐:微信公众号开发教程javascript视频教程

위 내용은 그 당시 WeChat 애플릿은 NetEase Cloud Music의 실시간 검색 기능을 모방했습니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.im에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제