仿网易云音乐-搜索歌曲功能
思路:
1.搜索静态页面,展示热门搜索关键词:点击关键词,自动填入搜索框,并实时得到搜索结果。
小细节:如果搜索框里有内容,则会出现“X”清除搜索内容,并回到默认页面。
2.手动输入搜索关键词,会给相似推荐的关键词,点击关键词进行搜索相关歌曲
API接口使用: https://binaryify.github.io/NeteaseCloudMusicApi/#/?id=neteasecloudmusicapi
视图层:搜索框和热门搜索词列表
1 <view class="m-input"> 2 <i class="u-svg u-svg-srch"> 3 <image src="/images/s.svg"></image> 4 </i> 5 <input class="inputcover" id="input" 6 bindinput="bindKeyInput" bindconfirm="getsearchList" 7 confirm-type="search" 8 placeholder="搜索歌曲、歌手、专辑" 9 value="{{searchValue}}" 10 /> 11 <i class="close u-svg u-svg-empty {{showClean ? 'header_view_hide' : 'clean-pic'}}" bindtap='clearInput'> 12 <image src="/images/close.svg"></image> 13 </i> 14 </view> 15 16 <view class="m-default {{showView?'option':'header_view_hide'}}"> 17 <view class="m-hotlist"> 18 <h3 class="title">热门搜索</h3> 19 <view class="list"> 20 <view class="item f-bd f-bd-full" 21 wx:for="{{hotLists}}" 22 wx:key="index" 23 data-value='{{item.first}}' 24 bindtap='fill_value'> 25 {{item.first}} 26 </view> 27 </view> 28 </view> 29 </view>
A. 用到的方法:
bindinput="bindKeyInput": 输入搜索关键词,实时输出相关搜索词
bindconfirm="getsearchList":输入完成,enter得到歌曲搜索列表
bindtap='clearInput':点击“x”清空搜索框里的内容,并清除搜索结果回到默认状态
B. 所用到的状态控制显示与隐藏
<i class="close u-svg u-svg-empty {{showClean ? 'header_view_hide' : 'clean-pic'}}" bindtap='clearInput'>
<view class="m-default {{showView?'option':'header_view_hide'}}">
showClean默认为ture,当前的class为'header_view_hide':display:none
showView: true,显示当前模块 getSearchSuggest()方法设置showView为false,
showSongResult: true,显示当前模块,showView为false,热门搜索隐藏
<view class="m-recom {{showSongResult ? 'search_suggest' : 'header_view_hide'}}"> <h3 class="title f-bd f-bd-btm f-thide">搜索“{{searchValue}}”</h3> <ul> <li class="recomitem" wx:for="{{searchSuggest}}" wx:key="index" data-value='{{item.keyword}}' bindtap='fill_value' > <i class="u-svg u-svg-search"> <image src="/images/s.svg"></image> </i> <span class="f-bd f-bd-btm f-thide">{{item.keyword}}</span> </li> </ul> </view>
data-value='{{item.keyword}}':设定当前点击的值,并传值给fill_value方法
bindtap='fill_value':fill_value方法,获取当前的data-value的值:e.currentTarget.dataset.value
最重要的就是获取搜索列表:用到的方法:getSearchList(),getSearchList()
<view class="m-searchresult {{showSearchResult ? 'search_result_songs':'header_view_hide'}}"> <view class="m-matchlist"> <h3 class="title">最佳匹配</h3> <ul> <li class="matchitem artist"> <navigator url="/artist?id={{searchResult[0].id}}"> <div class="linkcover f-bd f-bd-btm"> <figure class="piccover"> <image class="pic" src="{{searchResult[0].al.picUrl}}"></image> </figure> <article class="describe"> <h4 class="maindes f-thide"> 歌手: <p class="hcover">{{searchResult[0].ar[0].name}}</p> </h4> </article> <i class="u-svg u-svg-arr"> <image src="/images/arrow.svg"></image> </i> </div> </navigator> </li> <li class="matchitem album"> <navigator url="/album?id={{searchAlbum.id}}"> <div class="linkcover f-bd f-bd-btm"> <figure class="piccover"> <image class="pic" src="{{searchAlbum.picUrl}}"></image> </figure> <article class="describe"> <h4 class="maindes f-thide"> 专辑: <p class="hcover"> <span> <span class="highlight">{{searchAlbum.name}}</span> </span> <span> <span class="normal"> {{searchAlbum.alias}} </span> </span> </p> </h4> <p class="hcover addtional f-thide"> {{searchAlbum.artist.name}}</p> </article> <i class="u-svg u-svg-arr"> <image src="/images/arrow.svg"></image> </i> </div> </navigator> </li> </ul> </view> <view class="m-songlist"> <view class="m-sglst"> <block wx:for="{{searchResult}}" wx:key="index"> <navigator class="m-sgitem" url="../musicplay/musicplay?id={{item.id}}&title={{item.name}}"> <view class="sgfr f-bd f-bd-btm"> <view class="sgchfl"> <view class="f-thide sgtl"> <p class="hcover"> <span> <span class="highlight">{{item.name}}</span> </span> <span> </span> </p> </view> <view class="f-thide sginfo"> <i class="u-hmsprt sghot"></i> <p class="hcover">{{item.ar[0].name}}</p> - <p class="hcover">{{item.al.name}}</p> </view> </view> <view class="sgchfr"> <span class="u-hmsprt sgchply"> </span> </view> </view> </navigator> </block> </view> </view> </view>
完整的代码:
WXML:
<view class="page-section"> <view class="m-input"> <i class="u-svg u-svg-srch"> <image src="/images/s.svg"></image> </i> <input class="inputcover" id="input" bindinput="bindKeyInput" bindconfirm="fill_value" confirm-type="search" placeholder="搜索歌曲、歌手、专辑" value="{{searchValue}}" /> <i class="close u-svg u-svg-empty {{showClean ? 'header_view_hide' : 'clean-pic'}}" bindtap='clearInput'> <image src="/images/close.svg"></image> </i> </view> <view class="m-recom {{showSongResult ? 'search_suggest' : 'header_view_hide'}}"> <h3 class="title f-bd f-bd-btm f-thide">搜索“{{searchValue}}”</h3> <ul> <li class="recomitem" wx:for="{{searchSuggest}}" wx:key="index" data-value='{{item.keyword}}' bindtap='fill_value' > <i class="u-svg u-svg-search"> <image src="/images/s.svg"></image> </i> <span class="f-bd f-bd-btm f-thide">{{item.keyword}}</span> </li> </ul> </view> <view class="m-searchresult {{showSearchResult ? 'search_result_songs':'header_view_hide'}}"> <view class="m-matchlist" > <h3 class="title">最佳匹配</h3> <ul> <li class="matchitem artist"> <navigator url="/artist?id={{searchResult[0].id}}"> <div class="linkcover f-bd f-bd-btm"> <figure class="piccover"> <image class="pic" src="{{searchResult[0].al.picUrl}}"></image> </figure> <article class="describe"> <h4 class="maindes f-thide"> 歌手: <p class="hcover">{{searchResult[0].ar[0].name}}</p> </h4> </article> <i class="u-svg u-svg-arr"> <image src="/images/arrow.svg"></image> </i> </div> </navigator> </li> <li class="matchitem album"> <navigator url="/album?id={{searchAlbum.id}}"> <div class="linkcover f-bd f-bd-btm"> <figure class="piccover"> <image class="pic" src="{{searchAlbum.picUrl}}"></image> </figure> <article class="describe"> <h4 class="maindes f-thide"> 专辑: <p class="hcover"> <span> <span class="highlight">{{searchAlbum.name}}</span> </span> <span> <span class="normal"> {{searchAlbum.alias}} </span> </span> </p> </h4> <p class="hcover addtional f-thide"> {{searchAlbum.artist.name}}</p> </article> <i class="u-svg u-svg-arr"> <image src="/images/arrow.svg"></image> </i> </div> </navigator> </li> </ul> </view> <view class="m-songlist"> <view class="m-sglst"> <block wx:for="{{searchResult}}" wx:key="index"> <navigator class="m-sgitem" url="../musicplay/musicplay?id={{item.id}}&title={{item.name}}"> <view class="sgfr f-bd f-bd-btm"> <view class="sgchfl"> <view class="f-thide sgtl"> <p class="hcover"> <span> <span class="highlight">{{item.name}}</span> </span> <span> </span> </p> </view> <view class="f-thide sginfo"> <i class="u-hmsprt sghot"></i> <p class="hcover">{{item.ar[0].name}}</p> - <p class="hcover">{{item.al.name}}</p> </view> </view> <view class="sgchfr"> <span class="u-hmsprt sgchply"> </span> </view> </view> </navigator> </block> </view> </view> </view> <view class="m-default {{showView?'option':'header_view_hide'}}"> <view class="m-hotlist"> <h3 class="title">热门搜索</h3> <view class="list"> <view class="item f-bd f-bd-full" wx:for="{{hotLists}}" wx:key="index" data-value='{{item.first}}' bindtap='fill_value'> {{item.first}} </view> </view> </view> </view> </view>
// pages/search/search.js import {get} from "../../utils/request.js" Page({ /** * 页面的初始数据 */ data: { searchValue: '', cursor:0, hotLists:null, searchSuggest:null, searchResult:[], showView: true, showSearchResult:true, showSongResult: false, showSearchResult: false, showClean: true, offset:1 }, gethotList: async function(){ const url =`/search/hot` const res = await get(url) // console.log(res.data.result.hots) this.setData({ hotLists: res.data.result.hots }) }, getSearchSuggest:async function(searchWords){ const url =`/search/suggest?keywords=${searchWords}&type=mobile` const res = await get(url) // console.log(res.data) this.setData({ searchSuggest: res.data.result.allMatch, showView: false, showSearchResult: false }) }, // splitData: function (list) { // var res = []; // for (var i = 0, len = songCount; i < len; i += 10) { // list.push(res.data.list[i]); // } // return res; // }, getsearchList: async function(searchWords){ var that= this; const offset = this.data.offset; const url =`/cloudsearch?keywords=${searchWords}&limit=6&offset=${offset}` // const url_album =`/cloudsearch?keywords=${searchWords}&limit=1&type=10` const res = await get(url) // const res_album = await get(url_album) const arr1 = this.data.searchResult; const arr2= res.data.result.songs; const musicLists = arr1.concat(arr2); console.log(res.data.result) // console.log(res_album.data.result) this.setData({ count:res.data.result.songCount, searchResult: musicLists, // searchAlbum:res_album.data.result.albums[0], showSearchResult: true, showView: false, showSongResult:false }) }, getsearchAlbum: async function(searchWords){ const url_album =`/cloudsearch?keywords=${searchWords}&limit=1&type=10` const res_album = await get(url_album) // console.log(res_album.data.result) this.setData({ searchAlbum:res_album.data.result.albums[0], }) }, bindKeyInput: function (e) { console.log(e.detail.value); var that = this; if(e.detail.cursor != this.data.cursor) { this.setData({ showSongResult: true, // cursor: e.detail.cursor, searchValue: e.detail.value }) // 假设现在需要检测到用户输入的值,用户 500 毫秒内没有继续输入就将该值打印出来 that.throttle(that.getSearchSuggest, null, 1000, this.data.searchValue); } if (e.detail.value) { // 当input框有值时,才显示清除按钮'x' this.setData({ showClean: false }) } if(e.detail.cursor===0){ this.setData({ showSongResult: false, showClean: true }) return } }, // 节流 throttle: function (fn, context, delay, text) { clearTimeout(fn.timeoutId); fn.timeoutId = setTimeout(function () { fn.call(context, text); }, delay); }, //热门歌曲点击 fill_value: function (e) { var that=this; console.log(e.currentTarget.dataset.value) this.setData({ searchValue: e.currentTarget.dataset.value||e.detail.value, showSongResult: false, showClean: false }) that.getsearchList(that.data.searchValue) that.getsearchAlbum(that.data.searchValue) }, clearInput: function(e) { console.log(e) this.setData({ searchValue: '', showSongResult: false, showSearchResult: false, showView:true, showClean: true // 隐藏清除按钮 }) }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { this.gethotList() }, /** * 生命周期函数--监听页面初次渲染完成 */ onReady: function () { }, /** * 生命周期函数--监听页面显示 */ onShow: function () { }, /** * 生命周期函数--监听页面隐藏 */ onHide: function () { }, /** * 生命周期函数--监听页面卸载 */ onUnload: function () { }, /** * 页面相关事件处理函数--监听用户下拉动作 */ onPullDownRefresh: function () { wx.showNavigationBarLoading(); wx.stopPullDownRefresh; }, /** * 页面上拉触底事件的处理函数 */ onReachBottom: function () { var that=this; var total = Math.ceil(this.data.count/6); console.log(total); var offset = that.data.offset + 1; //获取当前页数并+1 that.setData({ offset: offset, //更新当前页数 }) this.getsearchList(this.data.searchValue) }, /** * 用户点击右上角分享 */ onShareAppMessage: function () { } })
.m-input { padding: 15px 10px; } .m-input .inputcover { position: relative; width: 100%; height: 30px; padding: 0 30px; box-sizing: border-box; background: #ebecec; border-radius: 30px; font-size: 14px; } .m-hotlist { padding: 15px 10px 0; } .m-hotlist .title { font-size: 12px; line-height: 12px; color: #666; } .m-hotlist .list { margin: 10px 0 7px; display: flex; flex-wrap: wrap; } .m-hotlist .item{ min-height: unset; height: 32px; margin-right: 8px; margin-bottom: 8px; padding: 0 14px; font-size: 14px; line-height: 32px; color: #333; border:1px solid #d3d4da; border-radius: 32px; display: inline-flex; align-items: center; } .header_view_hide{ display: none; } .m-recom .title { height: 50px; margin-left: 10px; padding-right: 10px; font-size: 15px; line-height: 50px; color: #507daf; } .m-recom .recomitem { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; display: flex; -webkit-box-align: center; -webkit-align-items: center; -ms-flex-align: center; align-items: center; height: 45px; padding-left: 10px; } .m-recom i { -webkit-box-flex: 0; -webkit-flex: 0 0 auto; -ms-flex: 0 0 auto; flex: 0 0 auto; margin-right: 7px; } .u-svg-search { width: 15px; height: 15px; } .m-input{ position: relative; } .m-input .u-svg-srch { position: absolute; left: 15px; top: 20px; margin: 0 8px; vertical-align: middle; z-index: 10; } .m-input .close { position: absolute; right: 20px; top: 16px; line-height: 28px; text-align: center; } .u-svg-srch image{ width: 13px; height: 13px; } .u-svg-empty image{ width: 14px; height: 14px; } .u-svg-search image{ width: 15px; height: 15px; } .m-matchlist .title { margin-left: 10px; font-size: 12px; line-height: 16px; color: #666; } .m-matchlist .linkcover { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; display: flex; -webkit-box-align: center; -webkit-align-items: center; -ms-flex-align: center; align-items: center; height: 66px; margin-left: 10px; padding: 8px 10px 8px 0; box-sizing: border-box; } .m-matchlist .piccover { position: relative; width: 50px; height: 50px; margin-right: 15px; line-height: 0; } .m-matchlist .pic { display: block; width: 100%; height: 100%; } .hcover .highlight { color: #507daf; } .m-matchlist .describe { -webkit-box-flex: 1; -webkit-flex: 1; -ms-flex: 1; flex: 1; display: inline-block; width: 1%; } .f-thide { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; word-break: normal; } .m-matchlist .describe .addtional { font-size: 12px; line-height: 15px; color: #999; width: 100%; display: flex; } .u-svg-arr image{ width: 8px; height: 13px; } .m-sgitem .sginfo { font-size: 12px; color: #888; } .m-sgitem, .m-sgitem .sgfl { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; display: flex; } .m-sgitem .sgchfl, .m-sgitem .sgfr { -webkit-box-flex: 1; -webkit-flex: 1 1 auto; -ms-flex: 1 1 auto; flex: 1 1 auto; } .m-sgitem .sgfr { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; display: flex; position: relative; } .m-sgitem { padding-left: 10px; } .m-sgitem .sgchfl { padding: 6px 0; width: 0; } .m-sgitem .sgchfr { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; display: flex; -webkit-box-align: center; -webkit-align-items: center; -ms-flex-align: center; align-items: center; padding: 0 10px; } .m-sgitem .sghot { display: inline-block; width: 12px; height: 8px; margin-right: 4px; } .m-sgitem .sgchply { display: inline-block; width: 22px; height: 22px; background-position: -24px 0; } .u-hmsprt { background: url(//s3.music.126.net/mobile-new/img/index_icon_2x.png?5207a28…=) no-repeat; background-size: 166px 97px; }