Home  >  Article  >  WeChat Applet  >  In those years, the WeChat applet imitated the real-time search function of NetEase Cloud Music

In those years, the WeChat applet imitated the real-time search function of NetEase Cloud Music

coldplay.xixi
coldplay.xixiforward
2020-09-14 13:18:363917browse

In those years, the WeChat applet imitated the real-time search function of NetEase Cloud Music

Related learning recommendations: WeChat mini program tutorial

Preface

My mini program some time ago My partner has introduced the music playback function of the NetEase Cloud Music applet in detail. As a front-end novice, I have been learning for a while. I have been very busy recently and did not write out the real-time search content in time to share with you (in fact, the code and The function has almost been written before), so today I will tell you some details and optimizations in it.

The search function is very common and can be used in many places. I hope I can share useful things with you. At the same time, if there are any deficiencies, I hope you guys can point them out and give some suggestions for modifications. Xiaomi Thank you very much!

We also need to use the API interface in the real-time search function, from inputting values ​​​​in the input box to search suggestions, then to search results, and finally to jumping to song playback. It is no longer as simple as picking up. Passing values ​​​​is critical. , at the same time, the hiding and display of different container boxes under different functions, as well as some details and optimization involved in search. Let’s take a look!

Interface preview

In those years, the WeChat applet imitated the real-time search function of NetEase Cloud Music

Interface analysis

In the head search bar: left return arrow, middle input box , the singer ranking page on the right jumps; as for the clear button, we hide it and it will only appear after entering the input value.

When going down the history record, like each search record value here is a small piece of equidistant distribution, the search value is as long as the length of this small piece, here it is used What you get is display: flex;flex-wrap: wrap;. Friends who are interested in this interface style can take a look at the entire code later.

Next is the hot search list. There is not much emphasis here. Just initiate an interface to request data, bury the data and display it.

The search suggestions will appear after the input is completed, and they are very three-dimensional and cover the entire page. Use box-shadow: 1px 1px 5px #888888 to achieve a three-dimensional effect.z-index has the effect of coverage.

The search results will appear after you click on one of the search suggestions or click on the search history or hot search. At the same time, all other containers on the interface will be hidden. This is actually the hiding and appearance of a container box. It’s a small detail, we will talk about it in detail later in the function. Here we first talk about how to hide and display components (containers), so as to avoid seeing these contents in the following functions.

Header display of several containers

<!-- 点击×可以清空正在输入 -->
<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;}}">复制代码

Analysis: Only the contents of the headers of these containers are placed here. showClean, showSongResult, showSearchResult, showView are placed in the data data source respectively, which is true Then these containers default to the style before :(colon), if false, the default is the style after :(colon); header_view_hide The style is set to display: none;, that is, hidden and not displayed; so in a certain method, you can change showClean, showSongResult, showSearchResult, showView to true Or false can make these containers show or hide respectively.

Interface encapsulation

Interface encapsulation My friend has already explained it very clearly in the previous article. We will not explain it more here. The same functions used now are also It is not just as simple as just adjusting the interface to request data, we need to pass the value to the interface, and let the interface return the corresponding data to us after receiving the value; in the search interface, we use the interface for search suggestions and search results. For the hot search list, we only use the most basic wx.request to obtain data directly

api.js

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

Real-time search function:

1. Data source analysis

There will be a lot of data that we design for a search function. We can list it in detail: input value inputValue, obtained during input; hot search Ranking data hots, obtained through hot search interface; search keyword searchKey, itself is the value of the input box, used to pass to search suggestions as search keywords; searchSuggest, the data (search suggestions) returned by the search suggestion interface after getting the search keywords; search results searchResult, when you click on one of the search suggestions, the value will be filled in the search box, and then search The keyword searchKey will be changed to this value and passed to the search result interface, and the returned data will be put into searchResult; finally, the search history history will be performed every time Search and put the value of the original input box into the history data source. Regarding other data sources, it involves component hiding and display, that is, under what circumstances is the container box of each piece hidden and under what circumstances is it displayed.

数据源展示

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:

In those years, the WeChat applet imitated the real-time search function of 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:

    In those years, the WeChat applet imitated the real-time search function of 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:

    In those years, the WeChat applet imitated the real-time search function of 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视频教程

The above is the detailed content of In those years, the WeChat applet imitated the real-time search function of NetEase Cloud Music. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:juejin.im. If there is any infringement, please contact admin@php.cn delete