仿网易云音乐-微信小程序开发
1.很多时候要找到完整的API接口很难,但网易云音乐的数据API是可以得到完整的。
安装API:https://github.com/Binaryify/NeteaseCloudMusicApi,只需按照步骤部署就可以的。
提示:由于本地电脑环境千差万别,建议会使用virtualBox虚拟机进行部署可一劳永逸
部署完成, cd NeteaseCloudMusicApi node app.js 开启服务即可
2.根据相关API开发页面
获取推荐歌单列表
tabBar,主页面色,以及路由设计
{
"pages": [
"pages/home/home",
"pages/hot/hot",
"pages/search/search",
"pages/listdetail/listdetail",
"pages/musicplay/musicplay"
],
"window": {
"backgroundColor": "#F6F6F6",
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#d43c33",
"navigationBarTitleText": "网易云音乐",
"navigationBarTextStyle": "white"
},
"tabBar": {
"backgroundColor": "#212121",
"color": "#8F8F8F",
"selectedColor": "#FFF",
"list": [{
"pagePath": "pages/home/home",
"text": "推荐音乐",
"iconPath": "/images/cm2_btm_icn_discovery.png",
"selectedIconPath": "/images/cm2_btm_icn_discovery_prs.png"
},
{
"pagePath": "pages/hot/hot",
"text": "热歌榜",
"iconPath": "/images/cm2_btm_icn_radio.png",
"selectedIconPath": "/images/cm2_btm_icn_radio_prs.png"
},
{
"pagePath": "pages/search/search",
"text": "搜索",
"iconPath": "/images/cm2_btm_icn_music.png",
"selectedIconPath": "/images/cm2_btm_icn_music_prs.png"
}
]
},
"requiredBackgroundModes": ["audio", "location"],
"sitemapLocation": "sitemap.json"
}
"requiredBackgroundModes": ["audio", "location"],
若需要在小程序切后台后继续播放音频,需要在 app.json 中配置 requiredBackgroundModes
属性。
获取API数据,此时有两种方法,使用小程序云函数,或者本地获取
本地获取:
封装request请求get,post
const baseurl = 'http://localhost:3000'
function getbaseurl() {
return baseurl;
}
//get请求
function get(url, data) {
return new Promise((reslove, reject) => {
wx.request({
method: 'GET',
url: baseurl + url,
data,
success: reslove,
fail: reject
})
})
}
//post请求
function post(url, data) {
return new Promise((reslove, reject) => {
wx.request({
method: 'POST',
url: baseurl + url,
data,
success: reslove,
fail: reject
})
})
}
//需要导出
module.exports = {
get,
post,
getbaseurl
}
// pages/home/home.js
var requestUrl = require('../../utils/request.js')
Page({
/**
* 页面的初始数据
*/
data: {
musicList: [ ],
detailUrl:''
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: async function (options) {
const res = await requestUrl.get('/personalized/?limit=6')
this.setData({ musicList: res.data.result});
console.log(res.data.result)
},
自定义组件还可以自己触发双向绑定更新,做法就是:使用 setData 设置自身的属性
<wxs module="common" src="../../utils/utils.wxs"></wxs> <view class="m-homeremd"> <h2 class="remd_tl"> 推荐歌单</h2> <view class="remd_ul"> <navigator url="../listdetail/listdetail?id={{item.id}}" class="remd_li" data-musicid="{{item.id}}" wx:for="{{musicList}}" wx:key="index"> <view class="remd_img"> <image class="u-img" src="{{item.picUrl}}"></image> <span class="u-earp remd_lnum">{{common.numberFormat(item.playCount)}}</span> </view> <text class="remd_text">{{item.name}}</text> </navigator> </view> </view>
推荐歌单页面有一个播放量转换
{{common.numberFormat(item.playCount)}}
function numberFormat(value) { var param = {}; var k = 10000, sizes = ['', '万', '亿', '万亿'], i; if (value < k) { param.value = value param.unit = '' } else { i = Math.floor(Math.log(value) / Math.log(k)); param.value = ((value / Math.pow(k, i))).toFixed(2); param.unit = sizes[i]; } return param.value + param.unit; } module.exports = { numberFormat: numberFormat }
.m-homeremd { padding-top: 20px; } .m-homeremd .remd_tl { position: relative; padding-left: 9px; margin-bottom: 14px; font-size: 17px; height: 20px; line-height: 20px; } .m-homeremd .remd_tl:after { content: " "; position: absolute; left: 0; top: 50%; margin-top: -9px; width: 2px; height: 16px; background-color: #d33a31; } .m-homeremd .remd_ul{ display: flex; flex-wrap: wrap; } .m-homeremd .remd_li{ box-sizing: border-box; flex: 0 1 33.3%; padding-bottom: 20px; } .m-homeremd .remd_img>.u-img{ width: 100px; height: 100px; } .m-homeremd .remd_text { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; padding: 6px 2px 0 6px; min-height: 30px; line-height: 1.2; font-size: 13px; } .m-homeremd .remd_img { position: relative; } .m-homeremd .remd_lnum { position: absolute; right: 5px; top: 2px; z-index: 3; padding-left: 13px; color: #fff; font-size: 12px; background-position: 0; background-repeat: no-repeat; background-size: 11px 10px; text-shadow: 1px 0 0 rgba(0,0,0,.15); } .u-earp { background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAABfUlEQVR4AZXSA0y/YRAH8PvbdhqyrbnmMDPXFIYwZmt2bo5jDRlDtu1mZHc9z737/Z7t4n833ffu8+p5Af+zePAZQ7AaB3AF57Ebq0X35X7wBhNxEXdZLWKSmNwB/mATW1XVjH84+IdD2nC/tyclLuC9u5l3QmBXyn6vlorpPwXkw7TK+HqjJ+WVG7iqeuXWlXK9QaQV3ygQQ+ubFVFiyRp+gRjBc3gLP0XnWh11vUkkRgc+45wMetPBBX7xzyiQS28agTmxSSBYtof9X9zg051f/vMXt8N+IqEErqpl05/Dr64KfvVnyx2xKcHFsGyKA+DZveBZob/cuRgigNui2YE/D/4Qf2hrm4D2peHdg+CdtoUKoC+OifLhq/pcgWt6JBylYIADfa4eabH0amW5jGJRHOjypfKrlaUyAuAif4LHgParaMAYnMHkMUBbxgh8wAHPFTin/+l0ggOWK9AYdzZzPl8bwwDPFQBHei17DlTOwVdwEMMvHPAc8D/rBs0Gmx5Y/w/0AAAAAElFTkSuQmCC'); }
列表页面渲染:根据id值跳转到相对应的列表页面
<navigator url="../listdetail/listdetail?id={{item.id}}" class="remd_li" data-musicid="{{item.id}}" wx:for="{{musicList}}" wx:key="index">
import {get} from "../../utils/request.js" // pages/listdetail/listdetail.js Page({ /** * 页面的初始数据 */ data: { detailList:[ ] }, /** * 生命周期函数--监听页面加载 */ onLoad: async function (options) { console.log(options) const url =`/playlist/detail?id=${options.id}` const res = await get(url) console.log(res.data) this.setData({ detailList: res.data.playlist}) },
<!-- <wxs module="common" src="../../utils/utils.wxs"></wxs> <view class="u-plhead pylst_header"> <view class="plhead_bg" style="background-image:url({{detailList.playlist.coverImgUrl}})"> </view> <view class="plhead_wrap"> <view class="plhead_fl lsthd_fl"> <image src="{{detailList.playlist.coverImgUrl}}" class='u-img'>{{detailList.playlist.coverImgUrl}}</image> <span class="lsthd_icon">歌单</span> <i class="u-earp lsthd_num">{{common.numberFormat(detailList.playlist.playCount)}}</i> </view> <view class="plhead_fr"> <h2 class="f-thide2 f-brk lsthd_title">{{detailList.playlist.name}}</h2> <view class="lsthd_auth f-thide"> <a class="lsthd_link" href=""> <view class="u-avatar lsthd_ava"> <image class="u-img" src="{{detailList.playlist.creator.avatarUrl}}"></image> <span class="ava-icon ava-icon-daren"></span> </view> {{detailList.playlist.creator.nickname}} </a> </view> </view> </view> </view> <h3 class="u-smtitle">歌曲列表</h3> <view class="m-sglst"wx:for="{{detailList}}" wx:key="index"> <view class="m-list"wx:for="{{item.tracks}}" wx:for-item="item" wx:key="index"> <navigator class="m-sgitem"url="/"> <view class="sgfl">{{index+1}}</view> <view class="sgfr f-bd f-bd-btm"> <view class="sgchfl"> <view class="f-thide sgtl">{{item.name}}</view> <view class="f-thide sginfo">{{}}</view> </view> <view class="sgchfr"> <span class="u-hmsprt sgchply"> </span> </view> </view> </navigator> </view> </view> --> <wxs module="common" src="../../utils/utils.wxs"></wxs> <view class="u-plhead pylst_header"> <view class="plhead_bg" style="background-image:url({{detailList.coverImgUrl}})"> </view> <view class="plhead_wrap"> <view class="plhead_fl lsthd_fl"> <image w-if="detailList.coverImgUrl" src="{{detailList.coverImgUrl}}" class='u-img'></image> <span class="lsthd_icon">歌单</span> <i class="u-earp lsthd_num">{{common.numberFormat(detailList.playCount)}}</i> </view> <view class="plhead_fr"> <h2 class="f-thide2 f-brk lsthd_title">{{detailList.name}}</h2> <view class="lsthd_auth f-thide"> <a class="lsthd_link" href=""> <view class="u-avatar lsthd_ava"> <image class="u-img" src="{{detailList.creator.avatarUrl}}"></image> <span class="ava-icon ava-icon-daren"></span> </view> {{detailList.creator.nickname}} </a> </view> </view> </view> </view> <h3 class="u-smtitle">歌曲列表</h3> <view class="m-sglst"wx:for="{{detailList.tracks}}" wx:key="index"> <!-- <view class="m-list"wx:for="{{item.tracks}}" wx:for-item="item" wx:key="index"> --> <navigator class="m-sgitem"url="../musicplay/musicplay?id={{item.id}}&title={{item.name}}"> <view class="sgfl">{{index+1}}</view> <view class="sgfr f-bd f-bd-btm"> <view class="sgchfl"> <view class="f-thide sgtl">{{item.name}} <span class="sgalia" wx:if="{{item.alia.length}}">(<!-- -->{{item.alia}}<!-- -->)</span> </view> <view class="f-thide sginfo">{{item.ar[0].name}}-{{item.al.name}}</view> </view> <view class="sgchfr"> <span class="u-hmsprt sgchply"> </span> </view> </view> </navigator> <!-- </view> --> </view>
.plhead_wrap { display: flex; position: relative; z-index: 2; } .u-plhead { position: relative; padding: 30px 10px 30px 15px; overflow: hidden; } .u-plhead .plhead_bg { background-repeat: no-repeat; background-size: cover; background-position: 50%; -webkit-filter: blur(20px); filter: blur(20px); -webkit-transform: scale(1.5); -ms-transform: scale(1.5); transform: scale(1.5); } .u-plhead .plhead_bg, .u-plhead .plhead_bg:after { position: absolute; left: 0; top: 0; right: 0; bottom: 0; z-index: 1; } .u-plhead .plhead_bg:after { content: " "; background-color: rgba(0,0,0,.25); } .pylst_header .lsthd_title { padding-top: 1px; font-size: 17px; line-height: 1.3; color: #fefefe; height: 44px; display: -webkit-box; -webkit-box-pack: center; } .plhead_fl { position: relative; width: 114px; height: 114px; background-color: #e2e2e3; } image.u-img { width: 100%; height: 100%; } .pylst_header .lsthd_icon { position: absolute; z-index: 3; top: 10px; left: 0; padding: 0 8px; height: 17px; color: #fff; font-size: 9px; text-align: center; line-height: 17px; background-color: rgba(217,48,48,.8); border-top-right-radius: 17px; border-bottom-right-radius: 17px; } .u-plhead .plhead_fr { -webkit-box-flex: 1; -webkit-flex: 1 1 auto; -ms-flex: 1 1 auto; flex: 1 1 auto; width: 1%; margin-left: 16px; } .pylst_header .lsthd_ava { display: inline-block; width: 30px; height: 30px; border-radius: 50%; vertical-align: middle; margin-right: 5px; } .u-avatar { position: relative;} .u-avatar>.u-img { border-radius: 50%; } .u-avatar .ava-icon.ava-icon-daren { background-position: -40px 0; } .u-avatar .ava-icon { position: absolute; right: -5px; bottom: 0; width: 12px; height: 12px; background-image: url(//s3.music.126.net/mobile-new/img/usericn_2x.png?6423c06…=); background-repeat: no-repeat; background-size: 75px auto; } .pylst_header .lsthd_auth { display: block; position: relative; margin-top: 20px; } .lsthd_link { display: inline-block; color: hsla(0,0%,100%,.7); } .f-thide { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; word-break: normal; } .pylst_header .lsthd_num { position: absolute; right: 2px; top: 0; z-index: 3; padding-left: 15px; color: #fff; font-size: 12px; background-position: 0; background-repeat: no-repeat; background-size: 11px 10px; text-shadow: 1px 0 0 rgba(0,0,0,.15); } .lsthd_fl:after { content: " "; position: absolute; left: 0; top: 0; width: 100%; height: 18px; z-index: 2; background-image: -webkit-linear-gradient(left,rgba(0,0,0,0),rgba(0,0,0,.2)); background-image: linear-gradient(90deg,rgba(0,0,0,0),rgba(0,0,0,.2)); } .u-earp { background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAABfUlEQVR4AZXSA0y/YRAH8PvbdhqyrbnmMDPXFIYwZmt2bo5jDRlDtu1mZHc9z737/Z7t4n833ffu8+p5Af+zePAZQ7AaB3AF57Ebq0X35X7wBhNxEXdZLWKSmNwB/mATW1XVjH84+IdD2nC/tyclLuC9u5l3QmBXyn6vlorpPwXkw7TK+HqjJ+WVG7iqeuXWlXK9QaQV3ygQQ+ubFVFiyRp+gRjBc3gLP0XnWh11vUkkRgc+45wMetPBBX7xzyiQS28agTmxSSBYtof9X9zg051f/vMXt8N+IqEErqpl05/Dr64KfvVnyx2xKcHFsGyKA+DZveBZob/cuRgigNui2YE/D/4Qf2hrm4D2peHdg+CdtoUKoC+OifLhq/pcgWt6JBylYIADfa4eabH0amW5jGJRHOjypfKrlaUyAuAif4LHgParaMAYnMHkMUBbxgh8wAHPFTin/+l0ggOWK9AYdzZzPl8bwwDPFQBHei17DlTOwVdwEMMvHPAc8D/rBs0Gmx5Y/w/0AAAAAElFTkSuQmCC'); } .m-sgitem, .m-sgitem .sgfl { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; display: flex; } .m-sgitem { padding-left: 10px; } .m-sgitem .sgfr { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; display: flex; position: relative; } .m-sgitem .sgfl { -webkit-box-align: center; -webkit-align-items: center; -ms-flex-align: center; align-items: center; -webkit-box-pack: center; -webkit-justify-content: center; -ms-flex-pack: center; justify-content: center; width: 40px; font-size: 17px; color: #999; margin-left: -10px; } .m-sgitem .sgchfl { padding: 6px 0; width: 0; } .m-sgitem .sgtl { font-size: 17px; } .m-sgitem .sginfo { font-size: 12px; color: #888; } .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; } .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 .sgchfr { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; -webkit-box-align: center; -webkit-align-items: center; -ms-flex-align: center; align-items: center; padding: 0 10px; } .m-sgitem .sgalia { color: #888; margin-left: 4px; } .u-smtitle { height: 23px; line-height: 23px; padding: 0 10px; font-size: 12px; color: #666; background-color: #eeeff0; }
下一章介绍歌曲播放页面(唱片旋转,歌曲进度条,以及歌词滚动等效果)