相關學習推薦:微信小程序教程
前言
前段時間我的小伙伴已經(jīng)將網(wǎng)易云音樂小程序的音樂播放功能詳細的介紹出來了,作為前端小白學習了一段時間,最近也比較忙,沒有及時將實時搜索這塊內(nèi)容及時寫出來跟大家分享(其實代碼和功能之前就寫的差不多了),那么今天就給大家講一講個人在里面的一些細節(jié)和優(yōu)化吧。
搜索功能很常見,很多地方都能用到,希望能夠給大家分享到有用的東西,同時,有不足的地方,也希望各位大佬指出,給出一些修改的意見,小白在此感謝了!
實時搜索功能里面我們也需要用到API接口,從input框輸入值到搜索建議,再到搜索結果,最后到跳轉(zhuǎn)歌曲播放,不再只是接那么簡單,傳值很關鍵,同時不同功能下不同容器框的隱藏與顯示,還有一些搜索當中涉及的細節(jié)內(nèi)容和優(yōu)化。讓我們一起來看看吧!
界面預覽

界面分析
頭部搜索欄中:左邊返回箭頭,中間輸入框,右邊歌手排行榜頁面跳轉(zhuǎn);至于清除按鈕呢我們隱藏了起來,只有在輸入輸入值之后才會出現(xiàn)。
往下時歷史記錄,像每個搜搜的記錄值這里都是一小塊一小塊等隔距離分布,搜索值有多長,這小塊就有多長,這里用到的是display: flex;flex-wrap: wrap;
,對這個界面樣式感興趣的小伙伴可以待會看看全部代碼。
接下來是熱搜榜,這里沒有太多講究,就是發(fā)起接口請求數(shù)據(jù),把數(shù)據(jù)埋進去顯示出來就行了。
搜索建議會在輸入結束后才會出現(xiàn),并且是很立體的一塊覆蓋在整個頁面上,用box-shadow: 1px 1px 5px #888888
達到立體效果,z-index
起到覆蓋的效果。
搜索結果會在點擊搜索建議中的某一條或者點擊搜索歷史或者熱搜才出現(xiàn),同時界面上其他所有的容器快都會隱藏起來,這里其實就是一個容器框的隱藏與出現(xiàn)的小細節(jié)了,待會在功能中我們會詳細講到。這里我們先講一下組件(容器)如何進行隱藏與顯示,以免下面的功能中看到這幾項內(nèi)容蒙圈
幾個容器的頭部展示
<!-- 點擊×可以清空正在輸入 --> <image class="{{showClean ? 'header_view_hide' : 'clean-pic'}}" src="../../image/search_delete.png" bindtap="clearInput" />復制代碼<!-- 搜索建議 --> <view class="{{showSongResult ? 'search_suggest' : 'header_view_hide'}}">復制代碼<!-- 搜索結果 --> <view class="{{showSearchResult ? 'header_view_hide' : 'search_result_songs'}}">復制代碼<!-- 搜索歷史 --> <view class="{{showView?'option':'header_view_hide'}}">復制代碼<!-- 熱搜榜 --> <view class="{{showView?'option':'header_view_hide'}}">復制代碼解析:這里只放了這幾塊容器的頭部的內(nèi)容,在
data
數(shù)據(jù)源中分別放了showClean,showSongResult,showSearchResult,showView
, 為true
則這幾塊容器默認為:
(冒號)前面的樣式,為false則默認為:
(冒號)后面的樣式;header_view_hide
樣式設置為display: none;
,即隱藏不顯示;所以當在某一個方法中可以去改變showClean,showSongResult,showSearchResult,showView
為true
還是false
可以讓這幾塊容器分別為顯示或是隱藏。
接口封裝
接口封裝在上一篇我的小伙伴已經(jīng)講的十分清晰了,我們這里不再多去講解了,同樣現(xiàn)在用到的功能也不只是光調(diào)接口請求數(shù)據(jù)那么簡單了,我們需要傳值給接口,讓接口收到值后再給我們返回相應的數(shù)據(jù);在搜索界面我們用到的是搜索建議以及搜索結果的接口。熱搜榜我們暫時只使用最基礎的wx.request
直接獲取數(shù)據(jù)
api.js
const API = { getSearchSuggest: (data) => request(GET, `/search/suggest`, data), // 搜索建議接口 getSearchResult: (data) => request(GET, `/search`, data), // 搜索結果接口 }復制代碼
實時搜索功能:
1.數(shù)據(jù)源分析
一個搜索功能我們設計到的數(shù)據(jù)會有很多,可以細列一下:輸入的值inputValue
,在輸入時獲取;熱搜榜數(shù)據(jù)hots
,熱搜接口獲??;搜索關鍵詞searchKey
,本身就是輸入框的值,用來傳遞給搜索建議作為搜索關鍵詞;searchSuggest
,搜索建議接口拿到搜索關鍵詞后返回的的數(shù)據(jù)(搜索建議);搜索結果searchResult
,當點擊搜索建議中的某一條,該值將填入搜索框,此時搜索關鍵詞searchKey
將變?yōu)樵撝涤謧鬟f給搜索結果接口,并返回數(shù)據(jù)放入searchResult
;最后是搜索歷史history
,每當進行一次搜索,將原本輸入框的值放到history
數(shù)據(jù)源中。關于其他數(shù)據(jù)源涉及到組件隱藏與展示,即每一塊的容器框在何種情況下隱藏,何種情況下顯示。
數(shù)據(jù)源展示
data: { inputValue: null,//輸入框輸入的值 history: [], //搜索歷史存放數(shù)組 searchSuggest: [], //搜索建議 showView: true,//組件的顯示與隱藏 showSongResult: true, searchResult: [],//搜索結果 searchKey: [], showSearchResult: true, showClean: true, hots: [] //熱門搜索 }復制代碼
2.獲取熱搜榜
這里我們直接在頁面的初始數(shù)據(jù)中調(diào)用接口,直接獲取到數(shù)據(jù)使用
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
框內(nèi)容分析<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
為輸入框為空時占位符,即還沒輸入前輸入框顯示的內(nèi)容,placeholder-style
可以去設置placeholder
的樣式;value
是輸入框的初始內(nèi)容,即自己在輸入框輸入的內(nèi)容,我們在這里直接將輸入的內(nèi)容value
直接作為了data數(shù)據(jù)源中inputValue
的內(nèi)容;bindinput
是在鍵盤輸入時觸發(fā),即我們一進行打字,就能觸發(fā)我們的自定義事件getSearchKey
,并且會返還相應數(shù)據(jù);bindblur
在輸入框失去焦點時觸發(fā),進行搜索功能時,需要在搜索框輸值,此時焦點一直在輸入框,當點擊輸入框以外的地方即輸入框失去焦點,同時觸發(fā)routeSearchResPage
事件,還會返回相應的數(shù)據(jù),在下面功能中會講到;bindconfirm
在點擊完成按鈕時觸發(fā),這里綁定一個searchOver
,用來隱藏組件(容器塊),再次觸發(fā)搜索功能,在下面的功能中也會講到。
獲取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 // 出現(xiàn)清除按鈕 }) } if(e.detail.cursor === 0){ this.setData({ // 當輸入框沒有值時,即沒有輸入時,隱藏搜索建議界面,返回到最開始的狀態(tài) showSongResult: false }) return } }復制代碼
bindinput
本身是會返回數(shù)據(jù),在代碼運行時,可以打印出來先看看;e.detail.value
即為輸入框的值,將它賦值給searchKey
; 查看打印數(shù)據(jù)e
:
解析:
疑惑的小伙伴可以將代碼運行,打印出以上設計的幾個數(shù)據(jù)進行分析
①當此時輸入框的值和bindinput
返回的輸入框的值時一樣的,就將輸入框的值賦給搜索關鍵詞searchKey
,此時顯示搜索建議欄(showSongResult
寫在wxml
當中,用來控制該容器是否展示,可以看到最后面發(fā)的整個界面的wxml中的詳情);同時searchSuggest
事件(方法)生效。
②當輸入框沒值時,清除按鈕x
是不會顯示的,只有當輸入框有值是才會出現(xiàn)清除按鈕x
③當輸入框沒有值時,隱藏搜索建議欄,其實本身我們最開始進入這個頁面時,輸入框是沒值的,搜索建議欄也是不展示的,為沒進行輸入就沒有數(shù)據(jù);但是當我們輸入內(nèi)容后,出現(xiàn)搜索建議,此時我們點擊清除按鈕,輸入框的內(nèi)容沒了,但是搜索建議還停留在之前的狀態(tài),所以這里我們優(yōu)化一下,讓showSongResult
為false
,即一清空輸入框內(nèi)容,隱藏掉搜索建議欄。另外我們?yōu)槭裁匆?code>return呢?這里還有一個bug
,當清除輸入框內(nèi)容后,再輸入發(fā)現(xiàn)已經(jīng)不再具備搜索功能了,所以需要return
回到初始的狀態(tài),就能重新進行輸入并且搜索。同時當輸入框為空時進行搜索功能還會報錯,這也是一個bug
,所以有了return
即使空值搜索也會立馬回到初始狀態(tài),解決了空值搜索報錯的bug
。
4.搜索框其他功能
-
清空輸入框內(nèi)容
clearInput: function (e) { // console.log(e) this.setData({ inputValue: '', // 將輸入框的值為空 showSongResult: false, // 隱藏搜索建議欄 showClean: true // 隱藏清除按鈕 (不加此項會出現(xiàn)清除輸入框內(nèi)容后清除按鈕不消失的bug) }) },復制代碼
點擊清除按鈕,就讓
inputValue
值為空,即輸入框的內(nèi)容為空,達到清除文本的效果;在獲取輸入框文本那里我們也提到了清除按鈕,也提到輸入框文本清空時,之前的搜索建議欄還會留下,所以這里我們讓showSongResult
為false
,使得搜索建議欄隱藏。清除文本的同時再隱藏掉清除按鈕。 -
取消搜索返回上頁
back: function () { wx: wx.navigateBack({ // 關閉當前頁面,返回上一頁面或多級頁面 delta: 0 // 返回的頁面數(shù),如果 delta 大于現(xiàn)有頁面數(shù),則返回到首頁 }); }復制代碼
這里用到的小程序自帶的返回頁面的功能,當給
delta
值為0即回到上一個頁面。(可去文檔查看詳情) -
跳轉(zhuǎn)歌手排行榜
singerPage: function () { wx.navigateTo({ // 保留當前頁面,跳轉(zhuǎn)到應用內(nèi)的某個頁面。但是不能跳到 tabbar 頁面 url: `../singer/singer` // 要跳轉(zhuǎn)去的界面 }) },復制代碼
在微信官方文檔可以查看到
navigateTo
的功能及其屬性,這里不多講。
5.搜索建議
searchSuggest() { $api. getSearchSuggest({ keywords: this.data.searchKey, type: 'mobile' }).then(res => { //請求成功 // console.log(res); // 打印出返回數(shù)據(jù)進行查看 if(res.statusCode === 200){ this.setData({ searchSuggest: res.data.result.allMatch // 將返回數(shù)據(jù)里的歌名傳給搜索建議 }) } }) .catch(err => { // 請求失敗 console.log('錯誤') }) }復制代碼解析:開始我們將接口進行了封裝,在上一篇講播放的文章中我的小伙伴已經(jīng)把接口跟封裝講的很仔細了,這里我們就不在講這個了,就分析我們的接口。
searchKey
作為搜索關鍵詞需要傳遞給接口,在前面的getSearchKey
方法中,我們已經(jīng)講輸入框的內(nèi)容傳給了searchKey
作為它的值;所以此時我們拿到有值的searchKey
傳遞給接口,讓接口返回相關數(shù)據(jù),返回的數(shù)據(jù)中的res.data.result.allMatch
就是從搜索關鍵詞返回的搜索建議里的所有歌名,在將這些數(shù)據(jù)放到searchSuggest
數(shù)據(jù)源中,這樣在wxml
埋好的空就能拿到數(shù)據(jù),將搜索建議欄顯示出。
6.搜索結果
- 搜索建議內(nèi)的歌曲點擊事件
// 看看 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`中的數(shù)據(jù)->點擊的值 this.setData({ searchKey: e.currentTarget.dataset.value, // 點擊時把值給searchKey進行搜索 inputValue: e.currentTarget.dataset.value, // 在輸入框顯示內(nèi)容 showSongResult: false, // 給false值,隱藏搜索建議頁面 showClean: false // 顯示清除按鈕 (不加此項,會出現(xiàn)點擊后輸入框有值但不顯示清除按鈕的bug) }) this.searchResult(); // 執(zhí)行搜索功能 },復制代碼
解析:首先點擊事件可以攜帶額外信息,如 id, dataset, touches;返回參數(shù)
event
,event
本身會有一個currentTarget
屬性;這里解釋一下data-value='{{item.keyword}}
=>data
就是dataset
;item.keyword
是搜索建議完成之后返回的數(shù)據(jù)賦值給searchSuggest
里面的某個數(shù)據(jù);當一點擊搜索建議里面的某一個歌名時,此歌名即為此時的item.keyword
,并將該值存入點擊事件的參數(shù)event
內(nèi)的dataset
。大家也可操作一波打印出來看看結果,currentTarget.dataset.value
就是我們點擊的那個歌曲名字。所以一點擊搜索建議中的某個歌名或者搜索歷史以及熱搜榜單中的某個歌名時,點擊事件生效,返回這樣該歌曲名稱,并將該值給到此時的searchKey
和inputValue
,此時輸入框的值會變成該值,搜索結果的關鍵詞的值也會變成該值;同時this.searchResult()
可讓此時執(zhí)行搜索結果功能。showSongResult: false
這里還將搜索建議欄給隱藏了。增加showClean: false
是為了解決點擊后輸入框有值但不顯示清除按鈕的bug
。 查看打印數(shù)據(jù)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) // 打印返回數(shù)據(jù) this.setData({ searchResult: res.data.result.songs, // 將搜索出的歌曲名稱給到搜索結果 showSearchResult: false, // 顯示搜索結果欄 showView: false, // 隱藏搜索歷史欄和熱搜榜單欄 }); } }) .catch(ree => { //請求失敗 }) },復制代碼
解析:上面的歌曲名稱點擊同時觸發(fā)了搜索結果的功能,將點擊后的新的
keywords
傳遞給了搜索結果的接口,接口請求后返回給我們數(shù)據(jù),數(shù)據(jù)中的res.data.result.songs
為搜索到的歌曲,此時將它賦值給到searchResult
,這樣搜索結果欄中會拿到數(shù)據(jù),并且showSearchResult: false
讓搜索結果欄顯示出來;這里還做了搜索歷史欄和熱搜欄的隱藏功能注:搜索結果和搜索建議都需要將搜索關鍵詞傳遞給接口,不清楚的小伙伴可以去查看接口文檔研究一下:https://binaryify.github.io/NeteaseCloudMusicApi/#/
- 搜索完成后的優(yōu)化
searchOver: function () { // 搜索結果完成后(再次點擊輸入框) this.setData({ showSongResult: false // 搜索建議這塊容器消失 }) this.searchResult(); // 執(zhí)行搜索結果 },復制代碼
解析:前面我們講到過,
searchOver
是綁定在input
框中的bindconfirm
事件,即點擊完成按鈕時觸發(fā)。當我們搜索完成之后,界面上還有搜索欄以及搜索結果的顯示,此時我們再次點擊輸入框,可以進行清除文本,同時我們還需要增加一個功能,即在此種情況下,我們還可以進行再次輸入并且返回搜索建議以及點擊搜索建議中的歌曲時再次執(zhí)行搜索結果功能。
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 對應的內(nèi)容,key指定為history // console.log(history); history = history.filter(item => item !== this.data.searchKey) // 歷史去重 history.unshift(this.data.searchKey) // 排序傳入 wx.setStorageSync("history", history); } }復制代碼
解析:之前講過
routeSearchResPage
事件時放在input
框中的,輸入框失去焦點時觸發(fā),即不在輸入框內(nèi)進行輸入,點擊輸入框以外的內(nèi)容時觸發(fā)。當輸入完成時會出現(xiàn)搜索建議,此時焦點還在輸入框,當我們點擊搜索建議中的某一天時,輸入框即失去焦點,此時該事件觸發(fā)。失去焦點函數(shù)是在搜索建議事件后發(fā)生,此時的搜索關鍵詞為搜索建議的搜索關鍵詞,前面也講到過,這個搜索關鍵詞就是我們在輸入框輸入的文本內(nèi)容,所以將此時的搜索關鍵詞賦值給搜索歷史history
。注:關于搜索歷史,我們這里增加了一個判斷,即當搜索關鍵詞不為空時,才會拿到搜索關鍵詞給到搜索歷史里面,否則,每一次不輸入值也去點擊輸入框以外,會將一個空值傳給搜索歷史,導致搜索歷史中會有空值得顯示,這也是一個`bug
得解決。同時還進一步將代碼進行優(yōu)化,用到filter
達到歷史去重得效果,即判斷新拿到得搜索關鍵詞是否與已有得搜索歷史中的搜索關鍵詞相同,同則過濾掉先前的那個,并使用到unshift
向數(shù)組開頭增加這個作為新的歷史記錄。 - 歷史緩存
onShow: function () { //每次顯示變動就去獲取緩存,給history,并for出來。 // console.log('a') this.setData({ history: wx.getStorageSync("history") || [] }) }復制代碼
解析:雖然上一步將拿到的搜索記錄存入到了搜索歷史,但是還不能顯示出來,讓數(shù)據(jù)源拿到數(shù)據(jù),這里要做一個歷史緩存的操作。
onShow
為監(jiān)聽頁面顯示,每次在搜素建議功能后進行點擊歌名出現(xiàn)搜索結果欄時觸發(fā),此時將上一步拿到的history
用getStorageSync
進行本地緩存,使得在刷新或者跳轉(zhuǎn)時,不會講搜索歷史丟失,一直保存下來。 - 刪除歷史
clearHistory: function () { // 清空page對象data的history數(shù)組 重置緩存為[](空) const that = this; wx.showModal({ content: '確認清空全部歷史記錄', cancelColor: '#DE655C', confirmColor: '#DE655C', success(res) { if (res.confirm) { // 點擊確認 that.setData({ history: [] }) wx.setStorageSync("history", []) //把空數(shù)組給history,即清空歷史記錄 } else if (res.cancel) { } } }) }復制代碼
解析:
showModal()
方法用于顯示對話窗,當點擊刪除按鈕時觸發(fā),顯示出確認清空全部歷史記錄
的窗口,并有兩個點擊按鈕:確認
和取消
;當點擊確認時,將history
數(shù)組中的內(nèi)容重置為空,即達到清空搜索歷史中的數(shù)據(jù)的功能;同時也需要將此時沒有數(shù)據(jù)的的搜索歷史進行緩存。點擊取消,提示窗消失,界面不會發(fā)生任何變化。
8.歌曲跳轉(zhuǎn)播放播放
- 傳值跳轉(zhuǎn)播放界面
// 先來看看handlePlayAudio綁定的地方 // <view wx:for="{{searchResult}}" wx:key="index" class='search_result_song_item' data-id="{{item.id}}" bindtap='handlePlayAudio'> // 以下為js: handlePlayAudio: function (e) { //event 對象,自帶,點擊事件后觸發(fā),event有type,target,timeStamp,currentTarget屬性 // console.log(e) // 打印出返回參數(shù)內(nèi)容 const musicId = e.currentTarget.dataset.id; //獲取到event里面的歌曲id賦值給musicId wx.navigateTo({ //獲取到musicId帶著完整url后跳轉(zhuǎn)到play頁面 url: `../play/play?musicId=${musicId}` // 跳轉(zhuǎn)到已經(jīng)傳值完成的歌曲播放界面 }) }復制代碼
解析:
handlePlayAudio
綁定在每天搜索結果上,即點擊搜索建議后完成搜索結果功能顯示出搜索結果欄,點擊每一天搜索結果都可以觸發(fā)handlePlayAudio
。前面也講到過bindtap
是帶有參數(shù)返回,攜帶額外信息dataset
,event
本身會有一個currentTarget
屬性,data-id="{{item.id}}"
的作用跟上面的搜索建議內(nèi)的歌曲點擊事件
是同樣的效果,item.id
為執(zhí)行搜索結果時接口返回給searchResult
的數(shù)據(jù),也就是搜索結果中每首歌曲各自對應的id
。當點擊搜索結果內(nèi)的某一首歌,即將這首歌的id
傳給event
中的dataset
,數(shù)據(jù)名為dataset
里的id
。此時我們定義一個musicId
,將event
里面的歌曲id
賦值給musicId
,用wx.navigateTo
跳轉(zhuǎn)到播放界面,同時將musicId
作為播放請求接口需要的傳入數(shù)據(jù)。 查看打印數(shù)據(jù)e
:
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='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> <!-- 跳轉(zhuǎn)歌手分類界面 --> <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: [], //搜索歷史存放數(shù)組 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將輸入框的內(nèi)容清空 clearInput: function (e) { // console.log(e) this.setData({ inputValue: '', showSongResult: false, showClean: true // 隱藏清除按鈕 }) }, //實現(xiàn)直接返回返回上一頁的功能,退出搜索界面 back: function () { wx: wx.navigateBack({ delta: 0 }); }, // 跳轉(zhuǎn)到歌手排行界面 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 // 出現(xiàn)清除按鈕 }) } }, // 搜索建議 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 對象,自帶,點擊事件后觸發(fā),event有type,target,timeStamp,currentTarget屬性 // console.log(e) const musicId = e.currentTarget.dataset.id; //獲取到event里面的歌曲id賦值給musicId wx.navigateTo({ //獲取到musicId帶著完整url后跳轉(zhuǎn)到play頁面 url: `../play/play?musicId=${musicId}` }) }, // input失去焦點函數(shù) 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數(shù)組 重置緩存為[](空) clearHistory: function () { const that = this; wx.showModal({ content: '確認清空全部歷史記錄', cancelColor: '#DE655C', confirmColor: '#DE655C', success(res) { if (res.confirm) { that.setData({ history: [] }) wx.setStorageSync("history", []) //把空數(shù)組給history,即清空歷史記錄 } else if (res.cancel) { } } }) }, // 搜索結果完成后(再次點擊輸入框) searchOver: function () { this.searchSuggest(); // 執(zhí)行搜索結果 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,//在輸入框顯示內(nèi)容 showSongResult: false, //給false值,隱藏搜索建議頁面 showClean: false // 顯示 清除按鈕 }) this.searchResult(); //執(zhí)行搜索功能 }, /** * 生命周期函數(shù)--監(jiān)聽頁面顯示 */ //每次顯示變動就去獲取緩存,給history,并for出來。 onShow: function () { // console.log('a') this.setData({ history: wx.getStorageSync("history") || [] }) }, })復制代碼
api.js
const app = getApp(); // method(HTTP 請求方法),網(wǎng)易云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) { //請求成功 //判斷狀態(tài)碼---errCode狀態(tài)根據(jù)后端定義來判斷 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 }復制代碼
總結
其實一點一點的捋清楚會發(fā)現(xiàn)也不是很難操作,首先思路要清晰,知道每一個功能是什么作用,同時在調(diào)試是時候去發(fā)現(xiàn)一些bug
,再去對代碼進行優(yōu)化。關于搜索這個功能用處廣泛,希望本次的分享能給大家?guī)硪稽c用處。
相關學習推薦:微信公眾號開發(fā)教程,javascript視頻教程