ホームページ > 記事 > WeChat アプレット > 当時、WeChat アプレットは NetEase Cloud Music のリアルタイム検索機能を模倣していました。
関連する学習の推奨事項: WeChat ミニ プログラム チュートリアル
私のミニ プログラムについてago 私のパートナーが NetEase Cloud Music アプレットの音楽再生機能を詳しく紹介してくれました。フロントエンドの初心者として、私はしばらく勉強しています。最近とても忙しくて、リアルタイム検索のコンテンツを書き出していませんでした皆さんと共有するのに間に合うように (実際、コードと関数は以前にほぼ書かれています)、今日はその詳細と最適化について説明します。
検索機能は非常に一般的で、さまざまな場所で使用できます。役立つことを皆さんと共有できれば幸いです。同時に、不備な点があれば、指摘していただければ幸いです。変更についていくつかの提案をしてください。Xiaomi、ありがとうございます!
入力ボックスへの値の入力から検索候補、検索結果、そして最後に曲の再生にジャンプするまで、リアルタイム検索機能でも API インターフェイスを使用する必要があります。もはや、単に拾うだけではなく、値を渡すことが重要ですが、同時に、さまざまな機能の下でさまざまなコンテナ ボックスを非表示にしたり表示したり、検索に関連する詳細や最適化も行われます。見てみましょう!
ヘッド検索バー: 左リターン矢印、中央入力ボックスを選択すると、右側の歌手ランキングページにジャンプします。クリアボタンは非表示にし、入力値を入力した後にのみ表示されます。
履歴レコードをたどる場合、ここでの各検索レコード値が等距離分布の小さな部分であるように、検索値はこの小さな部分の長さと同じ長さになります。ここではそれが使用されます。得られるものは display: flex;flex-wrap: Wrap;
. このインターフェイス スタイルに興味のある友人は、後でコード全体を確認できます。
次はホット検索リストです。ここではあまり強調されていません。データを要求するインターフェースを開始し、データを埋め込んで表示するだけです。
入力が完了すると検索候補が表示され、非常に立体的でページ全体をカバーします。3 つを実現するには、box-shadow: 1px 1px 5px #888888
を使用します。 -次元効果.z-index
にはカバー力の効果があります。
検索候補の 1 つをクリックするか、検索履歴またはホット検索をクリックすると、検索結果が表示されます。同時に、インターフェイス上の他のすべてのコンテナが非表示になります。これが実際の非表示です。コンテナボックスの外観については、細かい点なので機能の後半で詳しく説明します。ここではまず、次の関数でコンポーネント (コンテナー) の内容が表示されないように、コンポーネント (コンテナー) を非表示および表示する方法について説明します。これらのコンテナのヘッダーの内容のみがここに配置されます。
showClean、showSongResult、showSearchResult、showViewはそれぞれのみを使用してデータを直接取得しますdata
データ ソースに配置されます。これはtrue
wx.requestこれらのコンテナはデフォルトで
:(コロン) の前のスタイルになり、false の場合、デフォルトは
:(コロン) の後のスタイルになります;
header_view_hideスタイルが設定されます
display: none;、つまり非表示で表示されないため、特定のメソッドで、
showClean、showSongResult、showSearchResult、showViewを
trueに変更できます。または、
falseを使用して、これらのコンテナをそれぞれ表示または非表示にすることができます。
インターフェイスのカプセル化
インターフェイスのカプセル化については、私の友人が前の記事ですでにわかりやすく説明しています。ここではこれ以上説明しません。現在使用されているのと同じ関数も同様です。データをリクエストするようにインターフェイスを調整するだけと同じくらい簡単で、インターフェイスに値を渡し、値を受け取った後にインターフェイスが対応するデータを返すようにする必要があります。検索インターフェイスでは、検索候補にインターフェイスを使用し、の検索結果。ホット検索リストの場合、最も基本的な
api.js<pre class="brush:php;toolbar:false"><!-- 点击×可以清空正在输入 -->
<image class="{{showClean ? &#39;header_view_hide&#39; : &#39;clean-pic&#39;}}" src="../../image/search_delete.png" bindtap="clearInput" />复制代码</pre>
検索機能用に設計するデータは多数あります。詳細にリストできます: 入力値1. データ ソース分析
searchKey、それ自体が入力ボックスの値であり、検索候補に渡すために使用されます。検索キーワード;
searchSuggest、検索キーワードを取得した後に検索候補インターフェイスによって返されるデータ (検索候補); 検索結果
searchResult、検索候補の 1 つをクリックすると、値が検索ボックスに入力されて検索されます。キーワード
searchKey がこの値に変更されて検索結果インターフェイスに渡され、返されたデータが
searchResult に格納されます。 ; 最後に、検索履歴
history が検索のたびに実行され、元の入力ボックスの値が
history データ ソースに入力されます。その他のデータソースに関しては、コンポーネントの非表示と表示、つまり各ピースのコンテナボックスがどのような状況で非表示になり、どのような状況で表示されるかが関係します。
数据源展示
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
框内容分析<input focus='true' type="text" class="weui-search-bar__input" placeholder="大家都在搜 " placeholder-style="color:#eaeaea" value='{{inputValue}}' 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
:
解析:
疑惑的小伙伴可以将代码运行,打印出以上设计的几个数据进行分析
①当此时输入框的值和bindinput
返回的输入框的值时一样的,就将输入框的值赋给搜索关键词searchKey
,此时显示搜索建议栏(showSongResult
写在wxml
当中,用来控制该容器是否展示,可以看到最后面发的整个界面的wxml中的详情);同时searchSuggest
事件(方法)生效。
②当输入框没值时,清除按钮x
是不会显示的,只有当输入框有值是才会出现清除按钮x
③当输入框没有值时,隐藏搜索建议栏,其实本身我们最开始进入这个页面时,输入框是没值的,搜索建议栏也是不展示的,为没进行输入就没有数据;但是当我们输入内容后,出现搜索建议,此时我们点击清除按钮,输入框的内容没了,但是搜索建议还停留在之前的状态,所以这里我们优化一下,让showSongResult
为false
,即一清空输入框内容,隐藏掉搜索建议栏。另外我们为什么要return
呢?这里还有一个bug
,当清除输入框内容后,再输入发现已经不再具备搜索功能了,所以需要return
回到初始的状态,就能重新进行输入并且搜索。同时当输入框为空时进行搜索功能还会报错,这也是一个bug
,所以有了return
即使空值搜索也会立马回到初始状态,解决了空值搜索报错的bug
。
清空输入框内容
clearInput: function (e) { // console.log(e) this.setData({ inputValue: '', // 将输入框的值为空 showSongResult: false, // 隐藏搜索建议栏 showClean: true // 隐藏清除按钮 (不加此项会出现清除输入框内容后清除按钮不消失的bug) }) },复制代码点击清除按钮,就让
inputValue
值为空,即输入框的内容为空,达到清除文本的效果;在获取输入框文本那里我们也提到了清除按钮,也提到输入框文本清空时,之前的搜索建议栏还会留下,所以这里我们让showSongResult
为false
,使得搜索建议栏隐藏。清除文本的同时再隐藏掉清除按钮。
取消搜索返回上页
back: function () { wx: wx.navigateBack({ // 关闭当前页面,返回上一页面或多级页面 delta: 0 // 返回的页面数,如果 delta 大于现有页面数,则返回到首页 }); }复制代码这里用到的小程序自带的返回页面的功能,当给
delta
值为0即回到上一个页面。(可去文档查看详情)
跳转歌手排行榜
singerPage: function () { wx.navigateTo({ // 保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面 url: `../singer/singer` // 要跳转去的界面 }) },复制代码在微信官方文档可以查看到
navigateTo
的功能及其属性,这里不多讲。
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
埋好的空就能拿到数据,将搜索建议栏显示出。
// 看看 wxml中的点击事件展示 // <view wx:for="{{searchSuggest}}" wx:key="index" class='search_result' data-value='{{item.keyword}} ' bindtap='fill_value'> // 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;返回参数
event
,event
本身会有一个currentTarget
属性;这里解释一下data-value='{{item.keyword}}
=>data
就是dataset
;item.keyword
是搜索建议完成之后返回的数据赋值给searchSuggest
里面的某个数据;当一点击搜索建议里面的某一个歌名时,此歌名即为此时的item.keyword
,并将该值存入点击事件的参数event
内的dataset
。大家也可操作一波打印出来看看结果,currentTarget.dataset.value
就是我们点击的那个歌曲名字。所以一点击搜索建议中的某个歌名或者搜索历史以及热搜榜单中的某个歌名时,点击事件生效,返回这样该歌曲名称,并将该值给到此时的searchKey
和inputValue
,此时输入框的值会变成该值,搜索结果的关键词的值也会变成该值;同时this.searchResult()
可让此时执行搜索结果功能。showSongResult: false
这里还将搜索建议栏给隐藏了。增加showClean: false
是为了解决点击后输入框有值但不显示清除按钮的bug
。 查看打印数据e
:
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
事件,即点击完成按钮时触发。当我们搜索完成之后,界面上还有搜索栏以及搜索结果的显示,此时我们再次点击输入框,可以进行清除文本,同时我们还需要增加一个功能,即在此种情况下,我们还可以进行再次输入并且返回搜索建议以及点击搜索建议中的歌曲时再次执行搜索结果功能。
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
为监听页面显示,每次在搜素建议功能后进行点击歌名出现搜索结果栏时触发,此时将上一步拿到的history
用getStorageSync
进行本地缓存,使得在刷新或者跳转时,不会讲搜索历史丢失,一直保存下来。
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
数组中的内容重置为空,即达到清空搜索历史中的数据的功能;同时也需要将此时没有数据的的搜索历史进行缓存。点击取消,提示窗消失,界面不会发生任何变化。
// 先来看看handlePlayAudio绑定的地方 // <view wx:for="{{searchResult}}" wx:key="index" class='search_result_song_item' data-id="{{item.id}}" bindtap='handlePlayAudio'> // 以下为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
:
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='true' type="text" class="weui-search-bar__input" placeholder="大家都在搜 " placeholder-style="color:#eaeaea" value='{{inputValue}}' bindinput="getSearchKey" bindblur="routeSearchResPage" bindconfirm="searchOver" /> </view> <!-- 点击×可以清空正在输入 --> <view class="clean-bar"> <image class="{{showClean ? 'header_view_hide' : 'clean-pic'}}" 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 ? 'search_suggest' : 'header_view_hide'}}"> <view wx:for="{{searchSuggest}}" wx:key="index" class='search_result' data-value='{{item.keyword}} ' bindtap='fill_value'> <image class="search-pic" src="../../image/search_search.png"></image> <view class="search_suggest_name">{{item.keyword}}</view> </view> </view> <!-- 搜索结果 --> <view class="{{showSearchResult ? 'header_view_hide' : 'search_result_songs'}}"> <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='search_result_song_item' data-id="{{item.id}}" bindtap='handlePlayAudio'> <view class='search_result_song_song_name'>{{item.name}}</view> <view class='search_result_song_song_art-album'> {{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?'option':'header_view_hide'}}"> <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='{{item}}' data-index="{{index}}" bindtap="fill_value"> <text class="historyname">{{item}}</text> </view> </view> </view> </view> <!-- 热搜榜 --> <view class="{{showView?'option':'header_view_hide'}}"> <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='fill_value'> {{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='fill_value'> {{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 中国語 Web サイトの他の関連記事を参照してください。