相關學習推薦:微信小程式教學
前段時間我的小夥伴已經將網易雲音樂小程式的音樂播放功能詳細的介紹出來了,作為前端小白學習了一段時間,最近也比較忙,沒有及時將實時搜索這塊內容及時寫出來跟大家分享(其實代碼和功能之前就寫的差不多了),那麼今天就來跟大家講一講個人在裡面的一些細節和優化吧。
搜尋功能很常見,很多地方都能用到,希望能夠給大家分享到有用的東西,同時,有不足的地方,也希望各位大佬指出,給出一些修改的意見,小白在此感謝了!
即時搜尋功能裡面我們也需要用到API接口,從input框輸入值到搜尋建議,再到搜尋結果,最後到跳轉歌曲播放,不再只是接那麼簡單,傳值很關鍵,同時不同功能下不同容器框的隱藏與顯示,還有一些搜尋當中涉及的細節內容與最佳化。讓我們一起來看看吧!
頭搜尋列中:左邊回傳箭頭,中間輸入框,右邊歌手排行榜頁面跳轉;至於清除按鈕呢我們隱藏了起來,只有在輸入輸入值之後才會出現。
往下時歷史記錄,像每個搜搜的記錄值這裡都是一小塊等隔距離分佈,搜尋值有多長,這小塊就有多長,這裡用到的是display: flex;flex-wrap: wrap;
,對這個介面樣式有興趣的小夥伴可以待會看看全部程式碼。
接下來是熱搜榜,這裡沒有太多講究,就是發起介面請求數據,把數據埋進去顯示出來就行了。
搜尋建議會在輸入結束後才會出現,並且是很立體的一塊覆蓋在整個頁面上,用box-shadow: 1px 1px 5px #888888
達到立體效果,z-index
起到覆蓋的效果。
搜尋結果會在點擊搜尋建議中的某一條或是點選搜尋紀錄或熱搜才出現,同時介面上其他所有的容器快都會隱藏起來,這裡其實就是一個容器框的隱藏與出現的小細節了,待會在功能中我們會詳細講到。這裡我們先講一下元件(容器)如何進行隱藏與顯示,以免下面的功能中看到這幾項內容蒙圈
幾個容器的頭部展示
<!-- 点击×可以清空正在输入 --> <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'}}">复制代码
#解析:這裡只放了這幾塊容器的頭部的內容,在###data
資料來源中分別放了showClean,showSongResult,showSearchResult,showView
, 為true
則這幾塊容器預設為:
(冒號)前面的樣式,為false則預設為:
(冒號)後面的樣式;header_view_hide
樣式設定為display: none;
,即隱藏不顯示;所以當在某一個方法中可以去改變showClean,showSongResult,showSearchResult,showView
為true
#還是false
可以讓這幾塊容器分別為顯示或是隱藏。
介面封裝在上一篇我的小夥伴已經講的十分清晰了,我們這裡不再多去講解了,同樣現在用到的功能也不只是光調接口請求數據那麼簡單了,我們需要傳值給接口,讓接口收到值後再給我們返回相應的數據;在搜索界面我們用到的是搜索建議以及搜索結果的接口。熱搜榜我們暫時只使用最基礎的wx.request
直接取得資料
#api.js
const API = { getSearchSuggest: (data) => request(GET, `/search/suggest`, data), // 搜索建议接口 getSearchResult: (data) => request(GET, `/search`, data), // 搜索结果接口 }复制代码
一個搜尋功能我們設計到的資料會有很多,可以詳細列出:輸入的值inputValue
,在輸入時取得;熱搜清單資料hots
,熱搜介面取得;搜尋關鍵字searchKey
,本身就是輸入框的值,用來傳遞給搜尋建議作為搜尋關鍵字;searchSuggest
,搜尋建議介面拿到搜尋關鍵字後傳回的資料(搜尋建議);搜尋結果searchResult
,當點選搜尋建議中的某一條,該值將會填入搜尋框,此時搜尋關鍵字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视频教程
以上是那些年,微信小程式仿網易雲音樂有關即時搜尋功能的詳細內容。更多資訊請關注PHP中文網其他相關文章!