Maison  >  Article  >  Applet WeChat  >  Dans ces années-là, l'applet WeChat imitait la fonction de recherche en temps réel de NetEase Cloud Music

Dans ces années-là, l'applet WeChat imitait la fonction de recherche en temps réel de NetEase Cloud Music

coldplay.xixi
coldplay.xixiavant
2020-09-14 13:18:363825parcourir

Dans ces années-là, l'applet WeChat imitait la fonction de recherche en temps réel de NetEase Cloud Music

Recommandations d'apprentissage associées : Tutoriel du mini programme WeChat

Avant-propos

Ma petite application du temps il y a Mon partenaire a présenté en détail la fonction de lecture de musique de l'applet NetEase Cloud Music. En tant que novice du front-end, j'ai été très occupé récemment et je n'ai pas écrit le contenu de recherche en temps réel. à temps pour partager avec vous (en fait, le code et la fonction ont déjà été presque écrits), donc aujourd'hui je vais vous parler de certains des détails et des optimisations que j'y ai apportés.

La fonction de recherche est très courante et peut être utilisée dans de nombreux endroits. J'espère pouvoir partager des choses utiles avec vous. En même temps, s'il y a des lacunes, j'espère que vous pourrez les signaler et les signaler. donner quelques suggestions de modifications Xiao Merci beaucoup !

Nous devons également utiliser l'interface API dans la fonction de recherche en temps réel, de la saisie de valeurs​​dans la zone de saisie à la recherche de suggestions, puis aux résultats de recherche et enfin au passage à la lecture de la chanson. Ce n'est plus aussi simple que de recevoir, la transmission de valeur est essentielle, en même temps, le masquage et l'affichage de différentes boîtes de conteneurs sous différentes fonctions, ainsi que certains détails et optimisations impliqués dans la recherche. Jetons un coup d'oeil !

Aperçu de l'interface

Dans ces années-là, l'applet WeChat imitait la fonction de recherche en temps réel de NetEase Cloud Music

Analyse de l'interface

Dans la barre de recherche principale : flèche de retour gauche, zone de saisie du milieu , accédez à la page de classement des chanteurs à droite ; quant au bouton d'effacement, nous l'avons masqué, et il n'apparaîtra qu'après avoir saisi la valeur de saisie.

En parcourant l'enregistrement de l'historique, la valeur d'enregistrement de chaque recherche est distribuée en petits morceaux à intervalles égaux. La longueur de la valeur de recherche est la longueur du petit morceau. Ici, nous utilisons Celui qui est arrivé. display: flex;flex-wrap: wrap;. Les amis intéressés par ce style d'interface peuvent consulter l'intégralité du code plus tard.

L'étape suivante est la liste de recherche rapide. Il n'y a pas beaucoup d'accent ici, il suffit de lancer une interface pour demander des données, d'enterrer les données et de les afficher.

Les suggestions de recherche apparaîtront une fois la saisie terminée, et elles sont très tridimensionnelles et couvrent toute la page. Utilisez box-shadow: 1px 1px 5px #888888 pour obtenir un effet tridimensionnel, et z-index pour avoir une superposition. effet.

Les résultats de la recherche apparaîtront après avoir cliqué sur l'une des suggestions de recherche ou cliqué sur l'historique de recherche ou la recherche rapide. En même temps, tous les autres conteneurs de l'interface seront masqués. et l’apparence d’une boîte conteneur C’est un petit détail, nous en parlerons en détail plus tard dans la fonction. Ici on parle d'abord de comment masquer et afficher des composants (conteneurs), afin de ne pas voir ces contenus obscurcis dans les fonctions suivantes

L'affichage d'en-tête de plusieurs conteneurs

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

Analyse : Seul le contenu des en-têtes de ces conteneurs est placé ici. data est placé respectivement dans la source de données showClean,showSongResult,showSearchResult,showView. Si c'est true , ces conteneurs sont par défaut ceux devant : (deux-points). Style, s'il est faux, le style par défaut après : (deux-points) ; le style header_view_hide est défini sur display: none;, c'est-à-dire qu'il est masqué et non affiché, donc dans une certaine méthode, vous peut changer showClean,showSongResult,showSearchResult,showView en true ou falseVous pouvez afficher ou masquer ces conteneurs respectivement.

Encapsulation d'interface

L'encapsulation d'interface a été expliquée très clairement par mon ami dans l'article précédent. Nous ne l'expliquerons pas davantage ici. Les mêmes fonctions utilisées maintenant ne le sont pas non plus. c'est aussi simple que d'ajuster l'interface pour demander des données, nous devons transmettre la valeur à l'interface et laisser l'interface nous renvoyer les données correspondantes après avoir reçu la valeur dans l'interface de recherche, nous utilisons l'interface pour les suggestions de recherche et ; résultats de recherche. Pour la liste de recherche rapide, nous utilisons uniquement le plus basique wx.requestpour obtenir les données directement

api.js

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

fonction de recherche en temps réel :

1. Analyse de la source de données

Nous concevrons de nombreuses données pour une fonction de recherche, qui peuvent être répertoriées en détail : valeur d'entrée inputValue, obtenue lors de la saisie des données de la liste de recherche rapide ; hots, obtenu via l'interface de recherche chaude ; Le mot-clé de recherche searchKey, qui est la valeur de la zone de saisie, est utilisé pour transmettre aux suggestions de recherche en tant que mots-clés de recherche searchSuggest, les données (suggestions de recherche) renvoyées par le ; interface de suggestion de recherche après avoir obtenu le mot-clé de recherche searchResult, lorsque l'on clique sur l'une des suggestions de recherche, cette valeur sera renseignée dans le champ de recherche. À ce moment, le mot-clé de recherche searchKey deviendra cette valeur et transmis. à l'interface des résultats de recherche, et les données renvoyées seront placées dans searchResult; Enfin, il y a l'historique de recherche history Chaque fois qu'une recherche est effectuée, la valeur de la zone de saisie d'origine est mise dans les données history. source. Concernant les autres sources de données, cela implique le masquage et l'affichage des composants, c'est-à-dire dans quelles circonstances le cadre conteneur de chaque pièce est-il masqué et dans quelles circonstances est-il affiché.

数据源展示

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:

Dans ces années-là, l'applet WeChat imitait la fonction de recherche en temps réel de 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:

    Dans ces années-là, l'applet WeChat imitait la fonction de recherche en temps réel de 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:

    Dans ces années-là, l'applet WeChat imitait la fonction de recherche en temps réel de 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视频教程

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer